From 6aac900d98fa793ab3bb5055d47f7e86bee47663 Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 31 Jan 2024 21:41:29 +0100 Subject: [PATCH] More --- .gitignore | 18 + draws/scripts/script.py | 135 + .../scripts/ml/decisiontree.ipynb | 238 + .../scripts/ml/gradientboost.ipynb | 959 ++ .../scripts/ml/mutiple_regression.ipynb | 690 ++ machine_learning/scripts/ml/pca.ipynb | 375 + .../scripts/ml/randomforest.ipynb | 938 ++ .../scripts/ml/simple_models.ipynb | 295 + .../scripts/ml/train_attendance.ipynb | 859 ++ .../scripts/mutiple_regression.ipynb | 690 ++ .../scripts/qubo/generating_qubos.py | 8457 +++++++++++++++++ machine_learning/scripts/qubo/qubo.ipynb | 2483 +++++ machine_learning/scripts/qubo/qubo.py | 312 + .../scripts/qubo/qubo_from_model1_notebook.py | 1672 ++++ .../scripts/qubo/qubo_from_notebook.py | 2205 +++++ .../scripts/qubo/qubo_model2_from_notebook.py | 1936 ++++ machine_learning/scripts/script.py | 122 + qualifiers/scripts/debug.py | 165 + qualifiers/scripts/performance.py | 72 + referees/scripts/script.py | 91 + referees/scripts/script_wales.py | 127 + referees/scripts/seed_wales.py | 323 + stats/scripts/script.py | 234 + uefa/scripts/afc_chl23_seeder.py | 200 + uefa/scripts/django_script.py | 54 + uefa/scripts/ijs_u911_seedgames.py | 174 + uefa/scripts/script copy.py | 214 + uefa/scripts/script.py | 301 + uefa/scripts/solve_uefa24.py | 173 + uefa/scripts/solve_uefa24_create_graph.py | 280 + uefa/scripts/uefa24_conflicts_analyze.py | 705 ++ uefa/scripts/uefa24_debugmodel_1.py | 705 ++ uefa/scripts/uefa24_research.py | 7636 +++++++++++++++ uefa/scripts/uefa24_simulator.py | 208 + uefa/scripts/uefa_ucl24_seeder.py | 220 + uefa/scripts/uefa_uecl24_seeder.py | 190 + uefa/scripts/uefa_uel24_seeder.py | 232 + uefa/scripts/uefa_ueluecl24_seeder.py | 259 + 38 files changed, 34947 insertions(+) create mode 100755 draws/scripts/script.py create mode 100644 machine_learning/scripts/ml/decisiontree.ipynb create mode 100644 machine_learning/scripts/ml/gradientboost.ipynb create mode 100644 machine_learning/scripts/ml/mutiple_regression.ipynb create mode 100644 machine_learning/scripts/ml/pca.ipynb create mode 100644 machine_learning/scripts/ml/randomforest.ipynb create mode 100644 machine_learning/scripts/ml/simple_models.ipynb create mode 100644 machine_learning/scripts/ml/train_attendance.ipynb create mode 100644 machine_learning/scripts/mutiple_regression.ipynb create mode 100755 machine_learning/scripts/qubo/generating_qubos.py create mode 100755 machine_learning/scripts/qubo/qubo.ipynb create mode 100755 machine_learning/scripts/qubo/qubo.py create mode 100755 machine_learning/scripts/qubo/qubo_from_model1_notebook.py create mode 100755 machine_learning/scripts/qubo/qubo_from_notebook.py create mode 100755 machine_learning/scripts/qubo/qubo_model2_from_notebook.py create mode 100755 machine_learning/scripts/script.py create mode 100755 qualifiers/scripts/debug.py create mode 100755 qualifiers/scripts/performance.py create mode 100644 referees/scripts/script.py create mode 100755 referees/scripts/script_wales.py create mode 100755 referees/scripts/seed_wales.py create mode 100644 stats/scripts/script.py create mode 100755 uefa/scripts/afc_chl23_seeder.py create mode 100755 uefa/scripts/django_script.py create mode 100755 uefa/scripts/ijs_u911_seedgames.py create mode 100755 uefa/scripts/script copy.py create mode 100755 uefa/scripts/script.py create mode 100755 uefa/scripts/solve_uefa24.py create mode 100755 uefa/scripts/solve_uefa24_create_graph.py create mode 100755 uefa/scripts/uefa24_conflicts_analyze.py create mode 100755 uefa/scripts/uefa24_debugmodel_1.py create mode 100755 uefa/scripts/uefa24_research.py create mode 100755 uefa/scripts/uefa24_simulator.py create mode 100755 uefa/scripts/uefa_ucl24_seeder.py create mode 100755 uefa/scripts/uefa_uecl24_seeder.py create mode 100755 uefa/scripts/uefa_uel24_seeder.py create mode 100755 uefa/scripts/uefa_ueluecl24_seeder.py diff --git a/.gitignore b/.gitignore index c8e3fd4..09d3782 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,21 @@ solver/data/save_point/* *.lp *.mps *.pptx +*.svg +*.pdf +*.png +*.pulp +*.prt +*.log +*.cmd +*.attr +*.xlsx +*.xls +*.DS_Store +*.txt +*.stats +*.gexf +*.debug +*.sqlite3 +*.ods +*.slx diff --git a/draws/scripts/script.py b/draws/scripts/script.py new file mode 100755 index 0000000..53cf602 --- /dev/null +++ b/draws/scripts/script.py @@ -0,0 +1,135 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws +from draws.models import * + + +import time as timer + + + +# %% + +scenario = Scenario.objects.get(id=9541) +conferences = Conference.objects.filter(scenario=scenario) +supergroups = SuperGroup.objects.filter(draw__season=scenario.season) +allgroups = Group.objects.filter(supergroup__in=supergroups) +sgGroups = { sg.id: [g.id for g in sg.groups.all()] for sg in supergroups } + +# %% + + +for conference in conferences: + conference.teams.clear() + +for sg in supergroups: + draw = sg.draw + season = sg.draw.season + + g_name={ g.id : g.name for g in sg.groups.all() } + scen= False + scen = scenario + + teamObjects= sg.teams.all().values() + t_name = {} + t_pot = {} + t_country = {} + for t in sg.teams.all(): + t_name[t.id] = t.name + t_pot[t.id] = t.pot + t_country[t.id] = t.countryObj.shortname + teams = list (t_country.keys()) + pots=sorted(list(set(t_pot.values()))) + p_teams = { p: [t for t in teams if t_pot[t]==p] for p in pots} + + maxpot = max([ t_pot[t] for t in teams]) + sg_games_against_pot = sg.gamesPerTeam/maxpot + + + c_teams= { g.id: [] for g in allgroups } + for p in pots: + potTeams = p_teams[p] + random.shuffle(potTeams) + for t in potTeams: + allComments="" + candidates= sgGroups[sg.id] + minGroupLoad = min(len(c_teams[c]) for c in candidates) + cands = [c for c in candidates if len(c_teams[c]) == minGroupLoad] + c, r, b, fulldraw, comment, sol = optimize_draws.optimize_draws_New( scen.id, draw.id , t, cands, c_teams, [24, 25, 26, 27]) + allComments+=comment + if p==1 or draw.alwaysPickFirstOpenGroup: + c=[c[0]] + chosen = random.choice(c) + c_teams[chosen].append(t) + allowedString="" + for g in c: + + allowedString+=g_name[g][-1:] + if allComments!="": + allComments=";"+allComments.replace('\n',' ') + team = Team.objects.get(id=t) + conferences.filter(name=g_name[chosen]).first().teams.add(team) + + +# %% diff --git a/machine_learning/scripts/ml/decisiontree.ipynb b/machine_learning/scripts/ml/decisiontree.ipynb new file mode 100644 index 0000000..33e6c9d --- /dev/null +++ b/machine_learning/scripts/ml/decisiontree.ipynb @@ -0,0 +1,238 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import Count, F, Value\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity')\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['time', 'historic_season']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e69d24dc", + "metadata": {}, + "outputs": [], + "source": [ + "#Importing Libraries\n", + "import numpy as np # linear algebra\n", + "import pandas as pd # data processing\n", + "import matplotlib.pyplot as plt # plotting library\n", + "from sklearn.model_selection import train_test_split,cross_val_score, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.tree import DecisionTreeRegressor\n", + "from sklearn.ensemble import RandomForestRegressor" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time', 'historic_season', 'id', 'homeTeam_id', 'awayTeam_id']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "markdown", + "id": "94ade4b4", + "metadata": {}, + "source": [ + "#### Decision Tree" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4c9bdd0d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FITTING...done\n", + "VISUALIZE\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pydotplus\n", + "from six import StringIO\n", + "from sklearn.tree import export_graphviz\n", + "from sklearn.tree import DecisionTreeRegressor \n", + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# Create Decision Tree classifer object\n", + "regr = DecisionTreeRegressor(max_depth=5, random_state=1234)\n", + "\n", + "# Train Decision Tree Classifer\n", + "print(\"FITTING...\", end=\"\")\n", + "regr = regr.fit(X_train, y_train)\n", + "print(\"done\")\n", + "\n", + "# Predict the response for test dataset\n", + "y_pred = regr.predict(X_test)\n", + "\n", + "print(\"VISUALIZE\")\n", + "dot_data = StringIO()\n", + "export_graphviz(regr, out_file=dot_data,\n", + " filled=True, rounded=True,\n", + " special_characters=True, feature_names=feature_cols)\n", + "graph = pydotplus.graph_from_dot_data(dot_data.getvalue())\n", + "graph.write_png('attendance.png')\n", + "# Image(graph.create_png())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/gradientboost.ipynb b/machine_learning/scripts/ml/gradientboost.ipynb new file mode 100644 index 0000000..73673e2 --- /dev/null +++ b/machine_learning/scripts/ml/gradientboost.ipynb @@ -0,0 +1,959 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import F\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).exclude(historic_season=None).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# data cleaning\n", + "df['time'] = df['time'].replace('','0')\n", + "df = df[df['attendance'] != 0]\n", + "\n", + "# remove outliers\n", + "out_fields = ['attendance']\n", + "for field in out_fields:\n", + " q_low = df[field].quantile(0.01)\n", + " q_hi = df[field].quantile(0.99)\n", + " df = df[(df[field] < q_hi) & (df[field] > q_low)]\n", + "\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: r['time'].replace(':','') < \"1800\", axis=1)\n", + "df['before2010'] = df.apply(lambda r: r['historic_season'].split('-')[0] < \"2010\", axis=1)\n", + "\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.ensemble import GradientBoostingRegressor\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country','year']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "data = df[feature_cols+[label]]\n", + "\n", + "\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "45e08026", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random Forest Regression Accuracy: 0.6976274695189291\n" + ] + } + ], + "source": [ + "rf_regressor = GradientBoostingRegressor(n_estimators = 200 , random_state = 42)\n", + "rf_regressor.fit(X_train,y_train)\n", + "\n", + "# #Predicting the SalePrices using test set \n", + "y_pred_rf = rf_regressor.predict(X_test)\n", + "\n", + "# #Random Forest Regression Accuracy with test set\n", + "print('Random Forest Regression Accuracy: ', rf_regressor.score(X_test,y_test))\n", + "\n", + "# #Predicting the SalePrice using cross validation (KFold method)\n", + "# y_pred_rf = cross_val_predict(rf_regressor, X, y, cv=10 )\n", + "\n", + "# #Random Forest Regression Accuracy with cross validation\n", + "# accuracy_rf = metrics.r2_score(y, y_pred_rf)\n", + "# print('Cross-Predicted(KFold) Random Forest Regression Accuracy: ', accuracy_rf)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0de49b8a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ranking = np.argsort(-rf_regressor.feature_importances_)\n", + "f, ax = plt.subplots(figsize=(15, 10))\n", + "sns.barplot(x=rf_regressor.feature_importances_[ranking], y=X_train.columns.values[ranking], orient='h')\n", + "ax.set_xlabel(\"feature importance\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4c1f8b45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 4000 6269.331273160804\n", + "1 3264 2212.472559220525\n", + "2 6000 4474.321544046366\n", + "3 4250 5853.876279843164\n", + "4 1200 2724.8479999971523\n", + "5 4300 12248.194405029995\n", + "6 3874 12179.504105471333\n", + "7 2800 7179.957816540268\n", + "8 5500 4343.454242132351\n", + "9 6000 15615.670772432313\n", + "10 3500 2142.013025198476\n", + "11 4500 6581.086576491373\n", + "12 2140 2787.000793225451\n", + "13 3146 7948.163004948026\n", + "14 2600 5277.360995538767\n", + "15 4875 4182.090794311384\n", + "16 5807 1298.0546590068432\n", + "17 5200 7641.152367897079\n", + "18 3500 12709.91385314257\n", + "19 2643 13796.44798966877\n", + "20 4000 2910.0979973906897\n", + "21 2500 11367.203439146624\n", + "22 2000 4578.676165761646\n", + "23 3198 11801.197736843704\n", + "24 3571 3256.2683962230926\n", + "25 2712 13963.658569196019\n", + "26 2100 3428.2076365623343\n", + "27 4525 3179.082309242862\n", + "28 6625 5319.700728374097\n", + "29 4966 4814.201898718184\n", + "30 2000 11668.774282716573\n", + "31 2100 16459.759735440115\n", + "32 2310 10424.049272604385\n", + "33 2600 3249.7584854521983\n", + "34 2000 4933.417332760704\n", + "35 4300 11330.47572253357\n", + "36 2734 4916.62667788047\n", + "37 3500 7616.108008546191\n", + "38 3050 5834.707115002423\n", + "39 5256 10956.873320747929\n", + "40 3012 1746.8793669215172\n", + "41 5060 4642.521259437015\n", + "42 1500 10636.260356819768\n", + "43 4000 6226.573175422698\n", + "44 1950 6253.5032474003465\n", + "45 2300 5174.122252417559\n", + "46 2300 2724.3318310328705\n", + "47 1950 9804.743199526134\n", + "48 3058 3874.7670154643415\n", + "49 2000 10001.45516441721\n", + "50 2345 5023.8562523965875\n", + "51 5500 5174.122252417559\n", + "52 5585 3817.7336514404283\n", + "53 4046 4720.395073786049\n", + "54 6000 4569.1440149122045\n", + "55 4384 4918.984377964906\n", + "56 3000 4674.700091188421\n", + "57 3500 3544.0179791758733\n", + "58 7000 18261.80159810542\n", + "59 10195 13504.402960006135\n", + "60 3476 2439.2710475675904\n", + "61 2120 6683.8055866507775\n", + "62 3800 4831.40407430577\n", + "63 2675 18297.8862272201\n", + "64 6603 5900.839366216985\n", + "65 1500 5547.896256687706\n", + "66 4000 15716.448413829125\n", + "67 4545 3510.458423897759\n", + "68 1396 4299.864451241506\n", + "69 5400 11340.993418688882\n", + "70 5041 13344.492446272585\n", + "71 1500 12842.717635745401\n", + "72 1800 2826.6198584357285\n", + "73 3189 2216.6069457661515\n", + "74 3800 7551.124847543301\n", + "75 2178 10854.693947136366\n", + "76 6169 3754.054098366518\n", + "77 2676 6227.444878272318\n", + "78 3510 4372.710484500164\n", + "79 3900 5899.365613773657\n", + "80 2120 9146.338397438585\n", + "81 8000 6598.214034291334\n", + "82 2500 4180.2412129479535\n", + "83 4016 1013.1917302016966\n", + "84 1500 13356.059667395755\n", + "85 2860 3859.7092616602254\n", + "86 2150 12546.493369090958\n", + "87 3336 13781.48736284787\n", + "88 8820 6867.843525597063\n", + "89 4792 9977.417685682762\n", + "90 1650 23425.898130663616\n", + "91 2000 6589.051500702451\n", + "92 2111 3109.4945264556886\n", + "93 4470 13408.035401832343\n", + "94 7000 5519.4148042321\n", + "95 3850 6089.400396461468\n", + "96 4366 9610.498715489919\n", + "97 2645 8316.017866765993\n", + "98 2384 6197.035790504067\n", + "99 4522 12779.77200503699\n", + "100 6328 14744.016207170152\n", + "101 3877 19458.90971123009\n", + "102 2000 4874.224861034961\n", + "103 4157 5208.703053494322\n", + "104 2942 13386.059288545708\n", + "105 3655 13257.062956113012\n", + "106 4500 2441.3256508749882\n", + "107 2964 2416.608601291409\n", + "108 2863 2553.749826440011\n", + "109 4935 11198.828086622805\n", + "110 2526 12041.524132520446\n", + "111 5679 8045.757009722594\n", + "112 7286 13009.125855582837\n", + "113 6055 12448.19779590953\n", + "114 1200 11188.064346395086\n", + "115 4110 4271.21931546039\n", + "116 1957 3087.6529257626803\n", + "117 1790 2569.376378289409\n", + "118 5422 6173.099807850697\n", + "119 4650 12505.12258551472\n", + "120 5297 12652.949405216323\n", + "121 3036 6268.840108714523\n", + "122 3233 9367.596675826102\n", + "123 12000 2337.3771641354024\n", + "124 7632 1819.0748787672667\n", + "125 3620 7428.132912505317\n", + "126 2000 2124.849872483271\n", + "127 2145 6073.702450147759\n", + "128 2227 15691.707999141212\n", + "129 2520 17731.752052395845\n", + "130 10000 9224.693549735079\n", + "131 2000 4333.254162853479\n", + "132 4052 5463.585852647499\n", + "133 2137 3385.7917346498257\n", + "134 2609 8949.299267841006\n", + "135 3256 5594.136151838447\n", + "136 8173 7321.006115614261\n", + "137 1250 6314.334305949203\n", + "138 7401 1841.6632769675457\n", + "139 4200 5913.92021057092\n", + "140 6100 5914.498891029855\n", + "141 5182 4485.159036791194\n", + "142 1300 3930.555183694012\n", + "143 2117 2087.6087105330876\n", + "144 6500 3129.218824655229\n", + "145 5174 19100.66955411291\n", + "146 3867 7381.962946265084\n", + "147 1918 3668.522599331723\n", + "148 2800 5984.122720695453\n", + "149 7648 5579.807332727605\n", + "150 5638 5000.766880937242\n", + "151 5262 8796.640913054198\n", + "152 1650 11038.130604200423\n", + "153 1657 3053.4760351098107\n", + "154 4086 16594.785800693066\n", + "155 9000 3561.181162895718\n", + "156 1500 5042.543289014211\n", + "157 8145 12723.53945595642\n", + "158 2625 7117.118765444254\n", + "159 6281 3092.870875123699\n", + "160 4520 1916.9886938042089\n", + "161 1200 3334.4719289950585\n", + "162 4829 4248.693921085489\n", + "163 1760 3065.4160555916155\n", + "164 3469 10943.4771813175\n", + "165 7500 4646.274655834034\n", + "166 5227 7247.410995462059\n", + "167 1765 12061.617431570865\n", + "168 5200 3906.7362684340383\n", + "169 6402 5802.18818098374\n", + "170 4832 3735.241252929307\n", + "171 1500 8821.101907961975\n", + "172 2342 3612.477133461679\n", + "173 2799 908.2647209562191\n", + "174 3850 3078.4680731926246\n", + "175 4200 3812.544744635514\n", + "176 4531 2705.9998772537424\n", + "177 1751 1967.6894728073173\n", + "178 4250 5924.714538352343\n", + "179 5705 6513.58136727248\n", + "180 3528 1966.268176067289\n", + "181 2496 4368.04545595507\n", + "182 4370 2024.6278361643576\n", + "183 1350 5634.107750848085\n", + "184 5334 10956.873320747929\n", + "185 1423 6752.977449121771\n", + "186 4129 4490.334901030411\n", + "187 5858 2706.770656503444\n", + "188 3300 4860.877678091679\n", + "189 3500 3893.6704796446884\n", + "190 10280 3778.999270841025\n", + "191 4500 7722.956089373864\n", + "192 10500 3823.8621517621054\n", + "193 3932 5270.396448543778\n", + "194 5500 7707.251425986663\n", + "195 2200 14389.91484985415\n", + "196 8206 6044.3459440998595\n", + "197 8000 4457.480201516091\n", + "198 6372 8890.061129175148\n", + "199 7900 13621.063242184871\n", + "200 1628 13169.821966149577\n", + "201 4142 3606.329399178841\n", + "202 1150 6794.3761007737885\n", + "203 1750 3527.0690712828796\n", + "204 4072 4870.13044656876\n", + "205 1176 14005.1555660555\n", + "206 3200 5021.013261618377\n", + "207 1715 10318.676591702464\n", + "208 7530 10459.245297486614\n", + "209 4600 8316.395535933456\n", + "210 1200 5095.800766098369\n", + "211 4452 17619.45148176299\n", + "212 2400 2917.6212527362745\n", + "213 4057 3523.9765596992324\n", + "214 5000 7568.184421468349\n", + "215 4147 2328.238657453435\n", + "216 3046 9816.145570783112\n", + "217 6215 2815.1845153039812\n", + "218 3350 2458.220350724582\n", + "219 3500 1143.4289915252082\n", + "220 5870 3349.522734018162\n", + "221 4113 6562.149215912538\n", + "222 3420 15899.046839388688\n", + "223 6116 9045.668754767581\n", + "224 5902 2748.9553238280632\n", + "225 3787 4271.853869225162\n", + "226 3600 7439.183383616434\n", + "227 7017 2210.108006140514\n", + "228 10280 4927.066871405887\n", + "229 5600 9564.813393139277\n", + "230 3656 3329.8106332184043\n", + "231 6480 17110.713931585673\n", + "232 1646 4677.074303376587\n", + "233 2600 4440.882887387751\n", + "234 4300 2118.7542046100843\n", + "235 7948 3214.332854949672\n", + "236 4579 7753.321604795282\n", + "237 4364 6460.580153817578\n", + "238 5000 3400.25051693908\n", + "239 2034 4284.09527513928\n", + "240 3500 5231.567751256019\n", + "241 5688 6856.195658240203\n", + "242 1800 12184.56551356091\n", + "243 10131 2598.8215825730167\n", + "244 5784 5943.724350980219\n", + "245 1813 4361.9722090698815\n", + "246 3700 5182.000108363626\n", + "247 6700 5850.0908748940765\n", + "248 3700 2399.8908348974273\n", + "249 7799 1934.7516235634123\n", + "250 1884 11765.60702403286\n", + "251 3042 4656.923175922518\n", + "252 5000 13415.8010505132\n", + "253 5112 13024.815014128593\n", + "254 1404 3154.9780459262442\n", + "255 2471 4309.257915103401\n", + "256 7749 10739.861964061618\n", + "257 6254 3516.7357048629206\n", + "258 2502 11979.741281816012\n", + "259 2300 3650.093842398789\n", + "260 6500 11606.67581375423\n", + "261 2646 2003.5526871642137\n", + "262 9546 3357.754620510872\n", + "263 7500 1954.3537407404217\n", + "264 11016 5754.557478633303\n", + "265 5763 9554.299673198937\n", + "266 2460 9835.688834354334\n", + "267 5511 3086.8504515496356\n", + "268 1857 12616.742317378286\n", + "269 7000 3003.607544650991\n", + "270 6333 2916.8409179975624\n", + "271 6107 4089.944537354285\n", + "272 1518 2032.35131917741\n", + "273 9310 13077.88651001709\n", + "274 3551 5538.163168934835\n", + "275 1700 5176.458388224016\n", + "276 2250 6964.458867728808\n", + "277 6000 3658.89357449004\n", + "278 2003 12082.974153438317\n", + "279 15183 3440.5699078224566\n", + "280 7113 3704.6762193220106\n", + "281 3818 7853.080965569755\n", + "282 12300 4595.931196930642\n", + "283 12488 4204.879371769705\n", + "284 8000 14443.56582836753\n", + "285 10832 6863.813679372925\n", + "286 2107 6377.807460294606\n", + "287 2100 11228.724002036284\n", + "288 14135 4931.900522781224\n", + "289 6115 -1359.8525015661405\n", + "290 9364 7649.975494379696\n", + "291 4773 9481.483722041376\n", + "292 3525 3903.097436115244\n", + "293 6126 3235.0879045787524\n", + "294 6487 6016.561185150087\n", + "295 3879 5963.8817259278985\n", + "296 4943 2022.7875651402155\n", + "297 1335 16308.04344887568\n", + "298 4125 3686.2401457869464\n", + "299 7986 19570.539544136012\n", + "300 5000 1647.3199417777416\n", + "301 3559 10844.540513558119\n", + "302 6573 6749.5080657275785\n", + "303 2300 7761.8563105694675\n", + "304 5117 6850.569455542703\n", + "305 5000 6749.5080657275785\n", + "306 7165 7562.513169172154\n", + "307 1406 4792.945224334402\n", + "308 12300 5438.795622303128\n", + "309 3573 4076.6328454195104\n", + "310 6500 6484.405267932639\n", + "311 4508 4454.794170262632\n", + "312 7546 3612.3231311814357\n", + "313 5413 6996.1341583386675\n", + "314 5754 3103.9097707577357\n", + "315 1307 3807.778748518246\n", + "316 5433 3244.8492954237204\n", + "317 2304 3555.7092877099694\n", + "318 4000 15777.67435290044\n", + "319 6425 7309.990757727653\n", + "320 7250 11630.888375951426\n", + "321 5500 8636.615133701885\n", + "322 1800 8768.014881915431\n", + "323 2240 4826.610795633974\n", + "324 9000 10890.956739194247\n", + "325 1266 5814.283121498163\n", + "326 3850 6443.817549805847\n", + "327 2122 4535.813236908496\n", + "328 6423 4517.133895929519\n", + "329 6455 12060.20986179786\n", + "330 2100 9687.757282298406\n", + "331 7843 5535.984701360246\n", + "332 9617 3697.898412456338\n", + "333 5033 13417.324343888626\n", + "334 1129 1900.0831637980234\n", + "335 1500 9359.137469515703\n", + "336 8932 4625.923230167911\n", + "337 4637 6298.491882691631\n", + "338 15327 8789.796058286662\n", + "339 1233 3349.3518456663473\n", + "340 2364 5489.380456222845\n", + "341 10316 10482.794651236341\n", + "342 13200 816.4886171815257\n", + "343 1303 2434.5700084840264\n", + "344 8687 1339.7548137515469\n", + "345 1653 14174.38517292645\n", + "346 7067 4769.726084812967\n", + "347 8265 6296.217972893376\n", + "348 1587 3735.0734883030373\n", + "349 2479 9172.144059825323\n", + "350 6366 2943.565074257849\n", + "351 5114 4607.856876244279\n", + "352 6138 6525.133501060625\n", + "353 1765 5306.257077971849\n", + "354 3129 1846.9373281342548\n", + "355 2295 3398.94891754905\n", + "356 5507 6787.25326071226\n", + "357 5200 11426.371872238831\n", + "358 6326 13253.411199790291\n", + "359 10804 12321.66926933166\n", + "360 6721 7784.752069975153\n", + "361 5574 3078.4680731926246\n", + "362 10020 5279.60487971954\n", + "363 3678 3703.664495952156\n", + "364 4342 6008.626463397685\n", + "365 8000 12218.65449210225\n", + "366 1687 4676.4051865459105\n", + "367 1967 6462.389387331538\n", + "368 27252 4596.524835292175\n", + "369 20520 10707.201673752295\n", + "370 10000 16951.68427825804\n", + "371 1661 5088.693439115733\n", + "372 1356 4243.341369783109\n", + "373 8000 13092.177864088904\n", + "374 6288 7390.894960432231\n", + "375 9979 13110.150275789669\n", + "376 3083 9102.1199952011\n", + "377 1574 12359.06343032517\n", + "378 10452 3163.5893828816875\n", + "379 4790 4611.20167092646\n", + "380 5563 4506.779900903538\n", + "381 1103 5630.433669946042\n", + "382 3846 14705.914690376329\n", + "383 3750 1678.928854632361\n", + "384 4309 4188.9078922720755\n", + "385 6254 2185.786798925416\n", + "386 2133 5240.602438128821\n", + "387 12800 6440.899480264031\n", + "388 5300 2202.973850976274\n", + "389 10102 3349.487579181281\n", + "390 9326 6276.255533517775\n", + "391 2613 10027.074674303978\n", + "392 11976 3863.6273789168304\n", + "393 12143 3393.520429895661\n", + "394 13200 3769.0173149934762\n", + "395 6320 4363.984786293422\n", + "396 1542 12775.267839770944\n", + "397 4560 4771.697760449484\n", + "398 8304 4637.7607774767075\n", + "399 10480 4525.181055134041\n", + "400 5352 6595.799090340065\n", + "401 2137 6969.014509412282\n", + "402 5169 4750.975859023578\n", + "403 2799 11080.256944317674\n", + "404 6000 2003.8289254279437\n", + "405 4986 4197.359917287667\n", + "406 1824 4332.950723087893\n", + "407 1562 5099.093054122171\n", + "408 5890 12081.878696518148\n", + "409 6077 9270.461413927558\n", + "410 1485 1496.4321088051206\n", + "411 1825 4246.200510854761\n", + "412 5340 3255.3624000842133\n", + "413 9237 1859.449426524448\n", + "414 6499 7976.974080873097\n", + "415 1240 4436.075476859059\n", + "416 4656 3617.4469653780425\n", + "417 2335 4997.427948460792\n", + "418 8000 8685.398728410411\n", + "419 5641 8353.951860004401\n", + "420 1444 8676.625836361147\n", + "421 12900 2321.255461480635\n", + "422 6500 4629.528409764403\n", + "423 7506 13467.919271136741\n", + "424 6438 7930.93744973132\n", + "425 2261 13370.226646925103\n", + "426 2121 6247.557132112603\n", + "427 5437 3592.393531883706\n", + "428 1536 3084.700451088983\n", + "429 9295 11590.303689144444\n", + "430 3252 6586.4530811986915\n", + "431 1331 2974.1520911988323\n", + "432 5442 10642.588370514337\n", + "433 4527 4146.7680464203095\n", + "434 6500 5535.980475001773\n", + "435 1238 4462.334896687268\n", + "436 10702 3998.7386180649096\n", + "437 8056 6374.403929120289\n", + "438 4517 3968.6353947354496\n", + "439 5108 11261.565350761522\n", + "440 6354 8418.830560829478\n", + "441 4322 3305.0788833275337\n", + "442 3129 5121.086170529202\n", + "443 2486 17408.009128939066\n", + "444 6200 4741.563419125667\n", + "445 10320 5313.293980930455\n", + "446 5204 11883.544862171222\n", + "447 7429 11135.61940509253\n", + "448 1837 5034.717550057033\n", + "449 3311 5410.885965608366\n", + "450 5425 4536.934131183477\n", + "451 1141 9035.881634012976\n", + "452 8142 3094.3729707170237\n", + "453 9630 5759.575130507457\n", + "454 3400 2189.066685634728\n", + "455 5991 8549.539484234854\n", + "456 4537 2673.967338892564\n", + "457 1389 4718.1351657552605\n", + "458 6560 12005.006870572877\n", + "459 5417 8357.181906814383\n", + "460 1326 11175.583517484314\n", + "461 1226 11704.844017633459\n", + "462 9439 7822.772984507\n", + "463 6075 3640.18245452465\n", + "464 4139 12954.348380007397\n", + "465 6921 4400.219099707537\n", + "466 1412 3860.7463982703334\n", + "467 1580 3516.678313124346\n", + "468 6480 3492.9019039587733\n", + "469 7740 8106.980110824186\n", + "470 9187 2395.5573418076624\n", + "471 5923 15299.331508342839\n", + "472 1690 10362.006379124352\n", + "473 1829 9487.82923378465\n", + "474 13132 12486.558265969294\n", + "475 5673 14305.952439617615\n", + "476 10143 14864.146909677464\n", + "477 2631 2331.8590852138564\n", + "478 16753 3373.2896869040014\n", + "479 8300 6524.596616257537\n", + "480 2541 9190.959336462798\n", + "481 1638 8198.234066736764\n", + "482 6097 15793.35353841358\n", + "483 8250 10749.527129598226\n", + "484 1638 2035.970947988072\n", + "485 1145 4696.342706754138\n", + "486 8300 4612.694230043811\n", + "487 9750 12698.282535481198\n", + "488 12532 16313.121000032259\n", + "489 10739 4867.058831371186\n", + "490 18230 8459.589175229492\n", + "491 6125 9447.347582186656\n", + "492 6225 4050.031770291008\n", + "493 16509 3119.674368698192\n", + "494 6782 15229.554761308495\n", + "495 6125 6761.654267834814\n", + "496 1681 16165.79694411498\n", + "497 1798 5819.205951991664\n", + "498 13385 4938.678542279483\n", + "499 12300 12501.32563965754\n", + "500 4100 6001.137996121012\n", + "501 6190 1475.6160798039427\n", + "502 9246 3958.254475739062\n", + "503 14322 5117.760831637467\n", + "504 7396 3351.115059928001\n", + "505 3851 11349.992398228365\n", + "506 4734 2254.841749289375\n", + "507 2058 4497.8123659139665\n", + "508 8869 6254.021135129916\n", + "509 11269 5503.560118534005\n", + "510 2506 3923.4245548541553\n", + "511 11730 13493.388189645208\n", + "512 8045 11793.907143350536\n", + "513 7500 19150.25573472374\n", + "514 9166 1503.1775986772748\n", + "515 5368 768.7325537958045\n", + "516 2395 11349.634056006476\n", + "517 9087 6323.225289121863\n", + "518 7407 12191.765888863798\n", + "519 5949 4662.532235685591\n", + "520 10216 1407.9870443357727\n", + "521 4731 2077.9540157496162\n", + "522 9248 7446.569623693983\n", + "523 18500 7152.090683775011\n", + "524 6308 2046.8540380357522\n", + "525 5748 7367.288137184615\n", + "526 3138 13384.130363257449\n", + "527 2012 10653.209976527616\n", + "528 8657 4045.4916586458935\n", + "529 7500 5925.985098665058\n", + "530 1463 16696.570686355375\n", + "531 7625 8283.901419829008\n", + "532 17260 1332.645261264805\n", + "533 7020 8097.968034350275\n", + "534 6592 12655.393492203057\n", + "535 1463 9686.161910222057\n", + "536 5112 14887.791991132963\n", + "537 9672 11980.464991642053\n", + "538 5360 3200.7882786135533\n", + "539 7338 4568.898795584218\n", + "540 4113 4548.0218263260895\n", + "541 5443 6464.617670339604\n", + "542 7368 3509.1119407497345\n", + "543 8017 14989.39181389453\n", + "544 8619 5951.016907958763\n", + "545 2651 2389.4447101228966\n", + "546 14840 5539.6976673052395\n", + "547 6041 5739.887811430656\n", + "548 8685 4713.845075975436\n", + "549 1252 10368.07303045706\n", + "550 2655 6283.479460803347\n", + "551 15140 10087.168600437875\n", + "552 7885 5075.27707615952\n", + "553 8685 8212.189918949982\n", + "554 7542 5046.9757285848955\n", + "555 4676 12393.032671740262\n", + "556 2450 4089.113201090722\n", + "557 7225 8668.848872884808\n", + "558 18500 3625.579521584379\n", + "559 8499 11175.583517484314\n", + "560 5057 11954.107603250402\n", + "561 8418 9871.105936883523\n", + "562 22885 4480.13584030295\n", + "563 2820 12949.498758756125\n", + "564 1868 9863.602442864936\n", + "565 2523 7594.763347975478\n", + "566 10058 4106.730423429261\n", + "567 7138 6882.786147983847\n", + "568 7610 8398.894749936191\n", + "569 2670 3175.8554412316626\n", + "570 2364 12814.987663022268\n", + "571 8435 5162.810338028615\n", + "572 8841 1036.8156262763082\n", + "573 15240 6022.001956780606\n", + "574 10180 10816.936615359207\n", + "575 12534 6207.885794866743\n", + "576 20520 4784.208803197017\n", + "577 13500 2214.8851307287455\n", + "578 5000 3893.0598787794556\n", + "579 12813 6018.340623795528\n", + "580 7050 4794.636511757451\n", + "581 6665 719.7944431444473\n", + "582 16350 12795.036594803416\n", + "583 25623 14184.768449790256\n", + "584 2063 9810.563762098913\n", + "585 3393 4663.9954601549625\n", + "586 2217 8773.41625094894\n", + "587 9003 3036.44495239287\n", + "588 14470 14674.536778589223\n", + "589 7603 7067.956282785245\n", + "590 8685 4819.903639940648\n", + "591 6436 3119.275984646008\n", + "592 6112 13083.263275048763\n", + "593 6127 4357.32710824466\n", + "594 1373 15735.44034049763\n", + "595 8046 9829.34768547027\n", + "596 6865 3214.003054487506\n", + "597 8286 5601.510414450099\n", + "598 6302 4623.138939835301\n", + "599 2208 11173.469568286891\n", + "600 3615 9099.567629826535\n", + "601 15940 1128.3453286817544\n", + "602 5010 12327.197143168583\n", + "603 8212 2358.011077505077\n", + "604 1272 3507.632555908359\n", + "605 2540 3225.323372800012\n", + "606 9600 4090.038562587634\n", + "607 26043 9172.521869455777\n", + "608 6103 9608.33642905634\n", + "609 2747 3357.020239766667\n", + "610 2960 6086.4085631268035\n", + "611 26043 7352.588636862007\n", + "612 11444 5749.178712087224\n", + "613 6608 7009.789042663147\n", + "614 8685 4860.877678091679\n", + "615 7809 2574.283478221735\n", + "616 10910 16177.070094482096\n", + "617 6015 7330.670097266955\n", + "618 5233 5874.080532458615\n", + "619 1425 13376.351664670756\n", + "620 11160 11489.591677776654\n", + "621 2105 8093.267208688064\n", + "622 7428 12065.410201066788\n", + "623 5204 4090.2545827148147\n", + "624 1851 4009.0006330108404\n", + "625 26043 3438.1572509050984\n", + "626 5426 2253.846596804711\n", + "627 6219 17137.025597423733\n", + "628 11212 8852.762121006672\n", + "629 8124 2764.2110979500026\n", + "630 1982 16415.374793629482\n", + "631 3694 8244.824334909112\n", + "632 6075 2457.9006791839233\n", + "633 4561 2321.330005122368\n", + "634 3042 4222.503424740425\n", + "635 19747 8223.799821601031\n", + "636 15145 1871.3160227674202\n", + "637 7072 4620.709006674663\n", + "638 2582 2230.203623949988\n", + "639 1425 5754.610018189932\n", + "640 5219 18795.832958315765\n", + "641 7182 2545.237704767577\n", + "642 8899 4793.967670294426\n", + "643 6313 3355.7302621566423\n", + "644 2435 1977.784883709689\n", + "645 3108 5286.771821824771\n", + "646 12198 6863.347568508702\n", + "647 5761 3753.5604597260553\n", + "648 8685 3501.396202209124\n", + "649 8141 6710.759936928313\n", + "650 9185 6449.711014158032\n", + "651 5331 6111.495678866401\n" + ] + }, + { + "ename": "IndexError", + "evalue": "index 652 is out of bounds for axis 0 with size 652", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_56157/1621740581.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my_pred_rf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m: index 652 is out of bounds for axis 0 with size 652" + ] + } + ], + "source": [ + "for i,v in enumerate(y):\n", + " print(i,v,y_pred_rf[i])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bba1ad86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2171" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "970a3733", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/mutiple_regression.ipynb b/machine_learning/scripts/ml/mutiple_regression.ipynb new file mode 100644 index 0000000..cd3dfc0 --- /dev/null +++ b/machine_learning/scripts/ml/mutiple_regression.ipynb @@ -0,0 +1,690 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import F\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# data cleaning\n", + "df['time'] = df['time'].replace('','0')\n", + "df = df[df['attendance'] != 0]\n", + "\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: r['time'].replace(':','') < \"1800\", axis=1)\n", + "df['before2010'] = df.apply(lambda r: r['historic_season'].split('-')[0] < \"2010\", axis=1)\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'homeTeam_id', 'awayTeam_id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3a05ac61", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "from pandas import DataFrame,Series\n", + "from sklearn import tree\n", + "import matplotlib\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn import svm\n", + "from sklearn.preprocessing import StandardScaler\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "import seaborn as sns\n", + "from sklearn import neighbors\n", + "from sklearn import linear_model" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "76e774e8", + "metadata": {}, + "outputs": [], + "source": [ + "X_Train=X_train.values\n", + "X_Train=np.asarray(X_Train)\n", + "\n", + "# Finding normalised array of X_Train\n", + "X_std=StandardScaler().fit_transform(X_Train)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "45e08026", + "metadata": {}, + "outputs": [], + "source": [ + "number_of_samples = len(y_train)\n", + "np.random.seed(0)\n", + "random_indices = np.random.permutation(number_of_samples)\n", + "num_training_samples = int(number_of_samples*0.75)\n", + "x_train = X_Train[random_indices[:num_training_samples]]\n", + "y_train=y[random_indices[:num_training_samples]]\n", + "x_test=X_Train[random_indices[num_training_samples:]]\n", + "y_test=y[random_indices[num_training_samples:]]\n", + "y_Train=list(y_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "470425b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.11885891315514 percent in Ridge Regression\n", + "Test error = 100.86230814227264 percent in Ridge Regression\n" + ] + } + ], + "source": [ + "model=linear_model.Ridge()\n", + "model.fit(x_train,y_train)\n", + "y_predict=model.predict(x_train)\n", + "\n", + "error=0\n", + "for i in range(len(y_Train)):\n", + " error+=(abs(y_Train[i]-y_predict[i])/y_Train[i])\n", + "train_error_ridge=error/len(y_Train)*100\n", + "print(\"Train error = \"'{}'.format(train_error_ridge)+\" percent in Ridge Regression\")\n", + "\n", + "Y_test=model.predict(x_test)\n", + "y_Predict=list(y_test)\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y_Predict[i]-Y_test[i])/y_Predict[i])\n", + "test_error_ridge=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_ridge)+\" percent in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "88b4de2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Ridge Regression')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "\n", + "preds = pd.DataFrame({\"preds\":model.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f2f52314", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 86.04732435905834 percent in Knn algorithm\n", + "Test error = 29.305494469835775 percent in knn algorithm\n" + ] + } + ], + "source": [ + "n_neighbors=5\n", + "knn=neighbors.KNeighborsRegressor(n_neighbors,weights='uniform')\n", + "knn.fit(x_train,y_train)\n", + "y1_knn=knn.predict(x_train)\n", + "y1_knn=list(y1_knn)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_knn[i]-y_Train[i])/y_Train[i])\n", + "train_error_knn=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_knn)+\" percent\"+\" in Knn algorithm\")\n", + "\n", + "y2_knn=knn.predict(x_test)\n", + "y2_knn=list(y2_knn)\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_knn[i]-Y_test[i])/Y_test[i])\n", + "test_error_knn=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_knn)+\" percent\"+\" in knn algorithm\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "06ed0796", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Knn')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":knn.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Knn\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "27215d99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.41770536088067 percent in Bayesian Regression\n", + "Test error = 6.2704745296132876 percent in Bayesian Regression\n" + ] + } + ], + "source": [ + "reg = linear_model.BayesianRidge()\n", + "reg.fit(x_train,y_train)\n", + "y1_reg=reg.predict(x_train)\n", + "y1_reg=list(y1_reg)\n", + "y2_reg=reg.predict(x_test)\n", + "y2_reg=list(y2_reg)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_reg[i]-y_Train[i])/y_Train[i])\n", + "train_error_bay=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_bay)+\" percent\"+\" in Bayesian Regression\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_reg[i]-Y_test[i])/Y_test[i])\n", + "test_error_bay=(error/len(Y_test))*100\n", + "print(\"Test error = \"+'{}'.format(test_error_bay)+\" percent\"+\" in Bayesian Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c68c7026", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Bayesian Regression')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":reg.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Bayesian Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a3c76976", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.15574267963966 percent in Decision Tree Regressor\n", + "Test error = 7.1677452486549855 percent in Decision Tree Regressor\n" + ] + } + ], + "source": [ + "dec = tree.DecisionTreeRegressor(max_depth=1)\n", + "dec.fit(x_train,y_train)\n", + "y1_dec=dec.predict(x_train)\n", + "y1_dec=list(y1_dec)\n", + "y2_dec=dec.predict(x_test)\n", + "y2_dec=list(y2_dec)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_dec[i]-y_Train[i])/y_Train[i])\n", + "train_error_tree=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_tree)+\" percent\"+\" in Decision Tree Regressor\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y1_dec[i]-Y_test[i])/Y_test[i])\n", + "test_error_tree=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_tree)+\" percent in Decision Tree Regressor\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "52ec5460", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Decision Tree')" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":dec.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Decision Tree\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f2b28bd7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 73.1224560206315 percent in SVM Regressor\n", + "Test error = 21.522381238558573 percent in SVM Regressor\n" + ] + } + ], + "source": [ + "svm_reg=svm.SVR()\n", + "svm_reg.fit(x_train,y_train)\n", + "y1_svm=svm_reg.predict(x_train)\n", + "y1_svm=list(y1_svm)\n", + "y2_svm=svm_reg.predict(x_test)\n", + "y2_svm=list(y2_svm)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_svm[i]-y_Train[i])/y_Train[i])\n", + "train_error_svm=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_svm)+\" percent\"+\" in SVM Regressor\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_svm[i]-Y_test[i])/Y_test[i])\n", + "test_error_svm=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_svm)+\" percent in SVM Regressor\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "71819737", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in SVM')" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":knn.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in SVM\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ef357c92", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Train ErrorTest Error
Ridge Regression98.118859100.862308
Knn86.04732429.305494
Bayesian Regression98.4177056.270475
Decision Tree98.1557437.167745
SVM73.12245621.522381
\n", + "
" + ], + "text/plain": [ + " Train Error Test Error\n", + "Ridge Regression 98.118859 100.862308\n", + "Knn 86.047324 29.305494\n", + "Bayesian Regression 98.417705 6.270475\n", + "Decision Tree 98.155743 7.167745\n", + "SVM 73.122456 21.522381" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_error=[train_error_ridge,train_error_knn,train_error_bay,train_error_tree,train_error_svm]\n", + "test_error=[test_error_ridge,test_error_knn,test_error_bay,test_error_tree,test_error_svm]\n", + "\n", + "col={'Train Error':train_error,'Test Error':test_error}\n", + "models=['Ridge Regression','Knn','Bayesian Regression','Decision Tree','SVM']\n", + "df=DataFrame(data=col,index=models)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fbe3ec2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/pca.ipynb b/machine_learning/scripts/ml/pca.ipynb new file mode 100644 index 0000000..8da39b4 --- /dev/null +++ b/machine_learning/scripts/ml/pca.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import Count, F, Value\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: int(r['time'].replace(':','') < \"1800\"), axis=1)\n", + "df['before2010'] = df.apply(lambda r: int(r['historic_season'].split('-')[0] < \"2010\"), axis=1)\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1383108f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":6: RuntimeWarning: Converting input from bool to for compatibility.\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "df.hist(figsize = (20,20))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'homeTeam_id', 'awayTeam_id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "markdown", + "id": "1899ba5a", + "metadata": {}, + "source": [ + "#### Correlation Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "738f39ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "# GETTING Correllation matrix\n", + "corr_mat=X_train.corr(method='pearson')\n", + "plt.figure(figsize=(20,10))\n", + "sns.heatmap(corr_mat,vmax=1,square=True,annot=True,cmap='cubehelix')" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "38f78b1c", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "X_Train=X_train.values\n", + "X_Train=np.asarray(X_Train)\n", + "\n", + "# Finding normalised array of X_Train\n", + "X_std=StandardScaler().fit_transform(X_Train)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "ab28ec86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Cumulative explained variance')" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.decomposition import PCA\n", + "pca = PCA().fit(X_std)\n", + "plt.plot(np.cumsum(pca.explained_variance_ratio_))\n", + "plt.xlim(0,7,1)\n", + "plt.xlabel('Number of components')\n", + "plt.ylabel('Cumulative explained variance')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "b40d92f1", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n" + ] + }, + { + "data": { + "text/plain": [ + "(-2.0, 2.0)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAf4AAAHYCAYAAACsmooOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABfQklEQVR4nO3dd5xU1d0/8M/M7GyZreyyDViQIh07IBpREQQJuJBHLBiNDTW2xCf+gonG7mOIJsaoidEoxvooeWwUAbGi2CtFpRfZyvZeZub3x7p9yr1zz7333Hs/79eL1wvYmbln23zO93vOvdcVDAaDICIiIkdwmz0AIiIiMg6Dn4iIyEEY/ERERA7C4CciInIQBj8REZGDMPiJiIgcJE7Ei1RVVeG3v/0t9u/fj/j4eAwbNgx33HEHMjMzez2uqakJv/vd77B161Z4PB4sXboUp556qoghEBERkQJCKn6Xy4XLLrsM69atw8qVK1FQUID77ruv3+Mef/xxpKSk4I033sAjjzyCm2++GQ0NDSKGQERERAoICf6MjAxMnTq1699HHXUUioqK+j3u9ddfxznnnAMAOOywwzBx4kS89957IoZARERECghf4w8EAnj++ecxY8aMfh8rKirC4MGDu/6dn5+PkpIS0UMgIiKiMISs8fd05513wufz4ec//7nolwYAVFU1IBDgVYZjlZWVgoqKerOHIZw3OUm3165rblf+2Cblj42krKFZyOsAwL6aJmGvFcmwdLHfg5zkRE3PT02K7e0tNVH422JYaU3lhhynNinbkOOEk9ZUjopAiqljCEf290S324UBA5KFvqbQn/Bly5Zh3759eOSRR+B2928mDBo0CAcPHuza9FdcXNxriUCJQCDI4NfIjl8/vT6lWpVB3ubXPpDiOnFBvbPKuD00W8rqAACjBL1J7a9uRH5q7JOJyvo2AEC6z6vqedWN7UiLcdKgVnViNjIa9e16VvvyAJN/5asTsxGobzR3EBHY8T0xEmGt/r/85S/YsmULHn74YcTHx4d8zJw5c/DCCy8AAPbu3YvNmzfjpJNOEjUEcihvis/sIQAAahrbNL+GVUO/73FFHVvE1yOW74vaCZ8W1b48S762WrL8npKg4N+xYwf++c9/oqysDOeeey4KCwtx9dVXAwAKCwtRWloKALj00ktRW1uLWbNm4YorrsAdd9yBlBQ5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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.decomposition import PCA\n", + "sklearn_pca=PCA(n_components=5)\n", + "X_Train=sklearn_pca.fit_transform(X_std)\n", + "\n", + "sns.set(style='darkgrid')\n", + "f, ax = plt.subplots(figsize=(8, 8))\n", + "# ax.set_aspect('equal')\n", + "ax = sns.kdeplot(X_Train[:,0], X_Train[:,1], cmap=\"Greens\",\n", + " shade=True, shade_lowest=False)\n", + "ax = sns.kdeplot(X_Train[:,1], X_Train[:,2], cmap=\"Reds\",\n", + " shade=True, shade_lowest=False)\n", + "ax = sns.kdeplot(X_Train[:,2], X_Train[:,3], cmap=\"Blues\",\n", + " shade=True, shade_lowest=False)\n", + "red = sns.color_palette(\"Reds\")[-2]\n", + "blue = sns.color_palette(\"Blues\")[-2]\n", + "green = sns.color_palette(\"Greens\")[-2]\n", + "ax.text(0.5, 0.5, \"2nd and 3rd Projection\", size=12, color=blue)\n", + "ax.text(-4, 0.0, \"1st and 3rd Projection\", size=12, color=red)\n", + "ax.text(2, 0, \"1st and 2nd Projection\", size=12, color=green)\n", + "plt.xlim(-6,5)\n", + "plt.ylim(-2,2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ac1fc10", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/randomforest.ipynb b/machine_learning/scripts/ml/randomforest.ipynb new file mode 100644 index 0000000..369f01f --- /dev/null +++ b/machine_learning/scripts/ml/randomforest.ipynb @@ -0,0 +1,938 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import F\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# data cleaning\n", + "df['time'] = df['time'].replace('','0')\n", + "df = df[df['attendance'] != 0]\n", + "\n", + "# remove outliers\n", + "out_fields = ['attendance']\n", + "for field in out_fields:\n", + " q_low = df[field].quantile(0.01)\n", + " q_hi = df[field].quantile(0.99)\n", + " df = df[(df[field] < q_hi) & (df[field] > q_low)]\n", + "\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: r['time'].replace(':','') < \"1800\", axis=1)\n", + "df['before2010'] = df.apply(lambda r: r['historic_season'].split('-')[0] < \"2010\", axis=1)\n", + "\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'homeTeam_id', 'awayTeam_id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country','year']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "data = df[feature_cols+[label]]\n", + "\n", + "\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "45e08026", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random Forest Regression Accuracy: 0.701779484610914\n" + ] + } + ], + "source": [ + "rf_regressor = RandomForestRegressor(n_estimators = 200 , random_state = 42)\n", + "rf_regressor.fit(X_train,y_train)\n", + "\n", + "# #Predicting the SalePrices using test set \n", + "y_pred_rf = rf_regressor.predict(X_test)\n", + "\n", + "# #Random Forest Regression Accuracy with test set\n", + "print('Random Forest Regression Accuracy: ', rf_regressor.score(X_test,y_test))\n", + "\n", + "# #Predicting the SalePrice using cross validation (KFold method)\n", + "# y_pred_rf = cross_val_predict(rf_regressor, X, y, cv=10 )\n", + "\n", + "# #Random Forest Regression Accuracy with cross validation\n", + "# accuracy_rf = metrics.r2_score(y, y_pred_rf)\n", + "# print('Cross-Predicted(KFold) Random Forest Regression Accuracy: ', accuracy_rf)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "0de49b8a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABDAAAALICAYAAACJhQBYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABMtElEQVR4nO3deZhtV10m/vclN5BAQpgiogIXQiAmDIHcgEGCBBHBgTGIikiQHxEaRbSxpYVGsKUbpRUblCEgBjQtYQZBCIgMYQq5mRNmCTQKLXOYJECyfn/UjhTFHeqOZ9etz+d56ql91l577e8+mwN1X9bap2OMAAAAAMzZ1RZdAAAAAMD2CDAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzN6GRRew3t3gBjcYGzduXHQZAAAAMAvnnHPO58cYh65sF2As2MaNG7N58+ZFlwEAAACz0PaTW2q3hAQAAACYPQEGAAAAMHuWkCzYdz73xXzuuX+76DIAAADYhxz66F9ZdAm7nRkYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZm0WA0XZj24sXXMPXFnl+AAAAYOtmEWAAAAAAbMucAoz92r6g7SVt39z2wLZHt31f2wvbvrrtdZOk7dvbPrPt5rYfbHts21e1/WjbP7pqwLa/0vb9bc9v+/y2+22rgGnMS9q+te2hU9sj257d9oK2r2x7zan9QW0vntrfObXt1/YZU/8L2/76Vs5z8lT75i987Su76/0DAACAfdacAozDk/zlGOOoJF9O8sAkL0nye2OM2ya5KMkfLOv/rTHGpiTPS/LaJI9JcuskJ7W9ftsfTfLgJD8+xjg6yRVJHrKN818ryebp/O9Ydq5XjTGOHWPcLskHkzxian9ykp+e2u8ztT0iyWVjjGOTHJvkkW1vtvJEY4xTxhibxhibrn/QtVf59gAAAMD6tWHRBSxz6Rjj/Gn7nCSHJbnOGOMdU9uLk7x8Wf/XTb8vSnLJGOMzSdL240lunOQuSY5JcnbbJDkwyWe3cf4rk5w+bf9tkldN27eeZnVcJ8lBSc6Y2t+d5NS2L1vW955Jbtv2xOn1IVkKZi7dzrUDAAAA2zCnAOPyZdtXZCkwWE3/K1cce2WWrqtJXjzG+K87Wc+Yfp+a5H5jjAvanpTkbkkyxnhU2zsl+dkk57Q9Zjrnb44xzvj+4QAAAICdNaclJCtdluRLbY+fXj80S0s7VuutSU5s+wNJ0vZ6bW+6jf5XS3LVzIlfTvKuafvgJJ9pu3+WLUFpe9gY46wxxpOTfC5Lsz7OSPLoqW/a3rLttXagZgAAAGAL5jQDY0seluR504MzP57k4as9cIzxgbZPSvLmtldL8u0sPSfjk1s55OtJ7jgd89ksPT8jSf5bkrOyFFKclaVAI0me0fbwLM26eGuSC5JcmGRjknO7tG7lc0nut9qaAQAAgC3rGGP7vdhjjr7pzcdbnvCHiy4DAACAfcihj/6VRZew09qeM31px/eY8xISAAAAgCTzX0Ky27U9K8k1VjQ/dIxx0SLqAQAAALZv3QUYY4w7LboGAAAAYMdYQgIAAADMngADAAAAmD0BBgAAADB76+4ZGHOz4dDrremvtwEAAIC9wQwMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ6vUV2wb3/uM/l/z/2jRZcBwMz94KOftOgSAAAWygwMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPY2LLqA3antU5J8Lcm1k7xzjPGPW+l3vyQfGWN8YO9VBwAAAOysfXIGxhjjyVsLLyb3S3LkXioHAAAA2EVrPsBo+8S2H2n7riS3mtpObXvitP30th9oe2Hb/9X2zknuk+QZbc9ve1jbR7Y9u+0FbV/Z9prLxnlW2/e0/fhVY077fq/tRdMxT5/aDmv7prbntD2z7RF7/Q0BAACAfdCaXkLS9pgkv5jk6Cxdy7lJzlm2//pJ7p/kiDHGaHudMcaX274uyevHGK+Y+n15jPGCafuPkjwiybOnYW6U5C5JjkjyuiSvaHvvJPdNcqcxxjfaXm/qe0qSR40xPtr2Tkmek+TuW6j75CQnJ8kPX++Q3fZ+AAAAwL5qTQcYSY5P8uoxxjeSZAomlrssyTeT/FXb1yd5/VbGufUUXFwnyUFJzli27zVjjCuTfKDtDae2eyT566vOO8b4YtuDktw5ycvbXnXsNbZ0sjHGKVkKO3K7m/7wWOW1AgAAwLq11gOMbRpjfKftHZP8ZJITk/xGtjAjIsmpSe43xrig7UlJ7rZs3+XLtputu1qSL48xjt6FkgEAAIAtWOvPwHhnkvu1PbDtwUl+fvnOaVbEIWOMf0jy20luN+36apKDl3U9OMln2u6f5CGrOO9bkjx82bMyrjfG+EqSS9s+aGpr29ttaxAAAABgddZ0gDHGODfJ6UkuSPLGJGev6HJwkte3vTDJu5L8ztT+0iS/2/a8tocl+W9Jzkry7iQfWsV535Sl52Fsbnt+ksdPux6S5BFtL0hySZaekwEAAADsoo7hEQyLdLub/vA44wmPXnQZAMzcDz76SYsuAQBgr2h7zhhj08r2NT0DAwAAAFgfBBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsbFl3Aerf/oTfKDz76SYsuAwAAAGbNDAwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGbPt5As2Dc/+7F86C/vu+gyYF064jGvXXQJAADAKpmBAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQHGHtT2E21vsOg6AAAAYK1b9wFGl6z79wEAAADmbF3+w73txrYfbvuSJBcn+au2F7e9qO2Dpz53a/v6Zcf8RduTpu1PtH1q23OnY46Y2q/f9s1tL2n7wiTd+1cHAAAA+551GWBMDk/ynCRPTvIjSW6X5B5JntH2Rqs4/vNjjDskeW6Sx09tf5DkXWOMo5K8OslNtnRg25Pbbm67+Utf+9YuXgYAAADs+9ZzgPHJMcb7ktwlyd+NMa4YY/xbknckOXYVx79q+n1Oko3T9l2T/G2SjDHekORLWzpwjHHKGGPTGGPTdQ+6+i5cAgAAAKwP6znA+Pp29n8n3/v+HLBi/+XT7yuSbNhdRQEAAADfbz0HGFc5M8mD2+7X9tAszaJ4f5JPJjmy7TXaXifJT65irHcm+eUkaXvvJNfdMyUDAADA+mLmwNKzKo5LckGSkeS/jDH+X5K0fVmWHvJ5aZLzVjHWU5P8XdtLkrwnyf/dIxUDAADAOtMxxqJrWNdufZPrjFf83k8sugxYl454zGsXXQIAALBC23PGGJtWtltCAgAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzt2HRBax3B/zALXLEY1676DIAAABg1szAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOz5GtUF++rnP5q3v+BnF10GLMTdHvmGRZcAAACsEWZgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwNgBbZ/S9vGLrgMAAADWGwEGAAAAMHsCjO1o+8S2H2n7riS3mtoe2fbsthe0fWXba7Y9uO2lbfef+lx7+WsAAABg5wkwtqHtMUl+McnRSX4mybHTrleNMY4dY9wuyQeTPGKM8dUkb0/ys1OfX5z6fXsL457cdnPbzZd99Vt7+CoAAABg7RNgbNvxSV49xvjGGOMrSV43td+67ZltL0rykCRHTe0vTPLwafvhSf56S4OOMU4ZY2waY2w65OCr78HyAQAAYN8gwNg5pyb5jTHGbZI8NckBSTLGeHeSjW3vlmS/McbFiyoQAAAA9iUCjG17Z5L7tT2w7cFJfn5qPzjJZ6bnWzxkxTEvSfJ/spXZFwAAAMCOE2Bswxjj3CSnJ7kgyRuTnD3t+m9Jzkry7iQfWnHYaUmum+Tv9lKZAAAAsM/bsOgC5m6M8bQkT9vCrudu5ZC7JHnFGOPLe6woAAAAWGcEGLtR22cnuXeWvrEEAAAA2E0EGLvRGOM3F10DAAAA7Is8AwMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACz52tUF+zgGxyeuz3yDYsuAwAAAGbNDAwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGbPt5As2Jc+/9G84q/vtegyYI848eFvWnQJAADAPsIMDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2ZhFgtN3Y9uId6H9E2/Pbntf2sF08943bvq3tB9pe0va3lu27Xtu3tP3o9Pu6y87/3raXt338ivHu1fbDbT/W9gm7UhsAAACwZBYBxk64X5JXjDFuP8b45+117pKtXet3kvznMcaRSX4syWPaHjnte0KSt44xDk/y1ul1knwxyWOT/K8V59kvyV8muXeSI5P80rKxAAAAgJ00pwBjQ9vT2n6w7SvaXrPtMW3f0factme0vVHbn0nyuCSPbvu2JGn7O20vnn4eN7VtnGZCvCTJxUlu3PZ3257d9sK2T02SMcZnxhjnTttfTfLBJD881XTfJC+etl+cpeAkY4zPjjHOTvLtFddwxyQfG2N8fIzxrSQvncYAAAAAdsGcAoxbJXnOGONHk3wlyWOSPDvJiWOMY5K8KMnTxhj/kOR5SZ45xjih7TFJHp7kTlmaQfHItrefxjx8GvOoafzDsxQyHJ3kmLZ3XV5A241Jbp/krKnphmOMz0zb/y/JDbdzDT+c5FPLXv9LvhuGAAAAADtpw6ILWOZTY4x3T9t/m+T3k9w6yVvaJsl+ST6zhePukuTVY4yvJ0nbVyU5PsnrknxyjPG+qd89p5/zptcHZSnQeOd03EFJXpnkcWOMr6w8yRhjtB27epHTuU5OcnKS3OD6B+yOIQEAAGCfNqcAY2U48NUkl4wxjtuFMb++bLtJ/ucY4/krO7XdP0vhxWljjFct2/VvbW80xvhM2xsl+ex2zvevSW687PWPTG3fY4xxSpJTkuSwjYfsllAEAAAA9mVzWkJyk7ZXhRW/nOR9SQ69qq3t/m2P2sJxZya53/TMjGsluf/UttIZSX5tmmmRtj/c9ge6NL3jr5J8cIzxZyuOeV2Sh03bD0vy2u1cw9lJDm97s7ZXT/KL0xgAAADALpjTDIwPZ+kbQF6U5ANZev7FGUme1faQLNX650kuWX7QGOPctqcmef/U9MIxxnnT8yyW93tz2x9N8t5pScrXkvxKklsmeWiSi9qeP3X//elZG09P8rK2j0jyySS/kCRtfzDJ5iTXTnLl9ODQI8cYX2n7G1Pd+yV50Rjje+oFAAAAdlzHsIJhkQ7beMj44z/YlVUyMF8nPvxNiy4BAABYY9qeM8bYtLJ9TktIAAAAALZIgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAs7dh0QWsd9e9weE58eFvWnQZAAAAMGtmYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2fI3qgn3uCx/N8//mpxddxj7l1x96xqJLAAAAYDczAwMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQHGLmh7nbb/adnru7V9/SJrAgAAgH2RAGPXXCfJf9peJwAAAGDXrJsAo+3Gth9qe2rbj7Q9re092r677Ufb3rHt9dq+pu2Fbd/X9rbTsU9p+6K2b2/78baPnYZ9epLD2p7f9hlT20FtXzGd67S2XcgFAwAAwD5kw6IL2MtukeRBSX4tydlJfjnJXZLcJ8nvJ/lUkvPGGPdre/ckL0ly9HTsEUlOSHJwkg+3fW6SJyS59Rjj6GRpCUmS2yc5Ksmnk7w7yY8nedfyItqenOTkJLne9Q/YE9cJAAAA+5R1MwNjcukY46IxxpVJLkny1jHGSHJRko1ZCjP+JknGGP+U5Pptrz0d+4YxxuVjjM8n+WySG27lHO8fY/zLdI7zp3G/xxjjlDHGpjHGpoMOvvruuzoAAADYR623AOPyZdtXLnt9ZbY/G2X5sVdso/9q+wEAAACrtN4CjO05M8lDkv9YDvL5McZXttH/q1laUgIAAADsQWYHfK+nJHlR2wuTfCPJw7bVeYzxhekhoBcneWOSN+z5EgEAAGD96dIjIFiUm97skPH7f/hjiy5jn/LrDz1j0SUAAACwk9qeM8bYtLLdEhIAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmL0Niy5gvTv0+ofn1x96xqLLAAAAgFkzAwMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACz52tUF+zTX/ponvKyn150GQvzlF/wFbIAAABsnxkYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZW1iA0fZxba+5g8fcre3rp+37tH3Cnqluq+ff1PZZe/OcAAAAQLJhged+XJK/TfKNnTl4jPG6JK/bnQWt4pybk2zem+cEAAAA9tIMjLbXavuGthe0vbjtHyT5oSRva/u2qc9z225ue0nbpy479l5tP9T23CQPWNZ+Utu/mLZPbXvisn1fm37fre072r627cfbPr3tQ9q+v+1FbQ/bRs0Pmmq9oO07l4131QyQf2h7/vRzWduHtd2v7TPant32wra/vpWxT56udfM3vvKtXXhnAQAAYH3YWzMw7pXk02OMn02StockeXiSE8YYn5/6PHGM8cW2+yV5a9vbJvlIkhckuXuSjyU5fSfOfbskP5rki0k+nuSFY4w7tv2tJL+ZpZkgW/LkJD89xvjXttdZuXOM8TPTtRyT5K+TvCbJI5JcNsY4tu01kry77ZvHGJeuOPaUJKckyQ8ddsjYiWsCAACAdWVvPQPjoiQ/1faP2x4/xrhsC31+YZplcV6So5IcmeSIJJeOMT46xhhZWnKyo84eY3xmjHF5kn9O8uZlNW3cxnHvTnJq20cm2W9LHdreIMnfJPnl6ZrumeRX256f5Kwk109y+E7UDAAAACyzV2ZgjDE+0vYOSX4myR+1fevy/W1vluTxSY4dY3yp7alJDtiBU3wnUxjT9mpJrr5s3+XLtq9c9vrKbOP6xxiPanunJD+b5JxppsXymvdL8tIkfzjGuPiq5iS/OcY4YwdqBwAAALZjbz0D44eSfGOM8bdJnpHkDkm+muTgqcu1k3w9yWVtb5jk3lP7h5JsXPasil/ayik+keSqgOE+SfbfDTUfNsY4a4zx5CSfS3LjFV2enuTCMcZLl7WdkeTRbfefxrhl22vtai0AAACw3u2tZ2DcJskz2l6Z5NtJHp3kuCRvavvpMcYJbc/LUmDxqSwt38gY45ttT07yhrbfSHJmvht6LPeCJK9te0GSN2UpDNlVz2h7eJZmVbw1yQVJfmLZ/scnuWRaLpIsPTPjhVlalnJu22Yp+LjfbqgFAAAA1rUuPVqCRfmhww4ZJ//PH1t0GQvzlF+w2gYAAIDvanvOGGPTyva99RBPAAAAgJ22t5aQzFbbJyZ50Irml48xnraIegAAAIDvt+4DjCmoEFYAAADAjFlCAgAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD21v1DPBfth657eJ7yC2csugwAAACYNTMwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPd9CsmAf/fI/596vfeCiy9hr3njfVy66BAAAANYgMzAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgJGn79rabttPnpLZ/sbdqAgAAAL5LgAEAAADM3poMMNr+btvHTtvPbPtP0/bd257W9p5t39v23LYvb3vQtP+Ytu9oe07bM9reaMW4V2t7ats/ml4/vO1H2r4/yY8v6/fzbc9qe17bf2x7w+nYj7Y9dNlYH7vqNQAAALDz1mSAkeTMJMdP25uSHNR2/6ntwiRPSnKPMcYdkmxO8jvT/mcnOXGMcUySFyV52rIxNyQ5LclHxxhPmsKNp2YpuLhLkiOX9X1Xkh8bY9w+yUuT/JcxxpVJ/jbJQ6Y+90hywRjjc7v30gEAAGD92bDoAnbSOUmOaXvtJJcnOTdLQcbxSV6XpbDh3W2T5OpJ3pvkVkluneQtU/t+ST6zbMznJ3nZGOOqUONOSd5+VQDR9vQkt5z2/UiS06eQ4+pJLp3aX5TktUn+PMmvJfnrLRXf9uQkJyfJAYceuJNvAQAAAKwfa3IGxhjj21kKDU5K8p4szcg4Icktpva3jDGOnn6OHGM8IkmTXLKs/TZjjHsuG/Y9SU5oe8AqSnh2kr8YY9wmya8nOWCq61NJ/q3t3ZPcMckbt1L/KWOMTWOMTVe/9jV2/A0AAACAdWZNBhiTM5M8Psk7p+1HJTkvyfuS/HjbWyRJ22u1vWWSDyc5tO1xU/v+bY9aNt5fJfmHJC9ruyHJWUl+ou31p+UnD1rW95Ak/zptP2xFXS/M0lKSl48xrthtVwsAAADr2FoPMG6U5L1jjH9L8s0kZ05LPk5K8ndtL8zS8pEjxhjfSnJikj9ue0GS85PcefmAY4w/y1II8jdJ/i3JU6bj353kg8u6PiXJy9uek+TzK+p6XZKDspXlIwAAAMCO6xhj0TXsU9puSvLMMcbx2+2c5JBbXHfc+U/vvoermo833veViy4BAACAGWt7zhhj08r2tfoQz1lq+4Qkj853v4kEAAAA2A3W8hKS2RljPH2McdMxxrsWXQsAAADsSwQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmL0Niy5gvTv8Oofljfd95aLLAAAAgFkzAwMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACz52tUF+yjX/5MfubVf7ToMr7HP9z/SYsuAQAAAL6HGRgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwdoO2p7Y9cdF1AAAAwL5KgLGL2u636BoAAABgXyfAmLT9lbbvb3t+2+e33a/tc9tubntJ26cu6/uJtn/c9twkD1rWfve2r1n2+qfavnrvXgkAAADsewQYSdr+aJIHJ/nxMcbRSa5I8pAkTxxjbEpy2yQ/0fa2yw77whjjDmOMly5re1uSI9oeOr1+eJIXbeF8J0/ByOZvfeXre+CKAAAAYN8iwFjyk0mOSXJ22/On1zdP8gvTLIvzkhyV5Mhlx5y+cpAxxkjyN0l+pe11khyX5I1b6HfKGGPTGGPT1a99rd18KQAAALDv2bDoAmaiSV48xviv/9HQ3izJW5IcO8b4UttTkxyw7JitTZ346yR/n+SbSV4+xvjOnikZAAAA1g8zMJa8NcmJbX8gSdpeL8lNshRSXNb2hknuvZqBxhifTvLpJE/KUpgBAAAA7CIzMJKMMT7Q9klJ3tz2akm+neQxWVo68qEkn0ry7h0Y8rQkh44xPrjbiwUAAIB1SIAxGWOcnu9/rsX7ttJ344rXJ63ocpckL9hdtQEAAMB6J8DYzdqek6WlJ/950bUAAADAvkKAsZuNMY5ZdA0AAACwr/EQTwAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZs+3kCzY4de5Uf7h/k9adBkAAAAwa2ZgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPZ8jeqCffTLn8vPvuq5u3XMNzzg0bt1PAAAAFg0MzAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALO31wKMtv/Q9jrb6XNS2x/aSyUBAAAAa8ReCzDGGD8zxvjydrqdlGSHAoy2G3a2JgAAAGBt2G0BRtvfbfvYafuZbf9p2r5729PafqLtDdpubPvBti9oe0nbN7c9sO2JSTYlOa3t+VPbMW3f0factme0vdE05tvb/nnbzUl+ayv1PKjtxW0vaPvOqW2/ts9oe3bbC9v++tR+UNu3tj237UVt7zu1X6vtG6YxLm774Kn9J9ueN/V9UdtrTO2faPvUZeMcsZXaTm67ue3mb132td11CwAAAGCftTtnYJyZ5Phpe1OSg9ruP7W9c0Xfw5P85RjjqCRfTvLAMcYrkmxO8pAxxtFJvpPk2UlOHGMck+RFSZ62bIyrjzE2jTH+dCv1PDnJT48xbpfkPlPbI5JcNsY4NsmxSR7Z9mZJvpnk/mOMOyQ5Icmftm2SeyX59BjjdmOMWyd5U9sDkpya5MFjjNsk2ZDk0cvO+/lpnOcmefyWChtjnDLVvunqhxy0lfIBAACAq+zOAOOcJMe0vXaSy5O8N0tBxvFZCjeWu3SMcf6y4zZuYbxbJbl1kre0PT/Jk5L8yLL9p2+nnncnObXtI5PsN7XdM8mvTuOdleT6WQpTmuR/tL0wyT8m+eEkN0xyUZKfavvHbY8fY1w21XXpGOMj05gvTnLXZed91XauCwAAANhBu+35EWOMb7e9NEvPsXhPkguzNJvhFkk+uKL75cu2r0hy4BaGbJJLxhjHbeWUX99OPY9qe6ckP5vknLbHTGP+5hjjjO85UXtSkkOTHDNdxyeSHDDG+EjbOyT5mSR/1PatSV67rfMuu7YrshvfXwAAAFjPdvdDPM/M0rKJd07bj0py3hhjrPL4ryY5eNr+cJJD2x6XJG33b3vUagtpe9gY46wxxpOTfC7JjZOckeTR09KWtL1l22slOSTJZ6fw4oQkN532/1CSb4wx/jbJM5LcYaprY9tbTKd6aJJ3rLYuAAAAYMft7hkCZyZ5YpL3jjG+3vab+f7lI9tyapLntf33JMclOTHJs9oeMtX650kuWeVYz2h71fKQtya5IEuzQjYmOXd6xsXnktwvyWlJ/r7tRVl6DseHpjFuM41zZZJvJ3n0GOObbR+e5OXTN6CcneR5O3CNAAAAwA7q6idHsCcccoubjrv8yRN265hveMCjt98JAAAAZqjtOWOMTSvbd/cSEgAAAIDdbs0/ZLLtE5M8aEXzy8cYT9tSfwAAAGDtWfMBxhRUCCsAAABgH2YJCQAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZW/MP8VzrDr/OoXnDAx696DIAAABg1szAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9nwLyYJ97EtfzM+94rRdHuf1Jz5kN1QDAAAA82QGBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9tZdgNH2mW0ft+z1GW1fuOz1n7b9na0ce2rbE/dCmQAAAMAy6y7ASPLuJHdOkrZXS3KDJEct23/nJO9ZQF0AAADAVqzHAOM9SY6bto9KcnGSr7a9bttrJPnRJPdse3bbi9ue0rYrB2l7TNt3tD1nmsVxo6n9sW0/0PbCti/dWxcFAAAA+7J1F2CMMT6d5Dttb5Kl2RbvTXJWlkKNTUkuSvIXY4xjxxi3TnJgkp9bPkbb/ZM8O8mJY4xjkrwoydOm3U9Icvsxxm2TPGpLNbQ9ue3mtpu/9ZWv7PZrBAAAgH3NhkUXsCDvyVJ4ceckf5bkh6fty7K0xOSEtv8lyTWTXC/JJUn+ftnxt0py6yRvmSZn7JfkM9O+C5Oc1vY1SV6zpZOPMU5JckqSXOewm4/dd1kAAACwb1qvAcZVz8G4TZaWkHwqyX9O8pUkf53kBUk2jTE+1fYpSQ5YcXyTXDLGOC7f72eT3DXJzyd5YtvbjDG+s0euAgAAANaJdbeEZPKeLC0L+eIY44oxxheTXCdLy0iueoDn59selGRL3zry4SSHtj0uWVpS0vao6aGgNx5jvC3J7yU5JMlBe/ZSAAAAYN+3XmdgXJSlbx/5PyvaDhpjfL7tC7I0M+P/JTl75cFjjG9NX6f6rLaHZOl9/PMkH0nyt1NbkzxrjPHlPXkhAAAAsB6sywBjjHFFkmuvaDtp2faTkjxpC8ct73N+lpaKrHSX3VQmAAAAMFmvS0gAAACANUSAAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNnbsOgC1rtbXPd6ef2JD1l0GQAAADBrZmAAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9nyN6oJ97EuX5T6v+PtV9X3diT+/h6sBAACAeTIDAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJi9fT7AaPu1Hey/se3Fu+ncd2v7+t0xFgAAAKxn+3yAsSVtNyy6BgAAAGD11k2AMc2GOLPt65J8oO1+bZ/R9uy2F7b99S0cs3E65tzp587Lxnp721e0/VDb09p22nevqe3cJA/Yu1cJAAAA+6b1NhPhDkluPca4tO3JSS4bYxzb9hpJ3t32zUnGsv6fTfJTY4xvtj08yd8l2TTtu32So5J8Osm7k/x4281JXpDk7kk+luT0LRUxnfvkJDnwBofu7msEAACAfc66mYExef8Y49Jp+55JfrXt+UnOSnL9JIev6L9/khe0vSjJy5McuWKsfxljXJnk/CQbkxyR5NIxxkfHGCPJ326piDHGKWOMTWOMTVe/9iG758oAAABgH7beZmB8fdl2k/zmGOOM5R3ablz28reT/FuS22Up7Pnmsn2XL9u+IuvvvQQAAIC9Zr3NwFjujCSPbrt/krS9ZdtrrehzSJLPTLMsHppkv+2M+aEkG9seNr3+pd1ZMAAAAKxX6znAeGGSDyQ5d/ra1Ofn+2dRPCfJw9pekKXlIV/PNowxvpmlZ1u8YXqI52d3e9UAAACwDnXpUQ0synUOO3zc9Y//bFV9X3fiz+/hagAAAGCx2p4zxti0sn09z8AAAAAA1ggBBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADM3oZFF7De3eK6h+R1J/78ossAAACAWTMDAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2fMtJAv2z1/6Wu7/yndtt9+rH3iXvVANAAAAzJMZGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTCStH172027ecy7tX397hwTAAAA1isBBgAAADB7azLAaPu7bR87bT+z7T9N23dve1rbe7Z9b9tz27687UHT/mPavqPtOW3PaHujFeNere2pbf+o7X5tn9H27LYXtv31qc/dphkbr2j7oel8nfbda2o7N8kD9uqbAgAAAPuwNRlgJDkzyfHT9qYkB7Xdf2q7MMmTktxjjHGHJJuT/M60/9lJThxjHJPkRUmetmzMDUlOS/LRMcaTkjwiyWVjjGOTHJvkkW1vNvW9fZLHJTkyyc2T/HjbA5K8IMnPJzkmyQ/uiQsHAACA9WjDogvYSeckOabttZNcnuTcLAUZxyd5XZaChXdPEyOunuS9SW6V5NZJ3jK175fkM8vGfH6Sl40xrgo17pnktm1PnF4fkuTwJN9K8v4xxr8kSdvzk2xM8rUkl44xPjq1/22Sk7dUfNuTr9p34A1uuPPvAgAAAKwTazLAGGN8u+2lSU5K8p4szbo4Icktklya5C1jjF9afkzb2yS5ZIxx3FaGfU+SE9r+6Rjjm0ma5DfHGGesGOduWQpNrnJFdvB9HGOckuSUJLnuYUeMHTkWAAAA1qO1uoQkWVpG8vgk75y2H5XkvCTvy9KSjlskSdtrtb1lkg8nObTtcVP7/m2PWjbeXyX5hyQva7shyRlJHj0tPUnbW7a91jbq+VCSjW0Pm17/0jb6AgAAADtgrQcYN0ry3jHGvyX5ZpIzxxify9LMjL9re2GWlo8cMcb4VpITk/xx2wuSnJ/kzssHHGP8WZZCkL9J8sIkH0hybtuLs7TEZKszLaZZGycnecP0EM/P7r5LBQAAgPWtY1jBsEjXPeyIcbc/eeF2+736gXfZC9UAAADAYrU9Z4yxaWX7Wp6BAQAAAKwTAgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmL0Niy5gvTvsugfl1Q+8y6LLAAAAgFkzAwMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACz52tUF+zjX748D37Vx7a6//QH3GIvVgMAAADzZAYGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHvbDTDajrZ/uuz149s+ZUdO0vZube+87PWpbU/coUp3QtuntH38Dh6z22pr+/a2m3bHWAAAALCerWYGxuVJHtD2BjtzgrYbktwtyZ2303W147XtLs0cmWoCAAAA1ojVBAHfSXJKkt9euaPtxrb/1PbCtm9te5Op/dS2z2t7VpKXJXlUkt9ue37b46fD79r2PW0/vnzGQ9vfbXv2NOZTl53nw21fkuTiJMe3/WDbF7S9pO2b2x64rYuYZkP8edvNSX6r7TFt39H2nLZntL3RFo558lTLxW1PadtlY/1x2/e3/chV19T2wLYvnWp7dZJt1gQAAACszmpnMvxlkoe0PWRF+7OTvHiMcdskpyV51rJ9P5LkzmOMByR5XpJnjjGOHmOcOe2/UZK7JPm5JE9Pkrb3THJ4kjsmOTrJMW3vOvU/PMlzxhhHJfnk9Povp9dfTvLAVVzH1ccYm6Y6n53kxDHGMUlelORpW+j/F2OMY8cYt85SGPFzy/ZtGGPcMcnjkvzB1PboJN8YY/zo1HbMlopoe3LbzW03X37ZF1dRNgAAAKxvq1pKMcb4yjT74bFJ/n3ZruOSPGDa/pskf7Js38vHGFdsY9jXjDGuTPKBtjec2u45/Zw3vT4oS0HF/03yyTHG+5Ydf+kY4/xp+5wkG1dxKadPv2+V5NZJ3jJNqtgvyWe20P+Etv8lyTWTXC/JJUn+ftr3qi2c+66ZQpwxxoVtL9xSEWOMU7I0qyXXu8VtxirqBgAAgHVtR54F8edJzk3y16vs//Xt7L982XaX/f6fY4znL+/YduMWxlt+/BVZ3XKNq8ZokkvGGMdtrWPbA5I8J8mmMcanpgeXHrCF81+RHXsfAQAAgB206odhjjG+mKXnWTxiWfN7kvzitP2QJGeuPG7y1SQHr+I0ZyT5tbYHJUnbH277A6utcQd8OMmhbY+bzrN/26NW9LkqrPj8VM9qvpnknUl+eRrz1kluu5vqBQAAgHVtR7/N40+TLP82kt9M8vBpqcRDk/zWVo77+yT3X/EQz+8zxnhzkv+T5L1tL0ryiqwu+NghY4xvZSmQ+OO2FyQ5Pyu+JWWM8eUkL8jSQ0PPSHL2KoZ+bpKD2n4wyR9maXkJAAAAsIs6hkcwLNL1bnGb8VN/8uqt7j/9AbfYi9UAAADAYrU9Z/oCju+xozMwAAAAAPY6AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmb8OiC1jvbn6da+T0B9xi0WUAAADArJmBAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNnzNaoL9tkvfzt/+ep/+562x9z/hguqBgAAAObJDAwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9hYWYLR9YttL2l7Y9vy2d9rJce7W9s7LXp/a9sRVHnu/tqPtEcvaDm17Vtvz2h6/hWNe2PbInakVAAAA2DkbFnHStscl+bkkdxhjXN72BkmuvpPD3S3J15K8ZyeO/aUk75p+/8HU9pNJLhpj/H8rO7fdb0vtAAAAwJ61qBkYN0ry+THG5Ukyxvj8GOPTSdL2J6fZDxe1fVHba0ztn5iCjrTd1PbtbTcmeVSS355mcVw1Y+Kubd/T9uNbm43R9qAkd0nyiCS/OLUdneRPktx3Gu/Atl9r+6dtL0hy3HTeTVP/e7U9t+0Fbd86td2x7Xuna3hP21vt/rcPAAAA1pdFBRhvTnLjth9p+5y2P5EkbQ9IcmqSB48xbpOlGSKP3togY4xPJHlekmeOMY4eY5w57bpRlsKJn0vy9K0cft8kbxpjfCTJF9oeM8Y4P8mTk5w+jffvSa6V5Kwxxu3GGO+66uC2hyZ5QZIHjjFul+RB064PJTl+jHH7aaz/sfLEbU9uu7nt5q995YvbfqcAAACAxQQYY4yvJTkmyclJPpfk9LYnJblVkkunUCFJXpzkrjtxiteMMa4cY3wgyQ230ueXkrx02n7p9HpLrkjyyi20/1iSd44xLk2SMcZVScQhSV7e9uIkz0xy1MoDxxinjDE2jTE2HXTt663qggAAAGA9W8gzMJJkjHFFkrcneXvbi5I8LMl52zjkO/lu4HLAdoa/fNl2V+5se70kd09ym7YjyX5JRtvf3cJY35xqXa3/nuRtY4z7T0tc3r4DxwIAAABbsJAZGG1v1fbwZU1HJ/lkkg8n2dj2FlP7Q5O8Y9r+RJZmbSTJA5cd+9UkB+9gCScm+Zsxxk3HGBvHGDdOcmmS7/vWkW14X5aetXGz5D9CkWRpBsa/Ttsn7WBdAAAAwBYs6hkYByV5cdsPtL0wyZFJnjLG+GaSh2dpCcZFSa7M0jMukuSpSf53281ZWtZxlb9Pcv8VD/Hcnl9K8uoVba/M1peRfJ8xxueytATmVdMDPk+fdv1Jkv/Z9rwscIYLAAAA7Es6xlh0DevaTW5xu/F7z3jz97Q95v5be2wHAAAA7NvanjPG2LSyfVEzMAAAAABWTYABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALO3YdEFrHc/cJ3985j733DRZQAAAMCsmYEBAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDs+RaSBbvsS9/JG0///Pe03fvBN1hQNQAAADBPZmAAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAs7fmA4y2T2x7SdsL257f9k47ePzRbX9m2euT2v7FbqrtKW0fvzvGAgAAgPVsw6IL2BVtj0vyc0nuMMa4vO0Nklx9B4c5OsmmJP+wm8sDAAAAdpO1PgPjRkk+P8a4PEnGGJ8fY3y67bFt39P2grbvb3tw2wPa/nXbi9qe1/aEtldP8odJHjzN3njw8sHb/nzbs6b+/9j2hlP7U9q+qO3b23687WOXHfPEth9p+64kt9p7bwUAAADsu9Z6gPHmJDeeAoPntP2JKZQ4PclvjTFul+QeSf49yWOSjDHGbZL8UpIXZ+n6n5zk9DHG0WOM01eM/64kPzbGuH2Slyb5L8v2HZHkp5PcMckftN2/7TFJfjFLszp+Jsmxe+SqAQAAYJ1Z00tIxhhfm0KD45OckKXg4mlJPjPGOHvq85UkaXuXJM+e2j7U9pNJbrmdU/xIktPb3ihLS1MuXbbvDdPMj8vbfjbJDac6Xj3G+MZ0ztdtadC2Jyc5OUl+4AY/ssPXDQAAAOvNWp+BkTHGFWOMt48x/iDJbyR5wG4c/tlJ/mKatfHrSQ5Ytu/yZdtXZAfCoDHGKWOMTWOMTde+9vV3T6UAAACwD1vTAUbbW7U9fFnT0Uk+mORGbY+d+hzcdkOSM5M8ZGq7ZZKbJPlwkq8mOXgrpzgkyb9O2w9bRUnvTHK/tge2PTjJz+/YFQEAAABbsqYDjCQHJXlx2w+0vTDJkVl6psWDkzy77QVJ3pKlmRPPSXK1thdlaanJSdMSkLclOXJLD/FM8pQkL297TpLPb6+YMca509gXJHljkrN3wzUCAADAutcxxqJrWNcOP+zo8az/8Y/f03bvB99gQdUAAADAYrU9Z4yxaWX7Wp+BAQAAAKwDAgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmL0Niy5gvTvkuhty7wffYNFlAAAAwKyZgQEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2As2Dc+/51FlwAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AsQVtvzb93tj2l1fRf2Pbi/d8ZQAAALA+CTC2bWOS7QYYAAAAwJ4lwNi2pyc5vu35bX97mmlxZttzp587rzyg7TvbHr3s9bva3m5vFg0AAAD7GgHGtj0hyZljjKPHGM9M8tkkPzXGuEOSByd51haO+askJyVJ21smOWCMccHyDm1Pbru57eYvffULe/QCAAAAYF8gwNgx+yd5QduLkrw8yZFb6PPyJD/Xdv8kv5bk1JUdxhinjDE2jTE2Xffg6+/JegEAAGCfsGHRBawxv53k35LcLkvhzzdXdhhjfKPtW5LcN8kvJDlmr1YIAAAA+yABxrZ9NcnBy14fkuRfxhhXtn1Ykv22ctwLk/x9lpaffGkP1wgAAAD7PEtItu3CJFe0vaDtbyd5TpKHtb0gyRFJvr6lg8YY5yT5SpK/3muVAgAAwD7MDIwtGGMcNP3+dpK7r9h922Xbvzf1+0SSW1/V2PaHshQOvXmPFgoAAADrhBkYu1nbX01yVpInjjGuXHQ9AAAAsC8wA2M3G2O8JMlLFl0HAAAA7EvMwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAt2zRv4IhgAAADYHgEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAAAAZk+AAQAAAMyeAAMAAACYPQEGAAAAMHuzDjDabmx78aLr2JK2j2t7zWWvf3+R9QAAAMC+bNYBxsw9Lsk1l73eYoDRJd5nAAAA2AVr4R/W+7V9QdtL2r657YFtj277vrYXtn112+smSdu3t31m281tP9j22LavavvRtn901YBtf6Xt+9ue3/b5bffb2snbPnca75K2T53aHpvkh5K8re3b2j49yYHTeKdNM0c+3PYlSS5OcuMVY548jbn5c5/73B54ywAAAGDfshYCjMOT/OUY46gkX07ywCQvSfJ7Y4zbJrkoyR8s6/+tMcamJM9L8tokj0ly6yQntb1+2x9N8uAkPz7GODrJFUkeso3zP3Ea77ZJfqLtbccYz0ry6SQnjDFOGGM8Icm/jzGOHmNcNdbhSZ4zxjhqjPHJ5QOOMU4ZY2waY2w69NBDd/qNAQAAgPViw6ILWIVLxxjnT9vnJDksyXXGGO+Y2l6c5OXL+r9u+n1RkkvGGJ9JkrYfz9JMiLskOSbJ2W2T5MAkn93G+X+h7clZeq9ulOTIJBeuou5PjjHet4p+AAAAwHashQDj8mXbVyS5zir7X7ni2CuzdL1N8uIxxn/d3onb3izJ45McO8b4UttTkxywurLz9VX2AwAAALZjLSwhWemyJF9qe/z0+qFJ3rGN/iu9NcmJbX8gSdper+1Nt9L32lkKIi5re8Mk916276tJDl72+ttt99+BOgAAAIBVWgszMLbkYUmeN32N6ceTPHy1B44xPtD2SUnePH07yLez9JyMT26h7wVtz0vyoSSfSvLuZbtPSfKmtp8eY5wwvb6w7blJnriT1wUAAABsQccYi65hXdu0adPYvHnzossAAACAWWh7zvRlGt9jLS4hAQAAANaZtbqEZLdre1aSa6xofugY46JF1AMAAAB8lwBjMsa406JrAAAAALbMEhIAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZPgAEAAADMngADAAAAmD0BBgAAADB7AgwAAABg9gQYAAAAwOwJMAAAAIDZE2AAAAAAsyfAAAAAAGZvTQQYbX9/2fZ12v6n3Tj23dreednrR7X91e0c88K2R66sDQAAANgz1kSAkWR5SHCdJFsMMNpu2Imx75bkPwKMMcbzxhgv2dYBY4z/b4zxgS3UBgAAAOwBO/MP/j2q7WuS3DjJAUn+d5KbJzmw7flJLkmyX5LDptdvSfKGJP89yZeSHJHklivHGGOcMo19ryT/Yxrj80kekeRRSa5o+ytJfjPJTyb5WpLXJ3nJGOOO07Ebk/z9GOM2bd+e5PFJTlxR2z8n+eIY48+nY56W5LNjjP+9u98nAAAAWE9mF2Ak+bUxxhfbHpjk7CQ/keQ3xhhHJ/8RJNx62eu7JbnD1HbplsZo+8oszTZ5QZK7jjEubXu9qc/zknxtjPG/pvF+MknGGB9qe/W2N5vGfXCS05cXOsZ4QtuVtb0qyZ+3vVqSX0xyx5UX2PbkJCcnyU1ucpNdfb8AAABgnzfHJSSPbXtBkvdlaRbF4as45v3LwoutjfFjSd55Vb8xxhdXMe7LshRcJFsIMFYaY3wiyRfa3j7JPZOcN8b4whb6nTLG2DTG2HTooYeuogwAAABY32Y1A2OaTXGPJMeNMb4xLdU4YBWHfn03jLElpyd5edtXJRljjI+u4pgXJjkpyQ8medFOnhcAAABYZm4zMA5J8qUpeDgiS7MmkuTbbfeftr+a5OCdGON9Se7a9mZJ0vZ62xtvjPHPSa5I8t+y9dkXy2tLklcnuVeSY5OcsY06AQAAgFWaW4DxpiQb2n4wydOzFDokySlJLmx72rQk491tL277jNWOMcb4XJaeO/GqaXnJVYHE3ye5f9vz2x6/hfFOT/IrWVpOsiX/Udt0nm8leVuSl40xrtiRiwcAAAC2rGOMRdewT5ke3nlukgetZsnJpk2bxubNm/d8YQAAALAGtD1njLFpZfvcZmCsaW2PTPKxJG9d5fMyAAAAgFWY1UM817oxxgeS3HzRdQAAAMC+xgwMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AYye1PantDy17/bi211xkTQAAALCvEmDsvJOS/NCy149LssUAo+1+e6EeAAAA2GetmQCj7WvantP2krYnt31Q2z+b9v1W249P2zdv++5p+8ltz257cdtTuuSwtucuG/fw5a+3cN4tjXFikk1JTmt7ftvfylKY8ba2b5uO+1rbP217QZLj9tgbAwAAAOvAmgkwkvzaGOOYLAUHj03yniTHT/uOT/KFtj88bb9zav+LMcaxY4xbJzkwyc+NMf45yWVtj576PDzJX2/jvFsa4xVJNid5yBjj6DHG/07y6SQnjDFOmI67VpKzxhi3G2O8a/mAUwCzue3mz33uczv7fgAAAMC6sZYCjMdOsxnel+TG089BbQ+etv9PkrtmKcA4czrmhLZntb0oyd2THDW1vzDJw6elHQ+ejt2arY2xPVckeeWWdowxThljbBpjbDr00ENXORwAAACsX2siwGh7tyT3SHLcGON2Sc5LckCWZmE8PMmHsxRaHJ+l5RrvbntAkuckOXGMcZskL5iOSZaChXsn+bkk54wxvrCV825rjO355hjjih27UgAAAGBL1kSAkeSQJF8aY3yj7RFJfmxqPzPJ47O0ZOS8JCckuXyMcVm+GzR8vu1BSU68arAxxjeTnJHkudn28pGtjpHkq0kO3sZrAAAAYDdZKwHGm5JsaPvBJE/P0jKSZCnAuHGSd06zHT6V5F1JMsb4cpZmTFycpbDi7BVjnpbkyiRv3tpJtzPGqUmeNz3E88AkpyR501UP8QQAAAB2n44xFl3DQrR9fJJDxhj/bZF1bNq0aWzevHmRJQAAAMBstD1njLFpZfuGRRSzaG1fneSwLD2UEwAAAJi5dRlgjDHuv7JtCjVutqL598YYZ+ydqgAAAICtWZcBxpZsKdQAAAAA5mGtPMQTAAAAWMcEGAAAAMDsCTAAAACA2RNgAAAAALMnwAAAAABmT4ABAAAAzJ4AAwAAAJg9AQYAAAAwex1jLLqGda3tV5N8eNF1sNNukOTziy6Cneb+rW3u39rm/q1t7t/a5v6tbe7f2ub+rc5NxxiHrmzcsIhK+B4fHmNsWnQR7Jy2m92/tcv9W9vcv7XN/Vvb3L+1zf1b29y/tc392zWWkAAAAACzJ8AAAAAAZk+AsXinLLoAdon7t7a5f2ub+7e2uX9rm/u3trl/a5v7t7a5f7vAQzwBAACA2TMDAwAAAJg9AQYAAAAwewKMPajtvdp+uO3H2j5hC/uv0fb0af9ZbTcu2/dfp/YPt/3pvVo4SXb+/rXd2Pbf254//TxvrxfPau7fXdue2/Y7bU9cse9hbT86/Txs71XNVXbx/l2x7PP3ur1XNVdZxf37nbYfaHth27e2vemyfT5/C7aL98/nb8FWcf8e1fai6R69q+2Ry/b5+3PBdvb++ftzHrZ3/5b1e2Db0XbTsjafv9UYY/jZAz9J9kvyz0lunuTqSS5IcuSKPv8pyfOm7V9Mcvq0feTU/xpJbjaNs9+ir2k9/ezi/duY5OJFX8N6/lnl/duY5LZJXpLkxGXt10vy8en3daft6y76mtbTz67cv2nf1xZ9Dev5Z5X374Qk15y2H73svz99/tbw/Zte+/zN//5de9n2fZK8adr29+favn/+/lwD92/qd3CSdyZ5X5JNU5vP3yp/zMDYc+6Y5GNjjI+PMb6V5KVJ7ruiz32TvHjafkWSn2zbqf2lY4zLxxiXJvnYNB57z67cPxZvu/dvjPGJMcaFSa5ccexPJ3nLGOOLY4wvJXlLknvtjaL5D7ty/1i81dy/t40xvjG9fF+SH5m2ff4Wb1fuH4u3mvv3lWUvr5Xkqif6+/tz8Xbl/rF4q/n3Q5L89yR/nOSby9p8/lZJgLHn/HCSTy17/S9T2xb7jDG+k+SyJNdf5bHsWbty/5LkZm3Pa/uOtsfv6WL5PrvyGfL5W7xdvQcHtN3c9n1t77dbK2M1dvT+PSLJG3fyWHa/Xbl/ic/foq3q/rV9TNt/TvInSR67I8eyR+3K/Uv8/blo271/be+Q5MZjjDfs6LEs2bDoAmAf9JkkNxljfKHtMUle0/aoFYk5sOfcdIzxr21vnuSf2l40xvjnRRfF92v7K0k2JfmJRdfCjtvK/fP5WwPGGH+Z5C/b/nKSJyXxvJk1ZCv3z9+fM9f2akn+LMlJCy5lTTMDY8/51yQ3Xvb6R6a2LfZpuyHJIUm+sMpj2bN2+v5NU7++kCRjjHOytIbtlnu8Ypbblc+Qz9/i7dI9GGP86/T740nenuT2u7M4tmtV96/tPZI8Mcl9xhiX78ix7FG7cv98/hZvRz9DL01yv508lt1vp++fvz9nYXv37+Akt07y9rafSPJjSV43PcjT52+VBBh7ztlJDm97s7ZXz9JDHlc+jft1+W7ifWKSfxpjjKn9F7v0LRc3S3J4kvfvpbpZstP3r+2hbfdLkun/gTo8Sw+iY+9Zzf3bmjOS3LPtddteN8k9pzb2np2+f9N9u8a0fYMkP57kA3usUrZku/ev7e2TPD9L//j97LJdPn+Lt9P3z+dvFlZz/w5f9vJnk3x02vb35+Lt9P3z9+csbPP+jTEuG2PcYIyxcYyxMUvPELrPGGNzfP5WzRKSPWSM8Z22v5GlP7z2S/KiMcYlbf8wyeYxxuuS/FWSv2n7sSRfzNJ/yDP1e1mW/kf/O0keM8a4YiEXsk7tyv1Lctckf9j221l6wOCjxhhf3PtXsX6t5v61PTbJq7P0TQc/3/apY4yjxhhfbPvfs/Q/Qknyh+7f3rUr9y/JjyZ5ftsrsxTSP32M4R9Qe9Eq//vzGUkOSvLy6dnH/3eMcR+fv8XblfsXn7+FW+X9+41pBs23k3wp0/8Z4+/PxduV+xd/fy7cKu/f1o71+VulLv0f/gAAAADzZQkJAAAAMHsCDAAAAGD2BBgAAADA7AkwAAAAgNkTYAAAAACzJ8AAAHabto9t+8G2p+3EsRvb/vKeqGsa/4Vtj9xT42/lnL+/N88HAPsyX6MKAOw2bT+U5B5jjH/ZiWPvluTxY4yf28Hj9htjXLGj59uT2jZJk3xljHHQousBgH2BGRgAwG7R9nlJbp7kjW1/u+212r6o7fvbntf2vlO/jW3PbHvu9HPnaYinJzm+7fnT8Se1/Ytl479+CjnS9mtt/7TtBUmOa/sr03nOb/v8tvttob63t9207PhntL2k7T+2veO0/+Nt7zP1Oanta6f2j7b9g2Vj/U7bi6efxy27rg+3fUmSi5P8VZIDp5pOm/q8pu0503lPXjbe19o+re0Fbd/X9oZT+w3bvnpqv+Cq92o11wsA+xoBBgCwW4wxHpXk00lOGGM8M8kTk/zTGOOOSU5I8oy210ry2SQ/Nca4Q5IHJ3nWNMQTkpw5xjh6On5brpXkrDHG7ZJ8YRrnx8cYRye5IslDVnH8P40xjkry1SR/lOSnktw/yR8u63fHJA9MctskD2q7qe0xSR6e5E5JfizJI9vefup/eJLnjDGOGmM8PMm/T9dzVT2/NsY4JsmmJI9te/1l9bxvup53Jnnk1P6sJO+Y2u+Q5JK2P7oT1wsAa96GRRcAAOyz7pnkPm0fP70+IMlNshRy/EXbo7P0j+9b7sTYVyR55bT9k0mOSXL20sqNHJilkGRbvpXkTdP2RUkuH2N8u+1FSTYu6/eWMcYXkqTtq5LcJclI8uoxxteXtR+f5HVJPjnGeN82zvvYtveftm+cpcDjC1M9r5/az8lSmJIkd0/yq0kyLZO5rO1Dd+J6AWDNE2AAAHtKkzxwjPHh72lsn5Lk35LcLkuzQb+5leO/k++dLXrAsu1vLnvuRZO8eIzxX3egtm+P7z4I7MoklyfJGOPKtsv/Plr5sLDtPTzs61vbMS1/uUeS48YY32j79nz3mpbXc0W2/TfazlwvAKx5lpAAAHvKGUl+c3qgZZYtszgkyWfGGFcmeWiSq57f8NUkBy87/hNJjm57tbY3ztJyji15a5IT2/7AdJ7rtb3pbrqGn5rGOzDJ/ZK8O8mZSe7X9prTkpj7T21b8u22+0/bhyT50hReHJGl5Sfb89Ykj06WHlba9pDs2esFgNkSYAAAe8p/T7J/kgvbXjK9TpLnJHnY9ADOI/LdWQsXJrlieljlb2cpLLg0yQey9CyIc7d0kjHGB5I8Kcmb216Y5C1JbrSbruH9WVqqcmGSV44xNo8xzk1y6rTvrCQvHGOct5XjT8nS9Z+WpSUrG9p+MEsPLN3WUpOr/FaSE6alLeckOXIPXy8AzJavUQUA2IK2JyXZNMb4jUXXAgCYgQEAAACsAWZgAAAAALNnBgYAAAAwewIMAAAAYPYEGAAAAMDsCTAAAACA2RNgAAAAALP3/wNvB3/kcl14JgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ranking = np.argsort(-rf_regressor.feature_importances_)\n", + "f, ax = plt.subplots(figsize=(15, 10))\n", + "sns.barplot(x=rf_regressor.feature_importances_[ranking], y=X_train.columns.values[ranking], orient='h')\n", + "ax.set_xlabel(\"feature importance\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "4c1f8b45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 4000 6580.545\n", + "1 3264 1930.28\n", + "2 6000 4557.7\n", + "3 4250 5707.365\n", + "4 1200 2293.235\n", + "5 4300 14245.4\n", + "6 3874 10814.615\n", + "7 2800 7051.27\n", + "8 5500 5780.415\n", + "9 6000 16578.36\n", + "10 3500 3465.88\n", + "11 4500 6437.125\n", + "12 2140 2539.445\n", + "13 3146 7725.725\n", + "14 2600 4115.015\n", + "15 4875 3892.94\n", + "16 5807 2310.32\n", + "17 5200 5284.785\n", + "18 3500 14448.53\n", + "19 2643 13368.715\n", + "20 4000 5098.405\n", + "21 2500 11200.69\n", + "22 2000 4440.93\n", + "23 3198 13613.71\n", + "24 3571 2143.51\n", + "25 2712 14239.07\n", + "26 2100 2803.565\n", + "27 4525 3025.8\n", + "28 6625 5005.1\n", + "29 4966 4908.325\n", + "30 2000 11584.62\n", + "31 2100 17840.985\n", + "32 2310 11679.06\n", + "33 2600 3441.75\n", + "34 2000 4676.61\n", + "35 4300 12716.24\n", + "36 2734 4625.195\n", + "37 3500 8200.89\n", + "38 3050 5654.81\n", + "39 5256 9792.665\n", + "40 3012 2310.83\n", + "41 5060 4974.58\n", + "42 1500 9684.55\n", + "43 4000 7074.835\n", + "44 1950 9311.275\n", + "45 2300 5989.395\n", + "46 2300 2172.745\n", + "47 1950 9854.69\n", + "48 3058 3366.505\n", + "49 2000 9185.505\n", + "50 2345 5486.87\n", + "51 5500 6112.29\n", + "52 5585 3817.555\n", + "53 4046 5211.04\n", + "54 6000 5262.485\n", + "55 4384 4752.54\n", + "56 3000 4680.4\n", + "57 3500 3730.125\n", + "58 7000 19604.555\n", + "59 10195 12155.54\n", + "60 3476 2316.185\n", + "61 2120 6507.535\n", + "62 3800 4992.07\n", + "63 2675 17715.46\n", + "64 6603 5947.32\n", + "65 1500 6841.915\n", + "66 4000 14031.8475\n", + "67 4545 3450.985\n", + "68 1396 3998.6\n", + "69 5400 12293.125\n", + "70 5041 10571.61\n", + "71 1500 13793.105\n", + "72 1800 3019.735\n", + "73 3189 2286.085\n", + "74 3800 8072.33\n", + "75 2178 14740.815\n", + "76 6169 3358.27\n", + "77 2676 6266.61\n", + "78 3510 4507.855\n", + "79 3900 5281.48\n", + "80 2120 6582.6\n", + "81 8000 7046.1\n", + "82 2500 5538.39\n", + "83 4016 1779.045\n", + "84 1500 14032.36875\n", + "85 2860 3886.32\n", + "86 2150 12050.75\n", + "87 3336 13855.32\n", + "88 8820 6569.785\n", + "89 4792 8718.28\n", + "90 1650 23018.465\n", + "91 2000 7827.75\n", + "92 2111 3149.185\n", + "93 4470 12892.46\n", + "94 7000 6091.705\n", + "95 3850 6530.255\n", + "96 4366 10065.345\n", + "97 2645 9916.91\n", + "98 2384 6651.595\n", + "99 4522 14023.15\n", + "100 6328 16536.015\n", + "101 3877 22520.085\n", + "102 2000 5087.5858333333335\n", + "103 4157 4995.895\n", + "104 2942 14835.2\n", + "105 3655 11868.02\n", + "106 4500 2356.345\n", + "107 2964 3001.075\n", + "108 2863 2862.48\n", + "109 4935 11530.06\n", + "110 2526 13431.275\n", + "111 5679 8583.235\n", + "112 7286 12046.48\n", + "113 6055 12324.2175\n", + "114 1200 10979.595\n", + "115 4110 3696.885\n", + "116 1957 1784.81\n", + "117 1790 2310.685\n", + "118 5422 5891.27\n", + "119 4650 12989.17\n", + "120 5297 13769.85\n", + "121 3036 7880.975\n", + "122 3233 10235.395\n", + "123 12000 2019.825\n", + "124 7632 1784.93\n", + "125 3620 8620.28\n", + "126 2000 2247.685\n", + "127 2145 5211.695\n", + "128 2227 16537.25\n", + "129 2520 16835.395\n", + "130 10000 8674.06\n", + "131 2000 5183.62\n", + "132 4052 5794.44\n", + "133 2137 3961.6\n", + "134 2609 10219.755\n", + "135 3256 3750.165\n", + "136 8173 7718.525\n", + "137 1250 5951.49\n", + "138 7401 2454.21\n", + "139 4200 9402.87\n", + "140 6100 6322.165\n", + "141 5182 4632.07\n", + "142 1300 3705.42\n", + "143 2117 2149.76\n", + "144 6500 5198.585\n", + "145 5174 24179.485\n", + "146 3867 7086.495\n", + "147 1918 3776.675\n", + "148 2800 7035.84\n", + "149 7648 6624.045\n", + "150 5638 4533.465\n", + "151 5262 8931.215\n", + "152 1650 10611.175\n", + "153 1657 5332.755\n", + "154 4086 16697.77\n", + "155 9000 4076.79\n", + "156 1500 4362.885\n", + "157 8145 15920.005\n", + "158 2625 8195.605\n", + "159 6281 2241.7\n", + "160 4520 2029.15\n", + "161 1200 2951.745\n", + "162 4829 4604.96\n", + "163 1760 3564.905\n", + "164 3469 9817.235\n", + "165 7500 5750.205\n", + "166 5227 6575.79\n", + "167 1765 12158.355\n", + "168 5200 3810.195\n", + "169 6402 7280.82\n", + "170 4832 3182.905\n", + "171 1500 9403.08\n", + "172 2342 3885.02\n", + "173 2799 2147.4\n", + "174 3850 3098.345\n", + "175 4200 3161.31\n", + "176 4531 1797.735\n", + "177 1751 1703.18\n", + "178 4250 5936.4125\n", + "179 5705 7906.34\n", + "180 3528 1578.835\n", + "181 2496 5813.035\n", + "182 4370 1658.86\n", + "183 1350 3784.05\n", + "184 5334 9792.665\n", + "185 1423 6670.74\n", + "186 4129 3725.405\n", + "187 5858 2032.275\n", + "188 3300 4597.795\n", + "189 3500 4803.27\n", + "190 10280 3359.415\n", + "191 4500 8830.49625\n", + "192 10500 3642.33\n", + "193 3932 5338.775\n", + "194 5500 5575.605\n", + "195 2200 16004.22\n", + "196 8206 4797.275\n", + "197 8000 5357.22\n", + "198 6372 8878.1\n", + "199 7900 13958.58\n", + "200 1628 13360.12\n", + "201 4142 3265.425\n", + "202 1150 8374.72\n", + "203 1750 4471.255\n", + "204 4072 4537.83\n", + "205 1176 14088.795\n", + "206 3200 4798.96\n", + "207 1715 12487.48\n", + "208 7530 10884.455\n", + "209 4600 7020.67\n", + "210 1200 5571.775\n", + "211 4452 16737.72\n", + "212 2400 3627.235\n", + "213 4057 3048.885\n", + "214 5000 7612.795\n", + "215 4147 1906.285\n", + "216 3046 11647.86\n", + "217 6215 2800.26\n", + "218 3350 2044.6\n", + "219 3500 1575.61\n", + "220 5870 3940.91\n", + "221 4113 6889.23\n", + "222 3420 17471.3\n", + "223 6116 7448.86\n", + "224 5902 3766.12\n", + "225 3787 3984.03\n", + "226 3600 6773.825\n", + "227 7017 2120.505\n", + "228 10280 5304.46\n", + "229 5600 9301.37\n", + "230 3656 3520.035\n", + "231 6480 14618.295\n", + "232 1646 5412.37\n", + "233 2600 4642.425\n", + "234 4300 2358.205\n", + "235 7948 3387.4\n", + "236 4579 6892.56\n", + "237 4364 6032.51\n", + "238 5000 3701.665\n", + "239 2034 2459.645\n", + "240 3500 4322.685\n", + "241 5688 7433.355\n", + "242 1800 11416.59\n", + "243 10131 3018.53\n", + "244 5784 5802.455\n", + "245 1813 4643.985\n", + "246 3700 4748.285\n", + "247 6700 7172.535\n", + "248 3700 2260.81\n", + "249 7799 2543.05\n", + "250 1884 11840.945\n", + "251 3042 5026.895\n", + "252 5000 12697.255\n", + "253 5112 12816.485\n", + "254 1404 2985.83\n", + "255 2471 4937.79\n", + "256 7749 8946.405\n", + "257 6254 3540.255\n", + "258 2502 11979.99\n", + "259 2300 3865.78\n", + "260 6500 10954.815\n", + "261 2646 2107.79\n", + "262 9546 5031.68\n", + "263 7500 2161.645\n", + "264 11016 6702.55\n", + "265 5763 9477.27\n", + "266 2460 9735.075\n", + "267 5511 1635.865\n", + "268 1857 11158.305\n", + "269 7000 1856.91\n", + "270 6333 2795.195\n", + "271 6107 3487.66\n", + "272 1518 2304.03\n", + "273 9310 16034.98\n", + "274 3551 6612.595\n", + "275 1700 4897.935\n", + "276 2250 8678.285\n", + "277 6000 2745.995\n", + "278 2003 10819.06\n", + "279 15183 4209.865\n", + "280 7113 2508.52\n", + "281 3818 8803.065\n", + "282 12300 3207.05\n", + "283 12488 5297.225\n", + "284 8000 13887.795\n", + "285 10832 6841.995\n", + "286 2107 5646.825\n", + "287 2100 10862.95\n", + "288 14135 4668.9625\n", + "289 6115 3493.45\n", + "290 9364 6440.755\n", + "291 4773 12457.905\n", + "292 3525 4335.24\n", + "293 6126 3478.67\n", + "294 6487 6093.975\n", + "295 3879 5981.07\n", + "296 4943 2130.5\n", + "297 1335 18139.0\n", + "298 4125 2991.4\n", + "299 7986 20566.615\n", + "300 5000 1574.75\n", + "301 3559 12854.765\n", + "302 6573 6217.415\n", + "303 2300 7889.96\n", + "304 5117 7310.34\n", + "305 5000 6879.69\n", + "306 7165 6601.825\n", + "307 1406 4288.72\n", + "308 12300 5307.19\n", + "309 3573 4497.045\n", + "310 6500 6256.32\n", + "311 4508 5486.1\n", + "312 7546 3663.385\n", + "313 5413 7938.35\n", + "314 5754 3178.54\n", + "315 1307 3337.145\n", + "316 5433 2951.285\n", + "317 2304 4515.835\n", + "318 4000 10191.245\n", + "319 6425 6563.885\n", + "320 7250 14309.845\n", + "321 5500 8663.35\n", + "322 1800 6115.675\n", + "323 2240 3341.57\n", + "324 9000 9561.66\n", + "325 1266 6050.45\n", + "326 3850 5461.605\n", + "327 2122 5666.745\n", + "328 6423 4170.395\n", + "329 6455 12976.51\n", + "330 2100 9193.3\n", + "331 7843 6734.765\n", + "332 9617 3180.015\n", + "333 5033 10773.97125\n", + "334 1129 1717.375\n", + "335 1500 9708.96\n", + "336 8932 4352.705\n", + "337 4637 8567.97\n", + "338 15327 7835.35\n", + "339 1233 3639.265\n", + "340 2364 6209.395\n", + "341 10316 11016.235\n", + "342 13200 2119.405\n", + "343 1303 2709.155\n", + "344 8687 1634.185\n", + "345 1653 13753.1375\n", + "346 7067 4695.66\n", + "347 8265 8292.81\n", + "348 1587 4810.64\n", + "349 2479 7368.865\n", + "350 6366 3286.005\n", + "351 5114 4085.725\n", + "352 6138 9741.92\n", + "353 1765 5426.53\n", + "354 3129 1673.915\n", + "355 2295 3511.86\n", + "356 5507 4669.1925\n", + "357 5200 12605.555\n", + "358 6326 15411.865\n", + "359 10804 11185.52\n", + "360 6721 5787.13\n", + "361 5574 3080.71\n", + "362 10020 5907.88\n", + "363 3678 3327.455\n", + "364 4342 6535.975\n", + "365 8000 14431.15\n", + "366 1687 5395.356666666667\n", + "367 1967 5023.89\n", + "368 27252 5910.655\n", + "369 20520 9579.255\n", + "370 10000 17502.835\n", + "371 1661 5887.475\n", + "372 1356 4524.945\n", + "373 8000 13422.775\n", + "374 6288 8526.295\n", + "375 9979 12948.585\n", + "376 3083 7691.755\n", + "377 1574 11412.42\n", + "378 10452 2908.155\n", + "379 4790 3480.47\n", + "380 5563 4298.075\n", + "381 1103 4372.96\n", + "382 3846 9778.52\n", + "383 3750 2400.16\n", + "384 4309 5070.415\n", + "385 6254 1577.175\n", + "386 2133 4828.135\n", + "387 12800 7410.605\n", + "388 5300 1702.315\n", + "389 10102 2563.91\n", + "390 9326 6329.725\n", + "391 2613 7333.205\n", + "392 11976 4998.75\n", + "393 12143 2995.995\n", + "394 13200 4269.515\n", + "395 6320 5055.135\n", + "396 1542 13829.665\n", + "397 4560 4800.095\n", + "398 8304 5038.89\n", + "399 10480 3193.88\n", + "400 5352 4633.325\n", + "401 2137 7124.72\n", + "402 5169 6736.805\n", + "403 2799 13044.645\n", + "404 6000 2021.2\n", + "405 4986 3783.67\n", + "406 1824 6328.3\n", + "407 1562 4818.96\n", + "408 5890 12351.575\n", + "409 6077 8368.7\n", + "410 1485 2307.815\n", + "411 1825 4152.695\n", + "412 5340 3259.93\n", + "413 9237 1645.765\n", + "414 6499 7667.76125\n", + "415 1240 4280.79\n", + "416 4656 4129.7\n", + "417 2335 5560.904166666666\n", + "418 8000 11956.94\n", + "419 5641 7928.66\n", + "420 1444 7371.75\n", + "421 12900 2452.485\n", + "422 6500 3510.74\n", + "423 7506 13593.255\n", + "424 6438 10959.67\n", + "425 2261 13815.8\n", + "426 2121 7264.35\n", + "427 5437 3966.71\n", + "428 1536 3881.21\n", + "429 9295 14548.765\n", + "430 3252 6801.995\n", + "431 1331 2413.215\n", + "432 5442 9457.055\n", + "433 4527 3871.07\n", + "434 6500 5900.165\n", + "435 1238 4079.73\n", + "436 10702 4984.83\n", + "437 8056 6596.595\n", + "438 4517 3666.385\n", + "439 5108 12268.525\n", + "440 6354 9083.105\n", + "441 4322 3158.935\n", + "442 3129 6390.115\n", + "443 2486 16959.01\n", + "444 6200 3927.465\n", + "445 10320 6169.805\n", + "446 5204 11800.575\n", + "447 7429 17239.05\n", + "448 1837 4729.9\n", + "449 3311 6117.47\n", + "450 5425 4203.0\n", + "451 1141 7236.865\n", + "452 8142 4073.855\n", + "453 9630 6304.535\n", + "454 3400 2734.205\n", + "455 5991 7359.03\n", + "456 4537 2602.975\n", + "457 1389 8594.86\n", + "458 6560 13770.56\n", + "459 5417 7300.155\n", + "460 1326 12498.02\n", + "461 1226 12151.51\n", + "462 9439 7763.215\n", + "463 6075 2155.91\n", + "464 4139 11945.41\n", + "465 6921 4866.605\n", + "466 1412 4655.13\n", + "467 1580 3638.64\n", + "468 6480 3190.87\n", + "469 7740 8526.32\n", + "470 9187 3697.575\n", + "471 5923 16333.9175\n", + "472 1690 9985.465\n", + "473 1829 8347.22\n", + "474 13132 13420.35\n", + "475 5673 14952.235\n", + "476 10143 15199.465\n", + "477 2631 2170.875\n", + "478 16753 3160.5\n", + "479 8300 6444.085\n", + "480 2541 9135.885\n", + "481 1638 9089.395\n", + "482 6097 14731.42\n", + "483 8250 10358.07\n", + "484 1638 2472.25\n", + "485 1145 4099.34\n", + "486 8300 4606.435\n", + "487 9750 12722.03\n", + "488 12532 15722.44\n", + "489 10739 5063.045\n", + "490 18230 9996.75\n", + "491 6125 8251.825\n", + "492 6225 3833.575\n", + "493 16509 2648.755\n", + "494 6782 13865.8\n", + "495 6125 6070.855\n", + "496 1681 15837.77\n", + "497 1798 5082.45\n", + "498 13385 4033.305\n", + "499 12300 11861.3\n", + "500 4100 8838.96\n", + "501 6190 1632.66\n", + "502 9246 3630.915\n", + "503 14322 5414.425\n", + "504 7396 3048.95\n", + "505 3851 12195.32\n", + "506 4734 1627.625\n", + "507 2058 5133.655\n", + "508 8869 5051.91\n", + "509 11269 7680.75\n", + "510 2506 4868.0\n", + "511 11730 11872.185\n", + "512 8045 10004.22\n", + "513 7500 22047.495\n", + "514 9166 2411.96\n", + "515 5368 1678.645\n", + "516 2395 9312.065\n", + "517 9087 7342.77\n", + "518 7407 8699.05\n", + "519 5949 4514.755\n", + "520 10216 2410.22\n", + "521 4731 2032.19\n", + "522 9248 6795.98\n", + "523 18500 8829.36\n", + "524 6308 6920.595\n", + "525 5748 7288.28\n", + "526 3138 12551.665\n", + "527 2012 9096.635\n", + "528 8657 3791.66\n", + "529 7500 5467.56\n", + "530 1463 18574.32\n", + "531 7625 8138.19\n", + "532 17260 2172.135\n", + "533 7020 7228.85\n", + "534 6592 14669.08\n", + "535 1463 9407.33\n", + "536 5112 15252.19\n", + "537 9672 13353.205\n", + "538 5360 2470.61\n", + "539 7338 4989.74\n", + "540 4113 4484.305\n", + "541 5443 7201.3\n", + "542 7368 3404.155\n", + "543 8017 14148.48\n", + "544 8619 6160.425\n", + "545 2651 2679.64\n", + "546 14840 5971.57\n", + "547 6041 6304.95\n", + "548 8685 4880.825\n", + "549 1252 10886.525\n", + "550 2655 6104.335\n", + "551 15140 11179.75\n", + "552 7885 7929.78\n", + "553 8685 7818.845\n", + "554 7542 5478.925\n", + "555 4676 12477.1055\n", + "556 2450 5207.06\n", + "557 7225 9006.11\n", + "558 18500 2964.14\n", + "559 8499 12498.02\n", + "560 5057 13908.8225\n", + "561 8418 8635.185\n", + "562 22885 4714.2\n", + "563 2820 15387.885\n", + "564 1868 6215.625\n", + "565 2523 7000.925\n", + "566 10058 4604.805\n", + "567 7138 7506.575\n", + "568 7610 9124.655\n", + "569 2670 2921.225\n", + "570 2364 13164.26\n", + "571 8435 4459.1\n", + "572 8841 2059.765\n", + "573 15240 6943.635\n", + "574 10180 12877.445\n", + "575 12534 5554.46\n", + "576 20520 5569.985\n", + "577 13500 2075.635\n", + "578 5000 5377.4\n", + "579 12813 6347.0\n", + "580 7050 4979.655\n", + "581 6665 1697.77\n", + "582 16350 12666.47\n", + "583 25623 14755.155\n", + "584 2063 7468.775\n", + "585 3393 3799.25\n", + "586 2217 7613.54\n", + "587 9003 4315.82\n", + "588 14470 14461.775\n", + "589 7603 8187.18\n", + "590 8685 5318.81\n", + "591 6436 3784.09\n", + "592 6112 14373.265\n", + "593 6127 5815.515\n", + "594 1373 15596.585\n", + "595 8046 7578.585\n", + "596 6865 2831.665\n", + "597 8286 8018.355\n", + "598 6302 6389.695\n", + "599 2208 10284.765\n", + "600 3615 8835.65\n", + "601 15940 1988.025\n", + "602 5010 13787.54\n", + "603 8212 1622.005\n", + "604 1272 3604.73\n", + "605 2540 2657.585\n", + "606 9600 4641.735\n", + "607 26043 9152.84\n", + "608 6103 10734.435\n", + "609 2747 4064.025\n", + "610 2960 6617.965\n", + "611 26043 7470.415\n", + "612 11444 6470.97\n", + "613 6608 6619.14\n", + "614 8685 4750.37\n", + "615 7809 2736.045\n", + "616 10910 16122.215\n", + "617 6015 8841.82\n", + "618 5233 5604.625\n", + "619 1425 18549.55\n", + "620 11160 15778.39\n", + "621 2105 7980.145\n", + "622 7428 9160.515\n", + "623 5204 4859.245\n", + "624 1851 3186.285\n", + "625 26043 3114.36\n", + "626 5426 3059.535\n", + "627 6219 16506.235\n", + "628 11212 10394.045\n", + "629 8124 2754.425\n", + "630 1982 17523.185\n", + "631 3694 5186.16\n", + "632 6075 2738.09\n", + "633 4561 2731.69\n", + "634 3042 4152.18\n", + "635 19747 6661.585\n", + "636 15145 2865.705\n", + "637 7072 3242.73\n", + "638 2582 2405.715\n", + "639 1425 4791.017083333334\n", + "640 5219 18050.315\n", + "641 7182 2914.96\n", + "642 8899 5130.16\n", + "643 6313 1917.815\n", + "644 2435 2274.995\n", + "645 3108 4957.4\n", + "646 12198 6797.66\n", + "647 5761 4253.455\n", + "648 8685 3508.57\n", + "649 8141 7389.745\n", + "650 9185 6143.315\n", + "651 5331 5715.955\n" + ] + }, + { + "ename": "IndexError", + "evalue": "index 652 is out of bounds for axis 0 with size 652", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_11017/1621740581.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my_pred_rf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mIndexError\u001b[0m: index 652 is out of bounds for axis 0 with size 652" + ] + } + ], + "source": [ + "for i,v in enumerate(y):\n", + " print(i,v,y_pred_rf[i])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bba1ad86", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/simple_models.ipynb b/machine_learning/scripts/ml/simple_models.ipynb new file mode 100644 index 0000000..f28fc2c --- /dev/null +++ b/machine_learning/scripts/ml/simple_models.ipynb @@ -0,0 +1,295 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import F\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# data cleaning\n", + "df['time'] = df['time'].replace('','0')\n", + "df = df[df['attendance'] != 0]\n", + "\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: r['time'].replace(':','') < \"1800\", axis=1)\n", + "df['before2010'] = df.apply(lambda r: r['historic_season'].split('-')[0] < \"2010\", axis=1)\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'homeTeam_id', 'awayTeam_id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "53545faa", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt \n", + "import seaborn as sns \n", + "from sklearn.model_selection import train_test_split,cross_val_score, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.tree import DecisionTreeRegressor\n", + "from sklearn.ensemble import RandomForestRegressor" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "45e08026", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mutiple Linear Regression Accuracy: 0.3819963751047786\n", + "Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: 0.33440778552391626\n" + ] + } + ], + "source": [ + "lin_reg = LinearRegression()\n", + "lin_reg.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_lr = lin_reg.predict(X_test)\n", + "\n", + "#Mutiple Linear Regression Accuracy with test set\n", + "accuracy_lf = metrics.r2_score(y_test, y_pred_lr)\n", + "print('Mutiple Linear Regression Accuracy: ', accuracy_lf)\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_kf_lr = cross_val_predict(lin_reg, X, y, cv=10 )\n", + "\n", + "#Mutiple Linear Regression Accuracy with cross validation (KFold method)\n", + "accuracy_lf = metrics.r2_score(y, y_pred_kf_lr)\n", + "print('Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: ', accuracy_lf)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0de49b8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cross-Predicted(KFold) Polynominal Regression Accuracy: -261.39170432313074\n" + ] + } + ], + "source": [ + "poly_reg = PolynomialFeatures(degree = 2)\n", + "X_poly = poly_reg.fit_transform(X)\n", + "lin_reg_pl = LinearRegression()\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_pl = cross_val_predict(lin_reg_pl, X_poly, y, cv=10 )\n", + "#Polynominal Regression Accuracy with cross validation\n", + "accuracy_pl = metrics.r2_score(y, y_pred_pl)\n", + "print('Cross-Predicted(KFold) Polynominal Regression Accuracy: ', accuracy_pl)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "470425b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Decision Tree Regression Accuracy: 0.23642868476932866\n", + "Cross-Predicted(KFold) Decision Tree Regression Accuracy: 0.4183541357709245\n" + ] + } + ], + "source": [ + "dt_regressor = DecisionTreeRegressor(random_state = 0)\n", + "dt_regressor.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_dt = dt_regressor.predict(X_test)\n", + "\n", + "#Decision Tree Regression Accuracy with test set\n", + "print('Decision Tree Regression Accuracy: ', dt_regressor.score(X_test,y_test))\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_dt = cross_val_predict(dt_regressor, X, y, cv=10 )\n", + "#Decision Tree Regression Accuracy with cross validation\n", + "accuracy_dt = metrics.r2_score(y, y_pred_dt)\n", + "print('Cross-Predicted(KFold) Decision Tree Regression Accuracy: ', accuracy_dt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6629826f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/ml/train_attendance.ipynb b/machine_learning/scripts/ml/train_attendance.ipynb new file mode 100644 index 0000000..9cc0bfb --- /dev/null +++ b/machine_learning/scripts/ml/train_attendance.ipynb @@ -0,0 +1,859 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 247, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import Count, F, Value\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity')\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 248, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['time', 'historic_season']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "code", + "execution_count": 249, + "id": "e69d24dc", + "metadata": {}, + "outputs": [], + "source": [ + "#Importing Libraries\n", + "import numpy as np # linear algebra\n", + "import pandas as pd # data processing\n", + "import matplotlib.pyplot as plt # plotting library\n", + "from sklearn.model_selection import train_test_split,cross_val_score, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.tree import DecisionTreeRegressor\n", + "from sklearn.ensemble import RandomForestRegressor" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time', 'historic_season', 'id', 'homeTeam_id', 'awayTeam_id']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "markdown", + "id": "94ade4b4", + "metadata": {}, + "source": [ + "#### Decision Tree" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "id": "4c9bdd0d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FITTING...done\n", + "VISUALIZE\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 183, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pydotplus\n", + "from six import StringIO\n", + "from sklearn.tree import export_graphviz\n", + "from sklearn.tree import DecisionTreeRegressor \n", + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "\n", + "# Create Decision Tree classifer object\n", + "regr = DecisionTreeRegressor(max_depth=5, random_state=1234)\n", + "\n", + "# Train Decision Tree Classifer\n", + "print(\"FITTING...\", end=\"\")\n", + "regr = regr.fit(X_train, y_train)\n", + "print(\"done\")\n", + "\n", + "# Predict the response for test dataset\n", + "y_pred = regr.predict(X_test)\n", + "\n", + "\n", + "# %%\n", + "\n", + "\n", + "print(\"VISUALIZE\")\n", + "dot_data = StringIO()\n", + "export_graphviz(regr, out_file=dot_data,\n", + " filled=True, rounded=True,\n", + " special_characters=True, feature_names=feature_cols)\n", + "graph = pydotplus.graph_from_dot_data(dot_data.getvalue())\n", + "graph.write_png('attendance.png')\n", + "# Image(graph.create_png())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3297f84", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c2e02abe", + "metadata": {}, + "source": [ + "#### Other Models" + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "id": "3eeb8fa4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Suppport Vector Regression Accuracy: -0.026734414429896436\n", + "R2 square: -0.026734414429896436\n", + "MAE: 3585.035752797511\n", + "MSE: 36159653.599150375\n" + ] + } + ], + "source": [ + "from sklearn.svm import SVR\n", + "from sklearn import metrics\n", + "regressor= SVR(kernel='rbf')\n", + "regressor.fit(X_train,y_train)\n", + "y_pred_svm=regressor.predict(X_test)\n", + "#y_pred_svm = cross_val_predict(regressor, x, y)\n", + "mae=metrics.mean_absolute_error(y_test, y_pred_svm)\n", + "mse=metrics.mean_squared_error(y_test, y_pred_svm)\n", + "# Printing the metrics\n", + "print('Suppport Vector Regression Accuracy: ', regressor.score(X_test,y_test))\n", + "print('R2 square:',metrics.r2_score(y_test, y_pred_svm))\n", + "print('MAE: ', mae)\n", + "print('MSE: ', mse)" + ] + }, + { + "cell_type": "markdown", + "id": "1899ba5a", + "metadata": {}, + "source": [ + "#### Correlation Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "id": "738f39ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 197, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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xo1iux22XWWb7bttx/Pjn2b8G2O4/OpLytd33W4bhj37OM+MHcHI0cPovlM3ANqs24Z/33MLz40bxVPPP8FWW29VkGNpLC676iLGTRrNMy89xXY7dF1lm+122JZnx41g3KTRXHbVReXrbxp8PU+PGcbTY4bxyltjeHpM5nvXYUccUr7+6THDmLngY7put3VBjkcZ5557Lj179qR///51HYoquPbaa5kyZQqvvfYa3bp1W2WbSy65hI8//pgFCyrf72XgwIHMmjWLiRMnMnHiRI4//vhChNzo7LVHD557ajCjR93ByScdudL2NdZozg3XncfoUXfw6P3X06njRgCst25rhtx5De9OGsol559W3r5FizX596DLeXb4YEY9cSvnnnVCwY5FVVtawP/qQrVJakqpVUqpdfbfJiml5tlHk5RS62XtImKb/Iaaf/v025tNN9uUHl17cuYpv+Mv112xynZ/ue4Kzjj5t/To2pNNN9uUvfv2AeCCS//MlZdezV677sPlF1/J+ZdmZug64+zf8Pprb7LHzn349fGncvlfLl7lfpW7ffr1YdPNN2Hn7Xrx21PO4cq/Xb7Kdlddezlnnnw2O2/Xi00334S9+/YG4ISBv6Z3z3707tmPxx4eymMPPwHA/fc8WL7+17/4DR99+DFvTHqrYMclOPTQQxk0aFD1DVUw+++/P126dKFLly6ceOKJ3HDDDats9+ijj7Lzzjuvcts999xDt27d6NatGzfffHM+w22UmjRpwqUX/IZjfv57evc7lh8fuDddNv9+pTZHDjiAefMW0KvPMdx06xD+eM5JAHzz7SKuvOYWLr585fP6z0H3sGffgfQ76AR67NiV3nuu+vxKtSmXy/3Vub0W91Un9u/fj3vuuheAl8dNoE2b1mxUUvl+BRuVtKNVq5a8PG4CAPfcdS8HHLgfACklWrVuBUDrNq2YUToDgC232oLnnx0NwJR3p9L5+53ZsN0GBTmmhm6/H/XlnrvuA+CV8VWfs1fGLztn97F//34r7evgQw/kwSEPr7T+0AEH89B9j+QhelWlR48etGnTpq7DUAUHH3ww//73vwF46aWXWHfddSkpKVmp3UsvvcSMGTMKHZ6AbttvxYcfTefjT0pZvHgJDz82in777FapTd99dmPIA5mrRo8/8Sy9enYHYOHCbxj/yht8u2hRpfbffPMtL459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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "# GETTING Correllation matrix\n", + "corr_mat=X_train.corr(method='pearson')\n", + "plt.figure(figsize=(20,10))\n", + "sns.heatmap(corr_mat,vmax=1,square=True,annot=True,cmap='cubehelix')" + ] + }, + { + "cell_type": "code", + "execution_count": 198, + "id": "38f78b1c", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "X_Train=X_train.values\n", + "X_Train=np.asarray(X_Train)\n", + "\n", + "# Finding normalised array of X_Train\n", + "X_std=StandardScaler().fit_transform(X_Train)" + ] + }, + { + "cell_type": "code", + "execution_count": 199, + "id": "ab28ec86", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Cumulative explained variance')" + ] + }, + "execution_count": 199, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.decomposition import PCA\n", + "pca = PCA().fit(X_std)\n", + "plt.plot(np.cumsum(pca.explained_variance_ratio_))\n", + "plt.xlim(0,7,1)\n", + "plt.xlabel('Number of components')\n", + "plt.ylabel('Cumulative explained variance')" + ] + }, + { + "cell_type": "code", + "execution_count": 200, + "id": "b40d92f1", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.\n", + " FutureWarning\n", + "/home/md/miniconda3/envs/leagues/lib/python3.7/site-packages/seaborn/distributions.py:1718: UserWarning: `shade_lowest` is now deprecated in favor of `thresh`. Setting `thresh=0.05`, but please update your code.\n", + " warnings.warn(msg, UserWarning)\n" + ] + }, + { + "data": { + "text/plain": [ + "(-2.0, 2.0)" + ] + }, + "execution_count": 200, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from sklearn.decomposition import PCA\n", + "sklearn_pca=PCA(n_components=5)\n", + "X_Train=sklearn_pca.fit_transform(X_std)\n", + "\n", + "sns.set(style='darkgrid')\n", + "f, ax = plt.subplots(figsize=(8, 8))\n", + "# ax.set_aspect('equal')\n", + "ax = sns.kdeplot(X_Train[:,0], X_Train[:,1], cmap=\"Greens\",\n", + " shade=True, shade_lowest=False)\n", + "ax = sns.kdeplot(X_Train[:,1], X_Train[:,2], cmap=\"Reds\",\n", + " shade=True, shade_lowest=False)\n", + "ax = sns.kdeplot(X_Train[:,2], X_Train[:,3], cmap=\"Blues\",\n", + " shade=True, shade_lowest=False)\n", + "red = sns.color_palette(\"Reds\")[-2]\n", + "blue = sns.color_palette(\"Blues\")[-2]\n", + "green = sns.color_palette(\"Greens\")[-2]\n", + "ax.text(0.5, 0.5, \"2nd and 3rd Projection\", size=12, color=blue)\n", + "ax.text(-4, 0.0, \"1st and 3rd Projection\", size=12, color=red)\n", + "ax.text(2, 0, \"1st and 2nd Projection\", size=12, color=green)\n", + "plt.xlim(-6,5)\n", + "plt.ylim(-2,2)" + ] + }, + { + "cell_type": "code", + "execution_count": 202, + "id": "aa1bd2a4", + "metadata": {}, + "outputs": [], + "source": [ + "number_of_samples = len(y_train)\n", + "np.random.seed(0)\n", + "random_indices = np.random.permutation(number_of_samples)\n", + "num_training_samples = int(number_of_samples*0.75)\n", + "x_train = X_Train[random_indices[:num_training_samples]]\n", + "y_train=y[random_indices[:num_training_samples]]\n", + "x_test=X_Train[random_indices[num_training_samples:]]\n", + "y_test=y[random_indices[num_training_samples:]]\n", + "y_Train=list(y_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 204, + "id": "2acb04aa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.44699684022491 percent in Ridge Regression\n", + "Test error = 99.98330172684955 percent in Ridge Regression\n" + ] + } + ], + "source": [ + "from sklearn import linear_model\n", + "model=linear_model.Ridge()\n", + "model.fit(x_train,y_train)\n", + "y_predict=model.predict(x_train)\n", + "\n", + "error=0\n", + "for i in range(len(y_Train)):\n", + " error+=(abs(y_Train[i]-y_predict[i])/y_Train[i])\n", + "train_error_ridge=error/len(y_Train)*100\n", + "print(\"Train error = \"'{}'.format(train_error_ridge)+\" percent in Ridge Regression\")\n", + "\n", + "Y_test=model.predict(x_test)\n", + "y_Predict=list(y_test)\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y_Predict[i]-Y_test[i])/y_Predict[i])\n", + "test_error_ridge=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_ridge)+\" percent in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "id": "10001f77", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Ridge Regression')" + ] + }, + "execution_count": 206, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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EBERgS+++AIvvPACYmNj0aZNGzz22GNYtWoVAECj0aCyshIlJSXQarXIyckBwzDgOA5GoxFnzpyByWRChw4dbAMpe1avXo28vDz0798fWq0W06dPR319vYNATp48GUlJSYiNjcWdd97p8XkRjpCBlQgaCxYsQL9+/bBv3z785S9/waVLlxAdHY2SkhKsXbvWoVM2m83Izc2FXq/HW2+9hU8++QR///vf0atXL/z1r39Fly5dHM5dUVGB5ORk298MwyAlJUV22bZu3YoFCxbg7Nmz4Hke9fX16N69u+zf21+rffv2qKiocPlORUUFUlNTHT5LTU1FeXm57Os4k5CQYPt3q1atUFtb6/a74pqR0WjEm2++iT179mDq1Kku36uqqoLZbHaok325pe61/d8lJSV47bXXMG/ePNtngiCgvLxcckYBAHv27AHDMPj000+xcuVKmEwm6HQ6j+fyVg4ADn9XVVWhrq4O9913n8O5RFPc9OnT8f7779s8NMePH49HH30UHTt2xAsvvID33nsPp0+fxh133IGZM2ciKSnJ4VrOz5dlWaSkpDg8X+fnJdVOCGloZkQEnT59+uC+++6zdTApKSkYN24cDhw4YPvv8OHDNo+qAQMGYPHixdixYwc6d+6MF1980eWcCQkJKCsrs/0tCAJKS0ttf7dq1cq2NgAAFy9etP3baDRixowZeOSRR7Bz504cOHAAAwcOhC8B6+2vVVJSgsTERJfvJCYmoqSkxOV3zp1aqNHpdHj22Wfxyy+/YOPGjS7H4+LioNFoHOpk/++EhASHDlYQBId7n5KSgpdeesnheR45cgS9evXyWC6O4zBt2jRERETg888/93oub+UArAIl0rZtW0RGRmLVqlW2c/3444+2mUubNm0wc+ZMbNq0Cf/+97+xePFi29rQmDFjsHTpUmzZsgUMw+DNN990Kb/z8xXbYLifb3OFxIgICb/97W+xa9cunDhxAmPHjsWWLVuwfft2WCwWNDQ0YO/evSgrK8PFixexceNG1NbWQqfTQa/Xg2Vdm2VeXh5OnTqF9evXw2w249NPP3UQnIyMDOzfvx8lJSW4cuUKPvzwQ9sxo9EIo9Fo64S3bt2KnTt3+lSfRYsWobq6GqWlpfj0008xatQoyTKePXsWK1euhNlsxurVq3H69GkMGjQIANCuXTu3+3yCjU6nwyOPPGJb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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib\n", + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "\n", + "preds = pd.DataFrame({\"preds\":model.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 208, + "id": "aef6ee00", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 93.27939871725515 percent in Knn algorithm\n", + "Test error = 34.00485323676349 percent in knn algorithm\n" + ] + } + ], + "source": [ + "from sklearn import neighbors\n", + "n_neighbors=5\n", + "knn=neighbors.KNeighborsRegressor(n_neighbors,weights='uniform')\n", + "knn.fit(x_train,y_train)\n", + "y1_knn=knn.predict(x_train)\n", + "y1_knn=list(y1_knn)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_knn[i]-y_Train[i])/y_Train[i])\n", + "train_error_knn=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_knn)+\" percent\"+\" in Knn algorithm\")\n", + "\n", + "y2_knn=knn.predict(x_test)\n", + "y2_knn=list(y2_knn)\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_knn[i]-Y_test[i])/Y_test[i])\n", + "test_error_knn=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_knn)+\" percent\"+\" in knn algorithm\")" + ] + }, + { + "cell_type": "code", + "execution_count": 209, + "id": "d141476e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.42901599605693 percent in Bayesian Regression\n", + "Test error = 2.6273348309846623 percent in Bayesian Regression\n" + ] + } + ], + "source": [ + "reg = linear_model.BayesianRidge()\n", + "reg.fit(x_train,y_train)\n", + "y1_reg=reg.predict(x_train)\n", + "y1_reg=list(y1_reg)\n", + "y2_reg=reg.predict(x_test)\n", + "y2_reg=list(y2_reg)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_reg[i]-y_Train[i])/y_Train[i])\n", + "train_error_bay=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_bay)+\" percent\"+\" in Bayesian Regression\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_reg[i]-Y_test[i])/Y_test[i])\n", + "test_error_bay=(error/len(Y_test))*100\n", + "print(\"Test error = \"+'{}'.format(test_error_bay)+\" percent\"+\" in Bayesian Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 236, + "id": "cc4ef924", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 45.1440475751096 percent in Decision Tree Regressor\n", + "Test error = 115.23229120765764 percent in Decision Tree Regressor\n" + ] + } + ], + "source": [ + "from sklearn.tree import DecisionTreeRegressor \n", + "\n", + "dec = DecisionTreeRegressor(max_depth=5, random_state=1234)\n", + "dec.fit(X_train,y_train)\n", + "y1_dec=dec.predict(X_train)\n", + "y1_dec=list(y1_dec)\n", + "y2_dec=dec.predict(X_test)\n", + "y2_dec=list(y2_dec)\n", + "\n", + "error=0\n", + "for i,v in enumerate(y_train):\n", + " error+=(abs(y1_dec[i]-v)/v)\n", + "train_error_tree=error/len(y_train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_tree)+\" percent\"+\" in Decision Tree Regressor\")\n", + "\n", + "error=0\n", + "for i,v in enumerate(y_test):\n", + " error+=(abs(y1_dec[i]-v)/v)\n", + "test_error_tree=error/len(y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_tree)+\" percent in Decision Tree Regressor\")" + ] + }, + { + "cell_type": "code", + "execution_count": 240, + "id": "f8371c65", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mutiple Linear Regression Accuracy: 0.3760362035902819\n", + "Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: 0.26323368310834494\n" + ] + } + ], + "source": [ + "lin_reg = LinearRegression()\n", + "lin_reg.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_lr = lin_reg.predict(X_test)\n", + "\n", + "#Mutiple Linear Regression Accuracy with test set\n", + "accuracy_lf = metrics.r2_score(y_test, y_pred_lr)\n", + "print('Mutiple Linear Regression Accuracy: ', accuracy_lf)\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_kf_lr = cross_val_predict(lin_reg, X, y, cv=10 )\n", + "\n", + "#Mutiple Linear Regression Accuracy with cross validation (KFold method)\n", + "accuracy_lf = metrics.r2_score(y, y_pred_kf_lr)\n", + "print('Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: ', accuracy_lf)" + ] + }, + { + "cell_type": "code", + "execution_count": 241, + "id": "1a05e96e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cross-Predicted(KFold) Polynominal Regression Accuracy: 0.2973208431334121\n" + ] + } + ], + "source": [ + "poly_reg = PolynomialFeatures(degree = 2)\n", + "X_poly = poly_reg.fit_transform(X)\n", + "lin_reg_pl = LinearRegression()\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_pl = cross_val_predict(lin_reg_pl, X_poly, y, cv=10 )\n", + "#Polynominal Regression Accuracy with cross validation\n", + "accuracy_pl = metrics.r2_score(y, y_pred_pl)\n", + "print('Cross-Predicted(KFold) Polynominal Regression Accuracy: ', accuracy_pl)" + ] + }, + { + "cell_type": "code", + "execution_count": 242, + "id": "3002f71b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Decision Tree Regression Accuracy: 0.41316821537538995\n", + "Cross-Predicted(KFold) Decision Tree Regression Accuracy: 0.43545963056661785\n" + ] + } + ], + "source": [ + "dt_regressor = DecisionTreeRegressor(random_state = 0)\n", + "dt_regressor.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_dt = dt_regressor.predict(X_test)\n", + "\n", + "#Decision Tree Regression Accuracy with test set\n", + "print('Decision Tree Regression Accuracy: ', dt_regressor.score(X_test,y_test))\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_dt = cross_val_predict(dt_regressor, X, y, cv=10 )\n", + "#Decision Tree Regression Accuracy with cross validation\n", + "accuracy_dt = metrics.r2_score(y, y_pred_dt)\n", + "print('Cross-Predicted(KFold) Decision Tree Regression Accuracy: ', accuracy_dt)" + ] + }, + { + "cell_type": "code", + "execution_count": 243, + "id": "45e08026", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Random Forest Regression Accuracy: 0.5152096358767789\n", + "Cross-Predicted(KFold) Random Forest Regression Accuracy: 0.5008508177441224\n" + ] + } + ], + "source": [ + "rf_regressor = RandomForestRegressor(n_estimators = 300 , random_state = 0)\n", + "rf_regressor.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_rf = rf_regressor.predict(X_test)\n", + "\n", + "#Random Forest Regression Accuracy with test set\n", + "print('Random Forest Regression Accuracy: ', rf_regressor.score(X_test,y_test))\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_rf = cross_val_predict(rf_regressor, X, y, cv=10 )\n", + "\n", + "#Random Forest Regression Accuracy with cross validation\n", + "accuracy_rf = metrics.r2_score(y, y_pred_rf)\n", + "print('Cross-Predicted(KFold) Random Forest Regression Accuracy: ', accuracy_rf)" + ] + }, + { + "cell_type": "code", + "execution_count": 245, + "id": "0de49b8a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABCwAAALECAYAAADD1aFFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAA2MElEQVR4nO3de5yWdb3v//cM5xxUJDwtD4UpYEsD0gZSzNwuQRRUEG1rHlvaav/cGkWapCLgYaGo5QHdYsnDlQahKAou28uMtETMLHFt0wwPJIriKIcRYZjh3n/4aH6bpeSoyHzB5/Mv7rlOn/uG7wN8eV0zVZVKpRIAAACAglS39gAAAAAA/5VgAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOG1bewCSN998K2vXVlp7DNhsdO1ak7q6+tYeAzYr1hVseNYVbFjW1KanuroqXbpssd7tgkUB1q6tCBawgVlTsOFZV7DhWVewYVlTmxePhAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOJUVSqVSmsPAQAAAHx4q1avyYrlq1p7jA+kuroqXbvWrHd72404C+tx5qV35fU332rtMQAAANhE3XbZ8VmRTStYvB+PhAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIrzsQeLHj165K233vq4L9NiL730Umpra1t7DAAAAODvcIcFAAAAUJy2G+Mi//Zv/5b/+I//yNKlS3P22Wdn4MCBSZIHH3wwV155ZZqamrLNNttk3Lhx2XXXXTNv3rxcfPHF2XvvvfPEE0+kbdu2ueyyy3Lttdfm2WefzQ477JBrrrkmn/rUp9LQ0JCrrroqv/vd79LQ0JAePXrkwgsvzBZbbPF3Z/rXf/3X/Pa3v02SjBkzJvvss08aGxvzzW9+M2+++WZWr16dvffeO2PHjk379u3z+OOPZ/z48Vm7dm0aGxvzrW99K4cffnjq6+tz6aWX5plnnsnq1atTW1ubc889N23atPnYP1cAAADYXG2UOyxqampyxx135LLLLstFF12UJKmrq8vZZ5+diRMn5p577snhhx+eUaNGNR+zYMGCHH/88bnnnnvSu3fvfOMb38i5556be++9N9XV1Zk9e3aS5Kabbkrnzp1z++235+677862226bG2+88e/Os3Tp0vTs2TP33HNPzjvvvHznO99JQ0ND2rRpk4kTJ2bGjBmZNWtWmpqacscddyRJJk+enG984xuZOXNmZs2alQMOOCBJcumll2bffffN7bffnpkzZ+aNN95oPgYAAAD4cDbKHRaDBw9OkvTu3TuvvfZaVq9enSeeeCI9e/bM5z73uSTJ8OHDM3bs2NTX1ydJPvvZz6ZXr15Jkj333DMvv/xytt9++yTJ5z//+bz44otJkgceeCD19fX5xS9+kSRpaGhIz549/+487dq1y9ChQ5MktbW16dixY5577rnsvvvu+clPfpIHH3wwa9euzbJly9KxY8fm/a6//vosXLgw++23X77whS80X3/+/Pm5+eabkySrVq3Kdtttt2E+OAAAAPiE2ijBokOHDknS/JhEY2Pj+x7Tvn375l+3adOm+Rx/e7169eokSaVSyZgxY9K/f/+PPOc999yT3//+97n11ltTU1OTG264IS+88EKS5OSTT85BBx2Uhx9+OOPHj89+++2XkSNHplKpZNKkSdl5550/8vUBAACAd7TaN93s3bt3nn766SxYsCBJcuedd2bPPfdMTU3NBzrPQQcdlClTpmTVqlVJkvr6+uZzrs+aNWtyzz33JEkee+yxrFq1Kt27d8+KFSvSpUuX1NTUZMWKFZk1a1bzMc8//3x22WWXfO1rX8uJJ56YJ598svn6N954Y5qampIkb7zxRv76179+oPcAAAAArGuj3GHxXrbZZptcdtllGTVqVBobG7PNNtvk8ssv/8DnOf3003Pttdfm6KOPTlVVVaqqqnLGGWdkt912W+8xW2+9dZ5++uncdNNNSZIrr7wy7du3z5FHHplf/vKXGTRoULp27ZovfvGLzXdy/Nu//VvmzZuXdu3apX379jnvvPOSJKNHj87ll1+eI444IlVVVWnXrl1Gjx7tjgsAAAD4CKoqlUqltYf4pDvz0rvy+ptvtfYYAAAAbKJuu+z4LFmyorXH+ECqq6vStev6n7JotUdCAAAAANan1R4J+bhdcMEFeeKJJ9b5Wps2bTJjxoxWmggAAABoqc02WIwbN661RwAAAAA+JI+EAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxqiqVSqW1hwAAAAA+vFWr12TF8lWtPcYHUl1dla5da9a7ve1GnIX1qKurz9q1uhFsKN26dc6SJStaewzYrFhXsOFZV7BhWVObH4+EAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAilNVqVQqrT0EAAAAH05jw+q8uayhtcdodd26dc6SJStaeww+gOrqqnTtWrPe7W034iysx5M3nJOG5XWtPQYAALAJ+uLZNyURLNj8eCQEAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4m2Sw6NGjR956660cccQRWbVq1Xr3W758eSZPnrwRJwMAAAA2hE0yWPzNzJkz07Fjx/VuX758eW666aaNOBEAAACwIbRt7QFa4n//7/+dK6+8Mh06dMghhxzS/PUePXrk8ccfT6dOnTJu3Lg88sgjad++fT71qU9l6tSpGTduXFasWJEjjjginTp1ytSpU/OTn/wks2fPTlNTUzp06JALL7wwvXr1aj7fyJEj8x//8R9ZunRpzj777AwcODBJ8oc//CGXXXZZ3nrrrSTJ2Wefnf333z/PPfdcLrnkkrz55ptZs2ZNTjrppAwfPnzjf0gAAACwGSk+WLz++us5//zz87Of/Szdu3d/z0c8nn766cybNy/33ntvqqurs2zZsiTJBRdckOHDh2fmzJnN+x555JE59dRTkyQPP/xwxowZk5///OfN22tqanLHHXfk97//fb797W9n4MCBWbp0ac4444xcc8016du3b5qamlJfX5/GxsaMGjUql19+eXbbbbfU19dn+PDh6d27d3bbbbeP+ZMBAACAzVfxweKJJ57Innvume7duydJjj322EycOHGdfXbeeec0NjbmBz/4QWpra/PVr351vef7z//8z/yv//W/smzZslRVVeWFF15YZ/vgwYOTJL17985rr72W1atX549//GN222239O3bN0nSpk2bbLXVVvnLX/6SBQsW5Dvf+U7z8WvWrMlzzz0nWAAAAMBHUHywaInOnTtn9uzZmTdvXh5++OFMnDgxd95557v2a2hoyFlnnZWf/vSn+fznP59XX301BxxwwDr7dOjQIck7USJJGhsb13vdSqWSLl26rHMHBwAAAPDRFf9NN3v37p2nnnqq+U6I6dOnv2ufN954I2+//XYGDBiQUaNGpXPnzvnrX/+ampqarFq1qjk6NDQ0pLGxMTvssEOS5LbbbmvxDAsWLMgf/vCHJElTU1OWLVuWz372s+nYsWPuuuuu5n0XLFiQ+vr6j/COAQAAgOLvsOjatWvGjx+ff/mXf0nHjh3X+aabf/PKK6/k/PPPT2NjY5qamnLAAQekd+/eqa6uzpAhQzJkyJBstdVWmTp1as4888wcffTR2XrrrZu/oeb72XrrrXPNNdfkX//1X7Ny5cpUV1fnnHPOyZe//OXccMMNueSSS/LjH/84a9euTdeuXfPDH/5wA38KAAAA8MlSValUKq09xCfdkzeck4blda09BgAAsAn64tk3ZcmSFa09Rqvr1q2zz2ETU11dla5da9a/fSPOAgAAANAiggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHGqKpVKpbWHAAAA4MNpbFidN5c1tPYYra5bt85ZsmRFa4/BB1BdXZWuXWvWu73tRpyF9airq8/atboRbCj+soINz7qCDc+6Avj7PBICAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAoTtvWHoCka9ea1h4BNjvdunXeqNdb3dCQ5ctWb9RrAgDA5kywKMCo6WPzev0brT0G8BFMOeVHSQQLAADYUDwSAgAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQnE9csJg3b16GDRvWon0POuig/PnPf/6YJwIAAAD+q09csAAAAADKV2ywmDp1asaOHZskmT9/fnr06JH58+cnSS688MJMmzYtTzzxRE444YQMGzYsw4YNy5w5c5qP//Wvf52vfe1rGTZsWI499tj88Y9/fNc1li9fnhNPPDFTpkxJkjz22GMZMmRIhgwZknHjxqVSqTTvO2HChAwfPjxDhw7NSSedlEWLFiVJxo4dm5tuuql5v6eeeioDBw5c51gAAADggyk2WPTv3z9z585NksydOzd9+vTJI4880vz685//fMaMGZMrrrgiM2bMyA033JALLrggy5cvz8KFCzNp0qTcdNNNmTFjRi666KJ8+9vfXuf8ixYtysknn5zjjjsuJ598choaGjJy5Micd955ueeee7LPPvvk5Zdfbt7/tNNOyx133JG77747hx9+eCZOnJgk+frXv55p06Y1B4qf/vSnOe6441JVVbURPiUAAADYPLVt7QHWZ9ddd83q1auzePHizJ07NyNHjswNN9yQIUOGZM2aNamrq8tLL72U0047rfmYqqqqvPjii5k/f34WLlyY448/vnlbY2NjXn/99STJkiVLcuKJJ2bChAnZZ599kiTPPfdcOnXqlNra2iTJ4MGDc8EFFzQf/+CDD+a2227LypUr09jY2Pz13XbbLTvvvHMefPDB9O7dOw888EDOPffcj/WzAQAAgM1dscEiSfr165df/epXqaurS21tbcaPH585c+aktrY2lUolPXr0yK233vqu4+bPn58BAwbksssue9e2BQsWZKuttsr222+fBx98sDlYvJe/3SWxaNGiXHrppbn99tuz88475/HHH8+oUaOa9zvhhBPys5/9LAsWLMghhxySzp07b4B3DwAAAJ9cxT4SkrwTLCZPnpw+ffokSfr27ZvJkyenf//+6dOnT1588cXmx0SSd0JFpVLJfvvtl4ceeijPPvvsOtv+pn379pk0aVL+8pe/5KKLLkqlUkn37t2zatWqPPbYY0mS++67L8uXL0+S1NfXp127dunWrVvWrl2bqVOnrjPnV77ylTz//PO5+eabc9xxx31snwcAAAB8UhQfLBYtWpT+/fuv87pfv37ZaqutMmnSpFx33XUZOnRoDj300Fx77bWpVCr5zGc+k8svvzw/+MEPmrdNmzZtnXO3b98+V199derq6nL++eenbdu2ufLKKzN27NgMGTIkjz76aHbcccckSY8ePTJo0KAMHjw4I0aMyE477bTOuaqrq3PkkUdmp512Ss+ePTfOhwMAAACbsaqKH2exQZxyyik55phjcuihh37gY0dNH5vX69/4GKYCNpYpp/woS5asaO0x4GPTrVtnf8ZhA7OuYMOypjY91dVV6dq1Zv3bN+Ism6Unn3wyBx98cDp37pyBAwe29jgAAACwWSj6m25uCvbaa6/cf//9rT0GAAAAbFbcYQEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBx2rb2ACQTR4xp7RGAj2h1Q0NrjwAAAJsVwaIAdXX1Wbu20tpjwGajW7fOWbJkRWuPAQAAfAQeCQEAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABSnbWsPQNK1a01rj8An2JpVq7N0RUNrjwEAALAOwaIAv/rOqLz9el1rj8En1OBbbk4ECwAAoDAeCQEAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRngwSLI444IqtWrXrf/aZMmZK6uroNcUkAAABgM7ZBgsXMmTPTsWPH993vlltu+VDBorGx8cOMBQAAAGyi2r7fDlOnTs0zzzyTMWPGZP78+RkxYkSmT5+evffeOxdeeGF69eqVCy64II8//ni22GKLHHTQQTniiCPy8MMPZ8mSJTn11FPz9a9/Pddff31ee+21nHnmmenQoUOuuOKK7LLLLrnqqqvyu9/9Lg0NDenRo0cuvPDCbLHFFvn+97+fNm3a5Pnnn89bb72VmTNnvud8999/f370ox+luro6TU1NOf/881NbW5vXXnstF110UV5++eWsXr06hx12WP7lX/4lSTJhwoQ8+uijWbNmTbp06ZJLLrkk//AP/5C6urp897vfbY4q/fv3z+jRo9PU1JSJEyfmoYceSpIMGDAgo0aNSps2bfL9738/7du3zwsvvJDFixend+/emTBhQqqqqjbU7xEAAAB84rxvsOjfv3+mTJmSJJk7d2769OmTRx55JHvvvXfmzp2bU0899V3HrFq1KtOmTctLL72UIUOG5Kijjsq3vvWtTJ8+PVdffXX22GOPJMmkSZPSuXPn3H777UmSyy+/PDfeeGNGjhyZJPnTn/6Un/70p/nUpz613vmuvvrqjBs3Ln369ElTU1PefvvtJMk555yT//E//kf23XffNDQ05OSTT85ee+2V/fbbL6eddlrOOeecJMn06dMzceLEXHXVVbnnnnuyyy67NL/fZcuWJUmmTZuWP/3pT5kxY0aS5LTTTsu0adNy3HHHJUmeffbZTJkyJVVVVTnqqKPy8MMPZ7/99nu/jxYAAABYj/cNFrvuumtWr16dxYsXZ+7cuRk5cmRuuOGGDBkyJGvWrMkuu+zyrmMGDx6cJNlpp52y5ZZbZvHixdltt93etd8DDzyQ+vr6/OIXv0iSNDQ0pGfPns3bBw0a9HdjRZL069cvl156aQ455JAccMAB2WOPPbJy5co8+uijeeONN5r3e+utt7JgwYLst99+efDBB3Pbbbdl5cqV6zxu8oUvfCFTpkzJhAkT8qUvfSn7779/kndCzVFHHZX27dsnSYYNG5b777+/OVgcfPDB6dChQ5Jkzz33zMKFCwULAAAA+AjeN1gk70SBX/3qV6mrq0ttbW3Gjx+fOXPmpLa29j33/9t/vCdJmzZt0tTU9J77VSqVjBkzJv3793/P7e8XK5Jk9OjReeaZZ/LII4/krLPOyimnnJLBgwenqqoqt99+e9q1a7fO/osWLcqll16a22+/PTvvvHMef/zxjBo1KknSp0+f3HnnnXn44Yczc+bM3HjjjfnZz372vjO09P0CAAAALdOib7rZr1+/TJ48OX369EmS9O3bN5MnT15vaFifLbbYIitWrGh+fdBBB2XKlCnNP2Gkvr4+CxYs+EDnfO6559KjR4+cdNJJGTp0aJ588snU1NTki1/8Ym688cbm/V555ZUsWbIk9fX1adeuXbp165a1a9dm6tSpzfv89a9/TU1NTQ477LCce+65+T//5/9k7dq16d+/f+66666sWbMma9asyV133ZUvf/nLH2hOAAAAoOVafIfF2Wef3Rwo+vXrl2nTpqVfv34f6GInnnhiRo8enY4dO+aKK67I6aefnmuvvTZHH310qqqqUlVVlTPOOOM9Hx9ZnyuuuCIvvvhi2rRpky233DIXX3xxkmTixIm59NJLM2TIkCTvxJKLL744PXr0yKBBgzJ48OB06dIlX/nKV/LYY48lSR599NFMmTIl1dXVWbt2bcaOHZvq6uoce+yxWbhwYY466qgkyf77759jjjnmA713AAAAoOWqKpVKpbWH+KT71XdG5e3XP/iPe4UNYfAtN2fJkhXvv+MmpFu3zpvde4LWZl3BhmddwYZlTW16qqur0rVrzfq3b8RZAAAAAFqkRY+EtLa6urr3/PGp//RP/5QzzjijFSYCAAAAPk6bRLDo2rVrZs6c2dpjAAAAABuJR0IAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFCctq09AMlXr5zY2iPwCbZm1erWHgEAAOBdBIsC1NXVZ+3aSmuPAQAAAMXwSAgAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4bVt7AJKuXWs22rUaVjdm2fK3N9r1AAAA4MMQLApw3eX3ZtnSlRvlWqMvPnqjXAcAAAA+Co+EAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAU5xMVLObNm5dhw4Z9bOefMWNGzjzzzI/t/AAAAPBJ8YkKFgAAAMCmoW1rD/Bepk6dmmeeeSZjxozJ/PnzM2LEiEyfPj177713LrzwwvTq1Ss9e/bMxIkT89ZbbyVJzjzzzBx44IFJkl//+te5/vrr09DQkHbt2uXcc89N796917nG8uXLc8YZZ+Sggw7KySefnDvvvDO33XZbmpqaUlNTkwsvvDDdu3fPjBkzMmvWrGy55ZZ59tln07lz51xzzTXp1q1bGhoactFFF+WRRx5Jly5d0qtXr438SQEAAMDmqchg0b9//0yZMiVJMnfu3PTp0yePPPJI9t5778ydOzdHH310zjvvvNx4443Zdttt89prr+Xoo4/OrFmzsnTp0kyaNCk//vGPU1NTk2effTannXZa5syZ03z+RYsW5X/+z/+Z008/PYMGDcpjjz2Wf//3f8+tt96a9u3b59e//nVGjx6dqVOnJkmefPLJ3H333dlhhx1y3nnn5ac//WlGjhyZadOm5aWXXsrs2bPT2NiY448/PjvttFMrfGIAAACweSkyWOy6665ZvXp1Fi9enLlz52bkyJG54YYbMmTIkKxZsyZ1dXV56aWXctpppzUfU1VVlRdffDHz58/PwoULc/zxxzdva2xszOuvv54kWbJkSU488cRMmDAh++yzT5LkgQceyNNPP50RI0YkSSqVSpYvX958fN++fbPDDjskSb7whS/k4YcfTvLO98Q48sgj065du7Rr1y5Dhw7N448//vF+OAAAAPAJUGSwSJJ+/frlV7/6Verq6lJbW5vx48dnzpw5qa2tTaVSSY8ePXLrrbe+67j58+dnwIABueyyy961bcGCBdlqq62y/fbb58EHH2wOFpVKJcOHD89ZZ531nrN06NCh+ddt2rRJU1PTBnqXAAAAwHsp9ptu9uvXL5MnT06fPn2SvHOXw+TJk9O/f//06dMnL774Yh555JHm/efPn59KpZL99tsvDz30UJ599tl1tv1N+/btM2nSpPzlL3/JRRddlEqlkoMOOigzZ87M4sWLkyRNTU35z//8zxbNOHPmzDQ2NmbVqlWZNWvWhnr7AAAA8IlWdLBYtGhR+vfvv87rfv36ZauttsqkSZNy3XXXZejQoTn00ENz7bXXplKp5DOf+Uwuv/zy/OAHP2jeNm3atHXO3b59+1x99dWpq6vL+eefny9+8Yv59re/nW9961sZOnRoDj/88Pzyl7983xmPOeaY7Ljjjhk8eHBOOumk7LXXXh/LZwEAAACfNFWVSqXS2kN80l13+b1ZtnTlRrnW6IuPzpIlKzbKtaC1dOvW2Z9z2MCsK9jwrCvYsKypTU91dVW6dq1Z//aNOAsAAABAiwgWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQAAABRHsAAAAACKI1gAAAAAxREsAAAAgOIIFgAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFEewAAAAAIrTtrUHIPn/vjd4o12rYXXjRrsWAAAAfFiCRQHq6uqzdm2ltccAAACAYngkBAAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQQLAAAAoDiCBQAAAFAcwQIAAAAojmABAAAAFKdtaw9AUl1d1dojwGbHuoINz7qCDc+6gg3Lmtq0vN/vV1WlUqlspFkAAAAAWsQjIQAAAEBxBAsAAACgOIIFAAAAUBzBAgAAACiOYAEAAAAUR7AAAAAAiiNYAAAAAMURLAAAAIDiCBYAAABAcQSLjeD555/Psccem4EDB+bYY4/NCy+88K59mpqaMnbs2Bx88MH5p3/6p0yfPn3jDwqbkJasq9/85jcZNmxY/vEf/zETJkzY+EPCJqYl6+q6667LYYcdliFDhmTYsGF56KGHNv6gsAlpybq64447MmTIkBxxxBEZMmRIbrnllo0/KGxCWrKu/ua5557LF77wBf8W3ERVVSqVSmsPsbk78cQTM3z48BxxxBGZOXNm7rjjjnf9RXTXXXflnnvuyeTJk7N06dIceeSRue2227LTTju10tRQtpasqxdffDErV67Mfffdl4aGhpxzzjmtNC1sGlqyrh566KHss88+6dSpU55++ul8/etfz29+85t07NixlaaGsrVkXdXX12eLLbZIVVVV6uvrM2TIkFx//fXp2bNnK00NZWvJukre+Z/CJ598crbddttsu+22/i24CXKHxcesrq4uTz31VA4//PAkyeGHH56nnnoqb7zxxjr73XvvvRkxYkSqq6uzzTbb5OCDD859993XGiND8Vq6rnbdddf06tUrbdu2bY0xYZPS0nU1YMCAdOrUKUnSo0ePVCqVLF26dGOPC5uElq6rmpqaVFVVJUlWrVqVNWvWNL8G1tXSdZUkN954Yw488MB85jOf2chTsqEIFh+zV155Jdttt13atGmTJGnTpk223XbbvPLKK+/ab8cdd2x+vcMOO2Tx4sUbdVbYVLR0XQEt92HW1V133ZVddtkl22+//cYaEzYpH2Rd/fKXv8xhhx2Wr371q/nnf/7n9OjRY2OPC5uElq6rp59+Or/5zW9y8sknt8KUbCiCBQDwgT366KP50Y9+lCuuuKK1R4HNwn/7b/8ts2fPzi9+8YvMnDkzzz33XGuPBJusNWvW5Pzzz8/YsWObwwabJvdJf8x22GGHvPrqq2lqakqbNm3S1NSU1157LTvssMO79nv55Zez9957J3n3HRfA/6+l6wpouQ+yrv7whz/ke9/7XiZNmpTu3bu3wrSwafgwf1/tuOOO2WuvvTJnzhzrC95DS9bVkiVLsnDhwpx++ulJkuXLl6dSqaS+vj7jx49vrdH5ENxh8THr2rVrevXqlVmzZiVJZs2alV69emWbbbZZZ79BgwZl+vTpWbt2bd54443cf//9GThwYGuMDMVr6boCWq6l62r+/PkZOXJkrr766nz+859vjVFhk9HSdbVgwYLmX7/xxhuZN29e9thjj406K2wqWrKudtxxx8ybNy8PPPBAHnjggZx00kk55phjxIpNkJ8SshEsWLAg3//+97N8+fJsueWWmTBhQrp3757TTjstZ555Zvbaa680NTVl3Lhx+e1vf5skOe2003Lssce28uRQrpasq8ceeyzf+c53Ul9fn0qlks6dO+fiiy/OgAEDWnt8KFJL1tXw4cOzaNGibLfdds3HXXbZZZ63h/Voybq65JJL8tvf/jZt27ZNpVLJiBEjcsIJJ7T26FCslqyr/9c111yTlStX+ikhmyDBAgAAACiOR0IAAACA4ggWAAAAQHEECwAAAKA4ggUAAABQHMECAAAAKI5gAQCs13PPPZcjjjgiffr0yS233NLa43wgd999d0499dTWHgMA+JD8WFMAYL1Gjx6dmpqajB49+iOf64QTTsjQoUMzYsSIDTDZpmXGjBmZPn16fvazn7X2KACwyXCHBQCwXi+//HJ233331h4jSdLY2NjaI3wom+rcANDaBAsA4D2deOKJmTdvXsaNG5c+ffrk+eefT0NDQyZMmJADDzwwX/7yl3PBBRdk1apVSZJly5blm9/8Zvr165d999033/zmN7N48eIkyVVXXZXHHnus+Vzjxo3LSy+9lB49eqzzH/QnnHBCpk+fnuSduxK+9rWv5ZJLLkltbW2uueaav3v9/2rGjBn57//9vze/7tGjR2699dYccsgh6dOnT374wx9m4cKF+drXvpa+ffvmrLPOSkNDQ5Jk3rx5OeCAA3LDDTektrY2Bx10UO6+++7mc61YsSJnn312+vXrl69+9auZNGlS1q5d+55zjxw5MmPGjMkf//jH9OnTJ/vss0+SZM6cOTnyyCPTt2/ffOUrX8k111zTfP6/fTZ33nlnDjzwwNTW1ub6669v3t7U1JQbbrghBx98cPr06ZNhw4bllVdeSZIsWLAgp5xySr70pS9l4MCBuffeez/knwAAaF1tW3sAAKBMt9xyy7se47jkkkuycOHC3HXXXWnbtm1GjRqV6667Lt/97nezdu3aDBs2LD/84Q/T1NSU0aNHZ9y4cZk0aVJGjhyZxx9/fJ1zvfTSS+87w/z583PYYYflt7/9bRobGzNx4sT1Xr8lfvOb32TGjBl55ZVXctRRR+UPf/hDLr/88my99dY59thjM3v27Bx11FFJktdffz1vvvlmHnroofzxj3/M6aefnn/8x39M9+7dM378+KxYsSL3339/li5dmm984xvp1q1b83v7r3Pfe++973okpFOnTpkwYUJ23333/PnPf86pp56aXr165eCDD27e5/e//33uu+++vPDCCzn66KNzyCGHZLfddsvNN9+c2bNn58Ybb8xnP/vZPPPMM+nYsWNWrlyZU089NWeeeWYmT56cP//5zznllFOyxx575HOf+1zLfuMBoBDusAAAWqRSqeTnP/95Ro8ena233jo1NTX55je/mdmzZydJunTpkoEDB6ZTp06pqanJt771rfzud7/7SNfcdtttc8IJJ6Rt27bp0KHD371+S/zzP/9zampqsvvuu2ePPfbIfvvtl5133jmdO3fOAQcckKeeemqd/c8666y0b98+X/rSl/KVr3wl//7v/56mpqbce++9+e53v5uamprstNNOOeWUU9a5A+P/nbtjx47vOUttbW169OiR6urq9OzZM4cddlgeffTRdfY544wz0rFjx/Ts2TM9e/bM008/nSSZPn16zjrrrHTv3j1VVVXp2bNnunTpkjlz5uQf/uEfMnz48LRt2zZ77rlnBg4cmPvuu6/FnxEAlMIdFgBAi7zxxht5++23M2zYsOavVSqV5kch3n777Vx66aV56KGHsmzZsiTJW2+9laamprRp0+ZDXXP77bdv8fVb4tOf/nTzrzt06PCu16+//nrz6y233DKf+tSnml/vuOOOee211/Lmm29mzZo12XHHHdfZ9uqrr77n3OvzxBNPZOLEiXn22WezZs2aNDQ0ZNCgQeudt1OnTlm5cmWSZPHixdlll13edc5FixZl/vz5zY+dJO88PjJ06ND3nQcASiNYAAAt0qVLl3Ts2DGzZ8/Odttt967tP/nJT/L888/n5z//ebp165Y//elPOfLII7O+H0j2txiwatWq1NTUJEmWLFmyzj5VVVUtvv6Gtnz58qxcubJ5zldeeSW77757unTpknbt2uXll19ufszilVdeWWem/3fu93qdJN/97nfz9a9/PTfddFM6dOiQiy++OG+++WaLZtt+++2zcOHC7LHHHut8fYcddsi+++6bm2+++QO9VwAokUdCAIAWqa6uzogRI3LJJZekrq4uSfLqq6/moYceSvLO3RQdOnTIlltumaVLl+baa69d5/hPf/rT+etf/9r8eptttsl2222XmTNnpqmpKbfffvs62z/o9T8Of/tGn4899ljmzJmTQYMGpU2bNhk0aFCuuuqq1NfXZ9GiRbn55pv/7l0MXbt2zauvvtr8TT2Tdz6vrbbaKh06dMj8+fMza9asFs81YsSI/OhHP8oLL7yQSqWSp59+Om+++WYOPPDAvPDCC7nrrruyZs2arFmzJvPnz8+CBQs+0ucAAK1BsAAAWux73/tedt111xxzzDHp27dvTj755Dz//PNJkpNOOimrV69Ov379cuyxx2bAgAHrHHviiSfmF7/4Rfbdd99cdNFFSZLx48fnxz/+cWpra/OXv/wlffr0+dDX39A+/elPZ8stt8yAAQMyatSoXHjhhdltt92SJOeff346deqUgw8+OMcdd1wOP/zwDB8+fL3n6tevXz73uc9l//33T21tbZJkzJgxufrqq9OnT59cd911OfTQQ1s82ymnnJJDDz00p556avr27Zsf/OAHWb16dWpqavLjH/849957bwYMGJD9998/EydOXCeUAMCmoqqyvvs0AQA+oebNm5fvfe97efDBB1t7FAD4xHKHBQAAAFAcwQIAAAAojkdCAAAAgOK4wwIAAAAojmABAAAAFEewAAAAAIojWAAAAADFESwAAACA4ggWAAAAQHH+LzXnnIfFoAfPAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ranking = np.argsort(-rf_regressor.feature_importances_)\n", + "f, ax = plt.subplots(figsize=(15, 10))\n", + "sns.barplot(x=rf_regressor.feature_importances_[ranking], y=X_train.columns.values[ranking], orient='h')\n", + "ax.set_xlabel(\"feature importance\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 246, + "id": "eff9fa74", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mutiple Linear Regression Accuracy: 0.3760362035902819\n", + "Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: 0.26323368310834494\n" + ] + } + ], + "source": [ + "lin_reg = LinearRegression()\n", + "lin_reg.fit(X_train,y_train)\n", + "\n", + "#Predicting the SalePrices using test set \n", + "y_pred_lr = lin_reg.predict(X_test)\n", + "\n", + "#Mutiple Linear Regression Accuracy with test set\n", + "accuracy_lf = metrics.r2_score(y_test, y_pred_lr)\n", + "print('Mutiple Linear Regression Accuracy: ', accuracy_lf)\n", + "\n", + "#Predicting the SalePrice using cross validation (KFold method)\n", + "y_pred_kf_lr = cross_val_predict(lin_reg, X, y, cv=10 )\n", + "\n", + "#Mutiple Linear Regression Accuracy with cross validation (KFold method)\n", + "accuracy_lf = metrics.r2_score(y, y_pred_kf_lr)\n", + "print('Cross-Predicted(KFold) Mutiple Linear Regression Accuracy: ', accuracy_lf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "470425b6", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.13" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/mutiple_regression.ipynb b/machine_learning/scripts/mutiple_regression.ipynb new file mode 100644 index 0000000..19e3f85 --- /dev/null +++ b/machine_learning/scripts/mutiple_regression.ipynb @@ -0,0 +1,690 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "from common.functions import distanceInKmByGPS\n", + "season = Season.objects.filter(nicename=\"Imported: Benchmark Season\").first()\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import F\n", + "games = Game.objects.filter(season=season)\n", + "df = pd.DataFrame.from_records(games.values())\n", + "games = Game.objects.filter(season=season).annotate(\n", + " home=F('homeTeam__shortname'),\n", + " away=F('awayTeam__shortname'),\n", + " home_lat=F('homeTeam__latitude'),\n", + " home_lon=F('homeTeam__longitude'),\n", + " home_attr=F('homeTeam__attractivity'),\n", + " away_lat=F('awayTeam__latitude'),\n", + " away_lon=F('awayTeam__longitude'),\n", + " away_attr=F('awayTeam__attractivity'),\n", + " home_country=F('homeTeam__country'),\n", + " away_country=F('awayTeam__country'),\n", + ").values()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc191792", + "metadata": {}, + "source": [ + "#### Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e404cf8", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.preprocessing import OneHotEncoder\n", + "\n", + "# create dataset\n", + "df = pd.DataFrame.from_records(games.values())\n", + "\n", + "# data cleaning\n", + "df['time'] = df['time'].replace('','0')\n", + "df = df[df['attendance'] != 0]\n", + "\n", + "\n", + "# pivots\n", + "pivot_homeTeam_mean = df.pivot_table('attendance','homeTeam_id',aggfunc='mean')\n", + "pivot_homeTeam_max = df.pivot_table('attendance','homeTeam_id',aggfunc='max')\n", + "\n", + "# add more features\n", + "df['weekday'] = df.apply(lambda r: r['date'].weekday(), axis=1)\n", + "df['day'] = df.apply(lambda r: r['date'].day, axis=1)\n", + "df['month'] = df.apply(lambda r: r['date'].month, axis=1)\n", + "df['year'] = df.apply(lambda r: r['date'].year, axis=1)\n", + "df['distance'] = df.apply(lambda r: distanceInKmByGPS(r['home_lon'],r['home_lat'],r['away_lon'],r['away_lat']), axis=1)\n", + "df['weekend'] = df.apply(lambda r: int(r['weekday'] in [6,7]), axis=1)\n", + "df['winter_season'] = df.apply(lambda r: int(r['month'] in [1,2,3,10,11,12]), axis=1)\n", + "df['home_base'] = df.apply(lambda r: pivot_homeTeam_mean.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['stadium_size'] = df.apply(lambda r: pivot_homeTeam_max.loc[r['homeTeam_id'],'attendance'], axis=1)\n", + "df['early'] = df.apply(lambda r: r['time'].replace(':','') < \"1800\", axis=1)\n", + "df['before2010'] = df.apply(lambda r: r['historic_season'].split('-')[0] < \"2010\", axis=1)\n", + "\n", + "\n", + "# one hot encoding\n", + "ohe_fields = ['home_country']\n", + "\n", + "for field in ohe_fields:\n", + " ohe = OneHotEncoder()\n", + " transformed = ohe.fit_transform(df[[field]])\n", + " df[ohe.categories_[0]] = transformed.toarray()\n", + "\n", + "# sort label to last index\n", + "cols = list(df.columns)\n", + "cols.append(cols.pop(cols.index('attendance')))\n", + "df = df[cols]" + ] + }, + { + "cell_type": "markdown", + "id": "e2ea08e5", + "metadata": {}, + "source": [ + "#### Train/Test Data - Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "74e12f87", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "import pandas as pd \n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from sklearn.model_selection import train_test_split, cross_val_predict\n", + "from sklearn import metrics\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "\n", + "\n", + "remove_columns = ['season_id', 'resultEntered', 'reversible', 'reschedule', 'homeGoals', 'awayGoals',\n", + " 'homeGoals2', 'awayGoals2', 'homeGoals3', 'awayGoals3', 'home', 'away', 'date', 'time',\n", + " 'id', 'homeTeam_id', 'awayTeam_id', 'historic_season',\n", + " 'home_country','home_lat','home_lon','away_lat','away_lon','away_country']\n", + "feature_cols = list(set(df.columns[:-1]) - set(remove_columns))\n", + "# feature_cols = ['weekday','weekend','home_base','distance','winter_season']\n", + "label = 'attendance'\n", + "\n", + "\n", + "X = df[feature_cols] # Features\n", + "y = df[label] # Target variable\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3a05ac61", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "from pandas import DataFrame,Series\n", + "from sklearn import tree\n", + "import matplotlib\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn import svm\n", + "from sklearn.preprocessing import StandardScaler\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "import seaborn as sns\n", + "from sklearn import neighbors\n", + "from sklearn import linear_model" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "76e774e8", + "metadata": {}, + "outputs": [], + "source": [ + "X_Train=X_train.values\n", + "X_Train=np.asarray(X_Train)\n", + "\n", + "# Finding normalised array of X_Train\n", + "X_std=StandardScaler().fit_transform(X_Train)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "45e08026", + "metadata": {}, + "outputs": [], + "source": [ + "number_of_samples = len(y_train)\n", + "np.random.seed(0)\n", + "random_indices = np.random.permutation(number_of_samples)\n", + "num_training_samples = int(number_of_samples*0.75)\n", + "x_train = X_Train[random_indices[:num_training_samples]]\n", + "y_train=y[random_indices[:num_training_samples]]\n", + "x_test=X_Train[random_indices[num_training_samples:]]\n", + "y_test=y[random_indices[num_training_samples:]]\n", + "y_Train=list(y_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "470425b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.11885891315514 percent in Ridge Regression\n", + "Test error = 100.86230814227264 percent in Ridge Regression\n" + ] + } + ], + "source": [ + "model=linear_model.Ridge()\n", + "model.fit(x_train,y_train)\n", + "y_predict=model.predict(x_train)\n", + "\n", + "error=0\n", + "for i in range(len(y_Train)):\n", + " error+=(abs(y_Train[i]-y_predict[i])/y_Train[i])\n", + "train_error_ridge=error/len(y_Train)*100\n", + "print(\"Train error = \"'{}'.format(train_error_ridge)+\" percent in Ridge Regression\")\n", + "\n", + "Y_test=model.predict(x_test)\n", + "y_Predict=list(y_test)\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y_Predict[i]-Y_test[i])/y_Predict[i])\n", + "test_error_ridge=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_ridge)+\" percent in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "88b4de2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Ridge Regression')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZkAAAGDCAYAAAD56G0zAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABWzElEQVR4nO3deZwU9Zn48c/T3TPDyC14cWkMGAOsEJ0EFHUjJooXZtcjKllN1ugvG82xakSTJV6bxCMxq9EcRk00MVHETUA0MR4YlRXiGIEAXhOjMuABCCjXTB/P74/69lDdXX3MTNd0T8/zfr0Gur9dXf2tPuqp7y2qijHGGBOGSKUzYIwxpnZZkDHGGBMaCzLGGGNCY0HGGGNMaCzIGGOMCY0FGWOMMaGxIGN6nIjMEpE/FXj8SRH5Yhle55Mi0trF535eRJ7pbh7cvsaIyFYRiZZjfwH73yoi++d5rGzHUUtE5Jsicnul89EXWJAxBYnI6yKyw53I3haRX4rIgO7sU1XvUdVjypXHSisWFFX1TVUdoKrJLuz7kyKScu//ByLysoh8IWv/A1T1ta7kvatEZD8RUZevre57cllP5qE7VPW7qtrtCxlTnAUZU4qTVHUAMBn4GHB5ZbPT56xz7/8g4D+Bn4vIRyqcp7QhLm+nAnNE5NPlfgERiZV7n6bnWJAxJVPVt4FH8IINACIyVUT+T0Q2i8hyEfmk77HPi8hr7gr8HyIyy5f+jG+7T4vISyKyRURuAcT32JUi8mvf/fQVdMzd/4KIvOhe4zUR+X+lHo/bz1fd8zaIyA0iEvibEJHDROQ5l8fnROQwl/4d4AjgFndFf0vAc7Pz/KSIXCMii12+/yQiw4vlVz0PA+8BB2Udx1h3e5iILBCR90XkL8CHs/JyjCsNbRGRH4vIn/2lMBH5d/d+bhKRR0Rk3xLeSlS1GVhF5ncj774K5cN9PxaLyA9FZCNwpYg0iMj3ReRNEXlHRH4qIo1u++EistB9B98TkafTn6OIzBaRtb5S4NEuPft7NVNEVrl9PCkiH/U99rqIXCIiK1x+7xORfqW8L8aCjOkEERkFHAe0uPsjgYeA/wZ2By4BHhCRPUSkP3AzcJyqDgQOA5YF7HM48L/AfwHDgb8D0zqRrXeBE/Gu8r8A/FBEDu7E8/8FaAIOBk4G/j0gj7vjHefNwDDgRuAhERmmqt8CngYudNVWF5b4ume5/O4J1OO9dwWJSEREZuK9Ty15NrsV2Ans446l43jcez0PryQ6DHgZ73NJP34y8E3gX4E93HH9tpSDEZGpwER2fTfy7qtYPpwpwGvAXsB3gGuBA/CC2FhgJPBtt+3FQKt7nb3c66p4pb0LgY+77+CxwOsBeT/A5e3rbh8PAw+KSL1vs9OBGcCH8AL850t5X4wFGVOa34vIB8AavJP6FS79c8DDqvqwqqZU9VGgGTjePZ4CJopIo6q+paqrAvZ9PLBKVeepahz4H+DtUjOmqg+p6t/dVf6fgT/hlSxKdZ2qvqeqb7rXPjNgmxOAV1X1V6qaUNXfAi8BJ3XidbL9QlVfUdUdwFx8JYAAI0RkM7AD+B1wkaq+kL2ReB0LTgG+rarbVHUlcJdvk/R7/b+qmsALmv73+kvA91T1Rff4d4HJRUozG0RkB/As8GPg9yXsq1g+wKsi/JF7fCdwPvCf7rP6wO3vDLdtHC+o7quqcVV9Wr1JGZNAAzBeROpU9XVV/XvAMXwWeEhVH3Xfwe8DjWQGvptVdZ2qvgc8SOHPy/hYkDGl+Iy7EvwkcCDelTTAvsBprophszsRHg7so6rb8H68XwLeEpGHROTAgH2PwAtegFcl5L9fjIgcJyJLXDXJZrwTWNGqJx//a73h8hOUxzey0t7Au5ruKv9JdTtQqDPFOlUdgldauxmYnme7PYAYuceUFvRe+3vf7Qvc5Pss38Oruix0nMNd3i/G+37UlbCvYvkg6xj2AHYDnvft748uHeAGvBLUn8Sr+rzM7bcFr3RyJfCuiNwrIkU/X1VNudf3H3dnPi/jY0HGlMyVFH6Jd6UH3g/xV6o6xPfXX1Wvdds/oqqfxrvKfAn4ecBu3wJGp++IiPjvA9vwTjBpe/u2bQAecPnZy52IH8bXplMC/2uNAdYFbLMO76RJ1rZr3e0emcpcVduA2cA/ichnAjZZDyTIPaa0t4BR6TvuvR7le3wN8P+yPs9GVf2/IvlKquqNeCWOL5ewr2L5gMz3dANeKW6Cb1+DXYcDVPUDVb1YVfcHZgIXpdteVPU3qno43uenwHUBh5Dx+fq+g2sDtjWdZEHGdNb/AJ8WkUnAr4GTRORYEYmKSD/xutyOEpG9RORk1zbTBmzFqz7L9hAwQUT+VbyG8a/iCyR47ThHijfWZDCZPdvq8apD1gMJETkO6GzX6G+IyFARGQ18DbgvYJuHgQNE5CwRiYnIZ4HxwEL3+DtA4DiVclPVduAH7GqP8D+WxGvfulJEdhOR8cA5vk0ewgUo915fQOZ7/VPgchGZACAig0XktE5k71rgUtcoXmhfxfKRfVwpvAuUH4rInm5/I0XkWHf7RBEZ64LDFrxqspSIfEREpruLkZ14gSroOzgXOEFEjhaROrxSWRtQMLia0liQMZ2iquuBu/Hq/dfgNZZ/E+9Evwb4Bt73KgJchHeV+B7wz8B/BOxvA3Aa3glqIzAOWOx7/FG8E/8K4Hl2ndhxdfNfxTtJbMJrTF/QyUOa7/a7DO/kd0dAHjfidS642OXxUuBEl3eAm4BTxetFdXMnX78r7gTGiEhQm9CFeFU5b+OVOn+RfsD3Xl+Pdxzj8drQ2tzjv8O70r9XRN4HVuJ19CjVQ3ifw3mF9lUsH3nMxqsSW+L29xiQ7sY9zt3fimsbUtVFeBcg1+KVhN7G62SR0/1eVV/Ga1/8kdv2JLxu++2dOHaTh6gtWmb6KBFRYJyru+9zxOvm2wrMciflPp0PEw4ryRjTh7iqzSGuCumbeO1XS/pqPkz4LMgY07ccijcWKV0t9BnXjbqv5sOEzKrLjDHGhMZKMsYYY0JjQcYYY0xobHbTLMOHD9f99tuv0tkwxphe5fnnn9+gqntkp1uQybLffvvR3Nxc6WwYY0yvIiLZUy8BVl1mjDEmRBZkjDHGhMaCjDHGmNBYkDHGGBMaCzLGGGNCY0HGGGNMaCzIGGOMCY0FGWOMMaGxIGOMMSY0FmSMqZCNW9tYvmYzG7cWWhDSmN7NppUxpgLmL1vL7AdWUBeJEE+luP6Ug5g5eWSls2VM2VlJxpgetnFrG7MfWMHOeIoP2hLsjKe49IEVVqIxNamiQcYtvzpPRF4SkRdF5FAR2V1EHhWRV93/Q922IiI3i0iLiKwQkYN9+znHbf+qiJzjSz9ERP7mnnOziEgljtMYv9ZNO6iLZP706iIRWjfZwpCm9lS6JHMT8EdVPRCYBLwIXAY8rqrjgMfdfYDjgHHu73zgJwAisjtwBTAF+ARwRTowuW3O8z1vRg8ckzEFjRraSDyVykiLp1KMGtpYoRwZE56KBRkRGQwcCdwBoKrtqroZOBm4y212F/AZd/tk4G71LAGGiMg+wLHAo6r6nqpuAh4FZrjHBqnqEvXWmL7bty9jKmbYgAauP+Ug+tVFGNgQo19dhOtPOYhhAxoqnTVjyq6SDf8fAtYDvxCRScDzwNeAvVT1LbfN28Be7vZIYI3v+a0urVB6a0C6MRU3c/JIpo0dTuumHYwa2mgBxtSsSlaXxYCDgZ+o6seAbeyqGgPAlUA07IyIyPki0iwizevXrw/75YwBvBLNpNFDLMB0k3UFr26VDDKtQKuqLnX35+EFnXdcVRfu/3fd42uB0b7nj3JphdJHBaTnUNXbVLVJVZv22CNn9VBjTJWav2wt0657gs/dvpRp1z3BgmWBP3FTQRULMqr6NrBGRD7iko4GVgMLgHQPsXOA+e72AuBs18tsKrDFVas9AhwjIkNdg/8xwCPusfdFZKrrVXa2b1/GmF7OuoL3DpUejPkV4B4RqQdeA76AF/jmisi5wBvA6W7bh4HjgRZgu9sWVX1PRK4BnnPbXa2q77nbXwZ+CTQCf3B/xpgakO4KvpNdPfXSXcGtCrJ6VDTIqOoyoCngoaMDtlXggjz7uRO4MyC9GZjYvVwaY6qRdQXvHSo9TsYYY7rEuoL3DpWuLjPGmC6zruDVz4KMMaZXGzagwYJLFbPqMmOMMaGxIGOMMSY0FmSMMcaExoKMMcaY0FiQMcYYExoLMsYYY0JjQcYYY0xoLMgYY4wJjQUZY4wxobEgY4wxJjQWZIwxxoTGgowxxpjQWJAxxhgTGgsyxhhjQmNBxhhjTGgsyBhjjAmNBRljjDGhsSBjjDEmNBZkjDHGhMaCjDHGmNBYkDHGGBMaCzLGGGNCY0HGGGNMaCzIGGOMCY0FGWOMMaGxIGOMMSY0FmSMMcaExoKMMcaY0FiQMcYYExoLMsYYY0JjQcYYY0xoKhpkROR1EfmbiCwTkWaXtruIPCoir7r/h7p0EZGbRaRFRFaIyMG+/Zzjtn9VRM7xpR/i9t/inis9f5TGGNN3VUNJ5ihVnayqTe7+ZcDjqjoOeNzdBzgOGOf+zgd+Al5QAq4ApgCfAK5IBya3zXm+580I/3CMMcakVUOQyXYycJe7fRfwGV/63epZAgwRkX2AY4FHVfU9Vd0EPArMcI8NUtUlqqrA3b59GWOM6QGVDjIK/ElEnheR813aXqr6lrv9NrCXuz0SWON7bqtLK5TeGpBujDGmh8Qq/PqHq+paEdkTeFREXvI/qKoqIhp2JlyAOx9gzJgxYb+cMcb0GRUtyajqWvf/u8Dv8NpU3nFVXbj/33WbrwVG+54+yqUVSh8VkB6Uj9tUtUlVm/bYY4/uHpYxxhinYkFGRPqLyMD0beAYYCWwAEj3EDsHmO9uLwDOdr3MpgJbXLXaI8AxIjLUNfgfAzziHntfRKa6XmVn+/ZljDGmB1Syumwv4HeuV3EM+I2q/lFEngPmisi5wBvA6W77h4HjgRZgO/AFAFV9T0SuAZ5z212tqu+5218Gfgk0An9wf8YYY3qIeB2vTFpTU5M2NzdXOhvGGNOriMjzvqEoHSrdu8wYY0wNsyBjjDEmNBZkjDHGhMaCjDHGmNBYkDHGGBMaCzLGGGNCY0HGGGNMaCzIGGOMCY0FGWOMMaGxIGOMMSY0FmSMMcaExoKMMV2wcWsby9dsZuPWtkpnxZiqVulFy4zpdeYvW8vsB1ZQF4kQT6W4/pSDmDZ2OK2bdjBqaCPDBjRUOovGVA0LMsZ0wsatbcx+YAU74yl2kgLgornLiEYi1Ed3BZ2Zk22lb2PAqsuM6ZTWTTuoi2T+bBIpaEuk+KAtwc54iksfWGHVaMY4FmSM6YRRQxuJp1IFt6mLRGjdtKOHcmRMdbMgY0wnDBvQwPWnHES/uggDG2I0xIS6qGRs055MMWpoY4VyaEx1sTYZYzpp5uSRGQ39i1s2cPH9y4knvVVmk6kUi1s2WLuMMVhJxpguGTaggUmjhzBsQAPTxg4n4ivMJFJYu4wxjgUZY7qpddMO6qPRjDRrlzHGY0HGmG4K6gwQT1m7jDFgQcaYbsvuDNCvLsL1pxxkgzKNwRr+jSmL7M4AFmCM8ViQMaZMhg1osOBiTBarLjPGGBMaCzLGGGNCY0HGGGNMaCzIGGOMCY0FGWOMMaGxIGOMMSY0FmSMMcaExoKMMcaY0FiQMaabNm5tY/mazWWZdbmc+zKmGtiIf2O6Yf6ytcx+YAV1kQjxVIrrTzmoy+vIlHNfxlQLK8kY00Ubt7Yx+4EV7Iyn+KAtwc54qsvryJRzX8ZUk4oHGRGJisgLIrLQ3f+QiCwVkRYRuU9E6l16g7vf4h7fz7ePy136yyJyrC99hktrEZHLevzgTE1r3bSDukjmT6ir68iUc1/GVJOKBxnga8CLvvvXAT9U1bHAJuBcl34usMml/9Bth4iMB84AJgAzgB+7wBUFbgWOA8YDZ7ptjSmLcq4jY2vSmFpV0SAjIqOAE4Db3X0BpgPz3CZ3AZ9xt09293GPH+22Pxm4V1XbVPUfQAvwCffXoqqvqWo7cK/b1piyKOc6MrYmjalVlW74/x/gUmCguz8M2KyqCXe/FUi3fI4E1gCoakJEtrjtRwJLfPv0P2dNVvqUoEyIyPnA+QBjxozp+tGYPqec68jYmjSmFlUsyIjIicC7qvq8iHyyUvkAUNXbgNsAmpqatJJ5Mb1POdeRsTVpTK2pZElmGjBTRI4H+gGDgJuAISISc6WZUcBat/1aYDTQKiIxYDCw0Zee5n9OvnRjjDE9oGJtMqp6uaqOUtX98Brun1DVWcAi4FS32TnAfHd7gbuPe/wJVVWXfobrffYhYBzwF+A5YJzrrVbvXmNBDxyaMcYYp9JtMkFmA/eKyH8DLwB3uPQ7gF+JSAvwHl7QQFVXichcYDWQAC5Q1SSAiFwIPAJEgTtVdVWPHokxxvRx4hUGTFpTU5M2NzdXOhvGGNOriMjzqtqUnV4N42SMMcbUKAsyxhhjQmNBxhhjTGgsyBhjjAmNBRljjDGhsSBjjDEmNBZkjDHGhMaCjDHGmNBYkDHGGBMaCzLGGGNCY0HGmCqwcWsby9dsZuPWtkpnxZiyqsYJMo3pU+YvW8vsB1ZQF4kQT6W4/pSDmDl5ZPEnGtMLWEnGmArauLWN2Q+sYGc8xQdtCXbGU1z6wAor0ZiaYUHGmApq3bSDukjmz7AuEqF1044K5ciY8rIgY0wFjRraSDyVykiLp1KMGtpYoRwZU14WZIypoGEDGrj+lIPoVxdhYEOMfnURrj/lIIYNaKh01owpC2v4N6bCZk4eybSxw2ndtINRQxstwPQiG7e22edWhAUZY6rAsAENdpLqZaxXYGmsuswYYzrJegWWzoKMMcZ0kvUKLJ0FGWOM6STrFVg6CzLGGNNJ1iuwdNbwb4wxXWC9AktjQcYYY7rIegUWZ9VlxhhjQmNBxhhjTGgsyBhjjAmNBRljjDGhsSBjjDEmNBZkjDHGhMaCjDHGmNBYkDHGGBMaCzLGGGNCU1KQEZFpItLf3f6ciNwoIvuGmzVjjDG9XaklmZ8A20VkEnAx8Hfg7u68sIj0E5G/iMhyEVklIle59A+JyFIRaRGR+0Sk3qU3uPst7vH9fPu63KW/LCLH+tJnuLQWEbmsO/k1xhjTeaUGmYSqKnAycIuq3goM7OZrtwHTVXUSMBmYISJTgeuAH6rqWGATcK7b/lxgk0v/odsOERkPnAFMAGYAPxaRqIhEgVuB44DxwJluW2OMMT2k1CDzgYhcDnwOeEhEIkBdd15YPVvd3Tr3p8B0YJ5Lvwv4jLt9sruPe/xoERGXfq+qtqnqP4AW4BPur0VVX1PVduBet62pIhu3trF8zWZbUdCYGlXqLMyfBc4CzlXVt0VkDHBDd1/clTaeB8bilTr+DmxW1YTbpBVIL5o9ElgDoKoJEdkCDHPpS3y79T9nTVb6lDz5OB84H2DMmDHdOyhTMlsj3ZjaV1JJRlXfVtUbVfVpd/9NVe1Wm4zbT1JVJwOj8EoeB3Z3n13Mx22q2qSqTXvssUclstDn2BrpxvQNBUsyIvIBXhVWzkN4NV6DypEJVd0sIouAQ4EhIhJzpZlRwFq32VpgNNAqIjFgMLDRl57mf06+dFNh6TXSd7JrCdv0Gum2PocxtaNgSUZVB6rqoIC/gd0NMCKyh4gMcbcbgU8DLwKLgFPdZucA893tBe4+7vEnXGeEBcAZrvfZh4BxwF+A54BxrrdaPV7ngAXdybMpn76yRrq1OZm+rlMrY4rInkC/9H1VfbMbr70PcJdrl4kAc1V1oYisBu4Vkf8GXgDucNvfAfxKRFqA9/CCBqq6SkTmAquBBHCBqiZdfi8EHgGiwJ2quqob+TVllF4j/dKsNplaKsVYm5MxIF5hoMhGIjOBHwAjgHeBfYEXVXVCuNnreU1NTdrc3FzpbPQZG7e21eQa6Ru3tjHtuifYGd9VWutXF2Hx7Ok1dZzGpInI86ralJ1eahfma4CpwCuq+iHgaDJ7dBnTJcMGNDBp9JCaO/Gm25z80m1OxvQlpQaZuKpuBCIiElHVRUBOxDLGePpKm5MxxZQaZDaLyADgKeAeEbkJ2BZetozp3dJtTv3qIgxsiNGvLlJzbU7GlKLUhv+TgZ3AfwKz8LoPXx1WpoypBTMnj2Ta2OE12eZkTKlKCjKq6i+13JV3Q2NMhmEDGiy4mD6tpCCTNSizHm+esW3lGoxpjOm8Wu2ZZ2pLqSWZjhmXfZNSTg0rU8aYwmwMjuktOr0ypps9+ffAscW2NcaUn837ZnqTUqvL/tV3N4LXfXlnKDkyxhRk876Z3qTU3mUn+W4ngNextVmMqQgbg2N6k1LbZL4QdkaMMaXpC/O+mdpRbKr/HxE81T8AqvrVsufIGFOUjcExvUWxkkx6pshpwHjgPnf/NLxZj40xFWJjcExvUDDIqOpdACLyH8Dh6WWRReSnwNPhZ88YY0xvVmoX5qGAf+DlAJdmjDHG5FVq77JrgRfcEskCHAlcGVamjDHG1IZSe5f9QkT+AExxSbNV9e3wsmWMMaYWFKwuE5ED3f8H462Kucb9jXBpxhhjTF7FSjIXAefjLb2cTYHpZc+RMcaYmlGsd9n57v+jeiY7xhhjaklJvctE5DQRGehu/5eI/K+IfCzcrBljjOntSu3CPEdVPxCRw4FPAXcAPw0vW8YYY2pBqUEm6f4/AbhNVR/CW7zMGGOMyavUILNWRH4GfBZ4WEQaOvFcY4wxfVSpgeJ04BHgWFXdDOwOfCOsTBljjKkNJQUZVd0OvAsc7pISwKthZcoYE76NW9tYvmazrahpQlXqyphX4K2G+RHgF0Ad8Gu82ZmNMb3M/GVrmZ21Hs3MySMrnS1Tg0qtLvsXYCawDUBV1wEDw8qUMSY8G7e2MfuBFeyMp/igLcHOeIpLH1hhJRoTilKDTLuqKm4BMxHpH16WjDFhat20g7pI5k+/LhKhddOOCuXI1LKiQUZEBFjoepcNEZHzgMeAn4edOWNM+Y0a2kg8lcpIi6dSjBraWKEcmVpWNMi4EsxpwDzgAbx2mW+r6o9CzpsxJgTDBjRw/SkH0a8uwsCGGP3qIlx/ykG2yqYJRanryfwV2Kyq1m3ZmBowc/JIpo0dTuumHYwa2mgBxoSm1CAzBZglIm/gGv8BVPWgUHJljAndsAENFlxM6EoNMseGmgtjjDE1qdTBmG8E/XXnhUVktIgsEpHVIrJKRL7m0ncXkUdF5FX3/1CXLiJys4i0iMgK/6JpInKO2/5VETnHl36IiPzNPedm14nBGGNMlrAG51Zy/rEEcLGqjgemAheIyHjgMuBxVR0HPO7uAxwHjHN/5wM/AS8oAVfgVel9ArgiHZjcNuf5njejB47L9CE2at7UgvnL1jLtuif43O1LmXbdEyxYtrZs+y61uqzsVPUt4C13+wMReREYCZwMfNJtdhfwJDDbpd/terstEZEhIrKP2/ZRVX0PQEQeBWaIyJPAIFVd4tLvBj4D/KEHDs/0ATZq3tQC/+DcnXhd2y99YAXTxg4vS5tdVcykLCL7AR8DlgJ7uQAE8Dawl7s9Eljje1qrSyuU3hqQbky32ah5UyvCHpxb8SAjIgPwxt98XVXf9z/mn2Ug5DycLyLNItK8fv36sF/O9DB/lVa5qrds1LypFWEPzq1YdRmAiNThBZh7VPV/XfI7IrKPqr7lqsPedelrgdG+p49yaWvZVb2WTn/SpY8K2D6Hqt4G3AbQ1NQUelAzPcdfpbUzkURVaayLdbt6y0bNm1qRHpx7aVbVb7m6t1esJON6et0BvKiqN/oeWgCke4idA8z3pZ/teplNBba4arVHgGNEZKhr8D8GeMQ99r6ITHWvdbZvX6YPyK7SiieVRIpuV29t3NpG66YdzDlhvI2aNzVh5uSRLJ49nV9/cQqLZ08va9tiJUsy04B/A/4mIstc2jeBa4G5InIu8AbegmkADwPHAy3AduALAKr6nohcAzzntrs63QkA+DLwS6ARr8HfGv37kHSVVroxM1u6eqtQYEgHlPSo+OzG/jknjmfiiME2at70emENzq1k77JngHzjVo4O2F6BC/Ls607gzoD0ZmBiN7JperGgKi2/YtVbOQHlhPFc89DqjF441yxczeLZ0xk2oCEnIIWlp17HmHKoaJuMMWHKrmsOapPJd5IO6tZ51YOrqI8FN/Y/07KhaHfmcgQHf+BrTya58KhxnDVljAUbU7UsyJialj0RJFDSiT6oqq0uGqE9mdkvJJ5K0b8+WnScwT1L3uCqhaupjwqJlHap08HGrW1cOm85bQnteJ0fPPoKtyxq4YZTbYyOqU4V78JsTNiGDWhg0ughHXXO6duFBFW1JVW54qTcxv5t7cmC3ZnvWfIG3/r9StoTKba2Jbvc6eCepW/Slsjt/NiWyNyfzUJgqomVZIwJkK9b58zJI5kxYe+M0tDGrW15uzNv3NrGVQ+uytl/NCJFOx34bdzaxq2LWvI+3plqO2N6kgUZY/LIt+ZKdi+cQuMMlq/Z7KrZkhn7jie1U2NqWjftoD4aoS0R3JGh1Go7Y3qaBRljCii1W2e+gDRqaCNJza3iuuKk8Z068efrKde/PkpSNaPaLqMdqYRu2saEyYKMMWUSFJD8pZyoCPFkiitOmsCsKft2et/ZpaXsMTqFqu2MqRTRgKusvqypqUmbm5srnQ1TZbrb/Tj9/P71Uba1J7u9n3zPX7BsbWA7kjFhE5HnVbUpO91KMsYUUY4p/YcNaChLo3yx6rt81XbGVIp1YTamgHJN6d/d/XSmW3Kp3bSN6QlWkjGmgKBBmenux+nHSykxBA7uLLFR3hZHM72ZBRljCgjq1bWtLck9S99gwfJ1JZ/4u7o0QNirFhoTNqsuM6aAYQMamHPi+Jz0uc2tnar6SvcO6+zSANW8OJrNLGBKYSUZY4qYOGJwR6+wfEqp+upKo3y1Lo5mVXimVFaSMWVTq1e2+QZU+pV64u9so3xXS0BhKldnCNM3WEnGlEUtX9kGDYQ8vWkUc5tbQ1muNlu1dUvuTicG0/dYkDHd1hcap4NO9F87+oAeO/GHtWphV1RrFZ6pTlZdZrqtmhunyym7qquvjkepxio8U72sJGO6za5s+55qq8Iz1ctKMqbb7Mq2b+qrJTnTOVaSMWVR61e23Z0g05i+yoKMKZtqapwup1ruOWdM2Ky6zJgCwh4TUqtji4xJs5KMMQHS1WNbdrSHNibESkimL7AgY3qdsNtH/Cf/9mSSVNZg/1J7zhXKZ18YW2QMWJAxvUx3rv5LCU5BJ/9YBBpiEeqjpY/uL5ZPGzVv+goLMqbX6M7Vf/qkHxUhnkxxxUkTmDV135ztgk7+jXUxbp11MIMb63ICVFDgCsrnN+atYMhu9UwYMYhhAxpsbJHpMyzImF6jlKv/Yif9tG/9fiUIzJqSGWjynfzTwcH/GivXbuGah1bnlFaC8tmWSPGlXz1PCu3YLns+tDknju+YJcFKM6ZWWJAxvUaxq/98VVStm3YQldz9XfXgamZM2JthAxoyglP2yd9fPeYvEaWn/s8uVQXlE2B7PJmxnX9s0cq1W7hmYW7AKicb62MqwYKM6TWCZkNOB4BCVWkr125hW3vuSb8u6i2j/EzLhpzgtHj29JJKRH5RERa99C5HHbhnRz4jImzPWofGX/pK7/uztz0baieAsHqyWeAyxViQMb1KvpkF8lWlrVq3hasXrg7cVyKp9K+P5gSnS+at4OGvHM6k0UMytg96Db9t7UmufHAV/zV/ZUegWrVuC+fd3UxbYlcXtey2l7A7AYTVk826YJtS2GBM0+sEzZmVryoNhEhAVRnAv0/bj23tyZwZpNsTKY6/+WkWLFubkZ6vGmy3+l3P39qW7BiwCXDkAXtyw6mTCs7rVqgasOWdD5jXvIaWdz7I/4YUEcYs2bZwmSmVBRlTVbo6Aj7fJJ0TRgwikT3QxTn0w8PyBo72pOacNNOvEfP9amIR+JfJoxjQEM14vv8kPnPySBbPns6vvziFxbOn51zt58v7TY+9wqd++BSXzFvBp374FN+e/7dOvSdpYfRk6yvLO5jus+oy0ylh1sEXqn4p5XXzVaVdedIErzeZT11UmDBicMcJ/pJ5K2hPZJ6INaU5VVbTxg4nGomQcCftRArm/XUNkFlcyj6JF5vXLTvvm7a189V7l2Vsc/ezbzLzoBHUxaJ5B3gGvUeF2rK6yrpgm1JVNMiIyJ3AicC7qjrRpe0O3AfsB7wOnK6qm0REgJuA44HtwOdV9a/uOecA/+V2+9+qepdLPwT4JdAIPAx8TbXIYu0mrzDr4Au1G6Qb5mMRoT2pXHHS+Jyux2lBJ/NZU/cFgasWrCIaiZBS5dsnjc8oaYwY3I9Tf7Yk43ltrs3Gr3XTDuqjEdp8Aak+GuX8I/fn1idbutUd2Z/3B5evC9zmjJ8vpV9dJOd9KPbZlHuW7DACl6lNlS7J/BK4Bbjbl3YZ8LiqXisil7n7s4HjgHHubwrwE2CKC0pXAE2AAs+LyAJV3eS2OQ9YihdkZgB/6IHjqjlhT4OSv+H+/dwxLr9bCUrgYMp8eZ84YjAPf/UItrUnA7sL7zusP/3qIhmv068u0tFNOW3U0Ebak5lp8VSK4ybu7ToKKGve29Ht7sjDB9QHpidSytY27/XT78OMiXuX9NmUe5bsWl/ewZRHRYOMqj4lIvtlJZ8MfNLdvgt4Ei/InAzc7UoiS0RkiIjs47Z9VFXfAxCRR4EZIvIkMEhVl7j0u4HPYEGmS8LuAZW/4V6JBbTcX/XgKmZM3LvT07vMOXE81zy0OueEvPDCw/Pmy++Zlg0Zc5nFInD6IaM48ZZn3FxnKZKpFIlU7viZoLymq7j610fZ1p7sOFkf+uHhCN5VUyFXPbiK0bvvlvezAUINArW6vIMpn0qXZILspapvudtvA3u52yOBNb7tWl1aofTWgHTTBWHXweerfpkwYjDtydxTbV00N8Blt0kElb6uenA1dVlBqy7ilVjSrx+NCPGkMufE8Tn7n/3ACuK+/EREuK+5lbZEKm/X5qC2HdgVADWltCWVfnVeQ3q65HPRpw/gB4++UvB9q4tGAA38bFau3cJnb3vWuhibiqrq3mWu1BJ6G4qInC8izSLSvH79+rBfrlfqiSWWg3phDRvQwBUnjc/ZNqmaEeDmL1vLtOue4HO3L2XadU+wYNnawB5QUSGnuqs9mWTLjnamjR3OnBPGE0+kqIsI1yxcndGNOWh/sUiEaL4+0k5Q244/ALa5oLUznsroCnzWlDE0xAr/RJOqjBjcyAWfHEtDTDo+mzkn7CqxWRdjU0nVWJJ5R0T2UdW3XHXYuy59LTDat90ol7aWXdVr6fQnXfqogO1zqOptwG0ATU1N1jEgj56ogw9suJ+yL6hXNVQXjZBUzQhw+dqLFl54eM4V/o54iljEq+ZqrIuxI54gpXDBPS9kVHWlA1G6qgtgy472nADVlkwSoXCQaYhJTttOoYGd6aquSaOHcMOpu0p37ckUR4wdzp9fXU9dREiklGM+uifH3/y0K9EI5x+5P2dNGWOzPJuqUY0lmQXAOe72OcB8X/rZ4pkKbHHVao8Ax4jIUBEZChwDPOIee19EprqeaWf79mW6KGggZCm6uwLkrKn78uzlR/Ob86bmjDXJN2YjXQWWXRpIpCAaifC9f/0nopEI8aTyQVuCtoQXYLL3c8/SNzns2sf50q/+Snsi8xokmYJ4nnE4aW0J5Y8r38o4dq8DQXD1Wlsi2VHySZfuzjtyf0BZ+o/3SKW0owpxwYq3aU8q29qTtCVS3PpkS8f+w6retNU8TWdUNMiIyG+BZ4GPiEiriJwLXAt8WkReBT7l7oPXO+w1oAX4OfBlANfgfw3wnPu7Ot0JwG1zu3vO37FG/4oIqsrqinwBrtAJdebkkfz87CZ2q8usrqqPRtgZT1IfLfwTaE8muemxV2hLKNvjyS7X3f7kz69x2LWPdxz7sAENXHjU2MBtFTjxlmcy3qcfP9lCW8ILhkn1epkFtVVFRTpKK2FUb5brszR9R6V7l52Z56GjA7ZV4II8+7kTuDMgvRmY2J08doZNFpirJ1aAHDaggTknjM+pTgNYvmYzIwb3I5UVHuKpFJNHD8mp/sp2xNjhPPZSedrp2hLKJfcvZ/w+gxi710DOmjKGWxa9mjGvGUA8qcTdjAPp6slCc6ZlPndXaaXc1Zu2mqfpimpsk+mVbLLAYD3RNjB/2VqueWg19bFdgxQVmHbdEx2fx+lNo5jb3Jrx+ax6632SRaq6nnp1Q1nymNaeVI696Sl+ePpkpo0dzoVHjeN/HnuFgEIJqZSyat37gBYNhmlXnDQhtLEx1s5jusKCTBnYFV5+XW0bKLVUGDT9/tUPrgaUtoR2fB5zm1tZeOHhHWNRAKZ897GcNphsdbEISU2S3XwSjUhggKqPClecNIHWTdv5yZ9fC9xnMgX/ed8yYtEIdVEJDDDgBaR//+Vz1McipDSzs4KIEMHrudYQi6CQMQNAqUtNd6aUY1PJ5Ge1GPlZkCkDu8LLryvTj3SmVLhq3RYiktm7KxoRUAF2Xf2nOwKkp+9/6pX1RQMMQDKlXD1zYsdyASlVvvzPH2Zo/3q+89Bq/EvL1MciPPyVwxm710CWr9nMXf/3OtvzrD2TVEgmUrQlCr9+IqUkXM+0uqhw66yDmTBiUMexv78jwaDGWMc8bFDa+9eVkrdNJRPMajEKsyBTBnaFV1hn2gY6Uyqcv2wtl85bntOm4ZUwcttgMj+P4OLDKQePZOGKt6iP7jphKJBIpjpKHDc90UJDVFC8E3+/WLRj27F7DQRg5doteQNMV3mDQJVhAxrynthKef+6U/K2qWQyWS1GcRZkysCu8IortW2g1FJh+sedHWAaYhFuONVr9C/0eUwYMZi6qGSM3q+LCt88/qN88/iPdpxEAQ679vGcKq30AMr6qHLrrI9llCQ2bm3jigWZsz6XjxQ8sZXy/nW35G1TyexitRjFWZApE7vCK49SS4VBP+7d6qL89N8O4cgD9gAo+HkMG9DAmZ8Yzd3PvtmRduYnRndsl/5/+ZrNRCWCv+rNrz0Jy9ds4cgD9gS8APPg8nWBVXExN4CymIhAfQR2BrzkiMH9Cp7YSnn/rORdPvZeFleNgzF7ra4OVDS7lDq+I+jHnUI72ivS+8r3eWzc2sbc5taMtPue81agfOqVd3nqlfVs3NrGqKGNJLVwtdcti1rYuLWN+cvWcti1T/C9P7wUuJ2UOMqmPhYhiRDNmkigPiqs27Kz4ImtlPevJ6YI6ivsvSxObHmVTE1NTdrc3FzpbPRJ/h46UHz24G/P/1tGSeTsQ8dw9cn/VNJrLV+zmc/dvpQPslre/TMfxyJw4+mTAbho7rK8HQV2q4/y088dzLl3NWdUv/nVRYUrZ07givkrS+pwkE99VPj+aZOA3OpAf2Nzvt5OnX2PTWmsdxmIyPOq2pSTbkEmkwWZyuhsD52NW9uYdt0TOeu/LJ49HSh+8ty4tY1PfOexvN2H0xpiwv9d5o0NfvbvG/n6fS/kBImGWIQfnHYQF/52We7zowIi3HDqrob59NiX3eqi/O6FdfzmL28GlnH61UVIplLEs6rNYhH449eOZN2WnYBmtAcVUqizQF8/QZruyxdkrE3GVFxXeujka5e4Z+mb/Ni3QmXBYFXCgi1R2dXWMXr33bhq5kSuWLCrNFIX9QLIoMbgRcYuP/6jnDRpREZbz5EH7MH8ZWs5/4HniUUKV6JdPXMil/8usxNBIgXH3fQU/epiHcdYrD2w5Z0P+Mb9y2lPasZ7/MHOBNc81L0F1owpxIKMqbiu9NAJapdoTya5dVFLxtou35gXHKxaN+2gXyyaMztytqTuWpclKkI8meLSGQdy4N6DeH9HO4Ma65gwYjDglTD8pZxYhIwAkxY0gDRbfcyr2x+yW13g4/EUxF1V30VzlxGNRDK6XfsDxfxla/nGvBU5c51FRbhq4WraE5XvfmulqdplDf+m4rrSQyfd4NoQE3ari9IQEy48alzOhJdtiRS/Wbqr3SY9g3D/+ijJgKpi/9IwsQh8+8QJHeuybGtP0p5UvvvwSyxcsY5L5q3ggnteYNp1T7C4ZQM3nj45Iz83nj458IQZNGu0X10UHv7K4cycPLKjq3UhiZR3nEHrxqQDWntAQ1A8maI+mruAW3pFzZ5ik27WNivJmIrr7Dij9FXvBzsTgLhqL2H3AfWB0+ff9PgrnDVlDM+0bMhok0jPZ5YuoVxx0gRmTNybVeu2AMKEEYNo3bSDqOSe5NM90/wlgMWzp/N/lx2dszpn9hV6UFDNJKx+633G7jWQZ1o24G839Zai1oKdB/ylwHyTa6anv7nmodUZ6T3d/bY3DWa00lbXWJAxVaHUcUbpxuuo5C4Eds3C1Zz58dH88tk3MtITKXj27xtyTmbZ85mlXzM95iUtnmfdF79oRFj00rscdeCejBraSOumHfxx5duB7R3pWaO/9fvgAZvpGZjH7zOI2Q+syAgo0Qh8+8SJHfttTyZJKRm92vyBIiig+ae/GdgvVtFBxL1lMKNNHdN1FmRM1Sg2krxYW0ZdJMJ+w/sHPrZha3vOySwqwrI1mznqwD3zvq63/POEvAEhbVtbkm/PX0n8d4qqN2nltnbvtYKu0CeOHEz/+vxtQnWRCMvWbM7Jc300ysSRg1k8e3pHQF7csiFvoBg2oIHTDxnF3Ut2VRme8fFRHdPfVHoQcW8YzNibSlvVyNpkTK9RrC0jnkpx+NjhZC2ESSwCh48dnnMy29aeZM78VRx2beF2gFlT9+U7/zKR+liE/g1R+tVFOPvQMfSri2S0aWyPp4gnvaqsdIDx87d3rFy7pWCng/R6N4UGXaYHmqZXz/z1F6fkrBq6cWsbc5/PHHQ6t7k1Y1XLSg4i7g2DGfOtvNrTbVe9lZVkTFUpVO+dry1jt/ooyZRywSfHMrR/PTeePplvzFtOVCIkNcUNp05i7F4DO9p9/FVtO9wglIvvX17wynTWlH2ZMWHvjLydPXU/jrv56ZKPLR0gNm5ty2kLAYgK7Fa/q1uyP8/FqrPylQJ7Q3VUpUtTxQT3ZEyxZUecjVvbqi6/1caCjOlQ6YbNoHrv7JNP+qQLsDOeoj4CbYkkKNz21GvcsuhVLjxqHA995Yictpb0yezB5Wu58sEXM147nlRWrduS0x6T/Z7435dt7UkaYhHiJSwoVh+NcMEnveWWg078jTFh9nEfZf89+nd0iV6+ZjPTxg7PqBrr7OfSG6qjoLon3czumLIzkSSZSnHBPX+19pkS2Ij/LLUw4r8rwaLSDZtBI/jrouJNFhmNZuSp5Z0POP7mpwPXuE9Lz8YcNKL9qVfWc/adf8l5zn/88/588Yj9O7b948q3O5Z0TqSUC48ay1lTxmTMtjz1e4/nnUomLSpex4AGtyTAnBPHc83C1TltSw1RQSLCzEn78PsX1mUsJd2dz2LBsrU5JTs7KXaeN1vDFs67uzlj9u/0TBPVGiR7io347yO6EiyqoWEz6Oo+ffJuSyQy8uSVIKK0J/Ov+NWWSOUd0T5t7HCiQs6UMrc99Rp3PPM69TGhLZHqeP300sc/ePQVbln0asZJutDyzY0xIQWk1FvlMp3faxau5tAP7c6iVzKXdm5LKiSVuc1rM163u5+Fl8NdXb3zqXRJttoNG9DA4MZ66qPRju8kVF/1Y7V9jtbwX0P8wSJoYF4+1dCwWXzsSOHp7IOkR7Rnvx8A5x+5f872SfXq2re2JfOWTtoSyjfmLeepV97l2b9vJF+MqY8K5x35YX5w2iT6xaIZj8UTqZwAU+w4in0W6UGm2Z/1rnV3UmxvT3YE3+ztbEBkaaq9+rEaP0cLMjWkq8GiGn442b2MGmKRnF5i7ckkW3a0AzDnhPFF95lvRPs9S9/kzsWvdzmvbQnl//3qeb5+3wt5t2lPKj976jUuvn8FO+KZJa5ik3Jmiyd3fRb+YJK+fc+SN/KeWEr5TnT14qQvqubecNX6OVp1WQ3parColpU9s3sZ+cd/7IgnSClccM8LxFMpLvjkWAY0RNnalr/R/ZJjPsKNj72Skeaf36w7dpSwtHL6NaKREmbiLOCKkybkLLm8M5F043F2jbUJquos5TvRG3qgVVJ29VO19oar1s/RgkwN6U6wqJYfTrqX0catbew7rD8LLzycdVt2usbWFHHXrnHLolcp1Gelf0OUKfsPy+i2HE+mOGLccB57cX1JeYmKNzq+lIBSSKF2G7/d6qOc3jSK3/xlTccqmlecNJ5ZU/YNbDcDSASMtfGfWEr5TlRDSbZa5WvjrMbecNX6OVqQqYAwG+a6Eyyq5YeT/cO+4JNjqY9GMksfSt72EPBO7KOGNjJp9BA+2JngqoWrqYtGSg4wnz90X6Z/dE/WbtqRM9V+WOLJFPcsecPrzZaeS23C3ixfs5ktO+KBc5AF7ifrxFLsO1EtJdlqUw0dYjqjWj9H68KcJewuzJXuKpxPtfRICerK3BATQApWcUUFYtHc6e6D9leMf19tCW9usESJpZGuSpdcsvMRjQj1sQjxpJJMpQpOjNkQiyBCyatkZquW70C1CFo9dWBDjF9/cQqTRg+pXMaKqNTnaF2Yq0B3rozC/OJUU+ALnDVYhTM/MZp7m9dQF/FO/JGIZASO3epj3DrrYAY31mW8R/lmIc6nPuotItaWSHW73aZUn/ronvz5ldwSVlIhmdSOrsx1UaEh5o0b2pnI7QGnqjz0lSM65iUD77O9dN4KohEhmdKOsUNBqqUkWy2qtfqpmGr7HK13WQ8K6ukTEXFTy+cXZrfErvZIyddltruCfthtyRS/fPYNZk4awa+/OIWHv3pEzvPiqRQTRgzqmINr49Y2nnplPWve295xks6nPio0xISLP30At5/TlNPluByCRqdEBW4582N8Zfo4GrK70gWoi0a4/LiPcuusj3HHOR+nf31mPmORiFuS2bNxaxuX3L88o/vyxfcvr3hvo96ip3uShfWbqjQryfSgoBPo9vYk593dnHcUdtj1wl3pkVLukk92Ke36Uw7ikvuXkd2mPbe5lfOP2D9zHrKIEE8oXzhsv4z8XTx3WUfVUjQixCLQ6JYrnnPieNa8t507nnmNuog3mv/iT3+EKfsPo399tKQxOJ31pX/en9uffg1/rV0sKgxqjNG/PlpSddz29iQ3PPIySVXmnDg+Z9G17fH0d8n7PFat25JT2sk3fY4J1lMdYqqpNqHcrCTTg3at5pi9eqPmLT2EMVDSf8XUmSqBdOng0nnl64sfVEqbOXkkXzv6gMDtFyxfx8atbcycPJI5J4xnZ3uS9mSKn/z5NaZ+73HuWfoGl85bntF2kUwp0UiEW2cdzOLZ05my3+7c+cw/iCe9mZPbk8p3//ASs25fwom3PMPpTaPc4mCdl+95r23YSnazULpLdvo1S/kxbmtPsjOe4pqFq5lzwnjXXrWLf7Dl+zvyzYjQtWPrq8Kepbpax7eUi5VkyqTUNpOZk0cyZLd6vvSr59ke33Wpnq/00Nl64WL5CKqjL6VHSvpKKxLQAN/Vvvj5Smkf7Exw0+OvBD7n9qdf47anX2POieO56sGVGQMb40nlygWrAk/W0YgwuLGOZ1o2BK53D3SMubnvuTV0Mcbk7a78yKp3c9LiSe3okn3PkjdLbDXaZfTuu/Hzs5v40q//yvb2zO/SPUvf5NZFLTnPiUVgwohBnXwlU0h320urdXxLuViQKYPOFnUnjBhEKmtwXr7A0ZluicXyka6j91ehXHz/cpZcfnTBmX5b3vmAb9y/PO+ElF1tDA36cUUjwlUPrsqpKkvb7ooD3/79ysCR8/mmg0mmlP710bzr3ftFJeJN8VXC7MrZutoHrbOzAOyMpzj3rue4cuYEUlnVZvkGnNZH4funTa6JE1e1KEc1V2/tYFAqqy7rpq4UdTvboFhoUarO5KNQHX2+KoH5y9Zy/I+eCQwwu9VHu9UYGvjjSipSQimisyflZCrFH1a+XXDRs7Sd8WTHOjPVLJ5Urn5wNRd96gDqo0J/93lceNQ46qOZx7lbfZTbz/l4zdTzV4NyVXNV81Q15WAlmW7qalG3sw2KxbollpaPfGfv4PT0jyjoyr8hJvz0cwczYcTgLv8YgkppF33qAL77h5e6tL9CEim4ZVELpZQ1JCKFR3qWWVQgEpGiSwbEIuSMk1GF7//pZepjEdqT3gwBMybsza1PZlaVpVQ71qnxa3nnA5at2czk0UMyuj6b4spZzVUtM26EwYJMN3WnqJsdOLpTt1tKPiaMGJRzoipUR59vjEl9VLjh1Ell6aGU/ePyXlOIh3CSr49GOP/I/bml2NxlPTxAWQRSJRxvUJbbkyn3v1fyunL+SiLAFw7bjzsX/yNjLZ7s79S3f/837l7yZsf9sw8dw9Un/1PXD6TGFPs9lruaq9rGt5RLzVeXicgMEXlZRFpE5LJy779cRd3ujoXx56N/fZT6qDDnhPEZ+Rg2oIEbT59MQ0zYrS5KQ0y48fT8dfRBP6L6WISHv3pEWapd0r3cgI6qulFDGymhRqtLDfPtyRRnTRnDlScVnsG5s1Vx3ZVIle814ym4/Hcr+cmfXyOl3pIGQVWsLe98kBFgAO5+9k1a3vmgPBnppHKOESnHvkr5PdZ6NVe51HRJRkSiwK3Ap4FW4DkRWaCquQusd0N3i7rlGgszc/LIjnm66mMRrnloNQP7xTJOMJ3Ja1B11pwTx7OtPdnttc3zNZhu2tbOqQeP5rd/2dXbKhaBYybsxeMvvkt9NEp7MkkyRU6DdzEXHjWWYQMavOWa+4B4UrllUQtnTRmT89gyF9yzPdOyvserzco5RuSeJW943/+oN01PV/bVmd9jNVdzVcs0QTUdZIBPAC2q+hqAiNwLnAyUNchA94q65arb3bi1jWseWk17IkW7GyIR9OPoTF79P6KVa7dwzcLV3T4Z5PsRP9OygbnNrR3bferAPRi310DuXPw6T72yAVXhc1PHsGrdFp56dWOnXrMuAgP7RWl55wM2bYt3Os+9VTQigd+j/YbtFrj9dx56kd37N/RYB4FyDja+Z8kbfOv33mSmhb7/xXT291iN1VzVNLiz1oPMSGCN734rMCV7IxE5HzgfYMyY3Ku+sJWrbjes/vbp5372tmdLPhkEXUWl04JmFI5ARoABeOyl9Tz16nrak5Ceo/Anf36tpDzHIl53hvpYlO3tSeIpuPLBF4EXO3fwvVx6Nuq09GewPZ6kLprb2SCe6tmZhst5gXXVg6ty0vMF2UJ6e5fiaps9utaDTElU9TbgNvBmYS7HPjtTVC3XFN1h/jiCTgYRhFXr3ufIA/bI2DboKkqhI609mSKZlc90A3Y2oWsLfiVSXullR3uyG8uF9W51UeGGU3d9j/yfS6H53HpyIGBZL7CiuccVT2qn91WtU+aXqtoGd9Z6kFkLjPbdH+XSQlWoqJov+JSjbjfMH0fgvGtZc2VB8FXUN+atAJS2hHak+WcULtR1OXt+rs7o5lpjvdrnD9uXr0wfl1GKzP5cYhFveYDsnnbFTvLlrOsv5wVW0HflipPGdymP1dzWUky1lcRqPcg8B4wTkQ/hBZczgLPCfMFCRdVnWjYUrCctR91uuX8c/hPK9accxDfmLactsevHnJ4rK10UzzeK3w2h70iri0S4/tR/YlBjPeCN4WjdvJ27n83sUvuRvQfxrR5aNKyW/OYva/jK9HEd94M+l8Y6b3mE5Ws2c8uiVwt2d04Lo66/3BdY6VVQrzhpArOm7At0LTBWY1tLKaqtJFbTQUZVEyJyIfAIEAXuVNXcitsyyldUXbXu/R6rJ03/ONJdOQv9sAr9+IJOKPnmykoXxYOuorz5vHJnDP7avcuIRIR+sV0nt7On7pc7OFDhygUr+3TJpLOiQsZsy/mubieMGMSRB+zBWVPGZHwP8rWphfUdDvMCqxw9znqbaiqJ1XSQAVDVh4GHe+r18v2YQXu0nrSUK85i1XpBJ5SFFx6e03XYXxTPdxUF5JSC0otypSeJvPSBFSyePZ1Tm0Zn7H/W1H0ZvXtjTnAz+e2Ip/j3XzzH1z51AGdNGVP06tZ/ks/3vai2uv4g2cGqXD3OeqNqKYnVfJDpafl+zBNGDO6xetJ87SL+H1axq9J8J5Rt7UlOP2RUxkC+05tGldS+FBHhkvuXszPPaHv/EgbZz50wYnDBcTERgctmHMj3//Qy8aT22cZ+v4TCDx59hR898SpfmT6Os6aMyZgIFcgp6Rb6XlRbXX8x+XqcCdiaOj2o5kf8V0LQhJadGR3c3RHLQWvQtCVS/GbpmwW38Z/k851Q4okk92Z1NZ7b3JqT1+wJN+cvW8tFc1/IG2DA62H2h5Vvcdi1j+eMtM6Y0aAhd+XKiAjTD9yTZy8/mh+d+TGitmRKh/ak8oNHX+Gwa59gccsGJo0ewjMtGwJHtBf6XvS2Ee7pHmfZdsRTnHd3c1lXmC2nWlsh00oyIQkqqpZST1quqcODugTfsujVjqqTYlelQSWy05tGcdbtS3NmZC5WZbJxa1vGSpVp6Ykh+8W8NesTyRQ/deNg2hJe3cYl9y9n/D6DGLvXwI73b9FL73LFglVs81WdJVLK8Tc/zRkfH81vn1vT41PD9AbpThrj9xlUsLSyrT1zsbNt7YmO70U11fUXk6/HGexaKLDaqs1KWa6jN7z3flaSCVlnrkrKOXX4hUeNzUmvj0Y7SiqlXJX6S2QLLzycuc2tgVP+F6syWbXu/cDJHW8642Msufxobp31MSISPH9Xe1I57uanuWfpGx1pew7qF7hccXtSuXvJm0VnM641nZnHrS4SYdmazXlLK5u2tedMQJ1S2LStveN+dim1Wvm/442x3FNdukNOtZQaiv3+uzu/YaVYSSZE2Vclpx8yirnPt+a9Silnw+pZU8bwP4+9knHi3hFPZASDUq5K0yWy5e7ElDMjc6x4lcn7O9oD0wc1xhg2oIHBjfXUR6MdpZds8aTyrd+tZPmaTSxY/hZ1kQjJVIpYRAKDTV/TmbcgnkoxefSQvKXYB5cHn7iWrdnM0P71Fb+K7uyVfPo7vmrd+5x3d3PGmKAd8QTn3d1MfbTyU69A4d8/UFWj+DvDgkxIghpQ043l+b4k/eujOZM3drZhNf0j7F8fJRIRkr4oIwGrgZXaAyVwRuao8PBXDi84oeI9S97gqgdzp4qri0rH+iajhjaytS3fevS7zG32ToDp968+CnVR6AXri1WFBndBMHavgYGdU55p2cB3Hw6edue9be1Mu+6Jis6F1dWq5GEDGjjygD244dRdx9yeTJJSrwoxHXh66qSdL1AWqsLuDT378rEgE5J8a7H4+b8k6R9QxNUbNUQFiUinGlb9P8K2RJJo1kJY/WLRLn8p8/WaKxZg0t1H/eqjwvdPm9SRj03b2rvUG6w9qUwePYhla97vwrNrW79YJKeTRTLlVcNAbikWYNp1TwQuex0R+MGjL2fM2NDTV9HlGKPjP+YtO9q54J4XOrrPQ/BJu9xtIIUCZbFu5r2pZ5+fBZmQBF2VZEt/Sfw/oDQV4aELC5cS/IJ+hNmNHN39Upba6Ltxaxur1m3hygdzA0xjXYSf/dshGd1H8007XwoLMLlmTtqbP61+Nyc9kcKbPUG9sUf+QbuLXnqXWJ7GnX51UTeWNngAbjnlO6mX60ref8zFTtrlnt2glECZ7zdWbaP4O8OCTEjy9c6a29ya8yUJau9oiEYyek8VE/QjbIgKKkJDtHxfymLVax0lMpHAaqykkrMM8OTRQwL3FcFbNbKPteN3S0NUeGTVO3z246O59y9rAjtqXPngKmZM3DujBB0Vyft9C5qxIYyr6EIn9TBWofSmSVpBNCIk3UwAYc5uUGqgzPcb6009+/wsyIQo6EvxtaMPyPmSlOMHFLQPiXiloW3tyR75UgaVyLIFTVg4dq+BTB41mGWtWzLS62KSMUOAKa7NBZXfLH2Te8+bypm3L82dzj+p/Gbpm5w1ZUzg51UfFdqTSr86r0dWesaGMK+ii53Uw7iS1/S/mjvTdxhtIOX4nZfShlpt3ZwtyIQs+0sR9CUpxw+oK20m5ZavHaoxFiGpmjFhod/GrW28+HZutZcFmK5LpGB7PMWVMycETjD6oydeYdLowTmfV//6KFfNnMDk0UNyLk7CvIou5aReziv5dFDzvmNeCc4f1IICQnsyyZYd7V1eFbYnqryqabGyNAsyVaIcP6BKF6eDfpgNsQg/O7uJCSMG5c1P66YdBbswm65SZk3Zl9b3tucs9taehGf/vjHn80qkUhx14J4Z1Ub+qWfC+k6VepWfLw+lXL37tykW1LIDwo54gpTidRboxsk7zN9otS1WlmaDMatIOQa5VXKgXNAAzxtOPYgjD9ijYH5K6SRhOsffRfyLR+xPfe5MPNy5+HXmnDAe/zjFlMLilg1Azw7+686UNaXkM3ubleu2BJZUsseRLZ49nVtnHUw0EiGe1G4NkvYfaxi/0WJTRVWKlWRMWaSvEqeNHZ4xCWMpPyT/VWM0Imxrs4EvnRWRXYMyoxHhypkTMnomfWX6Afzg0VcynlMfjTB690YisqtNIp7UglPPjN9nUGhtfF25yi/l6j1om6sWrOKSYz7C9Y+81DEbRTrAZq/xNLixjvpo5uJu1ThGxZtOqnvj7MJgQcZ0Wznqgf3zkn3r93+z9phOqo8I7SkF9cbIXLNwNQMbYh2fw1lTxnDLopaME2U8leLZv28MnItuWZ4ZHo6/+WkafOv/lLu+v7NVcqW05QRt055Uvv+nl/FPbZYOsNnVS71l9ulnWjZkzP4Qi1AV3Zytusx0S7nmWwPvBDN59BALMF2wM6mkFFLAtvZk4Odw4VFjaYhJR3XUnBPGc+fi13P21ZZI0K8umnNVvDOeor1MVUblki8A9K+PdsxJlq86tj2pOd3jg6qXulOV11PSv0N/T8JoJMK0scMrmCuPlWRMt5S7q+e29iQNUenoimu6LiLCqnVb2LQ93lHSBOH8I/fvWAkzuxoIIKXC5f/7N1LqXQ031sVoS6YQ1YzPpRqqjPKNRzvxlmcyStbXn3IQl9y/PHDckF++Ekpnq/LC7EYctO+g32F9tPKfD1iQMd1U7qqEUUMbkXxTMjtR8Uahd2awal+0vT3JF+96DsWbXih9Arr1yRaOm7g3W3a055RWwFs2IT39TEMswq2zDmbE4H6ceMszGZ9LtVQZ+QNA//ooJ97yTE4bzeLZ03n4q0dw/I+eod3fthIVIuLNUF6sS3GpVXlhdiPOt+9qrtKz6jLTLeWuSsjeX1Qyp7KPReDqkycSt9mXS9KeJGcwZiKZ4vibn+aCe17oKK0MbIhRH5WOAZhp9dEIoGxrTzLnhPFVVWXkX0Yj3WNrW3sybw+rsXsN5PunZn5Xf3DaJP7vsqMzFhjsbp7KVX3cmX1Xc5WelWRMt5W773/Q5I2r1m0BZNd4GyFwkKEpzruQV9rd5JD1UeHiYw5g4ohBfO7Ov2RsuzORdNPhe1f6c04cz8QRg7v0OZezCqmrV/SF5gYrhzBnSy6275mTRzJ+n0EsW7OZyaOH9OhA7EIsyJiyKPdAvez9Za/HPmvKvqBw1YOrUDRnnrSozXlWsvak8r0/vAQon/346I759dqTKZKpFG3JXSuVXrNwNYtnT+/0Z13OKqRi3ZbnnDCeqx5cRV3Um2ki+4q+0He1u4EwzGqrYvuuxtH+YNVlphebNXVfnr38aOZ9aRrfPO5A6qNC//oo/eoiXH3yRBpi+ZeMjAp0YkHJmuetq+LNabbwwsP59Ren8POzm2isy7wO7crgvnJXIRUadDh/2VqueWg19bEI8ZQy58TxJZ9oOzv4NGjV2zCrrQrtO8xquu6ykozp1dJXpZNGD+GUQ0ZlXIUO7BfL6HU054TxjN69kXS12+KWDVxy/7KcNVQi4i3wlizQ7lMfFepiEZIp5fSmUdz33JqydL3+1IF78NhL60vatn99lKQq+w/fjdVvbS3pOQ2xCJ/9uDcbuCjsyOpZlkjBui07OfKAPUqaDr8U5a5CKtRtOXvCz2sWrmbGhL0zXieotNLZKVmCSg3pariuDEguVb7qvmpe1MyCjKkZ2dUgxdqK0o//Zumb3LKopWPK9xtO9U4YDy5fx/cefjGnO/XFnz6gowuwf3bt9H5iUa8311mfGM1vl7xBWwkz5sQicNXMicyYuDeHXft40YD1zeMOZMr+wxg1tJFFL73LJfNWFH2N+qjw87ObOPKAPfja0Qfw4PJ1XBmwaml69H+5JnQMa5r+7HylG/0LnWjzVSl15iQdFJAumruMaCTSI0s5B1X3VXPvMgsypqYVaysaNqCBrxw9LidoAJw0aQTX/vGljMadhliEs6aMCZxdO3s/APc+twYClq32j9fYrS7KT//tEI48YA8Abjh1Epc+sAJNeeNSYhEhkVLqoiCIN5v11F2zWedbjydbJOKV4NL5PWnSCL7z8IsZvc/8c55BeTp1hDH7cFC+ipW8CpVWOnOSDgpIiZQ3uWhPL+Wc1hMzPHeVBRljKN8SDNn76ZiTTYR4MsUlx36EGx99JSNwpdCOkz/kjvvY1p7s+D/oRD92r4Ec/0978fDf3snJTwTo3xALzPuwAQ384LRJGQt33XBq7vGVo1NHGLMPBwX6Qp9XodLKpNFDSv6sS5nQtRJVVcXe40qtMyOq1gXHr6mpSZubmyudDVNFuvvjzH7+gmVrc05m5Rifcdi1T2SM3m+ICQ995YiiE1pW2yJX3ZXveDZubWPq9x7PKbktufzojLaZUt4L/2fYnkyS0szxSP3qIl3qhReWnuh5JiLPq2pTdrqVZIwportX8p1tK+rqa9xwatcWrSt39/NKK3Q82RfV2fdLfS+yP8PFLRuqsqoKKr/OjAUZYyogjBN7pRetq3atm3bQWBfrmDIHvHnZulqt5f8Mq/m9r3TPMwsyxtSQWiuVlFPYPbCq9b2vdM8zG4xpjOkTqnl+rzBV+rit4T+LNfwbU9tqraNDqcI+bmv4N8YYqrdaK2yVOm6rLjPGGBOaigQZETlNRFaJSEpEmrIeu1xEWkTkZRE51pc+w6W1iMhlvvQPichSl36fiNS79AZ3v8U9vl+PHaAxxhigciWZlcC/Ak/5E0VkPHAGMAGYAfxYRKIiEgVuBY4DxgNnum0BrgN+qKpjgU3AuS79XGCTS/+h284YY0wPqkiQUdUXVfXlgIdOBu5V1TZV/QfQAnzC/bWo6muq2g7cC5wsIgJMB+a5598FfMa3r7vc7XnA0W57Y4wxPaTa2mRGAmt891tdWr70YcBmVU1kpWfsyz2+xW2fQ0TOF5FmEWlev760adaNMcYUF1rvMhF5DNg74KFvqer8sF63K1T1NuA28LowVzg7xhhTM0ILMqr6qS48bS0w2nd/lEsjT/pGYIiIxFxpxb99el+tIhIDBrvtjTHG9JBqqy5bAJzheoZ9CBgH/AV4DhjnepLV43UOWKDeSNJFwKnu+ecA8337OsfdPhV4Qm3kqTHG9KhKdWH+FxFpBQ4FHhKRRwBUdRUwF1gN/BG4QFWTrpRyIfAI8CIw120LMBu4SERa8Npc7nDpdwDDXPpFQEe3Z2OMMT3DppXJYtPKGGNM5+WbVsaCTBYRWQ+8UWCT4cCGHspOJdlx1hY7ztpSjce5r6rukZ1oQaaTRKQ5KFrXGjvO2mLHWVt603FWW8O/McaYGmJBxhhjTGgsyHTebZXOQA+x46wtdpy1pdccp7XJGGOMCY2VZIwxxoTGggwgIq+LyN9EZJmINLu03UXkURF51f0/1KWLiNzs1qlZISIH+/Zzjtv+VRE5J9/rVYqIDBGReSLykoi8KCKH1tpxishH3OeY/ntfRL5ea8cJICL/6dZlWikivxWRfl1ZX0nyrOFULUTka+4YV4nI111ar/88ReROEXlXRFb60sp2XCJyiDuvtbjnVmYWelXt83/A68DwrLTrgcvc7cuA69zt44E/AAJMBZa69N2B19z/Q93toZU+tqxjugv4ortdDwypxeP0HW8UeBvYt9aOE2+W8X8Aje7+XODz7v8zXNpPgf9wt78M/NTdPgO4z90eDywHGoAPAX8HopU+Pt9xTsRbf2o3vLkWHwPG1sLnCRwJHAys9KWV7bjwpuSa6p7zB+C4ihxnpb9E1fBHcJB5GdjH3d4HeNnd/hlwZvZ2wJnAz3zpGdtV+g9vgtB/4NrhavU4s47tGGBxLR4nu5ay2N2dfBcCx+IN0Iu5bQ4FHnG3HwEOdbdjbjsBLgcu9+23Y7tq+ANOA+7w3Z8DXFornyewH5lBpizH5R57yZeesV1P/ll1mUeBP4nI8yJyvkvbS1XfcrffBvZytzu75k21+BCwHviFiLwgIreLSH9q7zj9zgB+627X1HGq6lrg+8CbwFt46yU9T+fXV6rq48QrxRwhIsNEZDe8K/rR1Njn6VOu4xrpbmen9zgLMp7DVfVgvOWdLxCRI/0Pqncp0Nu74cXwiuY/UdWPAdvImjS0Ro4TANcWMRO4P/uxWjhOV1d/Mt7FwwigP96S5TVFVV/EWzr9T3iT5i4Dklnb9PrPM0itHJcFGTquClHVd4Hf4S33/I6I7APg/n/XbZ5vzZtCa+FUg1agVVWXuvvz8IJOrR1n2nHAX1X1HXe/1o7zU8A/VHW9qsaB/wWm4dZXctsEra+EZK6vVO3HiareoaqHqOqRwCbgFWrv80wr13Gtdbez03tcnw8yItJfRAamb+PV468kcz2a7HVqzna9PaYCW1zx9hHgGBEZ6q4yj3FpVUFV3wbWiMhHXNLReEsq1NRx+pzJrqoyqL3jfBOYKiK7uV5D6c+zs+sr5VvDqWqIyJ7u/zHAvwK/ofY+z7SyHJd77H0Rmeq+H2f79tWzKt3wVek/YH+83jXLgVV4y0ODV1/9OPAqXo+W3V26ALfi9cL5G9Dk29e/Ay3u7wuVPraAY50MNAMrgN/j9UapxePsj3eVPtiXVovHeRXwEt5F0a/weojtjxckWvCqChvctv3c/Rb3+P6+/XzLHf/LVKgHUpHjfBovgC4Hjq6VzxPvIugtII5X03BuOY8LaHLfjb8Dt5DV6aen/mzEvzHGmND0+eoyY4wx4bEgY4wxJjQWZIwxxoTGgowxxpjQWJAxxhgTGgsyxvRyIvJJEVlY6XwYE8SCjDFVSkSilc6DMd1lQcaYChCR/cRb1+ce8db2medG778uIteJyF+B00TkGBF5VkT+KiL3i8gA9/wZ7vl/xRsFn97vP8uutXReSM9mYUylWJAxpnI+AvxYVT8KvI+35gvARvUmbH0M+C/gU+5+M3CRiPQDfg6cBBwC7O3b5yXABao6GTgC2NETB2JMPhZkjKmcNaq62N3+NXC4u32f+38q3qJii0VkGd5cVvsCB+JNjvmqelN2/Nq3z8XAjSLyVWCI7pr235iKsCBjTOVkz+mUvr/N/S/Ao6o62f2NV9VzC+5Q9Vrgi0AjXnA6sKw5NqaTLMgYUzljRORQd/ss4Jmsx5cA00RkLHTMGH4A3qSY+4nIh912Z6afICIfVtW/qep1wHN4pR5jKsaCjDGV8zLeInkv4s2I/RP/g6q6Hvg88FsRWQE8CxyoqjuB84GHXMP/u76nfV1EVrrt43hruxtTMTYLszEVICL7AQtVdWKl82JMmKwkY4wxJjRWkjHGGBMaK8kYY4wJjQUZY4wxobEgY4wxJjQWZIwxxoTGgowxxpjQWJAxxhgTmv8Pn8yknobl9+gAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "\n", + "preds = pd.DataFrame({\"preds\":model.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Ridge Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f2f52314", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 86.04732435905834 percent in Knn algorithm\n", + "Test error = 29.305494469835775 percent in knn algorithm\n" + ] + } + ], + "source": [ + "n_neighbors=5\n", + "knn=neighbors.KNeighborsRegressor(n_neighbors,weights='uniform')\n", + "knn.fit(x_train,y_train)\n", + "y1_knn=knn.predict(x_train)\n", + "y1_knn=list(y1_knn)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_knn[i]-y_Train[i])/y_Train[i])\n", + "train_error_knn=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_knn)+\" percent\"+\" in Knn algorithm\")\n", + "\n", + "y2_knn=knn.predict(x_test)\n", + "y2_knn=list(y2_knn)\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_knn[i]-Y_test[i])/Y_test[i])\n", + "test_error_knn=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_knn)+\" percent\"+\" in knn algorithm\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "06ed0796", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Knn')" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":knn.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Knn\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "27215d99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.41770536088067 percent in Bayesian Regression\n", + "Test error = 6.2704745296132876 percent in Bayesian Regression\n" + ] + } + ], + "source": [ + "reg = linear_model.BayesianRidge()\n", + "reg.fit(x_train,y_train)\n", + "y1_reg=reg.predict(x_train)\n", + "y1_reg=list(y1_reg)\n", + "y2_reg=reg.predict(x_test)\n", + "y2_reg=list(y2_reg)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_reg[i]-y_Train[i])/y_Train[i])\n", + "train_error_bay=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_bay)+\" percent\"+\" in Bayesian Regression\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_reg[i]-Y_test[i])/Y_test[i])\n", + "test_error_bay=(error/len(Y_test))*100\n", + "print(\"Test error = \"+'{}'.format(test_error_bay)+\" percent\"+\" in Bayesian Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c68c7026", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Bayesian Regression')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":reg.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Bayesian Regression\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a3c76976", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 98.15574267963966 percent in Decision Tree Regressor\n", + "Test error = 7.1677452486549855 percent in Decision Tree Regressor\n" + ] + } + ], + "source": [ + "dec = tree.DecisionTreeRegressor(max_depth=1)\n", + "dec.fit(x_train,y_train)\n", + "y1_dec=dec.predict(x_train)\n", + "y1_dec=list(y1_dec)\n", + "y2_dec=dec.predict(x_test)\n", + "y2_dec=list(y2_dec)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_dec[i]-y_Train[i])/y_Train[i])\n", + "train_error_tree=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_tree)+\" percent\"+\" in Decision Tree Regressor\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y1_dec[i]-Y_test[i])/Y_test[i])\n", + "test_error_tree=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_tree)+\" percent in Decision Tree Regressor\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "52ec5460", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in Decision Tree')" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":dec.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in Decision Tree\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f2b28bd7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Train error = 73.1224560206315 percent in SVM Regressor\n", + "Test error = 21.522381238558573 percent in SVM Regressor\n" + ] + } + ], + "source": [ + "svm_reg=svm.SVR()\n", + "svm_reg.fit(x_train,y_train)\n", + "y1_svm=svm_reg.predict(x_train)\n", + "y1_svm=list(y1_svm)\n", + "y2_svm=svm_reg.predict(x_test)\n", + "y2_svm=list(y2_svm)\n", + "\n", + "error=0\n", + "for i in range(len(y_train)):\n", + " error+=(abs(y1_svm[i]-y_Train[i])/y_Train[i])\n", + "train_error_svm=error/len(y_Train)*100\n", + "print(\"Train error = \"+'{}'.format(train_error_svm)+\" percent\"+\" in SVM Regressor\")\n", + "\n", + "error=0\n", + "for i in range(len(y_test)):\n", + " error+=(abs(y2_svm[i]-Y_test[i])/Y_test[i])\n", + "test_error_svm=error/len(Y_test)*100\n", + "print(\"Test error = \"'{}'.format(test_error_svm)+\" percent in SVM Regressor\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "71819737", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Residual plot in SVM')" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "matplotlib.rcParams['figure.figsize'] = (6.0, 6.0)\n", + "preds = pd.DataFrame({\"preds\":knn.predict(x_train), \"true\":y_train})\n", + "preds[\"residuals\"] = preds[\"true\"] - preds[\"preds\"]\n", + "preds.plot(x = \"preds\", y = \"residuals\",kind = \"scatter\")\n", + "plt.title(\"Residual plot in SVM\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ef357c92", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Train ErrorTest Error
Ridge Regression98.118859100.862308
Knn86.04732429.305494
Bayesian Regression98.4177056.270475
Decision Tree98.1557437.167745
SVM73.12245621.522381
\n", + "
" + ], + "text/plain": [ + " Train Error Test Error\n", + "Ridge Regression 98.118859 100.862308\n", + "Knn 86.047324 29.305494\n", + "Bayesian Regression 98.417705 6.270475\n", + "Decision Tree 98.155743 7.167745\n", + "SVM 73.122456 21.522381" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_error=[train_error_ridge,train_error_knn,train_error_bay,train_error_tree,train_error_svm]\n", + "test_error=[test_error_ridge,test_error_knn,test_error_bay,test_error_tree,test_error_svm]\n", + "\n", + "col={'Train Error':train_error,'Test Error':test_error}\n", + "models=['Ridge Regression','Knn','Bayesian Regression','Decision Tree','SVM']\n", + "df=DataFrame(data=col,index=models)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fbe3ec2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/qubo/generating_qubos.py b/machine_learning/scripts/qubo/generating_qubos.py new file mode 100755 index 0000000..211e00c --- /dev/null +++ b/machine_learning/scripts/qubo/generating_qubos.py @@ -0,0 +1,8457 @@ +# %% +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") + + +# XPRESS ENVIRONMENT +os.environ['XPRESSDIR'] = "/opt/xpressmp_8.4" +os.environ['XPRESS'] = "/opt/xpressmp_8.4/bin" +os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"+os.environ['LD_LIBRARY_PATH'] +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['PYTHONPATH'] +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.environ['CLASSPATH'] +os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprm.jar:"+os.environ['CLASSPATH'] +os.environ['PATH'] =os.environ['XPRESSDIR']+"/bin:"+os.environ['PATH'] + + +from leagues import settings +settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3' + +import django +django.setup() + +from django.core.files.storage import FileSystemStorage +from django.utils.translation import gettext as _ + +# from gurobipy import * +from pulp import * +from math import sqrt,pow,sin,cos,atan2,pi, ceil +from collections import defaultdict +import timeit +import datetime +import operator +import random +import time +from os import dup, dup2, close, path +from importlib import import_module +import builtins as __builtin__ +import logging + +from scheduler.models import * +from leagues.celery import celery, TASK_TIME_LIMIT +from scheduler.helpers import serialize_scenario +from scheduler.solver.functions import * +from scheduler.solver.tasks.optimize_localsearch import smartNeighbor + +# from research.learners import AttendanceLearner +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestRegressor +from sklearn.ensemble import GradientBoostingRegressor + +# %% + + +task = None +s2 = 5 +user_name = 'md' +user_is_staff = True +runMode = 'NEW' +localsearch_time = 0 +RUN_ENV = 'local' +solver = 'xpress' + +# %% + + +def optimize_model3 (optCameraMovement): + + model3 = pulp.LpProblem("League_Scheduling_Model_--_Schedule_Broadcasting_Slots_"+str(thisScenario.id), pulp.LpMaximize) + # games3 = [ (t1,t2,r,d) for (t1,t2,r,d) in currentSolution if broadcastingNeeds[d]>0 or optCameraMovement] + games3 = [ (t1,t2,r,d) for (t1,t2,r,d) in currentSolution ] + # print ("games3") + # print (games3) + # print ( print (len(games3))) + + networkIds=networkName.keys() + + if optCameraMovement == "TV-Kits" : + + movements = [(gm1,gm2) for gm1 in games3 for gm2 in games3 if gm2[2]<=gm1[2]+3 and getDateTimeDay[gm1[3]]+datetime.timedelta(days=1)= minDays[gm1[0],gm2[0]]] + else: + movements = [(gm1,gm2) for (gm1,gm2) in movements if distanceById[gm1[0],gm2[0]]/(((getDateTimeDay[gm2[3]]-getDateTimeDay[gm1[3]]).total_seconds()/(3600*24))-1) <350 ] + + # TV_day_pairs = [ (d1, d2) for d1 in days for d2 in days if getDateTimeDay[d1]=getDateTimeDay[d2] -datetime.timedelta(days=20) ] + # movements = [ (t1,d1,t2,d2) for t1 in realteams for t2 in realteams for (d1,d2) in TV_day_pairs if getDateTimeDay[d2]-getDateTimeDay[d1] >= datetime.timedelta(days=distanceInDaysById[(t1,t2)]+1 ) ] + + print (len(movements), "accepted") + + pred3 ={ gm :[] for gm in games3} + succ3 ={ gm :[] for gm in games3} + for (gm1,gm2) in movements : + pred3[gm2].append(gm1) + succ3[gm1].append(gm2) + + br3 = {(t1,t2,d,nw) : pulp.LpVariable('br3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpInteger) \ + for (t1,t2,r,d) in games3 for nw in networkIds } + unserved = {(t1,t2,r,d) : pulp.LpVariable('unserved_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + for (t1,t2,r,d) in games3 } + start3 ={((t1,t2,r,d), nw) : pulp.LpVariable('start3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + for (t1,t2,r,d) in games3 for nw in networkIds } + + move3 = { ((t11,t12,r1,d1) ,(t21,t22,r2,d2),nw) : pulp.LpVariable('move3_'+str(t11)+'_'+str(t12)+'_'+str(d1)+'_'+str(t21)+'_'+str(t22)+'_'+str(d2)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + for ((t11,t12,r1,d1) ,(t21,t22,r2,d2)) in movements for nw in networkIds} + + for nw in networkIds: + model3+= lpSum([ start3[gm,nw] for gm in games3 ]) <=1 + + for gm in games3: + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for nw in networkIds])==1 - unserved[gm] + # model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for nw in networkIds])==1 + for nw in networkIds: + model3+= br3[gm[0],gm[1],gm[3],nw] == start3[gm,nw] + lpSum([ move3[gm1,gm,nw ] for gm1 in pred3[gm] ]) + model3+= br3[gm[0],gm[1],gm[3],nw] >= lpSum([ move3[gm,gm1,nw ] for gm1 in succ3[gm] ]) + + model3+= - lpSum([ distanceById[gm1[0],gm2[0]] * move3[(gm1,gm2,nw)] for (gm1,gm2) in movements for nw in networkIds ] ) - 100000*lpSum([ unserved[gm] for gm in games3]) + + + # br3 = {(t1,t2,d,nw) : pulp.LpVariable('br3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpInteger) \ + # for (t1,t2,r,d) in games3 for nw in networkIds } + # start3 = {(t1,t2,d,nw) : pulp.LpVariable('br3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + # for (t1,t2,r,d) in games3 for nw in networkIds } + + # move3 = { (t11,t12,d1, t21,t22,d2,nw) : pulp.LpVariable('br3_'+str(t11)+'_'+str(t12)+'_'+str(d1)+'_'+str(t21)+'_'+str(t22)+'_'+str(d2)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + # for ( (t11,t12,d1) ,(t21,t22,d2)) in movements for nw in networkIds} + + # for (t21,t22,r2,d2) in games3: + # for nw in networkIds: + # model3+= br3[(t21,t22,d2,nw)]== start3[(t21,t22,d2,nw)] + lpSum([ move[(t11,t12,d1,t21,t22,d2,nw)] for (t11,t12,r1,d1) in pred3[(t21,t22,r2,d2)] ]) + # model3+= br3[(t21,t22,d2,nw)]>= lpSum([ move[(t11,t12,d1,t21,t22,d2,nw)] for (t11,t12,r1,d1) in pred3[(t21,t22,r2,d2)] ]) + + + # for ((t11,t12,r1,d1) , (t21,t22,r2,d2)) in movements : + # daysInBetween = (getDateTimeDay[d2]-getDateTimeDay[d1]).total_seconds() /(3600*24) + # distanceInBetween=distanceById[t11,t21] + # distancePerDay = distanceById[t11,t21]/((getDateTimeDay[d2]-getDateTimeDay[d1]).total_seconds() /(3600*24)) + # print ( distanceById[t11,t21] ,getDateTimeDay[d1],getDateTimeDay[d2], (getDateTimeDay[d2]-getDateTimeDay[d1]).total_seconds() /(3600*24) , distancePerDay ) + # print (len(movements), "movements") + + # exit(0) + + if optCameraMovement == "Stadiums" : + + games3_early = [ (t1,t2,r,d) for (t1,t2,r,d) in games3 if getVal(x_time[(t1,t2,(r,d),getIdByTime["Early"])]) >=0.9] + games3_late = [ (t1,t2,r,d) for (t1,t2,r,d) in games3 if getVal(x_time[(t1,t2,(r,d),getIdByTime["Late"])]) >=0.9] + + networkIdByName = { networkName[nw] : nw for nw in networkName.keys() } + + br3 = {(t1,t2,d,nw) : pulp.LpVariable('br3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpInteger) \ + for (t1,t2,r,d) in games3 for nw in networkIds } + unserved = {(t1,t2,r,d) : pulp.LpVariable('unserved_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + for (t1,t2,r,d) in games3 } + + gm3ByDay= {d:[] for d in days} + gm3ByTeam= {t:[] for t in realteams} + for gm in games3: + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for nw in networkIds])==1 - unserved[gm] + # model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for nw in networkIds])==1 + gm3ByDay[gm[3]].append(gm) + gm3ByTeam[gm[0]].append(gm) + gm3ByTeam[gm[1]].append(gm) + + aloneInPune = {(t,r) : pulp.LpVariable('aloneInPune_'+str(t)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \ + for t in realteams for (t1,t2,r,d) in gm3ByTeam[t]} + + playInStadium = {(t,r,nw) : lpSum([ br3[(gm[0],gm[1],gm[3],nw)] for gm in gm3ByTeam[t] if gm[2]==r ]) for (t,r) in aloneInPune.keys() for nw in networkIds } + + for nw in networkIds: + maxGamesInStadium = 3 if networkName[nw] in ["Brabourne", "MCA Stadium, Pune"] else 4 + print ("MAX GAMES PER TEAM IN ", networkName[nw] , " ", maxGamesInStadium) + + for d in days: + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for gm in gm3ByDay[d] ] ) <=1 + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for d2 in days for gm in gm3ByDay[d2] if getDateTimeDay[d]<=getDateTimeDay[d2] and getDateTimeDay[d2]<= getDateTimeDay[d]+datetime.timedelta(days=2) ] ) <=2 + # print (nw, getDateTimeDay[d] , lpSum([ br3[gm[0],gm[1],gm[3],nw] for d2 in days for gm in gm3ByDay[d2] if getDateTimeDay[d]<=getDateTimeDay[d2] and getDateTimeDay[d2]<= getDateTimeDay[d]+datetime.timedelta(days=2) ] )) + for t in realteams: + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for gm in gm3ByTeam[t] ] ) <= maxGamesInStadium + + model3+= lpSum([ br3[gm[0],gm[1],gm[3],nw] for gm in games3_early ] ) <= 4 + + fix3s= [("CSK", "KKR", "2022-03-26", "Wankhede"), + ("MI", "DC", "2022-03-27", "Brabourne"), + ("PBKS", "RCB", "2022-03-27", "DY Patil"), + ("GT", "LSG", "2022-03-28", "Wankhede"), + ("RR", "SRH", "2022-03-29", "MCA Stadium, Pune"), + ] + if "ipl_only_first_game_fixed" in special_wishes_active: + fix3s= [("CSK", "KKR", "2022-03-26", "Wankhede")] + + for (t1, t2, dt, std) in fix3s: + if t1 in getTeamIdByShortName.keys() and t2 in getTeamIdByShortName.keys() and parse(dt) in getDayByDateTime.keys() and std in networkIdByName.keys(): + print ("fixing in model3 " , (t1, t2, dt, std) ) + t11= getTeamIdByShortName[t1] + t12= getTeamIdByShortName[t2] + nwid = networkIdByName[std] + if (t11, t12, getRoundByDay[getDayByDateTime[parse(dt)]], getDayByDateTime[parse(dt)] ) in games3: + model3+= br3[t11, t12,getDayByDateTime[parse(dt)],nwid] ==1 + if (t12, t11, getRoundByDay[getDayByDateTime[parse(dt)]], getDayByDateTime[parse(dt)] ) in games3: + model3+= br3[t12, t11,getDayByDateTime[parse(dt)],nwid] ==1 + + +# - Nachmittagsspiele (Early) gleichmäßig in den drei Stadien außer Pune verteilt - Pune keine Early games + +# - Pune no Back to Back (keine Spiele an aufeinanderfolgenden Tagen in Pune)- optimalerweise als Special Wish zum An und Ausschalten + + puneStadiumId = networkIdByName["MCA Stadium, Pune"] + b2b_vio=0 + + if "limit_b2b" in special_wishes_active: + sw_type="limit_b2b" + for nw in networkIds: + for r in rounds: + if r0 and "specialTripsToPune" in special_wishes_active: + print (getTeamById[t], ": TWO GAMES IN ROUNDS " ,r, r+2) + model3+= playInStadium[t, r,puneStadiumId ] + sum([ playInStadium[t,r2,puneStadiumId] for r2 in punelaterGames ]) >= 1- 4*aloneInPune[t,r] + model3+= playInStadium[t, r,puneStadiumId ] - sum([ playInStadium[t,r2,puneStadiumId] for r2 in punelaterGames ]) <= aloneInPune[t,r] + model3+= -playInStadium[t, r,puneStadiumId ] + sum([ playInStadium[t,r2,puneStadiumId] for r2 in punelaterGames ]) <= aloneInPune[t,r] + + # Matches at the Pune venue need to be finished by 15th May. + model3+= lpSum([ br3[(t1,t2,d,networkIdByName["MCA Stadium, Pune"]) ] for (t1,t2,r,d) in games3 if r>51 ]) ==0 + + model3+=( - 100 *lpSum([ unserved[gm] for gm in games3]) - 1 *lpSum([ aloneInPune[tr] for tr in aloneInPune.keys()]) \ + - 20*b2b_vio + - 100* lpSum([ br3[gm[0],gm[1],gm[3],puneStadiumId] for gm in games3_early ]) + ) + + + if optCameraMovement == "Standard" : + + + # b_slots[(b.id,r)] = [ (d,str(sl.timeslot.id),sl.quality) for sl in b.slots.all() for d in getDays[r] if getWeekDay[d]==sl.weekday[:3]] + # b_slots = {b.id : [( sl.weekday , sl.timeslot , sl.quality) for sl in b.slots.all() ] for b in broadcastingwishes} + # print (b_slots) + + + getBWish = { bwish.id : bwish for bwish in broadcastingwishes } + + bWeight = {'A':5, 'B':4, 'C':3} + possBroadcasts=[] + bQuality = {} + + for (t1,t2,r,d) in games3: + # print ("\nGAME" , t1,t2,r,d) + for b in broadcastingwishes: + # print ("CHECKING ", b) + qual = bWeight[b.quality]* attractivity[t1,t2] + if t1 in networkFavTeams[b.network.id]: + qual*=5 + if t2 in networkFavTeams[b.network.id]: + qual*=4 + for (d1,s,q) in b_slots[(b.id,r)]: + if d1==d and (not thisSeason.useFeatureKickOffTime or getVal(x_time[(t1,t2,(r,d),s)])>0.9): + possBroadcasts.append((t1,t2,d,b.id)) + qual*=bWeight[b.quality]/3.0 + if b.network.name== "Super Sport": + print (" " ,b, getTimeById[s] ,q," does fit ",t1,t2,r, getNiceDay[d], qual, "= x* ",bWeight[b.quality], " * " ,attractivity[t1,t2], getTeamById[t1] , " \t " , getTeamById[t2] , networkFavTeams[b.network.id] , t1 in networkFavTeams[b.network.id] , t2 in networkFavTeams[b.network.id]) + bQuality[(t1,t2,d,b.id)] = qual + + possBroadCasting2 = list(set(fixedBroadCastingSlots.copy() + [ (t1,t2,d,getBWish[b].network.id) for (t1,t2,d,b) in possBroadcasts])) + + # print ("bQuality", bQuality) + + getPossBroadCasting = { fbcs:[] for fbcs in possBroadCasting2 } + for (t1,t2,d,b) in possBroadcasts: + getPossBroadCasting[(t1,t2,d,getBWish[b].network.id)].append((t1,t2,d,b)) + + br3_1 = {(t1,t2,d,b) : pulp.LpVariable('br3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(b), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for (t1,t2,d,b) in possBroadcasts } + br3 = {(t1,t2,d,nw) : pulp.LpVariable('broadcast3_'+str(t1)+'_'+str(t2)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for (t1,t2,d,nw) in possBroadCasting2 } + + seenTooOften3 = {(t1,bn) : pulp.LpVariable('seenTooOften3_'+str(t1)+'_'+str(bn), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for bn in varietyNetWorks} + # slotmissing = {(bn,d) : pulp.LpVariable('slotmissing_'+str(d)+'_'+str(bn), lowBound = 0, cat = pulp.LpContinuous) for d in days for bn in varietyNetWorks} + + for (t1,t2,d,nw) in possBroadCasting2: + if len(getPossBroadCasting[ (t1,t2,d,nw) ])>0: + model3+= br3[ (t1,t2,d,nw) ] == sum([ br3_1[b] for b in getPossBroadCasting[ (t1,t2,d,nw) ] ]) + + print ("Checking networks") + for bn in varietyNetWorks: + # print ("network " , bn) + for t in teams: + # print ("team " , t) + relevantGames = [ (t1,t2,d,nw) for (t1,t2,d,nw) in br3.keys() if t in [t1,t2] and b==nw ] + # print ('relevantGames : ' ,t ,bn , relevantGames) + model3+= sum([ br3[b] for b in relevantGames ]) <= 1+seenTooOften3[(t,bn)] + model3+= sum([ br3[b] for b in relevantGames ]) <= 2+0.33*seenTooOften3[(t,bn)] + model3+= sum([ br3[b] for b in relevantGames ]) <= 4+0.2*seenTooOften3[(t,bn)] + + for bwish in broadcastingwishes : + relevantGames = [ b for b in br3_1.keys() if b[3]==bwish.id ] + thisNetwork = bwish.network.id + for r in rounds: + model3 += sum( [br3_1[b1] for b1 in relevantGames if b1[2] in getDays[r] ]) <= bwish.minGames + if bwish.exclusive: + for (t1,t2,d,b2) in relevantGames: + model3 += br3_1[(t1,t2,d,b2) ] + 0.1*sum( [br3[(t1,t2,d,nw)] for nw in networkIds if (t1,t2,d,nw) in br3.keys() and nw!=thisNetwork] ) <= 1 + if bwish.doNotShareWithNetwork.count()>0: + for (t1,t2,d,b2) in relevantGames: + model3 += br3_1[(t1,t2,d,b2) ] + sum( [br3[(t1,t2,d,nw2.id)] for nw2 in bwish.doNotShareWithNetwork.all() if (t1,t2,d,nw2.id) in br3.keys() and nw2.id!=thisNetwork ] ) <= 1 + + + + model3+= ( + sum([(1000+ bQuality[(t1,t2,d,b)])*br3_1[(t1,t2,d,b)] for (t1,t2,d,b) in br3_1.keys()]) + # -10*sum([seenTooOften3[(t,bn)] for bn in varietyNetWorks for t in teams]) + -1000000*sum([1-br3[ttdb] for ttdb in fixedBroadCastingSlots]) + ) + + # model3.solve() + print ('###################') + print ('# SOLVING MODEL 3 #') + print ('###################') + + if RUN_ENV == 'celery' and task: + task.update_state(state='Solving Model 3', meta={'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + + if solver == "CBC": + model3.solve(PULP_CBC_CMD(fracGap = 0.1, maxSeconds = 50, threads = 8,msg=1)) + elif solver == "Gurobi": + model3.solve(GUROBI(MIPGap=0.1, TimeLimit=500)) + else: + try: + model3.solve(XPRESS(msg=1,maxSeconds = 500, targetGap=0.1,options=["THREADS=12"], keepFiles=True)) + except: + model3.status = pulp.LpStatusNotSolved + + if model3.status<0: + writeProgress("Model infeasible.....", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + return {'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)} + else: + return 'Model could not be solved' + + # tv_dist=0.0 + # for nw in networkIds: + # for (gm1,gm2) in movements: + # if getVal(move3[(gm1,gm2,nw)])>0.1: + # print ("MOVE3 ", distanceById[gm1[0],gm2[0]], " \t" , getVal(move3[(gm1,gm2,nw)]) , gm1, gm2) + # tv_dist+=distanceById[gm1[0],gm2[0]] + # print (tv_dist) + + lo = "" + if isinstance(last_objective,(int,float)): + lo= " (" + str(int(0.01+last_objective-1)) +")" + feedback = "Done!"+ lo+"
" + for (t1,t2,d) in fixedGames: + if fixedGameVio[(t1,t2,d)].value() >0.9: + feedback += '
Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + for (t1,t2,d) in fixedGames2: + if fixedGame2Vio[(t1,t2,d)].value() >0.9: + feedback += '
Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + for (t1,t2) in realgames: + if missingGamesVio[(t1,t2)].value() >0.9: + feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + '
' + writeProgress(feedback , thisScenario.id ,100) + + print ('Solved Broadcasting') + + + # print ("resiult") + # for (t1,t2,d,b) in br3.keys() : + # if br3[(t1,t2,d,b)].value()>0: + # print ( (100+ bWeight[getBWish[b]['quality']]*attractivity[t1,t2]), t1,t2,d,b, br3[(t1,t2,d,b)].value() ) + + # print ("seen") + # for bn in varietyNetWorks : + # for t in teams: + # if seenTooOften3[(t,bn)].value()>0: + # print (t, bn, seenTooOften3[(t,bn)].value()) + + + broadcastingResult = [ b for b in br3.keys() if br3[b].value()>0.9 ] + + def g_tms2(ttdn): + return getDateTimeDay[ttdn[2]] + broadcastingResult= sorted(broadcastingResult, key=g_tms2) + + # print ("broadcastingResult", broadcastingResult) + # print ("lens ",len(games3), len (broadcastingResult)) + networks_by_game ={(t1,t2,d) : [] for t1 in teams for t2 in teams for d in days} + + if optCameraMovement in ["TV-Kits", "Stadiums"]: + # networks_by_game ={(t1,t2,d) : [st] for (t1,t2,d,st) in broadcastingResult} + for (t1,t2,d,st) in broadcastingResult: + networks_by_game[(t1,t2,d)].append(st) + # print ("appending " , t1,t2,d,st,networks_by_game[(t1,t2,d)]) + broadcastingResult =[] + if optCameraMovement == "Standard" : + for (t1,t2,d,nw) in broadcastingResult: + networks_by_game[(t1,t2,d)].append(nw) + + # print ("networks_by_game", networks_by_game) + + return broadcastingResult,networks_by_game + + +# %% + +thisScenario = Scenario.objects.get(id = s2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + +mathModelName=thisLeague.name +if thisSeason.optimizationParameters!="": + mathModelName=thisSeason.optimizationParameters + +start_time = time.time() + +if RUN_ENV == 'celery': + if task: + task.update_state(state='Computation started', meta={'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + filename = thisScenario.log_file + + fileh = logging.FileHandler('data/'+filename, 'a') + formatter = logging.Formatter('%(message)s') + fileh.setFormatter(formatter) + log = logging.getLogger() + for hdlr in log.handlers[:]: + log.removeHandler(hdlr) + log.addHandler(fileh) + + fs = FileSystemStorage('data/') + f = fs.open(filename,'a') + orig_std_out = dup(1) + dup2(f.fileno(), 1) + +def print(*args, **kwargs): + if RUN_ENV == 'celery': + log.info(str(" ".join([str(x) for x in args]))) + else: + return __builtin__.print(*args, **kwargs) + +writeProgress("Computation started !!", thisScenario.id, 3) + +lengthScale = 1.0 if thisSeason.lengthUnit=="km" else 0.62 + + +def scaledDistanceString (dd): + return str(int(lengthScale*dd+0.5))+ " "+ thisSeason.lengthUnit + +otherScenGames=[] +if thisSeason.improvementObjective!="--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective=="stick to old solutions": + impObjWeight = -5 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append((int(g[1]),int(g[2]),int(g[3]), impObjWeight )) + +getGlobalCountry={ gc['uefa']: gc['id'] for gc in GlobalCountry.objects.values() } + +teamObjects = Team.objects.filter(season=thisSeason,active=True).order_by('position').values() +teams=[] +realteams=[] +faketeams=[] +importantteams=[] +shortteams=[] +t_pos={} +t_pot={} +t_color={} +t_country={} +t_globalCountry={} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity ={} +getTeamById={} +getTeamIdByName={} +getTeamIdByShortName={} +getTeamByName={} +getStadiumById={} +teamByShort={} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']]= t['name'] + getTeamIdByShortName[t['shortname']]=t['id'] + if t['name']!='-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']]= t['country'] + t_globalCountry[t['id']]= t['globalCountry_id'] + if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys() : + t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]] + t_pos[t['name']]= t['position'] + t_color[t['name']]="lightyellow" + t_pot[t['id']]= t['pot'] + t_stadium[t['id']]=t['stadium'] + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_shortname[t['id']]=t['shortname'] + t_usePhases[t['id']]= thisScenario.usePhases + getTeamById[t['id']]=t['name'] + getStadiumById[t['id']]=t['stadium'] + getTeamIdByName[t['name']]=t['id'] + getTeamByName[t['name']]=t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient : + top4.append(t['id']) + +inactive_teams=[] +for t in Team.objects.filter(season=thisSeason,active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_country[t['id']]= t['country'] + getTeamById[t['id']]=t['name'] + + +lowerBoundFound = False +distance ={} +attractivity ={} +distanceById={} +distanceInDaysById={} +stadium_names = set([t['stadium'] for t in teamObjects if t['stadium']!='']) +stadium_id={} +stadium_name={} +sid=0 +for stadium in stadium_names : + stadium_name[sid]=stadium + stadium_id[stadium]=sid + sid+=1 +stadiums = list(stadium_name.keys()) +teamsOfStadium ={ st:[] for st in stadiums } + +travelDict= thisSeason.travelDict() +for t1 in teamObjects: + if t1['stadium']!='': + teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'],t2['name']] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'],t2['id']] = distance[t1['name'],t2['name']] + distanceInDaysById[t1['id'],t2['id']] = int(distance[t1['name'],t2['name']]/350 +0.99) + attractivity[t1['id'],t2['id']] = int(100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [ d['id'] for d in dayObjects if d['round']>0 ] + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +higherTeamObjects = Team.objects.filter(season__in=higherSeasons,active=True).values() +higherDayObjects = Day.objects.filter(season__in=higherSeasons,maxGames__gte=1).values() +higherTeams = [ t['id'] for t in higherTeamObjects] +higherTeamsOf={ t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']]=t['name'] + t_country[t['id']]=t['country'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print ("Teams : " , teams) +print ("VIP Teams : " , importantteams) +print ("TOP Teams : " , top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums= ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t : 0 for t in teams } +conf_teams={} +for c in Conference.objects.filter(scenario=s2,regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name]=[] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id]==0 or len(cteams)< len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id]=c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +prioVal ={'A': 25 , 'B': 5 , 'C': 1, 'Hard' : 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name]=prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name]=sw.float1 + sw_float2[sw.name]=sw.float2 + sw_int1[sw.name]=sw.int1 + sw_int2[sw.name]=sw.int2 + sw_text[sw.name]=sw.text +special_wishes=list(sw_prio.keys()) +special_wishes_active = [ sw for sw in special_wishes if sw_prio[sw]>0 ] + +print (special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active : + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print (noBreakLimitTeams) + noBreakLimitTeams = [t for t in teams if getTeamById[t] in noBreakLimitTeams] + print (noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter(scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values() +pairings_tmp3 = Pairing.objects.filter(scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values() +pairings = [] +for p in [ p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams ] + [ p for p in pairings_tmp2 ] + [ p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) +breaks = Break.objects.filter(season=thisSeason).values() +blockings_tmp = Blocking.objects.filter(scenario=s2,active=True).values() +blockings = [ bl for bl in blockings_tmp if bl['team_id'] in teams ] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [ str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']):t['name'] for t in timeObjects} +getIdByTime = {t['name']:str(t['id']) for t in timeObjects} + +blocked_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +hidden_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +nBlockingHome=0 +nBlockingAway=0 + +for bl in blockings: + if bl['type'] in ["Hide"]: + hidden_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + + if bl['type'] in ["Home","Hide"]: + nBlockingHome+=1 + blocked_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + else: + nBlockingAway+=1 + +nTeams=len(teams) +nPhases =thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} +undirectedGameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} + +if thisSeason.useFeatureOpponentMatrix: + gameRequirements = GameRequirement.objects.filter(season=thisSeason, team1__active=True, team2__active=True) + # print (len(gameRequirements)) + for gm in gameRequirements: + if thisSeason.undirectedGames: + undirectedGameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + undirectedGameCntr[(gm.team2.id,gm.team1.id)]+=gm.number + # print ( "found undirected game " , (gm.team1.id,gm.team2.id) , gm.number ) + else: + gameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + # print ( "found directed game " , (gm.team1.id,gm.team2.id) , gm.number ) +else: + for t1 in teams: + for t2 in teams: + if t1!=t2: + gameCntr[(t1,t2)]+=nPhases/2 + + for c in Conference.objects.filter(scenario=s2): + cteams = c.teams.filter(active=True) + for t1 in cteams: + for t2 in cteams: + if t1!=t2: + gameCntr[(t1.id,t2.id)]+=c.deltaGames/2 + + for (t1,t2) in gameCntr.keys(): + if gameCntr[(t1,t2)]%1!=0 : + undirectedGameCntr[(t1,t2)]=1 + gameCntr[(t1,t2)] = int(gameCntr[(t1,t2)]) + +games = [ gm for gm in gameCntr.keys() if gameCntr[gm]+undirectedGameCntr[gm]>0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values() + +gew={} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']]= ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips']>gew['Breaks'] : + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl= mathModelName in ["Florida State League"] + +if len(games)==0: + games = [ (t1,t2) for t1 in teams for t2 in teams if t1!=t2 ] + specialGameControl=True + +realgames= [(t1,t2) for (t1,t2) in games if getTeamById[t1]!="-" and getTeamById[t2]!="-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = ( runMode == "Improve" and localsearch_time==0 + and len(thisScenario.solutionlist())==sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames]) ) + +opponents = { t: set([]) for t in realteams} + +for (t1,t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) + +model7 = pulp.LpProblem("FindGameClusters_"+str(thisScenario.id), pulp.LpMinimize) +gmClusters = range(1,nTeams+2) + +if nPhases>0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl: + gmClusters = range(1,2) + +print ("gmClusters", gmClusters) +x7 = {(t,c) : pulp.LpVariable('x7_'+str(t)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for c in gmClusters} + +for t in realteams: + model7+= lpSum([ x7[(t,c)] for c in gmClusters]) ==1 + +for (t1,t2) in gameCntr.keys(): + if gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)]==0 and t1!=t2: + for c in gmClusters: + model7+= x7[(t1,c)]+ x7[(t2,c)] <=1 + # hätte zu infeas leicht geführt bei NBA, NFL.. + # else: + # model7+= x7[(t1,c)]== x7[(t2,c)] + +model7+= lpSum([ c*x7[(t,c)] for (t,c) in x7.keys()]) + +if solver == "CBC": + model7.solve(PULP_CBC_CMD(threads = 8,msg=1)) +elif solver == "Gurobi": + model7.solve(GUROBI(MIPGap=0.01,msg=1)) +else: + model7.solve(XPRESS(msg=1, targetGap=0.01, maxSeconds = 100, keepFiles=True)) + +gameClusterTeams = { c : [ t for t in teams if x7[(t,c)].value()>0.01] for c in gmClusters } +gameClusters=[ c for c in gameClusterTeams.keys() if len(gameClusterTeams[c])>0] +biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters]) + +print ("biggestGroupSize", biggestGroupSize) + +nPhases = min( [ gameCntr[(t1,t2)]+ gameCntr[(t2,t1)] + undirectedGameCntr[(t1,t2)] for (t1,t2) in realgames ]) +tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize=="None" else int(thisSeason.tripStartHeuristicGroupsize) +defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2 +nPhases=max(1,int(nPhases/defaultGameRepetions)) +phases = range(nPhases) + +useBasicGames = nPhases>0 + +if not useBasicGames: + print ('no basic games but biggest group size ', biggestGroupSize, ' in nPhases ' , nPhases ) + +nRounds = thisSeason.nRounds +rounds=range(1,nRounds+1) + +# nRoundsPerPhase= 1 +# if nPhases>0: +nRoundsPerPhase= int(nRounds/nPhases) + +print ('nRounds ',nRounds) +print ('nPhases ',nPhases) +print ('nRoundsPerPhase ',nRoundsPerPhase) +print ('defaultGameRepetions ',defaultGameRepetions) +print ('tripStartHeuristicGroupsize ',tripStartHeuristicGroupsize) + +script =thisSeason.optimizationScript +if script == '' : + if useBasicGames: + script += "HEURISTIC\n" + script += "GAME;1-"+str(nRounds)+";0.1;200\n" + # if nRounds <=10: + # script += "GAME;1;"+str(nRounds)+"\n" + # else: + if thisSeason.groupBased: + for c in allConferences: + if not c.regional and c.teams.count() <= 12: + # print ("### ", c, c.teams.count()) + # print (c, sum([1 for c in c.teams.all()])) + script += "GROUP;1-"+str(nRounds)+";0.05;30;"+c.name+"\n" + for cr in range(nPhases): + minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # script += "GROUP;"+ str(minIntRound) + ";"+str(maxIntRound)+";"+str(c.id)+"\n" + + # for cr in range(nPhases): + # minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + # maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # if len(newRounds)>1: + # script += "GAME;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + # else: + # for cr in range(nPhases): + # minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + # maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # newRounds= range(minIntRound,maxIntRound+1) + # # if nRounds1 > 9 : + # # script += "HOMEAWAY;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + # # if not useMinRounds: + # # script += "BASICGAME;"+ str(cr*(nTeams-1)+1) + ";"+str((cr+1)*(nTeams-1))+"\n" + # if len(newRounds)>1 and False: + # script += "GAME;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + +script=script.replace('\r', '') +print ("script") +print (script) +optSteps = [ st.split(';') for st in script.split("\n")] +print (optSteps) + +script =thisSeason.improvementScript +if script == '' : + if thisSeason.groupBased: + if nRounds >16: + script += "ROUNDS\n" + script += "GROUPS\n" + else: + # if solver != "CBC": + # script += "TEAMSROUNDS\n" + # script += "PAIRS\n" + script += "TEAMS\n" + # script += "ROUNDS\n" + # if solver != "CBC": + # script += "TEAMSROUNDS\n" + +impScript=[] +script=script.replace('\r', '') +print (script) +if localsearch_time>0: + for stall in script.split("\n") : + st_tok = stall.split(";") + print (st_tok) + st=st_tok[0] + if (st=="GROUPS"): + print ("ADDING GROUPS") + onlyUseReoptGroups = False + for c in allConferences: + if c.reopt: + onlyUseReoptGroups = True + + group_time=30 + if len(st_tok)>1: + group_time = int(st_tok[1]) + + for c in allConferences: + tms = [t.id for t in c.teams.filter(active=True)] + print ("C " , tms) + if (len(tms) <= 8 or c.reopt) and (not onlyUseReoptGroups or c.reopt): + print ("C " , tms) + for tt in tms: + print ( c.id, " TEAM " , getTeamById[tt]) + # impScript.append((st,1,nRounds, tms, max(30,1.2*nRounds))) + if nRounds <= 50 or len(tms)<=3: + # impScript.append((st,1,int(0.75*nRounds),tms, max(group_time,30), "")) + impScript.append((st,1,nRounds,tms, max(group_time,30), "")) + else: + impScript.append((st,1,int(0.5*nRounds),tms, max(group_time,30), "")) + impScript.append((st,int(0.25*nRounds),int(0.75*nRounds),tms,max(group_time, 30), "")) + impScript.append((st,int(0.5*nRounds),nRounds, tms, max(group_time,30), "")) + # impScript.append((st,1,int(0.75*nRounds),tms, max(group_time,300), "")) + # impScript.append((st,int(0.25*nRounds),nRounds, tms, max(group_time,300), "")) + # else: + # if len(tms) <= 8: + # impScript.append((st,1,int(0.5*nRounds),tms, 30)) + # impScript.append((st,int(0.5*nRounds),nRounds, tms, 30)) + # else: + # impScript.append((st,1,int(0.25*nRounds),tms, 30)) + # impScript.append((st,int(0.25*nRounds),int(0.5*nRounds),tms, 30)) + # impScript.append((st,int(0.5*nRounds),int(0.75*nRounds),tms, 30)) + # impScript.append((st,int(0.75*nRounds),nRounds, tms, 30)) + + + + if (st=="TEAMS"): + print ("ADDING TEAMS") + for t in teams: + nts=[t] + if solver != "CBC" and gew['Trips']>=0 and nTeams<=32: + na = 2 if nTeams*nRounds >200 else 3 + na= random.randint(1,na) + if len(st_tok)>1: + na=int(st_tok[1])-1 + for i in range(na): + # for i in range(4): + nts.append(teams[random.randint(0,nTeams-1)]) + # print ("adding ", i , nts) + nts=list(set(nts)) + ctm = len(nts)*15 + if len(st_tok)>2: + ctm=int(st_tok[2]) + impScript.append((st,1,nRounds,nts, ctm, "")) + + if (st=="ROUNDS"): + na=5 + if len(st_tok)>1: + na=int(st_tok[1])-1 + for r in rounds: + if r%5==1: + nrs = random.randint(na,max(na,int(nRounds/6))) + #dont make time window too big + nrs = na + impScript.append((st,r,min(nRounds,r+nrs ),teams, 30, "")) + + if (st=="ENCOUNTERS"): + na=10 + if len(st_tok)>1: + na=int(st_tok[1])-1 + for r in rounds: + if r%5==1 and r+na <=nRounds : + impScript.append((st,r,r+na,teams, 30 , "STICKY_HOME") ) + + if (st=="TEAMSROUNDS"): + for t in teams: + nts=[t] + for i in range(8 +2*(t%2)): + nts.append(teams[random.randint(0,nTeams-1)]) + nts=list(set(nts)) + st = random.randint( 1, nRounds-10 ) + impScript.append((st,st,min(st+12,nRounds),nts, 20, "")) + # st = random.randint( 1, nRounds-18 ) + # impScript.append((st,st,min(st+18,nRounds),nts, 120, "")) + + if (st=="PAIRS"): + for p in pairings: + impScript.append((st,1,nRounds, [p['team1_id'],p['team2_id']], 20, "")) + + if (st=="SMART_TEAMS"): + impTeams = int(st_tok[1]) if len(st_tok) > 1 else 3 + impTime = int(st_tok[2]) if len(st_tok) > 2 else 30 + impScript.append((st,1,nRounds,impTeams,impTime,"")) + if (st=="SMART_ROUNDS"): + impRounds = int(st_tok[1]) if len(st_tok) > 1 else 5 + impTime = int(st_tok[2]) if len(st_tok) > 2 else 30 + impScript.append((st,1,impRounds,nTeams,impTime,"")) + + +random.shuffle(impScript) +if runMode=='Improve': + impScript= [("INIT",1,nRounds, [], 2000, "")] + impScript + +getPhaseOfRound = { r : min( nPhases-1 , int((r-1)/nRoundsPerPhase)) for r in rounds } + +if thisSeason.lastRoundOfPhaseOne>0: + getPhaseOfRound = { r : 0 if r<=thisSeason.lastRoundOfPhaseOne else 1 for r in rounds } + +getDaysOfPhase = { p : [] for p in phases } +getDays = { r : [] for r in rounds} +roundGamesMax = { r : 0 for r in rounds} +roundGamesMin = { r : 0 for r in rounds} +getDayById = { d['id'] : d for d in dayObjects} +getDayByDateTime = { } +getNiceDayRaw = { d['id'] : d['day'] for d in dayObjects} +getNiceDay = { d['id'] : d['day'] for d in dayObjects} +getWeekDay = { d['id'] : '' for d in dayObjects} +getDateTimeDay = { d['id'] : '' for d in dayObjects} +getRoundByDay = { d['id'] : d['round'] for d in dayObjects if d['round']>0 } +getDayMinGames = { d['id'] : d['minGames'] for d in dayObjects if d['round']>0 } +getDayMaxGames = { d['id'] : d['maxGames'] for d in dayObjects if d['round']>0 } +getRoundsByDay = { d['id'] : [] for d in dayObjects } +nDerbies = { d['id'] : [d['nDerbies']] for d in dayObjects} +roundDays =[] +roundDaysMin ={ } +roundDaysMax ={ } +wds= {0:'Mon', 1:'Tue', 2:'Wed', 3:'Thu', 4:'Fri', 5:'Sat', 6:'Sun'} +for d in dayObjects: + if d['round']>0 : + getRoundsByDay[d['id']].append(d['round']) + getDays[d['round']].append(d['id']) + roundDays.append((d['round'],d['id'])) + roundDaysMin[(d['round'],d['id'])]=d['minGames'] + roundDaysMax[(d['round'],d['id'])]=d['maxGames'] + roundGamesMax[d['round']]=min(nTeams/2, roundGamesMax[d['round']]+d['maxGames'] ) + roundGamesMin[d['round']]+=d['minGames'] + ph = getPhaseOfRound[d['round']] + getDaysOfPhase[ph].append(d['id']) + if d['round2']>0: + getRoundsByDay[d['id']].append(d['round2']) + getDays[d['round2']].append(d['id']) + roundDays.append((d['round2'],d['id'])) + roundDaysMin[(d['round2'],d['id'])]=d['minGames2'] + roundDaysMax[(d['round2'],d['id'])]=d['maxGames2'] + roundGamesMax[d['round2']]=min(nTeams/2, roundGamesMax[d['round2']]+d['maxGames2'] ) + roundGamesMin[d['round2']]+=d['minGames2'] + + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getDayByDateTime[dt] = d['id'] + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +countries = list(set( [t_country[t] for t in teams ])) + +getWeekDaysPerRound = { r : [ getWeekDay[d] for d in getDays[r]] for r in rounds} + +wd = {"Mondays":0 , "Tuesdays":1 , "Wednesdays":2 , "Thursdays":3 , "Fridays":4 , "Saturdays":5 , "Sundays":6} +t_site_bestTimeSlots = { (t,d): [] for t in realteams for d in days } +prio_weight = { "A" : 0 , "B" : 50 , "C" : 100} + + +stadiumTimeSlotPreferences = StadiumTimeSlotPreference.objects.filter(team__id__in=teams ) +theseStadiums= set([ stsp.stadiumTimeSlot.stadium for stsp in stadiumTimeSlotPreferences ]) + +teamsInStadium ={st.id: set([]) for st in theseStadiums } +getSharedStadiumName ={st.id: st.name for st in theseStadiums } +for stsp in stadiumTimeSlotPreferences: + teamsInStadium[stsp.stadiumTimeSlot.stadium.id].add(stsp.team.id) +lonelyTeams = [] +for st in teamsInStadium.keys(): + if len(teamsInStadium[st])==1: + lonelyTeams+=teamsInStadium[st] + +incompatible_timslots = {} +getStadiumTimeSlot= {} +for st in theseStadiums: + theseTimeSlots = st.stadiumtimeslots.all().values() + for ts1 in theseTimeSlots: + getStadiumTimeSlot[ts1['id']]=ts1 + incompatible_timslots[ts1['id']]=set([]) + if ts1["end"]<=ts1["start"]: + ts1["end"]=datetime.time(ts1["start"].hour + 2, ts1["start"].minute) + for ts1 in theseTimeSlots: + for ts2 in theseTimeSlots: + if ts1["weekday"]==ts2["weekday"] and ts1["start"]<=ts2["start"] and ts2["start"]1: + # print (t_site_bestTimeSlots[(t,d)]) + t_site_bestTimeSlots[(t,d)]=sorted(t_site_bestTimeSlots[(t,d)]) + t_site_bestTimeSlots[(t,d)]=t_site_bestTimeSlots[(t,d)][:1] + # print (" -> " , t_site_bestTimeSlots[(t,d)]) + +# for (t,d) in t_site_bestTimeSlots.keys(): +# if len(t_site_bestTimeSlots[(t,d)])>0: +# print(getTeamById[t], getNiceDay[d], t_site_bestTimeSlots[(t,d)]) + +toTime=False +higherLeagueDayIds= [] +upperAndLowerLeagueIds = [] +higherLeagueGetRoundByDay= {d['id']:d['round'] for d in higherDayObjects} + +for d in higherDayObjects: + getRoundByDay[d['id']] = 0 + +for d in higherDayObjects: + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + if dt not in getDayByDateTime.keys(): + getDayByDateTime[dt] = d['id'] + higherLeagueDayIds.append(d['id']) + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + # getRoundByDay[d['id']] = d['round'] + else: + upperAndLowerLeagueIds.append(getDayByDateTime[dt]) + thisLeagueDay=getDayByDateTime[dt] + print ("found day in both ", getNiceDay[thisLeagueDay] , " putting in round " , getRoundByDay[thisLeagueDay] ) + for d2 in higherDayObjects : + if higherLeagueGetRoundByDay[d2['id']] == higherLeagueGetRoundByDay[d['id']]: + getRoundByDay[d2['id']] = getRoundByDay[thisLeagueDay] + print ("assigning day ", d2['id'] , d2['day'] , " to round " , getRoundByDay[thisLeagueDay] ) + +getHigherDaysByRound= { r: [d for d in higherLeagueDayIds if getRoundByDay[d]==r ] for r in rounds } +getHigherDaysByRound[0]= [] +# print (getHigherDaysByRound) + + +earliestDay={r: getDays[r][0] for r in rounds } +latestDay={r: getDays[r][0] for r in rounds } +for r in rounds : + for d in getDays[r]: + dt=getDateTimeDay[d] + if dt>getDateTimeDay[latestDay[r]]: + latestDay[r]=d + if dt0: + # nm=max(c for c in clusters )+1 + # clusters.append(nm) + # cluster_teams[nm]=[t] + # t_cluster[t]=nm + +# print ("clusters: ",clusters) +# print ("t_clusters: ",t_clusters) +# print ("cluster_teams: ",cluster_teams) + + +cluster_distances = {} + +c_weight = { c: 1 for c in clusters } + +for c1 in clusters: + for c2 in clusters: + dist=0 + for t1 in cluster_teams[c1]: + for t2 in cluster_teams[c2]: + dist+= distanceById[t1,t2] + cluster_distances[c1,c2]= int(dist /(max(1,len(cluster_teams[c1]))*max(1,len(cluster_teams[c2])))) + + if cluster_distances[c1,c1]<100: + c_weight[c1]= 1.3 + + print ("CLUSTER ", c1) + for t in cluster_teams[c1]: + print ("--",getTeamById[t]) + print ("---> Av Distance: ", c1 ,cluster_distances[c1,c1]) + +c_lat = { c: sum([t_lat[t] for t in cluster_teams[c] ])/max(1,len(cluster_teams[c])) for c in clusters } +c_lon = { c: sum([t_lon[t] for t in cluster_teams[c] ])/max(1,len(cluster_teams[c])) for c in clusters } +tripToClusterSaving = { (t,c): 2.0*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c])-cluster_distances[c,c] for t in teams for c in clusters } + +for c in regionalConferences: + print (c) + # print (c.teamlist()) + cteams = c.teams.filter(active=True) + for t in cteams: + print (' - ' , t) + regionalteams.add(t.id) + +regionalGroupSizes = [ len( c.teams.filter(active=True)) for c in regionalConferences ] +if len(regionalGroupSizes)>0: + maxRegionalGroupSize = max([ len( c.teams.filter(active=True)) for c in regionalConferences ] ) +else: + maxRegionalGroupSize =0 +print ("maxRegionalGroupSize " , maxRegionalGroupSize) +print ("maxTourLength " , thisSeason.maxTourLength) + +regionalParent={t : 0 for t in teams} +regionalKids={t : [] for t in teams} + +regionalPatternUse=tripStartHeuristicGroupsize>1 + +print("regionalPatternUse", regionalPatternUse) + +f_team={ t:t for t in teams } +if regionalPatternUse : + teamPairs = [ (t1,t2) for (t1,t2) in games if t10.9] + + + # for (t1,t2) in reg_edges: + # print ([getTeamById[t1],getTeamById[t2]] , distanceInKmByGPS(t_lon[t1],t_lat[t1],t_lon[t2],t_lat[t2]) , "\t " , not "-" in [getTeamById[t1],getTeamById[t2]]) + # for (t1,t2) in chosenEdges: + # print (" chosen " , getTeamById[t1],getTeamById[t2] , distanceInKmByGPS(t_lon[t1],t_lat[t1],t_lon[t2],t_lat[t2]) , "\t " , not "-" in [getTeamById[t1],getTeamById[t2]] ) + + + print (chosenEdges) + print (comp.value()) + + + model_reg = pulp.LpProblem("Clustering_--_Regional_"+str(thisScenario.id), pulp.LpMinimize) + reg_x = {(t,c) : pulp.LpVariable('reg_x_'+str(t)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in teams for c in reg_clusters} + for c in reg_clusters: + model_reg+= sum([ reg_x[(t,c)] for t in teams ])<=reg_clustersize + model_reg+= sum([ reg_x[(t,c)] for t in teams ])>=2 + (t1,t2) = chosenEdges[c] + compatibility = sum([1 for d in days if not blocked_arena[(t1,d, "----" )] and not blocked_arena[(t2,d, "----" )] ]) + print ("\nINIT CLUSTER ", getTeamById[t1], ", ", getTeamById[t2] , " ", compatibility ) + for t in [t1,t2]: + sst= " -- " + for d in days: + if blocked_arena[(t,d, "----" )]: + sst+= "#" + else: + sst+= " " + sst+= " " + getTeamById[t] + print (sst) + model_reg+= reg_x[(t1,c)] ==1 + model_reg+= reg_x[(t2,c)] ==1 + c_center_lat[c]= 0.5*(t_lat[t1] + t_lat[t2]) + c_center_lon[c]= 0.5*(t_lon[t1] + t_lon[t2]) + + + for t in teams : + model_reg+= sum([ reg_x[(t,c)] for c in reg_clusters])==1 + + for i in range(5): + model_reg+= sum([ distanceInKmByGPS(t_lon[t],t_lat[t],c_center_lon[c],c_center_lat[c]) * reg_x[(t,c)] for t in teams for c in reg_clusters]) + + if solver == "CBC": + model_reg.solve(PULP_CBC_CMD()) + elif solver == "Gurobi": + model_reg.solve(GUROBI(MIPGap=0.0, TimeLimit=120, msg=1)) + else: + model_reg.solve(XPRESS(msg=1,maxSeconds=120, keepFiles=True)) + + for c in reg_clusters: + # print ("CLUSTER ", c) + # for t in teams: + # if reg_x[(t,c)].value()>0.01: + # print ( getTeamById[t], reg_x[(t,c)].value() , t_lat[t] , t_lon[t] ) + c_center_lat[c]= 0.5*c_center_lat[c] + 0.5*sum ([ t_lat[t] *reg_x[(t,c)].value() for t in teams]) / sum ([reg_x[(t,c)].value() for t in teams]) + c_center_lon[c]= 0.5*c_center_lon[c] + 0.5*sum ([ t_lon[t] *reg_x[(t,c)].value() for t in teams]) / sum ([reg_x[(t,c)].value() for t in teams]) + # print ("new center ",c_center_lat[c], c_center_lon[c] ) + # print () + + reg_cluster_teams = { c : [ t for t in teams if reg_x[(t,c)].value()>0.9 ] for c in reg_clusters } + print (reg_clusters) + + for c in reg_clusters: + print ("CLUSTER ", c) + for t in reg_cluster_teams[c]: + print ( getTeamById[t], reg_x[(t,c)].value() , t_lat[t] , t_lon[t] ) + father = reg_cluster_teams[c][0] + basicTeams.append(father) + for t in reg_cluster_teams[c][1:]: + if t in realteams: + regionalParent[t]=father + regionalKids[father].append(t) + f_team[t]=father + + print ("ergebnis") + print (reg_cluster_teams) + print (regionalParent) + print (regionalKids) + print () + + # print ("ALL TEAMS REGIONAL ") + # for c in regionalConferences: + # father =0 + # for t in c.teams.filter(active=True): + # if father ==0: + # father =t.id + # basicTeams.append(t.id) + # else: + # regionalParent[t.id]=father + # regionalKids[father].append(t.id) + # f_team[t.id]=father +else : + basicTeams = teams + +basicGames = [ (t1,t2) for (t1,t2) in games if t1 in basicTeams and t2 in basicTeams ] + +# print("basicGames",basicGames) +# print (f_team) +# print ("basicTeams", basicTeams) +# print ('parents :') +# print (regionalParent) +# print (regionalKids) + +# for t in basicTeams: +# if len(regionalKids[t])>0: +# print (getTeamById[t]) +# for t2 in regionalKids[t]: +# print (" " ,getTeamById[ t2] , t_conference [t]==t_conference[t2]) +# miniGameSequenceLength = 1 + +nBasicTeams = len (basicTeams) +nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9) +if nBasicTeamsPerCluster%2==1: + nBasicTeamsPerCluster+=1 + +nRounds1 = nBasicTeamsPerCluster-1 +nBasicRounds= nPhases * (nBasicTeamsPerCluster-1) + +if nBasicTeamsPerCluster==1 or nBasicRounds==1: + nBasicRounds=nRounds + nRounds1=nRounds + +basicRounds = range (1,nBasicRounds+1) + +print ("nPhases ", nPhases) +print ("nGameClusters ", len(gameClusters)) +print ("nBasicTeamsPerCluster ", nBasicTeamsPerCluster) +print ("nBasicTeams ", nBasicTeams) +print ("nBasicRounds ", nBasicRounds) +print ("nRounds1 ", nRounds1) + +stretch = nRounds/nBasicRounds +# print ("regionalPatternUse " , regionalPatternUse) +rounds1=range(1,nRounds1+1) +nGames=nTeams*nRounds1 + + +getBasicRound={ r : int ((r-1)/stretch)+1 for r in rounds} +getRealRounds = { br : [ r for r in rounds if getBasicRound[r]==br ] for br in basicRounds} +# print ("stretch : " , stretch) + +getBasicDays= {r : [] for r in basicRounds} +for r in rounds: + getBasicDays[getBasicRound[r]]+=(getDays[r]) + +competitions = {} +for d in Day.objects.filter(season=thisSeason): + for comp in d.internationalCompetitions.all(): + dtlb = getDateTimeDay[d.id] - datetime.timedelta(days=comp.nDaysNotPlayBefore) + dtub = getDateTimeDay[d.id] + datetime.timedelta(days=comp.nDaysNotPlayAfter) + for d2 in days: + # dt =parse(getDayById[d2]['day']) + dt =getDateTimeDay[d2] + if dtlb<=dt and dt<=dtub: + for t in comp.teams.filter(active=True): + # print ("adding ", (t.id,d2) , " for " , comp.name , dtlb , " <= ", dt , " <= ", dtub ) + competitions[(t.id,d2)]=comp.name + +getRoundDaysByDay = {d: [ rd for rd in roundDays if rd[1]==d ] for d in days} +getRoundDaysByRound = {r: [rd for rd in roundDays if rd[0]==r ] for r in rounds} + +daysSorted =[] +for dt in sorted([ getDateTimeDay[d] for d in days]): + daysSorted.append ( getDayByDateTime[dt] ) + +minRest = { (t,s1,s2) : thisSeason.minDaysBetweenGames for t in teams for s1 in ['A','H'] for s2 in ['A','H']} +for t in teamObjects: + minRest[(t['id'],'H','H')] = max(minRest[(t['id'],'H','H')], t['minRest_HH'] ) + minRest[(t['id'],'H','A')] = max(minRest[(t['id'],'H','A')], t['minRest_HA'] ) + minRest[(t['id'],'A','H')] = max(minRest[(t['id'],'A','H')], t['minRest_AH'] ) + minRest[(t['id'],'A','A')] = max(minRest[(t['id'],'A','A')], t['minRest_AA'] ) + +maxMinRest = { t : max( [ minRest[(t,s1,s2)] for s1 in ['A','H'] for s2 in ['A','H'] ] ) for t in teams } + +conflictDays = { (t,d) : [d] for d in days+higherLeagueDayIds for t in teams} +for t in teams: + for d1 in days+higherLeagueDayIds: + diffRounds =False + for d2 in days+higherLeagueDayIds: + # print (getRoundsByDay[d1],getRoundsByDay[d2],getRoundsByDay[d1]!=getRoundsByDay[d2]) + if getDateTimeDay[d2]>getDateTimeDay[d1] and getDateTimeDay[d2]-getDateTimeDay[d1] <= datetime.timedelta(days=maxMinRest[t]): + # print (getDateTimeDay[d1],getDateTimeDay[d2], getRoundByDay[d1], getRoundByDay[d2],getDateTimeDay[d2] > getDateTimeDay[d1] , getDateTimeDay[d2]-getDateTimeDay[d1] , datetime.timedelta(days=thisSeason.minDaysBetweenGames)) + conflictDays[(t,d1)].append(d2) + if d1 in higherLeagueDayIds+upperAndLowerLeagueIds or d2 in higherLeagueDayIds+upperAndLowerLeagueIds or getRoundsByDay[d1]!=getRoundsByDay[d2]: + diffRounds=True + + if not diffRounds: + conflictDays[(t,d1)]=[] + if len(higherTeamsOf[t])>0 and d1 in upperAndLowerLeagueIds: + conflictDays[(t,d1)]=[d1] + +# print (thisScenario.sol_solution) + +varietyNetWorks=[] +networkName ={} +networkFavTeams ={} +for bn in BroadcastingNetwork.objects.filter(scenario=s2): + networkName[bn.id] =bn.name + networkFavTeams[bn.id] = [ t.id for t in bn.teams.all() ] + if bn.variety: + varietyNetWorks.append(bn.id) + + +fixedGames2 = [] +if thisScenario.sol_fixed_games != "": + for l in [ g.split('_') for g in thisScenario.sol_fixed_games.split('__')]: + # print (l) + if int(l[0]) in getRoundsByDay.keys() and int(l[1]) in realteams and int(l[2]) in realteams : + fixedGames2.append([int(l[1]),int(l[2]),int(l[0]) ]) + +fixedDays=[] +if thisScenario.sol_fixed_days!= "": + fixedDays = [ int(g) for g in thisScenario.sol_fixed_days.split('_')] + + +fixedRounds=[] +if thisScenario.sol_fixed!='': + fixedRounds = [ int(g) for g in thisScenario.sol_fixed.split('_')] + +fixedGames=[] +currentSolution=[] +currentAwayLocation = { (t,d) : False for t in teams for d in days} +currentKickoffTimes = defaultdict(lambda:None) + +excessGames = { rd: -roundDaysMax[rd] for rd in roundDays } +deficientGames = { rd: roundDaysMin[rd] for rd in roundDays } + +fixedBroadCastingSlots = [] + +for l in thisScenario.solutionlist(): + # print ("adding " , l , len(l)>=4 , int(l[0]) in getRoundsByDay.keys() ) + if len(l)>=4 and int(l[0]) in getRoundsByDay.keys() and int(l[1]) in realteams and int(l[2]) in realteams : + # print (" ++++ ") + h_id= int(l[1]) + a_id= int(l[2]) + r_id= int(l[3]) + d_id= int(l[0]) + # if len(l)>6 and l[6]!="None" and [h_id,a_id,d_id] in fixedGames2: + if r_id not in getRoundsByDay[d_id] and len(getRoundsByDay[d_id])>0: + print ("WRONG ROUND", d_id , " not in round " , r_id, " putting it in round " , getRoundsByDay[d_id][0]) + r_id = getRoundsByDay[d_id][0] + if (r_id,d_id) in roundDays: + excessGames[(r_id,d_id)]+=1 + deficientGames[(r_id,d_id)]-=1 + nf = [h_id, a_id, d_id] + for tm in ["----"] + times : + if blocked_arena[(h_id,d_id,tm)] : + print ("Allowing ",tm , "usage of blocked arena of " , getTeamById[h_id] , " at " , getNiceDay[d_id] ) + blocked_arena[(h_id,d_id,tm)]=False + + if len(intersection(getRoundsByDay[d_id], fixedRounds))>0 : + fixedGames.append(nf) + if d_id in fixedDays : + if not nf in fixedGames2: + fixedGames2.append(nf) + if len(l)>=4: + currentSolution.append([ h_id, a_id, r_id,d_id]) + currentAwayLocation[(a_id,d_id)]=h_id + if len(l) >= 5: + if l[4].isnumeric() and l[4] in times: + currentKickoffTimes[(h_id,a_id,d_id)] = l[4] + elif l[4] in getIdByTime.keys(): + currentKickoffTimes[(h_id,a_id,d_id)] = getIdByTime[l[4]] + else: + currentKickoffTimes[(h_id,a_id,d_id)] = None + else: + if len(times)>0: + currentKickoffTimes[(h_id,a_id,d_id)] = times[0] + + if len(l)>6 and l[6]!="None" and (evalRun or d_id in fixedDays or [h_id,a_id,d_id] in fixedGames2): + for nw in l[6].split(";"): + if int(nw) in networkName.keys(): + fixedBroadCastingSlots.append((h_id,a_id,d_id,int(nw))) + +excessGames = { rd: max(0,excessGames[rd]) for rd in roundDays } +deficientGames = { rd: max(0,deficientGames[rd]) for rd in roundDays } + +print ("currentSolution:") +print (len(currentSolution)) +print ("fixedGames (nur runde ist wichtig):",len(fixedGames)) +print ("fixedGames2 (datum ist wichtig):",len(fixedGames2)) + +# print ("fixedGames",fixedGames) +# for ff in fixedGames: +# print (getNiceDay[ff[2]], "\t",getTeamById[ff[0]],"\t",getTeamById[ff[1]] ) +# print ("fixedGames2",fixedGames2, thisScenario.sol_fixed_games) +# for ff in fixedGames2: +# print (getNiceDay[ff[2]], "\t",getTeamById[ff[0]],"\t",getTeamById[ff[1]] ) + +tripElements={t:[] for t in teams} +trip_minDays={} +trip_maxDays={} +trip_prio={} +for te in TripElement.objects.filter(scenario=s2,active=True): + trip_minDays[te.id]=te.minDaysBetweenGames + trip_maxDays[te.id]=te.maxDaysBetweenGames + trip_prio[te.id]=te.prio + vt1= [ tt.id for tt in te.visited_teams_first.all()] + vt2= [ tt.id for tt in te.visited_teams_second.all()] + vt3= [ tt.id for tt in te.visited_teams_third.all()] + vt4= [ tt.id for tt in te.visited_teams_fourth.all()] + for tf in te.travelling_teams.all(): + tripElements[tf.id].append((te.id,vt1,vt2,vt3,vt4)) + +allowed_weekdays={'--' : [0,1,2,3,4,5,6], 'Mondays':[0], 'Tuesdays':[1], 'Wednesdays':[2], 'Thursdays':[3], 'Fridays':[4], 'Saturdays':[5],'Sundays':[6], 'Weekdays':[0,1,2,3,4], 'Weekends':[5,6], 'Mon.-Thu.':[0,1,2,3] , 'Fri.-Sun.':[4,5,6] } + +hawishes = HAWish.objects.filter(scenario=s2,active=True).order_by('reason').values() +hawTeams = {} +hawDays = {} +hawTimes = {} +hawRounds = {} +hawRoundsString = {} +for c in HAWish.objects.filter(scenario=s2): + # print () + # print (c.reason ) + hawDays[c.id] = [] + hawTeams[c.id] = [] + hawTimes[c.id]= [ str(dd.id) for dd in c.timeslots.all() ] + if c.selection_mode == 0: + for t in c.teams.filter(active=True): + hawTeams[c.id].append(t.id) + elif c.selection_mode == 1: + for t in c.groups.all(): + for t2 in t.teams.filter(active=True): + hawTeams[c.id].append(t2.id) + elif c.selection_mode == 2: + cids = [ cn.id for cn in c.countries.all()] + for t in teams: + if t_globalCountry[t] in cids: + hawTeams[c.id].append(t) + + if c.multidate: + hawDays[c.id]= [ dd.id for dd in c.dates.all() ] + # print ("multidate") + else: + if c.day and not c.day2: + hawDays[c.id].append(c.day.id) + # print('+ ',getDayById[e['day_id']]) + + if not c.day and c.day2: + hawDays[c.id].append(c.day2.id) + # print('+ ',getDayById[e['day2_id']]) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + if dt.weekday() in allowed_weekdays[c.weekdays]: + # print (hawDays[e['id']]) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + if c.day and c.day2: + day1= getDateTimeDay[c.day.id] + day2= getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + if day1<=dt and dt<=day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + +print ("processing encounters") +encwishes = EncWish.objects.filter(scenario=s2,active=True).values() +encTeams1 = {} +encTeams2 = {} +encGroups = {} +encGames = {} +encTeams1String = {} +encTeams2String = {} +encDays = { e['id'] : [] for e in encwishes} +encDaySets={} +encTimes = {} +encRounds = { e['id'] : [] for e in encwishes} +encRoundsString = { e['id'] : "" for e in encwishes} +for c in EncWish.objects.filter(scenario=s2,active=True): + encTimes[c.id]= [ str(dd.id) for dd in c.timeslots.all() ] + encTeams1[c.id] = [] + encTeams2[c.id] = [] + encGroups[c.id] = [] + encTeams1String[c.id] = '' + encTeams2String[c.id] = '' + for t in c.encounterGroups.all(): + encGroups[c.id].append(t) + if c.useGroups: + for gr in c.teams1_groups.all(): + for t in gr.teams.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id]+=gr.name +', ' + for gr in c.teams2_groups.all(): + for t in gr.teams.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id]+=t.name +', ' + else: + for t in c.teams1.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id]+=t.name +', ' + for t in c.teams2.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id]+=t.name +', ' + encTeams1String[c.id] = encTeams1String[c.id][:-2] + encTeams2String[c.id] = encTeams2String[c.id][:-2] + + if c.useEncounterGroups: + tmp_games = [(t1.id,t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1!=t2 ] + tmp_games += [(t2.id,t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1!=t2 and ec.symmetry] + encGames[c.id] = [tmp_games] + else: + elemHomeTeams= [encTeams1[c.id]] + if c.forEachTeam1: + elemHomeTeams= [ [t] for t in encTeams1[c.id]] + elemAwayTeams= [ encTeams2[c.id]] + if c.forEachTeam2: + elemAwayTeams= [ [t] for t in encTeams2[c.id]] + encGames[c.id]= [] + # print ("NEW ENC ", elemHomeTeams,elemAwayTeams) + for elh in elemHomeTeams: + for ela in elemAwayTeams: + # print (" --- ENC ", elh,ela) + tmp_games= [(t1,t2) for t1 in elh for t2 in ela if t1!=t2 ] + if c.symmetry: + tmp_games += [(t1,t2) for t1 in ela for t2 in elh if t1!=t2 ] + encGames[c.id].append( tmp_games) + + if c.multidate: + encDays[c.id]= [ dd.id for dd in c.dates.all() ] + else: + if c.day: + day1= getDateTimeDay[c.day.id] + + if c.day and not c.day2: + encDays[c.id].append(c.day.id) + + if not c.day and c.day2: + encDays[c.id].append(c.day2.id) + + if not c.day and not c.day2: + for d in days: + dt= getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + if c.day and c.day2: + # day1= parse(c.day.day) + # day2= parse(c.day2.day) + day1= getDateTimeDay[c.day.id] + day2= getDateTimeDay[c.day2.id] + for d in days: + dt= getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if day1<=dt and dt<=day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + encDaySets[c.id]=[] + elemDays= [ encDays[c.id]] + if c.forEachDay or c.forOneDay: + elemDays= [ [d] for d in encDays[c.id]] + + lastDaySet=[] + for d in elemDays : + tmpDays=d + if (c.forEachDay or c.forOneDay) and c.timeframe!=0: + tmpDays = [] + # day1= parse(getDayById[d[0]]['day']) + day1= getDateTimeDay[d[0]] + if c.timeframe>0: + day2= day1 + datetime.timedelta(days=c.timeframe-1) + # print (e) + # print (day1, day2) + for d3 in days: + dt= getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1<=dt and dt<=day2 : + tmpDays.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt= getDateTimeDay[d3] + if day1<=dt and getRoundByDay[d3]< r1 + (-c.timeframe) : + tmpDays.append(d3) + # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe']) + # for d4 in elemEncWishDays[cntr]: + # print (" - " ,getDayById[d4]['day']) + + if len([ d for d in tmpDays if d not in lastDaySet ])>0: + encDaySets[c.id].append(tmpDays) + lastDaySet = tmpDays + # print("-.--- NEW DAYS", tmpDays) + + for ds in encDaySets[c.id]: + for d3 in ds: + encRounds[c.id]+= getRoundsByDay[d3] + encRounds[c.id]=sorted(list(set(encRounds[c.id]))) + for r in encRounds[c.id]: + encRoundsString[c.id]+= str(r)+"_" + if encRoundsString[c.id]!="": + encRoundsString[c.id]=encRoundsString[c.id][:-1] + # print (encRoundsString[c.id] , " # " ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds) + +# print(encDays) +# print (encGames) +# print (encDaySets) +# print (encRounds) +# print (encRoundsString) +# return ("") + +seed_games= [] +for enc in encwishes : + if not enc['useEncounterGroups'] and not enc['useGroups'] and not enc['multidate'] and enc['reason']=="Seed Game" and len(encTeams1[enc['id']])==1 and len(encTeams2[enc['id']])==1 and enc['day_id']: + seed_games.append((encTeams1[enc['id']][0] ,encTeams2[enc['id']][0] ,enc['day_id'])) + # if blocked_arena[(encTeams1[enc['id']][0], enc['day_id'], "----" )]: + # print ("SEED GAME IN " , getTeamById[ encTeams1[enc['id']][0]] , " at day ", getNiceDay[enc['day_id']]) + +# must_home = {t : sorted([ r for (h,a,r) in seed_games if h==t]) for t in teams} +# must_away = {t : sorted([ r for (h,a,r) in seed_games if h==a]) for t in teams} +# print (seed_games) +# print ("must_home",must_home) +# print ("must_away",must_away) +# exit(0) + +if runMode=='Improve' and len(thisScenario.solutionlist())>0 : + oldGameCntr={gm :0 for gm in games } + for gm in currentSolution: + if not (gm[0],gm[1]) in oldGameCntr.keys(): + print ("ALERT ", (gm[0],gm[1]) ," not in ", oldGameCntr.keys() ) + else: + oldGameCntr[(gm[0],gm[1])]+=1 + + unsatisfied_seed_games= [ (h_id,a_id,d_id) for (h_id,a_id,d_id) in seed_games if currentAwayLocation[(a_id,d_id)]!=h_id ] + missing_gms = [ (t1,t2) for (t1,t2) in realgames if oldGameCntr[(t1,t2)]0: + release_blocking_games += realgames + + for (t1,t2) in release_blocking_games: + for d in days: + if not hidden_arena[(t1,d, "----" )] : + blocked_arena[(t1,d, "----" )]=False + # print ("FREIGABE ", getTeamById[t1], getNiceDay[ d]) + + + +onlyEarlyDays= [] +onlyLateDays= [] + +if mathModelName=="NBA" and nRounds >34: + onlyLateDays=[d for d in days if not getWeekDay[d] in ["Sat", "Sun"] and not getNiceDay[d] in ["Fri, Nov 29, 2019", "Wed, Dec 25, 2019" ,"Tue, Dec 31, 2019", "Mon, Jan 20, 2020", "Mon, Jan 24, 2020"] + ["Fri, Dec 25, 2020" ,"Thu, Dec 31, 2020", "Mon, Jan 18, 2021" ] ] + +# if thisSeason.useFeatureOpponentMatrix: +# for gm in gameRequirements: +# # print (gm) +# if gm.number> 0.5*nPhases: +# extragames[(gm.team1.id,gm.team2.id)]= gm.number - 0.5*nPhases + +alwaysConsiderAllGames = not mathModelName in ["Florida State League" , "UEFA NL"] + +# print (conferences) +conferencewishes = ConferenceWish.objects.filter(scenario=s2).values() + + +# print ("encGroups" ,encGroups) + + + +elemEncWishes ={e['id'] : [] for e in encwishes} +elemEncWishGames={} +elemEncWishDays={} + +cntr=0 +for e in encwishes: + for eg in encGames[e['id']] : + for ed in encDaySets[e['id']]: + if len(eg)>0 and len(ed)>0: + cntr+=1 + elemEncWishes[e['id']].append(cntr) + elemEncWishGames[cntr] = eg + elemEncWishDays[cntr] = ed + + +# print (elemEncWishGames) +# print (elemEncWishDays) + + +encRelRoundsMin = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]} +encRelRoundsMax = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]} + +for enc in encwishes: + # print (e) + # print ("ENC !! " , enc['reason'] ) + for el in elemEncWishes[enc['id']]: + relDaysSet = set(elemEncWishDays[el]) + for r in basicRounds : + if len(relDaysSet.intersection(set(getBasicDays[r])))>0: + frac = len(relDaysSet.intersection(set(getBasicDays[r]))) / len (getBasicDays[r]) + # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac) + if frac>0: + encRelRoundsMin[el].append(r) + if frac==1: + encRelRoundsMax[el].append(r) + +nElemEncWishes = sum ([len(elemEncWishes[enc['id']]) for enc in encwishes]) + +# print (encRelRoundsMin) + +elemHaWishes ={e['id'] : [] for e in hawishes} +elemHaWishTeams={} +elemHaWishDays ={} +elemHaWishFirstDay ={} + +cntr =1 +for e in hawishes: + elemTeams= [ hawTeams[e['id']]] + if e['forEachTeam']: + elemTeams= [ [t] for t in hawTeams[e['id']]] + + elemDays= [ hawDays[e['id']]] + if e['forEachDay'] or e['forOneDay'] : + elemDays= [ [d] for d in hawDays[e['id']]] + + elemHaWishes[e['id']]=[] + allElemDays=[] + thisDaySet=[] + lastDaySet = [] + for d in elemDays : + # print (e) + if (e['forEachDay'] or e['forOneDay']) and e['timeframe']!=1: + thisDaySet=[] + # day1= parse(getDayById[d[0]]['day']) + day1= getDateTimeDay[d[0]] + if e['timeframe']>1: + day2=day1 + datetime.timedelta(days=e['timeframe']-1) + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt= getDateTimeDay[d3] + if day1<=dt and dt<=day2 : + thisDaySet.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + dt= getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1<=dt and getRoundByDay[d3]< r1 + (-e['timeframe']) : + thisDaySet.append(d3) + print (" ROUND HA WISH ", e, thisDaySet, e['timeframe']) + else: + thisDaySet=d + + # only create wish id new day set is superset + if len([d for d in thisDaySet if d not in lastDaySet])>0: + for t in elemTeams: + cntr+=1 + elemHaWishes[e['id']].append(cntr) + elemHaWishTeams[cntr]=t + elemHaWishDays[cntr]=thisDaySet.copy() + elemHaWishFirstDay[cntr]=d[0] + lastDaySet = thisDaySet.copy() + allElemDays+= thisDaySet + + hawRounds[e['id']]=[] + for d3 in set(allElemDays): + hawRounds[e['id']]+=getRoundsByDay[d3] + hawRounds[e['id']]=sorted(list(set(hawRounds[e['id']]))) + hawRoundsString[e['id']]= "" + for r in hawRounds[e['id']]: + hawRoundsString[e['id']]+= str(r)+"_" + if hawRoundsString[e['id']]!="": + hawRoundsString[e['id']]=hawRoundsString[e['id']][:-1] + +nElemHaWishes = sum ([len(elemHaWishes[enc['id']]) for enc in hawishes]) +# # print ("nElemHaWishes",nElemHaWishes) +# print ("hawRounds",hawRounds) +# print ("hawRoundsString",hawRoundsString) + + + +# encGroups = EncGroup.objects.filter(scenario=s2).values() +# derbyRestrictions = DerbyRestriction.objects.filter(scenario=s2).values() +# fairnessCons = FairnessCon.objects.filter(scenario=s2).values() +# englishWeeks = EnglishCon.objects.filter(scenario=s2).values() +# objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values() + +# gew={} +# for ow in objectiveFunctionWeights: +# gew[ow['name']]= ow['use'] * ow['prio'] + +# print(attractivity) +# allow={} +# allow["ATL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["BKN"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["BOS"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["CHA"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["CHI"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "NYK", "OKC", "PHI", "SAS", "TOR", "WAS"] +# allow["CLE"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["DAL"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["DEN"]=["DAL", "DEN", "GSW", "LAC", "LAL", "MEM", "MIL", "MIN", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["DET"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "TOR", "WAS"] +# allow["GSW"]=["DEN", "GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["HOU"]=["ATL", "CHA", "CHI", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["IND"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["LAC"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["LAL"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["MEM"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHI", "SAS", "UTA", "WAS"] +# allow["MIA"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "HOU", "IND", "MEM", "MIA", "MIL", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["MIL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "NYK", "OKC", "PHI", "SAS", "TOR", "WAS"] +# allow["MIN"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "PHI", "PHX", "SAS", "UTA"] +# allow["NOP"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["NYK"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["OKC"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["ORL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["PHI"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["PHX"]=["DAL", "DEN", "GSW", "HOU", "LAC", "LAL", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["POR"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["SAC"]=["DEN", "GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["SAS"]=["ATL", "CHA", "CHI", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["TOR"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIL", "MIN", "NYK", "PHI", "TOR", "WAS"] +# allow["UTA"]=["DAL", "DEN", "GSW", "LAC", "LAL", "MIN", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["WAS"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "ORL", "PHI", "TOR", "WAS"] + +# print ("DISTANCES NBA") + +# shortnames = allow.keys() +# for testDist in [1825,1850,1875]: +# maxdist =0 +# mindist =1000000 +# cntr=0 +# for t in shortnames: +# for t2 in shortnames: +# thisdist = distance[teamByShort[t],teamByShort[t2]] +# if t2 in allow[t]: +# maxdist = max(maxdist,thisdist) +# if thisdist > testDist: +# # print ("close : " ,teamByShort[t] , teamByShort[t2] , thisdist ) +# cntr+=1 +# else: +# mindist = min(mindist,thisdist) +# if thisdist < testDist: +# # print ("far : " ,teamByShort[t] , teamByShort[t2] , thisdist ) +# cntr+=1 +# print ("mismatch at " , testDist ," : ", cntr) + +# print ("maxdist ", maxdist) +# print ("mindist ", mindist) + + + +# gameCntr = { (t1,t2) :0 for t1 in shortnames for t2 in shortnames } +# wantedGames =[('TOR','NOP'), ('LAC','LAL'), ('CHA','CHI'), ('ORL','CLE'), ('IND','DET'), ('PHI','BOS'), ('MIA','MEM'), ('BKN','MIN'), ('SAS','NYK'), ('DAL','WAS'), ('UTA','OKC'), ('POR','DEN'), ('PHX','SAC'), ('DET','ATL'), ('HOU','MIL'), ('GSW','LAC'), ('CHA','MIN'), ('BOS','TOR'), ('BKN','NYK'), ('MEM','CHI'), ('NOP','DAL'), ('OKC','WAS'), ('DEN','PHX'), ('SAC','POR'), ('LAL','UTA'), ('MIL','MIA'), ('DET','PHI'), ('NYK','BOS'), ('ATL','ORL'), ('CLE','IND'), ('HOU','NOP'), ('CHI','TOR'), ('SAS','WAS'), ('UTA','SAC'), ('PHX','LAC'), ('OKC','GSW'), ('MEM','BKN'), ('MIN','MIA'), ('DAL','POR'), ('LAL','CHA'), ('NYK','CHI'), ('DET','IND'), ('TOR','ORL'), ('ATL','PHI'), ('MIL','CLE'), ('NOP','GSW'), ('HOU','OKC'), ('SAS','POR'), ('SAC','DEN'), ('PHX','UTA'), ('LAC','CHA'), ('MIA','ATL'), ('DEN','DAL'), ('LAL','MEM'), ('CLE','CHI'), ('PHI','MIN'), ('ORL','NYK'), ('TOR','DET'), ('BKN','IND'), ('BOS','MIL'), ('WAS','HOU'), ('OKC','POR'), ('SAC','CHA'), ('UTA','LAC'), ('GSW','PHX'), ('ATL','MIA'), ('NOP','DEN'), ('LAC','SAS'), ('IND','CLE'), ('BKN','HOU'), ('ORL','MIL'), ('BOS','NYK'), ('CHI','DET'), ('DAL','LAL'), ('SAC','UTA'), ('GSW','SAS'), ('OKC','NOP'), ('DET','BKN'), ('ORL','DEN'), ('WAS','MIN'), ('MEM','PHX'), ('MIL','TOR'), ('GSW','CHA'), ('POR','PHI'), ('IND','CHI'), ('MIA','HOU'), ('NYK','SAC'), ('SAS','LAL'), ('CLE','DAL'), ('LAC','UTA'), ('WAS','DET'), ('BKN','NOP'), ('MEM','HOU'), ('MIN','MIL'), ('PHX','PHI'), ('GSW','POR'), ('CLE','BOS'), ('CHA','IND'), ('ATL','SAS'), ('CHI','LAL'), ('OKC','ORL'), ('DEN','MIA'), ('DET','NYK'), ('IND','WAS'), ('ATL','CHI'), ('HOU','GSW'), ('TOR','SAC'), ('MEM','MIN'), ('DAL','ORL'), ('UTA','PHI'), ('LAC','MIL'), ('CHA','BOS'), ('SAS','OKC'), ('PHX','MIA'), ('LAC','POR'), ('WAS','CLE'), ('IND','DET'), ('ORL','MEM'), ('ATL','SAC'), ('MIN','GSW'), ('NOP','TOR'), ('DAL','NYK'), ('UTA','MIL'), ('DEN','PHI'), ('POR','BKN'), ('LAL','MIA'), ('SAS','BOS'), ('CHA','NOP'), ('MEM','DAL'), ('OKC','GSW'), ('CHI','HOU'), ('MIN','DEN'), ('PHI','CHA'), ('ORL','IND'), ('OKC','MIL'), ('NYK','CLE'), ('PHX','BKN'), ('POR','ATL'), ('LAL','TOR'), ('DET','MIN'), ('BOS','DAL'), ('NOP','HOU'), ('SAS','MEM'), ('LAC','TOR'), ('GSW','UTA'), ('PHI','CLE'), ('IND','OKC'), ('MIA','DET'), ('CHI','NYK'), ('DEN','ATL'), ('UTA','BKN'), ('PHX','LAL'), ('SAC','POR'), ('CHA','MEM'), ('ORL','PHI'), ('HOU','LAC'), ('BOS','WAS'), ('MIN','SAS'), ('LAL','GSW'), ('POR','TOR'), ('CLE','MIA'), ('MIL','CHI'), ('NYK','DAL'), ('NOP','LAC'), ('PHX','ATL'), ('DEN','BKN'), ('CHA','DET'), ('ORL','SAS'), ('HOU','IND'), ('OKC','PHI'), ('MEM','UTA'), ('MIN','WAS'), ('GSW','BOS'), ('LAL','SAC'), ('CHI','BKN'), ('IND','MIL'), ('NYK','CHA'), ('MIN','HOU'), ('MIA','NOP'), ('SAS','POR'), ('DAL','TOR'), ('LAC','ATL'), ('CLE','PHI'), ('SAC','BOS'), ('MEM','DEN'), ('ORL','WAS'), ('NOP','GSW'), ('LAL','ATL'), ('NYK','CLE'), ('TOR','CHA'), ('BKN','IND'), ('CHI','MIL'), ('HOU','POR'), ('DAL','SAS'), ('PHX','BOS'), ('UTA','MIN'), ('LAC','OKC'), ('MEM','GSW'), ('NOP','POR'), ('SAC','PHX'), ('LAL','OKC'), ('PHI','NYK'), ('WAS','SAS'), ('BKN','CHA'), ('MIA','CLE'), ('DAL','GSW'), ('ATL','MIL'), ('TOR','ORL'), ('CHI','DET'), ('MIN','UTA'), ('DEN','HOU'), ('LAC','BOS'), ('MIL','POR'), ('PHX','NOP'), ('DET','ATL'), ('WAS','CHA'), ('BKN','SAC'), ('OKC','LAL'), ('CHI','MIA'), ('PHI','SAS'), ('DAL','CLE'), ('DEN','BOS'), ('UTA','GSW'), ('LAC','HOU'), ('MIN','PHX'), ('CHA','CHI'), ('IND','ORL'), ('PHI','MIA'), ('NYK','SAS'), ('ATL','TOR'), ('MEM','LAL'), ('CLE','POR'), ('MIL','DET'), ('UTA','NOP'), ('HOU','DAL'), ('NYK','BKN'), ('WAS','SAC'), ('DEN','PHX'), ('LAC','NOP'), ('CLE','BKN'), ('IND','MEM'), ('DET','ORL'), ('MIA','CHA'), ('ATL','MIN'), ('TOR','PHI'), ('BOS','SAC'), ('CHI','POR'), ('MIL','UTA'), ('SAS','LAL'), ('GSW','OKC'), ('DAL','LAC'), ('DEN','WAS'), ('BOS','BKN'), ('CHA','DET'), ('CLE','ORL'), ('PHI','SAC'), ('IND','UTA'), ('TOR','NYK'), ('MIL','ATL'), ('MEM','LAC'), ('HOU','MIA'), ('SAS','MIN'), ('PHX','WAS'), ('NOP','LAL'), ('POR','OKC'), ('GSW','CHI'), ('BKN','BOS'), ('DET','CHA'), ('ORL','TOR'), ('CLE','MIL'), ('NYK','PHI'), ('IND','ATL'), ('MIA','GSW'), ('OKC','NOP'), ('MEM','UTA'), ('SAS','LAC'), ('PHX','DAL'), ('POR','CHI'), ('LAL','WAS'), ('SAC','DEN'), ('PHI','IND'), ('HOU','ATL'), ('MIL','CHA'), ('BKN','MIA'), ('NYK','BOS'), ('MIN','MEM'), ('LAL','DAL'), ('NOP','OKC'), ('DET','SAS'), ('ORL','GSW'), ('TOR','UTA'), ('LAC','WAS'), ('CHA','PHX'), ('PHI','UTA'), ('ATL','GSW'), ('MEM','IND'), ('MIL','NYK'), ('SAC','CHI'), ('CLE','DET'), ('WAS','ORL'), ('NOP','DAL'), ('TOR','MIA'), ('SAS','HOU'), ('DEN','LAL'), ('LAC','POR'), ('CHA','GSW'), ('DET','MIL'), ('ORL','PHX'), ('ATL','BKN'), ('BOS','MIA'), ('OKC','IND'), ('CHI','MEM'), ('DAL','MIN'), ('UTA','LAL'), ('POR','SAC'), ('WAS','PHI'), ('NYK','DEN'), ('TOR','HOU'), ('NOP','PHX'), ('CHA','BKN'), ('DET','IND'), ('CLE','ORL'), ('BOS','DEN'), ('CHI','GSW'), ('OKC','MIN'), ('MIA','WAS'), ('MIL','LAC'), ('SAS','SAC'), ('POR','LAL'), ('DAL','NOP'), ('PHI','CLE'), ('NYK','IND'), ('HOU','PHX'), ('UTA','MEM'), ('BKN','DEN'), ('CHA','ATL'), ('MIA','CHI'), ('WAS','LAC'), ('PHI','TOR'), ('DAL','SAC'), ('POR','OKC'), ('LAL','MIN'), ('IND','LAC'), ('BOS','CLE'), ('NOP','DET'), ('MIL','ORL'), ('HOU','SAC'), ('CHI','TOR'), ('PHX','MIN'), ('UTA','OKC'), ('GSW','MEM'), ('PHI','DEN'), ('CHA','WAS'), ('MIA','ATL'), ('POR','NYK'), ('IND','BOS'), ('CLE','HOU'), ('TOR','LAC'), ('ORL','LAL'), ('BKN','CHA'), ('CHI','ATL'), ('MIN','UTA'), ('PHX','MEM'), ('MIL','NOP'), ('SAC','OKC'), ('GSW','NYK'), ('BOS','PHI'), ('SAS','CLE'), ('DET','DAL'), ('DEN','POR'), ('ORL','HOU'), ('PHI','NOP'), ('ATL','IND'), ('CHI','CHA'), ('MIN','LAC'), ('MIA','LAL'), ('MEM','MIL'), ('SAC','NYK'), ('UTA','GSW'), ('PHX','SAS'), ('TOR','BKN'), ('CHI','LAC'), ('MEM','WAS'), ('MIL','CLE'), ('DAL','MIA'), ('HOU','DET'), ('DEN','OKC'), ('NOP','ORL'), ('IND','CHA'), ('ATL','LAL'), ('BKN','PHI'), ('DEN','NYK'), ('GSW','SAC'), ('DET','WAS'), ('TOR','CLE'), ('OKC','CHI'), ('MIL','DAL'), ('MEM','MIA'), ('HOU','SAS'), ('PHX','POR'), ('IND','LAL'), ('CHA','SAC'), ('NYK','ATL'), ('NOP','BKN'), ('UTA','ORL'), ('LAC','PHX'), ('CLE','CHA'), ('WAS','CHI'), ('DET','TOR'), ('PHI','MIA'), ('OKC','MEM'), ('MIN','NOP'), ('DAL','BOS'), ('DEN','ORL'), ('POR','GSW'), ('ATL','UTA'), ('MIL','LAL'), ('SAS','BKN'), ('LAC','HOU'), ('CLE','MEM'), ('IND','SAC'), ('BOS','DET'), ('TOR','WAS'), ('PHI','DAL'), ('MIA','NYK'), ('OKC','PHX'), ('DEN','MIN'), ('POR','ORL'), ('GSW','NOP'), ('CHA','UTA'), ('BKN','ATL'), ('DET','CHI'), ('PHI','WAS'), ('NYK','MIL'), ('MEM','SAC'), ('SAS','LAC'), ('PHX','HOU'), ('POR','MIN'), ('TOR','DAL'), ('BOS','CHA'), ('MIL','IND'), ('OKC','LAC'), ('LAL','DEN'), ('CLE','ATL'), ('ORL','CHI'), ('DET','PHI'), ('IND','TOR'), ('NYK','WAS'), ('MIA','UTA'), ('MEM','SAS'), ('PHX','DEN'), ('SAC','HOU'), ('POR','NOP'), ('GSW','MIN'), ('TOR','BOS'), ('PHI','MIL'), ('GSW','HOU'), ('LAL','LAC'), ('DEN','NOP'), ('DET','WAS'), ('OKC','MEM'), ('BKN','NYK'), ('DAL','SAS'), ('SAC','MIN'), ('UTA','POR'), ('BOS','CLE'), ('CHA','OKC'), ('ORL','PHI'), ('ATL','MIL'), ('MIA','IND'), ('GSW','PHX'), ('DEN','MEM'), ('NOP','IND'), ('BOS','TOR'), ('CHI','ATL'), ('HOU','BKN'), ('MIN','CLE'), ('WAS','NYK'), ('MIA','PHI'), ('GSW','DAL'), ('SAS','DET'), ('MIL','ORL'), ('SAC','PHX'), ('POR','LAL'), ('LAC','UTA'), ('TOR','OKC'), ('MEM','CHA'), ('NOP','HOU'), ('DEN','SAC'), ('LAL','DAL'), ('ORL','ATL'), ('WAS','MIA'), ('MIN','BKN'), ('CHI','MIL'), ('UTA','DET'), ('POR','PHX'), ('CHA','BOS'), ('IND','PHI'), ('SAC','LAC'), ('TOR','CLE'), ('HOU','DEN'), ('SAS','GSW'), ('OKC','DAL'), ('WAS','ORL'), ('NYK','POR'), ('MIL','MIN'), ('LAL','PHX'), ('CLE','CHA'), ('IND','DEN'), ('MIA','TOR'), ('MIN','GSW'), ('CHI','UTA'), ('DAL','BKN'), ('SAS','OKC'), ('SAC','MEM'), ('LAC','DET'), ('BOS','ATL'), ('ORL','MIA'), ('WAS','POR'), ('HOU','PHI'), ('PHX','NYK'), ('LAL','NOP'), ('LAC','MEM'), ('BKN','TOR'), ('ORL','UTA'), ('ATL','IND'), ('CLE','OKC'), ('CHI','BOS'), ('WAS','DEN'), ('DAL','CHA'), ('GSW','DET'), ('MIL','SAS'), ('SAC','NOP'), ('LAC','NYK'), ('MIA','POR'), ('CLE','MIN'), ('PHX','MEM'), ('LAL','DET'), ('ORL','BKN'), ('WAS','BOS'), ('CHA','IND'), ('PHI','OKC'), ('ATL','DEN'), ('NOP','UTA'), ('DAL','CHI'), ('SAS','MIL'), ('SAC','GSW'), ('CLE','DET'), ('TOR','POR'), ('BKN','OKC'), ('MEM','MIN'), ('PHX','SAC'), ('LAL','NYK'), ('IND','MIA'), ('BOS','SAS'), ('CHA','TOR'), ('ORL','WAS'), ('DAL','DEN'), ('ATL','HOU'), ('NOP','CHI'), ('UTA','NYK'), ('GSW','MIL'), ('PHI','BOS'), ('DET','CLE'), ('MIN','POR'), ('OKC','HOU'), ('WAS','ATL'), ('BKN','MIA'), ('CHI','IND'), ('NYK','NOP'), ('MEM','SAS'), ('DAL','LAL'), ('UTA','CHA'), ('PHX','ORL'), ('SAC','MIL'), ('LAC','GSW'), ('HOU','MIN'), ('DET','CHI'), ('BOS','NOP'), ('OKC','LAL'), ('DAL','PHI'), ('DEN','CLE'), ('POR','MIL'), ('NYK','MIA'), ('WAS','UTA'), ('BKN','ATL'), ('MEM','GSW'), ('TOR','SAS'), ('PHX','CHA'), ('DEN','LAC'), ('DET','NOP'), ('IND','PHI'), ('BOS','CHI'), ('MIN','OKC'), ('POR','CHA'), ('SAC','ORL'), ('LAL','CLE'), ('ATL','PHX'), ('BKN','UTA'), ('MEM','HOU'), ('MIL','NYK'), ('LAC','CLE'), ('GSW','DAL'), ('PHI','BKN'), ('BOS','DET'), ('MIA','SAS'), ('MIN','IND'), ('OKC','TOR'), ('CHI','WAS'), ('DEN','CHA'), ('HOU','POR'), ('SAC','DAL'), ('LAL','ORL'), ('NYK','PHX'), ('MIL','BOS'), ('NOP','UTA'), ('GSW','DEN'), ('LAC','ORL'), ('PHI','CHI'), ('IND','MIN'), ('TOR','WAS'), ('MEM','CLE'), ('OKC','MIA'), ('SAS','ATL'), ('DAL','POR'), ('NOP','LAC'), ('BKN','MIL'), ('BOS','PHX'), ('ATL','DET'), ('NYK','PHI'), ('CHI','CLE'), ('MIN','TOR'), ('HOU','LAL'), ('GSW','ORL'), ('OKC','POR'), ('UTA','SAC'), ('SAS','MIA'), ('DEN','IND'), ('WAS','DET'), ('ATL','TOR'), ('BKN','PHI'), ('MIL','CHI'), ('MEM','NOP'), ('CLE','NYK'), ('HOU','OKC'), ('CHA','ORL'), ('MIA','SAC'), ('BOS','LAL'), ('MIN','DEN'), ('UTA','IND'), ('PHX','SAS'), ('POR','GSW'), ('DAL','LAC'), ('ORL','OKC'), ('DET','SAC'), ('ATL','LAC'), ('NYK','LAL'), ('BOS','MEM'), ('TOR','PHI'), ('MIA','WAS'), ('HOU','DEN'), ('CHI','MIN'), ('NOP','SAS'), ('PHX','IND'), ('GSW','UTA'), ('CLE','WAS'), ('BKN','LAL'), ('POR','DAL'), ('CHA','MIL'), ('ORL','BOS'), ('DET','MEM'), ('NYK','TOR'), ('OKC','ATL'), ('NOP','DEN'), ('MIN','HOU'), ('MIA','LAC'), ('CHI','SAC'), ('SAS','PHX'), ('GSW','IND'), ('UTA','DAL'), ('DET','BKN'), ('CLE','CHI'), ('MIN','OKC'), ('PHI','LAL'), ('DEN','HOU'), ('SAS','TOR'), ('NYK','BKN'), ('NOP','BOS'), ('ORL','LAC'), ('MEM','PHX'), ('ATL','WAS'), ('POR','IND'), ('DET','CLE'), ('MIA','ORL'), ('OKC','DAL'), ('MIN','SAC'), ('CHI','SAS'), ('UTA','HOU'), ('CHA','NYK'), ('TOR','ATL'), ('MIA','BOS'), ('PHI','GSW'), ('CLE','NOP'), ('MEM','DEN'), ('MIL','WAS'), ('DAL','PHX'), ('LAL','LAC'), ('IND','CHI'), ('BKN','DET'), ('NYK','MEM'), ('SAS','UTA'), ('POR','HOU'), ('SAC','OKC'), ('WAS','CHA'), ('CLE','TOR'), ('ATL','PHI'), ('BOS','GSW'), ('LAC','SAC'), ('DEN','UTA'), ('DET','TOR'), ('BKN','CHI'), ('HOU','DAL'), ('NOP','MEM'), ('MIL','DEN'), ('PHX','OKC'), ('LAL','POR'), ('LAC','MIN'), ('ORL','MIA'), ('IND','NYK'), ('WAS','BKN'), ('CLE','GSW'), ('DAL','ATL'), ('BOS','PHI'), ('SAS','CHA'), ('SAC','LAL'), ('POR','UTA'), ('DET','DEN'), ('HOU','NOP'), ('MIL','PHX'), ('TOR','CHI'), ('IND','DAL'), ('WAS','GSW'), ('CLE','NYK'), ('CHA','ORL'), ('ATL','BOS'), ('MIA','PHI'), ('BKN','PHX'), ('MEM','DET'), ('SAC','MIN'), ('LAC','SAS'), ('NOP','MIL'), ('HOU','CHA'), ('DEN','POR'), ('LAL','SAS'), ('DET','PHX'), ('BKN','GSW'), ('TOR','IND'), ('BOS','ORL'), ('MIN','ATL'), ('OKC','CLE'), ('DAL','MEM'), ('UTA','DEN'), ('LAC','MIA'), ('NYK','ORL'), ('CHI','NOP'), ('MIL','PHI'), ('POR','SAS'), ('LAL','HOU'), ('WAS','DAL'), ('PHI','MEM'), ('BOS','ATL'), ('OKC','DET'), ('IND','TOR'), ('PHX','HOU'), ('SAC','MIA'), ('UTA','POR'), ('ORL','MIL'), ('CHA','DAL'), ('DET','NYK'), ('TOR','BKN'), ('IND','NOP'), ('MIN','LAC'), ('GSW','LAL'), ('PHX','DEN'), ('SAC','SAS'), ('OKC','BOS'), ('PHI','CHI'), ('WAS','MEM'), ('ATL','NYK'), ('HOU','UTA'), ('CLE','LAC'), ('POR','MIA'), ('ORL','ATL'), ('IND','BKN'), ('DET','CHA'), ('TOR','MIN'), ('MIL','SAC'), ('DAL','UTA'), ('DEN','SAS'), ('GSW','MIA'), ('LAL','PHX'), ('WAS','CHI'), ('PHI','LAC'), ('NOP','POR'), ('OKC','SAS'), ('HOU','BOS'), ('CLE','ATL'), ('ORL','DET'), ('IND','MIL'), ('BKN','TOR'), ('NYK','WAS'), ('MIN','CHA'), ('MEM','POR'), ('DAL','SAC'), ('PHX','GSW'), ('UTA','MIA'), ('DEN','LAL'), ('BOS','LAC'), ('NOP','OKC'), ('DET','MIL'), ('ATL','MIA'), ('PHI','BKN'), ('CHI','CHA'), ('SAC','MEM'), ('GSW','HOU'), ('WAS','CLE'), ('ORL','DAL'), ('NYK','IND'), ('TOR','PHX'), ('MIN','BOS'), ('OKC','DEN'), ('UTA','SAS'), ('LAL','MEM'), ('POR','NOP'), ('LAC','SAC'), ('CHA','BKN'), ('ATL','DAL'), ('MIA','CLE'), ('CHI','PHX'), ('MIL','PHI'), ('UTA','HOU'), ('LAL','BOS'), ('TOR','IND'), ('DEN','MIN'), ('OKC','SAS'), ('CHI','WAS'), ('GSW','NOP'), ('POR','DET'), ('PHI','ATL'), ('CLE','MIA'), ('WAS','MIL'), ('BKN','ORL'), ('HOU','NYK'), ('DAL','MIN'), ('UTA','PHX'), ('LAC','MEM'), ('IND','CHA'), ('TOR','MIL'), ('CHI','OKC'), ('DEN','DET'), ('POR','BOS'), ('LAL','NOP'), ('GSW','SAC'), ('WAS','BKN'), ('CHA','NYK'), ('CLE','PHI'), ('SAS','DAL'), ('MIA','MIN'), ('ATL','ORL'), ('HOU','MEM'), ('PHX','LAC'), ('UTA','BOS'), ('PHI','NYK'), ('IND','POR'), ('OKC','SAC'), ('GSW','LAL'), ('ORL','MIN'), ('ATL','BKN'), ('TOR','CHA'), ('NOP','CLE'), ('MIA','DAL'), ('MIL','OKC'), ('MEM','SAC'), ('PHX','DET'), ('UTA','WAS'), ('LAC','DEN'), ('NYK','CHI'), ('MIA','BKN'), ('ATL','POR'), ('CLE','IND'), ('MEM','LAL'), ('BOS','HOU'), ('SAS','ORL'), ('PHX','GSW'), ('CHA','MIL'), ('MIN','DAL'), ('LAC','PHI'), ('SAC','DET'), ('DEN','TOR'), ('NOP','LAL'), ('GSW','WAS'), ('NYK','HOU'), ('ORL','POR'), ('CLE','UTA'), ('ATL','MEM'), ('MIA','MIL'), ('CHI','DAL'), ('SAS','IND'), ('CHA','SAS'), ('BOS','BKN'), ('OKC','LAC'), ('NOP','MIN'), ('DEN','GSW'), ('PHX','TOR'), ('LAL','PHI'), ('SAC','WAS'), ('CLE','BOS'), ('DET','OKC'), ('MIA','ORL'), ('BKN','MEM'), ('NYK','UTA'), ('MIN','CHI'), ('DAL','NOP'), ('MIL','IND'), ('POR','WAS'), ('CHA','DEN'), ('HOU','LAC'), ('SAC','PHI'), ('GSW','TOR'), ('WAS','ATL'), ('NYK','OKC'), ('BKN','SAS'), ('CHI','IND'), ('NOP','MIA'), ('MIN','ORL'), ('BOS','UTA'), ('DAL','MEM'), ('PHX','POR'), ('LAL','MIL'), ('CHA','HOU'), ('DET','UTA'), ('CLE','DEN'), ('MEM','ATL'), ('GSW','PHI'), ('POR','SAC'), ('BKN','CHI'), ('LAC','LAL'), ('MIN','NOP'), ('PHX','MIL'), ('BOS','OKC'), ('DAL','IND'), ('WAS','MIA'), ('HOU','ORL'), ('NYK','DET'), ('CLE','SAS'), ('SAC','TOR'), ('ATL','CHA'), ('DEN','MIL'), ('UTA','TOR'), ('IND','BOS'), ('WAS','NYK'), ('CHI','CLE'), ('SAS','DAL'), ('HOU','MIN'), ('MEM','ORL'), ('POR','PHX'), ('LAL','BKN'), ('GSW','LAC'), ('MIA','CHA'), ('PHI','DET'), ('ATL','NYK'), ('OKC','UTA'), ('DAL','DEN'), ('SAC','NOP'), ('ORL','CHI'), ('MIL','BOS'), ('POR','MEM'), ('GSW','BKN'), ('LAL','HOU'), ('CHA','CLE'), ('BOS','WAS'), ('OKC','MIN'), ('MIA','NYK'), ('SAS','DEN'), ('UTA','NOP'), ('LAC','BKN'), ('DAL','PHX'), ('ATL','CLE'), ('TOR','DET'), ('PHI','IND'), ('MIA','CHI'), ('MIL','GSW'), ('SAS','MIN'), ('UTA','MEM'), ('LAC','NOP'), ('POR','HOU'), ('ORL','CHA'), ('WAS','OKC'), ('CHI','BOS'), ('SAC','BKN'), ('LAL','DEN'), ('PHI','WAS'), ('NOP','ATL'), ('TOR','GSW'), ('MIL','MIA'), ('SAS','MEM'), ('UTA','LAL'), ('LAC','DAL'), ('DET','ORL'), ('NYK','CHA'), ('HOU','CLE'), ('MEM','OKC'), ('SAC','DAL'), ('POR','MIN'), ('PHI','TOR'), ('IND','GSW'), ('BOS','NYK'), ('ATL','OKC'), ('BKN','WAS'), ('CHI','MIA'), ('NOP','SAS'), ('DEN','LAC'), ('PHX','MIN'), ('LAL','UTA'), ('ORL','CLE'), ('CHA','PHI'), ('MIL','MEM'), ('HOU','SAC'), ('POR','DAL'), ('DET','GSW'), ('IND','MIA'), ('TOR','BOS'), ('ATL','WAS'), ('OKC','DEN'), ('SAS','CHI'), ('UTA','MIN'), ('LAC','PHX'), ('BKN','BOS'), ('PHI','ATL'), ('IND','CLE'), ('CHA','LAL'), ('ORL','SAC'), ('NYK','GSW'), ('HOU','CHI'), ('WAS','MIL'), ('MEM','NOP'), ('PHX','DAL'), ('TOR','DEN'), ('DET','LAL'), ('MIN','POR'), ('NOP','SAC'), ('SAS','UTA'), ('WAS','BOS'), ('NYK','LAC'), ('IND','PHX'), ('MIA','OKC'), ('BKN','ORL'), ('CHI','DEN'), ('MIL','DET'), ('DAL','HOU'), ('CHA','POR'), ('CLE','SAC'), ('TOR','LAL'), ('NOP','MEM'), ('MIN','PHI'), ('UTA','SAS'), ('ORL','IND'), ('BKN','LAC'), ('WAS','PHX'), ('DET','POR'), ('MIA','DEN'), ('NYK','TOR'), ('MEM','BOS'), ('MIL','HOU'), ('GSW','ATL'), ('CLE','LAL'), ('OKC','CHA'), ('CHI','PHI'), ('MIN','SAS'), ('DAL','UTA'), ('SAC','ATL'), ('ORL','BKN'), ('IND','HOU'), ('DET','LAC'), ('PHI','PHX'), ('BOS','POR'), ('NOP','NYK'), ('MIL','WAS'), ('DEN','SAS'), ('BKN','CLE'), ('CHA','LAC'), ('WAS','LAL'), ('CHI','NYK'), ('MIA','PHX'), ('MEM','TOR'), ('GSW','OKC'), ('UTA','ATL'), ('DAL','MIL'), ('DET','HOU'), ('BOS','MIN'), ('ORL','NOP'), ('PHI','POR'), ('GSW','SAS'), ('SAC','IND'), ('CHA','MIA'), ('CLE','PHX'), ('TOR','MEM'), ('BKN','POR'), ('MIN','LAL'), ('UTA','CHI'), ('DEN','OKC'), ('LAC','IND'), ('PHI','HOU'), ('ATL','NOP'), ('GSW','DEN'), ('SAC','SAS'), ('ORL','CHA'), ('WAS','NOP'), ('BKN','DET'), ('BOS','MIA'), ('MIN','DAL'), ('MEM','NYK'), ('OKC','PHX'), ('UTA','CLE'), ('MIL','TOR'), ('LAL','IND'), ('HOU','GSW'), ('SAC','LAC'), ('POR','UTA'), ('IND','BKN'), ('DET','MIA'), ('WAS','PHI'), ('ATL','CHA'), ('TOR','MIL'), ('NYK','MIN'), ('BOS','ORL'), ('MEM','DAL'), ('SAS','GSW'), ('DEN','CHI'), ('PHX','CLE'), ('LAC','OKC'), ('SAC','LAL'), ('CHA','ATL'), ('PHI','ORL'), ('BOS','MIL'), ('NYK','MIA'), ('IND','WAS'), ('PHX','CHI'), ('BKN','DAL'), ('DEN','UTA'), ('MIN','DET'), ('SAS','NOP'), ('HOU','TOR'), ('SAC','CLE'), ('POR','MEM'), ('LAL','OKC'), ('WAS','TOR'), ('ATL','DET'), ('MIA','IND'), ('PHI','MIL'), ('OKC','BKN'), ('NOP','CHA'), ('MIN','PHX'), ('DAL','HOU'), ('SAS','SAC'), ('DEN','MEM'), ('POR','CLE'), ('UTA','LAC'), ('LAL','CHI'), ('BOS','IND'), ('CHA','TOR'), ('NYK','ORL'), ('HOU','SAS'), ('LAC','CHI'), ('GSW','CLE'), ('MIA','DET'), ('MIL','BKN'), ('NOP','PHX'), ('MIN','SAC'), ('POR','DEN'), ('LAL','GSW'), ('ORL','BOS'), ('TOR','ATL'), ('OKC','NYK'), ('HOU','WAS'), ('SAS','PHI'), ('DAL','DET'), ('IND','ORL'), ('NOP','PHI'), ('CHA','WAS'), ('CLE','MIL'), ('CHI','BKN'), ('MIA','BOS'), ('MEM','OKC'), ('LAC','GSW'), ('DEN','SAC'), ('PHX','UTA'), ('SAS','HOU'), ('TOR','NYK'), ('MIL','ATL'), ('LAL','MIN'), ('CLE','BKN'), ('DET','BOS'), ('CHA','MIA'), ('IND','SAS'), ('CHI','ORL'), ('MEM','PHI'), ('HOU','PHX'), ('OKC','UTA'), ('NOP','WAS'), ('DEN','DAL'), ('LAC','MIN'), ('GSW','POR'), ('NYK','DET'), ('MIA','TOR'), ('UTA','DEN'), ('LAL','SAC'), ('PHI','CHA'), ('BOS','CHI'), ('ATL','CLE'), ('WAS','IND'), ('HOU','MEM'), ('BKN','MIL'), ('SAS','NOP'), ('MIN','NYK'), ('DAL','OKC'), ('ORL','TOR'), ('SAC','GSW'), ('POR','LAC'), ('PHX','LAL')] +# wantedEncounters = set(wantedGames) +# unwantedEncounters = [ (t1,t2) for t1 in shortnames for t2 in shortnames if not (t1,t2) in wantedEncounters ] +# # print ("unwanted ", unwantedEncounters) + +# for (t1, t2) in wantedGames: +# gameCntr[(t1,t2)]+=1 + +# # for t1 in shortnames: +# # for t2 in shortnames: +# # print (gameCntr) + +# playOnce = [ e for e in wantedEncounters if gameCntr[e] ==1] +# playTwice = [ e for e in wantedEncounters if gameCntr[e] ==2] + +# t_cc = { t : [ c for c in confTeams.keys() if t in confTeams[c]] for t in teams } +# print (t_cc) + +# interconferencegames = { (c1,c2) : 0 for c1 in confTeams.keys() for c2 in confTeams.keys() } + +# for c in confTeams.keys(): +# print (confTeams[c]) + +# for (tn1, tn2) in playTwice: +# print (teamByShort[tn1] , teamByShort[tn2]) +# t1 = getTeamIdByName[teamByShort[tn1]] +# t2 = getTeamIdByName[teamByShort[tn2]] +# for c1 in t_cc[t1]: +# for c2 in t_cc[t2]: +# interconferencegames[(c1,c2)]+=2 + + +# for (c1,c2) in interconferencegames.keys(): +# print (confName[c1] , " - ", confName[c2], " : ", interconferencegames[(c1,c2)]) + +# # getTeamIdByName +# # print (playOnce) +# # print (playTwice) + + +# for t in teams: +# for c in clusters: +# # tripToClusterSaving[(t,c)]= cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) +# print (cluster_distances[c,c], "-2*",distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) , cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) ) +# print (" " , tripToClusterSaving[(t,c)] , tripToClusterSaving[t,c] ) + +# for t in teams: +# for c in clusters: +# print (getTeamById[t], " to ", c, " : " , cluster_distances[c,c] , "-2* " ,distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) , cluster_distances[c,c] -2.0*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]), cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c])) +# for t2 in cluster_teams[c]: +# print ("--",getTeamById[t2]) +# print ("---> " ,tripToClusterSaving[(t,c)]) + + + +# print (games) +# print (attractivity) +# for (t1,t2) in games: +# print (t1,t2) +# print (attractivity[t1,t2] ) +gameAttractivity = { (t1,t2) : attractivity[t1,t2] for (t1,t2) in games if t10 ]) for t in teams } + + +# scale blocking of basic round to [0,1] +for t in teams: + for r in basicRounds: + if len(getBasicDays[r])>0: + nonBlocked[(t,r)]/=len(getBasicDays[r]) + travelDays[(t,r)]/=len(getBasicDays[r]) + if getTeamById[t]=="AX Armani Exchange Milan": + print (r , getRealRounds[r] , nonBlocked[(t,r)] , nonBlocked[(t,r)] ) + +for t in teams: + if mathModelName=="UEFA NL" and noPlayRounds[t] in [ [3,4] ]: + t_usePhases[t]= False + print ("No need for phases " , getTeamById[t]) + for t2 in opponents[t]: + t_usePhases[t2]= False + print (" -- also no need for phases " , getTeamById[t2]) + if getTeamById[t]=="AX Armani Exchange Milan": + t_usePhases[t]= False + print ("setting t_usePhases of ", getTeamById[t],t_usePhases[t] ) + +runPatternAssignmentFirst=False + +if runMode=='New' and optSteps[0][0]=='HEURISTIC' and not thisSeason.useFeatureOpponentMatrix and not thisSeason.groupBased and len(fixedGames)+len(fixedGames2)==0 : + runPatternAssignmentFirst=True + +chosenGames = [ ] + +useFullModel1= False + +usePatterns= not mathModelName in ["NHL", "LNR"] +usePatterns= not mathModelName in ["NHL"] + + +balanceBreaks = mathModelName=="LNR" +haSymmetric = not mathModelName in ["NHL" , "LNR"] +haSymmetric = not mathModelName in ["NHL", "Ligue 1", "Costa Rica Premier League"] +haSymmetric = not mathModelName in ["NHL"] +# haSymmetric = not mathModelName in ["NHL" , "LNR"] +if thisSeason.symmetry: + haSymmetric=True + +use2BreakPatterns = False + +# if thisSeason.initBreaks>=2 and objectivePrio != 'Trips' : +if thisSeason.initBreaks>=2 : + use2BreakPatterns = True +# use2BreakPatterns = False + +# if objectivePrio == 'Trips' and not regionalPatternUse: +# use2BreakPatterns = False + + + +# basicRounds= range(1,40) +# half_symmetry_offset = 1 if mathModelName in [ "TOP 14"] else 0 +half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1 + +# if mathModelName in [ "Ligue 1"]: +# half_symmetry_offset = 4 + +# print (half_symmetry_offset , thisSeason.symmetry , use2BreakPatterns) + + +prev_mirror_round= { r : 0 if r<=nRounds1 else r-nRounds1+half_symmetry_offset for r in basicRounds } + + +# for r in basicRounds: +# print (r, " --> " , prev_mirror_round[r] ) +# print ("") + + +for r in basicRounds: + if r>nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1): + # prev_mirror_round[r]-=nRounds1 + prev_mirror_round[r] = int((r-1)/nRounds1-1)*nRounds1 +half_symmetry_offset+1-prev_mirror_round[r]%nRounds1 + +if mathModelName in [ "LNR"] and False: + prev_mirror_round[14]=4 + prev_mirror_round[15]=3 + prev_mirror_round[16]=2 + prev_mirror_round[17]=1 + prev_mirror_round[18]=13 + prev_mirror_round[19]=12 + prev_mirror_round[20]=11 + prev_mirror_round[21]=10 + + prev_mirror_round[22]=9 + prev_mirror_round[23]=8 + prev_mirror_round[24]=7 + prev_mirror_round[25]=6 + prev_mirror_round[26]=5 + + +# for r in basicRounds: +# print (r, " --> " , prev_mirror_round[r] ) + + + + + +getPhaseOfBasicRound={ br : getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds} +getBasicRoundsOfPhase={ ph : [ br for br in basicRounds if getPhaseOfBasicRound[br]==ph ] for ph in phases } + +# print (getPhaseOfBasicRound) +# print (getBasicRoundsOfPhase) + + +if use2BreakPatterns or nTeams >200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams>0 : + useFullModel1=True +useFullModel1=True + + +preplan_phases = phases +preplan_phases= [0] +if not haSymmetric: + preplan_phases = phases + +fixedGamesRounds=[] + +starweight={t : 0.0 for t in teams} +for t1 in teams : + for t2 in teams : + starweight[t1]+=distance[getTeamById[t1],getTeamById[t2]] + +# if thisSeason.minBreaks or len(fixedGames)==0: + +available_days={ (t,d) : 1 for t in teams for d in days} + +# for c in Conference.objects.filter(scenario=s2, name="Eastern Conference"): +# for t in c.teams.all(): +# for r in rounds: +# available_days[t.id,earliestDay[r]] =0 + +# for c in Conference.objects.filter(scenario=s2, name="Western Conference"): +# for t in c.teams.all(): +# for r in rounds: +# available_days[t.id,latestDay[r]] =0 + +for bl in blockings: + if bl['type'] in ["Home","Hide"] and bl['time']=='----': + available_days[ bl['team_id'],bl['day_id']] =0 + +t_blocked_at={ (t,r) : sum([available_days[t,d] for d in getDays[r]])==0 for t in teams for r in rounds} + +if runPatternAssignmentFirst: + + debug= True + debug= False + comptime = 500 + gap = 0.05 + if debug: + comptime = 30 + gap = 0.5 + + possGames = optimize_model1(comptime,gap) + print (possGames) + print (len(possGames)) + chosenGames, unChosenGames = optimize_model4(possGames) + + print (len(chosenGames) , " chosen games" , chosenGames ) + print (len(unChosenGames) , "unchosen games : ", unChosenGames) + + # exit(0) + # print (len(chosenGames)) + + fineDistributionModel4 = False + fineDistributionModel4 = nBasicTeams=10 ] + +# for t in teams: +# print (t, getTeamById[t], [ r for (r,d) in usingRDs[t]], t_conference[t]) + +# print (TVteams) +# print ("CHECKPOINT ", 5) + +# print (seedTV) +# print (dontPlay) + +x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +for (t1,t2) in games: + for rd in roundDays: + x[(t1,t2,rd)]=1 + +for (t1,t2,rd) in x.keys(): + if blocked_arena[(t1,rd[1],"----")] and runMode=='Improve' and not thisSeason.allowBlockingViosInImprove: + # cntr +=1 + # print ("FORBIDDING") + + x[(t1,t2,rd)]=0 + +for (t1,t2,d,channel) in seedTV: + if gameCntr[(t1,t2)]==1: + for (r,d2) in roundDays: + if d != d2 : + # cntr +=1 + x[(t1,t2,(r,d2))]=0 + + for t3 in teams: + if not t3 in [t1,t2]: + for rd in getRoundDaysByDay[d]: + x[(t1,t3,rd)]=0 + x[(t2,t3,rd)]=0 + x[(t3,t1,rd)]=0 + x[(t3,t2,rd)]=0 + # cntr +=4 + +for (t,d) in dontPlay: + for t3 in teams: + if not t3 in [t1,t2]: + for rd in getRoundDaysByDay[d]: + x[(t,t3,rd)]=0 + x[(t3,t,rd)]=0 + +for (t1,t2,d) in dontPlayGames: + for rd in getRoundDaysByDay[d]: + x[(t1,t2,rd)]=0 + +attendance = { (t1,t2,d) : 0 for (t1,t2) in games for d in days } +if thisSeason.useFeaturePrediction: + # learner = AttendanceLearner(thisSeason.id) + # attendance = learner.predict_games(attendance.keys()) + oldestTrainingGame= parse("2015-01-01") + oldestTrainingGame=oldestTrainingGame.date() + games_train = Game.objects.filter(season=thisSeason).exclude(historic_season=None) + print ( " all games",len(games_train), ) + games_train = [g for g in games_train if g.date>oldestTrainingGame ] + print ( " important games",len(games_train), ) + + games_predict = attendance.keys() + + X= [] + y =[] + homeAttendances = {t: [] for t in teams+inactive_teams} + for game in games_train: + if game.attendance>0: + homeAttendances[game.homeTeam.id].append(game.attendance) + for t in teams+inactive_teams: + homeAttendances[t]=sorted(homeAttendances[t]) + # print (getTeamById[t], homeAttendances[t]) + if len(homeAttendances[t])==0: + homeAttendances[t]=[7500] + maxAttendance = {t: homeAttendances[t][-1] for t in teams+inactive_teams } + # maxAttendance = {t: homeAttendances[t][int(0.9*(len(homeAttendances[t])))] for t in teams+inactive_teams } + medianAttendance = {t: homeAttendances[t][int(0.5*(len(homeAttendances[t])))] for t in teams+inactive_teams } + loyalty = {t: medianAttendance[t]/maxAttendance[t] for t in teams+inactive_teams } + + for t in teams+inactive_teams: + print (getTeamById[t], loyalty[t] , medianAttendance[t] , maxAttendance[t] , ) + + def getFeatureVector(t1,t2,d): + ds = distanceInKmByGPS(t_lon[t1],t_lat[t1], t_lon[t2], t_lat[t2]) + # print ( d.day , d.month , d.year , " -> " , (d.month+5)%12 , " -> " , int((d.month+4)%12/2) , d.month==12 and d.day >15 , d.weekday() , d.weekday() in [4,5,6]) + # print ( d.year," -> " , d.year-2015 ) + gm={ + # 'homeTeam_id': t1, 'awayTeam_id': t2, + # 'month': d.month, + 'year': d.year-2015 , + 'month': int((d.month+4)%12/2) , + 'weekday': d.weekday() , + 'weekend': d.weekday() in [4,5,6], + 'holiday': d.month==12 and d.day >15 , + 'summer': d.month in [6,7,8,9], + 'winter': d.month in [12,1,2], + 'home_attractivity': t_attractivity[t1], 'away_attractivity': t_attractivity[t2], + 'attractivity_ratio': t_attractivity[t2]/max(1,t_attractivity[t1]), + 'home_lat': t_lat[t1] , 'home_lon': t_lon[t1] , + 'away_lat': t_lat[t2], 'away_lon': t_lon[t2], + 'distance' : ds , + 'distance_frac' : ds/medianAttendance[t2] , + 'loyalty_home' : loyalty[t1] , + 'medianAttendance_home' : 0.001*medianAttendance[t1] , + 'maxAttendance_home' : 0.001*maxAttendance[t1] , + # 'medianAttendance_away' : 0.001*medianAttendance[t2] , + # 'maxAttendance_away' : 0.001*maxAttendance[t2] , + } + for t in teams+inactive_teams: + if t1 == t: + gm["home_"+str(t1)]=True + if t2 == t: + gm["away_"+str(t2)]=False + for c in countries: + if t_country[t1] == c: + gm["home_country_"+str(t1)]=True + if t_country[t2] == c: + gm["away_country_"+str(t2)]=False + return list(gm.values()) + + for game in games_train: + X.append(getFeatureVector(game.homeTeam.id, game.awayTeam.id,game.date) ) + y.append(game.attendance) + + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=1) + + rf_regressor = RandomForestRegressor(n_estimators = 200 , random_state = 42) + # score on training 0.959517674767985 + # score on test 0.8494965525219991 + # rf_regressor = GradientBoostingRegressor(n_estimators = 200 , random_state = 42) + # score on training 0.9022637786547619 + # score on test 0.8047414718789097 + rf_regressor.fit(X_train,y_train) + score = rf_regressor.score(X_train,y_train) + print ("score on training " , score) + score = rf_regressor.score(X_test,y_test) + print ("score on test " , score) + + X= [] + y =[] + for (t1,t2,d) in attendance.keys(): + X.append(getFeatureVector(t1,t2,getDateTimeDay[d]) ) + + attendance = dict(zip(games_predict,rf_regressor.predict(X))) + + for g in games_predict: + # print (g, int (attendance[g]) , maxAttendance[g[0]], int (attendance[g]) < maxAttendance[g[0]], min ( int (attendance[g]) , maxAttendance[g[0]])) + attendance[g] = min ( int (attendance[g]) , maxAttendance[g[0]]) + +# print ( sum (x[ttrd] for ttrd in x.keys()) , len(games)*len(roundDays)) + +# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +x_round= {(t1,t2,r) : 0 for t1 in teams for t2 in teams for r in rounds} +x_time= {(t1,t2,rd,tm) : 0 for t1 in teams for t2 in teams for rd in roundDays for tm in times} +if evalRun: + x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} + for (t1,t2,r,d) in currentSolution: + x[(t1,t2,(r,d))]=1 + if currentKickoffTimes[(t1,t2,d)] in times: + x_time[(t1, t2, (r, d), currentKickoffTimes[(t1,t2,d)])]=1 + # print ("setting " , t1,t2,r,d,currentKickoffTimes[(t1,t2,d)]) + +for (t1,t2) in games: + for rd in roundDays: + # print ("make var " ,t1,t2,rd) + if x[(t1,t2,rd)]==1 : + # if not blocked_arena[(t1,rd[1],"----")] or runMode=='New': + if not evalRun: + x[(t1,t2,rd)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1]), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + cntr +=1 + if thisSeason.useFeatureKickOffTime: + for tm in times: + x_time[(t1,t2,rd,tm)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1])+'_'+tm , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + else: + cntr+=len(roundDays) + + for r in rounds: + x_round[(t1,t2,r)]= pulp.LpVariable('x_round_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + model2+= x_round[(t1,t2,r)] == sum([x[(t1,t2,rd)] for rd in getRoundDaysByRound[r]]) + + +t_prev_mirror_round ={(t,r) : 0 for t in teams for r in rounds} + +for t1 in teams: + phaseLength= int(len(playRounds[t1])/nPhases+0.5) + # print ("phaseLength", phaseLength) + for i in range(len(playRounds[t1])): + # print (i, phaseLength) + if i >=phaseLength: + # print ("setting " , playRounds[t1], playRounds[t1][i-phaseLength]) + # print ("setting " , playRounds[t1][i], "->",playRounds[t1][i-phaseLength]) + t_prev_mirror_round[(t1,playRounds[t1][i])]=playRounds[t1][i-phaseLength] + # t_prev_mirror_round[(t,playRounds[t1][i])]=5 + + +# for tr in t_prev_mirror_round.keys(): +# print( "t_prev_mirror_round " ,tr , t_prev_mirror_round[tr]) + +# print (prev_mirror_round) + + +if not evalRun: + for (t1,t2) in games: + for r in rounds: + if r > nRounds1 and thisSeason.symmetry : + prev_round =prev_mirror_round[r] + if thisSeason.groupBased and len(noPlayRounds[t1])>0 and noPlayRounds[t1]==noPlayRounds[t2]: + prev_round=t_prev_mirror_round[t1,r] + + if prev_round>0: + model2+= x_round[(t1,t2,r)] == x_round[(t2,t1,prev_round)] + # print ("x_round[(",t1,",",t2,",",r,")] == x_round[(",t2,t1,prev_mirror_round[r],")]") + # print ( t1,t2,r , " -> ",prev_mirror_round[r]) + + +# for (t1,t2) in games: +# # every pair plays each other in each phase once +# for p in phases: +# if p0 and noPlayRounds[t1]==noPlayRounds[t2]: +# relDays = [] +# phaseLength= int(len(playRounds[t1])/nPhases+0.5) +# for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: +# relDays+=getDays[rr] +# print ("adding days of round ", rr , " to phase ", p) +# print ("reldays", relDays) +# else: +# relDays = getDaysOfPhase[p] +# model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ) <= 1 + + + +# for r in rounds: + # if r > nRounds1 and (thisSeason.symmetry or haSymmetric): + # print ( r , " -> ",prev_mirror_round[r]) +# print (len(games)) + + + + +for (t1,t2,(r,d)) in x.keys(): + if thisSeason.useFeatureKickOffTime: + for tm in times : + if blocked_arena[(t1,d,tm)] and runMode=='Improve': + x_time[(t1,t2,(r,d), tm)]=0 + # print ("forbidding tm ", t1,t2,r,d, tm) + cntr2+=1 + # print ("FORBIDDING") + + +print (" .... got rid of " , len(x.keys())-cntr, " + " ,cntr2, " vars") + + + + +for (t1,t2,d,channel) in seedTV: + print (t1,t2,d , gameCntr[(t1,t2)], gameCntr[(t1,t2)]==1) + model2+= sum([x[(t1,t2,rd)] for rd in getRoundDaysByDay[d] ])==1 + +# model2+= lpSum( x[rr] for rr in x.keys() ) +# model2.solve(XPRESS(msg=1,maxSeconds = 25, keepFiles=True)) +# return "" + +if thisSeason.lastRoundSync: + if thisSeason.useFeatureKickOffTime: + lastRoundPlayed= {(g,rd,tm1) : pulp.LpVariable('x_'+str(g)+'_'+str(rd[1])+'_'+tm1 , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for g in displayGroups.keys() for rd in finalRoundDays for tm1 in times} + else: + lastRoundPlayed= {(g,rd) : pulp.LpVariable('x_'+str(g)+'_'+str(rd[1]) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for g in displayGroups.keys() for rd in finalRoundDays } +homeInRound= {(t1,r) : pulp.LpVariable('homeInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +awayInRound= {(t1,r) : pulp.LpVariable('awayInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +gameInBasicRound= {(t1,t2,r) : pulp.LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = defaultGameRepetions, cat = pulp.LpContinuous) for (t1,t2) in games for r in basicRounds} +homeInBasicRound= {(t1,r) : pulp.LpVariable('homeInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds} +awayInBasicRound= {(t1,r) : pulp.LpVariable('awayInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds} +break3InRound= {(t1,r) : pulp.LpVariable('break3InRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +# breakInRound= {(t1,r) : pulp.LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +# breaksTotal = pulp.LpVariable('breaksTotal', lowBound = 0, cat = pulp.LpContinuous) + +tripToSingleTripElement = {(t1,d,c) : pulp.LpVariable('tripToSingleTripElement_'+str(t1)+'_'+str(d)+'_'+str(c), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) for t1 in teams for d in days for (c,v1,v2,v3,v4) in tripElements[t1]} +tripToCluster = {(t1,r,c) : pulp.LpVariable('tripToCluster_'+str(t1)+'_'+str(r)+'_'+str(c), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in rounds for c in clusters if not c in t_clusters[t1] and r= 1 and i==j : + patterns[2*j-1,i]=1-patterns[2*j-1,i] + patterns[2*j,i]= 1-patterns[2*j-1,i] + + forbiddenPatterns=[] + if use2BreakPatterns : + p_cntr=numPatterns + for b1 in rounds1: + for b2 in rounds1: + if b1+2<=b2 and b1>1 and b2-b11: + np = 1-patterns[p_cntr-1,r-1] + if r in [b1,b2]: + np=1-np + patterns[p_cntr-1,r]=np + patterns[p_cntr,r]=1-np + patternRange.append(p_cntr-1) + patternRange.append(p_cntr) + + for p in [p_cntr-1 ,p_cntr]: + p_nhome_games = sum ([patterns[p,d] for d in rounds1 ]) + print ( " - ", 100+p , " :" ,end="") + for d in rounds1: + print (patterns[p,d] , end="" ) + if p_nhome_games < nTeams/2-1 or p_nhome_games > nTeams/2 : + print (" ** BAD ** ", end="") + forbiddenPatterns.append(p) + print (" sum : " , sum ([patterns[p,d] for d in rounds1 ])) + + forbiddenStarterPatterns = [ p for p in patternRange if not thisSeason.startWithBreakAllowed and patterns[p,1]==patterns[p,2]] + forbiddenEndPatterns = [ p for p in patternRange if not thisSeason.endWithBreakAllowed and patterns[p,nRounds1-1]==patterns[p,nRounds1]] + forbiddenPatterns+=forbiddenStarterPatterns+forbiddenEndPatterns + patternRange=[p for p in patternRange if p not in forbiddenPatterns] + print (forbiddenPatterns) + print (patternRange) + print ("getPhaseOfRound",getPhaseOfRound) + print ("getBasicRound",getBasicRound) + + assignPattern2={(p,t1,ph) : pulp.LpVariable('assignPattern2_'+str(t1)+'_'+str(p)+'_'+str(ph), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for p in patternRange for t1 in realteams for ph in phases} + usePattern2={(p,ph) : pulp.LpVariable('usePat2_'+str(p) + '_' + str(ph), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for p in patternRange for ph in phases} + for ph in phases: + if runMode == 'New': + for p in patternRange: + model2+= usePattern2[(p,ph)]== sum([ assignPattern2[p,t,ph] for t in realteams]) + if p%2==0: + model2+= usePattern2[(p,ph)]==usePattern2[p-1,ph] + # print ("complementaries " , p, p-1) + for t in realteams: + # every team one pattern + model2 += lpSum( assignPattern2[(p,t,ph)] for p in patternRange)==1 , "one_pattern_for_2_%s_%s"%(t,ph) + + # for r in rounds: + # if r==1 or getBasicRound[r-1] != getBasicRound[r]: + # model2 += lpSum( homeInRound[(t,r2)] for r2 in rounds if getBasicRound[r2]==getBasicRound[r]) == lpSum(assignPattern2[(p,t,getPhaseOfRound[r])] * patterns[p,r-getPhaseOfRound[r]*nRounds1] for p in patternRange) + + for r in basicRounds: + model2 += lpSum( homeInRound[(t,r2)] for r2 in getRealRounds[r]) == lpSum(assignPattern2[(p,t,getPhaseOfBasicRound[r])] * patterns[p,r-getPhaseOfBasicRound[r]*nRounds1] for p in patternRange) + +# for cs in currentSolution: + # setLB(x[(cs[0],cs[1],(cs[2],cs[3]))],1) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=20,msg=1)) + +for t in higherTeams : + for d in days+higherLeagueDayIds: + away[t,d] = 0 + home[t,d] = 0 + if thisSeason.useFeatureKickOffTime: + for tm in times: + away_time[t,d,tm] = 0 + home_time[t,d,tm] = 0 + +print ("READING UPPER GAMES", higherLeagueDayIds) + +for (dy,t1,t2,rd,tm) in higherGames: + dy2 = getDayByDateTime[getDateTimeDay[dy]] + home[t1,dy2] = 1 + away[t2,dy2] = 1 + if tm in getIdByTime.keys(): + home_time[t1,dy2,getIdByTime[tm]] = 1 + away_time[t2,dy2,getIdByTime[tm]] = 1 + +for t in realteams: + for d in higherLeagueDayIds: + away[t,d] = 0 + home[t,d] = 0 + if thisSeason.useFeatureKickOffTime: + for tm in times: + away_time[t,d,tm] = 0 + home_time[t,d,tm] = 0 + + for d in days: + away[t,d] = lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('away'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) + home[t,d] = lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('home'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) + if thisSeason.useFeatureKickOffTime: + for t2 in opponents[t]: + # print (t,t2,getTeamById[t], getTeamById[t2], (t,t2) in games ) + # if (t,t2) in games: + for rd in getRoundDaysByDay[d]: + # TODO: HOTFIX + if (x[(t2,t,rd)] != 0): + model2+= x[(t2,t,rd)] == lpSum([ x_time[(t2,t,rd,tm)] for tm in times]) + for tm in times: + away_time[t,d,tm] = lpSum([x_time[(t2,t,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ]) + home_time[t,d,tm] = lpSum([x_time[(t,t2,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ]) + + for c in clusters: + away_in_cluster_day[t,d,c] = lpSum([x[(t2,t,rd)] for t2 in cluster_teams[c] for rd in getRoundDaysByDay[d] if t2 in opponents[t]]) + +for t in realteams: + tms = [t] + higherTeamsOf[t] + print (tms) + for d in days+higherLeagueDayIds : + model2 += home[(t,d)]+away[(t,d)] <= 1 + if len(higherTeamsOf[t])>0 and d in upperAndLowerLeagueIds: + model2 += lpSum([ home[(t1,d)]+away[(t1,d)] for t1 in tms ]) <= 1 + gamesTooClose[t,d] + + if conflictDays[(t,d)]: + # print (t,getNiceDay[d],conflictDays[(t,d)]) + # for d2 in conflictDays[(t,d)]: + # if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')] ): + # print (" - " ,getNiceDay[d2] ,getDateTimeDay[d2]-getDateTimeDay[d] , datetime.timedelta(days=minRest[(t,'A','A')]) ) + # model2 += lpSum([home[(t,d2)]+away[(t,d2)] for d2 in conflictDays[(t,d)]]) <= 1+ gamesTooClose[t,d] + # if len(tms)>1: + # print (lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','H')] )] )) + model2 += lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','H')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ away[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','A')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ away[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ away[(t1,d)] for t1 in tms ]) +lpSum([ away[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','A')] )] ) <= 1+ gamesTooClose[t,d] +# model2 += home[(t,d)]+lpSum([ away[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','A')]) ] ) <= 1+ gamesTooClose[t,d] +# model2 += away[(t,d)]+lpSum([ home[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')]) ] ) <= 1+ gamesTooClose[t,d] +# model2 += away[(t,d)]+lpSum([ away[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','A')]) ] ) <= 1+ gamesTooClose[t,d] + + +for t in realteams: + for r in rounds: + # awayInRound[t,r] = lpSum([x[(t2,t,dr)] for t2 in teams for dr in getRoundDaysByRound[r]]) + # homeInRound[t,r] = lpSum([x[(t,t2,dr)] for t2 in teams for dr in getRoundDaysByRound[r]]) + + if thisSeason.gamesPerRound=="one day": + # model2 += homeInRound[(t,r)] == lpSum([home[(t,d)] for (r2,d) in getRoundDaysByRound[r]]) + model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]]) + model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]]) + else: + model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,getRoundDaysByRound[r][0])] for t2 in opponents[t] ]) + model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,getRoundDaysByRound[r][0])] for t2 in opponents[t] ]) + + model2 += homeInRound[(t,r)] + awayInRound[(t,r)] <= 1 + if r>=3: + model2 += break3InRound[(t,r)] +2>= homeInRound[(t,r-2)]+homeInRound[(t,r-1)]+homeInRound[(t,r)] + model2 += break3InRound[(t,r)] +2>= awayInRound[(t,r-2)]+awayInRound[(t,r-1)]+awayInRound[(t,r)] + + if r>1: + if thisSeason.gamesPerRound=="one day": + model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in (getRoundDaysByRound[r-1]+getRoundDaysByRound[r]) if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)] + else: + model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in [getRoundDaysByRound[r-1][0],getRoundDaysByRound[r][0]] if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)] + + for c in clusters: + if thisSeason.gamesPerRound=="one day": + away_in_cluster[t,r,c] = lpSum([x[(t2,t,rd)] for t2 in cluster_teams[c] for rd in getRoundDaysByRound[r] if t2 in opponents[t] ]) + else: + away_in_cluster[t,r,c] = lpSum([x[(t2,t,getRoundDaysByRound[r][0])] for t2 in cluster_teams[c] if t2 in opponents[t] ]) + + +print ("fixedGamesRounds:" ,fixedGamesRounds) +for (t1,t2,r) in fixedGamesRounds: + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[r] ] )==1 + print ("fix ", t1,t2,r, lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[r]] )) + + +if sharedStadiums: + useStadiumTimeSlot = { (t,d,s) : pulp.LpVariable('useStadiumTimeSlot_'+str(t)+"_"+str(d)+"_"+str(s) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous ) for (t,d) in t_site_bestTimeSlots.keys() for (p,s) in t_site_bestTimeSlots[(t,d)]} + nonIceGame = { (t,d) : pulp.LpVariable('nonIceGame_'+str(t)+"_"+str(d) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous ) for (t,d) in home.keys()} + for (t,d) in t_site_bestTimeSlots.keys(): + if len(t_site_bestTimeSlots[t,d])==0 : + model2 += home[(t,d)] == 0 + nonIceGame[(t,d)] + else: + model2 += home[(t,d)] == lpSum([useStadiumTimeSlot[(t,d,s)] for (p,s) in t_site_bestTimeSlots[t,d]])+ nonIceGame[(t,d)] + if "AllowGamesWithoutIce" not in special_wishes_active: + model2 += nonIceGame[(t,d)] == 0 + + getStadiumTimeSlotsPerDay={ (st.id,d) : [] for st in theseStadiums for d in days } + # for s in sorted(getStadiumTimeSlot.keys()): + # print (s,getStadiumTimeSlot[s]) + for (t,d,s) in useStadiumTimeSlot.keys(): + getStadiumTimeSlotsPerDay[getStadiumTimeSlot[s]['stadium_id'],d].append((t,s)) + # print (t,d,s) + # print (getStadiumTimeSlot[s]) + + for st in theseStadiums: + for d in days : + if len(getStadiumTimeSlotsPerDay[st.id,d])>1: + # print ("CONFLICTS?",st, getNiceDay[d], getStadiumTimeSlotsPerDay[st.id,d]) + conflicts_all = [ sorted(list(incompatible_timslots[s])) for t,s in getStadiumTimeSlotsPerDay[st.id,d]] + conflicts =[] + for c in conflicts_all: + if c not in conflicts: + conflicts.append(c) + print ("conficts ", conflicts) + for c in conflicts: + conflicting_slots = [useStadiumTimeSlot[(t,d,s)] for t,s in getStadiumTimeSlotsPerDay[st.id,d] if s in c ] + # print ("not at same time " ,conflicting_slots) + if len(conflicting_slots)>1: + model2 += lpSum(conflicting_slots) <=1 + print ( "############## constraint built ") + +# cntr =0 +# for t in teams: +# for r in rounds: +# for c in real_clusters: +# if r>1 and runMode=='Improve': +# if gew['Trips']>0 and t_cluster[t]!=c \ +# and parse(getNiceDay[earliestDay[r]])-parse(getNiceDay[latestDay[r-1]])<=datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1): +# model2 += tripToCluster[(t,r-1,c)] <= away_in_cluster[t,r-1,c] +# model2 += tripToCluster[(t,r-1,c)] <= away_in_cluster[t,r,c] +# tripToCluster[(t,r-1,c)].upBound=1 +# # print ('considering trip of ' , getTeamById[t], ' in round ' , r , ' to cluster ' , c ) +# cntr +=2 +# else: +# model2 += tripToCluster[(t,r-1,c)] == 0 + +# if r==nRounds : +# model2 += tripToCluster[(t,r,c)] == 0 +# print ("built ", cntr , " trip constraints") + +if thisSeason.useFeatureKickOffTime: + for t in realteams: + for d in onlyEarlyDays: + model2+= home_time[t,d,getIdByTime["Late"]]==0 + model2+= away_time[t,d,getIdByTime["Late"]]==0 + for d in onlyLateDays: + model2+= home_time[t,d,getIdByTime["Early"]]==0 + model2+= away_time[t,d,getIdByTime["Early"]]==0 + + +breakVioBalance = { t : pulp.LpVariable('breakVioBalance_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in realteams } +numBreaks = { t : lpSum([ breakVio[(bl['id'],t)] for bl in breaks ]) for t in realteams } + + +succBreaks = [ bl for bl in breaks if bl['round1']+1==bl['round2'] ] + +if thisSeason.forbidDoubleBreaks: + for bl1 in succBreaks: + for bl2 in succBreaks: + if bl1['round2']+1==bl2['round1']: + for t in realteams: + model2+= homeInRound[t,bl1['round1']] + homeInRound[t,bl1['round2']] + awayInRound[t,bl2['round1']] + awayInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] + model2+= awayInRound[t,bl1['round1']] + awayInRound[t,bl1['round2']] + homeInRound[t,bl2['round1']] + homeInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] + +for t in realteams: + for bl in breaks: + # print (getTeamById[t], realteams, getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ) + # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t,t2,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ]) + # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t2,t,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ]) + model2+= breakVio[(bl['id'],t)] + 1 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']] + model2+= breakVio[(bl['id'],t)] + 1 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']] + if bl['round2'] + 1 <= nRounds: + model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']] + homeInRound[t,bl['round2']+1] + model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']] + awayInRound[t,bl['round2']+1] + + if balanceBreaks: + model2+= numBreaks[t] <= lpSum([ numBreaks[t2] for t2 in realteams])/len(realteams) + breakVioBalance[t] + for t2 in teams: + if half_symmetry_offset==0: + model2+= numBreaks[t2] <= 4 + model2+= numBreaks[t2] >= 3 + else: + model2+= numBreaks[t2] <= 5 + model2+= numBreaks[t2] >= 1 + print ("balancing Breaks ") + + for bl in breaks: + model2+= breakVio[(bl['id'],t)] <= 2 - homeInRound[t,bl['round1']] - awayInRound[t,bl['round2']] + model2+= breakVio[(bl['id'],t)] <= 2 - awayInRound[t,bl['round1']] - homeInRound[t,bl['round2']] + + + + if not evalRun: + if not thisSeason.minBreaks and len(breaks)>0 and not balanceBreaks: + for p in phases: + # print ("Don't allow breaks for ",t, " in phase ", p) + model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks if getPhaseOfRound[bl['round1']]==p and getPhaseOfRound[bl['round2']]==p]) <= thisSeason.initBreaks + + if mathModelName in [ "Ligue 1"]: + model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks ]) <= 4 + + if not thisSeason.startWithBreakAllowed: + model2+= homeInRound[t,1] + homeInRound[t,2] <= 1 + model2+= awayInRound[t,1] + awayInRound[t,2] <= 1 + if not thisSeason.endWithBreakAllowed: + print ("NO BREAK FOR " , t , " in rounds " ,nRounds-1, nRounds) + model2+= homeInRound[t,nRounds-1] + homeInRound[t,nRounds] <= 1 + model2+= awayInRound[t,nRounds-1] + awayInRound[t,nRounds] <= 1 + +# print ('\n') +# for p in phases: +# for d in getDaysOfPhase[p] : +# print (p,d, getRoundDaysByDay[d]) + +# return '' + +if len (fixedGamesRounds)==0: + for br in basicRounds: + imp_rds= getRoundDaysByBasicRound[br] if thisSeason.gamesPerRound=="one day" else [getRoundDaysByBasicRound[br][0]] + # print (br, imp_rds) + for t1 in realteams: + model2 += homeInBasicRound[(t1,br)] == lpSum([x[(t1,t2,rd)] for t2 in opponents[t1] for rd in imp_rds]) + model2 += awayInBasicRound[(t1,br)] == lpSum([x[(t2,t1,rd)] for t2 in opponents[t1] for rd in imp_rds]) + for t2 in opponents[t1]: + if (t1,t2) in games: + model2 += gameInBasicRound[(t1,t2,br)] == lpSum([ x[(t1,t2,rd)] for rd in imp_rds ]) + + # print ("§§§",t1,t2,br,gameInBasicRound[(t1,t2,br)].upBound ) + +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.3, TimeLimit=180,msg=1)) + + + +getDays[0] =[] + +# return 2 +for d in days+higherLeagueDayIds: + for stadium in stadiums: + factor = 0.001 if len(teamsOfStadium[stadium])==2 else 1.0 + # print (stadium, teamsOfStadium[stadium] , len(teamsOfStadium[stadium])) + model2 += lpSum([home[t,d] for t in teamsOfStadium[stadium]]) <= 1 + factor*tooManyHomesInStadium[(stadium,d)] + +if thisSeason.useFeaturePairings: + for pair in pairings: + # print (pair) + pDaysSets = [] + for d in days+higherLeagueDayIds: + pTeams = [pair['team1_id'], pair['team2_id']] + pDays = [d] + if pair['dist'] ==1 and nextDay[d]!=-1: + pDays.append(nextDay[d]) + if pair['dist'] in [3,7]: + pDays=getDays[getRoundByDay[d]] + getHigherDaysByRound[getRoundByDay[d]] + # print ( getNiceDay[d], getRoundByDay[d], getDays[getRoundByDay[d]] , " +" ,getHigherDaysByRound[getRoundByDay[d]]) + factor = 1.0 + if len(pDays)>0 and pDays not in pDaysSets: + pDaysSets.append(pDays.copy()) + # print ("+++ ") + # else: + # print ("--- ") + + # if pair['prio'] =='A': + # factor = 0.1 + for pDays in pDaysSets: + d= pDays[0] + print ("Treating day set starting with day " , getNiceDay[d], " ", pDays) + if pair['dist'] in [2,6] and thisSeason.useFeatureKickOffTime: + for tm in times: + if pair['type']== "Home": + if pair['dist']<=3: + model2 += lpSum([home_time[t,d,tm] for t in pTeams]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += home_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + model2 += home_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + else: + if pair['dist']<=3: + model2 += lpSum([home_time[t,d,tm] + away_time[t,d,tm] for t in pTeams]) <= 1 + pairingVio[(pair['id'],d)] + else: + model2 += home_time[pair['team1_id'],d,tm] + away_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] - away_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + model2 += home_time[pair['team2_id'],d,tm] + away_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] - away_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + + else: + if pair['type']== "Home": + if pair['dist']<=3: + model2 += lpSum([home[t,dd] for t in pTeams for dd in pDays]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += lpSum([home[pair['team1_id'],dd] - home[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + model2 += lpSum([home[pair['team2_id'],dd] - home[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + else: + if pair['dist']<=3: + model2 += lpSum([home[t,dd] + away[t,dd] for t in pTeams for dd in pDays]) <= 1 + pairingVio[(pair['id'],d)] + else: + model2 += lpSum([home[pair['team1_id'],dd] + away[pair['team1_id'],dd] - home[pair['team2_id'],dd] - away[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + model2 += lpSum([home[pair['team2_id'],dd] + away[pair['team2_id'],dd] - home[pair['team1_id'],dd] - away[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + + +# print ("fixedGames:" ,fixedGames) +for (t1,t2,d) in fixedGames: + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + # if not thisSeason.useFeatureKickOffTime or not currentKickoffTimes[(t1,t2,d)] : + # model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + # else: + # model2+= lpSum ( [x_time[(t1,t2,rd,currentKickoffTimes[(t1,t2,d)])] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + if not thisScenario.allowFixedVio: + model2+= fixedGameVio[(t1,t2,d)]==0 + print ("HARD FIXING TO ROUND", getRoundByDay[d], t1,t2,d ) + else: + print ("SOFT FIXING TO ROUND", getRoundByDay[d], t1,t2,d ) + +for (t1,t2,d) in fixedGames2: + if not thisSeason.useFeatureKickOffTime or not currentKickoffTimes[(t1,t2,d)] : + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByDay[d]] )== 1 - fixedGame2Vio[(t1,t2,d)] + else: + model2+= lpSum ( [x_time[(t1,t2,rd,currentKickoffTimes[(t1,t2,d)])] for rd in getRoundDaysByDay[d]] )== 1 - fixedGame2Vio[(t1,t2,d)] + if not thisScenario.allowFixedVio: + model2+= fixedGame2Vio[(t1,t2,d)]==0 + print ("HARD FIXING " , t1,t2,d , (t1,t2,d) in fixedGameVio.keys(), getRoundDaysByDay[d] , getRoundByDay[d] ) + else: + print ("SOFT FIXING " , t1,t2,d , (t1,t2,d) in fixedGameVio.keys(), getRoundDaysByDay[d] ) + +# return '' +# use_currentSolution= False +# for (t1,t2,d) in fixedRoundGames: +# # if thisScenario.allowFixedVio: +# # model2 += lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]]] )==1 - fixedRoundGameVio[(t1,t2,d)] +# # else: +# model2 += lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]]] )==1 + +# # TESTING + + +use_currentSolution= False + +currentGameCntr = { (t1,t2) :0 for t1 in teams for t2 in teams } + +if runMode=='Improve' and len(currentSolution) > len(fixedGames) + len(fixedGames2 ) : + use_currentSolution= True + +# if use_currentSolution and len(currentSolution) !=len(fixedGames): + +teamGameCntr = { (t,r) :0 for t in teams for r in rounds } + +if use_currentSolution: + for cs in currentSolution: + if len(cs)>=4: + t1=cs[0] + t2=cs[1] + r=cs[2] + d=cs[3] + if (t1,t2,(r,d)) in x.keys(): + currentGameCntr[(t1,t2)] +=1 + setLB(x[(t1,t2,(r,d))],1) + teamGameCntr[t1,r]+=1 + teamGameCntr[t2,r]+=1 + # if t1==16770: + # print ("fixing " , t1 , t2, r ,d) + +# for (t,r) in teamGameCntr.keys(): +# if teamGameCntr[(t,r)]!=1: +# print (getTeamById[t], " plays " ,teamGameCntr[(t,r)] , " games in round ", r ) + + +# sing for 16770 16766 0 2 + +# for ttr in x.keys(): +# makeIntVar(x[ttr]) + + +# if len(currentSolution) ==len(fixedGames): +# print ("alles fixieren") + +# print ("TESTING") +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) +# print ("TESTING DONE") + + + +# print ("days " , days) +for d in days: + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games for rd in getRoundDaysByDay[d] if distance[getTeamById[t1],getTeamById[t2]]<=thisSeason.maxDistanceDerby ]) >= nDerbies[d] - derbyMissing[d] + minG = sum([ roundDaysMin[rd] - deficientGames[rd] for rd in getRoundDaysByDay[d]]) + maxG = sum([ roundDaysMax[rd] + excessGames[rd] for rd in getRoundDaysByDay[d]]) + # print (getNiceDay[d] , maxG) + # if minG<3 : + if minG>0 : + model2 += lpSum([ home[(t,d)] for t in realteams]) >= minG + model2 += lpSum([ home[(t,d)] for t in realteams]) <= maxG + + if len(getRoundDaysByDay[d])>1: + for rd in getRoundDaysByDay[d]: + if roundDaysMin[rd]>0: + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] - deficientGames[rd] + # model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] + print ("At least ",roundDaysMin[rd] , " on " , rd) + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) <= roundDaysMax[rd] + excessGames[rd] + +print ("rounds " , rounds, nRounds) +for r in rounds: + # one game a round for everyone + for t1 in realteams: + if thisSeason.gamesPerRound=="one day": + cnstr = lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1] for rd in getRoundDaysByRound[r]] ) + if len(cnstr) > 0: + model2 += cnstr <= 1 + else: + print ( getRoundDaysByRound[r], opponents[t1]) + if len(getRoundDaysByRound[r])>0: + for rd in getRoundDaysByRound[r]: + if rd==getRoundDaysByRound[r][0]: + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1]] ) <= 1 + else: + for t2 in opponents[t1]: + model2 += x[(t1,t2,rd)] == x[(t1,t2,getRoundDaysByRound[r][0])] + # print ("linking ", getRoundDaysByRound[r][0] , " to ", rd , " game " , t1, t2) + if len(getRoundDaysByRound[r])>1 and False: + for rd in getRoundDaysByRound[r]: + for t1 in realteams: + model2 += lpSum([home[t1,rd[1]] + away[t1,rd[1]]] ) <= 1 + +# exit(0) +print ("realteams " , realteams) + +print ("taking care of right numbers of games ... " ) +cntr= 0 + +if not specialGameControl: + for (t1,t2) in realgames: + if undirectedGameCntr[(t1,t2)]==0: + model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)] - missingGamesVio[(t1,t2)] + else: + # model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - missingGamesVio[(t1,t2)] + model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] for rd in roundDays] ) + model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) <= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] + + missingGamesVio[(t1,t2)].upBound= max(0,gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - currentGameCntr[(t1,t2)]) + + # print (t1,t2,gameCntr[(t1,t2)], currentGameCntr[(t1,t2)] , lpSum([x[(t1,t2,rd)] for rd in roundDays] ) ) + +if thisSeason.useFeatureOpponentMatrix and False: + gameCntr = { (t1,t2) : 0 for t1 in teams for t2 in teams } + for gm in gameRequirements: + gameCntr[(gm.team1.id,gm.team2.id)] = gm.number + if gm.number>0: + # print ("++++ " ,gm) + if gm.number <= 0.5*nPhases or currentGameCntr[(gm.team1.id,gm.team2.id)]==gm.number: + model2 += lpSum([x[(gm.team1.id,gm.team2.id,rd)] for rd in roundDays] ) == gm.number + # print ("EQU " , gm) + else: + model2 += lpSum([x[(gm.team1.id,gm.team2.id,rd)] for rd in roundDays] ) == gm.number - missingGamesVio[(gm.team1.id,gm.team2.id)] + # print ("MAX " , gm) + model2 += missingGamesVio[(gm.team1.id,gm.team2.id)] <= gm.number -0.5*nPhases + print ("games still missing for " , gm.team1.id, gm.team2.id, currentGameCntr[(gm.team1.id,gm.team2.id)] , gm.number) + +print ("... done" ) + +# print (missingGamesVio.keys()) + + +if not evalRun: + for (t1,t2) in realgames: + # every pair plays each other in each phase once + for p in phases: + if p0 and noPlayRounds[t1]==noPlayRounds[t2]: + relDays = [] + phaseLength= int(len(playRounds[t1])/nPhases+0.5) + for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: + relDays+=getDays[rr] + print ("adding days of round ", rr , " to phase ", p) + else: + relDays = getDaysOfPhase[p] + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ) <= 1 + # print (len(relDays),"reldays") + # print (getTeamById[t1],getTeamById[t2], sum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] )) + +if not evalRun : + for t in realteams: + if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases: + for p in phases: + rds = [r for r in rounds if getPhaseOfRound[r]==p ] + nblocked =[ r for r in noHomeRounds[t] if r in rds] + print (p, len(rds), len(nblocked), rds,nblocked, getTeamById[t]) + for p in phases: + phaseLength= int(len(playRounds[t])/nPhases+0.5) + model2 += lpSum([ home[(t,d)] for d in getDaysOfPhase[p] ] ) <= int(phaseLength/2+1) + + if thisSeason.minRoundsBetweenGameOfSameTeams>0: + for r in rounds: + if r +thisSeason.minRoundsBetweenGameOfSameTeams <=nRounds: + rds = [ ] + for r2 in range(r,r+thisSeason.minRoundsBetweenGameOfSameTeams+1): + rds+=getRoundDaysByRound[r2] + rds2 = [ ] + for r2 in range(r,r+int (0.5*thisSeason.minRoundsBetweenGameOfSameTeams)): + rds2+=getRoundDaysByRound[r2] + # print ("ONLY ONE IN " , rds) + # print ("ONLY ONE IN " , rds2) + for t1 in realteams: + for t2 in realteams: + if t10 or x_round[(t1,t2,r)]!=0) : + # model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r] + model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + + +if thisSeason.lastRoundSync: + for g in displayGroups.keys(): + affectedRounds = set([r for (r,d) in finalRoundDays]) + print ("affectedRounds", affectedRounds) + if thisSeason.useFeatureKickOffTime: + print ("sync time ", g, " " , displayGroups[g], " " , finalRoundDays, " ", times) + for r in affectedRounds: + model2 += lpSum([lastRoundPlayed[(g,rd,tm)] for rd in finalRoundDays for tm in times if rd[0]==r ]) == 1 + # model2 += lpSum([lastRoundPlayed[(g,rd,tm)] for rd in finalRoundDays for tm in times]) == len(affectedRounds) + for t in displayGroups[g]: + for rd in finalRoundDays: + for tm in times: + model2+= home_time[t,rd[1],tm]+ away_time[t,rd[1],tm] <= lastRoundPlayed[(g,rd,tm)] + else: + # print ("sync time", g, " " , displayGroups[g], " " , finalRoundDays) + for r in affectedRounds: + model2 += lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays if rd[0]==r ]) == 1 + # model2 += lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays ]) == len(affectedRounds) + print (lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays ]) , " ==" , len(affectedRounds)) + for t in displayGroups[g]: + for rd in finalRoundDays: + model2+= home[(t,rd[1])] + away[(t,rd[1])] <= lastRoundPlayed[(g,rd)] + # print ("home[(",t,rd[1],")] + away[(",t,rd[1],")] <= lastRoundPlayed[(",g,rd,")])") + + +if not evalRun: + # max length home stands /trips + for r in range (1,nRounds-thisSeason.maxTourLength+1): + # print (" at least one home and away in rounds ") + # for r3 in range(r,r+thisSeason.maxTourLength+1): + # print (r3 ) + for t in teams: + if t not in noBreakLimitTeams: + # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1 + model2 += lpSum([awayInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength + model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength + +print ("check 1") + + # blockings +for bl in blockings: + if getDayById[bl['day_id']]['round'] !=0: + if thisSeason.useFeatureKickOffTime and bl['time']!='----': + if bl['type'] in ["Home", "Hide"]: + model2+= blockingVio[bl['id']]== home_time[bl['team_id'], bl['day_id'],bl['time']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2+= blockingVio[bl['id']]== away_time[bl['team_id'], bl['day_id'],bl['time']] + # print ('FOUND AWAY BLOCKING ', bl) + else: + if bl['type'] in ["Home", "Hide"]: + model2+= blockingVio[bl['id']]== home[bl['team_id'], bl['day_id']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2+= blockingVio[bl['id']]== away[bl['team_id'], bl['day_id']] + # print ('FOUND AWAY BLOCKING ', bl) + + +print ("check 2") + +hawOneVio={} +hawForOneNotViolated={} + +def getStringFromSet(ss): + s2 ="" + for s in ss: + s2+=str(s)+"_" + return s2[:-1] + + # hawishes +for haw in hawishes: + print (haw['reason']) + for el in elemHaWishes[haw['id']]: + if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']])>0: + if haw['homeAway']=='Home': + relGames = lpSum([home_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + # print (haw['id'] ," haw : ", relGames, hawTimes[haw['id']]) + elif haw['homeAway']=='Away': + relGames = lpSum([away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + else : + # print(haw,el) + relGames = lpSum([home_time[t,d,tm] + away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \ + - lpSum([x_time[(t1,t2,rd,tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd,tm) in x_time.keys()]) + + else: + if haw['homeAway']=='Home': + relGames = lpSum([home[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ]) + elif haw['homeAway']=='Away': + relGames = lpSum([away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ]) + else : + relGames = lpSum([home[t,d] + away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1,t2,rd] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd) in x.keys() ]) + if haw['minGames'] >0 : + model2+= relGames >= haw['minGames'] - HawVioTooLess[el] + # print ("adding min ha constraint") + if haw['maxGames'] >=0 : + model2+= relGames <= haw['maxGames'] + HawVioTooMuch[el] + # print ("adding max ha constraint") + + + usedConstraintNames = [""] + if haw['forOneDay']: + # print (haw['forOneDay'], hawDays[haw['id']]) + # print ([el for el in elemHaWishes[haw['id']] ]) + # for el in elemHaWishes[haw['id']] : + # print("- " , elemHaWishDays[el] , elemHaWishTeams[el] ) + # print ([ (el, elemHaWishFirstDay[el]) for el in elemHaWishes[haw['id']] ]) + relTeamString = { el : getStringFromSet(elemHaWishTeams[el]) for el in elemHaWishes[haw['id']] } + relTeams = set([ relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print (relTeams) + for rt in relTeams: + rtname = rt + if len(rt)>50 : + scname="" + while scname in usedConstraintNames: + ttt = bytes(rt+ ''.join(random.choice(string.ascii_lowercase) for i in range(10)), 'utf-8') + scname = hashlib.sha224(ttt).hexdigest() + rtname = scname[:10] + usedConstraintNames.append(rtname) + # print ("use rtname to encode wishes ", rtname) + + hawOneVio[(haw['id'], rt)]= pulp.LpVariable('hawOneVio_'+str(haw['id'])+"_"+rtname, cat=pulp.LpContinuous) + for fd in hawDays[haw['id']]: + relWishes = [el for el in elemHaWishes[haw['id']] if elemHaWishFirstDay[el]== fd] + # relWishes = [el for el in elemHaWishes[haw['id']] ] + # print (" -" , relWishes , [elemHaWishDays[el] for el in relWishes]) + hawForOneNotViolated[(haw['id'], rt, fd)]= 0 + if len(relWishes)>0: + hawForOneNotViolated[(haw['id'], rt, fd)]= pulp.LpVariable('hawForOneViolated_'+str(haw['id'])+"_"+rtname+"_"+str(fd), cat=pulp.LpBinary) + model2 += lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el]==rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'],rt, fd)]) + model2 += lpSum( hawForOneNotViolated[(haw['id'], rt, fd)] for fd in hawDays[haw['id']] ) >= haw['forOneDayNum']-hawOneVio[(haw['id'],rt)] + model2 += lpSum( hawOneVio[(haw['id'], rt)] for rt in relTeams ) == hawVio[haw['id']] + else: + # print (haw['forOneDay'], hawDays[haw['id']]) + model2 += hawVio[haw['id']]== lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in elemHaWishes[haw['id']]) + + + if haw['prio']=="Hard" and "HardHAWishesNotBreakable" in special_wishes_active: + model2+= hawVio[haw['id']] ==0 + print ("WISH HARD" ,haw['reason']) + + +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +# print (hawDays) +# print (elemHaWishFirstDay) + +encOneVio={} +encForOneNotViolated={} +print ("check 3") +# encwishes +for enc in encwishes: + print (enc) + for el in elemEncWishes[enc['id']]: + # print (enc) + # model2+= encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']] + + if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']])>0: + if enc['minGames'] >0 : + model2+= encVioTooLess[el] >= enc['minGames'] - sum([ x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ]) + if enc['maxGames'] >=0 : + # if enc['maxGames']==0: + print (enc['reason'], ' ', elemEncWishDays[el], ' ',enc['time'], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + # print (sum([x_time[(t1,t2,rd, enc['time'])] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])) + model2+= encVioTooMuch[el] >= -enc['maxGames'] + sum([x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ]) + else: + if enc['minGames'] >0 : + model2+= encVioTooLess[el] >= enc['minGames'] - sum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ]) + if enc['maxGames'] >=0 : + # if enc['maxGames']==0: + # print (enc['reason'], ' ', encDays[enc['id']], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + model2+= encVioTooMuch[el] >= -enc['maxGames'] + sum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ]) + + if enc['forOneDay']: + for ed in encDaySets[enc['id']]: + if len(ed)>0: + relWishes = [ el for el in elemEncWishes[enc['id']] if elemEncWishDays[el] == ed ] + print ("###",ed, relWishes) + encForOneNotViolated[(enc['id'], ed[0])]= pulp.LpVariable('encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=pulp.LpBinary) + model2 += sum( encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * (1-encForOneNotViolated[(enc['id'], ed[0])]) + model2 += sum( encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len(ed)>0 ) >= enc['forOneDayNum']-encVio[enc['id']] + else: + model2 += encVio[enc['id']]== sum( encVioTooMuch[el]+encVioTooLess[el] for el in elemEncWishes[enc['id']]) + + + if enc['prio']=="Hard" and "HardEncWishesNotBreakable" in special_wishes_active: + model2+= encVio[enc['id']] ==0 + print ("WISH HARD" ,enc['reason']) + + +print ("check 4") + + +for cf in conferencewishes: + print (confTeams[cf['conference_id']]) + model2 += lpSum([x[t1,t2, rd] for t1 in confTeams[cf['conference_id']] for t2 in confTeams[cf['conference_id']] for rd in getRoundDaysByDay[cf['day_id']] if (t1,t2, rd) in x.keys() ]) <= cf['maxGames'] + confVio[cf['id']] + model2 += lpSum([x[t1,t2, rd] for t1 in confTeams[cf['conference_id']] for t2 in confTeams[cf['conference_id']] for rd in getRoundDaysByDay[cf['day_id']] if (t1,t2, rd) in x.keys() ]) >= cf['minGames'] - confVio[cf['id']] + print (cf , " : " , confTeams[cf['conference_id']]) + if thisSeason.groupBased: + model2+= confVio[cf['id']]==0 + +b_slots={(b.id,r) : [] for b in broadcastingwishes for r in rounds} +if thisSeason.useFeatureKickOffTime: + for b in broadcastingwishes: + b_weekdays = [sl.weekday[:3] for sl in b.slots.all() ] + for r in rounds: + if len( [wd for wd in b_weekdays if wd in getWeekDaysPerRound[r]]) == len(b_weekdays): + b_slots[(b.id,r)] = [ (d,str(sl.timeslot.id),sl.quality) for sl in b.slots.all() for d in getDays[r] if getWeekDay[d]==sl.weekday[:3]] + if len(b_slots[(b.id,r)])>0: + model2 += lpSum([ x_time[g[0][0],g[0][1],(r,d),tm]+x_time[g[0][1],g[0][0],(r,d),tm] for g in topGames for (d,tm,q) in b_slots[(b.id,r)]]) >= b.minGames - broadVioTm[(b.id,r)] + model2 += lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)]]) >= b.minGames - 0.5*broadVioTm[(b.id,r)] + if b.network.id in networkFavTeams.keys() : + model2 += lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)] if t1 in networkFavTeams[b.network.id] or t2 in networkFavTeams[b.network.id] ]) >= b.minGames - 0.1*broadVioTm[(b.id,r)] + # if b.network.name== "Super Sport": + # print (" - " ,r , lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)] if t1 in networkFavTeams[b.network.id] or t2 in networkFavTeams[b.network.id] ])) + +# return "" +weekdayHomePref={} +dayHomePref={} +for t in teams: + tm = getTeamByName[getTeamById[t]] + weekdayHomePref[(t,'Mon')]=tm['home_pref_mo'] + weekdayHomePref[(t,'Tue')]=tm['home_pref_tu'] + weekdayHomePref[(t,'Wed')]=tm['home_pref_we'] + weekdayHomePref[(t,'Thu')]=tm['home_pref_th'] + weekdayHomePref[(t,'Fri')]=tm['home_pref_fr'] + weekdayHomePref[(t,'Sat')]=tm['home_pref_sa'] + weekdayHomePref[(t,'Sun')]=tm['home_pref_su'] + for d in days : + dayHomePref[(t,d)]=weekdayHomePref[(t,getWeekDay[d])] + + +maxTravelDistance = max([ distanceById[t1,t2] for t1 in realteams for t2 in realteams ]) +maxTravelDistance = max(1,maxTravelDistance) + +singleTripWeight=50 +breakImbalanceTotal= lpSum([ breakVioBalance[t] for t in realteams]) +tripSavedTotal2= ( lpSum([ singleTripWeight *tripToSingleTripElement[(t,d,c)] for (t,d,c) in tripToSingleTripElement.keys() ]) + +( lpSum([ c_weight[c] *tripToClusterSaving[(t,c)] *tripToCluster[(t,r,c)] for (t,r,c) in tripToCluster.keys() ]) + +lpSum([ c_weight[c] *tripToClusterSaving[(t,c)] *tripToClusterDaily[(t,d,c)] for (t,d,c) in tripToClusterDaily.keys()]))/maxTravelDistance ) +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +HawVioTotal=lpSum([prioVal[haw['prio']] * hawVio[haw['id']] for haw in hawishes]) +encVioTotal=lpSum([prioVal[enc['prio']] * encVio[enc['id']] for enc in encwishes]) +seedVioTotal=lpSum([100*prioVal[enc['prio']] * encVio[enc['id']] for enc in encwishes if enc['reason']=="Seed Game" ]) +confVioTotal=lpSum([prioVal[conf['prio']] * confVio[conf['id']] for conf in conferencewishes]) +# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes]) +broadVioTotal=lpSum([ 10 * broadVioTm[(b.id,r)] for b in broadcastingwishes for r in rounds]) +breakVioTotal=lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in realteams]) + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in importantteams]) +break3VioTotal=lpSum([2*break3InRound[t,r] for t in teams for r in rounds]) +tooManyTop4InRowTotal=lpSum([ 10*tooManyTop4InRow[(t,r)] for t in teams for r in rounds]) +pairingVioTotal=lpSum([ 5 *prioVal[pair['prio']] * pairingVio[(pair['id'],d)] for pair in pairings for d in days+higherLeagueDayIds]) +# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Home"]) +# print (blockings) +# for bl in blockings: + # if bl['type'] in ["Home"]: + # print (blocked_arena[(bl["team_id"],bl["day_id"],"----")], getTeamById[bl["team_id"]], getNiceDay[bl["day_id"]] , bl ) + +fulfBlocks =set([(bl["team_id"], getRoundByDay[bl["day_id"]]) for bl in blockings if bl['type'] in ["Home"] and blocked_arena[(bl["team_id"],bl["day_id"],"----")]] ) + +blockingVioTotal2=lpSum([ -30 * homeInRound[tr] for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode=='Improve'] ) +blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type'] in ["Home", "Hide"]]) +blockingVioTotal2 + +travelVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Away"]) +gamesTooCloseTotal=lpSum([ 120 * gamesTooClose[(t,d)] for t in teams for d in days+higherLeagueDayIds if conflictDays[(t,d)]]) + lpSum([ 120 * gamesTooClose2[(t,r)] for t in teams for r in rounds ]) +derbiesMissingTotal=lpSum([ 30 * derbyMissing[d] for d in days]) +tooManyHomesInStadiumTotal=lpSum([ 110 * tooManyHomesInStadium[(stadium, d)] for stadium in stadiums for d in days+higherLeagueDayIds]) +unpreferredTotal=lpSum([ home[t, d] for t in teams for d in days if dayHomePref[(t,d)]==0 ]) +# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions]) +competitionVioTotal=lpSum([ 100 * (home[t,d]+ away[t,d]) for (t,d) in competitions.keys()]) +fixedGameVioTotal=lpSum([ 10000 * fixedGameVio[(t1,t2,d)] for (t1,t2,d) in fixedGames]) + lpSum([ 10000 * fixedGame2Vio[(t1,t2,d)] for (t1,t2,d) in fixedGames2]) +missingGamesVioTotal=lpSum([ 2000000 * missingGamesVio[(t1,t2)] for (t1,t2) in realgames]) +# TODO - UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games]) +oldScenGamesTotal=lpSum([ wg * x[t1,t2,rd] for (t1,t2,r,wg) in otherScenGames for rd in getRoundDaysByRound[r]] ) +totalAttendance=lpSum([ attendance[(t1,t2,d)] * x[t1,t2,(r,d)] for (t1,t2) in games for (r,d) in roundDays ] ) + +specialObjectives = 0 +specialWishItems ={ sw:[] for sw in special_wishes_active} +specialWishVio ={} + + +optCameraMovement = "Standard" +if thisLeague.name in ["Indian Super League"] and thisSeason.name != "2021": + optCameraMovement = "TV-Kits" +if thisLeague.name in ["Indian Premier League"]: + optCameraMovement = "Stadiums" + +# tvkitproblem = {} +move2 = {} +newtrip2 = {} +if optCameraMovement == "TV-Kits": + networkIds=networkName.keys() + TV_day_pairs = [ (d1, d2) for d1 in days for d2 in days if getDateTimeDay[d1]+datetime.timedelta(days=1)=getDateTimeDay[d2] -datetime.timedelta(days=20) ] + movements = [ (t1,d1,t2,d2) for t1 in realteams for t2 in realteams for (d1,d2) in TV_day_pairs if getDateTimeDay[d2]-getDateTimeDay[d1] >= datetime.timedelta(days=distanceInDaysById[(t1,t2)]+1 ) ] + + pred2 ={ (t,d) :[] for t in realteams for d in days } + succ2 ={ (t,d) :[] for t in realteams for d in days } + move2 = { (t1,d1,t2,d2) : pulp.LpVariable('move2_'+str(t1)+'_'+str(d1)+'_'+str(t2)+'_'+str(d2), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,d1,t2,d2) in movements } + unserved_tv = { (t1,d1) : pulp.LpVariable('unserved_tv'+str(t1)+'_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in realteams for d1 in days } + newtrip2 = { (t1,d1) : pulp.LpVariable('newtrip2_'+str(t1)+'_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in realteams for d1 in days } + tooManyTrips2 = pulp.LpVariable('tooManyTrips2', lowBound = 0, cat = pulp.LpContinuous) + + for (t1,d1,t2,d2) in movements: + # print (getTeamById[t1] , getTeamById[t2] , distanceById[t1,t2] , distanceInDaysById[t1,t2] , getNiceDay[d1] , getNiceDay[d2] ) + pred2[(t2,d2)].append( (t1,d1,t2,d2)) + succ2[(t1,d1)].append( (t1,d1,t2,d2)) + + # if thisLeague.name in ["Indian Super League"]: + # for (t,d) in newtrip2.keys(): + # # model2+= home[(t,d)] == lpSum( movement[tdtd] for tdtd in pred2[(t,d)] ) + # model2+= home[(t,d)] >= lpSum( move2[tdtd] for tdtd in succ2[(t,d)] ) + # model2+= home[(t,d)] == lpSum( move2[tdtd] for tdtd in pred2[(t,d)] ) + newtrip2 [(t,d)] + unserved_tv[(t,d)] + # model2+= lpSum( move2[tdtd] for tdtd in move2.keys() if tdtd[0] == t and tdtd[1] == d) <= 1 - unserved_tv[(t,d)] + # model2+= lpSum( unserved_tv[k] for k in unserved_tv.keys()) <= 1 + # model2+= tooManyTrips2 <= 0 + # else: + for (t,d) in newtrip2.keys(): + # model2+= home[(t,d)] == lpSum( movement[tdtd] for tdtd in pred2[(t,d)] ) + model2+= home[(t,d)] >= lpSum( move2[tdtd] for tdtd in succ2[(t,d)] ) + model2+= home[(t,d)] == lpSum( move2[tdtd] for tdtd in pred2[(t,d)] ) + newtrip2 [(t,d)] + model2+= home[(t,d)] == lpSum( newtrip2[td] for td in newtrip2.keys()) <= len(networkIds) + tooManyTrips2 + + # model2+= home[(t,d)] <= lpSum(home[(t2,d2)] for t2 in realteams for d2 in otherDays if getDateTimeDay[d2]<=getDateTimeDay[d]- datetime.timedelta(days=distanceInDaysById[(t,t2)]) ) +tvkitproblem[(t,d)] + specialObjectives += 0.01*lpSum( distanceById[t1,t2]*move2[(t1,d1,t2,d2)] for (t1,d1,t2,d2) in movements)+ 5000* tooManyTrips2 + +jamshedpurVio={} +if thisLeague.name in ["Indian Super League"]: + jamshedpur = getTeamByName["Jamshedpur FC"]['id'] + print ("Jamshedpur FC" , jamshedpur) + jamshedpurVio = { d1 : pulp.LpVariable('jamshedpurVio_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for d1 in days } + for d in days: + fourdays = [d2 for d2 in days if getDateTimeDay[d2]>=getDateTimeDay[d] and getDateTimeDay[d2]<=getDateTimeDay[d]+datetime.timedelta(days=3) ] + otherday = [d2 for d2 in fourdays if getDateTimeDay[d2]==getDateTimeDay[d]+datetime.timedelta(days=3) ] + if len(otherday)>0: + otherday=otherday[0] + print (getNiceDay[d], fourdays , getNiceDay[otherday] ) + for t in realteams: + if t!=jamshedpur: + # model2+= lpSum( x[jamshedpur,t, rd] for d2 in fourdays for rd in getRoundDaysByDay[d2] ) + lpSum( home[t,d2] + away[t,d2] for d2 in fourdays ) <= 2 + jamshedpurVio[d] + model2+= lpSum( x[jamshedpur,t, rd] for rd in getRoundDaysByDay[d] ) + home[t,otherday] + away[t,otherday] <= 1 + jamshedpurVio[d] + model2+= lpSum( x[jamshedpur,t, rd] for rd in getRoundDaysByDay[otherday] ) + home[t,d] + away[t,d] <= 1 + jamshedpurVio[d] + else: + model2+= home[t,d] + away[t,otherday] <= 1 + jamshedpurVio[d] + model2+= away[t,d] + home[t,otherday] <= 1 + jamshedpurVio[d] + + specialObjectives += 1000*lpSum( jamshedpurVio[d] for d in days) + + + + +if "AlwaysEnoughRestDays" in special_wishes_active: + model2+=lpSum([ gamesTooClose[(t,d)] for t in teams for d in days if conflictDays[(t,d)]]) ==0 + +if "NoPairingVio" in special_wishes_active: + # model2+=pairingVioTotal==0 + hardpairs = [ p for p in pairings if p['prio']=="Hard" ] + if len(hardpairs)>0: + model2+=lpSum([ pairingVio[(pair['id'],d)] for pair in pairings for d in days+higherLeagueDayIds if pair['prio']=="Hard"])==0 + +if "NoBreaks" in special_wishes_active: + model2+=breakVioTotal==0 + +if "SyncDaysInGroups" in special_wishes_active: + for (t1,t2) in games: + for d in days: + model2+= home[(t1,d)]+away[(t1,d)] == home[(t2,d)]+away[(t2,d)] + +if "austrianSymmetry" in special_wishes_active: + for (t1,t2) in games: + if (t2,t1) in games: + for (r1,r2) in [(1,7),(2,8),(3,9),(4,10),(5,6)] : + model2+= lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[r1]]) == lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[r2]]) + + +# if "3RestDaysBetweenMD2andMD3" in special_wishes_active: +# print ("3RestDaysBetweenMD2andMD3") +# sw_type="3RestDaysBetweenMD2andMD3" +# # for (r2,d2) in getRoundDaysByRound[2] : +# # for (r3,d3) in getRoundDaysByRound[3] +# # print (getDateTimeDay[d2],getDateTimeDay[d3]) +# dpairs = [(d2, d3) for (r2,d2) in getRoundDaysByRound[2] for (r3,d3) in getRoundDaysByRound[3] if getDateTimeDay[d3]-getDateTimeDay[d2]== datetime.timedelta(days=sw_int1[sw_type]+1) ] +# for (d2,d3) in dpairs: +# for t in realteams: +# model2 += home[(t,d2)]+away[(t,d2)]==home[(t,d3)] +away[(t,d3)] +# print (getDateTimeDay[d2],getDateTimeDay[d3]) +# + +print (special_wishes_active) + + +if thisLeague.name in ["Indian Premier League"]: + + networkIds=networkName.keys() + networkIdByName = { networkName[nw] : nw for nw in networkName.keys() } + + z = {(t1,t2,(r,d),nw) : pulp.LpVariable('playsInPune_'+str(t1)+'_'+str(t2)+'_'+str(r)+'_'+str(d)+'_'+str(nw), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,t2,(r,d)) in x.keys() for nw in networkIds } + + playInStadium = {(t,r,nw) : lpSum([z[t,t2,rd,nw]+z[t2,t,rd,nw] for t2 in opponents[t] for rd in getRoundDaysByRound[r]]) for t in realteams for r in rounds for nw in networkIds } + + for (t1,t2,(r,d)) in x.keys() : + model2+=lpSum([z[t1,t2,(r,d),nw] for nw in networkIds ])== x[(t1,t2,(r,d))] + + for nw in networkIds: + maxGamesInStadium = 3 if networkName[nw] in ["Brabourne", "MCA Stadium, Pune"] else 4 + print ("MAX GAMES PER TEAM IN ", networkName[nw] , " ", maxGamesInStadium) + + for r in rounds: + model2+= lpSum(playInStadium[(t,r,nw)] for t in realteams ) <= 2 + + if r<=nRounds-2: + model2+= lpSum(playInStadium[(t,r2,nw)] for t in realteams for r2 in [r,r+1,r+2]) <= 4 + + for t in realteams: + model2+= lpSum(playInStadium[(t,r,nw)] for r in rounds) == maxGamesInStadium + + + fix3s= [("CSK", "KKR", "2022-03-26", "Wankhede"), + ("MI", "DC", "2022-03-27", "Brabourne"), + ("PBKS", "RCB", "2022-03-27", "DY Patil"), + ("GT", "LSG", "2022-03-28", "Wankhede"), + ("RR", "SRH", "2022-03-29", "MCA Stadium, Pune"), + ] + if "ipl_only_first_game_fixed" in special_wishes_active: + fix3s= [("CSK", "KKR", "2022-03-26", "Wankhede")] + + for (t1, t2, dt, std) in fix3s: + if t1 in getTeamIdByShortName.keys() and t2 in getTeamIdByShortName.keys() and parse(dt) in getDayByDateTime.keys() and std in networkIdByName.keys(): + t11 = getTeamIdByShortName[t1] + t12 = getTeamIdByShortName[t2] + nwid = networkIdByName[std] + model2+= lpSum( [z[t11,t12,rd,nwid] + z[t12,t11,rd,nwid] for rd in getRoundDaysByDay[getDayByDateTime[parse(dt)]] ] )==1 + print ("fixing" , (t1, t2, dt, std) ) + + # puneTrip = {(t,r) : pulp.LpVariable('puneTrip_'+str(t)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in realteams for r in rounds } + puneTrip = {(t,r) : pulp.LpVariable('puneTrip_'+str(t)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in realteams for r in rounds } + noPuneTrip = {t : pulp.LpVariable('noPuneTrip_'+str(t), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in realteams } + + puneStadiumId = networkIdByName["MCA Stadium, Pune"] + + # Matches at the Pune venue need to be finished by 15th May. + model2+= lpSum([ playInStadium[(t,r,puneStadiumId)] for t in realteams for r in rounds if r>51 ]) ==0 + + # for (t1,t2) in games: + # for rd in roundDays: + # z[(t1,t2,rd,puneStadiumId)].cat=pulp.LpInteger + + + if "specialTripsToPune" in special_wishes_active: + sw_type="specialTripsToPune" + for t in realteams: + for r in rounds: + if r<=nRounds-2: + model2+= puneTrip[t,r] <= playInStadium[t, r, puneStadiumId ] + if r == nRounds-2: + model2+= puneTrip[t,r] <= playInStadium[t, r+2, puneStadiumId ] + if r == nRounds-3: + model2+= puneTrip[t,r] <= playInStadium[t, r+2, puneStadiumId ] + playInStadium[t, r+3, puneStadiumId ] + if r < nRounds-3: + model2+= puneTrip[t,r] <= playInStadium[t, r+2, puneStadiumId ] + playInStadium[t, r+3, puneStadiumId ] + playInStadium[t, r+4, puneStadiumId ] + + model2+= lpSum( [puneTrip[t,r] for r in rounds ] ) >= 1 - noPuneTrip[t] + model2+= puneTrip[t,nRounds-1] ==0 + model2+= puneTrip[t,nRounds] ==0 + + specialObjectives += -20* sw_prio[sw_type]*lpSum([puneTrip[tr] for tr in puneTrip.keys()]) + 40* sw_prio[sw_type]*lpSum([noPuneTrip[t] for t in realteams]) + + if "no_b2b_in_pune" in special_wishes_active: + sw_type="no_b2b_in_pune" + for r in rounds: + if r= sw_int1[sw_type] - specialWishVio[(sw_type,t)] + specialObjectives += lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t)] for t in realteams ]) + +if "alwaysFixedRestDaysWhenPossible" in special_wishes_active: + print ("alwaysFixedRestDaysWhenPossible") + sw_type="alwaysFixedRestDaysWhenPossible" + dpairs=[] + for r in rounds: + if r sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= home[(t,d)] + lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for d2 in nextDays for rd in getRoundDaysByDay[d2] ]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", sum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", sum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "RecoverAfterDistantGame" in special_wishes_active: + sw_type="RecoverAfterDistantGame" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0 and (thisSeason.league.name != "ICE Hockey League" or getWeekDay[d]=="Fri" ): + if len(nextDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) + lpSum([ home[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", sum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", sum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "RecoverAfterDistantGame2" in special_wishes_active: + sw_type="RecoverAfterDistantGame2" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0 and (thisSeason.league.name != "ICE Hockey League" or getWeekDay[d]=="Fri" ): + if len(nextDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) + lpSum([ home[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", sum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", sum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "NoSingleDistantGame" in special_wishes_active: + sw_type="NoSingleDistantGame" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + otherDays = [ d2 for d2 in days if d!=d2 and getDateTimeDay[d2]-getDateTimeDay[d]<= datetime.timedelta(days=sw_int2[sw_type]-1) and getDateTimeDay[d]-getDateTimeDay[d2]<= datetime.timedelta(days=sw_int2[sw_type]-1) ] + if len(otherDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) - lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for d2 in otherDays for rd in getRoundDaysByDay[d2] ]) <= specialWishVio[(sw_type,t,d)] + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + # # print (specialObjectives) + + + +if "RestDaysAfterLateGame" in special_wishes_active: + sw_type="RestDaysAfterLateGame" + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= home_time[(t,d,getIdByTime["Late"])]+away_time[(t,d,getIdByTime["Late"])] + lpSum([ home[(t,d2)]+away[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + # print (specialObjectives) + + +if "playWeekendsCompletelyHomeOrAway" in special_wishes_active: + sw_type="playWeekendsCompletelyHomeOrAway" + weekendDays = [d for d in days if getWeekDay[d] in ["Fri"]] + specialHomeAwayOnWeekend={} + for d in weekendDays: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + relrd =[ rd for d in getDaysOfPhase[p-1]+getDaysOfPhase[p] for rd in getRoundDaysByDay[d]] + for (t1,t2) in games: + model2+= lpSum( x[(t1,t2,rd)] for rd in relrd ) <= 1 + +standardObjectives=1+gew['Home-/Away']*HawVioTotal\ + +gew['Home-/Away']*3*unpreferredTotal \ + +gew['Pairings']*pairingVioTotal \ + +gew['Blockings']*blockingVioTotal \ + +gew['Traveling']*travelVioTotal \ + +gew['Blockings']*gamesTooCloseTotal \ + -5*gew['Trips']*tripSavedTotal2 \ + +gew['Breaks']*breakVioTotal \ + +gew['Breaks']*2*break3VioTotal \ + +gew['Breaks']*1*breakImbalanceTotal \ + +5*gew['Encounters']*encVioTotal \ + +5*gew['Encounters']*seedVioTotal \ + +gew['Conferences']*confVioTotal \ + +gew['Blockings']*tooManyHomesInStadiumTotal \ + +gew['Broadcasting']*broadVioTotal \ + +gew['Derbies']*derbiesMissingTotal \ + +5*competitionVioTotal \ + +1.0*tooManyTop4InRowTotal\ + +fixedGameVioTotal\ + +missingGamesVioTotal\ + +oldScenGamesTotal\ + -0.01*totalAttendance + +if sharedStadiums: + standardObjectives+= lpSum ( prio_weight[p] * useStadiumTimeSlot[(t,d,s)] for (t,d) in t_site_bestTimeSlots.keys() for (p,s) in t_site_bestTimeSlots[(t,d)]) \ + +100000* lpSum([ nonIceGame[(t,d)] for (t,d) in nonIceGame.keys()]) + + +model2+= standardObjectives + +# model2+= fixedGameVioTotal +missingGamesVioTotal +# model2+= fixedGameVioTotal +# print ("TESTING") +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) +# print ("TESTING DONE") + + +global_coeff = {t['id'] : int(t['attractivity']) for t in teamObjects } +domestic_coeff = {t['id'] : int(0.1+10*(t['attractivity']-int(t['attractivity']))) for t in teamObjects } + +def quality_of_game(t1, t2): + return global_coeff[t1]*global_coeff[t2] + +def quality_of_game_dom(t1, t2): + return domestic_coeff[t1]*global_coeff[t2] + + +print ("Broadcasting " , gew['Broadcasting']) + +if thisSeason.useFeatureBackToBack: + critical_day_pairs = [ (d1,d2) for r in [r1 for r1 in rounds if r1>1] for d1 in getDays[r-1] for d2 in getDays[r] if getDateTimeDay[d2]-getDateTimeDay[d1]==datetime.timedelta(days=1) ] + nextCritical ={ d1 : False for d1 in days} + for (d1,d2) in critical_day_pairs: + nextCritical[d1]=d2 + + badBackToBack= { (t,d1) : pulp.LpVariable('badBackToBack_'+str(t)+"_"+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for (d1,d2) in critical_day_pairs } + back2backBlocks = [] + color_weight = { "Y" : 0.003 , "R" : 0.05 , "X" : 1.0 , } + + +show_TV_markets = False + +# START SPECIAL CONSTRAINTS + +# hat leider nicht geklappt : +# specialOpt = False +# optFileName = "optimize_" + thisSeason.league.name.replace(' ', '_') +# if path.exists("scheduler/"+optFileName+".py"): +# specialOpt = import_module('scheduler.'+optFileName) + +# if specialOpt: +# specialOpt.enhanceModel2(model2) +# with open("scheduler/"+optFileName+".py") as f: exec(f.read()) + +if mathModelName=="NHL": + startDoubleGame= { (t1,t2,r) : pulp.LpVariable('startDoubleGame'+str(t1)+"_"+str(t2)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for (t1,t2) in games for r in rounds if r1300] + # print (bad_travels) + # for (t1,t2,c) in bad_travels: + # print (getTeamById[t1] , " - " , getTeamById[t2]) + + # print(thisSeason.name[-3:]) + + thisCountry ="" + for t in realteams: + thisCountry=t_country[t] + # print (getTeamById[t], t_country[t] , t_conference[t]) + # print (thisCountry) + + for (t1,t2,r) in startDoubleGame.keys(): + model2 += startDoubleGame[(t1,t2,r)] <= x_round[(t1,t2,r)] + model2 += startDoubleGame[(t1,t2,r)] <= x_round[(t1,t2,r+1)] + + specialObjectives+= -100* lpSum( [ startDoubleGame[ttr] for ttr in startDoubleGame.keys()]) + + if thisCountry=="United States": + for (t1,t2) in games: + for r in rounds: + if r>=3: + model2 += x_round[(t1,t2,r-2)]+x_round[(t1,t2,r-1)]+x_round[(t1,t2,r)]<=2 + # print ("no 3 in row " , t1, t2, r-2 , r) + + bad_travels = [ (t1,t2,"X") for t1 in realteams for t2 in realteams if distanceById[t1,t2]>1300 and t_conference[t1]==t_conference[t2]] + # print (len(bad_travels)) + # for (t1,t2,c) in bad_travels: + # print (getTeamById[t1] , " - " , getTeamById[t2]) + + back2backBlocks += [ ([t1],[t2],1.0) for (t1,t2,c) in bad_travels] + + contractable=True + while contractable: + contractable=False + for ((tms1_a,tms2_a,w_a),(tms1_b,tms2_b,w_b)) in [ (b1,b2) for b1 in back2backBlocks for b2 in back2backBlocks if b1!=b2 and b1[2]==b2[2] and t_conference[b1[0][0]]==t_conference[b2[0][0]] ]: + if min([ distanceById[t1,t2] for t1 in tms1_a for t2 in tms2_b]) > 1300 and min([ distanceById[t1,t2] for t1 in tms1_b for t2 in tms2_a]) > 1300 : + # print ("unify ", b1,b2) + back2backBlocks.remove((tms1_a,tms2_a,w_a)) + back2backBlocks.remove((tms1_b,tms2_b,w_b)) + back2backBlocks.append((list(set(tms1_a+tms1_b)), list(set(tms2_a+tms2_b)), w_a)) + contractable=True + break; + + # print (len(back2backBlocks)," back2backBlocks") + # for (tms1,tms2,w) in back2backBlocks: + # print ("") + # print (tms1,tms2,w) + # for t1 in set(tms1): + # print ("from ", getTeamById[t1]) + # for t2 in set(tms2): + # print ("to ", getTeamById[t2]) + # print (len(bad_travels), "bad_travels") + # print (len(back2backBlocks),"back2backBlocks") + + else: + # startDoubleGame + for r in rounds: + if r>=5: + for (t1,t2) in games: + if gameCntr[(t2,t1)]>0 : + model2 += lpSum(x_round[(t1,t2,r2)] + x_round[(t2,t1,r2)] for r2 in [r-4,r-3,r-2,r-1,r])<=3 + else: + model2 += lpSum(x_round[(t1,t2,r2)] for r2 in [r-4,r-3,r-2,r-1,r])<=3 + # if r>2 and r <10: + # model2 += x_round[(t1,t2,r-1)] <= x_round[(t1,t2,r-2)] + x_round[(t1,t2,r)] + + if r>=9: + for t1 in teams : + model2 += lpSum(homeInRound[(t1,r2)] for r2 in rounds if r2>=r-8 and r2<=r)>=1 + model2 += lpSum(awayInRound[(t1,r2)] for r2 in rounds if r2>=r-8 and r2<=r)>=1 + + east_teams = [ t for t in realteams if t_lon[t]>= -87] + center_teams = [ t for t in realteams if -87 > t_lon[t] and t_lon[t]>=-100 ] + west_teams = [ t for t in realteams if t_lon[t]< -113] + back2backBlocks += [ (east_teams, center_teams+west_teams,1.0), (center_teams+west_teams,east_teams,1.0) ] + + +if mathModelName=="Champions Hockey League": + toTime={(c,d) : ["n/a"] for c in countries for d in days} + for d in days: + for (c,tms) in [ ("SWE", ["18:05/19:05","20:35"]), ("FIN",["19:00"]), + ("CZE",["17:00 or 17:30", "19:30 or 20:00"]), ("SVK",["17:00 or 17:30", "19:30 or 20:00"]), + ("HUN" , ["19:00"]), + ("AUT",["20:20"]), + ("SUI",["19:45"]), + ("GER",["18:00"]), + ("NOR",["18:00"]), + ("DEN",["18:00"]), + ("POL",["18:00"]), + ("SLO",["19:15"]), + ("FRA",["20:00"]), + ("SCO",["20:00 or 20:30"]), + ]: + toTime[(c,d)]=tms + + for (cntries,nds,tms) in [ + (["SWE"],["2022-09-03","2022-09-04"],["14:35","15:05"]), + (["CZE","SVK"],["2022-09-03","2022-09-04"],["16:00","18:30"]), + (["CZE","SVK"],["2022-09-10"],["15:00 or 16:00","17:30 or 18:30"]), + (["CZE","SVK"],["2022-09-11",],["15:00 or 16:00","18:30"]), + (["HUN"],["2022-09-03","2022-09-04","2022-09-10","2022-09-11"],["18:00"]), + (["AUT"],["2022-09-02"],["19:30"]),(["AUT"],["2022-09-09"],["18:00"]), + (["SUI"],["2022-09-01"],["20:00"]), + (["SUI"],["2022-09-03","2022-09-10"],["14:30/15:00"]), + (["SUI"],["2022-09-04","2022-09-11"],["16:30"]), + (["GER"],["2022-09-01","2022-09-02",],["20:15"]), + (["GER"],["2022-09-03","2022-09-10",],["17:00"]), + (["GER"],["2022-09-04",],["15:00 or 19:00"]), + (["GER"],["2022-09-08",],["19:00","20:00"]), + (["GER"],["2022-09-09",],["16:45"]), + (["GER"],["2022-09-11",],["?"]), + (["NOR"],["2022-09-08"],["no pref."]), + (["NOR"],["2022-10-04","2022-10-05","2022-10-10","2022-10-11",],["18:30"]), + (["DEN"],["2022-09-02","2022-09-09",],["19:30"]), + (["DEN"],["2022-09-08"],["no pref."]), + (["SLO"],["2022-09-08"],["20:00"]), + (["SLO"],["2022-09-03"],["18:00"]), + (["SLO"],["2022-09-04"],["17:30"]), + (["SLO"],["2022-09-08","2022-09-09",],["18:00 or 20:00"]), + (["SLO"],["2022-09-10","2022-09-11",],["18:00"]), + ] : + if getNiceDayRaw[d] in nds: + for c in cntries: + toTime[(c,d)]=tms + else: + print (getNiceDayRaw[d], nds, getNiceDayRaw[d] in nds) + + show_TV_markets = True + critical_day_pairs_CHL = [ (d1,d2) for d1 in set(getDays[1]+getDays[3]) for d2 in set(getDays[2]+getDays[4]) if getDateTimeDay[d2]-getDateTimeDay[d1]==datetime.timedelta(days=2) ] + + # confusingDays = [ d for d in getDays[1]+getDays[2] if d in getDays[3]+getDays[4]] + # for (t1,t2) in games: + # print (getTeamById[t1], " " ,getTeamById[t2], lpSum([ x[(t1,t2,rd)] for r2 in [1,2,3] for rd in getRoundDaysByRound[r2]]) ) + # model2+= lpSum([ x[(t1,t2,rd)] + x[(t2,t1,rd)] for r2 in [1,2,3] for rd in getRoundDaysByRound[r2]]) <= 1 + # model2+= lpSum([ x[(t1,t2,rd)] + x[(t2,t1,rd)] for r2 in [4,5,6] for rd in getRoundDaysByRound[r2]]) <= 1 + + # for d in confusingDays: + # rnds = [1,2] if t_pot[t]!=5 else [3,4] + # for r2 in rnds: + # if (r2,d) in roundDays: + # setUB(x[(t1,t2,(r2,d))],0) + # # print ("forbidding " ,r2 ,d, " on day " , getNiceDay[d]) + + + for t in teams : + # model2+= lpSum([ home[(t,d1)] + away[t,d1] for d1 in confusingDays ]) == 2 + for (d1,d2) in critical_day_pairs_CHL: + model2 += home[(t,d1)]==home[(t,d2)] + model2 += away[(t,d1)]==away[(t,d2)] + # model2 += homeInRound[(t1,1)] == homeInRound[(t1,2)] + + +if mathModelName=="Florida State League": + notEnoughHomes= { t : pulp.LpVariable('notEnoughHomes_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in teams } + notEnoughAways= { t : pulp.LpVariable('notEnoughAways_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in teams } + for t in teams: + # model2+= lpSum( [ homeInRound[t,r] for r in rounds]) >= 20 + model2+= lpSum( [ home[t,d] for d in days]) >= 70 - notEnoughHomes[t] + model2+= lpSum( [ away[t,d] for d in days]) >= 70 - notEnoughAways[t] + model2+= lpSum( [ home[t,d] for d in days]) <= 70 + notEnoughHomes[t] + model2+= lpSum( [ away[t,d] for d in days]) <= 70 + notEnoughAways[t] + # print ("doing special stuff ", thisSeason.gamesPerRound=="one day") + + + getConference={} + for c in confTeams.keys(): + if len(confTeams[c])>2: + for t in confTeams[c]: + getConference[t]=c + + visited_unbalanced_MINLB= {(t1,t2) : pulp.LpVariable('visited_too_less_'+str(t1)+'_'+str(t2), lowBound = 0, cat = pulp.LpContinuous) for (t1,t2) in games } + for (t1,t2) in games: + # print (t1 , t2, getRoundDaysByRound[1][0], distance[getTeamById[t1],getTeamById[t2]] , getConference[t1]==getConference[t2] ) + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) >=1 + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) >=2- visited_unbalanced_MINLB[(t1,t2)] + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) <=3+ visited_unbalanced_MINLB[(t1,t2)] + # exit(0) + + # model2+= x[(13859,13860,(1, 26627))] >=1 + + # for r in rounds: + # print (r, getRoundDaysByRound[r] , len(getRoundDaysByRound[r])) + + # visited_too_less_MINLB= { (t1,t2) : not_visited * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games } + travelCost_Driving_MINLB= { (t1,t2,r) : distance[getTeamById[t1],getTeamById[t2]] * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games for r in rounds } + travelCost_Hotel_MINLB = { (t1,t2,r) : len(getRoundDaysByRound[r]) * (getConference[t1]!=getConference[t2]) * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games for r in rounds } + + travelCost_Total_MINLB = lpSum([0.001*travelCost_Driving_MINLB[(t1,t2,r)]+0.2*travelCost_Hotel_MINLB[(t1,t2,r)] for (t1,t2) in games for r in rounds]) + + total_visited_unbalanced_MINLB = lpSum([visited_unbalanced_MINLB[(t1,t2)] for (t1,t2) in games]) + + notEnoughGames_MINLB = lpSum([notEnoughHomes[t] + notEnoughAways[t] for t in teams]) + + # specialObjectives+=travelCost_Total_MINLB+10*visited_too_less_MINLB + specialObjectives+=100*( 0.1*travelCost_Total_MINLB+0.2*total_visited_unbalanced_MINLB+2*notEnoughGames_MINLB) + + +if mathModelName in ["ALPS" ,"ICE Hockey League" , "ICEYSL"]: + + if mathModelName != "ALPS" and False: + firstTwoPhases= [ (r,d) for (r,d) in roundDays if r<=26 ] + secondTwoPhases= [ (r,d) for (r,d) in roundDays if r>= 27 ] + for (t1,t2) in games: + # print (t1 , t2, getRoundDaysByRound[1][0], distance[getTeamById[t1],getTeamById[t2]] , getConference[t1]==getConference[t2] ) + for rrr in [firstTwoPhases, secondTwoPhases]: + model2+= lpSum([x[(t1,t2,rd)] for rd in rrr]) <=1 + + # trips_ICE_raw={ + # 'DEC': [['AVS', 'VIC'],[ 'ZNO', 'BWL'],[ 'IBC', 'G99', 'KAC'],['VSV', 'HKO']], + # 'AVS': [['DEC', 'HCI', 'HCB', 'PUS', 'RBS']], + # 'HCB': [['ZNO', 'VIC'],['IBC', 'AVS']], + # 'G99': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'BWL': [['HCB', 'PUS', 'DEC']], + # 'ZNO': [['HCB', 'PUS' 'DEC', 'HCI']], + # 'VSV': [['HCB', 'PUS'],[ 'DEC', 'HCI'],[ 'VIC', 'AVS'],[ 'VIC', 'IBC', 'ZNO']], + # 'KAC': [['PUS', 'HCB'],[ 'HCI', 'DEC']], + # 'RBS': [], + # 'VIC': [['HCB', 'PUS'],['DEC', 'HCI']], + # 'HCI': [['VIC', 'AVS'],[ 'BWL', 'ZNO'],[ 'BWL', 'IBC'],[ 'VSV', 'KAC', 'HKO']], + # 'IBC': [['HCB', 'PUS'],[ 'DEC', 'HCI'],[ 'HKO', 'VSV']], + # 'HKO': [['HCB', 'PUS'],[ 'DEC', 'HCI']], + # 'PUS': []} + + + # trips_ICE_raw={ + # 'DEC': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ],[ 'IBC', 'G99', 'KAC'],['VSV', 'HKO']], + # 'AVS': [['DEC', 'HCI', 'HCB', 'PUS', 'RBS']], + # 'HCB': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ]], + # 'G99': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'BWL': [['HCB', 'PUS', 'DEC']], + # 'ZNO': [['HCB', 'PUS','DEC', 'HCI']], + # 'VSV': [['HCB', 'PUS', 'DEC', 'HCI'],[ 'VIC', 'AVS', 'VIC', 'IBC', 'ZNO']], + # 'KAC': [['PUS', 'HCB', 'HCI', 'DEC']], + # 'RBS': [], + # 'VIC': [['HCB', 'PUS','DEC', 'HCI']], + # 'HCI': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ],[ 'VSV', 'KAC', 'HKO']], + # 'IBC': [['HCB', 'PUS', 'DEC', 'HCI'],[ 'HKO', 'VSV']], + # 'HKO': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'PUS': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ]]} + + + trips_ICE_raw={ + 'DEC': [['AVS', 'VIC'],['IBC', 'ZNO'],['G99', 'HKO']], + 'AVS': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HCB': [['AVS', 'VIC'],['IBC', 'ZNO']], + 'G99': [], + 'BWL': [], + 'ZNO': [['DEC', 'HCI'],['HCB', 'PUS']], + 'VSV': [], + 'KAC': [], + 'RBS': [], + 'VIC': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HCI': [['AVS', 'VIC'],['IBC', 'ZNO']], + 'IBC': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HKO': [], + 'PUS': [['AVS', 'VIC'],['IBC', 'ZNO']]} + + # importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "AVS", "HCB", "DEC", "PUS", "IBC" , "HCI" ] ] + # importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "AVS", "HCB", "DEC", "PUS", "IBC" , "HCI" , "VIC" ] ] + + firstRoundOfChristmas=30 + lastRoundOfChristmas=36 + badweekdayGames=0 + + if mathModelName == "ALPS" : + trips_ICE_raw={ + 'EHC': [['JES', 'KFT'],['VCS', 'SWL']], + 'ECB': [['JES', 'KFT'],['VCS', 'SWL']], + 'VEU': [['JES', 'KFT'],['VCS', 'SWL']], + 'VCS': [['ECB', 'EHC'],['VEU', 'KEC'],['ASH', 'SGC', 'RIT', 'GHE', 'WSV', 'FAS', 'HCM']], + 'JES': [['ECB', 'EHC'], ['VEU', 'KEC']], + 'KFT': [['ECB', 'EHC'], ['VEU', 'KEC']], + 'ASH': [['VCS', 'SWL']], + 'SGC': [['VCS', 'SWL']], + 'RIT': [['VCS', 'SWL']], + 'GHE': [['VCS', 'SWL']], + 'WSV': [['VCS', 'SWL']], + 'FAS': [['VCS', 'SWL']], + 'HCM': [['VCS', 'SWL']] + } + badweekdayGames = lpSum([ distanceById[t1,t2]* distanceById[t1,t2] * distanceById[t1,t2] /8000000 * x[t1,t2,(r,d)] for (t1,t2) in games for (r,d) in roundDays if getWeekDay[d]=="Thu" and distanceById[t1,t2]>250 ]) + firstRoundOfChristmas=25 + lastRoundOfChristmas=29 + firstRoundOfChristmas=27 + lastRoundOfChristmas=28 + + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "EHC", "ECB", "VEU", "VCS", "JES", "KFT", "ASH", "SGC", "RIT", "GHE", "WSV", "FAS", "HCM" ] ] + + if mathModelName == "ICE Hockey League" : + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in ["HCB", "AVS", "DEC", "PUS", "IBC" , "ZNO" , "HCI" , "VIC"] ] + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "DEC", "AVS", "HCB", "VIC", "HCI", "IBC", "PUS"] ] + + + if mathModelName == "ICEYSL" : + trips_ICE_raw={ t_shortname[t] : [] for t in teams } + trips_ICE_raw["HCI"] = [['NHA', 'VSV'],['IHC', 'OHE'], ['EAS', 'EAO']] + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in ["HCI"] ] + badweekdayGames = lpSum([(x[t1,t2,(r,d)]) for (t1,t2) in games for (r,d) in roundDays if getWeekDay[d] in [ "Wed", "Thu" ] and distanceById[t1,t2]>200 ]) + + short_dist = { t : sorted([ (distanceById[t,t2] ,t2) for t2 in teams if t2!=t ]) for t in teams } + maxDist4 = { t : max(210, short_dist[t][3][0]) for t in teams } + + # for t in teams : + # print (getTeamById[t] , maxDist4[t], short_dist[t]) + # for ds,t2 in short_dist[t]: + # print (" - " , getTeamById[t2] , ds) + + + toofarForXmas = [ (t1,t2,(r,d)) for (t1,t2) in games for (r,d) in roundDays if r>= firstRoundOfChristmas and (r<=lastRoundOfChristmas or r>=51 ) and distanceById[t1,t2]> maxDist4[t2]+1 ] + wayToofarForXmas = [ (t1,t2,(r,d)) for (t1,t2) in games for (r,d) in roundDays if r>= firstRoundOfChristmas and r<=lastRoundOfChristmas and distanceById[t1,t2]> 300 ] + + # kac dec feh sind wichtig + # vci 2 trips hcidec pusboz + + cntr = 0 + trips_ICE = {} + for ts in trips_ICE_raw.keys(): + t= getTeamIdByShortName[ts] + for tp in trips_ICE_raw[ts]: + tps = [getTeamIdByShortName[ts2] for ts2 in tp] + trips_ICE[cntr]=(t,tps) + # print (trips_ICE) + print ("TRIP", cntr, tp, tps) + cntr+=1 + # for t2 in tps: + # print (" - " , getTeamById [t2] , " " , distanceById[t,t2] , "km") + print () + + print (trips_ICE) + + important_trips_ICE = [ cn for cn in trips_ICE.keys() if trips_ICE[cn][0] in importantTravellers ] + trips_ICE_by_traveller ={t: [ cn for cn in trips_ICE.keys() if trips_ICE[cn][0] == t ] for t in teams } + print (trips_ICE_by_traveller) + + + day1pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=1) and getRoundByDay[d1] != getRoundByDay[d2] ] + + toughWeekend_ICE = { (t,d1,d2) : pulp.LpVariable('toughWeekend_ICE_'+str(t)+'_'+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for t in realteams for (d1,d2) in day1pairs } + + for d1,d2 in day1pairs: + print (getNiceDay[d1], getNiceDay[d2]) + for t in realteams: + print (t,d1,d2 , getTeamById[t], getNiceDay[d1],getNiceDay[d2]) + model2+= home[(t,d1)] + lpSum([x[(t1,t,rd)] for t1 in teams for rd in getRoundDaysByDay[d2] if distanceById[t,t1]>150] ) <=1+toughWeekend_ICE[t,d1,d2] + model2+= home[(t,d2)] + lpSum([x[(t1,t,rd)] for t1 in teams for rd in getRoundDaysByDay[d1] if distanceById[t,t1]>150] ) <=1+toughWeekend_ICE[t,d1,d2] + + traveling_ICE = { (tr,d1,d2) : pulp.LpVariable('traveling_ICE_'+str(tr)+'_'+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for tr in trips_ICE.keys() for (d1,d2) in day1pairs } + notEnoughTravel = { t : pulp.LpVariable('important_travel_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in importantTravellers } + + for (tr,d1,d2) in traveling_ICE.keys(): + t2,tms = trips_ICE[tr] + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d1]]) + # if t2 in [getTeamIdByShortName["DEC"] ,getTeamIdByShortName["HCB"]] and nextDay[d2]!=-1: + if t2 in [] and nextDay[d2]!=-1: + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d2]+getRoundDaysByDay[nextDay[d2]]]) + else: + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d2]]) + + if mathModelName == "ALPS": + for t in realteams: + for d1,d2 in day1pairs: + model2+= away[(t,d1)] <= lpSum([ traveling_ICE[(tr,d1,d2)] for tr in trips_ICE_by_traveller[t] ]) + toughWeekend_ICE[t,d1,d2] + print ( "away ",t , getTeamById[t] , " only if travelling ", getNiceDay[d1] , d2) + + for t in importantTravellers: + model2+= lpSum([ traveling_ICE[(tr,d1,d2)] for tr in trips_ICE_by_traveller[t] for (d1,d2) in day1pairs]) >=2 - notEnoughTravel[t] + + notEnoughTravelTotal = lpSum([ notEnoughTravel[t] for t in notEnoughTravel.keys() ]) + badXmas = lpSum([ 0.01*distanceById[t1,t2] *x[(t1,t2,rd)] for (t1,t2,rd) in toofarForXmas ]) + 10 * lpSum([ x[ttrd] for ttrd in wayToofarForXmas ]) + toughWeekends = lpSum([ toughWeekend_ICE[tdd] for tdd in toughWeekend_ICE.keys() ]) + + # specialObjectives += 0.2* gew['Trips'] * (-100* lpSum([traveling_ICE[tdd] for tdd in traveling_ICE.keys()]) - 300 * lpSum([traveling_ICE[(tr,d1,d2)] for (tr,d1,d2) in traveling_ICE.keys() if tr in important_trips_ICE ])) + 100*badXmas + 1000 *toughWeekends + specialObjectives += ( 0.2* gew['Trips'] * ( + -0 * lpSum([traveling_ICE[tdd] for tdd in traveling_ICE.keys()]) + - 400 * lpSum([traveling_ICE[(tr,d1,d2)] for (tr,d1,d2) in traveling_ICE.keys() if tr in important_trips_ICE ]) + ) + + 100 * badXmas + + 1000 * toughWeekends + + 1000*notEnoughTravelTotal + # + 50*badweekdayGames + + 200*badweekdayGames + ) + + for (t,r,c) in tripToCluster.keys(): + model2 += tripToCluster[(t,r,c)] == 0 + + +if mathModelName=="NBA": + + for (t1,t2,d,channel) in seedTV: + if previousDay[d]!=-1: + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Late"])] for (t3,t4) in games for rd in getRoundDaysByDay[previousDay[d]] if t1 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Early"])] for rd in getRoundDaysByDay[d]]) <=1 + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Late"])] for (t3,t4) in games for rd in getRoundDaysByDay[previousDay[d]] if t2 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Early"])] for rd in getRoundDaysByDay[d]]) <=1 + if nextDay[d]!=-1: + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Early"])] for (t3,t4) in games for rd in getRoundDaysByDay[nextDay[d]] if t1 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Late"])] for rd in getRoundDaysByDay[d]]) <=1 + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Early"])] for (t3,t4) in games for rd in getRoundDaysByDay[nextDay[d]] if t2 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Late"])] for rd in getRoundDaysByDay[d]]) <=1 + + badRepeater= { (t,r) : pulp.LpVariable('badRepeater_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + tooLongTrip_NBA= { (t,r) : pulp.LpVariable('tooLongTrip_NBA_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + eastWestTrip_NBA= { (t,r) : pulp.LpVariable('eastWestTrip_NBA_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + + if nRounds >= 100: + for t in realteams: + if r>=3: + model2 += break3InRound[(t,r)] +2>= lpSum([ homeInRound[(t,r2)] + awayInRound[(t,r2)] for r2 in [r-2,r-1,r]]) + + userepeater= False + if userepeater: + for t in realteams: + print ("building repeater contraints for " , t) + for r in rounds: + # forbid same opponents on successive days + for t2 in realteams: + if t2: + model2 += lpSum([ (x[(t,t2,rd )]+x[(t2,t,rd)]) for rd in getRoundDaysByRound[r-2]+getRoundDaysByRound[r-1]+getRoundDaysByRound[r] ]) <= 1 + badRepeater[(t,r)] + # badRepeater[(t,r)].upBound=0 + if r>4: + model2 += lpSum([ home [(t,d )] for (r2,d) in roundDays if r2>=r-4 and r2<=r ]) >= 1 - tooLongTrip_NBA[(t,r)] + model2 += lpSum([ away [(t,d )] for (r2,d) in roundDays if r2>=r-4 and r2<=r ]) >= 1 - tooLongTrip_NBA[(t,r)] + + b2bmatrix = {"BOS": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "TOR": {"BOS": "Y", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "Y", "CHA": "Y", "ATL": "Y", "ORL": "R", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "NYK": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "BKN": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "PHI": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "G", "MEM": "Y", "NOP": "Y", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "WAS": {"BOS": "G", "TOR": "Y", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "Y", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CHA": {"BOS": "G", "TOR": "Y", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "Y", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "ATL": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "ORL": {"BOS": "Y", "TOR": "R", "NYK": "Y", "BKN": "Y", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "Y", "CLE": "Y", "IND": "G", "CHI": "R", "MIL": "R", "MEM": "Y", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "Y", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIA": {"BOS": "Y", "TOR": "R", "NYK": "Y", "BKN": "Y", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "Y", "CLE": "Y", "IND": "Y", "CHI": "R", "MIL": "R", "MEM": "Y", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DET": {"BOS": "G", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "Y", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "Y", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CLE": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "R", "DAL": "Y", "SAS": "R", "OKC": "Y", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "IND": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CHI": {"BOS": "G", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "R", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIL": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MEM": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "NOP": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "R", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "HOU": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "Y", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "Y", "CLE": "Y", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "R", "UTA": "R", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DAL": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "Y", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "Y", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "SAS": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "Y", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "R", "CLE": "Y", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "Y", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "OKC": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "Y", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIN": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "R", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "Y", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DEN": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "Y", "MIL": "G", "MEM": "Y", "NOP": "Y", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "R", "LAL": "R", "GSW": "Y", "SAC": "Y", "POR": "R"}, + "UTA": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "R", "MIL": "Y", "MEM": "Y", "NOP": "R", "HOU": "Y", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "PHX": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "R", "MIL": "R", "MEM": "R", "NOP": "R", "HOU": "Y", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "Y"}, + "LAC": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "LAL": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "GSW": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "Y", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "SAC": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "POR": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "Y", "UTA": "G", "PHX": "G", "LAC": "Y", "LAL": "Y", "GSW": "G", "SAC": "G", "POR": "G"} + } + + bad_travels = [ (getTeamIdByName[teamByShort[t2]],getTeamIdByName[teamByShort[t1]],b2bmatrix[t1][t2]) for t1 in b2bmatrix.keys() for t2 in b2bmatrix.keys() if b2bmatrix[t1][t2]!="G"] + + east_teams = [ t for t in realteams if t_lon[t]>= -87] + center_teams = [ t for t in realteams if -87 > t_lon[t] and t_lon[t]>=-100 ] + mountain_teams = [ t for t in realteams if -100 > t_lon[t] and t_lon[t]>=-113 ] + west_teams = [ t for t in realteams if t_lon[t]< -113] + + # print (len (east_teams)) + # print (len (center_teams)) + # print (len (mountain_teams)) + # for t in mountain_teams: + # print (" mt " , getTeamById[t]) + # print (len (west_teams)) + + + back2backBlocks += [ (east_teams, mountain_teams+west_teams,1.0), (mountain_teams+west_teams,east_teams,1.0), + (center_teams,west_teams,1.0) ,(west_teams,center_teams,1.0), + ([getTeamIdByName[teamByShort[ts]] for ts in ["MEM","NOP","HOU","DAL","SAS","OKC","MIN"]],[getTeamIdByName[teamByShort[ts]] for ts in ["BOS", "TOR", "NYK", "BKN", "PHI", "WAS"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["BOS", "TOR", "NYK", "BKN", "PHI", "WAS"]], [getTeamIdByName[teamByShort[ts]] for ts in ["NOP","HOU","DAL","SAS","OKC"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["DEN", "UTA", "PHX"]], [getTeamIdByName[teamByShort[ts]] for ts in ["CHI","MIL","MEM","NOP","HOU"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["ORL", "MIA"]], [getTeamIdByName[teamByShort[ts]] for ts in ["TOR","MIN"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["NOP", "HOU", "DAL", "SAS"]], [getTeamIdByName[teamByShort[ts]] for ts in ["DET","CLE"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["TOR", "CHI", "MIL", "MIN","OKC"]], [getTeamIdByName[teamByShort[ts]] for ts in ["ORL","MIA"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in [ "CHI", "MIL", "MEM","NOP"]], [getTeamIdByName[teamByShort[ts]] for ts in ["UTA","PHX"]],0.05), + ] + + playingAwayEast = { (t,r) : lpSum([ x[(t1,t,rd )] for rd in getRoundDaysByRound[r] for t1 in east_teams ]) for r in rounds for t in realteams } + playingAwayWest = { (t,r) : lpSum([ x[(t1,t,rd )] for rd in getRoundDaysByRound[r] for t1 in west_teams+mountain_teams ]) for r in rounds for t in realteams } + + for t in realteams: + for r in rounds: + if r>1: + model2 += playingAwayEast[(t,r-1)] + playingAwayWest[(t,r)] <=1 + eastWestTrip_NBA[(t,r)] + model2 += playingAwayWest[(t,r-1)] + playingAwayEast[(t,r)] <=1 + eastWestTrip_NBA[(t,r)] + + + for t in teams: + for r in rounds: + if r>2: + model2 += awayInRound[(t,r-1)] <= awayInRound[(t,r-2)] + awayInRound[(t,r)] + 3*eastWestTrip_NBA[(t,r)] + + + for t in west_teams+mountain_teams: + for r in rounds: + if r>2: + model2 += playingAwayEast[(t,r-1)] <= playingAwayEast[(t,r-2)] + playingAwayEast[(t,r)] + 0.3*eastWestTrip_NBA[(t,r)] + + for t in east_teams: + for r in rounds: + if r>2: + model2 += playingAwayWest[(t,r-1)] <= playingAwayWest[(t,r-2)] + playingAwayWest[(t,r)] + 0.3*eastWestTrip_NBA[(t,r)] + + + # for t1 in teams: + # print ("bad travel in ", getTeamById[t1] , bad_travel_in[t1] ) + # print ("bad travel out ", getTeamById[t1] , bad_travel_out[t1] ) + + # distant_teams = { t: [] for t in realteams} + + # for t in east_teams: + # print ("EAST " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= mountain_teams+west_teams + # for t in center_teams: + # print ("CENTER " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= west_teams + # for t in mountain_teams: + # print ("MOUNTAIN " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= east_teams + # for t in west_teams: + # print ("WEST " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= center_teams+east_teams + + # print ("building back to back contraints for " , t) + badRepeater_Total_NBA = lpSum([badRepeater[(t,r)] for t in teams for r in rounds]) + tooLongTrip_Total_NBA = lpSum([tooLongTrip_NBA[(t,r)] for t in teams for r in rounds]) + eastWestTrip_Total_NBA = lpSum([eastWestTrip_NBA[(t,r)] for t in teams for r in rounds]) + specialObjectives+=10*( badRepeater_Total_NBA)+100*tooLongTrip_Total_NBA + 100*eastWestTrip_Total_NBA + + +if mathModelName=="UEFA" : + + isUEL = thisSeason.nicename[:3]=="UEL" + + for sw in ["UsePosition14", "UseClassicWeekdayPatterns", "Sync1256", "UseClassicSchedules", "UseRedBlueGroups", "MaxAverageEarlyGlobal", "AvoidEarlyDomestic", "AvoidEarlyGlobal", "AvoidEarlyHighDomestic", "AvoidEarlyHighGlobal", "EachOnce", "3breaks", "firstlegs", "NoThreeTuesdaysWednesdays", "OneTuesdayOneWednesday", "everyEncounterOnceEarly"]: + if sw not in sw_prio.keys(): + sw_prio[sw]=0 + sw_int1[sw]=0 + + show_TV_markets = True + + model2+=pairingVioTotal==0 + # Sogar hart : Avoid playing early the match with the highest global coefficient of the night (if two matches have the same, one late is enough) à priority B + + use_classic_schedule= sw_prio['UseClassicSchedules']+sw_prio['UseClassicWeekdayPatterns']>0 + + if thisSeason.nicename[:3]=="UEL" : + for t in teams: + # model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for d in days ]) == 3 + print ("3 early games for ", getTeamById[t]) + if use_classic_schedule: + model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[2] ]) == lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[3] ]) + model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[2] ]) == lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[5] ]) + + + # for pair in pairings: + # t1 = pair['team1_id'] + # t2 = pair['team2_id'] + # # print ("+++ " ,t_conference[t1] , t_conference[t2] ) + # if t_conference[t1].name[:3] != t_conference[t2].name[:3] : + # print ("+++ " ,t_conference[t1] , t_conference[t2], " ", getTeamById[t1] , getTeamById[t2]) + + uefa_games = [ (t1,t2) for t1 in teams for t2 in teams if t14 ] + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for d in days } + best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,t2) in uefa_interesting_games for d in days } + best_global_early = { d : pulp.LpVariable('best_global_early_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for d in days } + + NAS15=["ESP","GER","ENG","ITA","FRA"] + uefa_interesting_domestic_games = {c:[] for c in NAS15} + uefa_interesting_domestic_teams = {c:set([]) for c in NAS15} + + + for (t1,t2) in uefa_games: + if quality_of_game_dom(t1, t2)>=10 and t_country[t1] in NAS15: + uefa_interesting_domestic_games[t_country[t1]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t1]].add(t1) + if quality_of_game_dom(t2, t1)>=10 and t_country[t2] in NAS15: + uefa_interesting_domestic_games[t_country[t2]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t2]].add(t2) + + # print ("cool domestic ", uefa_interesting_domestic_games) + # for c in NAS15: + # print (c) + # for (t1,t2) in uefa_interesting_domestic_games[c]: + # print (getTeamById[t1], getTeamById[t2] , quality_of_game_dom(t1, t2),quality_of_game_dom(t2, t1)) + # for t1 in uefa_interesting_domestic_teams[c]: + # print (getTeamById[t1]) + # exit(0) + best_dom= {(c,t1,t2,d) : pulp.LpVariable('best_dom_'+c+'_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for c in NAS15 for (t1,t2) in uefa_interesting_domestic_games[c] for d in days } + best_dom_early = { (c,d) : pulp.LpVariable('best_dom_early_'+str(d) + '_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for d in days for c in NAS15 } + + + if thisSeason.nicename[:3]=="UCL" : + for (t1,t2) in uefa_interesting_games : + for (r,d) in roundDays: + model2 += best_glo[(t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games]) == 1 + rd =getRoundDaysByDay[d] + for t in teams: + model2+= lpSum( [ quality_of_game(t1,t2)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_games if t in [t1,t2]]) \ + <= lpSum( [ best_glo[(t1,t2,d)]*quality_of_game(t1,t2) for (t1,t2) in uefa_interesting_games ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_games : + rd= getRoundDaysByDay[d][0] + model2 += best_glo[(t1,t2,d) ] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_global_early[d] + + + # TODO + # Avoid playing early the match with the highest domestic coefficient in NAs 1-5 if it is 10 or more à priority B + # nas 5 fran + + for c in NAS15: + for (t1,t2) in uefa_interesting_domestic_games[c]: + for (r,d) in roundDays: + model2 += best_dom[(c,t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_dom[(c,t1,t2,d)] for (t1,t2) in uefa_interesting_domestic_games[c]]) <= 1 + rd =getRoundDaysByDay[d] + for t in uefa_interesting_domestic_teams[c]: + model2+= lpSum( [ quality_of_game_dom(t1,t2)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_domestic_games[c] if t in [t1,t2]]) \ + <= lpSum( [ best_dom[(c,t1,t2,d)]*quality_of_game_dom(t1,t2) for (t1,t2) in uefa_interesting_domestic_games[c] ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_domestic_games[c]: + rd= getRoundDaysByDay[d][0] + model2 += best_dom[(c,t1,t2,d)] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_dom_early[(c,d)] + + + # forall (r in ROUNDS, c in NA14COUNTRIES | 1=1) do + # sum(t1, t2 in TEAMS | COUNTRY(t1)=c ) best_dom(t1,t2,r) =1 + # forall (t1 in TEAMS | COUNTRY(t1)=c ) do + # sum (t2 in TEAMS| GROUP(t1)=GROUP(t2) and t1<>t2 and quality_of_game_dom(t1,t2)>4 ) quality_of_game_dom(t1,t2)*(x(t1,t2,r)+x(t2,t1,r)) <= sum(t3,t4 in TEAMS| COUNTRY(t3)=c and GROUP(t3)=GROUP(t4) and t3<>t4) quality_of_game_dom(t3,t4)*best_dom(t3,t4,r) + # end-do + # end-do + + + # writeln ("Avoid the match with the highest domestic coefficient to be played early in NAs 1-5 if it is 10 or more (preference)") + # forall (c in NA14COUNTRIES, r in ROUNDS ) do + # ! Avoid the match with the highest domestic coefficient in NAs 1-5 if it is 10 or more 50 + # ! - put the best domestic coeff match late, if value >10 + + # forall (t1,t2 in TEAMS | exists(x(t1,t2,r)) and COUNTRY(t1)=c ) + # if ( quality_of_game_dom(t1,t2) >=10 ) then + # best_dom(t1,t2,r) <= y(t1,t2,r,1)+ y(t2,t1,r,1) + best_dom_early(r,c) + # end-if + + + specialObjectives= (0.01*sum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys()]) + # Avoid early matches with a global coefficient of 16 and more (60) + +0.6*0.2*sw_prio["AvoidEarlyGlobal"]*sum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)>=sw_int1["AvoidEarlyGlobal"]]) + # Maximise the average global coefficient of early matches (should stay under 16) à priority C + -0.1*sw_prio["MaxAverageEarlyGlobal"]*sum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)=sw_int1["AvoidEarlyDomestic"] or quality_of_game_dom(t2,t1)>=sw_int1["AvoidEarlyDomestic"] ]) + -0.1*sum([quality_of_game(t1,t2) * best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games for d in days ]) + +0.5*0.04*sw_prio["AvoidEarlyHighDomestic"]*sum([best_global_early[d] for d in days ]) + +5*0.04*sw_prio["AvoidEarlyHighGlobal"]*sum([best_dom_early[(c,d)] for c in NAS15 for d in days ]) + ) + + model2+= standardObjectives +specialObjectives + + + # todo : MORE PATTERNS + positions = range (1,9) + # pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for p in positions } + pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in teams for p in positions } + + for c in displayGroups.keys(): + for p in positions: + model2+= lpSum( [ pos[t,p] for t in displayGroups[c]]) <= 1 + model2+= lpSum( [ pos[t,1]-pos[t,2] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,3]-pos[t,4] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,5]-pos[t,6] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,7]-pos[t,8] for t in displayGroups[c]]) == 0 + + + for (t1,t2) in uefa_games: + # Each team has to play each opponent on a Tuesday and on a Wednesday + print (getTeamById[t1], getTeamById[t2]) + if sw_prio['OneTuesdayOneWednesday']>0 and len([d for d in days if getWeekDay[d]=="Tue"])>0: + model2+= lpSum([(x[t1,t2,(r,d)]+x[t2,t1,(r,d)]) for (r,d) in roundDays if getWeekDay[d]=="Tue"]) == 1 + + if use_classic_schedule: + # model2 += pos[t1,4] + pos[t2,1] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[1]]) + # model2 += pos[t2,4] + pos[t1,1] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[1]]) + + # model2 += pos[t1,1] + pos[t2,2] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[2]]) + # model2 += pos[t2,1] + pos[t1,2] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[2]]) + + # model2 += pos[t1,3] + pos[t2,1] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[3]]) + # model2 += pos[t2,3] + pos[t1,1] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[3]]) + + model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,4] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[1]]) + model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,2] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[2]]) + # model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,3] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[3]]) + + model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[1]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[5]]) + model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[2]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[6]]) + # model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[3]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[4]]) + + # every encounter once early once late + if sw_prio['everyEncounterOnceEarly']>0 : + model2+= lpSum([( x_time[(t1,t2,rd,getIdByTime["Early"])] + x_time[(t2,t1,rd,getIdByTime["Early"])] ) for rd in roundDays ]) == 1 + + + # implement classic Tue/Wed patterns + # 1 2 3 4 5 6 7 8 9 10 11 12 + # x x x x x x + if sw_prio['UseClassicWeekdayPatterns']>0: + # print ("using red/blue") + uefa_group_is_blue = {c : pulp.LpVariable('uefa_groupcolor_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in displayGroups.keys() } + bluetuesdays = [ d for d in days if getRoundByDay[d] in [1,4,6] and getWeekDay[d]=="Tue"] + bluewednesdays = [ d for d in days if getRoundByDay[d] in [2,3,5] and getWeekDay[d]=="Wed" ] + model2+= lpSum([uefa_group_is_blue[c] for c in displayGroups.keys()]) == 0.5*len(displayGroups.keys()) + for t in teams: + for d in bluetuesdays: + model2+= home[(t,d)]+away[(t,d)] == uefa_group_is_blue[t_conference[t].name] + for d in bluewednesdays: + model2+= home[(t,d)]+away[(t,d)] == uefa_group_is_blue[t_conference[t].name] + + if sw_prio['Sync1256']>0: + for (t1,t2) in uefa_games: + for d in days: + # groups play same days in rounds 1,2,5,6 + if getRoundByDay[d] in [1,2,5,6] + [3,4]: + model2+= home[(t1,d)]+away[(t1,d)] == home[(t2,d)]+away[(t2,d)] + + for t in teams: + model2 += lpSum( [ pos[t,p] for p in positions ]) ==1 + model2 += homeInRound[t,1]== pos[t,2] +pos[t,4] +pos[t,5] +pos[t,7] + model2 += homeInRound[t,2]== pos[t,1] +pos[t,3] + pos[t,6] +pos[t,8] + model2 += homeInRound[t,3]== pos[t,2] +pos[t,3] + pos[t,6] +pos[t,7] + model2 += homeInRound[t,4]== pos[t,1] +pos[t,4] +pos[t,5] +pos[t,8] + model2 += homeInRound[t,5]== pos[t,1] +pos[t,3] +pos[t,5] +pos[t,7] + model2 += homeInRound[t,6]== pos[t,2] +pos[t,4] + pos[t,6] +pos[t,8] + if use_classic_schedule or sw_prio['UsePosition14']: + model2 += pos[t,5]==0 + model2 += pos[t,6]==0 + model2 += pos[t,7]==0 + model2 += pos[t,8]==0 + print ("forbidding " ,t, 5,6,7,8) + else: + model2 += pos[t,1]==0 + model2 += pos[t,2]==0 + model2 += pos[t,3]==0 + model2 += pos[t,4]==0 + + if sw_prio['NoThreeTuesdaysWednesdays']>0 : + for t in teams: + model2+= lpSum([ home[t,d]+away[t,d] for (r,d) in roundDays if getWeekDay[d]=="Tue" and r<=2]) == 1 + model2+= lpSum([ home[t,d]+away[t,d] for (r,d) in roundDays if getWeekDay[d]=="Tue" and r>=5]) == 1 + + + print ("EXTRA UEFA UCL/UEL run") + writeProgress("Running special model ", thisScenario.id,10) + + print (confTeams.keys()) + print (confName) + # for cname in ["Group A" ,"Group B" ,"UCL" , "UEL Group D" ,"UEL Group A" ,"UEL Group B" , "UEL Group C", "UEL Group E", "UEL Group F", "UEL" , "UECL" ]: + for cname in ["Group A" ,"Group B" ,"UCL" , "UEL Group D" ,"UEL Group A" ,"UEL Group B" , "UEL" , "UECL" ]: + # for cname in ["UCL", "UEL", "UECL"]: + print ("TRYING ", cname) + for c in confTeams.keys(): + # print (confName[c],cname,confName[c]==cname) + if confName[c]==cname : + print () + print () + print (confName[c]) + print ("confTeams" , confTeams[c]) + for p in positions: + for t in confTeams[c]: + pos[t,p].cat = pulp.LpInteger + + if runMode!='Improve': + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.2, TimeLimit=120, msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds=60, keepFiles=True)) + + # posTeam = { p : t for p in positions for t in confTeams[c] if pos[(t,p)].value()>0.9 } + # print ("posTeam",posTeam) + for p in positions: + for t in confTeams[c]: + if pos[(t,p)].value()>0.9: + model2 += pos[(t,p)] == pos[(t,p)].value() + print(p,t) + + for (t1,t2) in uefa_games: + if t1 in confTeams[c]: + for rd in roundDays: + if isUEL: + makeIntVar(x_time[t1,t2,rd,getIdByTime["Early"]]) + else: + makeIntVar(x[t1,t2,rd]) + + + # if solver == "CBC": + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # elif solver == "Gurobi": + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120, msg=1)) + # else: + # model2.solve(XPRESS(msg=1,maxSeconds=60, keepFiles=True)) + + # for (t1,t2) in uefa_games: + # if t1 in confTeams[c]: + # for rd in roundDays: + # if getVal(x[t1,t2,rd]): + # setLB(x[t1,t2,rd],1) + + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # model2+= lpSum( [ - pos[t,p] for p in positions ]) + + # if solver == "CBC": + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # elif solver == "Gurobi": + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + # else: + # model2.solve(XPRESS(msg=1,maxSeconds = 80, keepFiles=True)) + + # for (t,p) in pos.keys(): + # if pos[(t,p)].value()>0.9: + # print ("position ", p , " for " , t) + # model2 += pos[(t,p)] == pos[(t,p)].value() + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + if False: + for d in days: + print (getNiceDay[d] ) + for (t1,t2) in uefa_interesting_games: + if best_glo[(t1,t2,d)].value()>0.01: + print (" Best GLO ",quality_of_game(t1,t2), getTeamById[t1] , getTeamById[t2] , 0.01*int(100*best_glo[(t1,t2,d)].value()) ) + print () + for c in NAS15: + print ( "checking " , c, uefa_interesting_domestic_games[c] ) + for (t1,t2) in uefa_interesting_domestic_games[c]: + print ( " -- " , getTeamById[t1] , getTeamById[t2] ) + print ( " --- " , (c,t1,t2,d) in best_dom.keys() ) + print ( " ---- " , best_dom[(c,t1,t2,d)].value() , best_dom[(c,t1,t2,d)].value() ==None ) + if not (c,t1,t2,d) in best_dom.keys() : + print (c,t1,t2,d, " not in " , best_dom) + if best_dom[(c,t1,t2,d)].value() and best_dom[(c,t1,t2,d)].value()>0.01: + print (" Best DOM ",c ,getTeamById[t1] , getTeamById[t2] , 0.01*int(100*best_dom[(c,t1,t2,d)].value())) + + +if mathModelName=="EuroLeague Basketball" : + show_TV_markets = True + + day2pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=2) and getRoundByDay[d1] <37] + # trip_ebl= {(t,d) : pulp.LpVariable('trip_'+str(t)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for (d,d2) in day2pairs} + + print ("day2pairs", day2pairs) + + doubleWeekendDays = { d1 : [d1,d2,d3,d4] for (d1,d2) in day2pairs for (d3,d4) in day2pairs if getDateTimeDay[d3]-getDateTimeDay[d1] ==datetime.timedelta(days=1) } + print ("doubleWeekendDays", doubleWeekendDays) + + criticalDayPairs = [ (d1,d2) ] + + getDW = { getDayById[dw]["round"] : dw for dw in doubleWeekendDays.keys() } + + elb_rounds = getDW.keys() + elb_rounds1 = [ r for r in elb_rounds if r <= nRounds1] + elb_rounds2 = [ r for r in elb_rounds if r > nRounds1] + + criticalAvailabilities = {r: [] for r in elb_rounds} + for d1 in doubleWeekendDays.keys(): + dd = doubleWeekendDays[d1] + for day in [dd[0],dd[3]]: + criticalAvailabilities[ getRoundByDay[day] ] =[t for t in teams if available_days[t,day]==0] + + canTravel={t: elb_rounds for t in teams } + + + # closeTeams_EBL_raw={'ALBA Berlin': ['FC Bayern Munich', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'Zenit St. Petersburg', 'CSKA Moscow', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'AS Monaco'], 'Anadolu Efes Istanbul': ['Maccabi FOX Tel Aviv', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'CSKA Moscow', 'Crvena Zvezda mts Belgrade', 'UNICS Kazan', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan'], 'AS Monaco': ['LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'FC Barcelona', 'Real Madrid', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'TD Systems Baskonia Vitoria-Gasteiz'], 'AX Armani Exchange Milan': ['LDLC ASVEL Villeurbanne', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'Zalgiris Kaunas'], 'Crvena Zvezda mts Belgrade': ['ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'Zalgiris Kaunas', 'Maccabi FOX Tel Aviv', 'FC Barcelona', 'CSKA Moscow', 'AS Monaco'], 'CSKA Moscow': ['Zenit St. Petersburg', 'Zalgiris Kaunas', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'UNICS Kazan', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'FC Barcelona': ['Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus'], 'FC Bayern Munich': ['ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'AS Monaco', 'Zenit St. Petersburg', 'CSKA Moscow', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Fenerbahce Beko Istanbul': ['Maccabi FOX Tel Aviv', 'Anadolu Efes Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'CSKA Moscow', 'UNICS Kazan', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan'], 'LDLC ASVEL Villeurbanne': ['AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'Zalgiris Kaunas'], 'Maccabi FOX Tel Aviv': ['Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'Crvena Zvezda mts Belgrade'], 'Olympiacos Piraeus': ['Panathinaikos OPAP Athens', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi FOX Tel Aviv', 'Crvena Zvezda mts Belgrade', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AS Monaco'], 'Panathinaikos OPAP Athens': ['Olympiacos Piraeus', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi FOX Tel Aviv', 'Crvena Zvezda mts Belgrade', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AS Monaco'], 'Real Madrid': ['TD Systems Baskonia Vitoria-Gasteiz', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'AS Monaco'], 'TD Systems Baskonia Vitoria-Gasteiz': ['Real Madrid', 'FC Barcelona', 'AS Monaco', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus'], 'UNICS Kazan': ['CSKA Moscow', 'Zenit St. Petersburg', 'Zalgiris Kaunas', 'Crvena Zvezda mts Belgrade', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Zalgiris Kaunas': ['Zenit St. Petersburg', 'CSKA Moscow', 'UNICS Kazan', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Crvena Zvezda mts Belgrade', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Zenit St. Petersburg': ['CSKA Moscow', 'UNICS Kazan', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich']} + closeTeams_EBL_raw= { + 'ALBA Berlin': ['FC Bayern Munich','Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade', + 'Zalgiris Kaunas', 'LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan', + 'AS Monaco','Panathinaikos OPAP Athens' , 'Olympiacos Piraeus','Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul',], + 'Anadolu Efes Istanbul': ['Maccabi Playtika Tel Aviv', 'Fenerbahce Beko Istanbul', 'Olympiacos Piraeus', + 'Panathinaikos OPAP Athens', 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade', + 'Zalgiris Kaunas', 'ALBA Berlin','FC Bayern Munich', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan'], + 'AS Monaco': ['LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna','AX Armani Exchange Milan', 'FC Bayern Munich', 'FC Barcelona', + 'Real Madrid', 'ALBA Berlin', 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade','Panathinaikos OPAP Athens' , 'Olympiacos Piraeus', 'Baskonia Vitoria-Gasteiz', 'Valencia Basket', ], + 'AX Armani Exchange Milan': ['Virtus Segafredo Bologna','LDLC ASVEL Villeurbanne', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade', + 'Zalgiris Kaunas','Panathinaikos OPAP Athens' , 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'Baskonia Vitoria-Gasteiz', 'Valencia Basket','AS Monaco'], + 'Crvena Zvezda mts Belgrade': [ 'Partizan Nis Belgrade', 'ALBA Berlin', 'FC Bayern Munich', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan', + 'LDLC ASVEL Villeurbanne','Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Olympiacos Piraeus', 'Panathinaikos OPAP Athens','Zalgiris Kaunas','Maccabi Playtika Tel Aviv' , 'AS Monaco'], + 'FC Barcelona': ['Real Madrid', 'Baskonia Vitoria-Gasteiz', 'Valencia Basket', 'AS Monaco', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna', + 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade', "FC Bayern Munich"], + 'FC Bayern Munich': ['ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade','Zalgiris Kaunas', + 'AS Monaco', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna', 'Panathinaikos OPAP Athens' , 'Olympiacos Piraeus', + 'FC Barcelona', 'Real Madrid', 'Baskonia Vitoria-Gasteiz', 'Valencia Basket','Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul' ], + 'Fenerbahce Beko Istanbul': ['Maccabi Playtika Tel Aviv', 'Anadolu Efes Istanbul', 'Olympiacos Piraeus', + 'Panathinaikos OPAP Athens', 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade', + 'Zalgiris Kaunas', 'ALBA Berlin','FC Bayern Munich', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan'], + 'LDLC ASVEL Villeurbanne': [ 'AS Monaco', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan','FC Bayern Munich','ALBA Berlin', + 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade','Panathinaikos OPAP Athens' , 'Olympiacos Piraeus', + 'FC Barcelona', 'Real Madrid', 'Valencia Basket', 'Baskonia Vitoria-Gasteiz', ], + 'Maccabi Playtika Tel Aviv': ['Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Partizan Nis Belgrade', 'Crvena Zvezda mts Belgrade','Panathinaikos OPAP Athens' , 'Olympiacos Piraeus', ], + 'Olympiacos Piraeus': [ 'Panathinaikos OPAP Athens', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi Playtika Tel Aviv', + 'Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade','Virtus Segafredo Bologna', 'AX Armani Exchange Milan','FC Bayern Munich', + 'ALBA Berlin','LDLC ASVEL Villeurbanne','AS Monaco'], + 'Panathinaikos OPAP Athens': [ 'Olympiacos Piraeus', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi Playtika Tel Aviv', + 'Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade','Virtus Segafredo Bologna', 'AX Armani Exchange Milan','FC Bayern Munich', + 'ALBA Berlin','LDLC ASVEL Villeurbanne','AS Monaco'], + 'Partizan Nis Belgrade' : [ 'Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade', 'ALBA Berlin', 'FC Bayern Munich', 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan', + 'LDLC ASVEL Villeurbanne','Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Olympiacos Piraeus', 'Panathinaikos OPAP Athens','Zalgiris Kaunas','Maccabi Playtika Tel Aviv' , 'AS Monaco'], + 'Real Madrid': ['Baskonia Vitoria-Gasteiz', 'FC Barcelona','Valencia Basket', 'LDLC ASVEL Villeurbanne', + 'Virtus Segafredo Bologna', 'AX Armani Exchange Milan','FC Bayern Munich','Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade','AS Monaco' ], + 'Baskonia Vitoria-Gasteiz': [ 'Real Madrid', 'FC Barcelona', 'Valencia Basket','LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna', + 'AX Armani Exchange Milan', 'AS Monaco','FC Bayern Munich' ], + 'Valencia Basket': [ 'Real Madrid', 'FC Barcelona', 'Baskonia Vitoria-Gasteiz','LDLC ASVEL Villeurbanne', 'Virtus Segafredo Bologna', + 'AX Armani Exchange Milan', 'AS Monaco','FC Bayern Munich' ], + 'Virtus Segafredo Bologna': [ 'Real Madrid', 'FC Barcelona','Valencia Basket' , 'Baskonia Vitoria-Gasteiz','LDLC ASVEL Villeurbanne', + 'AX Armani Exchange Milan', 'AS Monaco','FC Bayern Munich' , 'ALBA Berlin','Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade', 'Olympiacos Piraeus', 'Panathinaikos OPAP Athens'], + 'Zalgiris Kaunas': ['ALBA Berlin','FC Bayern Munich','Virtus Segafredo Bologna', 'AX Armani Exchange Milan','Crvena Zvezda mts Belgrade', 'Partizan Nis Belgrade','Anadolu Efes Istanbul', + 'Fenerbahce Beko Istanbul', 'LDLC ASVEL Villeurbanne','AS Monaco'] + } + + closeTeams_EBL= { getTeamIdByName[tn] : [ getTeamIdByName[tn2] for tn2 in closeTeams_EBL_raw[tn] ] for tn in closeTeams_EBL_raw.keys() } + + singleTrips_EBL = [ (t1,t2,r) for t2 in teams for t1 in closeTeams_EBL[t2] for r in elb_rounds ] + + print(elb_rounds) + + # for t2 in teams: + # for t1 in closeTeams_EBL[t2]: + # # if not t2 in closeTeams_EBL[t1]: + # # print (getTeamById[t2] ," not in list of ", getTeamById[t1]) + # for r in canHostSingle[t1]: + # print (" single trip in round " ,r , " : ", t1 , t2 , "possible" ) + + print (len (singleTrips_EBL) , " single trips") + + + conferences_ebl = [c for c in Conference.objects.filter(scenario=s2,regional=False).exclude(name='REOPT')] + neighbors_ebl = [c for c in Conference.objects.filter(scenario=s2,regional=True).exclude(name='REOPT') ] + + print ("conferences_ebl", conferences_ebl) + print ("neighbors_ebl", neighbors_ebl) + + getConference={} + getNeighborId={} + for c in conferences_ebl: + if len(c.teams.all())>3: + for t in c.teams.filter(active=True): + getConference[t.id]=c.id + else: + for (d1,d2) in day2pairs: + print ("forbid games for " , c.name, " in days" , d1,d2) + # model2 += lpSum( [x[t1.id, t2.id, rd] for t1 in c.teams.all() for t2 in c.teams.all() for rd in getRoundDaysByDay[d1]+getRoundDaysByDay[d2] if t1.id!=t2.id ]) == 0 + + + + getTeamsOfNeighbor={} + getNameOfNeighbor={} + getConfId={} + for c in neighbors_ebl: + getTeamsOfNeighbor[c.id]=[] + print (c.id, c.name) + getConfId[c.name]=c.id + getNameOfNeighbor[c.id]=c.name + for t in c.teams.filter(active=True): + getNeighborId[t.id]=c.id + getTeamsOfNeighbor[c.id].append(t.id) + print ( " - " , t.id , "\t",canTravel[t.id], "\t", " " , t.name) + # otherConference= { t: [ t2 for t2 in teams if getConference[t]!=getConference[t2] ] for t in teams } + # otherNeighbor= { t: [ t2 for t2 in teams if getNeighborId[t]!=getNeighborId[t2] ] for t in teams if t in getNeighborId.keys()} + + # print ("otherConference = ", otherConference) + # print ("otherNeighbor = ", otherNeighbor) + + + getConfOfNeighbor={} + getNeighborsInConf={ c : set([]) for c in set(getConference.values()) } + for t in teams : + # print (getTeamById[t]) + # print ("-",getNeighborId[t]) + # print ("-",getConference[t]) + + + getConfOfNeighbor[getNeighborId[t]]=getConference[t] + getNeighborsInConf[getConference[t]].add(getNeighborId[t]) + print (getConference[t],getNeighborId[t], getTeamById[t] ) + + # tripCands= + + print ("getConfOfNeighbor = ", getConfOfNeighbor) + print ("getNeighborsInConf = ", getNeighborsInConf) + + possTrips = {} + + for g1 in neighbors_ebl: + for g2 in neighbors_ebl: + if getConfOfNeighbor[g1.id]!=getConfOfNeighbor[g2.id] : + for t1 in getTeamsOfNeighbor[g1.id]: + for t2 in getTeamsOfNeighbor[g2.id]: + for r in elb_rounds: + if (t2,r,g1.id) not in possTrips.keys(): + possTrips[t2,r,g1.id]=[] + possTrips[t2,r,g1.id].append(t1) + + + remtrg= [ trg for trg in possTrips.keys() if len(possTrips[trg])==1] + + for trg in remtrg : + del possTrips[trg] + + for trg in possTrips.keys(): + print ("++" , trg, possTrips[trg]) + + trips_ebl = [ (t1,t2,r) for (t2,r,g) in possTrips.keys() for t1 in possTrips[(t2,r,g)]] + + games_ebl= set([(t1,t2) for (t1,t2,r) in trips_ebl + singleTrips_EBL ]) + + far_teams = [(t1,t2) for (t1,t2) in games_ebl if t1 not in closeTeams_EBL[t2]] + far_teams = {t : [ t1 for (t1,t2) in far_teams if t2==t] for t in teams} + + # print (far_games) + + # for t in teams: + # print (getTeamById[t], " :") + # for t2 in far_games[t]: + # print (" - " , getTeamById[t2]) + + + fixed_travels= [] + # fixed_travels= [(283, 15, 'A'), (296, 6, 'A'), (289, 24, 'A'), (293, 31, 'A'), (299, 20, 'A'), (287, 3, 'A'), (288, 10, 'A'), (297, 24, 'A'), (281, 24, 'B'), (283, 6, 'B'), (289, 15, 'B'), (293, 3, 'B'), (299, 6, 'B'), (297, 20, 'B'), (281, 6, 'C'), (296, 31, 'C'), (287, 10, 'C'), (288, 24, 'C'), (286, 20, 'D'), (291, 31, 'D'), (295, 24, 'D'), (282, 20, 'D'), (290, 3, 'D'), (298, 10, 'D'), (294, 15, 'D'), (300, 20, 'D'), (286, 6, 'E'), (291, 3, 'E'), (295, 10, 'E'), (282, 24, 'E'), (298, 31, 'E'), (284, 20, 'E'), (294, 3, 'E'), (300, 10, 'E'), (290, 15, 'F'), (284, 3, 'F')] + # fixed_travels= [ (getTeamById[t2],r,g) for (t2,r,g) in fixed_travels] + # fixed_travels= [('Zenit St. Petersburg', 24, 'A'), ('CSKA Moscow', 10, 'A'), ('Fenerbahce Beko Istanbul', 31, 'A'), ('Anadolu Efes Istanbul', 20, 'A'), ('Panathinaikos OPAP Athens', 3, 'A'), ('Olympiacos Piraeus', 24, 'A'), ('Crvena Zvezda mts Belgrade', 10, 'A'), ('UNICS Kazan', 20, 'B'), ('Zenit St. Petersburg', 15, 'B'), ('CSKA Moscow', 6, 'B'), ('Maccabi FOX Tel Aviv', 3, 'B'), ('Anadolu Efes Istanbul', 3, 'B'), ('Olympiacos Piraeus', 31, 'B'), ('Crvena Zvezda mts Belgrade', 6, 'B'), ('UNICS Kazan', 24, 'C'), ('Maccabi FOX Tel Aviv', 24, 'C'), ('Fenerbahce Beko Istanbul', 10, 'C'), ('Panathinaikos OPAP Athens', 10, 'C'), ('Real Madrid', 6, 'D'), ('TD Systems Baskonia Vitoria-Gasteiz', 6, 'D'), ('LDLC ASVEL Villeurbanne', 20, 'D'), ('AX Armani Exchange Milan', 10, 'D'), ('FC Bayern Munich', 3, 'D'), ('ALBA Berlin', 10, 'D'), ('Real Madrid', 31, 'E'), ('TD Systems Baskonia Vitoria-Gasteiz', 15, 'E'), ('FC Barcelona', 6, 'E'), ('AS Monaco', 10, 'E'), ('LDLC ASVEL Villeurbanne', 24, 'E'), ('AX Armani Exchange Milan', 31, 'E'), ('Zalgiris Kaunas', 6, 'E'), ('ALBA Berlin', 20, 'E'), ('FC Barcelona', 15, 'F'), ('AS Monaco', 24, 'F'), ('Zalgiris Kaunas', 3, 'F'), ('FC Bayern Munich', 15, 'F')] + + print (len (games_ebl)) + + xxx_travel_rounds = sorted( list(elb_rounds) + [ r+1 for r in elb_rounds ]) + + xxx_rounds = xxx_travel_rounds + # xxx_rounds = rounds + # xxx_rounds = sorted( list(elb_rounds) + [ r+1 for r in elb_rounds ]) + + travel_to_cluster= {(t2,r,g) : pulp.LpVariable('travel_to_cluster_'+str(t2)+'_'+str(r)+'_'+str(g), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t2,r,g) in possTrips.keys()} + + if runMode=="New": + + model8 = pulp.LpProblem("League_Scheduling_Model_--_Assign_EBL_Trips_"+str(thisScenario.id), pulp.LpMinimize) + + xxx = { (t1,t2,r) : pulp.LpVariable('xxx_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for r in xxx_rounds } + toomany = { (t1,r) : pulp.LpVariable('toomany_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds } + + for (t1,t2,r) in (singleTrips_EBL+trips_ebl) : + xxx[(t1,t2,r)].cat=pulp.LpInteger + xxx[(t1,t2,r)].upBound=1 + + home_ebl = { (t1,r) : lpSum([ xxx[(t1,t2,r)] for t2 in teams ]) for t1 in teams for r in xxx_rounds} + away_ebl = { (t2,r) : lpSum([ xxx[(t1,t2,r)] for t1 in teams ]) for t2 in teams for r in xxx_rounds} + + print (home_ebl.keys()) + + for t1 in teams: + for t2 in teams: + model8+= lpSum([ xxx[(t1,t2,r)]+xxx[(t2,t1,r)] for r in xxx_rounds if r<=17 ]) <= 1 + model8+= lpSum([ xxx[(t1,t2,r)]+xxx[(t2,t1,r)] for r in xxx_rounds if r>=18 ]) <= 1 + + model8+=lpSum([ xxx[(t1,t2,r)] for (t1,t2) in games_ebl for r in xxx_rounds ]) + + # do not visit two teams in doubleweeks which would result in a B2B + for r in elb_rounds: + for t in teams: + lhs1 = [ xxx[(t1,t,r)] for t1 in criticalAvailabilities[r] if t!=t1] + lhs2= [ xxx[(t2,t,r+1)] for t2 in criticalAvailabilities[r+1] if t!=t2] + if len(lhs1)*len(lhs2)>0: + model8+= lpSum( lhs1+lhs2 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r] and len(lhs2)>0: + model8+= home_ebl[(t,r)] + lpSum( lhs2 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r+1] and len(lhs1)>0: + model8+= home_ebl[(t,r+1)] + lpSum( lhs1 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r] and t in criticalAvailabilities[r+1] : + model8+= home_ebl[(t,r)] + home_ebl[(t,r+1)] <= 1 + toomany[(t,r)] + + for t1 in teams: + for r in xxx_rounds: + model8+=xxx[(t1,t1,r)]==0 + # one game a day1 + model8+= home_ebl[(t1,r)] + away_ebl[(t1,r)] ==1 + # print ("one game for ", getTeamById[t1] , " in round ", r, home_ebl[(t1,r)] + away_ebl[(t1,r)] ) + # half of the games played at home + if len(xxx_rounds) < len(rounds): + model8+= lpSum ( [ home_ebl[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + + for (t1,t2) in games_ebl: + model8+=lpSum([ xxx[(t1,t2,r)] for r in xxx_rounds ]) <=1 + if (t2,t1) in games_ebl and t117 ]) <=1 + model8+=lpSum([ xxx[(t1,t2,r)]+ xxx[(t2,t1,r)] for r in xxx_rounds if r>=13 and r<=19 ]) <=1 + + # model8+=lpSum([ xxx[(t1,t2,r)] for (t1,t2) in games_ebl for r in xxx_rounds ]) + + # no_travel_elb = [3,27,31] + # no_travel_elb = [27] + # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , + # ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , + # ("ALBA Berlin", 27 ) ] + # # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , ("ALBA Berlin", 27 ), ("Anadolu Efes Istanbul", 27 ) ] + # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , + # ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , + # ("Anadolu Efes Istanbul", 27 ), ("Zalgiris Kaunas", 27 ) ] + no_travel_elb = [] + for r in elb_rounds: + for t in teams: + ss1 = sum ( t_blocked_at[t,r2] for r2 in [r-2,r-1] ) + ss2 = sum ( t_blocked_at[t,r2] for r2 in [r-1,r+2] ) + ss3 = sum ( t_blocked_at[t,r2] for r2 in [r+2,r+3] ) + print (r, ss1,ss2,ss3, getTeamById[t]) + urt = max( t_blocked_at[t,r-2]+t_blocked_at[t,r-1] , t_blocked_at[t,r-1]+t_blocked_at[t,r+2] , t_blocked_at[t,r+2]+t_blocked_at[t,r+3] ) + if urt ==2 : + print ("DO NOT TRAVEL ") + no_travel_elb.append((getTeamById[t],r)) + + print ("no_travel_elb",no_travel_elb) + + travel_elb ={} + for t in teams: + # every team travels to other clusters twice + model8+= lpSum( [travel_to_cluster[ (t2,r,g)] for (t2,r,g) in possTrips.keys() if t==t2 ])== 2 + for r in elb_rounds: + travel_elb[(t,r)] = lpSum( [travel_to_cluster[ (t2,r2,g)] for (t2,r2,g) in possTrips.keys() if t==t2 and r==r2 ]) + print ("TRAVEL 1 ", t, r, travel_elb[(t,r)]) + model8+= away_ebl[(t,r)] + away_ebl[(t,r+1)] <= 1 + travel_elb[(t,r)] + model8+= lpSum([ xxx[(t1,t,r)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + model8+= lpSum([ xxx[(t1,t,r+1)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + + # model8+= travel_elb[(t,9)] + travel_elb[(t,13)] == 1 + # model8+= travel_elb[(t,18)] + travel_elb[(t,22)] == 1 + + model8+=lpSum([ travel_elb[(t,r)] for r in elb_rounds if r<=17 ]) ==1 + + + for (t,r) in no_travel_elb : + model8+= travel_elb[(getTeamIdByName[t],r)] ==0 + print ("no travel", getTeamIdByName[t],t,r) + + for (t2,r,g) in travel_to_cluster.keys(): + model8+= travel_to_cluster[ (t2,r,g)] <= lpSum( [xxx[(t1,t2,r)] for t1 in getTeamsOfNeighbor[g] ] ) + model8+= travel_to_cluster[ (t2,r,g)] <= lpSum( [xxx[(t1,t2,r+1)] for t1 in getTeamsOfNeighbor[g] ] ) + + for (t2,r,g) in fixed_travels: + print ( "fixed_travel " , t2,r,g) + model8 += travel_to_cluster[(getTeamIdByName[t2],r,getConfId[g])]==1 + + + # model8 += home_ebl [(getTeamIdByName["FC Bayern Munich"],24)] + home_ebl[(getTeamIdByName["FC Bayern Munich"],25)] <= 1 + # model8 += home_ebl [(getTeamIdByName["ALBA Berlin"],31)] + home_ebl [(getTeamIdByName["ALBA Berlin"],32)] >=1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],6)] + home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],7)] >=1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],10)] ==1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],11)] ==1 + # model8 += home_ebl [(getTeamIdByName["Zalgiris Kaunas"],21)] ==1 + # model8 += home_ebl [(getTeamIdByName["AS Monaco"],4)] ==1 + # model8 += home_ebl [(getTeamIdByName["AS Monaco"],6)]+ home_ebl [(getTeamIdByName["AS Monaco"],7)] >=1 + + for (t1,t2,r) in xxx.keys(): + if t_blocked_at[(t1,r)] and getTeamById[t2]!="ALBA Berlin" and False: + model8 += xxx[(t1,t2,r)]==0 + +# TEAMS;5;120 +# TEAMSROUNDS +# ROUNDS;10 +# GROUPS + + + + # model8+=lpSum([ xxx[(t1,t2,r)] for t1 in teams for t2 in teams for r in xxx_rounds ]) + # model8.solve(XPRESS(msg=1,maxSeconds = 160, options=["THREADS=12"], keepFiles=True)) + # for r in xxx_rounds : + # print ("Round ", r) + # for (t1,t2) in games_ebl : + # if xxx[(t1,t2,r)].value()>0.5: + # print (" " ,getTeamById[t1], " - " , getTeamById[t2] ) + # exit(0) + + + + blockvioTrips_ebl = lpSum( [travel_to_cluster[ (t2,r,g)] for (t2,r,g) in possTrips.keys() if t_blocked_at[(t1,r-1)] and t_blocked_at[(t1,r+2)]]) + blockvios_ebl=lpSum([ xxx[(t1,t2,r)] for (t1,t2,r) in xxx.keys() if t_blocked_at[(t1,r)] ]) + usegames_ebl=lpSum([ xxx[(t1,t2,r)] for (t1,t2,r) in xxx.keys() if (t1,t2) in games_ebl ]) + toomanyaways = lpSum([ toomany[(t,r)] for (t,r) in toomany.keys() ]) + + # model8+= usegames_ebl ==126 + model8+= usegames_ebl == len(teams) /2*len(xxx_rounds) + + model8+= blockvios_ebl + blockvioTrips_ebl + toomanyaways - 0.1*lpSum([ xxx[(t1,t2,r)] for (t1,t2) in games_ebl for r in xxx_rounds ]) + + if solver == "CBC": + model8.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 240, threads = 8,msg=1)) + elif solver == "Gurobi": + model8.solve(GUROBI( TimeLimit=240,msg=1)) + else: + model8.solve(XPRESS(msg=1,maxSeconds = 160, options=["THREADS=12"], keepFiles=True)) + + for r in xxx_rounds : + print ("Round ", r) + for (t1,t2) in games_ebl : + if xxx[(t1,t2,r)].value()>0.5: + print (" ",xxx[(t1,t2,r)].value() ,getTeamById[t1], " - " , getTeamById[t2] ) + + + + fixed_travels = [ ( getTeamById[t2],r, getNameOfNeighbor[g]) for (t2,r,g) in possTrips.keys() if travel_to_cluster[(t2,r,g)].value()>0.1 ] + + print ("fixed_travels=", fixed_travels) + + for t1 in teams: + model2+= lpSum ( [ homeInRound[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + tooFarViolation = { (t1,r) : pulp.LpVariable('tooFarViolation_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 10, cat = pulp.LpContinuous) for t1 in teams for r in xxx_rounds } + + for (t,r) in travel_elb.keys(): + if travel_elb[(t,r)].value()>=0.9: + print ("TRAVEL ",r, getTeamById[t], travel_elb[(t,r)].value()) + else: + for t1 in far_teams[t]: + model2+= x_round[(t1,t,r)]==0 + model2+= x_round[(t1,t,r+1)]== 0 + model2+= x_round[(t1,t,r+1)]<= tooFarViolation[(t,r+1)] + model2+= awayInRound[(t,r)]+awayInRound[(t,r+1)] <=1 + + print (len (travel_elb.keys())) + + + specialObjectives = 1000 * lpSum([ tooFarViolation[(t,r)] for (t,r) in tooFarViolation.keys() ]) + + + for (t,r) in toomany.keys(): + if toomany[(t,r)].value()>0.9: + print ("too many " ,r, " ", getTeamById[t]) + + + for (t2,r,g) in fixed_travels: + model2+= lpSum( [x_round[(t1,getTeamIdByName[t2],r)] for t1 in getTeamsOfNeighbor[getConfId[g]] ] ) ==1 + model2+= lpSum( [x_round[(t1,getTeamIdByName[t2],r+1)] for t1 in getTeamsOfNeighbor[getConfId[g]] ] ) ==1 + + for r in xxx_rounds: + print () + print ("ROUND ", r) + for (t1,t2) in games_ebl: + if xxx[(t1,t2,r)].value()>0.1: + bcked = "#### " if t_blocked_at[(t1,r)] else " - " + print (getTeamById[t1] , bcked , getTeamById[t2] , " ", xxx[(t1,t2,r)].value()) + model2+= x_round[(t1,t2,r)]==1 + + print (blockvios_ebl.value()) + print (blockvioTrips_ebl.value()) + print (toomanyaways.value()) + + else: + print ("++++++++++ IMPROVE SOLUTION ++++++++++") + for t1 in teams: + # half of the games played at home + model2+= lpSum ( [ homeInRound[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + travel_elb ={} + for t in teams: + # every team travels to other clusters twice + model2+= lpSum( [travel_to_cluster[ (t2,r,g)] for (t2,r,g) in possTrips.keys() if t==t2 ])== 2 + for r in elb_rounds: + travel_elb[(t,r)] = lpSum( [travel_to_cluster[ (t2,r2,g)] for (t2,r2,g) in possTrips.keys() if t==t2 and r==r2 ]) + print ("TRAVEL 1 ", t, r, travel_elb[(t,r)]) + model2+= awayInRound[(t,r)] + awayInRound[(t,r+1)] <= 1 + travel_elb[(t,r)] + model2+= lpSum([ x_round[(t1,t,r)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + model2+= lpSum([ x_round[(t1,t,r+1)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + + for (t2,r,g) in travel_to_cluster.keys(): + model2+= travel_to_cluster[ (t2,r,g)] <= lpSum( [x_round[(t1,t2,r)] for t1 in getTeamsOfNeighbor[g] ] ) + model2+= travel_to_cluster[ (t2,r,g)] <= lpSum( [x_round[(t1,t2,r+1)] for t1 in getTeamsOfNeighbor[g] ] ) + +if mathModelName=="UEFA NL" : + + if thisSeason.name=="2024" : + + clashes_raw =[] + clashes_raw +=['ARM-AZE','UKR-RUS','GIB-ESP','KOS-BIH','KOS-SRB','KOS-RUS','UKR-BLR'] + # clashes_raw +=['SRB-MKD','SRB-BIH','SRB-ALB','SRB-CRO','MKD-ALB','MKD-GRE','BIH-CRO','TUR-CYP','TUR-ARM','KOS-ARM','KOS-AZE','KOS-BLR','KOS-CYP','KOS-GEO','KOS-GRE','KOS-ISR','KOS-KAZ','KOS-MDA','KOS-ROU','KOS-RUS','KOS-SVK','KOS-ESP','KOS-UKR'] + clashes = [ (c[:3],c[4:]) for c in clashes_raw if c[:3] in countries and c[4:] in countries ] + print ("clashes", clashes) + + print ( thisSeason.name=="2024") + + american_teams = [t for t in teams if t_lon[t] <=-20] + european_teams = [t for t in teams if t_lon[t] >-20] + + print ("american_teams", american_teams) + print ("european_teams", european_teams) + + usedWeekdays = set([getWeekDay[d] for d in days]) + # print (getNiceDay[d['id']], getWeekDay[d['id']]) + + # print (t_conference) + + day3pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=3) ] + print (day3pairs) + + # possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0]] + possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0] and not (t_country[t1],t_country[t2]) in clashes and not (t_country[t2],t_country[t1]) in clashes ] + nopossGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if not (t1,t2) in possGames_UEFA ] + + # possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0]] + # nopossGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1==t2 or t_conference[t1].name[0]!=t_conference[t2].name[0]] + + print ("") + print (len(possGames_UEFA), "possGames_UEFA") + print (len(nopossGames_UEFA), "nopossGames_UEFA") + print ("") + + # for (t1,t2) in possGames_UEFA: + # print (getTeamById[t1] , " might play ", getTeamById[t2] , t_conference[t1].name[0] , t_conference[t2].name[0]) + + for (t1,t2) in nopossGames_UEFA: + for rd in roundDays: + if (t1,t2,rd) in x.keys(): + setUB(x[(t1,t2,rd)],0) + + # model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays]) == 0 + print (getTeamById[t1] , " will not play ", getTeamById[t2] , t_conference[t1].name[0] , t_conference[t2].name[0]) + + + for (t1,t2) in possGames_UEFA: + if t10: + model2 += lpSum([x[(t1,t2,(r,d))] for (t1,t2,(r,d)) in all_american_games_A ]) ==12 + if len(all_american_games_B)>0: + model2 += lpSum([x[(t1,t2,(r,d))] for (t1,t2,(r,d)) in all_american_games_B ]) == 4 + + for (t1,t2,(r,d)) in all_american_games_A: + if r in [1,2,7,8]: + model2 += x[(t1,t2,(r,d))]==0 + + for (t1,t2,(r,d)) in all_american_games_B: + if r in [3,4,5,6,7,8]: + model2 += x[(t1,t2,(r,d))]==0 + + + + + for t in teams: + if not getTeamById[t] in ["Iceland", "Faroe Islands"]: + for rn in [3,4,5,6,7,8]: + model2 += break3InRound[(t,rn)]== 0 + # if getTeamById[t]=="Ukraine": + # model2 += awayInRound[(t1,7)]== 1 + # model2 += awayInRound[(t1,8)]== 1 + + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in teams for rd in roundDays if t_conference[t]!=t_conference[t2]]) == 4 + model2 += lpSum([home[(t,d)] for d in days ]) == 4 + + for (d1,d2) in day3pairs: + model2 += home[(t,d1)]+away[(t,d1)] == home[(t,d2)]+ away[(t,d2)] + + if t_conference[t].name[0]=="A" : + for pp in ["A1","A2"]: + model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 4 + # 2 home matches against pot 1, 2 home matches against pot 2 and 2 away matches against pot 1, 2 away matches against pot 2 + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 2 + + # if t in american_teams: + # model2 += lpSum([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() + + # if t1 in american_teams and t2 in american_teams and r in [3,4,5,6]] [x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 4 + + + + # model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) >= 2 + + # For league B, the 16 European teams are split in 4 pots and the last four CONMEBOL teams (VEN, PAR, ECU, BOL) join them. + # The swiss system is still used and each team must play two teams from each pot (8MDs). + # For the CONMEBOL teams, two different splits are possible: one team per pot or 2 teams in the two first pots. + # Could you please try both options and see which one is the most convenient? + # In addition, the CONMEBOL teams must play each other but only over one international window + # (let's say in September 2024) as they don't have enough opponents to play 4 MDs against each other. + if t_conference[t].name[0]=="B" : + for pp in ["B1","B2","B3","B4"]: + model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 2 + # 1 home match against one team from each pot and 1 away match against one team from each pot + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 1 + # model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) >= 1 + + # For league C, the format is different and it is not a swiss system anymore. + # A proposal would be to have 4 groups of 4 teams but to pair 2 groups together (1A and 1B together, 2A and 2B together). + # The four teams from one group (1A) would play home and away the four teams from the other group (1B). + # In the excel table, you will see the group proposal. + + for (c1,c2) in [("C1A","C1B"), ("C2A","C2B")]: + if t_conference[t].name==c1: + for t2 in teams : + if t_conference[t2].name==c2: + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays]) == 1 + model2 += lpSum([x[(t2,t,rd)] for rd in roundDays]) == 1 + model2 += lpSum([x[(t,t2,(r,d))] + x[(t2,t,(r,d))] for (r,d) in roundDays if r<=4]) == 1 + + + # use every weekday at least once: + # for wd in usedWeekdays: + # model2 += lpSum([home[(t,d)] + away[(t,d)] for d in days if getWeekDay[d]==wd ]) >= 1 + + # for t2 in teams: + # if gameCntr[(t,t2)]+gameCntr[(t2,t)]>0: + # print("adding" , t , t2) + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for rd in roundDays if t!=t2]) <= 1 + # if t_conference[t].name[0]!=t_conference[t2].name[0]: + # model2 += lpSum([x[(t,t2,rd)] for rd in roundDays if t!=t2]) == 0 + # # print (getTeamById[t] , " does not play ", getTeamById[t2] , t_conference[t].name[0] , t_conference[t2].name[0] ) + + # # else: + # # print("ignoring" , t , t2) + + # print ("TESTING") + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + # print ("TESTING DONE") + + + print (t_conference) + + + # for t in american_teams: + # for (r,d) in getRoundDaysByRound[3]: + # if t!= american_teams[0]: + # model2 += home[(t,d)]+away[(t,d)] == home[(american_teams[0],d)]+away[(american_teams[0],d)] + + # for r in [3,4,5,6]: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in getRoundDaysByRound[r] if t!=t2]) == 1 + # for r in [1,2,7,8]: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in getRoundDaysByRound[r] if t!=t2]) == 0 + + # league_A_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ] + # league_B_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" ] + # league_C_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="C" and t_conference[t2].name[0]=="C" ] + league_A1_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" if r in [3,4,5,6] ]) + league_A_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ]) + league_B_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" ]) + league_B1_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" if r <=4 ]) + league_C_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="C" and t_conference[t2].name[0]=="C" ]) + + # all_american_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in american_teams and t2 in american_teams and r in [3,4,5,6]] + # half_american_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in american_teams != t2 in american_teams and r in [1,2,7,8]] + # all_european_games1 = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in european_teams and t2 in european_teams and r in [1,2,3,4]] + # all_european_games2 = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in european_teams and t2 in european_teams and r in [5,6,7,8]] + + if runMode=='New': + + # print (len(league_A_games)) + # print (len(league_B_games)) + # print (len(league_C_games)) + + + optIterations = [] + if len(league_C_games)>0: + optIterations+=[(league_C_games, " league_C_games ","rounds")] + optIterations+=[(league_A1_games, " league_A1_games ","rounds" )] + optIterations+=[(league_A_games, " league_A_games " ,"rounds")] + optIterations+=[(league_B1_games, " league_B1_games " ,"rounds")] + optIterations+=[(league_B_games, " league_B_games " ,"rounds")] + else: + league_A1_games = set([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" if r in [3,4,5,6] ]) + league_A_games = set([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ]) + optIterations+=[(league_A1_games, " league_A1_games " ,"days")] + optIterations+=[(league_A_games, " league_A_games " , "days")] + + + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ), ( half_american_games, " HALF AMERICAN " ) , ( all_european_games1, " all_european_games1 " ) , ( all_european_games2, " all_european_games2 " ) ] : + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ), ( all_european_games1, " all_european_games1 " ) , ( all_european_games2, " all_european_games2 " ) ] : + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ) ] : + for gms,txt,gran in optIterations : + print ("!!!!!!! PRE RUN "+txt+"!!!!!!") + print (len(gms)) + for rr in gms: + if gran=="rounds": + makeIntVar(x_round[rr]) + else: + makeIntVar(x[rr]) + + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.1, TimeLimit=240,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 140, options=["THREADS=12"], keepFiles=True)) + + for rr in gms: + if gran=="rounds": + model2+= x_round[rr] ==getVal(x_round[rr]) + if getVal(x_round[rr]) >0.1: + print (rr , getVal(x_round[rr])) + else: + model2+= x[rr] ==getVal(x[rr]) + if getVal(x[rr]) >0.1: + print (rr , getVal(x[rr])) + # for t in european_teams: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in roundDays ]) >= 1 + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in roundDays ]) <= 2 + + + else : + + # model2+=gamesTooCloseTotal==0 + specialObjectives+=1000*gamesTooCloseTotal + + # closeTeams_UEFA = { "Kazakhstan" : [ "Andorra", "England","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + closeTeams_UEFA = { "Kazakhstan" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", + "Northern Ireland", "Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + "Azerbaijan" : ["Gibraltar", "Iceland", "Portugal"], + "Iceland" : ["Armenia", "Cyprus", "Georgia", "Israel"] } + + # Label to represent both teams (Kazakstan/Modolva - Cyprus/Estonia) to carry both constrains of each pair. + # closeTeams_UEFA = { "KAZ/MDA 1" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + # "KAZ/MDA 2" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + # "Azerbaijan" : ["Gibraltar", "Iceland", "Portugal"], + # "Iceland" : ["Armenia", "CYP/EST 1", "CYP/EST 2", "Georgia", "Israel"] } + + # for tn1 in closeTeams_UEFA.keys(): + # for tn2 in closeTeams_UEFA[tn1]: + # t1 = getTeamIdByName[tn1] + # t2 = getTeamIdByName[tn2] + # print (t1, t2 , t_conference[t1] ,t_conference[t2] , t_conference[t1]==t_conference[t2] , tn1, tn2 ) + + closeTeams_UEFA = [ (getTeamIdByName[tn1],getTeamIdByName[tn2]) for tn1 in closeTeams_UEFA.keys() for tn2 in closeTeams_UEFA[tn1] if t_conference[getTeamIdByName[tn1]]==t_conference[getTeamIdByName[tn2]] ] + print ("closeTeams_UEFA", [(getTeamById[t1] ,getTeamById[t2]) for (t1,t2) in closeTeams_UEFA]) + + # for (tn1,tn2) in closeTeams_UEFA: + # t1 = getTeamById[tn1] + # t2 = getTeamById[tn2] + # print (t1, t2 , t_conference[tn1] ,t_conference[tn2] , t_conference[tn1]==t_conference[tn2] , tn1, tn2 ) + + + # 1234 56 + two_starter_uefa= [5] + mid_starter_uefa= [2,3] + three_starter_uefa= [1] + + two_starter_uefa= [1,3,5,7,9] + mid_starter_uefa= [] + three_starter_uefa= [] + # for t in teams: + # if noPlayRounds[t] == [7,8]: + # two_starter_uefa= [7,9] + # mid_starter_uefa= [2,5] + # three_starter_uefa= [1,4] + + print ("two_starter_uefa", two_starter_uefa) + print ("three_starter_uefa", three_starter_uefa) + + # TODO + # no back to back in md 3 and 4 + # no break in md 5/6 + # max 3 rest days between games + + day3pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=3) ] + day34pairs ={ d1 : [d2 for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] in [datetime.timedelta(days=di) for di in [3,4]]] for d1 in days } + day345pairs ={ d1 : [d2 for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] in [datetime.timedelta(days=di) for di in [3,4,5]]] for d1 in days } + + closeDays_UEFA = two_starter_uefa+mid_starter_uefa+three_starter_uefa + closeDays_UEFA = [] + + if "3DaysForDistantTeams" in special_wishes_active: + sw_type="3DaysForDistantTeams" + for (t1,t2) in closeTeams_UEFA: + # model2 += lpSum( [ x[t1,t2,rd] + x[t2,t1,rd] for rd in getRoundDaysByRound[4]] ) ==1 + # model2 += lpSum( [ x[t1,t2,rd] + x[t2,t1,rd] for rd in getRoundDaysByRound[1]] ) ==1 + for (d1,d2) in day3pairs: + specialWishVio[(sw_type,t1,t2,d1)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t1)+"_"+str(t2)+"_"+str(d1), lowBound=0, cat=pulp.LpContinuous) + specialWishItems[sw_type].append((t1,t2,d1)) + for t3 in teams: + if t_conference[t1]==t_conference[t3] and not t3 in [t1,t2]: + print ("taking care of " , getTeamById[t3] , "'s trip to ", getTeamById[t1] , " and ", getTeamById[t2] ) + model2 += lpSum( [ x[t,t3,rd] for t in [t1,t2] for rd in getRoundDaysByDay[d1]+getRoundDaysByDay[d2] ] ) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t1,d1] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByDay[d2]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t1,d2] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByDay[d1]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t2,d1] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByDay[d2]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t2,d2] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByDay[d1]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + specialObjectives += 1000*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t1,t2,d1)] for (t1,t2,d1) in specialWishItems[sw_type] ]) + + + # for (t1,t2) in closeTeams_UEFA: + # for rn in closeDays_UEFA: + # for t3 in teams: + # if t_conference[t1]==t_conference[t3] and not t3 in [t1,t2]: + # # print ("taking care of " , getTeamById[t3] , "'s trip to ", getTeamById[t1] , " and ", getTeamById[t2] ) + # model2 += lpSum( [ x[t1,t3,d] + x[t2,t3,d] for d in getRoundDaysByRound[rn]+getRoundDaysByRound[rn+1] ] ) <=1 + + # # print ("I don't care about " , getTeamById[t1] , "'s trip to ", getTeamById[t2]) + # # print ("taking care of " , getTeamById[t1] , "'s trip to ", getTeamById[t2]) + # model2 += homeInRound[(t1,rn)] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[rn+1]]) <=1 + # model2 += homeInRound[(t1,rn+1)] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[rn]]) <=1 + # # print ("taking care of " , getTeamById[t2] , "'s trip to ", getTeamById[t1]) + # # if getTeamById[t2]!="France": + # model2 += homeInRound[(t2,rn)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn+1]]) <=1 + # model2 += homeInRound[(t2,rn+1)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn]]) <=1 + # # else: + # # if rn>1: + # # model2 += homeInRound[(t2,rn-1)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn]]) + homeInRound[(t2,rn+1)] <=2 + + print ("getRoundDaysByRound", getRoundDaysByRound) + + conferences6 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==6] + conferences5 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==5] + conferences4 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==4] + conferences3 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==3] + + # print ("day34pairs",day34pairs) + # print (conferences4) + # print ("confs 3 " , len(conferences3)) + # print ("confs 4 " , len(conferences4)) + # print ("confs 5 " , len(conferences5)) + # print ("confs 6 " , len(conferences6)) + + # alltms =set([]) + + c4= {(c.id,d) : pulp.LpVariable('c4_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences4 for d in days} + c5= {(c.id,d) : pulp.LpVariable('c5_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences5 for d in days} + c6= {(c.id,d) : pulp.LpVariable('c6_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences6 for d in days} + + + travelControl_UEFA= False + travelControl_UEFA= True + dontPlayOnGamesList_UEFA= ["2022-06-04", "2022-06-05", "2022-06-08", "2022-06-11", "2022-06-12"] + if runMode!='Improve': + dontPlayOnGamesList_UEFA+= ["2022-06-02"]+ ["2022-06-13"] + ["2022-06-06","2022-06-09"] + dontPlayOnGamesList_UEFA = [ parse(dd) for dd in dontPlayOnGamesList_UEFA ] + print("dontPlayOnGamesList_UEFA",dontPlayOnGamesList_UEFA) + dontPlayOnGamesList_UEFA = [ getDayByDateTime[dd] for dd in dontPlayOnGamesList_UEFA if dd in getDayByDateTime.keys()] + print("dontPlayOnGamesList_UEFA",dontPlayOnGamesList_UEFA) + if travelControl_UEFA : + for c in conferences4: + cteams= c.teams.filter(active=True) + # print ("checking group" , c ) + for t in cteams: + # print ("checking team" , t ) + for (t1,t2) in closeTeams_UEFA: + if t.id==t1: + for d1 in sorted(dontPlayOnGamesList_UEFA ): + model2 += c4[(t_conference[t1].id,d1)] == 0 + print (c.name , " cannot play on ", d1 , " " , getNiceDay[d1]) + + for c in conferences3+conferences4: + cteams= c.teams.filter(active=True) + for t1 in cteams: + for t2 in cteams: + if t2.id < t1.id: + model2 += lpSum( [ x[t1.id,t2.id,d] + x[t2.id,t1.id,d] for d in getRoundDaysByRound[3]+getRoundDaysByRound[4] ]) <=1 + + forbidBackToBack = True + + # when are the next games allowed to be played + compNextGames = { 1:[4,5], 2:[5,6], 3:[6,7], + 4:[7,8,9], 5:[8,9,10], 6:[9,10], + 7:[10,11,12], + 8:[11,12], 9:[12,13], 10:[13], + 14:[17,18], 15:[18,19], 16:[19], + } + + if "forbid4DaysOff" in special_wishes_active : + compNextGames[4]=[7,8] + compNextGames[5]=[8,9] + compNextGames[7]=[10,11] + + if runMode!='Improve': + compNextGames[14]=[17] + compNextGames[15]=[18] + + if "3RestDaysBetweenMD2andMD3" in special_wishes_active: + compNextGames[4]=[8] + compNextGames[5]=[9] + compNextGames[6]=[10] + + for c in conferences4: + cteams= c.teams.filter(active=True) + t1 = cteams.first() + for t2 in cteams: + # no b2b between rounds 3 and 4 + # if t2.id != t1.id : + # model2 += lpSum( [ x[t1.id,t2.id,d] + x[t2.id,t1.id,d] for d in getRoundDaysByRound[3]+getRoundDaysByRound[4] ]) <=1 + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] == c4[(c.id,d1)] + # adjust pattern abcbca + # for t1 in cteams: + # for t2 in cteams: + # if t1.id != t2.id: + # model2 += lpSum( [ x[t1.id,t2.id,d] for d in getRoundDaysByRound[2]]) == lpSum( [ x[t2.id,t1.id,d] for d in getRoundDaysByRound[4]]) + # model2 += lpSum( [ x[t1.id,t2.id,d] for d in getRoundDaysByRound[3]]) == lpSum( [ x[t2.id,t1.id,d] for d in getRoundDaysByRound[5]]) + # for (d1,d2) in day3pairs: + # if getRoundByDay[d1]>=5: + # model2 += c4[(c.id,d1)] == c4[(c.id,d2)] + for di in compNextGames.keys(): + model2 += c4[(c.id,daysSorted[di-1])] <= lpSum([c4[(c.id,daysSorted[di2-1])] for di2 in compNextGames[di]]) + + for c in conferences6: + cteams= c.teams.filter(active=True) + for t2 in cteams: + # alltms.add(t2.id) + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] <= c6[(c.id,d1)] + + if runMode!='Improve': + for (d1,d2) in day3pairs: + model2 += c6[(c.id,d1)] == c6[(c.id,d2)] + else: + for d1 in day34pairs.keys(): + if len(day34pairs[d1])>0: + model2 += c6[(c.id,d1)] <= lpSum([c6[(c.id,d2)] for d2 in day34pairs[d1]]) + + for r in rounds: + model2 += lpSum( [ c6[(c.id,d)] for d in getDays[r] ] ) == 1 + + model2 += lpSum( [ c6[(c.id,d)] for d in days ] ) == 10 + + c5_repeater={} + for c in conferences5: + cteams= c.teams.filter(active=True) + for t2 in cteams: + # alltms.add(t2.id) + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] <= c5[(c.id,d1)] + if runMode!='Improve': + for (d1,d2) in day3pairs: + model2 += c5[(c.id,d1)] == c5[(c.id,d2)] + else: + for d1 in day34pairs.keys(): + if len(day34pairs[d1])>0: + model2 += c5[(c.id,d1)] <= lpSum([c5[(c.id,d2)] for d2 in day34pairs[d1]]) + + for r in rounds: + model2 += lpSum( [ c5[(c.id,d)] for d in getDays[r] ] ) == 1 + + model2 += lpSum( [ c5[(c.id,d)] for d in days ] ) == 10 + + + for t1 in cteams: + for t2 in cteams: + if t1.id= -1 + + + # t_in_French_Group={t4 : False for t4 in teams} + # for c in allConferences: + # tms = [t.id for t in c.teams.filter(active=True)] + # print ("C " , tms) + # franceFound = False + # for tt in tms: + # if getTeamById[tt]== "France": + # franceFound = True + # if franceFound: + # for tt in tms: + # t_in_French_Group[tt]= True + # print ("found french friend " , tt , getTeamById[tt]) + # + + + # for t in teams: + # if not t_usePhases[t] and not t_in_French_Group[t]: + # model2 += lpSum([c5_repeater[(t1,t2)] for (t1,t2) in c5_repeater.keys() if t in [t1,t2]]) <=1 + + + if len(conferences4)>0 : + for d1 in days: + model2 += lpSum( [ c4[(c.id,d1)] for c in conferences4 ] ) >=0 + model2 += lpSum( [ c4[(c.id,d1)] for c in conferences4 ] ) <=5 + + # todo: no 3*5 c4 on days 1,2,6 -> spacing for c3 possible + # todo: no 3*5 c4 on days 1,5,6 -> spacing for c3 possible + + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [1,2,6]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [1,5,6]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [8,9,13]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [8,12,13]] ) <=14 + # for di in [1,2,3,4,5,6, 8,9,10,11,12,13]: + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 ] ) >=3 + + + if runMode!='Improve': + print ("EXTRA UEFA NL run") + writeProgress("Running special model ", thisScenario.id,10) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 40, options=["THREADS=12"], keepFiles=True)) + + for (c,d) in c4.keys(): + model2 += c4[(c,d)] == c4[(c,d)].value() + if c4[(c,d)].value() >0.9: + print ("c4 " , c, getNiceDay[d] ) + + + for (c,d) in c5.keys(): + model2 += c5[(c,d)] == c5[(c,d)].value() + if c5[(c,d)].value() >0.01: + print (c, d, c5[(c,d)].value() ) + + +print ("thisSeason.useFeatureBackToBack", thisSeason.useFeatureBackToBack) +if thisSeason.useFeatureBackToBack: + print ("critical_day_pairs", critical_day_pairs) + print ("back2backBlocks", back2backBlocks) + fiveDayFourRounds=[] + for d in days: + fiveDays = [ d2 for d2 in days if getDateTimeDay[d]<= getDateTimeDay[d2] and getDateTimeDay[d2]-getDateTimeDay[d]<=datetime.timedelta(days=4) ] + fourRounds = [] + for d2 in fiveDays: + fourRounds+=[ r2 for r2 in getRoundsByDay[d2] ] + fourRounds=list(set(fourRounds)) + # print (d, fiveDays,fourRounds , len(fourRounds)>=4) + if len(fourRounds)>=4: + fiveDayFourRounds.append(fiveDays) + + bad_travel_in = { t : [] for t in teams} + bad_travel_out = { t : [] for t in teams} + for (t1,t2,c) in bad_travels: + bad_travel_in[t2].append((t1,color_weight[c])) + bad_travel_out[t1].append((t2,color_weight[c])) + + for fdfr in fiveDayFourRounds: + for t in realteams: + model2 += lpSum([ home[(t,d)] + away[(t,d)] for d in fdfr ]) <= 3 + 0.001*badBackToBack[(t,fdfr[0])] + # print(t, " cannot play more than three times in ", fdfr) + + for (d1,d2) in critical_day_pairs: + # print ("no time zone crossings for at days " , getNiceDay[d1]," -> ",getNiceDay[d2],nextCritical[d2]) + for t in realteams: + + # print ("counting b2b for ", getTeamById[t]) + model2 += home[(t,d1)]+home[(t,d2)]+away[(t,d1)]+away[(t,d2)] <= 1 + badBackToBack[(t,d1)] + rd1s=getRoundDaysByDay[d1] + rd2s=getRoundDaysByDay[d2] + + if nRounds <= 72 and nextCritical[d2]: + # print ("no 3 series for " , t , " at days " , d1,d2,nextCritical[d2]) + model2 += lpSum([ home[(t,d)] + away[(t,d)] for d in [d1,d2,nextCritical[d2]] ]) <= 2 + 0.0001*badBackToBack[(t,d1)] + + for (tms1,tms2,w) in back2backBlocks: + model2 += lpSum([x[(t1,t,rd1)] for rd1 in rd1s for t1 in tms1 if gameCntr[(t1,t)]>0]) + lpSum([x[(t2,t,rd2)] for rd2 in rd2s for t2 in tms2 if gameCntr[(t2,t)]>0 ]) <= 1 + 0.001/w*badBackToBack[(t,d1)] + + if currentAwayLocation[(t,d1)] and currentAwayLocation[(t,d2)]: + t1 = currentAwayLocation[(t,d1)] + for (t2,w) in bad_travel_out[t1]: + if t2==currentAwayLocation[(t,d2)]: + print ("TRAVEL BACK TO BACK FOUND WITH WEIGHT ", w, ":", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[currentAwayLocation[(t,d1)]] , " -> " , getTeamById[currentAwayLocation[(t,d2)]] ) + model2 += lpSum([x[(t1,t,rd1)] for rd1 in rd1s ]) + lpSum([x[(t2,t,rd2)] for rd2 in rd2s ]) <= 1 + 0.001/w*badBackToBack[(t,d1)] + + # todo : add red teams to distant_teams here to take care that trips do not start and end with bad b2b + # print ("after home game no away game at " , rd1s, rd2s, "for ", t , " in ", distant_teams[t]) + if not blocked_arena[(t,d1,"----")]: + model2 += home[(t,d1)] + lpSum([ wg*x[(t2,t,rd2)] for rd2 in rd2s for (t2,wg) in bad_travel_out[t] if gameCntr[(t2,t)]>0 ]) <= 1 + 0.001*badBackToBack[(t,d1)] + if not blocked_arena[(t,d2,"----")]: + # if d1==32363 and getTeamById[t]=="Cleveland Cavaliers": + # print (" ++++ ",getTeamById[t] , rd1s, bad_travel_in[t] ) + # for t33 in bad_travel_in[t]: + # print("+++++ " ,t33, getTeamById[t33] , gameCntr[(t33,t)]) + # print (" ++++ ",lpSum([ wg*x[(t2,t,rd1)] for rd1 in rd1s for (t2,wg) in bad_travel_in[t] if gameCntr[(t2,t)]>0 ]) ) + model2 += lpSum([ wg*x[(t2,t,rd1)] for rd1 in rd1s for (t2,wg) in bad_travel_in[t] if gameCntr[(t2,t)]>0 ]) + home[(t,d2)] <= 1 + 0.001*badBackToBack[(t,d1)] + + badBackToBack_Total = lpSum([badBackToBack[(t,d1)] for t in teams for (d1,d2) in critical_day_pairs]) + specialObjectives+= 50*gew['Breaks']* badBackToBack_Total +# END SPECIAL CONSTRAINTS + +model2+= standardObjectives +specialObjectives + +print("Model built now solving .... ") + +nRuns =1 +maxSolveTime = 300 + +if thisSeason.groupBased: + maxSolveTime = 40 + +mipgap=0.01 + +# print ("######## Testing") +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + + +use_LP_heuristic= False +print (runMode=='New' , useBasicGames , runPatternAssignmentFirst) + +if runMode=='New' and useBasicGames and runPatternAssignmentFirst: + print ('Coupling Home Away to patterns', use_LP_heuristic) + + if use_LP_heuristic: + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=maxSolveTime,msg=1)) + roundedGames = { (t1,t2,r) : gameInBasicRound[(t1,t2,r)].value() for r in rounds1 for t1 in realteams for t2 in realteams if t1!=t2 } + + for g in roundedGames: + if roundedGames[g]>0: + print (g, " : " ,roundedGames[g]) + + model5 = pulp.LpProblem("League Scheduling Model -- LP heuristic_"+str(thisScenario.id), pulp.LpMinimize) + x5={(t1,t2,r) : pulp.LpVariable('x4_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for (t1,t2,r) in roundedGames.keys() } + for t1 in realteams: + for t2 in realteams: + if t10.1: + print (r, " " , t1," - " , t2, " : " , roundedGames[(t1,t2,r)]) + gameInBasicRound[(t1,t2,r)].lowBound = 1 + gameInBasicRound[(t1,t2,r)].upBound = 1 + gameInBasicRound[(t2,t1,r+nTeams-1)].lowBound = 1 + gameInBasicRound[(t2,t1,r+nTeams-1)].upBound = 1 + + else: + # use2BreakPatterns + undecidedGames = gameCntr.copy() + for t in realteams: + for r in basicRounds: + if r > 0 and runPatternAssignmentFirst and False: + # print ("homeInBasicRound[(",str(t),",",str(r), " ] == " , int(homePat[(t,r)].value())) + # print ("awayInBasicRound[(",str(t),",",str(r), " ] == " , 1-homePat[(t,r)].value()) + # homeInBasicRound[(t,r)].lowBound = homePat[(t,r)].value() + homeInBasicRound[(t,r)].upBound = homePat[(t,r)].value() + # awayInBasicRound[(t,r)].lowBound = 1-homePat[(t,r)].value() + awayInBasicRound[(t,r)].upBound = awayPat[(t,r)].value() + + for (t1,t2) in games: + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = 1 + gameInBasicRound[(t1,t2,r)].upBound = 0 + + cntr =0 + for (t1,t2,r) in chosenGames: + # print (t1,t2,r) + homers = [t1] + regionalKids[t1] + awayers = [t2] + regionalKids[t2] + # print (" chosen game " ,r, homers , awayers, getRoundDaysByBasicRound[r] , getMaxGameOnRoundDaysByBasicRound[r],defaultGameRepetions) + # print (" chosen game " , getMaxGameOnRoundDaysByBasicRound[r]) + theseRounds = sorted(list(set([ r2 for (r2,d) in getRoundDaysByBasicRound[r]]))) + if len(theseRounds)>0: + fr = theseRounds[0] + lr = theseRounds[-1] + # print ( " RDS " , theseRounds , theseRounds[0], min([ r2 for (r2,d) in getRoundDaysByBasicRound[r]]) ) + for t11 in homers: + for t22 in awayers: + #print (" - chosen game " , getTeamById[t11], getTeamById[t22], getRoundDaysByBasicRound[r] , getMaxGameOnRoundDaysByBasicRound[r]) + # model2 += gameInBasicRound[(t11,t22,r)] >= 1 - 0.9*missingGamesVio[(t11,t22)] + if (t11,t22,r) in gameInBasicRound.keys(): + gameInBasicRound[(t11,t22,r)].lowBound = defaultGameRepetions-1 + gameInBasicRound[(t11,t22,r)].upBound = defaultGameRepetions + # print ("setting ",(t11,t22,r) , defaultGameRepetions-1, defaultGameRepetions ) + + if tripStartHeuristicGroupsize>=2 and False: + model2+= x_round[(t11,t22,fr)] == x_round[(t11,t22,fr+1)] + model2+= x_round[(t11,t22,fr+2)] == x_round[(t11,t22,fr+3)] + # print ("setting " , t11,t22, " twice") + # model2+= x_round[(t1,t2,lr)] == x_round[(t1,t2,lr-1)] + + # print ( t11,t22, (t11,t22) in undecidedGames, undecidedGames) + undecidedGames[(t11,t22)]-=defaultGameRepetions + cntr+=1 + else: + print ("PROBLEM " , theseRounds, homers, awayers, r) + print (cntr, " games set") + # print (len(undecidedGames), " undecided Games :", undecidedGames) + for (t1,t2) in games: + # if gameCntr[(t1,t2)]>0: + # print ("to be scheduled : " , getTeamById[ t1], " --- " , getTeamById[ t2] , gameCntr[(t1,t2)]) + if undecidedGames[(t1,t2)]!=0 and undecidedGames[(t1,t2)]!=-undirectedGameCntr[(t1,t2)]: + print ("not scheduled yet " , getTeamById[ t1], " --- " , getTeamById[ t2] , undecidedGames[(t1,t2)]) + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].upBound = defaultGameRepetions + + for ttr in x.keys(): + makeIntVar(x[ttr]) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 120, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.3, TimeLimit=180,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 120, options=["THREADS=12"], keepFiles=True)) + + for ttr in x.keys(): + if getVal(x[ttr])>0.9: + # print ( "SETTING GAME ", ttr, getVal(x[ttr]) ) + setLB(x[ttr],1) + + for (t1,t2) in games: + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = defaultGameRepetions + # if gameInBasicRound[(t1,t2,r)].value()>0 and gameInBasicRound[(t1,t2,r)].value()<2 : + # print ("??",getTeamById[t1],getTeamById[t2],r, gameInBasicRound[(t1,t2,r)].value()) + + + + +# # TEST START +# for t1 in realteams: +# for t2 in realteams: +# if t1!=t2: +# for r in basicRounds: +# gameInBasicRound[(t1,t2,r)].lowBound = 0 +# gameInBasicRound[(t1,t2,r)].upBound = 0 + +# cntr =0 +# undecidedGames = [ (t1,t2) for t1 in realteams for t2 in realteams if t1!=t2 ] +# for (t1,t2,r) in chosenGames: +# homers = [t1] + regionalKids[t1] +# awayers = [t2] + regionalKids[t2] +# for t11 in homers: +# for t22 in awayers: +# gameInBasicRound[(t11,t22,r)].lowBound = 1 +# gameInBasicRound[(t11,t22,r)].upBound = 1 +# undecidedGames.remove((t11,t22)) +# cntr+=1 +# print (cntr, " games set") +# print (len(undecidedGames), " undecided Games :", undecidedGames) +# for (t1,t2) in undecidedGames: +# for r in basicRounds: +# gameInBasicRound[(t1,t2,r)].upBound = 1 + +# for ttr in x.keys(): +# x[ttr].cat= LpInteger + +# if solver == "CBC": +# model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) +# elif solver == "Gurobi": +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) +# else: +# model2.solve(XPRESS(msg=1,maxSeconds = 25, keepFiles=True)) + +# for ttr in x.keys(): +# if x[ttr].value()>0.01: +# print ( ttr, x[ttr].value() ) + + +# # TEST END + + +else: + nRuns=nPhases + # maxSolveTime = 120 + mipgap=0.0 + # mipgap=0.95 + +# nRuns =1 +onlyReopt= True +onlyReopt= False +onlyFewTrips= False +singleTripWeight =10 + +if onlyReopt: + nRuns=0 + +maxIntRound=nRounds + +print (optSteps) + +missing_imp=[] +cntr =0 +for st in optSteps: + print (" - " ,st ) + cntr +=1 + if runMode=='New' and st[0] == "HARDCONSTRAINTS": + for bl in blockings: + blockingVio[bl['id']].upBound=0 + print ("blocking tightened : " , getTeamById [bl['team_id']] , bl['day_id'] ) + for enc in encwishes: + if enc['reason']=="Seed Game": + encVio[enc['id']].upBound=0 + + + if runMode=='New' and len(st)>=1 and st[0] in ["PATTERNS","HOMEAWAY", "BASICGAME", "GAME","GAMES", "GROUP", "TRIPS", "LP-HEURISTIC"]: + newRounds = [] + print() + optTarget = st[0] + if len(st)>1 : + for rf in st[1].split(","): + rr= rf.split("-") + if len(rr)==1 : + newRounds.append(min(nRounds,int(rr[0]))) + else: + for ii in range (int(rr[0]), min(nRounds, int(rr[1])+1)): + newRounds.append(ii) + newRoundsString = st[1] + else : + newRounds = rounds + newRoundsString = "1-"+str(nRounds) + + optsteptime = maxSolveTime + optstepgap = mipgap + + if len(st)>=3 and st[2]!="": + optstepgap = float(st[2]) + if len(st)>=4 and st[3]!="": + optsteptime = float(st[3]) + + print (newRounds) + + # if not thisSeason.minBreaks and runMode=='Improve': + if st[0] == "LP-HEURISTIC": + print("STARTING LP HEURISTIC") + + if st[0] == "PATTERNS": + for (p,t,ph) in assignPattern2.keys(): + assignPattern2[(p,t,ph)].cat = pulp.LpInteger + + + if st[0] == "HOMEAWAY": + for t in teams: + for r in newRounds: + homeInRound[(t,r)].cat = pulp.LpInteger + + if st[0] == "BASICGAME": + for (t1,t2) in games: + for r in newRounds: + gameInBasicRound[(t1,t2,r)].cat = pulp.LpInteger + + if st[0] in ["GAME","GAMES"]: + for (t,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t,t2,rd)]) + # if thisSeason.gamesPerRound=="one day": + # makeIntVar(x[(t,t2,getRoundDaysByRound[r][0])]) + # print ("makeintvar " ,t1,t2, getRoundDaysByRound[r][0],getRoundDaysByRound[r]) + # else: + # for rd in getRoundDaysByRound[r]: + # makeIntVar(x[(t,t2,rd)]) + + getSingleTripElementsByRound = { r : [] for r in rounds} + if st[0] in ["TRIP","TRIPS"]: + singleTripWeight =1000 + # onlyFewTrips= True + for (t1,d,c) in tripToSingleTripElement.keys(): + getSingleTripElementsByRound[getRoundByDay[d]].append((t1,d,c)) + for r in newRounds: + for tdc in getSingleTripElementsByRound[r]: + makeIntVar(tripToSingleTripElement[tdc]) + + if st[0] == "GROUP" and len(st)>=5: + cfname = st[4].strip() + if cfname in conf_teams.keys(): + optTarget += " "+ cfname + print ("checking GROUP", st[4].strip()) + for (t1,t2) in games: + optstepgap = mipgap + if t1 in conf_teams[cfname] and t2 in conf_teams[cfname]: + print ("REOPT GROUP ", cfname , t1,t2) + for r in newRounds: + makeIntVar(x_round[(t1,t2,r)]) + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t1,t2,rd)]) + + + # optsteptime= 30 + + print ('########################') + print ('# SOLVING MODEL '+optTarget+' FOR ROUNDS '+ newRoundsString+' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' #') + print ('########################') + writeProgress("Optimize "+st[0]+" for rounds " + newRoundsString, thisScenario.id, int( cntr/len(optSteps)*100 )) + + if RUN_ENV == 'celery' and task: + task.update_state(state='Solving Model '+str(st[0]), meta={'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = optstepgap, maxSeconds = optsteptime, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=optstepgap, TimeLimit=optsteptime,msg=1, Method=2,NodeMethod=2)) + else: + # for debugging: + # with open ("model2.txt", "w") as f: + # f.write(model2.__repr__()) + model2.solve(XPRESS(msg=1,targetGap=optstepgap, maxSeconds = optsteptime, options=["THREADS=12,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + # model2.solve(XPRESS(msg=1,targetGap=optstepgap, maxSeconds = optsteptime, options=["THREADS=12,DEFAULTALG=4,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + # # model2.solve(XPRESS()) + # model2.solve(XPRESS(path= "/opt/xpressmp/bin/optimizer",msg=True, keepFiles=True)) + + if model2.status<0: + print("Status: " , model2.status) + if solver != "xpress": + writeProgress("Model infeasible .", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print({'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print('Model could not be solved') + + if not lowerBoundFound: + lowerBoundFound=value(model2.objective) + + cntr_rnd =0 + if st[0] == "LP-HEURISTIC": + for (t1,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.65: + setLB(x[(t1,t2,rd)],1) + print ("rounding up " , getTeamById[t1], "-" ,getTeamById[t2], rd , getVal(x[(t1,t2,rd)])) + cntr_rnd +=1 + # print (t1,t2,rd) + # else: + # setUB(x[(t1,t2,rd)],0) + # # print(t1,t2 , " no game ") + # # x[(t1,t2,rd)].upBound = 0 + print ("Totally rounded up " , cntr_rnd) + + + if st[0] == "PATTERNS": + for (p,t,ph) in assignPattern2.keys(): + if assignPattern2[(p,t,ph)].value() >0.9 : + print ('fixing pattern '+ str(p) + ' : '+ getTeamById[t] +" in phase " + str(ph)) + assignPattern2[(p,t,ph)].lowBound = 1 + else: + assignPattern2[(p,t,ph)].upBound = 0 + + + if st[0] == "HOMEAWAY": + print (teams) + print (newRounds) + for t in teams: + for r in newRounds: + if homeInRound[(t,r)].value() >0.9 : + print ('fixing home '+ str(r) + ' : '+ getTeamById[t] +" " + str(homeInRound[(t,r)].value())) + homeInRound[(t,r)].lowBound = 1 + else: + homeInRound[(t,r)].upBound = 0 + + + if st[0] == "BASICGAME": + for r in newRounds: + for (t1,t2) in games: + if getTeamById[t1]!="-" and getTeamById[t2]!="-" and (t1,t2,r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1,t2,r)])!= int: + if getVal(gameInBasicRound[(t1,t2,r)])>0.9: + gameInBasicRound[(t1,t2,r)].lowBound = 1 + else: + gameInBasicRound[(t1,t2,r)].upBound = 0 + # print (r, ' : ', getTeamById[t1], ' - ',getTeamById[t2] ) + + if st[0] in ["GAME","GAMES"]: + # crappyGames = [ g for g in fixedGames if fixedGameVio[g].value() >0.9 ] + feedback = "Optimize games...." + missing_imp=[] + for (t1,t2,d) in fixedGames: + if fixedGameVio[(t1,t2,d)].value() >0.9: + feedback += 'Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + '
' + for (t1,t2,d) in fixedGames2: + if fixedGame2Vio[(t1,t2,d)].value() >0.9: + feedback += 'Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + '
' + for (t1,t2) in realgames: + if missingGamesVio[(t1,t2)].value() >0.9: + feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str(missingGamesVio[(t1,t2)].value()) + '
\n' + missing_imp += [(1,nRounds,[t1,t2], 50)] + + print (missing_imp) + print (feedback) + print ("number of assigned games : " , sum([getVal(x[ttrd]) for ttrd in x.keys()])) + + for (t1,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.9: + setLB(x[(t1,t2,rd)],1) + print ("fixing " ,t1,t2,rd, x[(t1,t2,rd)].lowBound ) + else: + setUB(x[(t1,t2,rd)],0) + # print(t1,t2 , " no game ") + # x[(t1,t2,rd)].upBound = 0 + + # for (t1,t2,rd) in x.keys(): + # if getVal(x[(t1,t2,rd)])>0.9: + # print ("setLB(x[",t1,",",t2, "," , rd , "],1)") + + if st[0] in ["TRIP","TRIPS"]: + for r in newRounds: + for tdc in getSingleTripElementsByRound[r]: + if getVal(tripToSingleTripElement[tdc])>0.9: + setLB(tripToSingleTripElement[tdc],1) + print ("fixing " ,tdc, tripToSingleTripElement[tdc] ) + else: + setUB(tripToSingleTripElement[tdc],0) + + if st[0] == "GROUP" and len(st)>=5: + print ("fixing GROUP", st[4].strip()) + for (t1,t2) in games: + if t_conference[t1]!=0 and t_conference[t2]!=0 and st[4].strip() in [ t_conference[t2].name, t_conference[t1].name] : + print ("checking in group ", t1,t2) + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.99: + # if x[(t1,t2,rd)].value()>0.99: + setLB(x[(t1,t2,rd)],1) + print ("fixing in GROUP",t1,t2,rd) + # print (t1,t2,rd) + else: + setUB(x[(t1,t2,rd)],0) + + for r in basicRounds: + for t1 in realteams: + homeInBasicRound[(t1,r)].lowBound = 0 + homeInBasicRound[(t1,r)].upBound = 10 + awayInBasicRound[(t1,r)].lowBound = 0 + awayInBasicRound[(t1,r)].upBound = 10 + for t2 in opponents[t1]: + if (t1,t2) in games: + gameInBasicRound[(t1,t2,r)].cat = pulp.LpContinuous + gameInBasicRound[(t1,t2,r)].lowBound = 0 + + debug = False + if debug: + print (str(blockingVioTotal.value()) , " violated Blockings out of " , nBlockingHome ) + for bl in blockings : + if (blockingVio[bl['id']].value()>0.9) and bl['type'] in ["Home", "Hide"] : + print (bl ) + + print (str(travelVioTotal.value()) , " violated Travel Restrictions out of " , nBlockingAway ) + for bl in blockings : + if (blockingVio[bl['id']].value()>0.9) and bl['type']=='Away' : + print (bl ) + + if gew['Breaks'] > 0: + print (str(breakVioTotal.value()) , " Breaks :" ) + for bl in breaks : + for t in realteams: + if (breakVio[(bl['id'],t)].value()>0.9) : + print (bl, ' ' , str(t) ) + + if gew['Home-/Away'] > 0: + print ("Violated HA-Wishes out of " , len(hawishes) ) + for haw in hawishes : + for el in elemHaWishes[haw['id']]: + if (HawVioTooLess[el].value()+HawVioTooMuch[el].value() >0.9) : + print (haw) + + if gew['Encounters'] > 0: + print ("Violated Encounter-Wishes out of " , nElemEncWishes ) + for enc in encwishes : + if (encVio[enc['id']].value()>0.9) : + print (enc) + + + for t in realteams: + for r in newRounds: + homeInRound[(t,r)].cat = pulp.LpContinuous + for t2 in opponents[t]: + for rd in getRoundDaysByRound[r]: + # if x[(t,t2,d)].value() >0.1 : + # print ('looking '+ str(r) + ' : '+ getTeamById[t] + ' - '+ getTeamById[t2] + " " + str(x[(t,t2,d)].value())) + if (t,t2) in games and getVal(x[(t,t2,rd)]) >0.9 : + # print ('fixing '+ str(rd) + ' : '+ getTeamById[t] + ' - '+ getTeamById[t2] ) + setLB(x[(t,t2,rd)], x[(t,t2,rd)].value()) + currentSolution =[ (t1,t2,r,d) for (t1,t2) in realgames for (r,d) in roundDays if getVal(x[(t1,t2,(r,d))]) >0.9 ] + +# print ("####################") +# print ("testing feasibility 2") +# print ("####################") +# model2.solve(XPRESS(msg=1,maxSeconds = 10 , keepFiles=True)) +# print ("####################") +# print ("testing done") +# print ("####################") + +print (impScript) + +for r in rounds: + for t in teams: + homeInRound[(t,r)].lowBound = 0 + homeInRound[(t,r)].upBound = 1 + +for (t1,t2) in games: + for r in rounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t1,t2,rd)]) + +if runMode!='Improve': + if mathModelName=="NBA": + print ("buidling badRepeaters") + badRepeaters = [ (t,r) for (t,r) in badRepeater.keys() if badRepeater[(t,r)].value()>0.9] + for (t,r) in badRepeaters : + print ("bad repeater :",r, getTeamById[t]) + + if thisSeason.useFeatureBackToBack: + print ("buidling badBackToBackers") + badBackToBackers = [ (t,r) for (t,r) in badBackToBack.keys() if badBackToBack[(t,r)].value()>0.9] + print ("done buidling badBackToBackers") + for (t,r) in badBackToBackers : + print ("bad back to back :", int(badBackToBack[(t,r)].value()) , getNiceDay[r], getTeamById[t] ) + +tightenBlockings = nTeams>20 and nRounds>60 +if runMode=='Improve': + if tightenBlockings: + for bl in blockings: + if blockingVio[bl['id']].value()==0: + blockingVio[bl['id']].upBound=0 + print ("blocking tightened : " , bl['team'] , bl['day'] ) + +print ('Solved Again') +print ('NOW REOPT') + +mipgap=0.05 + + +starweights= sorted([ starweight[t] for t in teams], reverse = True) + +localsearch_time = max(0,min(localsearch_time, 0.9*TASK_TIME_LIMIT-(time.time()-start_time))) + +if runMode == 'New' and localsearch_time == 0: + localsearch = False +else: + localsearch = True + +# localsearch_time=10 + +print (starweights) + +nFarTeams = 1 +if gew['Trips'] > 5: + # nFarTeams += gew['Trips'] -5 + nFarTeams = nTeams + +nFarTeams = min(nFarTeams,len(starweights)-1) + +print ("nFarTeams ", nFarTeams) +print (starweight) + +farTeams = [ t for t in teams if starweight[t] > starweights[nFarTeams] ] +print ("farTeams ", farTeams) + +# cntr=0 +# for (t,t1,t2,r) in tripSaving.keys(): + # if t in farTeams or t1 in farTeams or t2 in farTeams : + # # print ("consider trip " , (t,t1,t2,r) ) + # cntr+=4 + # tripSaving[(t,t1,t2,r)].upBound =1 + # model2 += tripSaving[(t,t1,t2,r)] <= sum([ (x[(t1,t,(r,d) )]+x[(t2,t,(r,d))]) for d in [ latestDay[r] ] ]) + # model2 += tripSaving[(t,t1,t2,r)] <= sum([ (x[(t1,t,(r+1,d))]+x[(t2,t,(r+1,d))]) for d in [earliestDay[r+1]] ]) + # model2 += tripSaving[(t,t1,t2,r)] <= sum([ (x[(t1,t,(r,latestDay[r]))]+x[(t1,t,(r+1,earliestDay[r+1]))]) ]) + # model2 += tripSaving[(t,t1,t2,r)] <= sum([ (x[(t2,t,(r,latestDay[r]))]+x[(t2,t,(r+1,earliestDay[r+1]))]) ]) +# print (str(cntr) + ' constraints added' ) + +local_start =time.time() + +last_objective =value(model2.objective) + +forbidRepetitions=True +forbidRepetitions=False + +useDailyTrips = nRounds*nTeams <=200 + +print ("useDailyTrips" ,useDailyTrips , nRounds , nTeams ) + +if localsearch: + cntr =0 + + if thisSeason.tripMode=="Clusters": + + if useDailyTrips: + for d1 in days: + otherTripDays = [ d2 for d2 in days if getDateTimeDay[d1]< getDateTimeDay[d2] and getDateTimeDay[d2]<=getDateTimeDay[d1]+datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1) and (len(getRoundsByDay[d1])*len(getRoundsByDay[d2])>1 or list(getRoundsByDay[d1])!=list(getRoundsByDay[d2]) )] + + for t in realteams: + for c in clusters: + if gew['Trips']>0 and not c in t_clusters[t] and len(otherTripDays)>0: + tripToClusterDaily[(t,d1,c)].upBound=1 + # tripToClusterDaily[(t,d1,c)] = pulp.LpVariable('tripToClusterDaily_'+str(t)+'_'+str(d1)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + model2 += tripToClusterDaily[(t,d1,c)] <= away_in_cluster_day[t,d1,c] + model2 += tripToClusterDaily[(t,d1,c)] <= lpSum([away_in_cluster_day[t,d2,c] for d2 in otherTripDays ]) + # print ('considering trip of ' , getTeamById[t], ' in days ' , getNiceDay[d1] , getNiceDay[otherTripDays[0]] , otherTripDays,' to cluster ' , c , cluster_teams[c]) + cntr +=2 + else : + for r in rounds: + otherTripRounds = [r2 for r2 in rounds if r2>r and getDateTimeDay[latestDay[r]]<= getDateTimeDay[earliestDay[r2]] and getDateTimeDay[earliestDay[r2]]-getDateTimeDay[latestDay[r]]<=datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1)] + # print ("trip rounds ", r, otherTripRounds) + for t in realteams: + # forbid same opponents on successice days + for t2 in realteams: + if t!=t2 and r>1 and forbidRepetitions: + model2 += sum([ (x[(t,t2,rd )]+x[(t2,t,rd)]) for rd in getRoundDaysByRound[r-1]+getRoundDaysByRound[r] ]) <= 1 + + for c in clusters: + if len(otherTripRounds)>0 and gew['Trips']>0 and not c in t_clusters[t] : + tripToCluster[(t,r,c)].upBound=1 + model2 += tripToCluster[(t,r,c)] <= away_in_cluster[t,r,c] + model2 += tripToCluster[(t,r,c)] <= lpSum([away_in_cluster[t,r2,c] for r2 in otherTripRounds ]) + # print ('considering trip of ' , getTeamById[t], ' in rounds ' , r , otherTripRounds,' to cluster ' , c ) + cntr +=2 + + # else : + # model2 += tripToCluster[(t,r,c)] == 0 + else: + tripDayPairs = {} + for d1 in days: + for d2 in days: + daysBetween = (getDateTimeDay[d2]-getDateTimeDay[d1]).days -1 + if daysBetween>=0 and getDayMaxGames[d1]>0 and getDayMaxGames[d2]>0 and daysBetween<=thisSeason.maxDistanceWithinTrip and getRoundByDay[d1]!=getRoundByDay[d2]: + # print (d1,d2,daysBetween, getNiceDay[d1], getNiceDay[d2], ) + if d1 not in tripDayPairs.keys(): + tripDayPairs[d1]=[] + tripDayPairs[d1].append((d2, daysBetween)) + + + for t in teams: + # print (getTeamById[t]) + # print (getTeamById[t],tripElements) + for (c,v1,v2,v3,v4) in tripElements[t]: + # if getTeamById[t]=="Regatas": + # print ("POSS TRIP ",getTeamById[t], (c, [ getTeamById[t3] for t3 in v1 ] , v2, [ getTeamById[t3] for t3 in v2 ] , [ getTeamById[t3] for t3 in v3 ] , [ getTeamById[t3] for t3 in v4 ]), trip_minDays[c], trip_maxDays[c]) + for d1 in tripDayPairs.keys(): + if not onlyFewTrips or t%6==d%6 : + otherTripDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]] + inBetweenDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween<=trip_minDays[c] ] + otherTripDays3 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + otherTripDays4 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays3 and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + # print ("possible? ", getTeamById[t] , getNiceDay[d1], otherTripDays,otherTripDays3,otherTripDays4) + if len(otherTripDays)>0 and (thisSeason.tripLength <3 or len (v3)==0 or len(otherTripDays3)>0) and (thisSeason.tripLength <4 or len (v4)==0 or len(otherTripDays4)>0) : + # print ("building trip for " , getTeamById[t], " starting" , getNiceDay[d1] , [ getNiceDay[d] for d in otherTripDays ], [ getNiceDay[d] for d in otherTripDays3 ] , [ getNiceDay[d] for d in otherTripDays4 ] ) + tripToSingleTripElement[(t,d1,c)].upBound=1 + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v1 for rd in getRoundDaysByDay[d1]]) + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + for d3 in inBetweenDays: + model2 += tripToSingleTripElement[(t,d1,c)] <= 1- home[t,d3] + model2 += tripToSingleTripElement[(t,d1,c)] <= 1-lpSum([ x[(t2,t,rd)] for t2 in realteams if t2 not in v2 for rd in getRoundDaysByDay[d3]]) + # if getTeamById[t]=="Regatas": + # print (" ", getNiceDay[d1], [ getNiceDay[d2] for d2 in inBetweenDays ] ,[ getNiceDay[d2] for d2 in otherTripDays ] ," <= ", [ (t2,t,rd) for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=3 and len(v3)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v3 for d2 in set(otherTripDays3) for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=4 and len(v4)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v4 for d2 in set(otherTripDays4) for rd in getRoundDaysByDay[d2]]) + if (len(v3)>0 and len(otherTripDays3)==0) or (len(v4)>0 and len(otherTripDays4)==0) : + tripToSingleTripElement[(t,d1,c)].upBound=0 + else: + cntr +=2 + + print ("built ", cntr , " trip constraints") + + for t1 in realteams: + for r in rounds: + homeInRound[(t1,r)].lowBound = 0 + homeInRound[(t1,r)].upBound = 1 + awayInRound[(t1,r)].lowBound = 0 + awayInRound[(t1,r)].upBound = 1 + for rd in roundDays: + for t2 in opponents[t1]: + setLB(x[(t1,t2,rd)],0) + + for (t1,t2) in games: + for r in rounds: + if (t1,t2,r) in gameInBasicRound.keys(): + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = 10 + for rd in getRoundDaysByRound[r]: + setLB(x[(t1,t2,rd)],0) + setUB(x[(t1,t2,rd)],1) + + mipgap=0.00 + if thisSeason.useFeatureTrips and thisLeague.name not in ["EuroLeague Basketball", "La Liga Argentina"] : + mipgap=0.02 + + maxSolveTime= 2000 + nReopt=4 + nReopt=1*nTeams+5 + for (t1,t2) in games: + for rd in roundDays: + setLB(x[(t1,t2,rd)],0) + + # restore solution just created + print ("RESTORING OLD SOLUTION !!!") + if runMode=='New' : + use_currentSolution= True + + # print ("TESTING") + # model2+= lpSum([ fixedGameVio[(t1,t2,d)] for (t1,t2,d) in fixedGames]) + # model2+= standardObjectives + # for ttr in x.keys(): + # makeIntVar(x[ttr]) + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + # for (t1,t2,d) in fixedGames: + # if fixedGameVio[(t1,t2,d)].value() >0.1: + # print ("Games missing : " , getTeamById[t1] , getTeamById[t2] , getNiceDay[d]) + # print ("TESTING DONE9") + + + # print ("\n###############\nTIME TAKEN SO FAR : " , time.time()-start_time, "\n##################\n") + + + newRounds = [] + localSearchResults = [] + while localsearch and len(impScript)>0: + print ( (time.time() - local_start) , " " , localsearch_time, " ", (time.time() - local_start) < localsearch_time) + for (impType, firstNewRound,lastNewRound, newTeams, maxTime, par) in impScript: + if localsearch: + # if impType != "LOCALAI": + # print ("trying ") + # if len(newRounds) == 0: + # print(lastNewRound) + # newRounds = range (firstNewRound,lastNewRound+1) + if impType == "INIT": + impScript = impScript[1:] + elif impType in ["SMART_TEAMS", "SMART_ROUNDS"]: + newTeams, newRounds = smartNeighbor(impType, int(newTeams), int(lastNewRound), model2, x, hawishes, elemHaWishes, HawVioTooLess, HawVioTooMuch, + elemHaWishTeams,elemHaWishDays, encwishes, elemEncWishes, encVioTooLess, + encVioTooMuch, prioVal, elemEncWishGames,elemEncWishDays,breaks,realteams, + breakVio,nRounds,currentSolution,getTeamById) + if len(newRounds) == 0: + newRounds = range (firstNewRound,nRounds+1) + else: + newRounds = range (firstNewRound,lastNewRound+1) + + + stadium_sharers = [] + for t3 in newTeams: + if t_stadium[t3]!='': + stadium_sharers+=teamsOfStadium[stadium_id[t_stadium[t3]]] + + print ("newTeams ", newTeams) + print ("newTeams++ ",stadium_sharers) + if len(stadium_sharers)<=3: + newTeams = list(set(newTeams+stadium_sharers)) + print ("allTeams ", newTeams) + print (newRounds, newTeams, maxTime) + print ((time.time() - local_start) > localsearch_time) + print ("STOPCOMPUTATION",Scenario.objects.get(id=thisScenario.id).stopComputation) + # if time is up then do a last run sticking to currentSolution + if (time.time() - local_start) > localsearch_time or Scenario.objects.get(id=thisScenario.id).stopComputation: + print ("Time is up, restoring best solution found so far\n\n") + localsearch=False + # firstNewRound=1 + # lastNewRound=nRounds, + newTeams=[] + newRounds=[] + maxTime=2000 + newDays=[] + for r in newRounds: + for r2 in rounds : + if r==r2 or (thisSeason.symmetry and (r2-r)%(nTeams-1)==0 ): + # print ("adding round " , r2) + newDays+=getDays[r2] + + # print (newDays) + print ("###################################################") + print ("REOPT {} ROUNDS {} FOR TEAMS {} IN AT MOST {}s".format(impType,newRounds,[getTeamById[t] for t in newTeams],maxTime)) + print ("###################################################") + print ( " seconds ", (time.time() - local_start)) + if localsearch_time >0 : + pro=min(95,10+ int(90*((time.time() - local_start) )/ localsearch_time ) ) + print ("§§§§§§§§" ,pro, ' ', (time.time() - local_start) , ' / ', localsearch_time ) + else: + pro = 95 + ntstring ='' + for t in newTeams: + ntstring += getTeamById[t] + ', ' + lo = "" + if isinstance(last_objective,(int,float)): + lo= " (" + str(int(0.01+last_objective-1)) +")" + # print (task.request) + # writeProgress("Reoptimize " +str(firstNewRound)+ " - " + str(lastNewRound ) + " for teams " + ntstring[:-2] + lo, thisScenario.id ,pro) + if user_is_staff: + writeProgress("Reoptimize " +str(newRounds) + " for teams " + ntstring[:-2] + lo, thisScenario.id ,pro) + else: + writeProgress("Reoptimizing ... " + lo, thisScenario.id ,pro) + + print ("len(currentSolution) " ,len(currentSolution), use_currentSolution) + + for (t1,t2,r,d) in currentSolution: + if (t1,t2,(r,d)) in x.keys(): + if ((t1 in newTeams or t2 in newTeams) and d in newDays) or not use_currentSolution: + setLB(x[(t1,t2,(r,d))],0) + setStart(x[(t1,t2,(r,d))],1) + if par=="STICKY_HOME" and random.random()<1.0: + model2+= home[(t1,d)]>=1 + # setLB(home[(t1,d)],1) + print ( getTeamById[t1] , " stays home on " , getNiceDay[d]) + # print (type(home[(t1,d)])) + + # print ("RELEASING ", (t1,t2,d)) + else: + setLB(x[(t1,t2,(r,d))],1) + # print ("FIXXING ", (t1,t2,d)) + + # if RUN_ENV == 'celery' and task: + # task.update_state(state='PHASE-8', meta={'timestamp':time.time(),'objective':99999999}) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = mipgap, maxSeconds = maxTime, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=maxTime)) + else: + try: + model2.solve(XPRESS(msg=1,targetGap=mipgap,maxSeconds = -maxTime, options=["THREADS=12,DEFAULTALG=4,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + except: + model2.status = pulp.LpStatusNotSolved + + # print ("RRRR") + # for (t1,t2,r) in x_round.keys(): + # if getVal(x_round[(t1,t2,r)])>0.1: + # print (t1,t2, getTeamById[t1], getTeamById[t2], r, getVal(x_round[(t1,t2,r)])) + + if model2.status<0 or not value(model2.objective): + if localsearch: + continue + elif solver != "xpress" or not value(model2.objective): + writeProgress("Model could not be solved within " + str(maxTime)+" seconds ....", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print({'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print('Model could not be solved') + + if RUN_ENV == 'celery' and task: + # reset_stdout(f,filename) + task.update_state(state='Reopt Rounds', meta={'timestamp':time.time(),'objective':value(model2.objective), "user ": user_name, "league ": str(thisLeague)}) + + new_currentSolution =[ (t1,t2,r,d) for (t1,t2) in realgames for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9999 ] + + print ("status", model2.status, LpStatus[model2.status], len(new_currentSolution), len(currentSolution), len(new_currentSolution)>=len(currentSolution), not value(model2.objective), value(model2.objective), last_objective) + + newLSR = LocalSearchResult(season=thisSeason, + created_at=datetime.datetime.now(), + searchType=impType, + nTeams=len(newTeams), + nRounds=len(newRounds), + maxTime=maxTime, + objective=value(model2.objective), + lastObjective=last_objective or 0, + nElemEncWishes=nElemEncWishes, + nElemHaWishes=nElemHaWishes, + nBlockings=(nBlockingHome+nBlockingAway), + nGames=nGames, + nCols=model2.numVariables(), + nRows=model2.numConstraints(), + runtime=model2.solutionTime) + if (specialGameControl or len(new_currentSolution)>=len(currentSolution)) and (solver != "xpress" or model2.status>=0) and ( not last_objective or value(model2.objective) <= last_objective+0.01): + print ("now using this solution with objective value " + str(value(model2.objective)) ) + for (t1,t2,r,d) in currentSolution: + if (t1 in newTeams or t2 in newTeams) and d in newDays: + x[(t1,t2,(r,d))].start=0 + currentSolution =[ (t1,t2,r,d) for (t1,t2) in games for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9 ] + currentSolution =[ (t1,t2,r,d) for (t1,t2,r,d) in currentSolution if getTeamById[t1]!="-" and getTeamById[t2]!="-" ] + currentLocation = { (t,d) : False for t in teams for d in days} + for (t1,t2,r,d) in currentSolution: + currentLocation[(t2,d)]=t1 + currentLocation[(t1,d)]=t1 + + print ("length of current solution " , len (currentSolution)) + last_objective=value(model2.objective) + print ("Home-/Away : " , gew['Home-/Away'], "*",HawVioTotal.value()) + print ("Pairings :" , gew['Pairings'], "*", pairingVioTotal.value()) + print ("Blockings :" , gew['Blockings'], "*", blockingVioTotal.value() ,"+ " , blockingVioTotal2.value() ) + print ("travelVioTotal :" , gew['Traveling'], "*", travelVioTotal.value()) + print ("gamesTooCloseTotal :" , gew['Blockings'], "*", gamesTooCloseTotal.value()) + print ("tripSavedTotal2 : -5*" , gew['Trips'], "*", tripSavedTotal2.value()) + print ("breakVioTotal :" , gew['Breaks'], "*", breakVioTotal.value()) + print ("break3VioTotal :" , gew['Breaks'], "*", break3VioTotal.value()) + print ("encVioTotal : 5*" , gew['Encounters'], "*", encVioTotal.value()) + print ("seedVioTotal : 5* " , gew['Encounters'], "*", seedVioTotal.value()) + print ("confVioTotal :" , gew['Conferences'], "*", confVioTotal.value()) + print ("tooManyHomesInStadiumTotal :" , gew['Blockings'], "*", tooManyHomesInStadiumTotal.value()) + print ("broadVioTotal :" , gew['Broadcasting'], "*", broadVioTotal.value()) + print ("derbiesMissingTotal :" , gew['Derbies'], "*", derbiesMissingTotal.value()) + print ("competitionVioTotal : 5 *", competitionVioTotal.value()) + print ("tooManyTop4InRowTotal : 0.0 *", tooManyTop4InRowTotal.value()) + print ("fixedGameVioTotal :" , gew['Blockings'], "*", fixedGameVioTotal.value()) + print ("missingGamesVioTotal :" , gew['Blockings'], "*", missingGamesVioTotal.value()) + print ("totalAttendance : -0.01*", totalAttendance.value()) + print ("specialObjectives :" , specialObjectives.value() if specialObjectives else 0) + if thisSeason.useFeatureBackToBack: + greyCntr =0 + redCntr =0 + yellowCntr =0 + allCntr=0 + for (d1,d2) in critical_day_pairs: + print ("no time zone crossings for at days " , getNiceDay[d1],"->",getNiceDay[d2],nextCritical[d2]) + for t in realteams: + if currentLocation[(t,d1)] and currentLocation[(t,d2)] : + t1 = currentLocation[(t,d1)] + t2 = currentLocation[(t,d2)] + allCntr+=1 + if (t2,1.0) in bad_travel_out[t1]: + print ("GREY BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + greyCntr +=1 + if (t2,0.05) in bad_travel_out[t1]: + print ("RED BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + redCntr +=1 + if (t2,0.003) in bad_travel_out[t1]: + print ("YELLOW BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + yellowCntr +=1 + + print ("badBackToBack_Total ", badBackToBack_Total.value()) + b2bvios = [(getTeamById[t],getNiceDay[d], int(0.5+badBackToBack[(t,d)].value())) for t in teams for d in days if (t,d) in badBackToBack.keys() and badBackToBack[(t,d)].value()>0.1 ] + greenB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1] + yellowB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==3] + redB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==50] + greyB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1000] + print ( len(greyB2B) , " + ",len(redB2B) , " + ",len(yellowB2B) , " + ",len(greenB2B)) + print ( greyCntr , " + ", redCntr , " + ", yellowCntr , " + green = ", allCntr) + print (greyB2B+redB2B) + + if mathModelName=="NBA": + print ("badRepeater_Total_NBA ", badRepeater_Total_NBA.value()) + print ("tooLongTrip_Total_NBA ", tooLongTrip_Total_NBA.value()) + print ("eastWestTrip_Total_NBA ", eastWestTrip_Total_NBA.value()) + + newRounds = [] + if impType != 'INIT': + localSearchResults.append(newLSR) + + LocalSearchResult.objects.bulk_create(localSearchResults) + +mipgap=0.05 + +if thisSeason.useFeatureKickOffTime and not evalRun: + for (t1,t2) in games : + for rd in roundDays: + if getVal(x[(t1,t2,rd)]) >0.99: + model2+= x[(t1,t2,rd)]==1 + for tm in times: + makeIntVar(x_time[(t1,t2,rd,tm)]) + # makeIntVar(x_time[(t1,t2,rd,getIdByTime["Early"])]) + + writeProgress("Refining times ", thisScenario.id , 99) + + print ("######################") + print ("## REFINING TIMES ####") + print ("######################") + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = mipgap, maxSeconds = 600, threads = 8,msg=1)) + elif solver == "Gurobi": + # mipgap = 1.0 + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=600)) + else: + try: + model2.solve(XPRESS(msg=1,maxSeconds = 60, targetGap=mipgap ,options=["THREADS=12"], keepFiles=True)) + except: + model2.status = pulp.LpStatusNotSolved + + if model2.status<0 or not value(model2.objective): + if solver != "xpress" or not value(model2.objective): + writeProgress("Model could not be solved within " + str(maxTime)+" seconds ....", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print({'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print('Model could not be solved') + + if RUN_ENV == 'celery' and task: + task.update_state(state='Reopt Rounds', meta={'timestamp':time.time(),'objective':value(model2.objective), "user ": user_name, "league ": str(thisLeague)}) + +# %% + +broadcastingResult,networks_by_game = optimize_model3 (optCameraMovement) + +# for (t,d) in tvkitproblem.keys(): +# if getVal(tvkitproblem[(t,d)])>0.1: +# print ("TVKIT PROBLEM " , getTeamById[t], getNiceDay[d]) + +succ2={(t1,d1) : (t2,d2) for (t1,d1,t2,d2) in move2.keys() if getVal(move2[(t1,d1,t2,d2)])>0.1 } +for (t1,d1,t2,d2) in move2.keys(): + if getVal(move2[(t1,d1,t2,d2)])>0.1: + # print ("MOVING " , getTeamById[t1], getNiceDay[d1] , " --> " , getTeamById[t2], getNiceDay[d2] , " : " ,getVal(move2[(t1,d1,t2,d2)]) , " ", distanceById[t1,t2]) + # tv_dist+=distanceById[t1,t2] + succ2[(t1,d1)]=(t2,d2) + +tv_dist=0.0 +for (t,d) in newtrip2.keys(): + if getVal(newtrip2[(t,d)])>0.1: + print ("NEW TRIP STARTING " , getTeamById[t], getNiceDay[d]) + t1,d1=t,d + while (t1,d1) in succ2.keys(): + t2,d2=succ2[t1,d1] + tv_dist+=distanceById[t1,t2] + print (" --> " , getTeamById[t2], getNiceDay[d2] , " : " ,getVal(move2[(t1,d1,t2,d2)]) , " ", distanceById[t1,t2]) + t1,d1=succ2[t1,d1] + +jamshedpurVios = [ d for d in jamshedpurVio.keys() if getVal(jamshedpurVio[d])>0.1] +for d in jamshedpurVios: + print("jamshedpurVios : " ,getNiceDay[d]) + + +print ("total distance by tv teams" ,tv_dist ) + +sol_quality='
'+_('Summary')+'
' +sol_solution="" +sol_kpis="" +sol_broadcasting="" +sol_special_wishes="" + +print ("sol_kpis : ", sol_kpis) + +weekdays = wds.values() +weekDayDistributionHome={ (t,w) : 0 for t in teams for w in weekdays } +weekDayDistributionAway={ (t,w) : 0 for t in teams for w in weekdays } +getNiceDayOfGameByRound={ (t1,t2,r) : '' for t1 in teams for t2 in teams for r in rounds} +# getNiceDayOfGameByRound={} +t_games = { t: [] for t in teams} +game_ko_time ={} +sol_schedule='
'+_('Schedule')+'
' +sol_schedule="
" +if thisSeason.useFeatureGameIDs: + sol_schedule+="" +sol_schedule+="" +if thisSeason.groupBased: + sol_schedule+="" +sol_schedule+="" +if thisSeason.useFeatureKickOffTime or sharedStadiums: + if thisSeason.league.sport=="Hockey" : + sol_schedule+="" + elif thisSeason.league.sport=="Basketball" : + sol_schedule+="" + else : + sol_schedule+="" + +if sharedStadiums: + sol_schedule+="" + +sol_schedule+="" + +if sharedStadiums: + sol_schedule+="" + +if thisLeague.name in ["Indian Super League 2021"]: + sol_schedule+="" + sol_schedule+="" + sol_schedule+="" + last_played = { + t : 0 for t in realteams + } +if thisSeason.useFeatureBroadcasting: + sol_schedule+="" +if thisSeason.useFeaturePrediction: + sol_schedule+="" +sol_schedule+="" + +getGameID={} +gameID = thisSeason.firstGameID +for (t1,t2,r,d2) in currentSolution : + if getTeamById[t1]!='-' and getTeamById[t2]!='-': + getGameID[(t1,t2,r,d2)]=gameID + gameID+=1 + +gameID = thisSeason.firstGameID +print (currentSolution) + +if sharedStadiums : + usedStadiumTimeSlot = { (t,d) : getStadiumTimeSlot[s] for (t,d,s) in useStadiumTimeSlot.keys() if useStadiumTimeSlot[(t,d,s)].value()!=None and useStadiumTimeSlot[(t,d,s)].value() >0.9} + +# print (stadium_name) + +getTimeOfGame = {} + +for d in daysSorted: + sol_schedule_late="" + for (t1,t2,r,d2) in currentSolution : + gm_time = "" + if thisSeason.useFeatureKickOffTime: + for tm in times: + if t1 in realteams and t2 in realteams and getVal(x_time[(t1,t2,(r,d2),tm)]) >=0.9: + gm_time=tm + + if mathModelName=="UEFA" : + if t1 in realteams and t2 in realteams and getVal(x_time[(t1,t2,(r,d2),getIdByTime["Early"])]) >=0.9: + gm_time="18:45" + else: + gm_time="21:00" + + if toTime : + gm_time=toTime[t_country[t1],d2][0] + # gm12=""+ toTime[t_country[t1],d][0] + "
" + if len(toTime[t_country[t1],d2])>1: + toTime[t_country[t1],d2].pop(0) + + if sharedStadiums : + if nonIceGame[(t1,d2)].value()>0.5: + gm_time= " --- " + else: + if (t1, usedStadiumTimeSlot[(t1,d2)]['id']) not in stadiumTimeSlotPref.keys() : + gm_time= str(usedStadiumTimeSlot[(t1,d2)]['start'])+ " ("+str(usedStadiumTimeSlot[(t1,d2)]['id'])+")" + else: + gm_time= str(usedStadiumTimeSlot[(t1,d2)]['start'])+ " (" + stadiumTimeSlotPref[(t1, usedStadiumTimeSlot[(t1,d2)]['id'])].prio +")" + gm_stadium=getSharedStadiumName[usedStadiumTimeSlot[(t1,d2)]['stadium_id']] + + + game_ko_time[(t1,t2,r,d2)]=gm_time + # print (getTeamById[t1], getTeamById[t2], gm_time) + + if d2==d : + t_games[t2].append((d,t1,"A")) + t_games[t1].append((d,t2,"H")) + isLate = False + sol_schedule_tmp="" + getNiceDayOfGameByRound[(t1,t2,getRoundByDay[d])]=getNiceDay[d] + # print (str(d) , ' : ', str(t1) , ' - ' , str(t2) , ' ' , str(x[(t1,t2,(r,d2))].value()) ) + networks_by_game_string ='' + networkIds_by_game_string ='' + for n in networks_by_game[(t1,t2,d)]: + networks_by_game_string+=networkName[n]+ ', ' + networkIds_by_game_string+=str(n)+ ';' + if networks_by_game_string!="": + networks_by_game_string=networks_by_game_string[:-2] + if networkIds_by_game_string!="": + networkIds_by_game_string=networkIds_by_game_string[:-1] + else: + networkIds_by_game_string="None" + if gm_time not in times: + networks_by_game_string+= gm_time + + star1= '' + star2= '' + if len(countries)>1: + if t_country[t1]!= "" and t_country[t1]!= getTeamById[t1] : + star1=" ("+t_country[t1]+")" + if t_country[t2]!= "" and t_country[t2]!= getTeamById[t2]: + star2=" ("+t_country[t2]+")" + if getTeamById[t1]!='-' and getTeamById[t2]!='-' : + sol_schedule_tmp+='' + if thisSeason.useFeatureGameIDs: + getGameID[(t1,t2,r,d2)]=gameID + sol_schedule_tmp+='' + gameID+=1 + sol_schedule_tmp+='' + if thisSeason.groupBased and t_conference[t1]!=0: + sol_schedule_tmp+="" + sol_schedule_tmp+='' + if thisSeason.useFeatureKickOffTime: + for tm in times: + if (t1,t2,(r,d2),tm) in x_time.keys() and getVal(x_time[(t1,t2,(r,d2),tm)]) > 0.1 : + sol_schedule_tmp+='' + if "Late" in getIdByTime.keys() and tm==getIdByTime["Late"]: + isLate = True + + if sharedStadiums: + sol_schedule_tmp+='' + sol_schedule_tmp+='' + + sol_schedule_tmp+='' + + if sharedStadiums: + sol_schedule_tmp+='' + + if thisLeague.name in ["Indian Super League 2021"]: + sol_schedule_tmp+="" + if last_played[t1] == 0: + sol_schedule_tmp+="" + else: + sol_schedule_tmp+="" + if last_played[t2] == 0: + sol_schedule_tmp+="" + else: + sol_schedule_tmp+="" + last_played[t1] = d + last_played[t2] = d + + if thisSeason.useFeatureBroadcasting: + sol_schedule_tmp+='' + if thisSeason.useFeaturePrediction: + sol_schedule_tmp+='' + sol_schedule_tmp+='' + if isLate: + sol_schedule_late+=sol_schedule_tmp + else: + sol_schedule+=sol_schedule_tmp + chosenTime = "None" + if thisSeason.useFeatureKickOffTime: + for tm in times: + if getTeamById[t1]!='-' and getTeamById[t2]!='-' and (t1,t2,(r,d2),tm) in x_time.keys() and getVal(x_time[(t1,t2,(r,d2),tm)]) > 0.1 : + chosenTime = getTimeById[tm] + getTimeOfGame[(t1,t2,d2)]=tm + if getTeamById[t1]!='-' and getTeamById[t2]!='-': + sol_solution+=str(d)+'_'+str(t1)+'_'+str(t2)+'_'+str(r)+'_'+chosenTime+'_'+str(getGameID[(t1,t2,r,d)])+'_'+networkIds_by_game_string+'__' + if getWeekDay[d] != '' and t1 in realteams and t2 in realteams: + weekDayDistributionHome[(t1, getWeekDay[d])] +=1 + weekDayDistributionAway[(t2, getWeekDay[d])] +=1 + sol_schedule+= sol_schedule_late +sol_schedule+= '
ID"+_('Match Day')+""+_('Group')+""+_('Date')+""+_('Face-Off')+""+_('Tip-Off')+""+_('Kick-Off')+""+_('Stadium')+""+_('Home')+""+_('Away')+""+_('Home Short')+""+_('Away Short')+""+_('Stadium')+""+_('Rest Days Home')+""+_('Rest Days Away')+""+_('Broadcasting')+""+_('Estimated Attendance')+""+_('Distance')+"
'+ str(getGameID[(t1,t2,r,d2)]) +''+ str(r) +'" + t_conference[t1].name + "'+ getNiceDay[d] +''+getTimeById[tm]+''+gm_time+''+gm_stadium+''+ getTeamById[t1] + star1+''+ getTeamById[t2] + star2+''+ t_shortname[t1] + ''+ t_shortname[t2] + '"+getStadiumById[t1]+"-"+str((getDateTimeDay[d]-getDateTimeDay[last_played[t1]]).days-1)+"-"+str((getDateTimeDay[d]-getDateTimeDay[last_played[t2]]).days-1)+"'+ networks_by_game_string +''+ str(attendance[t1,t2,d]) +''+ scaledDistanceString(distance[getTeamById[t1],getTeamById[t2]]) + if distance[getTeamById[t1],getTeamById[t2]]<=thisSeason.maxDistanceDerby : + sol_schedule_tmp+=' (D)' + sol_schedule_tmp+='
' + +for t in teams: + t_games[t]=sorted(t_games[t],key=lambda at: getDateTimeDay[at[0]]) + +cntr=0 +sol_breaks='
' +sol_breaks+="" +for bl in breaks : + homes ="" + aways ="" + for t in realteams: + if breakVio[(bl['id'],t)].value()>0.9 and homeInRound[(t,bl['round1'])].value() > 0.9: + homes += getTeamById[t]+ ", " + cntr+=1 + if breakVio[(bl['id'],t)].value()>0.9 and awayInRound[(t,bl['round1'])].value() > 0.9: + aways += getTeamById[t]+ ", " + cntr+=1 + sol_breaks+='' +sol_breaks+='
"+_('First Round')+""+_('Second Round')+""+_('Home-Break')+""+_('Away-Break')+"
'+ str(bl['round1']) +''+ str(bl['round2']) +''+ homes[:-2]+''+ aways[:-2]+'
' + +cntr=0 +first=0 +sol_breaks+='___' + +print ("sol_kpis : ", sol_kpis) + + +for bl in breaks : + home_ids = '' + away_ids = '' + for t in realteams: + # print (bl['round1'], breakVio[(bl['id'],t)].value() , homeInRound[(t,bl['round1'])].value(), awayInRound[(t,bl['round1'])].value(), getTeamById[t] ) + if breakVio[(bl['id'],t)].value()>0.9 and homeInRound[(t,bl['round1'])].value() > 0.9: + home_ids += str(t) + '-' + cntr+=1 + if breakVio[(bl['id'],t)].value()>0.9 and awayInRound[(t,bl['round1'])].value() > 0.9: + away_ids += str(t) + '-' + cntr+=1 + # print (bl, ' ' , str(t) ) + if (home_ids[:-2] != '') or (away_ids[:-2] != ''): + if first > 0: + sol_breaks+= '---' + sol_breaks+=str(bl['round1']) + '_' + str(bl['round2']) + '_' + home_ids[:-1] + 'x' + away_ids[:-1] + first=1 + + +sol_kpis+=""+_('Breaks')+"__"+ str(cntr)+"___" + +sol_pairings='' +if thisSeason.useFeaturePairings: + violation_str = defaultdict(lambda:"") + kpi_text="" + cntr=0 + sol_pairings='
' + sol_pairings+="
" + for d in days+higherLeagueDayIds: + for pair in pairings : + ds = "do not play both on same day" + if pair['dist']==1: + ds = "do not play both within two successive days" + if pair['dist']==2: + ds = "do not play both at the same time" + if pair['dist']==3: + ds = "do not play both in the same round" + if pair['dist']==4: + ds = "play both on same day" + if pair['dist']==6: + ds = "play both at the same time" + if pair['dist']==7: + ds = "play both in the same round" + if pairingVio[(pair['id'],d)].value()>0.9 : + # if pairingVio[(pair['id'],d)].value()>0.9 and sum( getVal(home[t,d]) + getVal(away[t,d]) for t in [pair['team1_id'],pair['team2_id']]) >0.9 : + sol_pairings+='' + violation_str[pair['id']] += str(getNiceDay[d]) +"__" + kpi_text+='' + # kpi_text+='' + cntr+=1 + + kpi_text+="
"+_('Team')+" 1"+_('Team')+" 2"+_('Day')+""+_('Type')+""+_('Dist')+""+_('Prio')+"
'+ str(getTeamById[pair['team1_id']]) +''+ str(getTeamById[pair['team2_id']]) +''+ str(getNiceDay[d]) +''+ str(pair['type']) +''+ ds +''+ str(pair['prio']) +'
'+ str(getNiceDay[d]) +''+ str(getTeamById[pair['team1_id']]).replace("'","") +' - '+ str(getTeamById[pair['team2_id']]).replace("'","") +'
'+ str(getNiceDay[d]) +''+ str(getTeamById[pair['team1_id']]).replace("'","") +' - '+ str(getTeamById[pair['team2_id']]).replace("'","") +' '+ ds +' '+ str(pair['type']) +'
" + sol_pairings+='
' + print (sol_pairings) + sol_kpis+=""+_('Violated Pairings')+"__"+ str(cntr)+"__"+ kpi_text+"___" + + Pairing.objects.filter(scenario=thisScenario).update(violation='') + for pair in Pairing.objects.filter(id__in=violation_str.keys()): + pair.violation = violation_str[pair.id][:-2] + pair.save() + +sol_distance_saved=0.0 +sol_trips='
' +sol_trips+="" +print(teams) +print(rounds) +print(clusters) +currentTrip='' +totalDistanceTravelled = {t : 0.0 for t in teams } + +cntr=0 +for t in teams: + print ("new Team" , getTeamById[t] ) + currentTrip += 'getTrips["'+getTeamById[t] +'"]=['; + lastDay = False + curTrip = [] + theseTrips = [] + for (thisDay,thisTeam,ha) in t_games[t]: + # print ("checking " ,thisDay,thisTeam , getNiceDay[thisDay]) + if lastDay: + if getDateTimeDay[thisDay] - getDateTimeDay[lastDay] > datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1) or ha=="H": + if len(curTrip)>=1: + theseTrips.append(curTrip) + # print ("new trip ", curTrip) + curTrip = [] + if ha=="A": + curTrip.append((thisDay,thisTeam)) + lastDay=thisDay + + if len(curTrip)>0: + theseTrips.append(curTrip) + + if len(theseTrips) >0: + realTripFound= False + for curTrip in theseTrips: + c_sav = sum([ distanceById[t,curTrip[i-1][1] ] + distanceById[t,curTrip[i][1] ] - distanceById[curTrip[i-1][1],curTrip[i][1] ] for i in range(1,len(curTrip)) ]) + c_tot = 2*sum([ distanceById[t,curTrip[i][1]] for i in range(len(curTrip))]) - c_sav + totalDistanceTravelled[t] += c_tot + sol_distance_saved += c_sav + if len (curTrip)>1: + realTripFound=True + currentTrip += '['; + sol_trips+='' + cntr+=1 + sol_trips+='' + currentTrip=currentTrip[:-1] + currentTrip += '],'; + if realTripFound: + currentTrip=currentTrip[:-1] + currentTrip += '];\n'; + +sol_trips+='
"+_('Team')+""+_('Destinations')+""+_('Trip Length')+""+_('Distance saved')+"
'+ getTeamById[t] + '' + lastTeam=False + for (thisDay,thisTeam) in curTrip: + wantedTrip = "" + for (tt,tv1,tv2,tv3,tv4) in tripElements[t]: + # print ("") + # print (tt," - " ,tv1,tv2,tv3,tv4 ) + for (tt1,tt2) in [(tv1,tv2), (tv2,tv3), (tv3,tv4)] : + # if lastTeam in tt1 or thisTeam in tt2: + # print ( lastTeam , " in " , tt1 ," -- ", thisTeam , " in " , tt2, lastTeam in tt1 and thisTeam in tt2) + if lastTeam in tt1 and thisTeam in tt2: + wantedTrip = "*" + print ( " ", getNiceDay[thisDay] , getTeamById[lastTeam] ," -> ", getTeamById[thisTeam] , " added ") + lastTeam=thisTeam + currentTrip+= '"'+ getTeamById[thisTeam] +'",' + sol_trips+= ' '+getNiceDay[thisDay] + " :    " + getTeamById[thisTeam] + " "+ wantedTrip+"
" + sol_trips=sol_trips[:-4] + sol_trips+='
'+ scaledDistanceString(c_tot) + ''+ scaledDistanceString(c_sav) + '
' +sol_trips+='' + +if thisSeason.useFeatureTrips: + travelSum=sum([totalDistanceTravelled[t] for t in teams]) + if sol_distance_saved+travelSum >0: + percentAgeSaved= int(sol_distance_saved/(sol_distance_saved+travelSum)*100) + else : + percentAgeSaved= 0 + sol_kpis+=""+_('Num. Trips')+"__"+ str(cntr)+"___" + sol_kpis+=""+_('Distance Traveled')+"__"+ scaledDistanceString(travelSum)+"___" + sol_kpis+=""+_('Distance saved on trips')+"__"+ scaledDistanceString(sol_distance_saved)+" (" + str(percentAgeSaved) + "%)___" + +currentSolutionOfDay = { d: [] for d in days } +currentSolutionDayOfTeamRound = { (t,r) : 0 for t in teams for r in rounds } +for (t1,t2,r,d) in currentSolution: + currentSolutionOfDay[d].append((t1,t2,r)) + currentSolutionDayOfTeamRound[(t1,r)]= d + currentSolutionDayOfTeamRound[(t2,r)]= d + +redStripes = '' +for bl in breaks : + for t in realteams: + if breakVio[(bl['id'],t)].value()>0.9 : + # print ("Break for " , getTeamById[t] , ' from ', currentSolutionDayOfTeamRound[(t,bl['round1'])] , ' to ' , currentSolutionDayOfTeamRound[(t,bl['round2'])] ) + redStripes+= '"'+str(t)+'_'+str(currentSolutionDayOfTeamRound[(t,bl['round1'])]) +'__'+str(t)+'_'+str(currentSolutionDayOfTeamRound[(t,bl['round2'])]) +'", ' + +cntr={ t : 0 for t in ["Home", "Away",'Hide']} + +blockobjects = [] +kpi_text={ ha : "" for ha in ['Home','Away','Hide']} +for bl in blockings : + if (blockingVio[bl['id']].value()>0.9) : + cntr[bl['type']]+=1 + kpi_text[bl['type']]+= "" +kpi_text["Home"]+= "
"+ getTeamById[bl['team_id']].replace("'","") +"" + getDayById[bl['day_id']]['day'] +"
" +kpi_text["Away"]+= "" + +sol_kpis+=_('Violated Blockings')+"__"+ str(cntr['Home'])+ ' of '+ str(nBlockingHome) +"__"+ kpi_text['Home'] +"___" + +if thisSeason.useFeatureNoTravel: + sol_kpis+=_('Violated Travel Restrictions')+'__'+ str(cntr['Away'])+ ' of '+ str(nBlockingAway) +"__"+ kpi_text['Away'] + '___' + +cntr=0 +kpi_text="" +for st in stadiums : + for d in days+higherLeagueDayIds: + if tooManyHomesInStadium[(st, d)].value()>0.9: + kpi_text+= "" + cntr+=1 +kpi_text+="
"+ stadium_name[st] +"" + getDayById[d]['day'] +"
" + +if cntr==0 and len(stadiums)==0: + cntr="0 of 0" + +sol_kpis+=_('Overbooked stadiums')+'__'+ str(cntr)+ "__"+ kpi_text + '___' + +displayVios = { i: str(i) for i in range(100) } +displayVios[-1]="--" +cntr=0 +wishobjects = [] +kpi_text="" +for haw in hawishes : + # if (haw['homeAway']=='Home' and homePat[haw['team_id'], getDayById[haw['day_id']]['round']].value()<0.1) or (haw['homeAway']=='Away' and homePat[haw['team_id'], getDayById[haw['day_id']]['round']].value()>0.9) : + violtext ="" + if haw['forOneDay'] : + relTeamString = { el : getStringFromSet(elemHaWishTeams[el]) for el in elemHaWishes[haw['id']] } + relTeams = set([ relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print ("\n", relTeams ) + # for rt in relTeams: + # print (hawOneVio[(haw['id'],rt)].value()) + if (gew['Home-/Away']>0 and hawVio[haw['id']].value()>0.9 ) : + violtext =""+_('Not fulfilled')+"" + if haw['forEachTeam'] : + violtext +=":" + relTeamString = { el : getStringFromSet(elemHaWishTeams[el]) for el in elemHaWishes[haw['id']] } + relTeams = set([ relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print ("\n", relTeams ) + for rt in relTeams: + # print (hawOneVio[(haw['id'],rt)].value()) + if hawOneVio[(haw['id'],rt)].value()>0.9: + for rr in rt.split("_"): + violtext+="
"+ getTeamById[int(rr)] + cntr+=1 + else: + cntr+=1 + + + else: + for el in elemHaWishes[haw['id']]: + if (gew['Home-/Away']>0 and HawVioTooMuch[el].value()+HawVioTooLess[el].value()>0.9 ) : + who='' + if haw['forEachTeam']: + who = getTeamById[elemHaWishTeams[el][0]] + ': ' + if haw['forEachDay']: + who = getNiceDay[elemHaWishDays[el][0]] + if haw['timeframe']!=0: + # day1= parse(getDayById[elemHaWishDays[el][0]]['day']) + day1= getDateTimeDay[elemHaWishDays[el][0]] + day2= day1 + datetime.timedelta(days=haw['timeframe']-1) + # print ( day1, day2) + if haw['timeframe']>0 and day2!=day1: + who += " - "+str(day2.strftime('%a, %b %d, %Y')) + who +=": " + if (HawVioTooLess[el].value()>0.9) : + violtext += who + str(int(HawVioTooLess[el].value() +0.1)) + " too few
" + if (HawVioTooMuch[el].value()>0.9) : + violtext += who + str(int(HawVioTooMuch[el].value() +0.1)) + " too many
" + # print ( "HAW TOO MANY ", haw, elemHaWishTeams[el] , elemHaWishDays[el] , el) + # for ddd in elemHaWishDays[el]: + # print (" - day ", getDayById[ddd]) + # for ddd in elemHaWishTeams[el]: + # print (" - team ", getTeamById[ddd]) + cntr+=1 + if violtext != "": + kpi_text += haw['reason'].replace("'","") +"
" + + dy='' + if haw['day_id']: + dy += getNiceDay[haw['day_id']] + if haw['day2_id']: + dy += ' - ' +getNiceDay[haw['day2_id']] + if not haw['weekdays'] in ['--', '-'] : + if haw['day_id'] or haw['day2_id']: + dy += ', ' + dy += haw['weekdays'] + + if violtext != haw['violation'] or hawRoundsString[haw['id']]!=haw['affected_rounds']: + wishobj = HAWish.objects.filter(id=haw['id']).first() + if wishobj: + wishobj.violation = violtext + wishobj.affected_rounds = hawRoundsString[haw['id']] + wishobjects.append(wishobj) +HAWish.objects.bulk_update(wishobjects, ['violation','affected_rounds']) +if thisSeason.useFeatureHomeAway: + if kpi_text=="": + kpi_text="
" + sol_kpis+=_('Violated Home/Away Wishes')+'__'+ str(cntr)+ ' of '+ str(nElemHaWishes)+ '__' + kpi_text + '___' + +cntr=0 +comments = set([enc['reason'] for enc in encwishes ]) +violatedEncs={ c : True for c in comments } + +for enc in encwishes : + if (encVio[enc['id']].value()<0.1 and encVio[enc['id']].value()>-0.1 ) : + violatedEncs[enc['reason']]=False + +# count bundles wishes (those with the same comments are considered a bundle) +commentlist = sorted(list(comments)) +if '-' in commentlist: + commentlist.remove('-') +numEncWishes = len(commentlist) +violatedEncWishes = sum( violatedEncs[c] for c in commentlist) + +# count other wishes +for enc in encwishes : + if enc['reason']=='-': + numEncWishes+=1 + if (encVio[enc['id']].value()>0.9) : + violatedEncWishes+=1 +print ('violatedEncWishes 1:' ,violatedEncWishes) +print ('numEncWishes 1:' ,numEncWishes) +violatedEncWishes=sum(encVio[enc['id']].value() for enc in encwishes ) +violatedEncWishes=int(violatedEncWishes+0.1) +numEncWishes=sum(enc['minGames'] for enc in encwishes ) +print ('violatedEncWishes 2:' ,violatedEncWishes) +print ('numEncWishes 2:' ,numEncWishes) + +# commentlist.append('-') +totalVios =0 +wishobjects = [] +kpi_text="" +for enc in encwishes : + violtext ="" + # violtext = str(int(encVio[enc['id']].value() +0.1)) + " too many/few" + if enc['forOneDay'] : + print ("\n\n ENCOUNTER", enc['reason'] , gew['Encounters']>0 , encVio[enc['id']].value()) + # for el in elemEncWishes[enc['id']]: + # if (encVioTooMuch[el].value()+encVioTooLess[el].value()>0.9 and len(elemEncWishDays[el])>0 ) : + + # for ed in encDaySets[enc['id']]: + # if len(ed)>0: + # print (ed, encForOneNotViolated[(enc['id'], ed[0])].value() ) + # relWishes = [ el for el in elemEncWishes[enc['id']] if elemEncWishDays[el] == ed ] + # print ("###",ed, relWishes) + # encForOneNotViolated[(enc['id'], ed[0])]= pulp.LpVariable('encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=pulp.LpContinuous) + # model2 += sum( encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * (1-encForOneNotViolated[(enc['id'], ed[0])]) + # model2 += sum( encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len(ed)>0 ) >= 1-encVio[enc['id']] + + if (gew['Encounters']>0 and encVio[enc['id']].value()>0.9 ) : + violtext =""+_('Not fulfilled')+"" + totalVios += 1 + print ("violtext",violtext) + else: + for el in elemEncWishes[enc['id']]: + if (encVioTooMuch[el].value()+encVioTooLess[el].value()>0.9 and len(elemEncWishDays[el])>0 ) : + when='' + if enc['forEachDay'] or enc['forOneDay'] : + when = getNiceDay[elemEncWishDays[el][0]] + if enc['timeframe']!=0: + # day1= parse(getDayById[elemEncWishDays[el][0]]['day']) + day1= getDateTimeDay[elemEncWishDays[el][0]] + day2= day1 + datetime.timedelta(days=enc['timeframe']-1) + if enc['timeframe']>1 and not enc['multidate']: + when += " - "+str(day2.strftime('%a, %b %d, %Y')) + if when !="" and not enc['useEncounterGroups'] and not enc['useGroups']: + when += "
" + t1,t2=elemEncWishGames[el][0] + if enc['forEachTeam1']: + when += getTeamById[t1] + if enc['forEachTeam2']: + when += " - " +getTeamById[t2] + if when !="" and not enc['useEncounterGroups'] and not enc['useGroups']: + when +="
" + totalVios += 1 + if (encVioTooLess[el].value()>0.9) : + violtext += when + " " +str(int(encVioTooLess[el].value() +0.1)) + " too few

" + if (encVioTooMuch[el].value()>0.9) : + violtext += when + " " +str(int(encVioTooMuch[el].value() +0.1)) + " too many

" + + print ("$$$$$$ violtext",violtext, enc['timeframe'],not enc['multidate']) + + if violtext != "": + kpi_text += enc['reason'] + "
" + print (enc) + if violtext != enc['violation'] or encRoundsString[enc['id']]!=enc['affected_rounds']: + wishobj = EncWish.objects.filter(id=enc['id']).first() + if wishobj: + wishobj.violation = violtext + wishobj.affected_rounds = encRoundsString[enc['id']] + wishobjects.append(wishobj) +EncWish.objects.bulk_update(wishobjects, ['violation','affected_rounds']) + +if thisSeason.useFeatureEncounters: + if kpi_text!="": + sol_kpis+=_('Violated Encounter Wishes')+'__'+ str(totalVios)+ ' of '+ str(nElemEncWishes)+ '__' + kpi_text + '___' + else: + sol_kpis+=_('Violated Encounter Wishes')+'__'+ str(totalVios)+ ' of '+ str(nElemEncWishes)+ '___' + + +nGames2 =sum(home[t,d].value() for t in realteams for d in days ) +# print('Preferred Weekdays: '+ str(int(100*(nGames2-unpreferredTotal.value())/nGames2)) + ' % ') +if nGames2: + sol_kpis+=_('Preferred Weekdays')+'__'+ str(int(100*(nGames2-unpreferredTotal.value())/nGames2)) + ' %___' + +competitionCntr=0 +for (t1,t2,r,d2) in currentSolution : + if getTeamById[t1]!='-' and getTeamById[t2]!='-' : + if (t1,d2) in competitions.keys() or (t2,d2) in competitions.keys() : + competitionCntr+=1 + +if thisSeason.useFeatureCompetitions: + if competitionCntr==0 and len(competitions.keys())==0: + competitionCntr="0 of 0" + + sol_kpis+=_('Conflicts with other competitions')+'__'+ str(competitionCntr)+ '___' + +if isinstance(last_objective,(int,float)): + sol_kpis+=_('Overall Quality')+'__'+ str(int(0.01+last_objective-1)) + '___' + +if show_TV_markets: + cntr=0 + def c_att(cntry): + return -sum([ int(t['attractivity']) for t in teamObjects if t['country']==cntry]) + countries = sorted(countries, key=c_att) + global_coeff = {t['id'] : int(t['attractivity']) for t in teamObjects } + domestic_coeff = {t['id'] : int(0.1+10*(t['attractivity']-int(t['attractivity']))) for t in teamObjects } + get_games_per_country_and_day= {(c,d) : "" for c in countries for d in days } + num_home_games_per_country_and_day= {(c,d) : 0 for c in countries for d in days } + num_home_games_per_country_and_round= {(c,r) : 0 for c in countries for r in rounds } + num_higher_home_games_per_country_and_round= {(c,r) : 0 for c in countries for r in rounds } + get_games_per_day= {d : [] for d in days } + + highestGlobal = { d: [ global_coeff[t1]*global_coeff[t2] for (t1,t2,r,d2) in currentSolution if d2==d ] for d in days } + highestGlobal = { d: 0 if highestGlobal[d]==[] else max(highestGlobal[d]) for d in days } + highestDomestic = { (c,d): 30 for d in days for c in countries} + + + for (t1,t2,r,d) in currentSolution : + get_games_per_day[d].append((t1,t2,r,d)) + t1f= getTeamById[t1] + t2f= getTeamById[t2] + glob_game_coeff = global_coeff[t1]*global_coeff[t2] + domestic_game_coeff_1 = quality_of_game_dom(t1,t2) + domestic_game_coeff_2 = quality_of_game_dom(t2,t1) + + gm12="" + if game_ko_time[(t1,t2,r,d)]: + gm12=""+game_ko_time[(t1,t2,r,d)]+"
" + + if glob_game_coeff >= 16: + gm12 += "" + t1f + " -
" + t2f + "

" + else: + gm12 += t1f + " -
" + t2f +"
" + + if glob_game_coeff >= highestGlobal[d]: + gm12 += "(" + str(glob_game_coeff) + "/" + else: + gm12 += "("+str(glob_game_coeff) +"/" + + gm12_1=gm12 + gm12_2=gm12 + + if domestic_game_coeff_1 >= highestDomestic[(t_country[t1],d)]: + gm12_1 += "" + str(domestic_game_coeff_1) + ")

" + else: + gm12_1 += str(domestic_game_coeff_1) +")

" + + if domestic_game_coeff_2 >= highestDomestic[(t_country[t2],d)]: + gm12_2 += "" + str(domestic_game_coeff_2) + ")

" + else: + gm12_2 += str(domestic_game_coeff_2) +")

" + + num_home_games_per_country_and_day[(t_country[t1],d)]+= 1 + num_home_games_per_country_and_round[(t_country[t1],r)]+= 1 + get_games_per_country_and_day[(t_country[t1],d)]+= gm12_1 + get_games_per_country_and_day[(t_country[t2],d)]+= gm12_2 + + + for (dy,t1,t2,r,tm) in higherGames: + if t_country[t1] in countries: + num_higher_home_games_per_country_and_round[(t_country[t1],r)]+= 1 + + sol_special_wishes='
' + sol_special_wishes+="
" + + sol_special_wishes+='
' + sol_special_wishes+="" + for d in days: + sol_special_wishes+="" + sol_special_wishes+="" + + for c in countries: + sol_special_wishes+="" + for d in days: + sol_special_wishes+="" + sol_special_wishes+="" + sol_special_wishes+='
 " + getNiceDay[d] + "
" + c + "" + get_games_per_country_and_day[(c,d)][:-8] + "
' + + + sol_special_wishes+='
' + sol_special_wishes+="

"+_('Home Games per Country and Day')+"

" + for d in days: + sol_special_wishes+="" + sol_special_wishes+="" + + for c in countries: + sol_special_wishes+="" + for d in days: + sol_special_wishes+="" + sol_special_wishes+="" + sol_special_wishes+='
 " + getNiceDay[d] + "
" + c + "" + str(num_home_games_per_country_and_day[(c,d)]) + "
' + + + + print ("higherGames ", higherGames) + print ("len(higherGames)>0", len(higherGames)) + + also_count_highergames = len(higherGames)>0 + sol_special_wishes+='
' + sol_special_wishes+="

Home Games per Country and Week

" + for r in rounds: + sol_special_wishes+="" + sol_special_wishes+="" + + for c in countries: + sol_special_wishes+="" + for r in rounds: + sol_special_wishes+="" + sol_special_wishes+="" + sol_special_wishes+='
 " + str(r) + "
" + c + "" + str(num_home_games_per_country_and_round[(c,r)]) + if also_count_highergames: + sol_special_wishes+= " + " + str(num_higher_home_games_per_country_and_round[(c,r)]) + " = " + str(num_home_games_per_country_and_round[(c,r)]+num_higher_home_games_per_country_and_round[(c,r)]) + "" + sol_special_wishes+="
' + + def g_tms(ttrd): + return game_ko_time[ttrd] + for d in days: + get_games_per_day[d]= sorted(get_games_per_day[d], key=g_tms) + + # # Avoid early matches with a global coefficient of 16 and more (60) + # +0.6*sum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] + + if mathModelName=="UEFA" : + sol_special_wishes+='
' + sol_special_wishes+="" + nCols = min([len(getDays[r]) for r in rounds]) + for r in rounds: + sol_special_wishes+=' ' + sol_special_wishes+='' + for d in getDays[r]: + sol_special_wishes+=' ' + sol_special_wishes+='' + nRows= max([len(get_games_per_day[d]) for d in days ]) + for i in range(nRows): + sol_special_wishes+='' + for d in getDays[r]: + if i>=len(get_games_per_day[d]): + sol_special_wishes+=' ' + sol_special_wishes+='' + sol_special_wishes+='' + sol_special_wishes+='
Matchday ' + str(r) + '
Day ' + getNiceDay[d] + '  
 ' + else: + t1,t2,r2,d2=get_games_per_day[d][i] + sol_special_wishes+='' + getTeamById[t1] + ' ' + getTeamById[t2] + '' + str(global_coeff[t1]*global_coeff[t2]) + ' ' + game_ko_time[(t1,t2,r,d)] + '  
 
' + + earlyMatches = { cc :[] for cc in NAS15 } + + if not isUEL: + sol_special_wishes+='
' + sol_special_wishes+="

Early Match Violations

" + for (t1,t2,rd) in x.keys() : + t1f= getTeamById[t1] + t2f= getTeamById[t2] + if getVal(x_time[(t1,t2,rd,getIdByTime["Early"])])>0.9: + if t_country[t1] in NAS15: + earlyMatches[t_country[t1]].append((rd[1] , "H", t1)) + if t_country[t2] in NAS15: + earlyMatches[t_country[t2]].append((rd[1] , "A", t2)) + + print ("') + if quality_of_game(t1,t2)>=16 and getVal(x_time[(t1,t2,rd,getIdByTime["Early"])])>0.9: + sol_special_wishes+='' + if (quality_of_game_dom(t1,t2)>=25 or quality_of_game_dom(t2,t1)>=25 ) and getVal(x_time[(t1,t2,rd,getIdByTime["Early"])])>0.9: + sol_special_wishes+='' + + + for d in days: + if best_global_early[d].value()>0.9: + sol_special_wishes+='' + for c in NAS15: + if best_dom_early[(c,d)].value()>0.9: + sol_special_wishes+='' + sol_special_wishes+='
Early match with a global coefficient of " + str(quality_of_game(t1,t2)) +' ' + str(quality_of_game_dom(t1,t2)) +' / ' + str(quality_of_game_dom(t2,t1)) + ' :' + t1f + " - " +t2f + '
Early match with a global coefficient of ' + str(quality_of_game(t1,t2)) +' :' + t1f + " - " +t2f+ '
Early match with a domestic coefficient of ' + str(quality_of_game_dom(t1,t2)) +' / ' + str(quality_of_game_dom(t2,t1)) + ' :' + t1f + " - " +t2f+ '
' + getNiceDay[d]+ ' : Best global match early.
' + getNiceDay[d]+ ' : Best domestic match early in '+ c +'.
' + + def g_tms2(dtt): + return getDateTimeDay[dtt[0]] + for cc in NAS15: + earlyMatches[cc]= sorted(earlyMatches[cc], key=g_tms2) + + sol_special_wishes+='
' + sol_special_wishes+="

Early Matches in main Markets (home + away)

" + for c in NAS15: + sol_special_wishes+=' ' + sol_special_wishes+="" + nRows= max([len(earlyMatches[c]) for c in NAS15 ]) + for i in range(nRows): + sol_special_wishes+='' + for c in NAS15: + if i>=len(earlyMatches[c]): + sol_special_wishes+=' ' + sol_special_wishes+='' + + sol_special_wishes+='' + for c in NAS15: + sol_special_wishes+=' ' + sol_special_wishes+='' + sol_special_wishes+='
' + c+ '
 ' + else: + d,tp,t =earlyMatches[c][i] + sol_special_wishes+='' + getNiceDay[d] + ' ' + tp + '' + getTeamById[t] + '  
' + str(len(earlyMatches[c]))+ '
' + + sol_special_wishes+='
' + sol_special_wishes+="

Home-/Away patterns and positions

" + sol_special_wishes+='' + for cc in sorted([confName[c] for c in confTeams.keys() if len(confTeams[c])<=4]): + for c in confTeams.keys(): + if confName[c]== cc: + sol_special_wishes+='' + for p in positions: + for t in confTeams[c]: + if pos[(t,p)].value()>0.9: + sol_special_wishes+='' + sol_special_wishes+='' + for r in rounds: + ha = "H" if homeInRound[(t,r)].value() > 0.9 else "A" + sol_special_wishes+='' + sol_special_wishes+='' + sol_special_wishes+='' + # sol_special_wishes+='' + sol_special_wishes+='
 PositionMD 1MD 2MD 3MD 4MD 5MD 6
' + confName[c] + '              
' + getTeamById[t] + ' ' + str(p) + ''+ha+'
               
 
' + +if mathModelName=="UEFA NL" : + sol_special_wishes+='
' + sol_special_wishes+='
Game sequences
' + sol_special_wishes+="" + sol_special_wishes+="" + sol_special_wishes+="" + weekdayCntr={ (t,ha): 0 for t in realteams for ha in ["H","A"] } + weekendCntr={ (t,ha): 0 for t in realteams for ha in ["H","A"] } + opp_sp = { (t,r) :"" for t in realteams for r in range(1,7)} + for (t1,t2,r,d) in currentSolution : + opp_sp[(t1,r)]=t2 + opp_sp[(t2,r)]=t1 + if getWeekDay[d] in ["Fri" , "Sat", "Sun"]: + weekendCntr[(t1,"H")]+=1 + weekendCntr[(t2,"A")]+=1 + else: + weekdayCntr[(t1,"H")]+=1 + weekdayCntr[(t2,"A")]+=1 + + for c in conf_teams.keys() : + if len(conf_teams[c])<=20: + sol_special_wishes+="" + for t in conf_teams[c]: + seq="" + if len(conf_teams[c])==4: + seq="123" + for r2 in [4,5,6]: + for r1 in [1,2,3]: + if opp_sp[(t,r1)]== opp_sp[(t,r2)]: + seq+=str(r1) + sol_special_wishes+="" + sol_special_wishes+="" + sol_special_wishes+='
weekdays: Mon, Tue, Wed, Thuweekdays: Fri, Sat, Sun
SequenceHAHA
"+ c + "
" + getTeamById[t] + "" + seq + "" + str(weekdayCntr[t,"H"]) + "" + str(weekdayCntr[t,"A"]) + "" + str(weekendCntr[t,"H"]) + "" + str(weekendCntr[t,"A"]) + "
 
' + +if "minHomeAttractivity" in special_wishes_active: + sw_type="minHomeAttractivity" + sol_special_wishes+='
' + sol_special_wishes+='
Home Game Attractivities
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
TeamAttractivity ScoreMissing Score
" + str(getTeamById[t]) + "" + str("") + "" + str(specialWishVio[(sw_type,t)].value()) + "
' + +if "limit_b2b" in special_wishes_active: + sw_type="limit_b2b" + sol_special_wishes+='
' + sol_special_wishes+='
Back to Back
' + sol_special_wishes+="" + sol_special_wishes+="" + for (nw,r) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,nw,r)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
Stadium"+_('Round 1')+""+_('Round 2')+"
" + str(networkName[nw]) + "" + str(r) + "" + str(r+1) + "
' + +if "RecoverBeforeDistantGame" in special_wishes_active : + sol_special_wishes+='
' + sol_special_wishes+='
Not enough recovery time before a distant game
' + sol_special_wishes+="" + sol_special_wishes+="" + for sw_type in ["RecoverBeforeDistantGame"]: + if sw_type in special_wishes_active: + for (t,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
TeamDay
" + getTeamById[t] + "" + getNiceDay[d] + "
' + +if "RecoverAfterDistantGame" in special_wishes_active or "RecoverAfterDistantGame2" in special_wishes_active : + sol_special_wishes+='
' + sol_special_wishes+='
Not enough recovery time after a distant game
' + sol_special_wishes+="" + sol_special_wishes+="" + for sw_type in ["RecoverAfterDistantGame","RecoverAfterDistantGame2"]: + if sw_type in special_wishes_active: + for (t,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
TeamDay
" + getTeamById[t] + "" + getNiceDay[d] + "
' + +if "NoSingleDistantGame" in special_wishes_active: + sw_type="NoSingleDistantGame" + sol_special_wishes+='
' + sol_special_wishes+='
Single distant games
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t,d) in sorted(specialWishItems[sw_type]): + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
TeamDay
" + getTeamById[t] + "" + getNiceDay[d] + "
' + +if "RestDaysAfterLateGame" in special_wishes_active: + sw_type="RestDaysAfterLateGame" + sol_special_wishes+='
' + sol_special_wishes+='
Late game not followed buy enough rest days
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
TeamDay
" + getTeamById[t] + "" + getNiceDay[d] + "
' + +if "3DaysForDistantTeams" in special_wishes_active: + sw_type="3DaysForDistantTeams" + sol_special_wishes+='
' + sol_special_wishes+='
Not enough rest days when traveling to distant teams
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t1,t2,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t1,t2,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
Team 1Team 2Day
" + getTeamById[t1] + "" + getTeamById[t2] + "" + getNiceDay[d] + "
' + +if "playWeekendsCompletelyHomeOrAway" in special_wishes_active: + sw_type="playWeekendsCompletelyHomeOrAway" + sol_special_wishes+='
' + sol_special_wishes+='
Teams playing home and away on the same weekend
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
Team 1Day
" + getTeamById[t] + "" + getNiceDay[d] + "
' + +if "alwaysFixedRestDaysWhenPossible" in special_wishes_active: + sw_type="alwaysFixedRestDaysWhenPossible" + sol_special_wishes+='
' + sol_special_wishes+='
Teams not having ' + str(sw_int1[sw_type])+ ' rest days between games
' + sol_special_wishes+="" + sol_special_wishes+="" + for (t,d) in specialWishItems[sw_type]: + if specialWishVio[(sw_type,t,d)].value()>0.9: + sol_special_wishes+="" + sol_special_wishes+='
Team 1Day
" + getTeamById[t] + "" + getNiceDay[d] + "
' + + +if thisSeason.useFeatureBroadcasting: + print("getTimeOfGame", getTimeOfGame) + cntr=0 + broadCastFrequency={ (n,t) : 0 for t in teams for n in networkName.keys() } + sol_broadcasting='
' + sol_broadcasting+="
" + sol_broadcasting+='
' + sol_broadcasting+="" + bgcolor="style='background-color:White '" + # for bw in broadcastingwishes : + # # if (haw['homeAway']=='Home' and homePat[haw['team_id'], getDayById[haw['day_id']]['round']].value()<0.1) or (haw['homeAway']=='Away' and homePat[haw['team_id'], getDayById[haw['day_id']]['round']].value()>0.9) : + # bgcolor="style='background-color:PaleGreen '" + # bgcolor="style='background-color:White '" + # # if (broadVio[bw['day_id']].value()>0.9) : + # # bgcolor="style='background-color:#ff9980'" + # ddd = "SLOTS" + # if bw.day: + # ddd=getNiceDay[bw.day.id] + # sol_broadcasting+='' + + for n in networkName.keys(): + sol_broadcasting+='' + for (t1,t2,d,nw) in broadcastingResult : + if n==nw: + tm = "" + if (t1,t2,d) in getTimeOfGame.keys(): + tm = ", " + getTimeById[getTimeOfGame[(t1,t2,d)]] + sol_broadcasting+='' + cntr+=attractivity[t1,t2] + broadCastFrequency[(nw,t1)]+=1 + broadCastFrequency[(nw,t2)]+=1 + # for g in topGames : + # if x[g[0][0],g[0][1],bw['day_id']].value() >0.9: + # sol_broadcasting+='' + # if x[g[0][1],g[0][0],bw['day_id']].value() >0.9: + # sol_broadcasting+='' + sol_broadcasting+='' + sol_broadcasting+='
"+_('Network')+""+_('Day')+""+_('Match')+""+_('Quality')+""+_('Comment')+"
'+ networkName[bw.network.id] +' '+ ddd +' '+ str(bw.minGames) +' '+ bw.quality +' '+ bw.reason +'
'+ networkName[n]+'
 '+getNiceDay[d] + tm+' '+ getTeamById[t1] +' - '+ getTeamById[t2] +'' + str(attractivity[t1,t2]) +'  
w '+ getTeamById[g[0][0]] +' - '+ getTeamById[g[0][1]] +'' + str(g[1]) +'
w '+ getTeamById[g[0][1]] +' - '+ getTeamById[g[0][0]] +'' + str(g[1]) +'
 
' + + sol_broadcasting+='' + sol_broadcasting+='" + + if cntr==0 and len(networkName.keys())==0: + cntr="0 of 0" + + # sol_broadcasting+='
' + + sol_kpis+='Quality of Broadcasting slot assignment__'+ str(cntr)+ '___' + print ("broadCastFrequency ", broadCastFrequency) + +cntr=0 +kpi_text="" +sol_conferences='
' +sol_conferences+="
" +for conf in conferencewishes : + bgcolor="style='background-color:PaleGreen '" + if (confVio[conf['id']].value() >0.9) : + bgcolor="style='background-color:#ff9980'" + cntr+= int (0.1+confVio[conf['id']].value()) + sol_conferences+='' + kpi_text += '' + for t1 in confTeams[conf['conference_id']]: + for t2 in confTeams[conf['conference_id']]: + for (r,d) in getRoundDaysByDay[conf['day_id']]: + if (t1,t2,(r,d)) in x.keys() and getVal(x[(t1,t2,(r,d))]) >0.9: + sol_conferences+='' +sol_conferences+='
"+_('Group')+""+_('Day')+""+_('Min. Number of Games')+" "+_('Max. Number of Games')+" "+_('Priority')+""+_('Comment')+"
'+ confName[conf['conference_id']] +' '+ getNiceDay[conf['day_id']] +' '+ str(conf['minGames']) +' '+ str(conf['maxGames']) +' '+ str(conf['prio']) +' '+ conf['reason'] +'
'+confName[conf['conference_id']] + ': ' + getNiceDay[conf['day_id']] + '
'+ getTeamById[t1] +' - '+ getTeamById[t2] +'  
' +kpi_text+="" + +if cntr==0 and len(conferencewishes)==0: + cntr="0 of 0" + +if thisSeason.useFeatureConferences: + sol_kpis+=_('Violated Group Wishes')+'__'+ str(cntr)+ '__'+ kpi_text+ '___' + +if mathModelName=="NBA": + sol_kpis+='Game Repetitions__'+ str(badRepeater_Total_NBA.value())+ '___' + sol_kpis+='East/West Travel__'+ str(int(eastWestTrip_Total_NBA.value()+0.1))+ '___' + +kpi_text="" +for (t1,t2) in realgames: + if missingGamesVio[(t1,t2)].value() >0.9: + kpi_text += '' +kpi_text+="
'+getTeamById[t1].replace("'","") + '-' + getTeamById[t2].replace("'","") + '
" + +sol_kpis+=_('Missing Games')+'__'+ str(int(missingGamesVioTotal.value()/2000000+0.1))+ '__'+ kpi_text + '___' + +if thisSeason.useFeatureBackToBack: + b2bvios = [(getTeamById[t],getNiceDay[d], int(0.5+badBackToBack[(t,d)].value())) for t in teams for d in days if (t,d) in badBackToBack.keys() and badBackToBack[(t,d)].value()>0.1 ] + greenB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1] + yellowB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==3] + redB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==50] + greyB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1000] + print ( len(greyB2B) , " + ",len(redB2B) , " + ",len(yellowB2B) , " + ",len(greenB2B)) + print (greyB2B+redB2B) + + sol_kpis+='Back to back__'+ str(len(greyB2B)) + " + " + str(len(redB2B)) + " + " + str(len(yellowB2B)) + " + " + str(len(greenB2B)) + '___' + + +if sol_kpis!="": + sol_kpis = sol_kpis[:-3] + +print (sol_kpis) + +kpis= [kpit.split("__") for kpit in sol_kpis.split("___")] + +print (kpis) +for kpi in kpis: + sol_quality+='' + kpi[0] + ':'+ kpi[1] + '' + +sol_quality+='' + +sol_quality+='
'+_('Show travels at')+'
' +sol_quality+='
' +sol_quality+='
' +sol_quality+='' + + +sol_quality+='
' +sol_quality+='
'+_('Distribution over Weekdays')+'
' +sol_quality+='' +sol_quality+=" " +sol_quality+='' +for wd in weekdays: + sol_quality+=" " +sol_quality+="" +for t in realteams : + goodHomeCntr=0 + goodAllCntr=0 + sol_quality+=' ' + for wd in weekdays: + bgcolor = '' + # if weekdayHomePref[(t,wd)]>= 1 and weekDayDistributionHome[(t, wd)] >= 1 : + if weekdayHomePref[(t,wd)]>= 1 : + bgcolor="style='background-color:#1ab394; color:white'" + # bgcolor="style='background-color:#1ab394'" + goodHomeCntr+=weekDayDistributionHome[(t, wd)] + goodAllCntr+=weekDayDistributionHome[(t, wd)] + sol_quality+=' ' + sol_quality+=' ' + sss=0 + if goodAllCntr>0 : + sss = int(100*goodHomeCntr/goodAllCntr) + sol_quality+='' +sol_quality+='
 "+_('Monday')+""+_('Tuesday')+""+_('Wednesday')+""+_('Thursday')+""+_('Friday')+""+_('Saturday')+""+_('Sunday')+"  
  "+_('Home')+" "+_('Away')+" "+_('Pref. Home')+"
' + getTeamById[t] + ' ' + str(weekDayDistributionHome[(t, wd)]) + ' ' + str(weekDayDistributionAway[(t, wd)]) + ' '+str(goodHomeCntr)+'/'+str(goodAllCntr)+'   '+ str(sss) +' %
' + + +firstDay=getDays[1][0] +for d in getDays[1]: + if getDateTimeDay[d]getDateTimeDay[lastDay]: + d=lastDay + +seasonLength= (getDateTimeDay[lastDay]-getDateTimeDay[firstDay]).days +avPhaseLength= seasonLength/nPhases + +realteams_dst = realteams + +# for c in conferences: +# print (c) + +# for (t,d,c) in tripToClusterDaily.keys(): +# print ("+ " , getTeamById[t], getNiceDay[d], " - " , c , type(tripToClusterDaily[(t,d,c)])) +# if tripToClusterDaily[(t,d,c)].value() >0.3: +# print ("++ " ,getTeamById[t], getNiceDay[d],c,tripToClusterDaily[(t,d,c)].value() ) + +if thisSeason.groupBased or mathModelName=="Florida State League": + def t_group(t): + if t_conference[t]==0: + return "" + return t_conference[t].name + realteams_dst = sorted(realteams, key=t_group) + +sol_quality+='
'+_('Number of days between encounters of same opponents')+'
\n' +sol_quality+="" +sol_quality+="" +for t2 in realteams_dst : + sol_quality+=' ' +sol_quality+='' +for t1 in realteams_dst : + sol_quality+=' ' + for t2 in realteams_dst : + games_string ="" + if not t2 in opponents[t1]: + sol_quality+=' ' + else: + gds = [] + for d in daysSorted: + for rd in getRoundDaysByDay[d]: + if getVal(x[(t1,t2,rd)]) + getVal(x[(t2,t1,rd)]) >0.9 and (thisSeason.gamesPerRound=="one day" or rd== getRoundDaysByRound[rd[0]][0]): + gds.append(d) + if getVal(x[(t1,t2,rd)]) >0.9: + games_string +="" + else : + games_string +="" + dists =[] + dist_string = "" + for i in range(1,len(gds)): + # print (gds[i-1] , gds[i]) + dt1=getDateTimeDay[gds[i-1]] + dt2=getDateTimeDay[gds[i]] + dd=(dt2-dt1).days + dists.append(dd ) + dist_string += str(dd)+ " " + pp = 1 + for j in dists: + pp*=j + pp = int(100*pow(pp,1.0/max(1,len(dists)) )/avPhaseLength) + bgcolor="style='background-color:lightyellow;white-space: nowrap; '" + if pp>85: + bgcolor="style='background-color:PaleGreen;white-space: nowrap; '" + if pp<70: + bgcolor="style='background-color:#ffc966;white-space: nowrap; '" + if pp<50: + bgcolor="style='background-color:#ff9980;white-space: nowrap; '" + if len(dists)==0: + bgcolor="" + if dist_string=="" and games_string !="": + dist_string="x" + sol_quality+='' + sol_quality+='' +sol_quality+=' ' +for t in realteams_dst : + sol_quality+=' ' +sol_quality+=' ' +sol_quality+=' ' +for t in realteams_dst : + sol_quality+=' ' +sol_quality+=' ' + +sol_quality+='
 
' + getTeamById[t2] + '
' + getTeamById[t1] + '  
" + getNiceDay[d] +" : " + getTeamById[t1] +" - " + getTeamById[t2] +"
" + getNiceDay[d] +" : " + getTeamById[t2] +" - " + getTeamById[t1] +"
'+ dist_string+ '
'+_('Distance Traveled')+' '+ scaledDistanceString(totalDistanceTravelled[t]) +'
'+_('Home Games')+''+ str( int(1*sum([ home[t,d].value()*getDayById[d]['attractivity'] for d in days ]))) +'
' + +sol_quality+='
'+_('Teams not having enough time to recover between games')+'
' +sol_quality+="" +sol_quality+="" +sol_quality+='' +for t1 in realteams : + for d in days+higherLeagueDayIds : + playsThatDay = sum([ getVal(home[(t,d)])+getVal(away[(t,d)]) for t in [t1]+higherTeamsOf[t1]])>0.9 + # if playsThatDay : + # print (getTeamById[t1] , "PLAYS ON ", getNiceDay[d] , [ getNiceDay[d3] for d3 in conflictDays[(t1,d)]] ) + if conflictDays[(t1,d)] and gamesTooClose[(t1,d)].value()>0.9 and playsThatDay: + sol_quality+=' ' + sol_quality+=' ' + for d2 in conflictDays[(t1,d)]: + playsThatOtherDay = sum([ getVal(home[(t,d2)])+getVal(away[(t,d2)]) for t in [t1]+higherTeamsOf[t1]])>0.9 + if d2 !=d and playsThatOtherDay: + # sol_quality+=' ' + sol_quality+= getNiceDay[d2] + ' ' + sol_quality+=' ' +sol_quality+='
"+_('Team')+" "+_('First Game')+" "+_('Second Game')+"
' + getTeamById[t1] + ' ' + getNiceDay[d] + ' ' + # sol_quality+=' ' + str(gamesTooClose[(t,d)].value()) + ' ' + getNiceDay[d2] + '
' + +sol_quality+='
'+_('Conflicts with international competitions')+'
' +sol_quality+="" +sol_quality+="" +sol_quality+='' + +for (t1,t2,r,d2) in currentSolution : + if getTeamById[t1]!='-' and getTeamById[t2]!='-' : + if (t1,d2) in competitions.keys() : + # print ('PROBLEM ', t1,t2,r,d2) + sol_quality+='' + if (t2,d2) in competitions.keys() : + # print ('PROBLEM ', t1,t2,r,d2) + sol_quality+='' +sol_quality+='
"+_('Round')+""+_('Date')+""+_('Home')+""+_('Away')+""+_('Competition')+"
'+ str(r) +''+ getNiceDay[d2] +''+ getTeamById[t1] +''+ getTeamById[t2] +''+ competitions[(t1,d2)] +'
'+ str(r) +''+ getNiceDay[d2] +''+ getTeamById[t1] +''+ getTeamById[t2] +''+ competitions[(t2,d2)] +'
' + +# for (t,r) in tooFarViolation.keys(): + # if tooFarViolation[(t,r)].value()>0.9: + # print ("too far violation " ,r, " ", getTeamById[t]) + +if mathModelName in ["ALPS" ,"ICE Hockey League" , "ICEYSL"]: + + print ("NICE TRIPS") + for (tr,d1,d2) in traveling_ICE.keys(): + if traveling_ICE[(tr,d1,d2)].value()>0.9: + t2,tms = trips_ICE[tr] + print ("ICE TRIP " , getNiceDay[d1], " ", getTeamById[t2]) + + print () + for (t,d1,d2) in toughWeekend_ICE.keys(): + if toughWeekend_ICE[(t,d1,d2)].value()>0.9: + print ("TOUGH WEEKEND " , getNiceDay[d1], " ", getTeamById[t]) + + print () + for (t1,t2,(r,d)) in x.keys(): + if getVal( x[(t1,t2,(r,d))])>0.9 and getWeekDay[d]=="Thu" and distanceById[t1,t2]>250 : + print ("TOUGH WEEK " , getNiceDay[d], " ", distanceById[t1,t2], " ", getTeamById[t1], " - ", getTeamById[t2]) + + print () + for (t1,t2,(r,d)) in toofarForXmas: + if getVal( x[(t1,t2,(r,d))])>0.9: + print ("BAD XMAS " , getNiceDay[d], " ", getTeamById[t1], " - ", getTeamById[t2] ," \t " , distanceById[t1,t2] ,"/" , maxDist4[t2]+1 ) + + # for enc in encwishes: + # if enc['reason']=="Seed Game" : + # print ("SEED ", 100*prioVal[enc['prio']] * encVio[enc['id']].value()) + # for el in elemEncWishes[enc['id']]: + # print (el, encVioTooMuch[el].value() , encVioTooLess[el].value() , elemEncWishDays[el] ) + # for d in elemEncWishDays[el]: + # print (" - ", getNiceDay[d] ) + + +if sharedStadiums : + stadiumTimeSlotBlockingGames=[] + getTeamObject = { t.id : t for t in Team.objects.filter(season=thisSeason,active=True)} + StadiumTimeSlotBlocking.objects.filter(homeTeam_id__in=teams).delete() + + for (t1,t2,r,d) in currentSolution: + print ("trying to create " , getTeamById[t1], getTeamById[t2],getNiceDayRaw[d]) + if (t1,d) in usedStadiumTimeSlot.keys(): + ts = stadiumTimeSlotPref[(t1,usedStadiumTimeSlot[(t1,d)]['id'])].stadiumTimeSlot + newSTSBG = StadiumTimeSlotBlocking(stadiumTimeSlot=ts, + day=getNiceDayRaw[d], homeTeam=getTeamObject[t1], awayTeam=getTeamObject[t2]) + stadiumTimeSlotBlockingGames.append(newSTSBG) + print (" ... created ", newSTSBG.day , " \t" , newSTSBG.stadiumTimeSlot.start , " \t" ,newSTSBG.homeTeam.name , " \t" , newSTSBG.awayTeam.name , ) + StadiumTimeSlotBlocking.objects.bulk_create(stadiumTimeSlotBlockingGames) + + print (usedStadiumTimeSlot.keys()) + +print ("TRIPS") +for t in teams: + for d in days: + for (c,v1,v2,v3,v4) in tripElements[t]: + if tripToSingleTripElement[(t,d,c)].value() and tripToSingleTripElement[(t,d,c)].value()>0.1: + print ("- " , getTeamById[t],getNiceDay[d] ," : ",[getTeamById[t2] for t2 in v1] ,"->" ,[getTeamById[t2] for t2 in v2 ] ,"->" ,[getTeamById[t2] for t2 in v3] ,"->" ,[getTeamById[t2] for t2 in v4 ] ) + +getBWish = { bwish.id : bwish for bwish in broadcastingwishes } +for (b,r) in broadVioTm.keys(): + if broadVioTm[(b,r)].value()>0.9: + print ("broadVioTm" , b,r , broadVioTm[(b,r)].value() , getBWish[b].network.name) + + +if lowerBoundFound: + print ("") + print("********************************") + print("* LOWER BOUND : ", lowerBoundFound , " *") + print("********************************") + print ("") + + +thisScenario.lastComputation=datetime.datetime.now() +thisScenario.sol_solution = sol_solution[:-2] +thisScenario.sol_prev_solution = thisScenario.sol_solution +thisScenario.sol_schedule = sol_schedule +thisScenario.sol_kpis = sol_kpis +thisScenario.sol_quality = sol_quality +thisScenario.sol_breaks = sol_breaks +thisScenario.sol_trips = sol_trips +thisScenario.sol_pairings = "" +thisScenario.sol_blockings = "" +thisScenario.sol_homeaway = "" +thisScenario.sol_encounters = "" +thisScenario.sol_broadcasting = sol_broadcasting +thisScenario.sol_special_wishes = sol_special_wishes +thisScenario.sol_conferences = sol_conferences +thisScenario.sol_checked = True +thisScenario.stopComputation = Scenario.objects.get(id=thisScenario.id).stopComputation + +if RUN_ENV == 'celery' and task: + thisScenario.task_id = task.request.id +else: + thisScenario.task_id = 0 +thisScenario.save() + +# create a backup of the scenario +if not evalRun: + savingtime = datetime.datetime.now().replace(microsecond=0) + backup = ScenarioBackup.objects.create(season=thisSeason, name=thisScenario.name, saved_at=savingtime, objective=last_objective) + fs = FileSystemStorage('data/') + directory = "save_point/{}/{}/".format(thisLeague.id,thisSeason.id) + os.makedirs("data/"+directory, exist_ok=True) + filename = "{}.json".format(backup.id) + with fs.open('{}/{}'.format(directory,filename), 'w') as f: + f.write(serialize_scenario(thisScenario)) + +if RUN_ENV == 'celery': + dup2(orig_std_out, 1) + close(orig_std_out) + f.close() + print({'timestamp':time.time(),'objective': last_objective, "user ": user_name, "league ": str(thisLeague)}) +else: + print("Success!") + +# %% \ No newline at end of file diff --git a/machine_learning/scripts/qubo/qubo.ipynb b/machine_learning/scripts/qubo/qubo.ipynb new file mode 100755 index 0000000..bd44d5e --- /dev/null +++ b/machine_learning/scripts/qubo/qubo.ipynb @@ -0,0 +1,2483 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d2a8b6c", + "metadata": {}, + "source": [ + "#### Database" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "7be9eeff", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT_PATH = '/home/md/Work/ligalytics/leagues_stable/'\n", + "import os, sys\n", + "sys.path.insert(0, PROJECT_PATH)\n", + "os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"leagues.settings\")\n", + "\n", + "\n", + "# XPRESS ENVIRONMENT\n", + "os.environ['XPRESSDIR'] = \"/opt/xpressmp_8.4\"\n", + "os.environ['XPRESS'] = \"/opt/xpressmp_8.4/bin\"\n", + "os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+\"/lib:\"+os.environ['LD_LIBRARY_PATH']\n", + "os.environ['DYLD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+\"/lib:\"\n", + "os.environ['SHLIB_PATH'] = os.environ['XPRESSDIR']+\"/lib:\"\n", + "os.environ['LIBPATH'] = os.environ['XPRESSDIR']+\"/lib:\"\n", + "os.environ['PYTHONPATH'] =os.environ['XPRESSDIR']+\"/lib:\"+os.environ['PYTHONPATH']\n", + "os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+\"/lib/xprs.jar:\"\n", + "os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+\"/lib/xprb.jar:\"+os.environ['CLASSPATH']\n", + "os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+\"/lib/xprm.jar:\"+os.environ['CLASSPATH']\n", + "os.environ['PATH'] =os.environ['XPRESSDIR']+\"/bin:\"+os.environ['PATH']\n", + "\n", + "\n", + "from leagues import settings\n", + "settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3'\n", + "\n", + "import django\n", + "django.setup()\n", + "\n", + "from scheduler.models import *\n", + "import operator\n", + "from scheduler.solver.functions import *\n", + "from common.functions import distanceInKmByGPS\n", + "import pandas as pd\n", + "import numpy as np\n", + "from django.db.models import Count, F, Value\n", + "from pulp import *\n", + "\n", + "\n", + "def makeIntVar(v):\n", + " if type(v) != int :\n", + " v.cat=pulp.LpInteger\n", + " # print (\"int var \", v)\n", + "\n", + "def setStart(v,vl):\n", + " if type(v) != int :\n", + " vl=float(vl)\n", + " v.start = vl\n", + "\n", + "def setLB(v,vl):\n", + " if type(v) != int :\n", + " vl=float(vl)\n", + " v.lowBound = vl\n", + " # print (\"set lb \", v , \" = \", vl)\n", + "\n", + "\n", + "def setUB(v,vl):\n", + " if type(v) != int :\n", + " vl=float(vl)\n", + " v.upBound = vl\n", + " # print (\"set ub \", v , \" = \", vl)\n", + "\n", + "def getVal(v):\n", + " if type(v) == int :\n", + " return v\n", + " else: \n", + " return v.value()\n", + "\n", + "\n", + "\n", + "\n", + "user_is_staff = True\n", + "runMode = 'New'\n", + "localsearch_time = 0\n", + "solver = '?'\n", + "\n", + "s2 = 2\n", + "thisScenario = Scenario.objects.get(id = s2)\n", + "thisSeason = thisScenario.season\n", + "thisLeague = thisSeason.league\n", + "\n", + "\n", + "mathModelName=thisLeague.name\n", + "if thisSeason.optimizationParameters!=\"\":\n", + " mathModelName=thisSeason.optimizationParameters" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ca4ac4b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Teams : [10, 3, 17, 14, 15, 13, 12, 6, 9, 5, 8, 18, 19, 20, 11, 7, 4, 16]\n", + "VIP Teams : []\n", + "TOP Teams : []\n", + "[]\n", + "objectivePrio: Breaks\n" + ] + } + ], + "source": [ + "\n", + "\n", + "otherScenGames=[]\n", + "if thisSeason.improvementObjective!=\"--\":\n", + " for otherScenario in Scenario.objects.filter(season=thisSeason):\n", + " impObjWeight = 5\n", + " if thisSeason.improvementObjective==\"stick to old solutions\":\n", + " impObjWeight = -5\n", + " if otherScenario.solutionlist() != [['']]:\n", + " for g in otherScenario.solutionlist(): \n", + " otherScenGames.append((int(g[1]),int(g[2]),int(g[3]), impObjWeight ))\n", + "\n", + "getGlobalCountry={ gc['uefa']: gc['id'] for gc in GlobalCountry.objects.values() }\n", + "\n", + "teamObjects = Team.objects.filter(season=thisSeason,active=True).order_by('position').values()\n", + "teams=[]\n", + "realteams=[]\n", + "faketeams=[]\n", + "importantteams=[]\n", + "shortteams=[]\n", + "t_pos={}\n", + "t_pot={}\n", + "t_color={}\n", + "t_country={}\n", + "t_globalCountry={}\n", + "t_stadium = {}\n", + "t_shortname = {}\n", + "t_usePhases = {}\n", + "t_lon = {}\n", + "t_lat = {}\n", + "t_attractivity ={}\n", + "getTeamById={}\n", + "getTeamIdByName={}\n", + "getTeamIdByShortName={}\n", + "getTeamByName={}\n", + "getStadiumById={}\n", + "teamByShort={}\n", + "top4 = []\n", + "for t in teamObjects:\n", + " # print (t['name'], t['id'])\n", + " teams.append(t['id'])\n", + " shortteams.append(t['shortname'])\n", + " teamByShort[t['shortname']]= t['name']\n", + " getTeamIdByShortName[t['shortname']]=t['id']\n", + " if t['name']!='-':\n", + " realteams.append(t['id'])\n", + " else:\n", + " faketeams.append(t['id'])\n", + " if t['very_important']:\n", + " importantteams.append(t['id'])\n", + " t_country[t['id']]= t['country']\n", + " t_globalCountry[t['id']]= t['globalCountry_id']\n", + " if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys() :\n", + " t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]]\n", + " t_pos[t['name']]= t['position']\n", + " t_color[t['name']]=\"lightyellow\"\n", + " t_pot[t['id']]= t['pot']\n", + " t_stadium[t['id']]=t['stadium']\n", + " t_attractivity[t['id']]=t['attractivity']\n", + " t_lon[t['id']]=t['longitude']\n", + " t_lat[t['id']]=t['latitude']\n", + " t_shortname[t['id']]=t['shortname']\n", + " t_usePhases[t['id']]= thisScenario.usePhases\n", + " getTeamById[t['id']]=t['name']\n", + " getStadiumById[t['id']]=t['stadium']\n", + " getTeamIdByName[t['name']]=t['id']\n", + " getTeamByName[t['name']]=t\n", + " if t['attractivity'] >= thisSeason.topTeamMinCoefficient :\n", + " top4.append(t['id'])\n", + "\n", + "inactive_teams=[]\n", + "for t in Team.objects.filter(season=thisSeason,active=False).order_by('position').values():\n", + " inactive_teams.append(t['id'])\n", + " t_attractivity[t['id']]=t['attractivity']\n", + " t_lon[t['id']]=t['longitude']\n", + " t_lat[t['id']]=t['latitude']\n", + " t_country[t['id']]= t['country']\n", + " getTeamById[t['id']]=t['name']\n", + "\n", + "\n", + "lowerBoundFound = False\n", + "distance ={}\n", + "attractivity ={}\n", + "distanceById={}\n", + "distanceInDaysById={}\n", + "stadium_names = set([t['stadium'] for t in teamObjects if t['stadium']!=''])\n", + "stadium_id={}\n", + "stadium_name={}\n", + "sid=0\n", + "for stadium in stadium_names :\n", + " stadium_name[sid]=stadium\n", + " stadium_id[stadium]=sid\n", + " sid+=1\n", + "stadiums = list(stadium_name.keys())\n", + "teamsOfStadium ={ st:[] for st in stadiums }\n", + "\n", + "travelDict= thisSeason.travelDict()\n", + "for t1 in teamObjects:\n", + " if t1['stadium']!='':\n", + " teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id'])\n", + " for t2 in teamObjects:\n", + " # distance[t1['name'],t2['name']] = distanceInKm(t1,t2)\n", + " # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ,\" -> \", travelDict[t1['id']][t2['id']]['distance'] )\n", + " distance[t1['name'],t2['name']] = travelDict[t1['id']][t2['id']]['distance'] \n", + " distanceById[t1['id'],t2['id']] = distance[t1['name'],t2['name']]\n", + " distanceInDaysById[t1['id'],t2['id']] = int(distance[t1['name'],t2['name']]/350 +0.99)\n", + " attractivity[t1['id'],t2['id']] = int(100*t1['attractivity']*t2['attractivity'])\n", + "\n", + "\n", + "dayObjects = Day.objects.filter(season=thisSeason).values()\n", + "days = [ d['id'] for d in dayObjects if d['round']>0 ]\n", + "\n", + "higherSeasons = thisSeason.higherSeasons()\n", + "higherGames = thisSeason.higherGames()\n", + "\n", + "this_season_team_names = list(getTeamByName.keys())\n", + "\n", + "higherTeamObjects = Team.objects.filter(season__in=higherSeasons,active=True).values()\n", + "higherDayObjects = Day.objects.filter(season__in=higherSeasons,maxGames__gte=1).values()\n", + "higherTeams = [ t['id'] for t in higherTeamObjects] \n", + "higherTeamsOf={ t: [] for t in teams}\n", + "for t in higherTeamObjects:\n", + " getTeamById[t['id']]=t['name']\n", + " t_country[t['id']]=t['country']\n", + " if t['name'] in this_season_team_names:\n", + " higherTeamsOf[getTeamIdByName[t['name']]].append(t['id'])\n", + "\n", + "print (\"Teams : \" , teams)\n", + "print (\"VIP Teams : \" , importantteams)\n", + "print (\"TOP Teams : \" , top4)\n", + "\n", + "allConferences = Conference.objects.filter(scenario=s2)\n", + "sharedStadiums = False\n", + "for ff in thisSeason.federationmember.all():\n", + " sharedStadiums= ff.federation.sharedStadiums\n", + "\n", + "# print (\"\\nGroups : \")\n", + "t_conference = {t : 0 for t in teams }\n", + "conf_teams={}\n", + "for c in Conference.objects.filter(scenario=s2,regional=False).order_by('name'):\n", + " # print (\"A\" , c)\n", + " cteams = c.teams.filter(active=True)\n", + " conf_teams[c.name]=[]\n", + " for t in cteams:\n", + " conf_teams[c.name].append(t.id)\n", + " # print (\"group for \", t)\n", + " # if t_conference[t.id]!=0:\n", + " # print (getTeamById[t.id] , \" in several groups\")\n", + " if t_conference[t.id]==0 or len(cteams)< len(t_conference[t.id].teams.filter(active=True)):\n", + " t_conference[t.id]=c\n", + " # print (\" is \" , c )\n", + "# for t in set([t for t in teams if t_conference[t]==0 ]):\n", + " # print (\" no group \" , getTeamById[ t])\n", + "# return ''\n", + "\n", + "prioVal ={'A': 25 , 'B': 5 , 'C': 1, 'Hard' : 1000}\n", + "\n", + "sw_prio = {}\n", + "sw_float1 = {}\n", + "sw_float2 = {}\n", + "sw_int1 = {}\n", + "sw_int2 = {}\n", + "sw_text = {}\n", + "for sw in SpecialWish.objects.filter(scenario=s2):\n", + " sw_prio[sw.name]=prioVal[sw.prio] if sw.active else 0\n", + " sw_float1[sw.name]=sw.float1\n", + " sw_float2[sw.name]=sw.float2\n", + " sw_int1[sw.name]=sw.int1\n", + " sw_int2[sw.name]=sw.int2\n", + " sw_text[sw.name]=sw.text\n", + "special_wishes=list(sw_prio.keys())\n", + "special_wishes_active = [ sw for sw in special_wishes if sw_prio[sw]>0 ]\n", + "\n", + "print (special_wishes_active)\n", + "\n", + "noBreakLimitTeams = []\n", + "if \"allowManyBreaksInRow\" in special_wishes_active :\n", + " noBreakLimitTeams = sw_text[\"allowManyBreaksInRow\"].split(\",\")\n", + " print (noBreakLimitTeams)\n", + " noBreakLimitTeams = [t for t in teams if getTeamById[t] in noBreakLimitTeams]\n", + " print (noBreakLimitTeams) \n", + "\n", + "pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values()\n", + "pairings_tmp2 = Pairing.objects.filter(scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values()\n", + "pairings_tmp3 = Pairing.objects.filter(scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values()\n", + "pairings = []\n", + "for p in [ p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams ] + [ p for p in pairings_tmp2 ] + [ p for p in pairings_tmp3]:\n", + " if p not in pairings:\n", + " pairings.append(p) \n", + "breaks = Break.objects.filter(season=thisSeason).values()\n", + "blockings_tmp = Blocking.objects.filter(scenario=s2,active=True).values()\n", + "blockings = [ bl for bl in blockings_tmp if bl['team_id'] in teams ]\n", + "\n", + "# times = ['Early', 'Late']\n", + "timeObjects = TimeSlot.objects.filter(season=thisSeason).values()\n", + "times = [ str(t['id']) for t in timeObjects]\n", + "getTimeById = {str(t['id']):t['name'] for t in timeObjects}\n", + "getIdByTime = {t['name']:str(t['id']) for t in timeObjects}\n", + "\n", + "blocked_arena = {(t,d, tm) : False for t in teams for d in days for tm in [\"----\"] + times }\n", + "hidden_arena = {(t,d, tm) : False for t in teams for d in days for tm in [\"----\"] + times }\n", + "nBlockingHome=0\n", + "nBlockingAway=0\n", + "\n", + "for bl in blockings:\n", + " if bl['type'] in [\"Hide\"]:\n", + " hidden_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True\n", + "\n", + " if bl['type'] in [\"Home\",\"Hide\"]:\n", + " nBlockingHome+=1\n", + " blocked_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True\n", + " else:\n", + " nBlockingAway+=1\n", + "\n", + "nTeams=len(teams)\n", + "nPhases =thisSeason.numPhases\n", + "groupView = thisSeason.groupBased\n", + "\n", + "gameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams}\n", + "undirectedGameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams}\n", + "\n", + "if thisSeason.useFeatureOpponentMatrix:\n", + " gameRequirements = GameRequirement.objects.filter(season=thisSeason, team1__active=True, team2__active=True) \n", + " # print (len(gameRequirements))\n", + " for gm in gameRequirements:\n", + " if thisSeason.undirectedGames:\n", + " undirectedGameCntr[(gm.team1.id,gm.team2.id)]+=gm.number\n", + " undirectedGameCntr[(gm.team2.id,gm.team1.id)]+=gm.number\n", + " # print ( \"found undirected game \" , (gm.team1.id,gm.team2.id) , gm.number )\n", + " else:\n", + " gameCntr[(gm.team1.id,gm.team2.id)]+=gm.number\n", + " # print ( \"found directed game \" , (gm.team1.id,gm.team2.id) , gm.number )\n", + "else:\n", + " for t1 in teams:\n", + " for t2 in teams:\n", + " if t1!=t2:\n", + " gameCntr[(t1,t2)]+=nPhases/2\n", + "\n", + " for c in Conference.objects.filter(scenario=s2):\n", + " cteams = c.teams.filter(active=True)\n", + " for t1 in cteams:\n", + " for t2 in cteams:\n", + " if t1!=t2:\n", + " gameCntr[(t1.id,t2.id)]+=c.deltaGames/2\n", + "\n", + " for (t1,t2) in gameCntr.keys():\n", + " if gameCntr[(t1,t2)]%1!=0 :\n", + " undirectedGameCntr[(t1,t2)]=1\n", + " gameCntr[(t1,t2)] = int(gameCntr[(t1,t2)])\n", + "\n", + "games = [ gm for gm in gameCntr.keys() if gameCntr[gm]+undirectedGameCntr[gm]>0]\n", + "\n", + "objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values()\n", + "\n", + "gew={}\n", + "gew['Trips'] = 5\n", + "gew['Derbies'] = 5\n", + "gew['Pairings'] = 5\n", + "for ow in objectiveFunctionWeights:\n", + " gew[ow['name']]= ow['use'] * ow['prio']\n", + "\n", + "objectivePrio = 'Breaks'\n", + "if gew['Trips']>gew['Breaks'] :\n", + " objectivePrio = 'Trips'\n", + "\n", + "print(\"objectivePrio:\", objectivePrio)\n", + "\n", + "specialGameControl= mathModelName in [\"Florida State League\"]\n", + "\n", + "if len(games)==0:\n", + " games = [ (t1,t2) for t1 in teams for t2 in teams if t1!=t2 ]\n", + " specialGameControl=True\n", + " \n", + "realgames= [(t1,t2) for (t1,t2) in games if getTeamById[t1]!=\"-\" and getTeamById[t2]!=\"-\"]\n", + "\n", + "# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert\n", + "evalRun = ( runMode == \"Improve\" and localsearch_time==0 \n", + " and len(thisScenario.solutionlist())==sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames]) )\n", + "\n", + "opponents = { t: set([]) for t in realteams}\n", + "\n", + "for (t1,t2) in games:\n", + " if t1 in realteams and t2 in realteams:\n", + " opponents[t1].add(t2)\n", + " opponents[t2].add(t1)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1c51e80c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gmClusters range(1, 2)\n" + ] + } + ], + "source": [ + "\n", + "model7 = pulp.LpProblem(\"FindGameClusters_\"+str(thisScenario.id), pulp.LpMinimize)\n", + "gmClusters = range(1,nTeams+2)\n", + "\n", + "if nPhases>0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl:\n", + " gmClusters = range(1,2) \n", + "\n", + "print (\"gmClusters\", gmClusters)\n", + "x7 = {(t,c) : pulp.LpVariable('x7_'+str(t)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for c in gmClusters}\n", + "\n", + "for t in realteams: \n", + " model7+= lpSum([ x7[(t,c)] for c in gmClusters]) ==1 \n", + "\n", + "for (t1,t2) in gameCntr.keys():\n", + " if gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)]==0 and t1!=t2:\n", + " for c in gmClusters:\n", + " model7+= x7[(t1,c)]+ x7[(t2,c)] <=1\n", + "\n", + "model7+= lpSum([ c*x7[(t,c)] for (t,c) in x7.keys()]) \n", + "\n", + "if solver == \"CBC\":\n", + " model7.solve(PULP_CBC_CMD(threads = 8,msg=1))\n", + "elif solver == \"Gurobi\":\n", + " model7.solve(GUROBI(MIPGap=0.01,msg=1))\n", + "else:\n", + " model7.solve(XPRESS(msg=1, targetGap=0.01, maxSeconds = 100, keepFiles=True))\n", + "\n", + "\n", + "gameClusterTeams = { c : [ t for t in teams if x7[(t,c)].value()>0.01] for c in gmClusters }\n", + "gameClusters=[ c for c in gameClusterTeams.keys() if len(gameClusterTeams[c])>0]\n", + "biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters])" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a446fffc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nRounds 34\n", + "nPhases 2\n", + "nRoundsPerPhase 17\n", + "defaultGameRepetions 1\n", + "tripStartHeuristicGroupsize 1\n" + ] + } + ], + "source": [ + "nPhases = min( [ gameCntr[(t1,t2)]+ gameCntr[(t2,t1)] + undirectedGameCntr[(t1,t2)] for (t1,t2) in realgames ])\n", + "tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize==\"None\" else int(thisSeason.tripStartHeuristicGroupsize)\n", + "defaultGameRepetions = 1 if not mathModelName in [\"NHL\", \"NBA\"] else 2\n", + "nPhases=max(1,int(nPhases/defaultGameRepetions))\n", + "phases = range(nPhases)\n", + "\n", + "useBasicGames = nPhases>0\n", + "\n", + "if not useBasicGames:\n", + " print ('no basic games but biggest group size ', biggestGroupSize, ' in nPhases ' , nPhases )\n", + "\n", + "nRounds = thisSeason.nRounds\n", + "rounds=range(1,nRounds+1)\n", + "\n", + "# nRoundsPerPhase= 1\n", + "# if nPhases>0:\n", + "nRoundsPerPhase= int(nRounds/nPhases)\n", + "\n", + "print ('nRounds ',nRounds)\n", + "print ('nPhases ',nPhases)\n", + "print ('nRoundsPerPhase ',nRoundsPerPhase)\n", + "print ('defaultGameRepetions ',defaultGameRepetions)\n", + "print ('tripStartHeuristicGroupsize ',tripStartHeuristicGroupsize)\n", + "\n", + "getPhaseOfRound = { r : min( nPhases-1 , int((r-1)/nRoundsPerPhase)) for r in rounds }\n", + "getDaysOfPhase = { p : [] for p in phases }\n", + "getDays = { r : [] for r in rounds}\n", + "roundGamesMax = { r : 0 for r in rounds}\n", + "roundGamesMin = { r : 0 for r in rounds}\n", + "getDayById = { d['id'] : d for d in dayObjects}\n", + "getDayByDateTime = { }\n", + "getNiceDayRaw = { d['id'] : d['day'] for d in dayObjects}\n", + "getNiceDay = { d['id'] : d['day'] for d in dayObjects}\n", + "getWeekDay = { d['id'] : '' for d in dayObjects}\n", + "getDateTimeDay = { d['id'] : '' for d in dayObjects}\n", + "getRoundByDay = { d['id'] : d['round'] for d in dayObjects if d['round']>0 }\n", + "getDayMinGames = { d['id'] : d['minGames'] for d in dayObjects if d['round']>0 }\n", + "getDayMaxGames = { d['id'] : d['maxGames'] for d in dayObjects if d['round']>0 }\n", + "getRoundsByDay = { d['id'] : [] for d in dayObjects }\n", + "nDerbies = { d['id'] : [d['nDerbies']] for d in dayObjects}\n", + "roundDays =[]\n", + "roundDaysMin ={ }\n", + "roundDaysMax ={ }\n", + "wds= {0:'Mon', 1:'Tue', 2:'Wed', 3:'Thu', 4:'Fri', 5:'Sat', 6:'Sun'}\n", + "for d in dayObjects:\n", + " if d['round']>0 :\n", + " getRoundsByDay[d['id']].append(d['round'])\n", + " getDays[d['round']].append(d['id'])\n", + " roundDays.append((d['round'],d['id']))\n", + " roundDaysMin[(d['round'],d['id'])]=d['minGames']\n", + " roundDaysMax[(d['round'],d['id'])]=d['maxGames']\n", + " roundGamesMax[d['round']]=min(nTeams/2, roundGamesMax[d['round']]+d['maxGames'] )\n", + " roundGamesMin[d['round']]+=d['minGames']\n", + " ph = getPhaseOfRound[d['round']]\n", + " getDaysOfPhase[ph].append(d['id'])\n", + " if d['round2']>0:\n", + " getRoundsByDay[d['id']].append(d['round2'])\n", + " getDays[d['round2']].append(d['id'])\n", + " roundDays.append((d['round2'],d['id']))\n", + " roundDaysMin[(d['round2'],d['id'])]=d['minGames2']\n", + " roundDaysMax[(d['round2'],d['id'])]=d['maxGames2']\n", + " roundGamesMax[d['round2']]=min(nTeams/2, roundGamesMax[d['round2']]+d['maxGames2'] )\n", + " roundGamesMin[d['round2']]+=d['minGames2']\n", + "\n", + " dt = parse(d['day'])\n", + " getDateTimeDay[d['id']] = dt\n", + " getDayByDateTime[dt] = d['id']\n", + " getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y'))\n", + " getWeekDay[d['id']] = str(wds[dt.weekday()])\n", + "\n", + "countries = list(set( [t_country[t] for t in teams ])) \n", + "\n", + "getWeekDaysPerRound = { r : [ getWeekDay[d] for d in getDays[r]] for r in rounds}\n", + "\n", + "wd = {\"Mondays\":0 , \"Tuesdays\":1 , \"Wednesdays\":2 , \"Thursdays\":3 , \"Fridays\":4 , \"Saturdays\":5 , \"Sundays\":6}\n", + "t_site_bestTimeSlots = { (t,d): [] for t in realteams for d in days }\n", + "prio_weight = { \"A\" : 0 , \"B\" : 50 , \"C\" : 100}\n", + "\n", + "\n", + "toTime=False\n", + "\n", + "\n", + "earliestDay={r: getDays[r][0] for r in rounds }\n", + "latestDay={r: getDays[r][0] for r in rounds }\n", + "for r in rounds :\n", + " for d in getDays[r]:\n", + " dt=getDateTimeDay[d]\n", + " if dt>getDateTimeDay[latestDay[r]]:\n", + " latestDay[r]=d\n", + " if dt0:\n", + " day2= day1 + datetime.timedelta(days=c.timeframe-1)\n", + " # print (e)\n", + " # print (day1, day2)\n", + " for d3 in days:\n", + " dt= getDateTimeDay[d3] \n", + " # dt = parse(getDayById[d3]['day'])\n", + " if day1<=dt and dt<=day2 :\n", + " tmpDays.append(d3) \n", + " else: \n", + " r1 = getDayById[d[0]]['round']\n", + " for d3 in days:\n", + " # dt = parse(getDayById[d3]['day'])\n", + " dt= getDateTimeDay[d3] \n", + " if day1<=dt and getRoundByDay[d3]< r1 + (-c.timeframe) :\n", + " tmpDays.append(d3)\n", + " # print (\" ROUNDWISH \", e, elemEncWishDays[cntr], e['timeframe'])\n", + " # for d4 in elemEncWishDays[cntr]:\n", + " # print (\" - \" ,getDayById[d4]['day'])\n", + "\n", + " if len([ d for d in tmpDays if d not in lastDaySet ])>0:\n", + " encDaySets[c.id].append(tmpDays)\n", + " lastDaySet = tmpDays\n", + " # print(\"-.--- NEW DAYS\", tmpDays)\n", + "\n", + " for ds in encDaySets[c.id]:\n", + " for d3 in ds:\n", + " encRounds[c.id]+= getRoundsByDay[d3]\n", + " encRounds[c.id]=sorted(list(set(encRounds[c.id])))\n", + " for r in encRounds[c.id]:\n", + " encRoundsString[c.id]+= str(r)+\"_\"\n", + " if encRoundsString[c.id]!=\"\":\n", + " encRoundsString[c.id]=encRoundsString[c.id][:-1]\n", + " # print (encRoundsString[c.id] , \" # \" ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds)\n", + "\n", + "\n", + "onlyEarlyDays= []\n", + "onlyLateDays= []\n", + "\n", + "\n", + "alwaysConsiderAllGames = not mathModelName in [\"Florida State League\" , \"UEFA NL\"]\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "elemEncWishes ={e['id'] : [] for e in encwishes}\n", + "elemEncWishGames={}\n", + "elemEncWishDays={}\n", + "\n", + "cntr=0\n", + "for e in encwishes:\n", + " for eg in encGames[e['id']] :\n", + " for ed in encDaySets[e['id']]:\n", + " if len(eg)>0 and len(ed)>0:\n", + " cntr+=1\n", + " elemEncWishes[e['id']].append(cntr)\n", + " elemEncWishGames[cntr] = eg\n", + " elemEncWishDays[cntr] = ed\n", + "\n", + "\n", + "# print (elemEncWishGames)\n", + "# print (elemEncWishDays)\n", + "\n", + "\n", + "encRelRoundsMin = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]}\n", + "encRelRoundsMax = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]}\n", + "\n", + "for enc in encwishes:\n", + " # print (e)\n", + " # print (\"ENC !! \" , enc['reason'] )\n", + " for el in elemEncWishes[enc['id']]:\n", + " relDaysSet = set(elemEncWishDays[el])\n", + " for r in basicRounds :\n", + " if len(relDaysSet.intersection(set(getBasicDays[r])))>0:\n", + " frac = len(relDaysSet.intersection(set(getBasicDays[r]))) / len (getBasicDays[r])\n", + " # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac)\n", + " if frac>0:\n", + " encRelRoundsMin[el].append(r)\n", + " if frac==1:\n", + " encRelRoundsMax[el].append(r)\n", + "\n", + "nElemEncWishes = sum ([len(elemEncWishes[enc['id']]) for enc in encwishes]) \n", + "\n", + "# print (encRelRoundsMin)\n", + "\n", + "elemHaWishes ={e['id'] : [] for e in hawishes}\n", + "elemHaWishTeams={}\n", + "elemHaWishDays ={}\n", + "elemHaWishFirstDay ={}\n", + "\n", + "cntr =1\n", + "for e in hawishes:\n", + " elemTeams= [ hawTeams[e['id']]]\n", + " if e['forEachTeam']:\n", + " elemTeams= [ [t] for t in hawTeams[e['id']]]\n", + "\n", + " elemDays= [ hawDays[e['id']]]\n", + " if e['forEachDay'] or e['forOneDay'] :\n", + " elemDays= [ [d] for d in hawDays[e['id']]]\n", + "\n", + " elemHaWishes[e['id']]=[]\n", + " allElemDays=[]\n", + " thisDaySet=[]\n", + " lastDaySet = []\n", + " for d in elemDays :\n", + " # print (e)\n", + " if (e['forEachDay'] or e['forOneDay']) and e['timeframe']!=1:\n", + " thisDaySet=[]\n", + " # day1= parse(getDayById[d[0]]['day'])\n", + " day1= getDateTimeDay[d[0]]\n", + " if e['timeframe']>1:\n", + " day2=day1 + datetime.timedelta(days=e['timeframe']-1)\n", + " for d3 in days:\n", + " # dt = parse(getDayById[d3]['day'])\n", + " dt= getDateTimeDay[d3]\n", + " if day1<=dt and dt<=day2 :\n", + " thisDaySet.append(d3)\n", + " else:\n", + " r1 = getDayById[d[0]]['round']\n", + " for d3 in days:\n", + " dt= getDateTimeDay[d3]\n", + " # dt = parse(getDayById[d3]['day'])\n", + " if day1<=dt and getRoundByDay[d3]< r1 + (-e['timeframe']) :\n", + " thisDaySet.append(d3)\n", + " print (\" ROUND HA WISH \", e, thisDaySet, e['timeframe'])\n", + " else:\n", + " thisDaySet=d\n", + "\n", + " # only create wish id new day set is superset\n", + " if len([d for d in thisDaySet if d not in lastDaySet])>0:\n", + " for t in elemTeams:\n", + " cntr+=1\n", + " elemHaWishes[e['id']].append(cntr)\n", + " elemHaWishTeams[cntr]=t\n", + " elemHaWishDays[cntr]=thisDaySet.copy()\n", + " elemHaWishFirstDay[cntr]=d[0]\n", + " lastDaySet = thisDaySet.copy()\n", + " allElemDays+= thisDaySet \n", + "\n", + " hawRounds[e['id']]=[]\n", + " for d3 in set(allElemDays):\n", + " hawRounds[e['id']]+=getRoundsByDay[d3]\n", + " hawRounds[e['id']]=sorted(list(set(hawRounds[e['id']])))\n", + " hawRoundsString[e['id']]= \"\"\n", + " for r in hawRounds[e['id']]:\n", + " hawRoundsString[e['id']]+= str(r)+\"_\"\n", + " if hawRoundsString[e['id']]!=\"\":\n", + " hawRoundsString[e['id']]=hawRoundsString[e['id']][:-1]\n", + " \n", + "nElemHaWishes = sum ([len(elemHaWishes[enc['id']]) for enc in hawishes]) \n", + "\n", + "gameAttractivity = { (t1,t2) : attractivity[t1,t2] for (t1,t2) in games if t10 ]) for t in teams }\n", + "\n", + "\n", + "# scale blocking of basic round to [0,1]\n", + "for t in teams: \n", + " for r in basicRounds:\n", + " if len(getBasicDays[r])>0:\n", + " nonBlocked[(t,r)]/=len(getBasicDays[r])\n", + " travelDays[(t,r)]/=len(getBasicDays[r])\n", + " if getTeamById[t]==\"AX Armani Exchange Milan\":\n", + " print (r , getRealRounds[r] , nonBlocked[(t,r)] , nonBlocked[(t,r)] )\n", + "\n", + "for t in teams:\n", + " if mathModelName==\"UEFA NL\" and noPlayRounds[t] in [ [3,4] ]:\n", + " t_usePhases[t]= False\n", + " print (\"No need for phases \" , getTeamById[t])\n", + " for t2 in opponents[t]:\n", + " t_usePhases[t2]= False\n", + " print (\" -- also no need for phases \" , getTeamById[t2])\n", + " if getTeamById[t]==\"AX Armani Exchange Milan\":\n", + " t_usePhases[t]= False\n", + " print (\"setting t_usePhases of \", getTeamById[t],t_usePhases[t] )\n", + "\n", + "runPatternAssignmentFirst=False\n", + "\n", + "\n", + "chosenGames = [ ]\n", + "\n", + "useFullModel1= False\n", + "\n", + "usePatterns= not mathModelName in [\"NHL\", \"LNR\"]\n", + "usePatterns= not mathModelName in [\"NHL\"]\n", + "\n", + "\n", + "balanceBreaks = mathModelName==\"LNR\" \n", + "haSymmetric = not mathModelName in [\"NHL\" , \"LNR\"]\n", + "haSymmetric = not mathModelName in [\"NHL\", \"Ligue 1\", \"Costa Rica Premier League\"]\n", + "haSymmetric = not mathModelName in [\"NHL\"]\n", + "# haSymmetric = not mathModelName in [\"NHL\" , \"LNR\"]\n", + "if thisSeason.symmetry:\n", + " haSymmetric=True\n", + "\n", + "use2BreakPatterns = False\n", + "\n", + "if thisSeason.initBreaks>=2 :\n", + " use2BreakPatterns = True\n", + "\n", + "half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1\n", + "\n", + "\n", + "prev_mirror_round= { r : 0 if r<=nRounds1 else r-nRounds1+half_symmetry_offset for r in basicRounds }\n", + "\n", + "\n", + "for r in basicRounds:\n", + " if r>nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1):\n", + " prev_mirror_round[r] = int((r-1)/nRounds1-1)*nRounds1 +half_symmetry_offset+1-prev_mirror_round[r]%nRounds1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "getPhaseOfBasicRound={ br : getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds}\n", + "getBasicRoundsOfPhase={ ph : [ br for br in basicRounds if getPhaseOfBasicRound[br]==ph ] for ph in phases }\n", + "\n", + "if use2BreakPatterns or nTeams >200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams>0 :\n", + " useFullModel1=True\n", + "useFullModel1=True \n", + "\n", + "\n", + "preplan_phases = phases\n", + "preplan_phases= [0]\n", + "if not haSymmetric:\n", + " preplan_phases = phases\n", + "\n", + "fixedGamesRounds=[]\n", + "\n", + "starweight={t : 0.0 for t in teams}\n", + "for t1 in teams :\n", + " for t2 in teams :\n", + " starweight[t1]+=distance[getTeamById[t1],getTeamById[t2]]\n", + "\n", + "available_days={ (t,d) : 1 for t in teams for d in days}\n", + "\n", + "\n", + "for bl in blockings:\n", + " if bl['type'] in [\"Home\",\"Hide\"] and bl['time']=='----':\n", + " available_days[ bl['team_id'],bl['day_id']] =0\n", + "\n", + "t_blocked_at={ (t,r) : sum([available_days[t,d] for d in getDays[r]])==0 for t in teams for r in rounds}" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "a11f1f0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " .... got rid of 3006 + 0 vars\n" + ] + } + ], + "source": [ + "\n", + "model2 = pulp.LpProblem(\"League_Scheduling_Model_--_Schedule_Games_\"+str(thisScenario.id), pulp.LpMinimize)\n", + "\n", + "\n", + "nextDay = { d : -1 for d in days }\n", + "previousDay = { d : -1 for d in days}\n", + "\n", + "# cntr=0\n", + "lastDay=parse(\"01.01.1990\")\n", + "for thisDay in sorted(getDayByDateTime.keys()):\n", + " if thisDay-lastDay==datetime.timedelta(days=1):\n", + " # cntr+=1\n", + " d1 = getDayByDateTime[lastDay]\n", + " d2 = getDayByDateTime[thisDay]\n", + " nextDay[d1]=d2\n", + " previousDay[d2]=d1\n", + " # print (\"next day of \" , getNiceDay[d1] , ' ' , getNiceDay[d2])\n", + " lastDay=thisDay\n", + "\n", + "# print (cntr)\n", + "# cntr=0\n", + "# for d in days+higherLeagueDayIds :\n", + "# for d2 in days+higherLeagueDayIds:\n", + "# if getDateTimeDay[d2]-getDateTimeDay[d]==datetime.timedelta(days=1):\n", + "# cntr+=1\n", + "# nextDay[d]=d2\n", + "# previousDay[d2]=d\n", + "# print (\"next day of \" , getNiceDay[d] , ' ' , getNiceDay[d2])\n", + "# print (cntr)\n", + "\n", + "\n", + "cntr= 0\n", + "cntr2= 0\n", + "\n", + "# print (\"games\",games)\n", + "# print (roundDays)\n", + "\n", + "seedTV = []\n", + "dontPlay = []\n", + "dontPlayGames = []\n", + "\n", + "for bl in blockings:\n", + " if bl['type']==\"Hide\":\n", + " dontPlay += [(bl['team_id'],bl['day_id']) ]\n", + "\n", + "dontPlay += [(t,d) for d in days for t in teams if getDayMaxGames[d]==0 ] \n", + "\n", + "# for t1 in teams: \n", + "# for d in days:\n", + "# if blocked_arena[(t1,d, \"----\" )] :\n", + "# dontPlayGames += [(t1,t2,d) for t2 in teams]\n", + "\n", + "\n", + "# print()\n", + "# print(len(dontPlayGames) , \" \", len (days)*len(teams)*(len(teams)-1))\n", + "\n", + "\n", + "if mathModelName==\"NBA\":\n", + " for t1 in teams: \n", + " for d in days:\n", + " if blocked_arena[(t1,d, \"----\" )] :\n", + " dontPlayGames += [(t1,t2,d) for t2 in teams]\n", + " # print (len(dontPlayGames), \"BLOKING \" )\n", + "\n", + " for enc in encwishes:\n", + " # print (\"ENC \" ,enc , enc['reason'][:11],enc['reason'][:11]== 'TV Network:') \n", + " if enc['reason'][:11]== 'TV Network:': \n", + " print (\"ENC \" , encGames[enc['id']] ,encDays[enc['id']] )\n", + " seedTV += [ (t1,t2,d,enc['reason'][12:]) for (t1,t2) in encGames[enc['id']] for d in encDays[enc['id']]]\n", + " if enc['maxGames']== 0 and not enc['time'] in [getIdByTime[\"Early\"], getIdByTime[\"Late\"]]:\n", + " dontPlayGames += [ (t1,t2,d) for (t1,t2) in encGames[enc['id']] for d in encDays[enc['id']]]\n", + "\n", + " # print (len(dontPlayGames),encGames[enc['id']] , encDays[enc['id']] , enc['reason'] )\n", + " \n", + "\n", + " usingRDs={t: [] for t in teams}\n", + " for (t1,t2,d,channel) in seedTV:\n", + " # print (t1,t2,d , gameCntr[(t1,t2)], gameCntr[(t1,t2)]==1)\n", + " usingRDs[t1]+=getRoundDaysByDay[d]\n", + " usingRDs[t2]+=getRoundDaysByDay[d]\n", + " for d2 in [ previousDay[d], nextDay[d]]:\n", + " if channel==\"ABC\" and d2!=-1:\n", + " dontPlay+=[(t1,d2), (t2,d2)]\n", + "\n", + " TVteams = [t for t in teams if len (usingRDs[t])>=10 ]\n", + "\n", + "# for t in teams: \n", + "# print (t, getTeamById[t], [ r for (r,d) in usingRDs[t]], t_conference[t]) \n", + "\n", + "# print (TVteams)\n", + "# print (\"CHECKPOINT \", 5)\n", + "\n", + "# print (seedTV)\n", + "# print (dontPlay)\n", + "\n", + "x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays}\n", + "for (t1,t2) in games:\n", + " for rd in roundDays:\n", + " x[(t1,t2,rd)]=1\n", + "\n", + "for (t1,t2,rd) in x.keys():\n", + " if blocked_arena[(t1,rd[1],\"----\")] and runMode=='Improve' and not thisSeason.allowBlockingViosInImprove:\n", + " # cntr +=1\n", + " # print (\"FORBIDDING\")\n", + " \n", + " x[(t1,t2,rd)]=0\n", + "\n", + "for (t1,t2,d,channel) in seedTV:\n", + " if gameCntr[(t1,t2)]==1:\n", + " for (r,d2) in roundDays:\n", + " if d != d2 :\n", + " # cntr +=1\n", + " x[(t1,t2,(r,d2))]=0\n", + "\n", + " for t3 in teams: \n", + " if not t3 in [t1,t2]:\n", + " for rd in getRoundDaysByDay[d]:\n", + " x[(t1,t3,rd)]=0\n", + " x[(t2,t3,rd)]=0\n", + " x[(t3,t1,rd)]=0\n", + " x[(t3,t2,rd)]=0\n", + " # cntr +=4\n", + "\n", + "for (t,d) in dontPlay:\n", + " for t3 in teams:\n", + " if not t3 in [t1,t2]:\n", + " for rd in getRoundDaysByDay[d]:\n", + " x[(t,t3,rd)]=0\n", + " x[(t3,t,rd)]=0\n", + "\n", + "for (t1,t2,d) in dontPlayGames:\n", + " for rd in getRoundDaysByDay[d]:\n", + " x[(t1,t2,rd)]=0\n", + "\n", + "attendance = { (t1,t2,d) : 0 for (t1,t2) in games for d in days }\n", + "if thisSeason.useFeaturePrediction:\n", + " # learner = AttendanceLearner(thisSeason.id)\n", + " # attendance = learner.predict_games(attendance.keys())\n", + " oldestTrainingGame= parse(\"2015-01-01\")\n", + " oldestTrainingGame=oldestTrainingGame.date()\n", + " games_train = Game.objects.filter(season=thisSeason).exclude(historic_season=None)\n", + " print ( \" all games\",len(games_train), )\n", + " games_train = [g for g in games_train if g.date>oldestTrainingGame ]\n", + " print ( \" important games\",len(games_train), )\n", + "\n", + " games_predict = attendance.keys()\n", + "\n", + " X= []\n", + " y =[]\n", + " homeAttendances = {t: [] for t in teams+inactive_teams}\n", + " for game in games_train:\n", + " if game.attendance>0:\n", + " homeAttendances[game.homeTeam.id].append(game.attendance)\n", + " for t in teams+inactive_teams:\n", + " homeAttendances[t]=sorted(homeAttendances[t])\n", + " # print (getTeamById[t], homeAttendances[t])\n", + " if len(homeAttendances[t])==0:\n", + " homeAttendances[t]=[7500]\n", + " maxAttendance = {t: homeAttendances[t][-1] for t in teams+inactive_teams }\n", + " # maxAttendance = {t: homeAttendances[t][int(0.9*(len(homeAttendances[t])))] for t in teams+inactive_teams }\n", + " medianAttendance = {t: homeAttendances[t][int(0.5*(len(homeAttendances[t])))] for t in teams+inactive_teams }\n", + " loyalty = {t: medianAttendance[t]/maxAttendance[t] for t in teams+inactive_teams }\n", + "\n", + " for t in teams+inactive_teams: \n", + " print (getTeamById[t], loyalty[t] , medianAttendance[t] , maxAttendance[t] , )\n", + "\n", + "# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays}\n", + "x_round= {(t1,t2,r) : 0 for t1 in teams for t2 in teams for r in rounds}\n", + "x_time= {(t1,t2,rd,tm) : 0 for t1 in teams for t2 in teams for rd in roundDays for tm in times}\n", + "\n", + "\n", + "for (t1,t2) in games:\n", + " for rd in roundDays:\n", + " # print (\"make var \" ,t1,t2,rd)\n", + " if x[(t1,t2,rd)]==1 :\n", + " # if not blocked_arena[(t1,rd[1],\"----\")] or runMode=='New':\n", + " if not evalRun:\n", + " x[(t1,t2,rd)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1]), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \n", + " cntr +=1\n", + " if thisSeason.useFeatureKickOffTime:\n", + " for tm in times:\n", + " x_time[(t1,t2,rd,tm)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1])+'_'+tm , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) \n", + " else: \n", + " cntr+=len(roundDays)\n", + "\n", + " for r in rounds:\n", + " x_round[(t1,t2,r)]= pulp.LpVariable('x_round_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous)\n", + " model2+= x_round[(t1,t2,r)] == sum([x[(t1,t2,rd)] for rd in getRoundDaysByRound[r]])\n", + "\n", + "\n", + "t_prev_mirror_round ={(t,r) : 0 for t in teams for r in rounds}\n", + "\n", + "for t1 in teams: \n", + " phaseLength= int(len(playRounds[t1])/nPhases+0.5)\n", + " # print (\"phaseLength\", phaseLength)\n", + " for i in range(len(playRounds[t1])):\n", + " # print (i, phaseLength)\n", + " if i >=phaseLength:\n", + " # print (\"setting \" , playRounds[t1], playRounds[t1][i-phaseLength])\n", + " # print (\"setting \" , playRounds[t1][i], \"->\",playRounds[t1][i-phaseLength])\n", + " t_prev_mirror_round[(t1,playRounds[t1][i])]=playRounds[t1][i-phaseLength]\n", + " # t_prev_mirror_round[(t,playRounds[t1][i])]=5\n", + "\n", + "\n", + "# for tr in t_prev_mirror_round.keys():\n", + "# print( \"t_prev_mirror_round \" ,tr , t_prev_mirror_round[tr])\n", + "\n", + "# print (prev_mirror_round)\n", + "\n", + "\n", + "if not evalRun:\n", + " for (t1,t2) in games:\n", + " for r in rounds:\n", + " if r > nRounds1 and thisSeason.symmetry : \n", + " prev_round =prev_mirror_round[r] \n", + " if thisSeason.groupBased and len(noPlayRounds[t1])>0 and noPlayRounds[t1]==noPlayRounds[t2]:\n", + " prev_round=t_prev_mirror_round[t1,r]\n", + "\n", + " if prev_round>0:\n", + " model2+= x_round[(t1,t2,r)] == x_round[(t2,t1,prev_round)]\n", + " # print (\"x_round[(\",t1,\",\",t2,\",\",r,\")] == x_round[(\",t2,t1,prev_mirror_round[r],\")]\")\n", + " # print ( t1,t2,r , \" -> \",prev_mirror_round[r])\n", + "\n", + "\n", + " \n", + "\n", + "for (t1,t2,(r,d)) in x.keys():\n", + " if thisSeason.useFeatureKickOffTime:\n", + " for tm in times :\n", + " if blocked_arena[(t1,d,tm)] and runMode=='Improve':\n", + " x_time[(t1,t2,(r,d), tm)]=0\n", + " # print (\"forbidding tm \", t1,t2,r,d, tm)\n", + " cntr2+=1\n", + " # print (\"FORBIDDING\")\n", + " \n", + "\n", + "print (\" .... got rid of \" , len(x.keys())-cntr, \" + \" ,cntr2, \" vars\") \n", + "\n", + "\n", + "\n", + "\n", + "for (t1,t2,d,channel) in seedTV:\n", + " print (t1,t2,d , gameCntr[(t1,t2)], gameCntr[(t1,t2)]==1)\n", + " model2+= sum([x[(t1,t2,rd)] for rd in getRoundDaysByDay[d] ])==1\n", + "\n", + "# model2+= lpSum( x[rr] for rr in x.keys() )\n", + "# model2.solve(XPRESS(msg=1,maxSeconds = 25, keepFiles=True))\n", + "# return \"\"\n", + "\n", + "homeInRound= {(t1,r) : pulp.LpVariable('homeInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds}\n", + "awayInRound= {(t1,r) : pulp.LpVariable('awayInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds}\n", + "gameInBasicRound= {(t1,t2,r) : pulp.LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = defaultGameRepetions, cat = pulp.LpContinuous) for (t1,t2) in games for r in basicRounds}\n", + "homeInBasicRound= {(t1,r) : pulp.LpVariable('homeInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds}\n", + "awayInBasicRound= {(t1,r) : pulp.LpVariable('awayInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds}\n", + "break3InRound= {(t1,r) : pulp.LpVariable('break3InRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in rounds}\n", + "# breakInRound= {(t1,r) : pulp.LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds}\n", + "# breaksTotal = pulp.LpVariable('breaksTotal', lowBound = 0, cat = pulp.LpContinuous)\n", + "tooManyTop4InRow= {(t1,r) : pulp.LpVariable('tooManyTop4InRow_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds}\n", + "\n", + "\n", + "tooManyHomesInStadium= {(stadium,d) : pulp.LpVariable('tooManyHomesInStadium_'+str(stadium)+'_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for stadium in stadiums for d in days}\n", + "pairingVio= {(pair['id'],d) : pulp.LpVariable('pairingVio_'+str(pair['id'])+'_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for pair in pairings for d in days}\n", + "derbyMissing= {d : pulp.LpVariable('derbyMissing_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for d in days}\n", + "\n", + "breakVio = { (br['id'],t) : pulp.LpVariable('breakVio_'+ str(br['id'])+'_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for br in breaks for t in teams}\n", + "blockingVio = { bl['id'] : pulp.LpVariable('blockingVio_'+ str(bl['id']) , lowBound = 0, cat = pulp.LpContinuous) for bl in blockings}\n", + "# blockingVioTotal = pulp.LpVariable('blockingVioTotal', lowBound = 0, cat = pulp.LpContinuous)\n", + "\n", + "hawVio = { haw['id'] : pulp.LpVariable('hawVio'+ str(haw['id']) , lowBound = 0, cat = pulp.LpContinuous) for haw in hawishes}\n", + "HawVioTooLess = { el : pulp.LpVariable('havviotooless_'+ str(el) , lowBound = 0, cat = pulp.LpContinuous) for haw in hawishes for el in elemHaWishes[haw['id']] }\n", + "HawVioTooMuch = { el : pulp.LpVariable('havviotoomuch_'+ str(el) , lowBound = 0, cat = pulp.LpContinuous) for haw in hawishes for el in elemHaWishes[haw['id']] }\n", + "# HawVioTotal = pulp.LpVariable('HawVioTotal', lowBound = 0, cat = pulp.LpContinuous)\n", + "encVio = { enc['id'] : pulp.LpVariable('encVio'+ str(enc['id']) , lowBound = 0, cat = pulp.LpContinuous) for enc in encwishes}\n", + "encVioTooLess = { el : pulp.LpVariable('encViotooless'+ str(enc['id'])+\"_\"+str(el) , lowBound = 0, cat = pulp.LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]}\n", + "encVioTooMuch = { el : pulp.LpVariable('encViotoomuch'+ str(enc['id'])+\"_\"+str(el) , lowBound = 0, cat = pulp.LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]}\n", + "# encVioTotal = pulp.LpVariable('encVioTotal', lowBound = 0, cat = pulp.LpContinuous)\n", + "# broadVio = { d : pulp.LpVariable('broadVio'+ str(d) , lowBound = 0, cat = pulp.LpContinuous) for d in days}\n", + "broadVioTm = { (b.id, r): pulp.LpVariable('broadVioTm_'+ str(b.id) +\"_\"+ str(r) , lowBound = 0, cat = pulp.LpContinuous) for b in broadcastingwishes for r in rounds}\n", + "gamesTooClose2 = { (t,r) : pulp.LpVariable('gamesTooClose2_'+ str(t) +'_'+ str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds }\n", + "missingGamesVio = { (t1,t2) : pulp.LpVariable('missingGamesVio_'+ str(t1) +'_'+str(t2) , lowBound = 0, cat = pulp.LpContinuous) for (t1,t2) in games}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "7bc64f33", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rounds range(1, 35) 34\n", + "realteams [10, 3, 17, 14, 15, 13, 12, 6, 9, 5, 8, 18, 19, 20, 11, 7, 4, 16]\n", + "taking care of right numbers of games ... \n", + "0 17 3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [4, 13, 16] ratiopharm ulm\n", + "1 17 3 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 30, 34] ratiopharm ulm\n", + "0 17 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [1, 5, 9, 10, 12, 16] Basketball Löwen Braunschweig\n", + "1 17 9 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [18, 19, 20, 22, 23, 26, 29, 30, 33] Basketball Löwen Braunschweig\n", + "0 17 2 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [7, 14] MHP RIESEN Ludwigsburg\n", + "1 17 6 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [18, 21, 23, 25, 28, 29] MHP RIESEN Ludwigsburg\n", + "0 17 4 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [2, 9, 11, 12] BG Göttingen\n", + "1 17 5 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 21, 29, 31, 33] BG Göttingen\n", + "0 17 0 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [] SYNTAINICS MBC\n", + "1 17 4 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 21, 29, 31] SYNTAINICS MBC\n", + "0 17 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [1, 4, 9, 12, 13, 16] FRAPORT SKYLINERS\n", + "1 17 8 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 21, 23, 25, 27, 29, 31, 33] FRAPORT SKYLINERS\n", + "0 17 5 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [2, 4, 11, 13, 16] FC Bayern München\n", + "1 17 5 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [20, 23, 27, 32, 34] FC Bayern München\n", + "0 17 2 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [4, 17] Brose Bamberg\n", + "1 17 1 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [23] Brose Bamberg\n", + "0 17 3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [1, 5, 6] ALBA BERLIN\n", + "1 17 5 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [18, 19, 21, 31, 33] ALBA BERLIN\n", + "0 17 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [2, 3, 5, 10, 15, 17] Hamburg Towers\n", + "1 17 4 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 21, 24, 30] Hamburg Towers\n", + "0 17 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [5, 6, 11, 12, 16, 17] EWE Baskets Oldenburg\n", + "1 17 5 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 20, 22, 25, 28] EWE Baskets Oldenburg\n", + "0 17 6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [2, 4, 9, 13, 15, 17] HAKRO Merlins Crailsheim\n", + "1 17 3 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [23, 29, 31] HAKRO Merlins Crailsheim\n", + "0 17 7 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [2, 5, 6, 9, 10, 13, 15] medi bayreuth\n", + "1 17 2 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [24, 27] medi bayreuth\n", + "0 17 4 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [6, 9, 15, 17] NINERS Chemnitz\n", + "1 17 7 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [18, 19, 23, 24, 31, 33, 34] NINERS Chemnitz\n", + "0 17 0 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [] MLP Academics Heidelberg\n", + "1 17 2 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [21, 29] MLP Academics Heidelberg\n", + "0 17 2 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [11, 12] JobStairs Gießen 46ers\n", + "1 17 4 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [18, 21, 31, 33] JobStairs Gießen 46ers\n", + "0 17 4 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [1, 4, 7, 10] Telekom Baskets Bonn\n", + "1 17 4 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [19, 20, 22, 25] Telekom Baskets Bonn\n", + "0 17 5 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] [6, 14, 15, 16, 17] s.Oliver Würzburg\n", + "1 17 3 [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] [23, 25, 34] s.Oliver Würzburg\n", + "check 1\n", + "check 2\n", + "- mindestens ein Heimspiel in den letzten zwei Wochen vor Weihnachten\n", + "ALBA hat nur Option\n", + "ALBA sollte nicht am Freitag Auswärts spielen\n", + "ALBA sollte nicht am Freitag Heim spielen\n", + "ALBA sollte nicht am Samstag Auswärts spielen\n", + "ALBA sollte nicht am Samstag Heim spielen\n", + "BBL 21 unbedingt Heimspiel am 14. 02.\n", + "BBL10 hätten wir gerne ein Heimspiel, da an BBL9, BBL11 + BBL12 die Arena leider nicht zur Verfügung steht.\n", + "Bitte Heimspiele aufgrund möglicher Verlegung durch Kollision mit EL-Spielen\n", + "Christmas Day on Dec 11, 2022 at 15:00\n", + "Da wir die reguläre Saison 21/22 mit drei Auswärtsspielen beenden, bitte ich um ein Heimspiel bei BBL 34 (BBL 33 ist nicht verfügbar).\n", + "Easter Special on April 08,2023 at 20: 30\n", + "Gerne Heimspiele an den Spieltagen 11, 12 und 13, da wir dort die Halle stehen lassen können.\n", + "In February 2023 (National Team Window) we need at least one home game, otherwise team is absent for interactions with fans for too long.\n", + "In November 2022 (National Team Window) we need at least one home game, otherwise team is absent for interactions with fans for too long.\n", + "gerne ein Heimspiel \"zwischen den Jahren\"\n", + "check 3\n", + "{'id': 12, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': True, 'forEachTeam2': False, 'day_id': 100, 'day2_id': 198, 'multidate': False, 'timeframe': -7, 'forEachDay': True, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': -1, 'maxGames': 1, 'prio': 'B', 'reason': 'die Heimspiele gegen Bayern und Alba zeitlich gerne so weit wie möglich auseinander\\r\\nx', 'violation': 'Fri, Mar 10, 2023
HAKRO Merlins Crailsheim
1 too many

Fri, Mar 17, 2023
HAKRO Merlins Crailsheim
1 too many

Fri, Mar 24, 2023
HAKRO Merlins Crailsheim
1 too many

Fri, Mar 31, 2023
HAKRO Merlins Crailsheim
1 too many

Sat, Apr 08, 2023
HAKRO Merlins Crailsheim
1 too many

Fri, Apr 14, 2023
HAKRO Merlins Crailsheim
1 too many

', 'active': True, 'scenario_id': 2, 'affected_rounds': '1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34'}\n", + "{'id': 13, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 100, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Seed Game', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '1'}\n", + "{'id': 14, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 100, 'day2_id': 109, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'das Derby gegen Ludwigsburg als erstes Heimspiel', 'violation': ' 1 too few

', 'active': True, 'scenario_id': 2, 'affected_rounds': '1_2_3'}\n", + "{'id': 15, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 123, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Seed Game', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '7'}\n", + "{'id': 16, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 135, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Verlegung Spieltag 10 oder 11 auf den 23.12.2022! Prio 1 ist ein Weihnachtsspiel am 23.12.2022 :-). Bitte keine europäische Mannschaft zwecks Verschiebungen.', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '11'}\n", + "{'id': 17, 'useEncounterGroups': True, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 135, 'day2_id': 136, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 9, 'maxGames': -1, 'prio': 'B', 'reason': 'Derbyspieltag', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '11'}\n", + "{'id': 18, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 137, 'day2_id': 138, 'multidate': True, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 4, 'maxGames': -1, 'prio': 'B', 'reason': 'Bitte Heimspiele aufgrund möglicher Verlegung durch Kollision mit EL-Spielen:BBL München 12 (29./30.12.)BBL 18 (31.01./01.02)BBL 29 (18./19.04.)BBL 31 (25./26.04.) Diese\\r\\nHeimspiele wenn möglich gegen nicht europäisch spielende Mannschaften, so dass eine Verlegung problemlos möglich ist.', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '12_18_29_31'}\n", + "{'id': 19, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 154, 'day2_id': 157, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': True, 'weekdays': '--', 'time': '----', 'minGames': -1, 'maxGames': 0, 'prio': 'B', 'reason': 'Spieltag 18 nicht gegen München, Berlin und Giessen', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '18'}\n", + "{'id': 20, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 166, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Seed Game', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '22'}\n", + "{'id': 21, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 179, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Seed Game', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '27'}\n", + "{'id': 22, 'useEncounterGroups': False, 'useGroups': False, 'forEachTeam1': False, 'forEachTeam2': False, 'day_id': 184, 'day2_id': None, 'multidate': False, 'timeframe': 1, 'forEachDay': False, 'forOneDay': False, 'forOneDayNum': 1, 'symmetry': False, 'weekdays': '--', 'time': '----', 'minGames': 1, 'maxGames': -1, 'prio': 'B', 'reason': 'Seed Game', 'violation': '', 'active': True, 'scenario_id': 2, 'affected_rounds': '28'}\n", + "check 4\n" + ] + } + ], + "source": [ + "\n", + "home={}\n", + "away={}\n", + "home_time={}\n", + "away_time={}\n", + "away_in_cluster={}\n", + "away_in_cluster_day={}\n", + "\n", + "getRoundDaysByBasicRound={br : [rd for r in getRealRounds[br] for rd in getRoundDaysByRound[r]] for br in basicRounds}\n", + "getMaxGameOnRoundDaysByBasicRound={br : sum ( roundDaysMax[rd] for r in getRealRounds[br] for rd in getRoundDaysByRound[r]) for br in basicRounds}\n", + "\n", + "for t in realteams:\n", + " for d in days:\n", + " away[t,d] = lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('away'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous)\n", + " home[t,d] = lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('home'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous)\n", + " if thisSeason.useFeatureKickOffTime:\n", + " for t2 in opponents[t]:\n", + " # print (t,t2,getTeamById[t], getTeamById[t2], (t,t2) in games )\n", + " # if (t,t2) in games:\n", + " for rd in getRoundDaysByDay[d]:\n", + " # TODO: HOTFIX\n", + " if (x[(t2,t,rd)] != 0): \n", + " model2+= x[(t2,t,rd)] == lpSum([ x_time[(t2,t,rd,tm)] for tm in times])\n", + " for tm in times:\n", + " away_time[t,d,tm] = lpSum([x_time[(t2,t,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ])\n", + " home_time[t,d,tm] = lpSum([x_time[(t,t2,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ])\n", + "\n", + "for t in realteams:\n", + " for r in rounds:\n", + " # awayInRound[t,r] = lpSum([x[(t2,t,dr)] for t2 in teams for dr in getRoundDaysByRound[r]])\n", + " # homeInRound[t,r] = lpSum([x[(t,t2,dr)] for t2 in teams for dr in getRoundDaysByRound[r]])\n", + " \n", + " if thisSeason.gamesPerRound==\"one day\":\n", + " # model2 += homeInRound[(t,r)] == lpSum([home[(t,d)] for (r2,d) in getRoundDaysByRound[r]])\n", + " model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]])\n", + " model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]])\n", + " else:\n", + " model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,getRoundDaysByRound[r][0])] for t2 in opponents[t] ])\n", + " model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,getRoundDaysByRound[r][0])] for t2 in opponents[t] ])\n", + "\n", + " model2 += homeInRound[(t,r)] + awayInRound[(t,r)] <= 1\n", + " if r>=3:\n", + " model2 += break3InRound[(t,r)] +2>= homeInRound[(t,r-2)]+homeInRound[(t,r-1)]+homeInRound[(t,r)] \n", + " model2 += break3InRound[(t,r)] +2>= awayInRound[(t,r-2)]+awayInRound[(t,r-1)]+awayInRound[(t,r)] \n", + "\n", + " if r>1:\n", + " if thisSeason.gamesPerRound==\"one day\":\n", + " model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in (getRoundDaysByRound[r-1]+getRoundDaysByRound[r]) if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)]\n", + " else:\n", + " model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in [getRoundDaysByRound[r-1][0],getRoundDaysByRound[r][0]] if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)]\n", + "\n", + "\n", + "breakVioBalance = { t : pulp.LpVariable('breakVioBalance_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in realteams }\n", + "numBreaks = { t : lpSum([ breakVio[(bl['id'],t)] for bl in breaks ]) for t in realteams }\n", + "\n", + "\n", + "succBreaks = [ bl for bl in breaks if bl['round1']+1==bl['round2'] ]\n", + "\n", + "if thisSeason.forbidDoubleBreaks:\n", + " for bl1 in succBreaks:\n", + " for bl2 in succBreaks:\n", + " if bl1['round2']+1==bl2['round1']:\n", + " for t in realteams:\n", + " model2+= homeInRound[t,bl1['round1']] + homeInRound[t,bl1['round2']] + awayInRound[t,bl2['round1']] + awayInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] \n", + " model2+= awayInRound[t,bl1['round1']] + awayInRound[t,bl1['round2']] + homeInRound[t,bl2['round1']] + homeInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] \n", + "\n", + "for t in realteams:\n", + " for bl in breaks:\n", + " # print (getTeamById[t], realteams, getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] )\n", + " # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t,t2,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ])\n", + " # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t2,t,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ])\n", + " model2+= breakVio[(bl['id'],t)] + 1 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']]\n", + " model2+= breakVio[(bl['id'],t)] + 1 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']]\n", + " if bl['round2'] + 1 <= nRounds:\n", + " model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']] + homeInRound[t,bl['round2']+1] \n", + " model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']] + awayInRound[t,bl['round2']+1] \n", + "\n", + " if balanceBreaks:\n", + " model2+= numBreaks[t] <= lpSum([ numBreaks[t2] for t2 in realteams])/len(realteams) + breakVioBalance[t]\n", + " for t2 in teams: \n", + " if half_symmetry_offset==0:\n", + " model2+= numBreaks[t2] <= 4\n", + " model2+= numBreaks[t2] >= 3\n", + " else:\n", + " model2+= numBreaks[t2] <= 5\n", + " model2+= numBreaks[t2] >= 1\n", + " print (\"balancing Breaks \")\n", + "\n", + " for bl in breaks:\n", + " model2+= breakVio[(bl['id'],t)] <= 2 - homeInRound[t,bl['round1']] - awayInRound[t,bl['round2']] \n", + " model2+= breakVio[(bl['id'],t)] <= 2 - awayInRound[t,bl['round1']] - homeInRound[t,bl['round2']]\n", + "\n", + " \n", + "\n", + " if not evalRun:\n", + " if not thisSeason.minBreaks and len(breaks)>0 and not balanceBreaks:\n", + " for p in phases:\n", + " # print (\"Don't allow breaks for \",t, \" in phase \", p)\n", + " model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks if getPhaseOfRound[bl['round1']]==p and getPhaseOfRound[bl['round2']]==p]) <= thisSeason.initBreaks\n", + "\n", + " if mathModelName in [ \"Ligue 1\"]:\n", + " model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks ]) <= 4\n", + "\n", + " if not thisSeason.startWithBreakAllowed:\n", + " model2+= homeInRound[t,1] + homeInRound[t,2] <= 1\n", + " model2+= awayInRound[t,1] + awayInRound[t,2] <= 1\n", + " if not thisSeason.endWithBreakAllowed:\n", + " print (\"NO BREAK FOR \" , t , \" in rounds \" ,nRounds-1, nRounds)\n", + " model2+= homeInRound[t,nRounds-1] + homeInRound[t,nRounds] <= 1\n", + " model2+= awayInRound[t,nRounds-1] + awayInRound[t,nRounds] <= 1\n", + "\n", + "\n", + "\n", + "getDays[0] =[]\n", + "\n", + "\n", + "if thisSeason.useFeaturePairings:\n", + " for pair in pairings:\n", + " # print (pair)\n", + " pDaysSets = []\n", + " for d in days:\n", + " pTeams = [pair['team1_id'], pair['team2_id']]\n", + " pDays = [d]\n", + " if pair['dist'] ==1 and nextDay[d]!=-1:\n", + " pDays.append(nextDay[d])\n", + " if pair['dist'] in [3,7]:\n", + " pDays=getDays[getRoundByDay[d]]\n", + " # print ( getNiceDay[d], getRoundByDay[d], getDays[getRoundByDay[d]] , \" +\" ,getHigherDaysByRound[getRoundByDay[d]])\n", + " factor = 1.0\n", + " if len(pDays)>0 and pDays not in pDaysSets:\n", + " pDaysSets.append(pDays.copy())\n", + " # print (\"+++ \")\n", + " # else: \n", + " # print (\"--- \")\n", + "\n", + " # if pair['prio'] =='A':\n", + " # factor = 0.1 \n", + " for pDays in pDaysSets:\n", + " d= pDays[0]\n", + " print (\"Treating day set starting with day \" , getNiceDay[d], \" \", pDays)\n", + " if pair['dist'] in [2,6] and thisSeason.useFeatureKickOffTime:\n", + " for tm in times:\n", + " if pair['type']== \"Home\":\n", + " if pair['dist']<=3:\n", + " model2 += lpSum([home_time[t,d,tm] for t in pTeams]) <= 1 + factor*pairingVio[(pair['id'],d)]\n", + " else:\n", + " model2 += home_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)]\n", + " model2 += home_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)]\n", + " else:\n", + " if pair['dist']<=3:\n", + " model2 += lpSum([home_time[t,d,tm] + away_time[t,d,tm] for t in pTeams]) <= 1 + pairingVio[(pair['id'],d)]\n", + " else:\n", + " model2 += home_time[pair['team1_id'],d,tm] + away_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] - away_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)]\n", + " model2 += home_time[pair['team2_id'],d,tm] + away_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] - away_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)]\n", + "\n", + " else:\n", + " if pair['type']== \"Home\":\n", + " if pair['dist']<=3:\n", + " model2 += lpSum([home[t,dd] for t in pTeams for dd in pDays]) <= 1 + factor*pairingVio[(pair['id'],d)]\n", + " else:\n", + " model2 += lpSum([home[pair['team1_id'],dd] - home[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)]\n", + " model2 += lpSum([home[pair['team2_id'],dd] - home[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)]\n", + " else:\n", + " if pair['dist']<=3:\n", + " model2 += lpSum([home[t,dd] + away[t,dd] for t in pTeams for dd in pDays]) <= 1 + pairingVio[(pair['id'],d)]\n", + " else:\n", + " model2 += lpSum([home[pair['team1_id'],dd] + away[pair['team1_id'],dd] - home[pair['team2_id'],dd] - away[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)]\n", + " model2 += lpSum([home[pair['team2_id'],dd] + away[pair['team2_id'],dd] - home[pair['team1_id'],dd] - away[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)]\n", + "\n", + "\n", + "\n", + "use_currentSolution= False\n", + "\n", + "currentGameCntr = { (t1,t2) :0 for t1 in teams for t2 in teams }\n", + "\n", + "\n", + "# if use_currentSolution and len(currentSolution) !=len(fixedGames): \n", + "\n", + "teamGameCntr = { (t,r) :0 for t in teams for r in rounds }\n", + "\n", + "\n", + "\n", + "# print (\"days \" , days)\n", + "for d in days: \n", + " model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games for rd in getRoundDaysByDay[d] if distance[getTeamById[t1],getTeamById[t2]]<=thisSeason.maxDistanceDerby ]) >= nDerbies[d] - derbyMissing[d]\n", + " minG = sum([ roundDaysMin[rd] - deficientGames[rd] for rd in getRoundDaysByDay[d]]) \n", + " maxG = sum([ roundDaysMax[rd] + excessGames[rd] for rd in getRoundDaysByDay[d]]) \n", + " # print (getNiceDay[d] , maxG)\n", + " # if minG<3 :\n", + " if minG>0 :\n", + " model2 += lpSum([ home[(t,d)] for t in realteams]) >= minG\n", + " model2 += lpSum([ home[(t,d)] for t in realteams]) <= maxG\n", + "\n", + " if len(getRoundDaysByDay[d])>1:\n", + " for rd in getRoundDaysByDay[d]:\n", + " if roundDaysMin[rd]>0:\n", + " model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] - deficientGames[rd] \n", + " # model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] \n", + " print (\"At least \",roundDaysMin[rd] , \" on \" , rd)\n", + " model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) <= roundDaysMax[rd] + excessGames[rd] \n", + "\n", + "print (\"rounds \" , rounds, nRounds)\n", + "for r in rounds:\n", + " # one game a round for everyone\n", + " for t1 in realteams:\n", + " if thisSeason.gamesPerRound==\"one day\":\n", + " cnstr = lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1] for rd in getRoundDaysByRound[r]] )\n", + " if len(cnstr) > 0:\n", + " model2 += cnstr <= 1\n", + " else:\n", + " print ( getRoundDaysByRound[r], opponents[t1])\n", + " if len(getRoundDaysByRound[r])>0:\n", + " for rd in getRoundDaysByRound[r]:\n", + " if rd==getRoundDaysByRound[r][0]:\n", + " model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1]] ) <= 1\n", + " else:\n", + " for t2 in opponents[t1]:\n", + " model2 += x[(t1,t2,rd)] == x[(t1,t2,getRoundDaysByRound[r][0])]\n", + "\n", + "# exit(0)\n", + "print (\"realteams \" , realteams)\n", + "\n", + "print (\"taking care of right numbers of games ... \" ) \n", + "cntr= 0\n", + "\n", + "if not specialGameControl:\n", + " for (t1,t2) in realgames: \n", + " if undirectedGameCntr[(t1,t2)]==0:\n", + " model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)] - missingGamesVio[(t1,t2)]\n", + " else:\n", + " # model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - missingGamesVio[(t1,t2)]\n", + " model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] for rd in roundDays] ) \n", + " model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) \n", + " model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) <= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] \n", + " \n", + " missingGamesVio[(t1,t2)].upBound= max(0,gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - currentGameCntr[(t1,t2)])\n", + "\n", + "\n", + "\n", + "if not evalRun:\n", + " for (t1,t2) in realgames:\n", + " # every pair plays each other in each phase once\n", + " for p in phases:\n", + " if p0 and noPlayRounds[t1]==noPlayRounds[t2]:\n", + " relDays = []\n", + " phaseLength= int(len(playRounds[t1])/nPhases+0.5)\n", + " for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]:\n", + " relDays+=getDays[rr]\n", + " print (\"adding days of round \", rr , \" to phase \", p)\n", + " else:\n", + " relDays = getDaysOfPhase[p]\n", + " model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ) <= 1\n", + " # print (len(relDays),\"reldays\") \n", + " # print (getTeamById[t1],getTeamById[t2], sum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ))\n", + "\n", + "if not evalRun :\n", + " for t in realteams:\n", + " if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases:\n", + " for p in phases:\n", + " rds = [r for r in rounds if getPhaseOfRound[r]==p ]\n", + " nblocked =[ r for r in noHomeRounds[t] if r in rds]\n", + " print (p, len(rds), len(nblocked), rds,nblocked, getTeamById[t])\n", + " for p in phases:\n", + " phaseLength= int(len(playRounds[t])/nPhases+0.5)\n", + " model2 += lpSum([ home[(t,d)] for d in getDaysOfPhase[p] ] ) <= int(phaseLength/2+1)\n", + "\n", + " if thisSeason.minRoundsBetweenGameOfSameTeams>0:\n", + " for r in rounds:\n", + " if r +thisSeason.minRoundsBetweenGameOfSameTeams <=nRounds:\n", + " rds = [ ] \n", + " for r2 in range(r,r+thisSeason.minRoundsBetweenGameOfSameTeams+1):\n", + " rds+=getRoundDaysByRound[r2]\n", + " rds2 = [ ] \n", + " for r2 in range(r,r+int (0.5*thisSeason.minRoundsBetweenGameOfSameTeams)):\n", + " rds2+=getRoundDaysByRound[r2]\n", + " # print (\"ONLY ONE IN \" , rds)\n", + " # print (\"ONLY ONE IN \" , rds2)\n", + " for t1 in realteams:\n", + " for t2 in realteams:\n", + " if t10 or x_round[(t1,t2,r)]!=0) :\n", + " # model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r]\n", + " model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 \n", + "\n", + " \n", + "if not evalRun:\n", + " # max length home stands /trips\n", + " for r in range (1,nRounds-thisSeason.maxTourLength+1):\n", + " # print (\" at least one home and away in rounds \")\n", + " # for r3 in range(r,r+thisSeason.maxTourLength+1):\n", + " # print (r3 )\n", + " for t in teams:\n", + " if t not in noBreakLimitTeams:\n", + " # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1\n", + " model2 += lpSum([awayInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength\n", + " model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength\n", + "\n", + "print (\"check 1\")\n", + "\n", + " # blockings\n", + "for bl in blockings:\n", + " if getDayById[bl['day_id']]['round'] !=0:\n", + " if thisSeason.useFeatureKickOffTime and bl['time']!='----':\n", + " if bl['type'] in [\"Home\", \"Hide\"]:\n", + " model2+= blockingVio[bl['id']]== home_time[bl['team_id'], bl['day_id'],bl['time']]\n", + " # print ('FOUND HOME BLOCKING ', bl)\n", + " else:\n", + " model2+= blockingVio[bl['id']]== away_time[bl['team_id'], bl['day_id'],bl['time']]\n", + " # print ('FOUND AWAY BLOCKING ', bl)\n", + " else:\n", + " if bl['type'] in [\"Home\", \"Hide\"]:\n", + " model2+= blockingVio[bl['id']]== home[bl['team_id'], bl['day_id']]\n", + " # print ('FOUND HOME BLOCKING ', bl)\n", + " else:\n", + " model2+= blockingVio[bl['id']]== away[bl['team_id'], bl['day_id']]\n", + " # print ('FOUND AWAY BLOCKING ', bl)\n", + "\n", + "\n", + "print (\"check 2\")\n", + "\n", + "hawOneVio={}\n", + "hawForOneNotViolated={}\n", + "\n", + "def getStringFromSet(ss):\n", + " s2 =\"\"\n", + " for s in ss: \n", + " s2+=str(s)+\"_\"\n", + " return s2[:-1]\n", + "\n", + " # hawishes\n", + "for haw in hawishes:\n", + " print (haw['reason'])\n", + " for el in elemHaWishes[haw['id']]:\n", + " if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']])>0:\n", + " if haw['homeAway']=='Home':\n", + " relGames = lpSum([home_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]])\n", + " # print (haw['id'] ,\" haw : \", relGames, hawTimes[haw['id']])\n", + " elif haw['homeAway']=='Away':\n", + " relGames = lpSum([away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]])\n", + " else :\n", + " # print(haw,el)\n", + " relGames = lpSum([home_time[t,d,tm] + away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \\\n", + " - lpSum([x_time[(t1,t2,rd,tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd,tm) in x_time.keys()]) \n", + "\n", + " else:\n", + " if haw['homeAway']=='Home':\n", + " relGames = lpSum([home[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ])\n", + " elif haw['homeAway']=='Away':\n", + " relGames = lpSum([away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ])\n", + " else :\n", + " relGames = lpSum([home[t,d] + away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1,t2,rd] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd) in x.keys() ]) \n", + " if haw['minGames'] >0 :\n", + " model2+= relGames >= haw['minGames'] - HawVioTooLess[el]\n", + " # print (\"adding min ha constraint\")\n", + " if haw['maxGames'] >=0 :\n", + " model2+= relGames <= haw['maxGames'] + HawVioTooMuch[el]\n", + " # print (\"adding max ha constraint\")\n", + "\n", + "\n", + " usedConstraintNames = [\"\"]\n", + " if haw['forOneDay']:\n", + " # print (haw['forOneDay'], hawDays[haw['id']])\n", + " # print ([el for el in elemHaWishes[haw['id']] ])\n", + " # for el in elemHaWishes[haw['id']] :\n", + " # print(\"- \" , elemHaWishDays[el] , elemHaWishTeams[el] )\n", + " # print ([ (el, elemHaWishFirstDay[el]) for el in elemHaWishes[haw['id']] ])\n", + " relTeamString = { el : getStringFromSet(elemHaWishTeams[el]) for el in elemHaWishes[haw['id']] }\n", + " relTeams = set([ relTeamString[el] for el in elemHaWishes[haw['id']]])\n", + " # print (relTeams)\n", + " for rt in relTeams:\n", + " rtname = rt \n", + " if len(rt)>50 :\n", + " scname=\"\"\n", + " while scname in usedConstraintNames:\n", + " ttt = bytes(rt+ ''.join(random.choice(string.ascii_lowercase) for i in range(10)), 'utf-8')\n", + " scname = hashlib.sha224(ttt).hexdigest()\n", + " rtname = scname[:10]\n", + " usedConstraintNames.append(rtname)\n", + " # print (\"use rtname to encode wishes \", rtname)\n", + "\n", + " hawOneVio[(haw['id'], rt)]= pulp.LpVariable('hawOneVio_'+str(haw['id'])+\"_\"+rtname, cat=pulp.LpContinuous)\n", + " for fd in hawDays[haw['id']]:\n", + " relWishes = [el for el in elemHaWishes[haw['id']] if elemHaWishFirstDay[el]== fd]\n", + " # relWishes = [el for el in elemHaWishes[haw['id']] ]\n", + " # print (\" -\" , relWishes , [elemHaWishDays[el] for el in relWishes])\n", + " hawForOneNotViolated[(haw['id'], rt, fd)]= 0\n", + " if len(relWishes)>0:\n", + " hawForOneNotViolated[(haw['id'], rt, fd)]= pulp.LpVariable('hawForOneViolated_'+str(haw['id'])+\"_\"+rtname+\"_\"+str(fd), cat=pulp.LpBinary)\n", + " model2 += lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el]==rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'],rt, fd)])\n", + " model2 += lpSum( hawForOneNotViolated[(haw['id'], rt, fd)] for fd in hawDays[haw['id']] ) >= haw['forOneDayNum']-hawOneVio[(haw['id'],rt)]\n", + " model2 += lpSum( hawOneVio[(haw['id'], rt)] for rt in relTeams ) == hawVio[haw['id']]\n", + " else:\n", + " # print (haw['forOneDay'], hawDays[haw['id']])\n", + " model2 += hawVio[haw['id']]== lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in elemHaWishes[haw['id']]) \n", + "\n", + "\n", + " if haw['prio']==\"Hard\" and \"HardHAWishesNotBreakable\" in special_wishes_active:\n", + " model2+= hawVio[haw['id']] ==0\n", + " print (\"WISH HARD\" ,haw['reason'])\n", + "\n", + "\n", + "# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]])\n", + "# print (hawDays)\n", + "# print (elemHaWishFirstDay) \n", + "\n", + "encOneVio={}\n", + "encForOneNotViolated={}\n", + "print (\"check 3\")\n", + "# encwishes\n", + "for enc in encwishes:\n", + " print (enc)\n", + " for el in elemEncWishes[enc['id']]: \n", + " # print (enc)\n", + " # model2+= encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']]\n", + "\n", + " if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']])>0:\n", + " if enc['minGames'] >0 :\n", + " model2+= encVioTooLess[el] >= enc['minGames'] - sum([ x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ])\n", + " if enc['maxGames'] >=0 :\n", + " # if enc['maxGames']==0:\n", + " print (enc['reason'], ' ', elemEncWishDays[el], ' ',enc['time'], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] )\n", + " # print (sum([x_time[(t1,t2,rd, enc['time'])] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ]))\n", + " model2+= encVioTooMuch[el] >= -enc['maxGames'] + sum([x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ])\n", + " else:\n", + " if enc['minGames'] >0 :\n", + " model2+= encVioTooLess[el] >= enc['minGames'] - sum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])\n", + " if enc['maxGames'] >=0 :\n", + " # if enc['maxGames']==0:\n", + " # print (enc['reason'], ' ', encDays[enc['id']], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] )\n", + " model2+= encVioTooMuch[el] >= -enc['maxGames'] + sum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])\n", + "\n", + " if enc['forOneDay']:\n", + " for ed in encDaySets[enc['id']]:\n", + " if len(ed)>0:\n", + " relWishes = [ el for el in elemEncWishes[enc['id']] if elemEncWishDays[el] == ed ]\n", + " print (\"###\",ed, relWishes) \n", + " encForOneNotViolated[(enc['id'], ed[0])]= pulp.LpVariable('encForOneNotViolated_'+str(enc['id'])+\"_\"+str(ed[0]), cat=pulp.LpBinary)\n", + " model2 += sum( encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * (1-encForOneNotViolated[(enc['id'], ed[0])])\n", + " model2 += sum( encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len(ed)>0 ) >= enc['forOneDayNum']-encVio[enc['id']]\n", + " else:\n", + " model2 += encVio[enc['id']]== sum( encVioTooMuch[el]+encVioTooLess[el] for el in elemEncWishes[enc['id']]) \n", + "\n", + "\n", + " if enc['prio']==\"Hard\" and \"HardEncWishesNotBreakable\" in special_wishes_active:\n", + " model2+= encVio[enc['id']] ==0\n", + " print (\"WISH HARD\" ,enc['reason'])\n", + "\n", + "\n", + "print (\"check 4\")\n", + " \n", + "\n", + "# return \"\"\n", + "weekdayHomePref={}\n", + "dayHomePref={}\n", + "for t in teams:\n", + " tm = getTeamByName[getTeamById[t]]\n", + " weekdayHomePref[(t,'Mon')]=tm['home_pref_mo']\n", + " weekdayHomePref[(t,'Tue')]=tm['home_pref_tu']\n", + " weekdayHomePref[(t,'Wed')]=tm['home_pref_we']\n", + " weekdayHomePref[(t,'Thu')]=tm['home_pref_th']\n", + " weekdayHomePref[(t,'Fri')]=tm['home_pref_fr']\n", + " weekdayHomePref[(t,'Sat')]=tm['home_pref_sa']\n", + " weekdayHomePref[(t,'Sun')]=tm['home_pref_su']\n", + " for d in days :\n", + " dayHomePref[(t,d)]=weekdayHomePref[(t,getWeekDay[d])]\n", + "\n", + "\n", + "maxTravelDistance = max([ distanceById[t1,t2] for t1 in realteams for t2 in realteams ]) \n", + "maxTravelDistance = max(1,maxTravelDistance)\n", + "\n", + "singleTripWeight=50\n", + "breakImbalanceTotal= lpSum([ breakVioBalance[t] for t in realteams])\n", + "# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]])\n", + "HawVioTotal=lpSum([prioVal[haw['prio']] * hawVio[haw['id']] for haw in hawishes])\n", + "encVioTotal=lpSum([prioVal[enc['prio']] * encVio[enc['id']] for enc in encwishes])\n", + "seedVioTotal=lpSum([100*prioVal[enc['prio']] * encVio[enc['id']] for enc in encwishes if enc['reason']==\"Seed Game\" ])\n", + "# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes])\n", + "broadVioTotal=lpSum([ 10 * broadVioTm[(b.id,r)] for b in broadcastingwishes for r in rounds])\n", + "breakVioTotal=lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in realteams]) + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in importantteams])\n", + "break3VioTotal=lpSum([2*break3InRound[t,r] for t in teams for r in rounds])\n", + "tooManyTop4InRowTotal=lpSum([ 10*tooManyTop4InRow[(t,r)] for t in teams for r in rounds])\n", + "pairingVioTotal=lpSum([ 5 *prioVal[pair['prio']] * pairingVio[(pair['id'],d)] for pair in pairings for d in days])\n", + "# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']==\"Home\"])\n", + "# print (blockings) \n", + "# for bl in blockings: \n", + " # if bl['type'] in [\"Home\"]:\n", + " # print (blocked_arena[(bl[\"team_id\"],bl[\"day_id\"],\"----\")], getTeamById[bl[\"team_id\"]], getNiceDay[bl[\"day_id\"]] , bl )\n", + "\n", + "fulfBlocks =set([(bl[\"team_id\"], getRoundByDay[bl[\"day_id\"]]) for bl in blockings if bl['type'] in [\"Home\"] and blocked_arena[(bl[\"team_id\"],bl[\"day_id\"],\"----\")]] )\n", + "\n", + "blockingVioTotal2=lpSum([ -30 * homeInRound[tr] for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode=='Improve'] ) \n", + "blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type'] in [\"Home\", \"Hide\"]]) +blockingVioTotal2\n", + "\n", + "travelVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']==\"Away\"])\n", + "derbiesMissingTotal=lpSum([ 30 * derbyMissing[d] for d in days])\n", + "unpreferredTotal=lpSum([ home[t, d] for t in teams for d in days if dayHomePref[(t,d)]==0 ])\n", + "# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions])\n", + "missingGamesVioTotal=lpSum([ 2000000 * missingGamesVio[(t1,t2)] for (t1,t2) in realgames])\n", + "# TODO - UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games])\n", + "oldScenGamesTotal=lpSum([ wg * x[t1,t2,rd] for (t1,t2,r,wg) in otherScenGames for rd in getRoundDaysByRound[r]] )\n", + "totalAttendance=lpSum([ attendance[(t1,t2,d)] * x[t1,t2,(r,d)] for (t1,t2) in games for (r,d) in roundDays ] )\n", + "\n", + "specialObjectives = 0\n", + "specialWishItems ={ sw:[] for sw in special_wishes_active}\n", + "specialWishVio ={}\n", + "\n", + "\n", + "optCameraMovement = \"Standard\"\n", + "\n", + "# tvkitproblem = {}\n", + "move2 = {}\n", + "newtrip2 = {}\n", + "\n", + "\n", + "standardObjectives=1+gew['Home-/Away']*HawVioTotal\\\n", + " +gew['Home-/Away']*3*unpreferredTotal \\\n", + " +gew['Pairings']*pairingVioTotal \\\n", + " +gew['Blockings']*blockingVioTotal \\\n", + " +gew['Traveling']*travelVioTotal \\\n", + " +gew['Breaks']*breakVioTotal \\\n", + " +gew['Breaks']*2*break3VioTotal \\\n", + " +gew['Breaks']*1*breakImbalanceTotal \\\n", + " +5*gew['Encounters']*encVioTotal \\\n", + " +5*gew['Encounters']*seedVioTotal \\\n", + " +gew['Broadcasting']*broadVioTotal \\\n", + " +gew['Derbies']*derbiesMissingTotal \\\n", + " +1.0*tooManyTop4InRowTotal\\\n", + " +missingGamesVioTotal\\\n", + " +oldScenGamesTotal\\\n", + " -0.01*totalAttendance \n", + "\n", + "\n", + "model2+= standardObjectives " + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "952f1e44", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "script\n", + "HOMEAWAY;1-17\n", + "BASICGAME;1-17\n", + "HOMEAWAY;18-34\n", + "GAME;1-34\n", + "[['HOMEAWAY', '1-17'], ['BASICGAME', '1-17'], ['HOMEAWAY', '18-34'], ['GAME', '1-34']]\n" + ] + } + ], + "source": [ + "script =thisSeason.optimizationScript\n", + "if script == '' :\n", + " if useBasicGames:\n", + " script += \"HEURISTIC\\n\"\n", + " script += \"GAME;1-\"+str(nRounds)+\";0.1;200\\n\"\n", + " if thisSeason.groupBased:\n", + " for c in allConferences:\n", + " if not c.regional and c.teams.count() <= 12: \n", + " script += \"GROUP;1-\"+str(nRounds)+\";0.05;30;\"+c.name+\"\\n\"\n", + " for cr in range(nPhases):\n", + " minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds)\n", + " maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds)\n", + "\n", + "\n", + "script=script.replace('\\r', '')\n", + "print (\"script\")\n", + "print (script)\n", + "optSteps = [ st.split(';') for st in script.split(\"\\n\")]\n", + "print (optSteps)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c4dc8d33", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model built now solving .... \n", + "True True False\n", + " - ['HOMEAWAY', '1-17']\n", + "\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]\n", + "########################\n", + "# SOLVING MODEL HOMEAWAY FOR ROUNDS 1-17 USING GAP 0.0 and MAXTIME 300 #\n", + "########################\n", + "[10, 3, 17, 14, 15, 13, 12, 6, 9, 5, 8, 18, 19, 20, 11, 7, 4, 16]\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]\n", + "fixing home 5 : ratiopharm ulm 1.0\n", + "fixing home 6 : ratiopharm ulm 1.0\n", + "fixing home 8 : ratiopharm ulm 1.0\n", + "fixing home 9 : ratiopharm ulm 1.0\n", + "fixing home 11 : ratiopharm ulm 1.0\n", + "fixing home 12 : ratiopharm ulm 1.0\n", + "fixing home 15 : ratiopharm ulm 1.0\n", + "fixing home 17 : ratiopharm ulm 1.0\n", + "fixing home 2 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 3 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 4 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 6 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 8 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 11 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 13 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 15 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 17 : Basketball Löwen Braunschweig 1.0\n", + "fixing home 2 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 4 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 6 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 10 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 13 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 17 : MHP RIESEN Ludwigsburg 1.0\n", + "fixing home 4 : BG Göttingen 1.0\n", + "fixing home 6 : BG Göttingen 1.0\n", + "fixing home 8 : BG Göttingen 1.0\n", + "fixing home 13 : BG Göttingen 1.0\n", + "fixing home 15 : BG Göttingen 1.0\n", + "fixing home 16 : BG Göttingen 1.0\n", + "fixing home 2 : SYNTAINICS MBC 1.0\n", + "fixing home 4 : SYNTAINICS MBC 1.0\n", + "fixing home 6 : SYNTAINICS MBC 1.0\n", + "fixing home 7 : SYNTAINICS MBC 1.0\n", + "fixing home 9 : SYNTAINICS MBC 1.0\n", + "fixing home 10 : SYNTAINICS MBC 1.0\n", + "fixing home 14 : SYNTAINICS MBC 1.0\n", + "fixing home 16 : SYNTAINICS MBC 1.0\n", + "fixing home 2 : FRAPORT SKYLINERS 1.0\n", + "fixing home 5 : FRAPORT SKYLINERS 1.0\n", + "fixing home 6 : FRAPORT SKYLINERS 1.0\n", + "fixing home 8 : FRAPORT SKYLINERS 1.0\n", + "fixing home 11 : FRAPORT SKYLINERS 1.0\n", + "fixing home 14 : FRAPORT SKYLINERS 1.0\n", + "fixing home 15 : FRAPORT SKYLINERS 1.0\n", + "fixing home 17 : FRAPORT SKYLINERS 1.0\n", + "fixing home 1 : FC Bayern München 1.0\n", + "fixing home 3 : FC Bayern München 1.0\n", + "fixing home 5 : FC Bayern München 1.0\n", + "fixing home 8 : FC Bayern München 1.0\n", + "fixing home 14 : FC Bayern München 1.0\n", + "fixing home 17 : FC Bayern München 1.0\n", + "fixing home 1 : Brose Bamberg 1.0\n", + "fixing home 5 : Brose Bamberg 1.0\n", + "fixing home 9 : Brose Bamberg 1.0\n", + "fixing home 12 : Brose Bamberg 1.0\n", + "fixing home 14 : Brose Bamberg 1.0\n", + "fixing home 16 : Brose Bamberg 1.0\n", + "fixing home 2 : ALBA BERLIN 1.0\n", + "fixing home 4 : ALBA BERLIN 1.0\n", + "fixing home 7 : ALBA BERLIN 1.0\n", + "fixing home 9 : ALBA BERLIN 1.0\n", + "fixing home 11 : ALBA BERLIN 1.0\n", + "fixing home 13 : ALBA BERLIN 1.0\n", + "fixing home 15 : ALBA BERLIN 1.0\n", + "fixing home 17 : ALBA BERLIN 1.0\n", + "fixing home 4 : Hamburg Towers 1.0\n", + "fixing home 7 : Hamburg Towers 1.0\n", + "fixing home 11 : Hamburg Towers 1.0\n", + "fixing home 12 : Hamburg Towers 1.0\n", + "fixing home 14 : Hamburg Towers 1.0\n", + "fixing home 16 : Hamburg Towers 1.0\n", + "fixing home 2 : EWE Baskets Oldenburg 1.0\n", + "fixing home 3 : EWE Baskets Oldenburg 1.0\n", + "fixing home 4 : EWE Baskets Oldenburg 1.0\n", + "fixing home 7 : EWE Baskets Oldenburg 1.0\n", + "fixing home 9 : EWE Baskets Oldenburg 1.0\n", + "fixing home 10 : EWE Baskets Oldenburg 1.0\n", + "fixing home 13 : EWE Baskets Oldenburg 1.0\n", + "fixing home 15 : EWE Baskets Oldenburg 1.0\n", + "fixing home 3 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 5 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 8 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 10 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 11 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 12 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 14 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 16 : HAKRO Merlins Crailsheim 1.0\n", + "fixing home 3 : medi bayreuth 1.0\n", + "fixing home 4 : medi bayreuth 1.0\n", + "fixing home 7 : medi bayreuth 1.0\n", + "fixing home 11 : medi bayreuth 1.0\n", + "fixing home 12 : medi bayreuth 1.0\n", + "fixing home 16 : medi bayreuth 1.0\n", + "fixing home 17 : medi bayreuth 1.0\n", + "fixing home 1 : NINERS Chemnitz 1.0\n", + "fixing home 2 : NINERS Chemnitz 1.0\n", + "fixing home 5 : NINERS Chemnitz 1.0\n", + "fixing home 7 : NINERS Chemnitz 1.0\n", + "fixing home 8 : NINERS Chemnitz 1.0\n", + "fixing home 14 : NINERS Chemnitz 1.0\n", + "fixing home 16 : NINERS Chemnitz 1.0\n", + "fixing home 1 : MLP Academics Heidelberg 1.0\n", + "fixing home 3 : MLP Academics Heidelberg 1.0\n", + "fixing home 7 : MLP Academics Heidelberg 1.0\n", + "fixing home 9 : MLP Academics Heidelberg 1.0\n", + "fixing home 11 : MLP Academics Heidelberg 1.0\n", + "fixing home 13 : MLP Academics Heidelberg 1.0\n", + "fixing home 15 : MLP Academics Heidelberg 1.0\n", + "fixing home 17 : MLP Academics Heidelberg 1.0\n", + "fixing home 1 : JobStairs Gießen 46ers 1.0\n", + "fixing home 3 : JobStairs Gießen 46ers 1.0\n", + "fixing home 5 : JobStairs Gießen 46ers 1.0\n", + "fixing home 8 : JobStairs Gießen 46ers 1.0\n", + "fixing home 10 : JobStairs Gießen 46ers 1.0\n", + "fixing home 15 : JobStairs Gießen 46ers 1.0\n", + "fixing home 17 : JobStairs Gießen 46ers 1.0\n", + "fixing home 3 : Telekom Baskets Bonn 1.0\n", + "fixing home 6 : Telekom Baskets Bonn 1.0\n", + "fixing home 9 : Telekom Baskets Bonn 1.0\n", + "fixing home 11 : Telekom Baskets Bonn 1.0\n", + "fixing home 12 : Telekom Baskets Bonn 1.0\n", + "fixing home 14 : Telekom Baskets Bonn 1.0\n", + "fixing home 16 : Telekom Baskets Bonn 1.0\n", + "fixing home 3 : s.Oliver Würzburg 1.0\n", + "fixing home 5 : s.Oliver Würzburg 1.0\n", + "fixing home 7 : s.Oliver Würzburg 1.0\n", + "fixing home 9 : s.Oliver Würzburg 1.0\n", + "fixing home 12 : s.Oliver Würzburg 1.0\n", + "fixing home 13 : s.Oliver Würzburg 1.0\n", + "fixing home 16 : s.Oliver Würzburg 1.0\n", + " - ['BASICGAME', '1-17']\n", + "\n", + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]\n", + "########################\n", + "# SOLVING MODEL BASICGAME FOR ROUNDS 1-17 USING GAP 0.0 and MAXTIME 300 #\n", + "########################\n" + ] + }, + { + "ename": "TypeError", + "evalue": "'>' not supported between instances of 'NoneType' and 'float'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 132\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgames\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 133\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgetTeamById\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m!=\u001b[0m\u001b[0;34m\"-\"\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mgetTeamById\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m!=\u001b[0m\u001b[0;34m\"-\"\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgameInBasicRound\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mtype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgameInBasicRound\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m!=\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 134\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mgetVal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgameInBasicRound\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m>\u001b[0m\u001b[0;36m0.9\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 135\u001b[0m \u001b[0mgameInBasicRound\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlowBound\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 136\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: '>' not supported between instances of 'NoneType' and 'float'" + ] + } + ], + "source": [ + "\n", + "print(\"Model built now solving .... \")\n", + "\n", + "nRuns =1\n", + "maxSolveTime = 300\n", + "\n", + "if thisSeason.groupBased:\n", + " maxSolveTime = 40\n", + "\n", + "mipgap=0.01\n", + "\n", + "# print (\"######## Testing\")\n", + "# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1))\n", + "\n", + "\n", + "use_LP_heuristic= False\n", + "print (runMode=='New' , useBasicGames , runPatternAssignmentFirst)\n", + "\n", + "nRuns=nPhases\n", + "# maxSolveTime = 120\n", + "mipgap=0.0\n", + "# mipgap=0.95\n", + "\n", + "# nRuns =1\n", + "onlyReopt= True\n", + "onlyReopt= False\n", + "onlyFewTrips= False\n", + "singleTripWeight =10 \n", + "\n", + "if onlyReopt:\n", + " nRuns=0\n", + "\n", + "maxIntRound=nRounds\n", + "\n", + "\n", + "missing_imp=[] \n", + "cntr =0\n", + "\n", + "for st in optSteps:\n", + " print (\" - \" ,st )\n", + " cntr +=1\n", + " if runMode=='New' and st[0] == \"HARDCONSTRAINTS\":\n", + " for bl in blockings:\n", + " blockingVio[bl['id']].upBound=0\n", + " print (\"blocking tightened : \" , getTeamById [bl['team_id']] , bl['day_id'] ) \n", + " for enc in encwishes:\n", + " if enc['reason']==\"Seed Game\":\n", + " encVio[enc['id']].upBound=0\n", + " \n", + " \n", + " if runMode=='New' and len(st)>=1 and st[0] in [\"PATTERNS\",\"HOMEAWAY\", \"BASICGAME\", \"GAME\",\"GAMES\", \"GROUP\", \"TRIPS\", \"LP-HEURISTIC\"]:\n", + " newRounds = []\n", + " print() \n", + " optTarget = st[0]\n", + " if len(st)>1 :\n", + " for rf in st[1].split(\",\"):\n", + " rr= rf.split(\"-\")\n", + " if len(rr)==1 :\n", + " newRounds.append(min(nRounds,int(rr[0])))\n", + " else:\n", + " for ii in range (int(rr[0]), min(nRounds, int(rr[1])+1)):\n", + " newRounds.append(ii) \n", + " newRoundsString = st[1]\n", + " else : \n", + " newRounds = rounds\n", + " newRoundsString = \"1-\"+str(nRounds) \n", + "\n", + " optsteptime = maxSolveTime\n", + " optstepgap = mipgap\n", + "\n", + " if len(st)>=3 and st[2]!=\"\":\n", + " optstepgap = float(st[2])\n", + " if len(st)>=4 and st[3]!=\"\":\n", + " optsteptime = float(st[3])\n", + " \n", + " print (newRounds)\n", + "\n", + " if st[0] == \"HOMEAWAY\":\n", + " for t in teams:\n", + " for r in newRounds:\n", + " homeInRound[(t,r)].cat = pulp.LpInteger\n", + "\n", + " if st[0] == \"BASICGAME\":\n", + " for (t1,t2) in games:\n", + " for r in newRounds:\n", + " gameInBasicRound[(t1,t2,r)].cat = pulp.LpInteger\n", + "\n", + " if st[0] in [\"GAME\",\"GAMES\"]:\n", + " for (t,t2) in games:\n", + " for r in newRounds:\n", + " for rd in getRoundDaysByRound[r]:\n", + " makeIntVar(x[(t,t2,rd)])\n", + "\n", + "\n", + "\n", + " print ('########################')\n", + " print ('# SOLVING MODEL '+optTarget+' FOR ROUNDS '+ newRoundsString+' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' #')\n", + " print ('########################')\n", + " \n", + " if solver == \"CBC\":\n", + " model2.solve(PULP_CBC_CMD(fracGap = optstepgap, maxSeconds = optsteptime, threads = 8,msg=1))\n", + " elif solver == \"Gurobi\":\n", + " model2.solve(GUROBI(MIPGap=optstepgap, TimeLimit=optsteptime,msg=1, Method=2,NodeMethod=2))\n", + " else:\n", + " # for debugging: \n", + " # with open (\"model2.txt\", \"w\") as f:\n", + " # f.write(model2.__repr__())\n", + " model2.solve(XPRESS(msg=1,targetGap=optstepgap, maxSeconds = optsteptime, options=[\"THREADS=12,DETERMINISTIC=0,CUTSTRATEGY=0\"], keepFiles=True))\n", + "\n", + " if model2.status<0:\n", + " print(\"Status: \" , model2.status)\n", + "\n", + " if not lowerBoundFound:\n", + " lowerBoundFound=value(model2.objective)\n", + "\n", + " cntr_rnd =0\n", + " \n", + "\n", + " if st[0] == \"HOMEAWAY\":\n", + " print (teams)\n", + " print (newRounds)\n", + " for t in teams:\n", + " for r in newRounds:\n", + " if homeInRound[(t,r)].value() >0.9 :\n", + " print ('fixing home '+ str(r) + ' : '+ getTeamById[t] +\" \" + str(homeInRound[(t,r)].value()))\n", + " homeInRound[(t,r)].lowBound = 1\n", + " else:\n", + " homeInRound[(t,r)].upBound = 0\n", + " \n", + "\n", + " if st[0] == \"BASICGAME\":\n", + " for r in newRounds:\n", + " for (t1,t2) in games:\n", + " if getTeamById[t1]!=\"-\" and getTeamById[t2]!=\"-\" and (t1,t2,r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1,t2,r)])!= int:\n", + " if getVal(gameInBasicRound[(t1,t2,r)])>0.9:\n", + " gameInBasicRound[(t1,t2,r)].lowBound = 1\n", + " else:\n", + " gameInBasicRound[(t1,t2,r)].upBound = 0\n", + "\n", + " if st[0] in [\"GAME\",\"GAMES\"]:\n", + " feedback = \"Optimize games....\"\n", + " missing_imp=[]\n", + " \n", + " for (t1,t2) in realgames:\n", + " if missingGamesVio[(t1,t2)].value() >0.9:\n", + " feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str(missingGamesVio[(t1,t2)].value()) + '
\\n'\n", + " missing_imp += [(1,nRounds,[t1,t2], 50)] \n", + "\n", + " print (missing_imp)\n", + " print (feedback)\n", + " print (\"number of assigned games : \" , sum([getVal(x[ttrd]) for ttrd in x.keys()]))\n", + "\n", + " for (t1,t2) in games:\n", + " for r in newRounds:\n", + " for rd in getRoundDaysByRound[r]:\n", + " if getVal(x[(t1,t2,rd)])>0.9:\n", + " setLB(x[(t1,t2,rd)],1) \n", + " print (\"fixing \" ,t1,t2,rd, x[(t1,t2,rd)].lowBound )\n", + " else:\n", + " setUB(x[(t1,t2,rd)],0)\n", + "\n", + " for r in basicRounds:\n", + " for t1 in realteams:\n", + " homeInBasicRound[(t1,r)].lowBound = 0\n", + " homeInBasicRound[(t1,r)].upBound = 10\n", + " awayInBasicRound[(t1,r)].lowBound = 0\n", + " awayInBasicRound[(t1,r)].upBound = 10\n", + " for t2 in opponents[t1]:\n", + " if (t1,t2) in games:\n", + " gameInBasicRound[(t1,t2,r)].cat = pulp.LpContinuous\n", + " gameInBasicRound[(t1,t2,r)].lowBound = 0\n", + "\n", + "\n", + " for t in realteams:\n", + " for r in newRounds:\n", + " homeInRound[(t,r)].cat = pulp.LpContinuous\n", + " for t2 in opponents[t]:\n", + " for rd in getRoundDaysByRound[r]:\n", + " if (t,t2) in games and getVal(x[(t,t2,rd)]) >0.9 :\n", + " setLB(x[(t,t2,rd)], x[(t,t2,rd)].value())\n", + " currentSolution =[ (t1,t2,r,d) for (t1,t2) in realgames for (r,d) in roundDays if getVal(x[(t1,t2,(r,d))]) >0.9 ]\n", + "\n", + "\n", + "\n", + "for r in rounds:\n", + " for t in teams:\n", + " homeInRound[(t,r)].lowBound = 0\n", + " homeInRound[(t,r)].upBound = 1\n", + "\n", + "for (t1,t2) in games:\n", + " for r in rounds:\n", + " for rd in getRoundDaysByRound[r]:\n", + " makeIntVar(x[(t1,t2,rd)])\n", + "\n", + "print ('Solved Again')\n", + "print ('NOW REOPT')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "496f8609", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.13 ('leagues')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + }, + "vscode": { + "interpreter": { + "hash": "a07b7f3079ca8c056705d3c757c4f3f92f9509f33eeab9ad5420dacec37bc01a" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/machine_learning/scripts/qubo/qubo.py b/machine_learning/scripts/qubo/qubo.py new file mode 100755 index 0000000..f381ddf --- /dev/null +++ b/machine_learning/scripts/qubo/qubo.py @@ -0,0 +1,312 @@ +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") + +from leagues import settings +settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3' + +import django +django.setup() + +from scheduler.models import * +from common.functions import distanceInKmByGPS +import pandas as pd +import numpy as np +from django.db.models import Count, F, Value + + +thisScenario = Scenario.objects.get(id = 2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + + +mathModelName=thisLeague.name +if thisSeason.optimizationParameters!="": + mathModelName=thisSeason.optimizationParameters + + + +otherScenGames=[] +if thisSeason.improvementObjective!="--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective=="stick to old solutions": + impObjWeight = -5 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append((int(g[1]),int(g[2]),int(g[3]), impObjWeight )) + +getGlobalCountry={ gc['uefa']: gc['id'] for gc in GlobalCountry.objects.values() } + +teamObjects = Team.objects.filter(season=thisSeason,active=True).order_by('position').values() +teams=[] +realteams=[] +faketeams=[] +importantteams=[] +shortteams=[] +t_pos={} +t_pot={} +t_color={} +t_country={} +t_globalCountry={} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity ={} +getTeamById={} +getTeamIdByName={} +getTeamIdByShortName={} +getTeamByName={} +getStadiumById={} +teamByShort={} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']]= t['name'] + getTeamIdByShortName[t['shortname']]=t['id'] + if t['name']!='-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']]= t['country'] + t_globalCountry[t['id']]= t['globalCountry_id'] + if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys() : + t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]] + t_pos[t['name']]= t['position'] + t_color[t['name']]="lightyellow" + t_pot[t['id']]= t['pot'] + t_stadium[t['id']]=t['stadium'] + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_shortname[t['id']]=t['shortname'] + t_usePhases[t['id']]= thisScenario.usePhases + getTeamById[t['id']]=t['name'] + getStadiumById[t['id']]=t['stadium'] + getTeamIdByName[t['name']]=t['id'] + getTeamByName[t['name']]=t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient : + top4.append(t['id']) + +inactive_teams=[] +for t in Team.objects.filter(season=thisSeason,active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_country[t['id']]= t['country'] + getTeamById[t['id']]=t['name'] + + +lowerBoundFound = False +distance ={} +attractivity ={} +distanceById={} +distanceInDaysById={} +stadium_names = set([t['stadium'] for t in teamObjects if t['stadium']!='']) +stadium_id={} +stadium_name={} +sid=0 +for stadium in stadium_names : + stadium_name[sid]=stadium + stadium_id[stadium]=sid + sid+=1 +stadiums = list(stadium_name.keys()) +teamsOfStadium ={ st:[] for st in stadiums } + +travelDict= thisSeason.travelDict() +for t1 in teamObjects: + if t1['stadium']!='': + teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'],t2['name']] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'],t2['id']] = distance[t1['name'],t2['name']] + distanceInDaysById[t1['id'],t2['id']] = int(distance[t1['name'],t2['name']]/350 +0.99) + attractivity[t1['id'],t2['id']] = int(100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [ d['id'] for d in dayObjects if d['round']>0 ] + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +higherTeamObjects = Team.objects.filter(season__in=higherSeasons,active=True).values() +higherDayObjects = Day.objects.filter(season__in=higherSeasons,maxGames__gte=1).values() +higherTeams = [ t['id'] for t in higherTeamObjects] +higherTeamsOf={ t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']]=t['name'] + t_country[t['id']]=t['country'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print ("Teams : " , teams) +print ("VIP Teams : " , importantteams) +print ("TOP Teams : " , top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums= ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t : 0 for t in teams } +conf_teams={} +for c in Conference.objects.filter(scenario=s2,regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name]=[] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id]==0 or len(cteams)< len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id]=c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +prioVal ={'A': 25 , 'B': 5 , 'C': 1, 'Hard' : 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name]=prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name]=sw.float1 + sw_float2[sw.name]=sw.float2 + sw_int1[sw.name]=sw.int1 + sw_int2[sw.name]=sw.int2 + sw_text[sw.name]=sw.text +special_wishes=list(sw_prio.keys()) +special_wishes_active = [ sw for sw in special_wishes if sw_prio[sw]>0 ] + +print (special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active : + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print (noBreakLimitTeams) + noBreakLimitTeams = [t for t in teams if getTeamById[t] in noBreakLimitTeams] + print (noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter(scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values() +pairings_tmp3 = Pairing.objects.filter(scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values() +pairings = [] +for p in [ p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams ] + [ p for p in pairings_tmp2 ] + [ p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) +breaks = Break.objects.filter(season=thisSeason).values() +blockings_tmp = Blocking.objects.filter(scenario=s2,active=True).values() +blockings = [ bl for bl in blockings_tmp if bl['team_id'] in teams ] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [ str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']):t['name'] for t in timeObjects} +getIdByTime = {t['name']:str(t['id']) for t in timeObjects} + +blocked_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +hidden_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +nBlockingHome=0 +nBlockingAway=0 + +for bl in blockings: + if bl['type'] in ["Hide"]: + hidden_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + + if bl['type'] in ["Home","Hide"]: + nBlockingHome+=1 + blocked_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + else: + nBlockingAway+=1 + +nTeams=len(teams) +nPhases =thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} +undirectedGameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} + +if thisSeason.useFeatureOpponentMatrix: + gameRequirements = GameRequirement.objects.filter(season=thisSeason, team1__active=True, team2__active=True) + # print (len(gameRequirements)) + for gm in gameRequirements: + if thisSeason.undirectedGames: + undirectedGameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + undirectedGameCntr[(gm.team2.id,gm.team1.id)]+=gm.number + # print ( "found undirected game " , (gm.team1.id,gm.team2.id) , gm.number ) + else: + gameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + # print ( "found directed game " , (gm.team1.id,gm.team2.id) , gm.number ) +else: + for t1 in teams: + for t2 in teams: + if t1!=t2: + gameCntr[(t1,t2)]+=nPhases/2 + + for c in Conference.objects.filter(scenario=s2): + cteams = c.teams.filter(active=True) + for t1 in cteams: + for t2 in cteams: + if t1!=t2: + gameCntr[(t1.id,t2.id)]+=c.deltaGames/2 + + for (t1,t2) in gameCntr.keys(): + if gameCntr[(t1,t2)]%1!=0 : + undirectedGameCntr[(t1,t2)]=1 + gameCntr[(t1,t2)] = int(gameCntr[(t1,t2)]) + +games = [ gm for gm in gameCntr.keys() if gameCntr[gm]+undirectedGameCntr[gm]>0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values() + +gew={} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']]= ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips']>gew['Breaks'] : + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl= mathModelName in ["Florida State League"] + +if len(games)==0: + games = [ (t1,t2) for t1 in teams for t2 in teams if t1!=t2 ] + specialGameControl=True + +realgames= [(t1,t2) for (t1,t2) in games if getTeamById[t1]!="-" and getTeamById[t2]!="-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = ( runMode == "Improve" and localsearch_time==0 + and len(thisScenario.solutionlist())==sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames]) ) + +opponents = { t: set([]) for t in realteams} + +for (t1,t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) \ No newline at end of file diff --git a/machine_learning/scripts/qubo/qubo_from_model1_notebook.py b/machine_learning/scripts/qubo/qubo_from_model1_notebook.py new file mode 100755 index 0000000..709736c --- /dev/null +++ b/machine_learning/scripts/qubo/qubo_from_model1_notebook.py @@ -0,0 +1,1672 @@ +# %% +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") + + +# XPRESS ENVIRONMENT +os.environ['XPRESSDIR'] = "/opt/xpressmp_8.4" +os.environ['XPRESS'] = "/opt/xpressmp_8.4/bin" +os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"+os.environ['LD_LIBRARY_PATH'] +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['PYTHONPATH'] +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.environ['CLASSPATH'] +os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprm.jar:"+os.environ['CLASSPATH'] +os.environ['PATH'] =os.environ['XPRESSDIR']+"/bin:"+os.environ['PATH'] + + +from leagues import settings +settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3' + +import django +django.setup() + +from scheduler.models import * +import operator +from scheduler.solver.functions import * +from common.functions import distanceInKmByGPS +import pandas as pd +import numpy as np +from django.db.models import Count, F, Value +from pulp import * + + +def makeIntVar(v): + if type(v) != int: + v.cat = LpInteger + # print ("int var ", v) + + +def setStart(v, vl): + if type(v) != int: + vl = float(vl) + v.start = vl + + +def setLB(v, vl): + if type(v) != int: + vl = float(vl) + v.lowBound = vl + # print ("set lb ", v , " = ", vl) + + +def setUB(v, vl): + if type(v) != int: + vl = float(vl) + v.upBound = vl + # print ("set ub ", v , " = ", vl) + + +def getVal(v): + if type(v) == int: + return v + else: + return v.value() + + +user_is_staff = True +runMode = 'New' +localsearch_time = 0 +solver = '?' + +s2 = 3 +thisScenario = Scenario.objects.get(id=s2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + + +mathModelName = thisLeague.name +if thisSeason.optimizationParameters != "": + mathModelName = thisSeason.optimizationParameters + +# %% + + +otherScenGames = [] +if thisSeason.improvementObjective != "--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective == "stick to old solutions": + impObjWeight = -5 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append( + (int(g[1]), int(g[2]), int(g[3]), impObjWeight)) + +getGlobalCountry = {gc['uefa']: gc['id'] + for gc in GlobalCountry.objects.values()} + +teamObjects = Team.objects.filter( + season=thisSeason, active=True).order_by('position').values() +teams = [] +realteams = [] +faketeams = [] +importantteams = [] +shortteams = [] +t_pos = {} +t_pot = {} +t_color = {} +t_country = {} +t_globalCountry = {} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity = {} +getTeamById = {} +getTeamIdByName = {} +getTeamIdByShortName = {} +getTeamByName = {} +getStadiumById = {} +teamByShort = {} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']] = t['name'] + getTeamIdByShortName[t['shortname']] = t['id'] + if t['name'] != '-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']] = t['country'] + t_globalCountry[t['id']] = t['globalCountry_id'] + if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys(): + t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]] + t_pos[t['name']] = t['position'] + t_color[t['name']] = "lightyellow" + t_pot[t['id']] = t['pot'] + t_stadium[t['id']] = t['stadium'] + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_shortname[t['id']] = t['shortname'] + t_usePhases[t['id']] = thisScenario.usePhases + getTeamById[t['id']] = t['name'] + getStadiumById[t['id']] = t['stadium'] + getTeamIdByName[t['name']] = t['id'] + getTeamByName[t['name']] = t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient: + top4.append(t['id']) + +inactive_teams = [] +for t in Team.objects.filter(season=thisSeason, active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_country[t['id']] = t['country'] + getTeamById[t['id']] = t['name'] + + +lowerBoundFound = False +distance = {} +attractivity = {} +distanceById = {} +distanceInDaysById = {} +stadium_names = set([t['stadium'] for t in teamObjects if t['stadium'] != '']) +stadium_id = {} +stadium_name = {} +sid = 0 +for stadium in stadium_names: + stadium_name[sid] = stadium + stadium_id[stadium] = sid + sid += 1 +stadiums = list(stadium_name.keys()) +teamsOfStadium = {st: [] for st in stadiums} + +travelDict = thisSeason.travelDict() +for t1 in teamObjects: + if t1['stadium'] != '': + teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'], t2['name'] + ] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'], t2['id']] = distance[t1['name'], t2['name']] + distanceInDaysById[t1['id'], t2['id']] = int( + distance[t1['name'], t2['name']]/350 + 0.99) + attractivity[t1['id'], t2['id']] = int( + 100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [d['id'] for d in dayObjects if d['round'] > 0] + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +higherTeamObjects = Team.objects.filter( + season__in=higherSeasons, active=True).values() +higherDayObjects = Day.objects.filter( + season__in=higherSeasons, maxGames__gte=1).values() +higherTeams = [t['id'] for t in higherTeamObjects] +higherTeamsOf = {t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']] = t['name'] + t_country[t['id']] = t['country'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print("Teams : ", teams) +print("VIP Teams : ", importantteams) +print("TOP Teams : ", top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums = ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t: 0 for t in teams} +conf_teams = {} +for c in Conference.objects.filter(scenario=s2, regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name] = [] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id] == 0 or len(cteams) < len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id] = c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +prioVal = {'A': 25, 'B': 5, 'C': 1, 'Hard': 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name] = prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name] = sw.float1 + sw_float2[sw.name] = sw.float2 + sw_int1[sw.name] = sw.int1 + sw_int2[sw.name] = sw.int2 + sw_text[sw.name] = sw.text +special_wishes = list(sw_prio.keys()) +special_wishes_active = [sw for sw in special_wishes if sw_prio[sw] > 0] + +print(special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active: + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print(noBreakLimitTeams) + noBreakLimitTeams = [ + t for t in teams if getTeamById[t] in noBreakLimitTeams] + print(noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter( + scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values() +pairings_tmp3 = Pairing.objects.filter( + scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values() +pairings = [] +for p in [p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams] + [p for p in pairings_tmp2] + [p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) +breaks = Break.objects.filter(season=thisSeason).values() +blockings_tmp = Blocking.objects.filter(scenario=s2, active=True).values() +blockings = [bl for bl in blockings_tmp if bl['team_id'] in teams] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']): t['name'] for t in timeObjects} +getIdByTime = {t['name']: str(t['id']) for t in timeObjects} + +blocked_arena = { + (t, d, tm): False for t in teams for d in days for tm in ["----"] + times} +hidden_arena = {(t, d, tm): False for t in teams for d in days for tm in [ + "----"] + times} +nBlockingHome = 0 +nBlockingAway = 0 + +for bl in blockings: + if bl['type'] in ["Hide"]: + hidden_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + + if bl['type'] in ["Home", "Hide"]: + nBlockingHome += 1 + blocked_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + else: + nBlockingAway += 1 + +nTeams = len(teams) +nPhases = thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} +undirectedGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} + +for t1 in teams: + for t2 in teams: + if t1 != t2: + gameCntr[(t1, t2)] += nPhases/2 + +for (t1, t2) in gameCntr.keys(): + if gameCntr[(t1, t2)] % 1 != 0: + undirectedGameCntr[(t1, t2)] = 1 + gameCntr[(t1, t2)] = int(gameCntr[(t1, t2)]) + +games = [gm for gm in gameCntr.keys() if gameCntr[gm] + + undirectedGameCntr[gm] > 0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter( + scenario=s2).values() + +gew = {} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']] = ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips'] > gew['Breaks']: + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl = mathModelName in ["Florida State League"] + +if len(games) == 0: + games = [(t1, t2) for t1 in teams for t2 in teams if t1 != t2] + specialGameControl = True + +realgames = [(t1, t2) for (t1, t2) in games if getTeamById[t1] + != "-" and getTeamById[t2] != "-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = (runMode == "Improve" and localsearch_time == 0 + and len(thisScenario.solutionlist()) == sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames])) + +opponents = {t: set([]) for t in realteams} + +for (t1, t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) + + +gmClusters = range(1, nTeams+2) + +if nPhases > 0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl: + gmClusters = range(1, 2) + +print("gmClusters", gmClusters) + + +gameClusterTeams = {c: [t for t in teams] for c in gmClusters} +gameClusters = [c for c in gameClusterTeams.keys() if len( + gameClusterTeams[c]) > 0] +biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters]) + +nPhases = min([gameCntr[(t1, t2)] + gameCntr[(t2, t1)] + + undirectedGameCntr[(t1, t2)] for (t1, t2) in realgames]) +tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize == "None" else int( + thisSeason.tripStartHeuristicGroupsize) +defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2 +nPhases = max(1, int(nPhases/defaultGameRepetions)) +phases = range(nPhases) + +useBasicGames = nPhases > 0 + +if not useBasicGames: + print('no basic games but biggest group size ', + biggestGroupSize, ' in nPhases ', nPhases) + +nRounds = thisSeason.nRounds +rounds = range(1, nRounds+1) + +# nRoundsPerPhase= 1 +# if nPhases>0: +nRoundsPerPhase = int(nRounds/nPhases) + +print('nRounds ', nRounds) +print('nPhases ', nPhases) +print('nRoundsPerPhase ', nRoundsPerPhase) +print('defaultGameRepetions ', defaultGameRepetions) +print('tripStartHeuristicGroupsize ', tripStartHeuristicGroupsize) + +getPhaseOfRound = {r: min(nPhases-1, int((r-1)/nRoundsPerPhase)) + for r in rounds} +getDaysOfPhase = {p: [] for p in phases} +getDays = {r: [] for r in rounds} +roundGamesMax = {r: 0 for r in rounds} +roundGamesMin = {r: 0 for r in rounds} +getDayById = {d['id']: d for d in dayObjects} +getDayByDateTime = {} +getNiceDayRaw = {d['id']: d['day'] for d in dayObjects} +getNiceDay = {d['id']: d['day'] for d in dayObjects} +getWeekDay = {d['id']: '' for d in dayObjects} +getDateTimeDay = {d['id']: '' for d in dayObjects} +getRoundByDay = {d['id']: d['round'] for d in dayObjects if d['round'] > 0} +getDayMinGames = {d['id']: d['minGames'] for d in dayObjects if d['round'] > 0} +getDayMaxGames = {d['id']: d['maxGames'] for d in dayObjects if d['round'] > 0} +getRoundsByDay = {d['id']: [] for d in dayObjects} +nDerbies = {d['id']: [d['nDerbies']] for d in dayObjects} +roundDays = [] +roundDaysMin = {} +roundDaysMax = {} +wds = {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun'} +for d in dayObjects: + if d['round'] > 0: + getRoundsByDay[d['id']].append(d['round']) + getDays[d['round']].append(d['id']) + roundDays.append((d['round'], d['id'])) + roundDaysMin[(d['round'], d['id'])] = d['minGames'] + roundDaysMax[(d['round'], d['id'])] = d['maxGames'] + roundGamesMax[d['round']] = min( + nTeams/2, roundGamesMax[d['round']]+d['maxGames']) + roundGamesMin[d['round']] += d['minGames'] + ph = getPhaseOfRound[d['round']] + getDaysOfPhase[ph].append(d['id']) + if d['round2'] > 0: + getRoundsByDay[d['id']].append(d['round2']) + getDays[d['round2']].append(d['id']) + roundDays.append((d['round2'], d['id'])) + roundDaysMin[(d['round2'], d['id'])] = d['minGames2'] + roundDaysMax[(d['round2'], d['id'])] = d['maxGames2'] + roundGamesMax[d['round2']] = min( + nTeams/2, roundGamesMax[d['round2']]+d['maxGames2']) + roundGamesMin[d['round2']] += d['minGames2'] + + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getDayByDateTime[dt] = d['id'] + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +countries = list(set([t_country[t] for t in teams])) + +getWeekDaysPerRound = {r: [getWeekDay[d] for d in getDays[r]] for r in rounds} + +wd = {"Mondays": 0, "Tuesdays": 1, "Wednesdays": 2, + "Thursdays": 3, "Fridays": 4, "Saturdays": 5, "Sundays": 6} +t_site_bestTimeSlots = {(t, d): [] for t in realteams for d in days} +prio_weight = {"A": 0, "B": 50, "C": 100} + + +toTime = False + + +earliestDay = {r: getDays[r][0] for r in rounds} +latestDay = {r: getDays[r][0] for r in rounds} +for r in rounds: + for d in getDays[r]: + dt = getDateTimeDay[d] + if dt > getDateTimeDay[latestDay[r]]: + latestDay[r] = d + if dt < getDateTimeDay[earliestDay[r]]: + earliestDay[r] = d + + +basicTeams = teams +basicGames = [(t1, t2) + for (t1, t2) in games if t1 in basicTeams and t2 in basicTeams] + +nBasicTeams = len(basicTeams) +nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9) +if nBasicTeamsPerCluster % 2 == 1: + nBasicTeamsPerCluster += 1 + + +nRounds1 = nBasicTeamsPerCluster-1 +nBasicRounds = nPhases * (nBasicTeamsPerCluster-1) + +if nBasicTeamsPerCluster == 1 or nBasicRounds == 1: + nBasicRounds = nRounds + nRounds1 = nRounds + +basicRounds = range(1, nBasicRounds+1) + +print("nPhases ", nPhases) +print("nGameClusters ", len(gameClusters)) +print("nBasicTeamsPerCluster ", nBasicTeamsPerCluster) +print("nBasicTeams ", nBasicTeams) +print("nBasicRounds ", nBasicRounds) +print("nRounds1 ", nRounds1) + + +stretch = nRounds/nBasicRounds +# print ("regionalPatternUse " , regionalPatternUse) +rounds1 = range(1, nRounds1+1) +nGames = nTeams*nRounds1 + + +getBasicRound = {r: int((r-1)/stretch)+1 for r in rounds} +getRealRounds = {br: [r for r in rounds if getBasicRound[r] == br] + for br in basicRounds} +# print ("stretch : " , stretch) + +getBasicDays = {r: [] for r in basicRounds} +for r in rounds: + getBasicDays[getBasicRound[r]] += (getDays[r]) + + +getRoundDaysByDay = {d: [rd for rd in roundDays if rd[1] == d] for d in days} +getRoundDaysByRound = { + r: [rd for rd in roundDays if rd[0] == r] for r in rounds} + +daysSorted = [] +for dt in sorted([getDateTimeDay[d] for d in days]): + daysSorted.append(getDayByDateTime[dt]) + +minRest = {(t, s1, s2): thisSeason.minDaysBetweenGames for t in teams for s1 in [ + 'A', 'H'] for s2 in ['A', 'H']} +for t in teamObjects: + minRest[(t['id'], 'H', 'H')] = max( + minRest[(t['id'], 'H', 'H')], t['minRest_HH']) + minRest[(t['id'], 'H', 'A')] = max( + minRest[(t['id'], 'H', 'A')], t['minRest_HA']) + minRest[(t['id'], 'A', 'H')] = max( + minRest[(t['id'], 'A', 'H')], t['minRest_AH']) + minRest[(t['id'], 'A', 'A')] = max( + minRest[(t['id'], 'A', 'A')], t['minRest_AA']) + +maxMinRest = {t: max([minRest[(t, s1, s2)] for s1 in ['A', 'H'] + for s2 in ['A', 'H']]) for t in teams} + +excessGames = {rd: -roundDaysMax[rd] for rd in roundDays} +deficientGames = {rd: roundDaysMin[rd] for rd in roundDays} + +excessGames = {rd: max(0, excessGames[rd]) for rd in roundDays} +deficientGames = {rd: max(0, deficientGames[rd]) for rd in roundDays} + + + +allowed_weekdays = {'--': [0, 1, 2, 3, 4, 5, 6], 'Mondays': [0], 'Tuesdays': [1], 'Wednesdays': [2], 'Thursdays': [3], 'Fridays': [4], + 'Saturdays': [5], 'Sundays': [6], 'Weekdays': [0, 1, 2, 3, 4], 'Weekends': [5, 6], 'Mon.-Thu.': [0, 1, 2, 3], 'Fri.-Sun.': [4, 5, 6]} + +hawishes = HAWish.objects.filter( + scenario=s2, active=True).order_by('reason').values() +hawTeams = {} +hawDays = {} +hawTimes = {} +hawRounds = {} +hawRoundsString = {} +for c in HAWish.objects.filter(scenario=s2): + # print () + # print (c.reason ) + hawDays[c.id] = [] + hawTeams[c.id] = [] + hawTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + if c.selection_mode == 0: + for t in c.teams.filter(active=True): + hawTeams[c.id].append(t.id) + elif c.selection_mode == 1: + for t in c.groups.all(): + for t2 in t.teams.filter(active=True): + hawTeams[c.id].append(t2.id) + elif c.selection_mode == 2: + cids = [cn.id for cn in c.countries.all()] + for t in teams: + if t_globalCountry[t] in cids: + hawTeams[c.id].append(t) + + if c.multidate: + hawDays[c.id] = [dd.id for dd in c.dates.all()] + # print ("multidate") + else: + if c.day and not c.day2: + hawDays[c.id].append(c.day.id) + # print('+ ',getDayById[e['day_id']]) + + if not c.day and c.day2: + hawDays[c.id].append(c.day2.id) + # print('+ ',getDayById[e['day2_id']]) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + if dt.weekday() in allowed_weekdays[c.weekdays]: + # print (hawDays[e['id']]) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + if c.day and c.day2: + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + +print("processing encounters") +encwishes = EncWish.objects.filter(scenario=s2, active=True).values() +encTeams1 = {} +encTeams2 = {} +encGroups = {} +encGames = {} +encTeams1String = {} +encTeams2String = {} +encDays = {e['id']: [] for e in encwishes} +encDaySets = {} +encTimes = {} +encRounds = {e['id']: [] for e in encwishes} +encRoundsString = {e['id']: "" for e in encwishes} +for c in EncWish.objects.filter(scenario=s2, active=True): + encTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + encTeams1[c.id] = [] + encTeams2[c.id] = [] + encGroups[c.id] = [] + encTeams1String[c.id] = '' + encTeams2String[c.id] = '' + for t in c.encounterGroups.all(): + encGroups[c.id].append(t) + if c.useGroups: + for gr in c.teams1_groups.all(): + for t in gr.teams.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += gr.name + ', ' + for gr in c.teams2_groups.all(): + for t in gr.teams.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + else: + for t in c.teams1.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += t.name + ', ' + for t in c.teams2.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + encTeams1String[c.id] = encTeams1String[c.id][:-2] + encTeams2String[c.id] = encTeams2String[c.id][:-2] + + if c.useEncounterGroups: + tmp_games = [(t1.id, t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all( + ) for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2] + tmp_games += [(t2.id, t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() + for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2 and ec.symmetry] + encGames[c.id] = [tmp_games] + else: + elemHomeTeams = [encTeams1[c.id]] + if c.forEachTeam1: + elemHomeTeams = [[t] for t in encTeams1[c.id]] + elemAwayTeams = [encTeams2[c.id]] + if c.forEachTeam2: + elemAwayTeams = [[t] for t in encTeams2[c.id]] + encGames[c.id] = [] + # print ("NEW ENC ", elemHomeTeams,elemAwayTeams) + for elh in elemHomeTeams: + for ela in elemAwayTeams: + # print (" --- ENC ", elh,ela) + tmp_games = [(t1, t2) for t1 in elh for t2 in ela if t1 != t2] + if c.symmetry: + tmp_games += [(t1, t2) + for t1 in ela for t2 in elh if t1 != t2] + encGames[c.id].append(tmp_games) + + if c.multidate: + encDays[c.id] = [dd.id for dd in c.dates.all()] + else: + if c.day: + day1 = getDateTimeDay[c.day.id] + + if c.day and not c.day2: + encDays[c.id].append(c.day.id) + + if not c.day and c.day2: + encDays[c.id].append(c.day2.id) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + if c.day and c.day2: + # day1= parse(c.day.day) + # day2= parse(c.day2.day) + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + encDaySets[c.id] = [] + elemDays = [encDays[c.id]] + if c.forEachDay or c.forOneDay: + elemDays = [[d] for d in encDays[c.id]] + + lastDaySet = [] + for d in elemDays: + tmpDays = d + if (c.forEachDay or c.forOneDay) and c.timeframe != 0: + tmpDays = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if c.timeframe > 0: + day2 = day1 + datetime.timedelta(days=c.timeframe-1) + # print (e) + # print (day1, day2) + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and dt <= day2: + tmpDays.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and getRoundByDay[d3] < r1 + (-c.timeframe): + tmpDays.append(d3) + # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe']) + # for d4 in elemEncWishDays[cntr]: + # print (" - " ,getDayById[d4]['day']) + + if len([d for d in tmpDays if d not in lastDaySet]) > 0: + encDaySets[c.id].append(tmpDays) + lastDaySet = tmpDays + # print("-.--- NEW DAYS", tmpDays) + + for ds in encDaySets[c.id]: + for d3 in ds: + encRounds[c.id] += getRoundsByDay[d3] + encRounds[c.id] = sorted(list(set(encRounds[c.id]))) + for r in encRounds[c.id]: + encRoundsString[c.id] += str(r)+"_" + if encRoundsString[c.id] != "": + encRoundsString[c.id] = encRoundsString[c.id][:-1] + # print (encRoundsString[c.id] , " # " ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds) + + +onlyEarlyDays = [] +onlyLateDays = [] + + +alwaysConsiderAllGames = not mathModelName in [ + "Florida State League", "UEFA NL"] + + +elemEncWishes = {e['id']: [] for e in encwishes} +elemEncWishGames = {} +elemEncWishDays = {} + +cntr = 0 +for e in encwishes: + for eg in encGames[e['id']]: + for ed in encDaySets[e['id']]: + if len(eg) > 0 and len(ed) > 0: + cntr += 1 + elemEncWishes[e['id']].append(cntr) + elemEncWishGames[cntr] = eg + elemEncWishDays[cntr] = ed + + +# print (elemEncWishGames) +# print (elemEncWishDays) + + +encRelRoundsMin = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} +encRelRoundsMax = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} + +for enc in encwishes: + # print (e) + # print ("ENC !! " , enc['reason'] ) + for el in elemEncWishes[enc['id']]: + relDaysSet = set(elemEncWishDays[el]) + for r in basicRounds: + if len(relDaysSet.intersection(set(getBasicDays[r]))) > 0: + frac = len(relDaysSet.intersection( + set(getBasicDays[r]))) / len(getBasicDays[r]) + # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac) + if frac > 0: + encRelRoundsMin[el].append(r) + if frac == 1: + encRelRoundsMax[el].append(r) + +nElemEncWishes = sum([len(elemEncWishes[enc['id']]) for enc in encwishes]) + +# print (encRelRoundsMin) + +elemHaWishes = {e['id']: [] for e in hawishes} +elemHaWishTeams = {} +elemHaWishDays = {} +elemHaWishFirstDay = {} + +cntr = 1 +for e in hawishes: + elemTeams = [hawTeams[e['id']]] + if e['forEachTeam']: + elemTeams = [[t] for t in hawTeams[e['id']]] + + elemDays = [hawDays[e['id']]] + if e['forEachDay'] or e['forOneDay']: + elemDays = [[d] for d in hawDays[e['id']]] + + elemHaWishes[e['id']] = [] + allElemDays = [] + thisDaySet = [] + lastDaySet = [] + for d in elemDays: + # print (e) + if (e['forEachDay'] or e['forOneDay']) and e['timeframe'] != 1: + thisDaySet = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if e['timeframe'] > 1: + day2 = day1 + datetime.timedelta(days=e['timeframe']-1) + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and dt <= day2: + thisDaySet.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and getRoundByDay[d3] < r1 + (-e['timeframe']): + thisDaySet.append(d3) + print(" ROUND HA WISH ", e, thisDaySet, e['timeframe']) + else: + thisDaySet = d + + # only create wish id new day set is superset + if len([d for d in thisDaySet if d not in lastDaySet]) > 0: + for t in elemTeams: + cntr += 1 + elemHaWishes[e['id']].append(cntr) + elemHaWishTeams[cntr] = t + elemHaWishDays[cntr] = thisDaySet.copy() + elemHaWishFirstDay[cntr] = d[0] + lastDaySet = thisDaySet.copy() + allElemDays += thisDaySet + + hawRounds[e['id']] = [] + for d3 in set(allElemDays): + hawRounds[e['id']] += getRoundsByDay[d3] + hawRounds[e['id']] = sorted(list(set(hawRounds[e['id']]))) + hawRoundsString[e['id']] = "" + for r in hawRounds[e['id']]: + hawRoundsString[e['id']] += str(r)+"_" + if hawRoundsString[e['id']] != "": + hawRoundsString[e['id']] = hawRoundsString[e['id']][:-1] + +nElemHaWishes = sum([len(elemHaWishes[enc['id']]) for enc in hawishes]) + +gameAttractivity = {(t1, t2): attractivity[t1, t2] + for (t1, t2) in games if t1 < t2} + + +sorted_gameAttractivity = sorted( + gameAttractivity.items(), key=operator.itemgetter(1)) + +nGames = len(games) +topGames = [sorted_gameAttractivity[i] for i in range( + int(0.8*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] +goodGames = [sorted_gameAttractivity[i] for i in range( + int(0.6*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] + +broadcastingwishes = BroadcastingWish.objects.filter(scenario=s2) + + +nonBlocked = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +travelDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +hideDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +nonBlockedRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +travelRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +hideRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} + +for bl in blockings: + bl_val = 1.0 if bl['time'] == "----" else 0.5 + # if bl['time']!="----": + # print (bl, bl_val , getDayById[bl['day_id']] ,getDayById[bl['day_id']]['round'] ,getDayById[bl['day_id']]['round'] !=0 ) + if getDayById[bl['day_id']]['round'] != 0: + if bl['type'] in ["Home", "Hide"]: + nonBlocked[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + nonBlockedRounds[( + bl['team_id'], getDayById[bl['day_id']]['round'])] -= bl_val + # print ('FOUND HOME BLOCKING ', bl) + if bl['type'] in ["Away", "Hide"]: + travelDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + travelRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + if bl['type'] in ["Hide"]: + hideDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + hideRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + +noPlayRounds = {t: [r for r in rounds if nonBlockedRounds[( + t, r)] + travelRounds[(t, r)] == 0] for t in teams} +noHomeRounds = { + t: [r for r in rounds if nonBlockedRounds[(t, r)] == 0] for t in teams} +# print("nonBlockedRounds" ,nonBlockedRounds) +# print("noHomeRounds" ,noHomeRounds) +# print("noPlayRounds" ,noPlayRounds) + +playRounds = {t: sorted( + [r for r in rounds if hideRounds[(t, r)] > 0]) for t in teams} + + +# scale blocking of basic round to [0,1] +for t in teams: + for r in basicRounds: + if len(getBasicDays[r]) > 0: + nonBlocked[(t, r)] /= len(getBasicDays[r]) + travelDays[(t, r)] /= len(getBasicDays[r]) + if getTeamById[t] == "AX Armani Exchange Milan": + print(r, getRealRounds[r], + nonBlocked[(t, r)], nonBlocked[(t, r)]) + +for t in teams: + if mathModelName == "UEFA NL" and noPlayRounds[t] in [[3, 4]]: + t_usePhases[t] = False + print("No need for phases ", getTeamById[t]) + for t2 in opponents[t]: + t_usePhases[t2] = False + print(" -- also no need for phases ", getTeamById[t2]) + if getTeamById[t] == "AX Armani Exchange Milan": + t_usePhases[t] = False + print("setting t_usePhases of ", getTeamById[t], t_usePhases[t]) + +runPatternAssignmentFirst = False + + +chosenGames = [] + +useFullModel1 = False + +usePatterns = not mathModelName in ["NHL", "LNR"] +usePatterns = not mathModelName in ["NHL"] + + +balanceBreaks = mathModelName == "LNR" +haSymmetric = not mathModelName in ["NHL", "LNR"] +haSymmetric = not mathModelName in [ + "NHL", "Ligue 1", "Costa Rica Premier League"] +haSymmetric = not mathModelName in ["NHL"] +# haSymmetric = not mathModelName in ["NHL" , "LNR"] +if thisSeason.symmetry: + haSymmetric = True + +use2BreakPatterns = False + +if thisSeason.initBreaks >= 2: + use2BreakPatterns = True + +half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1 + + +prev_mirror_round = {r: 0 if r <= nRounds1 else r - + nRounds1+half_symmetry_offset for r in basicRounds} + + +for r in basicRounds: + if r > nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1): + prev_mirror_round[r] = int( + (r-1)/nRounds1-1)*nRounds1 + half_symmetry_offset+1-prev_mirror_round[r] % nRounds1 + + +getPhaseOfBasicRound = { + br: getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds} +getBasicRoundsOfPhase = {ph: [ + br for br in basicRounds if getPhaseOfBasicRound[br] == ph] for ph in phases} + +if use2BreakPatterns or nTeams > 200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams > 0: + useFullModel1 = True +useFullModel1 = True + + +preplan_phases = phases +preplan_phases = [0] +if not haSymmetric: + preplan_phases = phases + +fixedGamesRounds = [] + +starweight = {t: 0.0 for t in teams} +for t1 in teams: + for t2 in teams: + starweight[t1] += distance[getTeamById[t1], getTeamById[t2]] + +available_days = {(t, d): 1 for t in teams for d in days} + + +for bl in blockings: + if bl['type'] in ["Home", "Hide"] and bl['time'] == '----': + available_days[bl['team_id'], bl['day_id']] = 0 + +t_blocked_at = {(t, r): sum([available_days[t, d] + for d in getDays[r]]) == 0 for t in teams for r in rounds} + + + +comptime,gap = 500, 0 + +# def optimize_model1(comptime,gap): +TSP=[] + +numPatterns= 2*nRounds1 +numBreaks= nTeams/2 + +patternRange=[p for p in range(1,numPatterns+1)] +patterns={} + +# print (numPatterns, " :"+numPatterns) +# for t in teams: + # print (t, " :", strength[t]) +for j in range (1,int(numPatterns/2)+1): + patterns[2*j-1,1]= 1 + patterns[2*j,1]=0 +for j in range(1,int(numPatterns/2)+1): + for i in range (2,int(numPatterns/2)+1): + patterns[2*j-1,i]= 1-patterns[2*j-1,i-1] + if i>= 1 and i==j : + patterns[2*j-1,i]=1-patterns[2*j-1,i] + patterns[2*j,i]= 1-patterns[2*j-1,i] + +forbiddenPatterns=[] + +# IMPORTANT: THIS SHOULD BE INCLUDED AGAIN: WHY RESTRICTING SO HARD? +# -> because it insures feasibility of chosen pattern set +onlyUsePrimaryPatternSet=nTeams<20 +if onlyUsePrimaryPatternSet: + for p in patternRange: + if thisSeason.symmetry: + if (p % 4 == 0 and p<2*nRounds1-4) or p==2*nRounds1: + forbiddenPatterns.append(p-1) + forbiddenPatterns.append(p) + else: + if (p % 4 == 0 ) : + forbiddenPatterns.append(p-1) + forbiddenPatterns.append(p) + +nBreaksOfPat ={ p : 3 for p in patternRange } +nBreaksOfPat[1]=0 +nBreaksOfPat[2]=0 + +print ("forbiddenPatterns" , forbiddenPatterns) +# print ("patternRange" , patternRange) + + +if use2BreakPatterns : + p_cntr=numPatterns + for b1 in rounds1: + for b2 in rounds1: + if b1+4<=b2 and (b1-b2)%2==0 and b1>1 and b2-b11: + np = 1-patterns[p_cntr-1,r-1] + if r in [b1,b2]: + np=1-np + patterns[p_cntr-1,r]=np + patterns[p_cntr,r]=1-np + patternRange.append(p_cntr-1) + patternRange.append(p_cntr) + nBreaksOfPat[p_cntr-1]=4 + nBreaksOfPat[p_cntr]=4 + +forbiddenStarterPatterns = [ p for p in patternRange if not thisSeason.startWithBreakAllowed and patterns[p,1]==patterns[p,2]] +forbiddenEndPatterns = [ p for p in patternRange if not thisSeason.endWithBreakAllowed and patterns[p,nRounds1-1]==patterns[p,nRounds1]] + +if thisSeason.forbidDoubleBreaks and thisSeason.symmetry: + forbiddenStarterPatterns+= [ p for p in patternRange if patterns[p,2]==patterns[p,3]] + forbiddenEndPatterns += [ p for p in patternRange if patterns[p,nRounds1-2]==patterns[p,nRounds1-1]] + +if mathModelName in ["Lega Basket Seria A"] and True : + forbiddenPatterns = [ p for p in patternRange if patterns[p,3]==patterns[p,4] or patterns[p,nRounds1-3]==patterns[p,nRounds1-2] ] + +for p in patternRange: + if balanceBreaks and p in [1,2]: + forbiddenPatterns.append(p) + + # take care that season halves can be glued together + if (thisSeason.maxTourLength==2 and patterns[p,1]!=patterns[p,nRounds1] + and (patterns[p,1]==patterns[p,2] or patterns[p,nRounds1-1]==patterns[p,nRounds1])): + forbiddenPatterns.append(p) + +if haSymmetric: + forbiddenPatterns+=forbiddenStarterPatterns+forbiddenEndPatterns + +print ("forbiddenPatterns ",forbiddenPatterns) +print ("patternRange ",patternRange) + +for p in patternRange: + nHome = sum ([ patterns[p,d] for d in rounds1 ]) + # print ("+++" ,p, nHome , nHome < nRounds1/2-1 , nHome > nRounds1/2+1 , thisScenario.usePhases) + if (nHome < nRounds1/2-1 or nHome > nRounds1/2+1) and thisScenario.usePhases: + forbiddenPatterns.append(p) + # print ("\nbad pattern :" , p) + +print ("\nforbidding :") +for p in set(forbiddenPatterns): + for d in rounds1: + print (patterns[p,d],end="") + print ("removing ", p) + # Why should we only really remove patterns with 2 breaks? + if len(patternRange)>2*len(teams)-2 or mathModelName in ["Lega Basket Seria A"]: + patternRange.remove(p) + +# take always care of proper start and end +patternRange = [p for p in patternRange if p not in forbiddenStarterPatterns+forbiddenEndPatterns ] + +for p in patternRange: + print ( "\n",100+p , " :" ,end="") + for d in rounds1: + print (patterns[p,d],end="") + print (" " ,p not in forbiddenPatterns ,end="") + # if (p % 4 == 0 and p= len(patternRange): + i-=len(patternRange) + if i >0: + i+=1 + patternCircle.append(patternRange[i]) +patternCircle.append(2) + +neighborPattern={} +for i in range(1,len(patternCircle)): + neighborPattern[patternCircle[i-1]]=patternCircle[i] +neighborPattern[patternCircle[-1]] =patternCircle[0] + + +# %% + + +model = LpProblem("League_Scheduling_Model_-_Assign_Patterns_"+str(thisScenario.id), LpMinimize) +# usePattern={(p,ph) : LpVariable('usePat_'+str(p) + '_' + str(ph), lowBound = 0, upBound = 10, cat = LpInteger) for p in patternRange for ph in preplan_phases} +assignPattern={(p,t1,ph) : LpVariable('assignPattern_'+str(t1)+'_'+str(p)+'_'+str(ph), lowBound = 0, upBound = 1, cat = LpInteger) for p in patternRange for t1 in basicTeams for ph in preplan_phases} +# blockingVio1 = LpVariable('blockingVio1', lowBound = 0, cat = LpContinuous) +# HawVio1TooLess = { el : LpVariable('havvio1tooless_'+ str(haw['id'])+"_"+str(el) , lowBound = 0, cat = LpContinuous) for haw in hawishes for el in elemHaWishes[haw['id']]} +# HawVio1TooMuch = { el : LpVariable('havvio1toomuch_'+ str(haw['id'])+"_"+str(el) , lowBound = 0, cat = LpContinuous) for haw in hawishes for el in elemHaWishes[haw['id']]} +# HawVio1 = { haw['id'] : LpVariable('havvio_'+ str(haw['id']) , lowBound = 0, cat = LpContinuous) for haw in hawishes} +# HawVio1Total = LpVariable('HawVio1Total', lowBound = 0, cat = LpContinuous) +# encVio1 = { el : LpVariable('encVio1'+ str(enc['id']) + '_'+ str(el) , lowBound = 0, cat = LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]} +# encVio1Total = LpVariable('encVio1Total', lowBound = 0, cat = LpContinuous) +# totalDistanceSaved1 = LpVariable('totalDistanceSaved1', lowBound = 0, cat = LpContinuous) +# tooManyHomesInStadium1= {(stadium,r) : LpVariable('tooManyHomesInStadium_'+str(stadium)+'_'+str(r), lowBound = 0, cat = LpContinuous) for stadium in stadiums for r in basicRounds} +# pairingVio1= {(pair['id'],r) : LpVariable('pairingVio1_'+str(pair['id'])+'_'+str(r), lowBound = 0, cat = LpContinuous) for pair in pairings for r in basicRounds} +# gamesTooClose1 = { (t1,t2) : LpVariable('gamesTooClose1_'+ str(t1) +'_'+ str(t2) , lowBound = 0, cat = LpContinuous) for (t1,t2) in games } + +print ("basicRounds",basicRounds) +print ("useFullModel1",useFullModel1) + + +complementary_pattern = { p : p-1 if p%2==0 else p+1 for p in patternRange} + +print ("phases", phases) +print ("preplan_phases", preplan_phases) +print ("patternRange",patternRange) + +for ph in preplan_phases: + if ph==0 or half_symmetry_offset==0 : + for p in patternRange : + # model+= usePattern[(p,ph)]== sum([ assignPattern[p,t,ph] for t in basicTeams]), f"usePattern_{p}_{ph}" + # if p%2==0: + # model+= usePattern[(p,ph)]==usePattern[complementary_pattern[p],ph], f"complementaries_{p}_{complementary_pattern[p]}" + # print ("complementaries " , p, complementary_pattern[p]) + + # every pattern at most one team in each game cluster + for c in gameClusters: + # print (c, gameClusterTeams[c] , len(patternRange)) + model += lpSum( assignPattern[(p,t,ph)] for t in basicTeams if t in gameClusterTeams[c] )<=1 , "one_team_for_pattern_%s_%s_%s" %(p,c,ph) + + # every team one pattern + # for t in basicTeams: + # print ("one_pattern_for_",ph,t) + # print ("one_pattern_for_",t, lpSum( assignPattern[(p,t,ph)] for p in patternRange)) + # model += lpSum( assignPattern[(p,t,ph)] for p in patternRange)==1 , "one_pattern_for_each_team_%s_%s"%(t,ph) + + + +x12 ={} +if useFullModel1 : + x11={(t1,t2,d) : LpVariable('x11_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = LpInteger) for (t1,t2) in basicGames for d in basicRounds if (d <= nRounds1 or (not thisSeason.symmetry and not haSymmetric))} + tripToCluster1 = {} + away_in_cluster1 = {} + for r in basicRounds: + for (t1,t2) in games: + if t1 in basicTeams and t2 in basicTeams: + if r <= nRounds1 or (not thisSeason.symmetry and not haSymmetric): + x12[(t1,t2,r)] = x11[(t1,t2,r)] + else: + x12[(t1,t2,r)] = x12[(t2,t1,prev_mirror_round[r])] + + + for (t1,t2) in basicGames: + model += lpSum( x12[(t1,t2,r)] for r in basicRounds) >= gameCntr[(t1,t2)], f"gameCntr_{t1}_{t2}" + + # teams meet at most once in each phase + # print (t1,t2,not haSymmetric , thisScenario.usePhases , (t2,t1) in games) + if thisScenario.usePhases and (t2,t1) in basicGames and t10: +# print (pt, assignPattern[pt].value()) + + +# # todo2 raus +# model += lpSum( x12[(t1,t2,r)] for r in basicRounds for (t1,t2) in games ) >=304 + +# model.writeLP("LPfile.txt") + +# model.solve(GUROBI(MIPGap=0.0, TimeLimit=10)) +# print (basicTeams) +# print (patternRange) + +print (basicTeams) +print (basicRounds) + +homePat={} +awayPat={} + +for t in basicTeams: + for d in sorted(basicRounds): + # model+=homePat[t,d]+awayPat[t,d]<=1 + if usePatterns and (half_symmetry_offset==0 or d<=nRounds1) and getPhaseOfBasicRound[d] in preplan_phases: + # if usePatterns and (half_symmetry_offset==0 or d<=nRounds1) and getPhaseOfBasicRound[d] in [0]: + homePat[t,d] = lpSum(assignPattern[(p,t,getPhaseOfBasicRound[d])] * patterns[p,d-getPhaseOfBasicRound[d]*nRounds1] for p in patternRange) + else: + homePat[t,d] = LpVariable('homePat_'+str(t) + '_' + str(d), lowBound = 0, upBound = 1, cat = LpInteger) + awayPat[t,d] = 1- homePat[t,d] + if useFullModel1: + model+=homePat[t,d] == lpSum( [ x12[(t,t2,d)] for t2 in basicTeams if gameCntr[(t,t2)]+undirectedGameCntr[(t,t2)]>0] ), f"homeGame_{t}_{d}" + # model+=awayPat[t,d] == lpSum( [ x12[(t2,t,d)] for t2 in basicTeams if gameCntr[(t2,t)]>0] ) + + + +print ("teams constraints built") + +if useFullModel1: + # as many teams playing home as away in every round + + for r in basicRounds: + if getPhaseOfBasicRound[r]==0 or not thisSeason.symmetry : + realBasicTeams = [ t for t in basicTeams if t in realteams] + # print (basicTeams , realBasicTeams , " play at least ", int(0.5*len(realBasicTeams) ) , " home games per basicround", basicRounds ) + model += lpSum( homePat[t,r] for t in realBasicTeams )>= int(0.5*len(realBasicTeams) ) + for t1 in basicTeams: + # play at most one game per round + model += lpSum( x12[(t1,t2,r)]+x12[(t2,t1,r)] for t2 in basicTeams if (t1,t2) in games )<= 1 + # couple to home pattern + # print ( "couple to home pattern" , t1,r , lpSum( x12[(t1,t2,r)] for t2 in basicTeams if (t1,t2) in games )) + model += lpSum( x12[(t1,t2,r)] for t2 in basicTeams if (t1,t2) in games )== homePat[t1,r] + relRounds = [ r2 for r2 in basicRounds if r2>=r and r2<=r+thisSeason.maxTourLength/(defaultGameRepetions*tripStartHeuristicGroupsize)] +# print(len(relRounds) , thisSeason.maxTourLength/(defaultGameRepetions*tripStartHeuristicGroupsize)+1) + if not usePatterns and not haSymmetric and len(relRounds)==thisSeason.maxTourLength/(defaultGameRepetions*tripStartHeuristicGroupsize)+1: + for t in realBasicTeams: +# print (getTeamById[t]) + model += lpSum( homePat[t,r2] for r2 in relRounds) >= 1 + model += lpSum( awayPat[t,r2] for r2 in relRounds) >= 1 + print (" At least one home/away for " , getTeamById[ t], " in round ", relRounds) + # print (lpSum( homePat[t1,r2] for r2 in relRounds)) + # print ("+++++ " , r<=nRounds1 , r+thisSeason.minRoundsBetweenGameOfSameTeams>nRounds1 , not haSymmetric ) + + # if r<=nRounds1 and r+thisSeason.minRoundsBetweenGameOfSameTeams>nRounds1 and not thisSeason.symmetry : + # relRounds = [ r2 for r2 in basicRounds if r2>=r and r2<=r+thisSeason.minRoundsBetweenGameOfSameTeams ] + # for (t1,t2) in games: + # if (t2,t1) in games and t1 0: +# home_dict[key[2],key[0]] = key[1] +# away_dict[key[2],key[1]] = key[0] +for key in x12.keys(): + if type(x12[key]) != int and x12[key].value() > 0: + home_dict[key[2],key[0]] = key[1] + away_dict[key[2],key[1]] = key[0] +# for key in missingGamesVio.keys(): +# if type(missingGamesVio[key]) != int and missingGamesVio[key].value() > 0: +# missing_games.append((t_shortname[split_key[0]],t_shortname[split_key[1]])) + + + +sol = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for r in rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in teams: + tname = t_shortname[t] + sol += f"" + for r in rounds: + if (r,t) in home_dict.keys(): + opponent = t_shortname[home_dict[(r,t)]] + sol += f"" + elif (r,t) in away_dict.keys(): + opponent = t_shortname[away_dict[(r,t)]] + sol += f"" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{r}
{tname}{opponent}{opponent}
\n" +sol += "


\n" +# for game in missing_games: +# sol += f'{game}
\n' + + +with open('xpress_sol.html', 'w') as f: + f.write(sol) + +# %% + +from ortools.sat.python import cp_model +"""Minimal CP-SAT example to showcase calling the solver.""" +# Creates the model. +sat_model = cp_model.CpModel() + +# Creates the variables. +# x = model.NewBoolVar('x') +# y = model.NewBoolVar('y') +# z = model.NewBoolVar('z') + +sat = {} +for var in model.variables(): + sat[str(var)] = sat_model.NewBoolVar(f'var') + +# %% + +for ckey,cval in model.constraints.items(): + cons_dict = cval.toDict() + vars = cons_dict['coefficients'] + sense = cons_dict['sense'] + constant = cons_dict['constant'] + if sense == -1: + sat_model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant <= 0) + elif sense == 0: + sat_model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant == 0) + elif sense == 1: + sat_model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant >= 0) + + +objective_terms = [] +# for var in model2.objective.toDict(): +# objective_terms.append(var['value']*sat[var['name']]) +sat_model.Minimize(sum(objective_terms)) + +# %% + +# # Creates the constraints. +# # one_game_in_round +# for ckey,cval in model2.constraints.items(): +# if cval.sense == -1: +# vars_in_cons = cval.toDict()['coefficients'] +# model.AddAtMostOne(sat[i['name']] for i in vars_in_cons) +# # for q in itertools.combinations(vars_in_cons,2): +# # qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']] + +# # let_play_gameCntr_times +# for ckey,cval in model2.constraints.items(): +# if cval.sense == 0: +# vars_in_cons = cval.toDict()['coefficients'] +# model.AddExactlyOne(sat[i['name']] for i in vars_in_cons) + + +# objective_terms = [] +# for var in model2.objective: +# objective_terms.append(sat[str(var)]) +# model.Minimize(sum(objective_terms)) + +# %% + +print("STARTING SAT-SOLVER") +# Creates a solver and solves the model. +solver = cp_model.CpSolver() +solver.parameters.log_search_progress = True +print("STARTING SAT-SOLVER") +status = solver.Solve(sat_model) + + +# %% + +home_dict = {} +away_dict = {} +missing_games = [] +for key in sat.keys(): + if solver.BooleanValue(sat[key]): + split_key = key.split('_') + if split_key[0] == 'x': + home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3]) + away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2]) + elif split_key[0] == 'missingGamesVio': + missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])])) + + +sol = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for r in rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in teams: + tname = t_shortname[t] + sol += f"" + for r in rounds: + if (r,t) in home_dict.keys(): + opponent = t_shortname[home_dict[(r,t)]] + sol += f"" + elif (r,t) in away_dict.keys(): + opponent = t_shortname[away_dict[(r,t)]] + sol += f"" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{r}
{tname}{opponent}{opponent}
\n" +sol += "


\n" +for game in missing_games: + sol += f'{game}
\n' + + +with open('sat_sol.html', 'w') as f: + f.write(sol) +# %% + +# %% + +API_KEY = 'AJwxeJRRZtJ9rOKVGTV6ruvG9TvC1wnE' + +from quantagonia.qubo import QuboModel +from quantagonia.enums import HybridSolverConnectionType +from quantagonia.runner import Runner +from quantagonia.runner_factory import RunnerFactory +from quantagonia.spec_builder import QuboSolverType, QUBOSpecBuilder + + +spec = QUBOSpecBuilder() +spec.set_time_limit(time_limit=600) + +runner = RunnerFactory.getRunner(HybridSolverConnectionType.CLOUD, api_key=API_KEY) + + +# %% + +qubo_prob = QuboModel() + +qubos = {} +for var in model.variables(): + qubos[str(var)] = qubo_prob.addVariable(f"{var}", initial=0) + + +for var in model.objective: + qubo_prob.objective += -1*qubos[str(var)] + +for ckey,cval in model.constraints.items(): + if cval.sense == -1: + vars_in_cons = cval.toDict()['coefficients'] + for q in itertools.combinations(vars_in_cons,2): + qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']] + +for ckey,cval in model.constraints.items(): + if cval.sense == 0: + qubo_prob.objective += 1 + vars_in_cons = cval.toDict()['coefficients'] + for var in vars_in_cons: + qubo_prob.objective += -1*-1*qubos[var['name']] + for q in itertools.combinations(vars_in_cons,2): + qubo_prob.objective += -1*2*qubos[q[0]['name']]*qubos[q[1]['name']] + + + + +with open('model_qubo.txt','w') as f: + f.write(str(qubo_prob)) +status = qubo_prob.solve(spec.getd(), runner=runner) + + +# %% + +home_dict = {} +away_dict = {} +missing_games = [] +for key in qubo_prob.vars.keys(): + if qubo_prob.vars[key].eval() > 0: + split_key = key.split('_') + if split_key[0] == 'x': + home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3]) + away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2]) + elif split_key[0] == 'missingGamesVio': + missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])])) + + +sol = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for r in rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in teams: + tname = t_shortname[t] + sol += f"" + for r in rounds: + if (r,t) in home_dict.keys(): + opponent = t_shortname[home_dict[(r,t)]] + sol += f"" + elif (r,t) in away_dict.keys(): + opponent = t_shortname[away_dict[(r,t)]] + sol += f"" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{r}
{tname}{opponent}{opponent}
\n" +sol += "


\n" +for game in missing_games: + sol += f'{game}
\n' + + +with open('qubo_sol.html', 'w') as f: + f.write(sol) + + + diff --git a/machine_learning/scripts/qubo/qubo_from_notebook.py b/machine_learning/scripts/qubo/qubo_from_notebook.py new file mode 100755 index 0000000..3e4e222 --- /dev/null +++ b/machine_learning/scripts/qubo/qubo_from_notebook.py @@ -0,0 +1,2205 @@ +# %% +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") + + +# XPRESS ENVIRONMENT +os.environ['XPRESSDIR'] = "/opt/xpressmp_8.4" +os.environ['XPRESS'] = "/opt/xpressmp_8.4/bin" +os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"+os.environ['LD_LIBRARY_PATH'] +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['PYTHONPATH'] +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.environ['CLASSPATH'] +os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprm.jar:"+os.environ['CLASSPATH'] +os.environ['PATH'] =os.environ['XPRESSDIR']+"/bin:"+os.environ['PATH'] + + +from leagues import settings +settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3' + +import django +django.setup() + +from scheduler.models import * +import operator +from scheduler.solver.functions import * +from common.functions import distanceInKmByGPS +import pandas as pd +import numpy as np +from django.db.models import Count, F, Value +from pulp import * + + +def makeIntVar(v): + if type(v) != int: + v.cat = LpInteger + # print ("int var ", v) + + +def setStart(v, vl): + if type(v) != int: + vl = float(vl) + v.start = vl + + +def setLB(v, vl): + if type(v) != int: + vl = float(vl) + v.lowBound = vl + # print ("set lb ", v , " = ", vl) + + +def setUB(v, vl): + if type(v) != int: + vl = float(vl) + v.upBound = vl + # print ("set ub ", v , " = ", vl) + + +def getVal(v): + if type(v) == int: + return v + else: + return v.value() + + +user_is_staff = True +runMode = 'New' +localsearch_time = 0 +solver = '?' + +s2 = 3 +thisScenario = Scenario.objects.get(id=s2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + + +mathModelName = thisLeague.name +if thisSeason.optimizationParameters != "": + mathModelName = thisSeason.optimizationParameters + +# %% + + +otherScenGames = [] +if thisSeason.improvementObjective != "--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective == "stick to old solutions": + impObjWeight = -5 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append( + (int(g[1]), int(g[2]), int(g[3]), impObjWeight)) + +getGlobalCountry = {gc['uefa']: gc['id'] + for gc in GlobalCountry.objects.values()} + +teamObjects = Team.objects.filter( + season=thisSeason, active=True).order_by('position').values() +teams = [] +realteams = [] +faketeams = [] +importantteams = [] +shortteams = [] +t_pos = {} +t_pot = {} +t_color = {} +t_country = {} +t_globalCountry = {} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity = {} +getTeamById = {} +getTeamIdByName = {} +getTeamIdByShortName = {} +getTeamByName = {} +getStadiumById = {} +teamByShort = {} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']] = t['name'] + getTeamIdByShortName[t['shortname']] = t['id'] + if t['name'] != '-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']] = t['country'] + t_globalCountry[t['id']] = t['globalCountry_id'] + if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys(): + t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]] + t_pos[t['name']] = t['position'] + t_color[t['name']] = "lightyellow" + t_pot[t['id']] = t['pot'] + t_stadium[t['id']] = t['stadium'] + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_shortname[t['id']] = t['shortname'] + t_usePhases[t['id']] = thisScenario.usePhases + getTeamById[t['id']] = t['name'] + getStadiumById[t['id']] = t['stadium'] + getTeamIdByName[t['name']] = t['id'] + getTeamByName[t['name']] = t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient: + top4.append(t['id']) + +inactive_teams = [] +for t in Team.objects.filter(season=thisSeason, active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_country[t['id']] = t['country'] + getTeamById[t['id']] = t['name'] + + +lowerBoundFound = False +distance = {} +attractivity = {} +distanceById = {} +distanceInDaysById = {} +stadium_names = set([t['stadium'] for t in teamObjects if t['stadium'] != '']) +stadium_id = {} +stadium_name = {} +sid = 0 +for stadium in stadium_names: + stadium_name[sid] = stadium + stadium_id[stadium] = sid + sid += 1 +stadiums = list(stadium_name.keys()) +teamsOfStadium = {st: [] for st in stadiums} + +travelDict = thisSeason.travelDict() +for t1 in teamObjects: + if t1['stadium'] != '': + teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'], t2['name'] + ] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'], t2['id']] = distance[t1['name'], t2['name']] + distanceInDaysById[t1['id'], t2['id']] = int( + distance[t1['name'], t2['name']]/350 + 0.99) + attractivity[t1['id'], t2['id']] = int( + 100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [d['id'] for d in dayObjects if d['round'] > 0] + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +higherTeamObjects = Team.objects.filter( + season__in=higherSeasons, active=True).values() +higherDayObjects = Day.objects.filter( + season__in=higherSeasons, maxGames__gte=1).values() +higherTeams = [t['id'] for t in higherTeamObjects] +higherTeamsOf = {t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']] = t['name'] + t_country[t['id']] = t['country'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print("Teams : ", teams) +print("VIP Teams : ", importantteams) +print("TOP Teams : ", top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums = ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t: 0 for t in teams} +conf_teams = {} +for c in Conference.objects.filter(scenario=s2, regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name] = [] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id] == 0 or len(cteams) < len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id] = c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +prioVal = {'A': 25, 'B': 5, 'C': 1, 'Hard': 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name] = prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name] = sw.float1 + sw_float2[sw.name] = sw.float2 + sw_int1[sw.name] = sw.int1 + sw_int2[sw.name] = sw.int2 + sw_text[sw.name] = sw.text +special_wishes = list(sw_prio.keys()) +special_wishes_active = [sw for sw in special_wishes if sw_prio[sw] > 0] + +print(special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active: + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print(noBreakLimitTeams) + noBreakLimitTeams = [ + t for t in teams if getTeamById[t] in noBreakLimitTeams] + print(noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter( + scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values() +pairings_tmp3 = Pairing.objects.filter( + scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values() +pairings = [] +for p in [p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams] + [p for p in pairings_tmp2] + [p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) +breaks = Break.objects.filter(season=thisSeason).values() +blockings_tmp = Blocking.objects.filter(scenario=s2, active=True).values() +blockings = [bl for bl in blockings_tmp if bl['team_id'] in teams] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']): t['name'] for t in timeObjects} +getIdByTime = {t['name']: str(t['id']) for t in timeObjects} + +blocked_arena = { + (t, d, tm): False for t in teams for d in days for tm in ["----"] + times} +hidden_arena = {(t, d, tm): False for t in teams for d in days for tm in [ + "----"] + times} +nBlockingHome = 0 +nBlockingAway = 0 + +for bl in blockings: + if bl['type'] in ["Hide"]: + hidden_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + + if bl['type'] in ["Home", "Hide"]: + nBlockingHome += 1 + blocked_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + else: + nBlockingAway += 1 + +nTeams = len(teams) +nPhases = thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} +undirectedGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} + +for t1 in teams: + for t2 in teams: + if t1 != t2: + gameCntr[(t1, t2)] += nPhases/2 + +for (t1, t2) in gameCntr.keys(): + if gameCntr[(t1, t2)] % 1 != 0: + undirectedGameCntr[(t1, t2)] = 1 + gameCntr[(t1, t2)] = int(gameCntr[(t1, t2)]) + +games = [gm for gm in gameCntr.keys() if gameCntr[gm] + + undirectedGameCntr[gm] > 0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter( + scenario=s2).values() + +gew = {} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']] = ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips'] > gew['Breaks']: + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl = mathModelName in ["Florida State League"] + +if len(games) == 0: + games = [(t1, t2) for t1 in teams for t2 in teams if t1 != t2] + specialGameControl = True + +realgames = [(t1, t2) for (t1, t2) in games if getTeamById[t1] + != "-" and getTeamById[t2] != "-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = (runMode == "Improve" and localsearch_time == 0 + and len(thisScenario.solutionlist()) == sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames])) + +opponents = {t: set([]) for t in realteams} + +for (t1, t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) + +# %% + +gmClusters = range(1, nTeams+2) + +if nPhases > 0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl: + gmClusters = range(1, 2) + +print("gmClusters", gmClusters) + + +gameClusterTeams = {c: [t for t in teams] for c in gmClusters} +gameClusters = [c for c in gameClusterTeams.keys() if len( + gameClusterTeams[c]) > 0] +biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters]) + +# %% +nPhases = min([gameCntr[(t1, t2)] + gameCntr[(t2, t1)] + + undirectedGameCntr[(t1, t2)] for (t1, t2) in realgames]) +tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize == "None" else int( + thisSeason.tripStartHeuristicGroupsize) +defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2 +nPhases = max(1, int(nPhases/defaultGameRepetions)) +phases = range(nPhases) + +useBasicGames = nPhases > 0 + +if not useBasicGames: + print('no basic games but biggest group size ', + biggestGroupSize, ' in nPhases ', nPhases) + +nRounds = thisSeason.nRounds +rounds = range(1, nRounds+1) + +# nRoundsPerPhase= 1 +# if nPhases>0: +nRoundsPerPhase = int(nRounds/nPhases) + +print('nRounds ', nRounds) +print('nPhases ', nPhases) +print('nRoundsPerPhase ', nRoundsPerPhase) +print('defaultGameRepetions ', defaultGameRepetions) +print('tripStartHeuristicGroupsize ', tripStartHeuristicGroupsize) + +getPhaseOfRound = {r: min(nPhases-1, int((r-1)/nRoundsPerPhase)) + for r in rounds} +getDaysOfPhase = {p: [] for p in phases} +getDays = {r: [] for r in rounds} +roundGamesMax = {r: 0 for r in rounds} +roundGamesMin = {r: 0 for r in rounds} +getDayById = {d['id']: d for d in dayObjects} +getDayByDateTime = {} +getNiceDayRaw = {d['id']: d['day'] for d in dayObjects} +getNiceDay = {d['id']: d['day'] for d in dayObjects} +getWeekDay = {d['id']: '' for d in dayObjects} +getDateTimeDay = {d['id']: '' for d in dayObjects} +getRoundByDay = {d['id']: d['round'] for d in dayObjects if d['round'] > 0} +getDayMinGames = {d['id']: d['minGames'] for d in dayObjects if d['round'] > 0} +getDayMaxGames = {d['id']: d['maxGames'] for d in dayObjects if d['round'] > 0} +getRoundsByDay = {d['id']: [] for d in dayObjects} +nDerbies = {d['id']: [d['nDerbies']] for d in dayObjects} +roundDays = [] +roundDaysMin = {} +roundDaysMax = {} +wds = {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun'} +for d in dayObjects: + if d['round'] > 0: + getRoundsByDay[d['id']].append(d['round']) + getDays[d['round']].append(d['id']) + roundDays.append((d['round'], d['id'])) + roundDaysMin[(d['round'], d['id'])] = d['minGames'] + roundDaysMax[(d['round'], d['id'])] = d['maxGames'] + roundGamesMax[d['round']] = min( + nTeams/2, roundGamesMax[d['round']]+d['maxGames']) + roundGamesMin[d['round']] += d['minGames'] + ph = getPhaseOfRound[d['round']] + getDaysOfPhase[ph].append(d['id']) + if d['round2'] > 0: + getRoundsByDay[d['id']].append(d['round2']) + getDays[d['round2']].append(d['id']) + roundDays.append((d['round2'], d['id'])) + roundDaysMin[(d['round2'], d['id'])] = d['minGames2'] + roundDaysMax[(d['round2'], d['id'])] = d['maxGames2'] + roundGamesMax[d['round2']] = min( + nTeams/2, roundGamesMax[d['round2']]+d['maxGames2']) + roundGamesMin[d['round2']] += d['minGames2'] + + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getDayByDateTime[dt] = d['id'] + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +countries = list(set([t_country[t] for t in teams])) + +getWeekDaysPerRound = {r: [getWeekDay[d] for d in getDays[r]] for r in rounds} + +wd = {"Mondays": 0, "Tuesdays": 1, "Wednesdays": 2, + "Thursdays": 3, "Fridays": 4, "Saturdays": 5, "Sundays": 6} +t_site_bestTimeSlots = {(t, d): [] for t in realteams for d in days} +prio_weight = {"A": 0, "B": 50, "C": 100} + + +toTime = False + + +earliestDay = {r: getDays[r][0] for r in rounds} +latestDay = {r: getDays[r][0] for r in rounds} +for r in rounds: + for d in getDays[r]: + dt = getDateTimeDay[d] + if dt > getDateTimeDay[latestDay[r]]: + latestDay[r] = d + if dt < getDateTimeDay[earliestDay[r]]: + earliestDay[r] = d + + +# %% +basicTeams = teams +basicGames = [(t1, t2) + for (t1, t2) in games if t1 in basicTeams and t2 in basicTeams] + +nBasicTeams = len(basicTeams) +nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9) +if nBasicTeamsPerCluster % 2 == 1: + nBasicTeamsPerCluster += 1 + + +nRounds1 = nBasicTeamsPerCluster-1 +nBasicRounds = nPhases * (nBasicTeamsPerCluster-1) + +if nBasicTeamsPerCluster == 1 or nBasicRounds == 1: + nBasicRounds = nRounds + nRounds1 = nRounds + +basicRounds = range(1, nBasicRounds+1) + +print("nPhases ", nPhases) +print("nGameClusters ", len(gameClusters)) +print("nBasicTeamsPerCluster ", nBasicTeamsPerCluster) +print("nBasicTeams ", nBasicTeams) +print("nBasicRounds ", nBasicRounds) +print("nRounds1 ", nRounds1) + + +stretch = nRounds/nBasicRounds +# print ("regionalPatternUse " , regionalPatternUse) +rounds1 = range(1, nRounds1+1) +nGames = nTeams*nRounds1 + + +getBasicRound = {r: int((r-1)/stretch)+1 for r in rounds} +getRealRounds = {br: [r for r in rounds if getBasicRound[r] == br] + for br in basicRounds} +# print ("stretch : " , stretch) + +getBasicDays = {r: [] for r in basicRounds} +for r in rounds: + getBasicDays[getBasicRound[r]] += (getDays[r]) + + +getRoundDaysByDay = {d: [rd for rd in roundDays if rd[1] == d] for d in days} +getRoundDaysByRound = { + r: [rd for rd in roundDays if rd[0] == r] for r in rounds} + +daysSorted = [] +for dt in sorted([getDateTimeDay[d] for d in days]): + daysSorted.append(getDayByDateTime[dt]) + +minRest = {(t, s1, s2): thisSeason.minDaysBetweenGames for t in teams for s1 in [ + 'A', 'H'] for s2 in ['A', 'H']} +for t in teamObjects: + minRest[(t['id'], 'H', 'H')] = max( + minRest[(t['id'], 'H', 'H')], t['minRest_HH']) + minRest[(t['id'], 'H', 'A')] = max( + minRest[(t['id'], 'H', 'A')], t['minRest_HA']) + minRest[(t['id'], 'A', 'H')] = max( + minRest[(t['id'], 'A', 'H')], t['minRest_AH']) + minRest[(t['id'], 'A', 'A')] = max( + minRest[(t['id'], 'A', 'A')], t['minRest_AA']) + +maxMinRest = {t: max([minRest[(t, s1, s2)] for s1 in ['A', 'H'] + for s2 in ['A', 'H']]) for t in teams} + +excessGames = {rd: -roundDaysMax[rd] for rd in roundDays} +deficientGames = {rd: roundDaysMin[rd] for rd in roundDays} + +excessGames = {rd: max(0, excessGames[rd]) for rd in roundDays} +deficientGames = {rd: max(0, deficientGames[rd]) for rd in roundDays} + +# %% + + +allowed_weekdays = {'--': [0, 1, 2, 3, 4, 5, 6], 'Mondays': [0], 'Tuesdays': [1], 'Wednesdays': [2], 'Thursdays': [3], 'Fridays': [4], + 'Saturdays': [5], 'Sundays': [6], 'Weekdays': [0, 1, 2, 3, 4], 'Weekends': [5, 6], 'Mon.-Thu.': [0, 1, 2, 3], 'Fri.-Sun.': [4, 5, 6]} + +hawishes = HAWish.objects.filter( + scenario=s2, active=True).order_by('reason').values() +hawTeams = {} +hawDays = {} +hawTimes = {} +hawRounds = {} +hawRoundsString = {} +for c in HAWish.objects.filter(scenario=s2): + # print () + # print (c.reason ) + hawDays[c.id] = [] + hawTeams[c.id] = [] + hawTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + if c.selection_mode == 0: + for t in c.teams.filter(active=True): + hawTeams[c.id].append(t.id) + elif c.selection_mode == 1: + for t in c.groups.all(): + for t2 in t.teams.filter(active=True): + hawTeams[c.id].append(t2.id) + elif c.selection_mode == 2: + cids = [cn.id for cn in c.countries.all()] + for t in teams: + if t_globalCountry[t] in cids: + hawTeams[c.id].append(t) + + if c.multidate: + hawDays[c.id] = [dd.id for dd in c.dates.all()] + # print ("multidate") + else: + if c.day and not c.day2: + hawDays[c.id].append(c.day.id) + # print('+ ',getDayById[e['day_id']]) + + if not c.day and c.day2: + hawDays[c.id].append(c.day2.id) + # print('+ ',getDayById[e['day2_id']]) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + if dt.weekday() in allowed_weekdays[c.weekdays]: + # print (hawDays[e['id']]) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + if c.day and c.day2: + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + +print("processing encounters") +encwishes = EncWish.objects.filter(scenario=s2, active=True).values() +encTeams1 = {} +encTeams2 = {} +encGroups = {} +encGames = {} +encTeams1String = {} +encTeams2String = {} +encDays = {e['id']: [] for e in encwishes} +encDaySets = {} +encTimes = {} +encRounds = {e['id']: [] for e in encwishes} +encRoundsString = {e['id']: "" for e in encwishes} +for c in EncWish.objects.filter(scenario=s2, active=True): + encTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + encTeams1[c.id] = [] + encTeams2[c.id] = [] + encGroups[c.id] = [] + encTeams1String[c.id] = '' + encTeams2String[c.id] = '' + for t in c.encounterGroups.all(): + encGroups[c.id].append(t) + if c.useGroups: + for gr in c.teams1_groups.all(): + for t in gr.teams.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += gr.name + ', ' + for gr in c.teams2_groups.all(): + for t in gr.teams.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + else: + for t in c.teams1.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += t.name + ', ' + for t in c.teams2.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + encTeams1String[c.id] = encTeams1String[c.id][:-2] + encTeams2String[c.id] = encTeams2String[c.id][:-2] + + if c.useEncounterGroups: + tmp_games = [(t1.id, t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all( + ) for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2] + tmp_games += [(t2.id, t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() + for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2 and ec.symmetry] + encGames[c.id] = [tmp_games] + else: + elemHomeTeams = [encTeams1[c.id]] + if c.forEachTeam1: + elemHomeTeams = [[t] for t in encTeams1[c.id]] + elemAwayTeams = [encTeams2[c.id]] + if c.forEachTeam2: + elemAwayTeams = [[t] for t in encTeams2[c.id]] + encGames[c.id] = [] + # print ("NEW ENC ", elemHomeTeams,elemAwayTeams) + for elh in elemHomeTeams: + for ela in elemAwayTeams: + # print (" --- ENC ", elh,ela) + tmp_games = [(t1, t2) for t1 in elh for t2 in ela if t1 != t2] + if c.symmetry: + tmp_games += [(t1, t2) + for t1 in ela for t2 in elh if t1 != t2] + encGames[c.id].append(tmp_games) + + if c.multidate: + encDays[c.id] = [dd.id for dd in c.dates.all()] + else: + if c.day: + day1 = getDateTimeDay[c.day.id] + + if c.day and not c.day2: + encDays[c.id].append(c.day.id) + + if not c.day and c.day2: + encDays[c.id].append(c.day2.id) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + if c.day and c.day2: + # day1= parse(c.day.day) + # day2= parse(c.day2.day) + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + encDaySets[c.id] = [] + elemDays = [encDays[c.id]] + if c.forEachDay or c.forOneDay: + elemDays = [[d] for d in encDays[c.id]] + + lastDaySet = [] + for d in elemDays: + tmpDays = d + if (c.forEachDay or c.forOneDay) and c.timeframe != 0: + tmpDays = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if c.timeframe > 0: + day2 = day1 + datetime.timedelta(days=c.timeframe-1) + # print (e) + # print (day1, day2) + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and dt <= day2: + tmpDays.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and getRoundByDay[d3] < r1 + (-c.timeframe): + tmpDays.append(d3) + # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe']) + # for d4 in elemEncWishDays[cntr]: + # print (" - " ,getDayById[d4]['day']) + + if len([d for d in tmpDays if d not in lastDaySet]) > 0: + encDaySets[c.id].append(tmpDays) + lastDaySet = tmpDays + # print("-.--- NEW DAYS", tmpDays) + + for ds in encDaySets[c.id]: + for d3 in ds: + encRounds[c.id] += getRoundsByDay[d3] + encRounds[c.id] = sorted(list(set(encRounds[c.id]))) + for r in encRounds[c.id]: + encRoundsString[c.id] += str(r)+"_" + if encRoundsString[c.id] != "": + encRoundsString[c.id] = encRoundsString[c.id][:-1] + # print (encRoundsString[c.id] , " # " ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds) + + +onlyEarlyDays = [] +onlyLateDays = [] + + +alwaysConsiderAllGames = not mathModelName in [ + "Florida State League", "UEFA NL"] + + +elemEncWishes = {e['id']: [] for e in encwishes} +elemEncWishGames = {} +elemEncWishDays = {} + +cntr = 0 +for e in encwishes: + for eg in encGames[e['id']]: + for ed in encDaySets[e['id']]: + if len(eg) > 0 and len(ed) > 0: + cntr += 1 + elemEncWishes[e['id']].append(cntr) + elemEncWishGames[cntr] = eg + elemEncWishDays[cntr] = ed + + +# print (elemEncWishGames) +# print (elemEncWishDays) + + +encRelRoundsMin = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} +encRelRoundsMax = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} + +for enc in encwishes: + # print (e) + # print ("ENC !! " , enc['reason'] ) + for el in elemEncWishes[enc['id']]: + relDaysSet = set(elemEncWishDays[el]) + for r in basicRounds: + if len(relDaysSet.intersection(set(getBasicDays[r]))) > 0: + frac = len(relDaysSet.intersection( + set(getBasicDays[r]))) / len(getBasicDays[r]) + # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac) + if frac > 0: + encRelRoundsMin[el].append(r) + if frac == 1: + encRelRoundsMax[el].append(r) + +nElemEncWishes = sum([len(elemEncWishes[enc['id']]) for enc in encwishes]) + +# print (encRelRoundsMin) + +elemHaWishes = {e['id']: [] for e in hawishes} +elemHaWishTeams = {} +elemHaWishDays = {} +elemHaWishFirstDay = {} + +cntr = 1 +for e in hawishes: + elemTeams = [hawTeams[e['id']]] + if e['forEachTeam']: + elemTeams = [[t] for t in hawTeams[e['id']]] + + elemDays = [hawDays[e['id']]] + if e['forEachDay'] or e['forOneDay']: + elemDays = [[d] for d in hawDays[e['id']]] + + elemHaWishes[e['id']] = [] + allElemDays = [] + thisDaySet = [] + lastDaySet = [] + for d in elemDays: + # print (e) + if (e['forEachDay'] or e['forOneDay']) and e['timeframe'] != 1: + thisDaySet = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if e['timeframe'] > 1: + day2 = day1 + datetime.timedelta(days=e['timeframe']-1) + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and dt <= day2: + thisDaySet.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and getRoundByDay[d3] < r1 + (-e['timeframe']): + thisDaySet.append(d3) + print(" ROUND HA WISH ", e, thisDaySet, e['timeframe']) + else: + thisDaySet = d + + # only create wish id new day set is superset + if len([d for d in thisDaySet if d not in lastDaySet]) > 0: + for t in elemTeams: + cntr += 1 + elemHaWishes[e['id']].append(cntr) + elemHaWishTeams[cntr] = t + elemHaWishDays[cntr] = thisDaySet.copy() + elemHaWishFirstDay[cntr] = d[0] + lastDaySet = thisDaySet.copy() + allElemDays += thisDaySet + + hawRounds[e['id']] = [] + for d3 in set(allElemDays): + hawRounds[e['id']] += getRoundsByDay[d3] + hawRounds[e['id']] = sorted(list(set(hawRounds[e['id']]))) + hawRoundsString[e['id']] = "" + for r in hawRounds[e['id']]: + hawRoundsString[e['id']] += str(r)+"_" + if hawRoundsString[e['id']] != "": + hawRoundsString[e['id']] = hawRoundsString[e['id']][:-1] + +nElemHaWishes = sum([len(elemHaWishes[enc['id']]) for enc in hawishes]) + +gameAttractivity = {(t1, t2): attractivity[t1, t2] + for (t1, t2) in games if t1 < t2} + + +sorted_gameAttractivity = sorted( + gameAttractivity.items(), key=operator.itemgetter(1)) + +nGames = len(games) +topGames = [sorted_gameAttractivity[i] for i in range( + int(0.8*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] +goodGames = [sorted_gameAttractivity[i] for i in range( + int(0.6*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] + +broadcastingwishes = BroadcastingWish.objects.filter(scenario=s2) + + +nonBlocked = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +travelDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +hideDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +nonBlockedRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +travelRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +hideRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} + +for bl in blockings: + bl_val = 1.0 if bl['time'] == "----" else 0.5 + # if bl['time']!="----": + # print (bl, bl_val , getDayById[bl['day_id']] ,getDayById[bl['day_id']]['round'] ,getDayById[bl['day_id']]['round'] !=0 ) + if getDayById[bl['day_id']]['round'] != 0: + if bl['type'] in ["Home", "Hide"]: + nonBlocked[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + nonBlockedRounds[( + bl['team_id'], getDayById[bl['day_id']]['round'])] -= bl_val + # print ('FOUND HOME BLOCKING ', bl) + if bl['type'] in ["Away", "Hide"]: + travelDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + travelRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + if bl['type'] in ["Hide"]: + hideDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + hideRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + +noPlayRounds = {t: [r for r in rounds if nonBlockedRounds[( + t, r)] + travelRounds[(t, r)] == 0] for t in teams} +noHomeRounds = { + t: [r for r in rounds if nonBlockedRounds[(t, r)] == 0] for t in teams} +# print("nonBlockedRounds" ,nonBlockedRounds) +# print("noHomeRounds" ,noHomeRounds) +# print("noPlayRounds" ,noPlayRounds) + +playRounds = {t: sorted( + [r for r in rounds if hideRounds[(t, r)] > 0]) for t in teams} + + +# scale blocking of basic round to [0,1] +for t in teams: + for r in basicRounds: + if len(getBasicDays[r]) > 0: + nonBlocked[(t, r)] /= len(getBasicDays[r]) + travelDays[(t, r)] /= len(getBasicDays[r]) + if getTeamById[t] == "AX Armani Exchange Milan": + print(r, getRealRounds[r], + nonBlocked[(t, r)], nonBlocked[(t, r)]) + +for t in teams: + if mathModelName == "UEFA NL" and noPlayRounds[t] in [[3, 4]]: + t_usePhases[t] = False + print("No need for phases ", getTeamById[t]) + for t2 in opponents[t]: + t_usePhases[t2] = False + print(" -- also no need for phases ", getTeamById[t2]) + if getTeamById[t] == "AX Armani Exchange Milan": + t_usePhases[t] = False + print("setting t_usePhases of ", getTeamById[t], t_usePhases[t]) + +runPatternAssignmentFirst = False + + +chosenGames = [] + +useFullModel1 = False + +usePatterns = not mathModelName in ["NHL", "LNR"] +usePatterns = not mathModelName in ["NHL"] + + +balanceBreaks = mathModelName == "LNR" +haSymmetric = not mathModelName in ["NHL", "LNR"] +haSymmetric = not mathModelName in [ + "NHL", "Ligue 1", "Costa Rica Premier League"] +haSymmetric = not mathModelName in ["NHL"] +# haSymmetric = not mathModelName in ["NHL" , "LNR"] +if thisSeason.symmetry: + haSymmetric = True + +use2BreakPatterns = False + +if thisSeason.initBreaks >= 2: + use2BreakPatterns = True + +half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1 + + +prev_mirror_round = {r: 0 if r <= nRounds1 else r - + nRounds1+half_symmetry_offset for r in basicRounds} + + +for r in basicRounds: + if r > nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1): + prev_mirror_round[r] = int( + (r-1)/nRounds1-1)*nRounds1 + half_symmetry_offset+1-prev_mirror_round[r] % nRounds1 + + +getPhaseOfBasicRound = { + br: getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds} +getBasicRoundsOfPhase = {ph: [ + br for br in basicRounds if getPhaseOfBasicRound[br] == ph] for ph in phases} + +if use2BreakPatterns or nTeams > 200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams > 0: + useFullModel1 = True +useFullModel1 = True + + +preplan_phases = phases +preplan_phases = [0] +if not haSymmetric: + preplan_phases = phases + +fixedGamesRounds = [] + +starweight = {t: 0.0 for t in teams} +for t1 in teams: + for t2 in teams: + starweight[t1] += distance[getTeamById[t1], getTeamById[t2]] + +available_days = {(t, d): 1 for t in teams for d in days} + + +for bl in blockings: + if bl['type'] in ["Home", "Hide"] and bl['time'] == '----': + available_days[bl['team_id'], bl['day_id']] = 0 + +t_blocked_at = {(t, r): sum([available_days[t, d] + for d in getDays[r]]) == 0 for t in teams for r in rounds} + +# %% + +model2 = LpProblem( + "League_Scheduling_Model_--_Schedule_Games_"+str(thisScenario.id), LpMinimize) + +nextDay = {d: -1 for d in days} +previousDay = {d: -1 for d in days} + +# cntr=0 +lastDay = parse("01.01.1990") +for thisDay in sorted(getDayByDateTime.keys()): + if thisDay-lastDay == datetime.timedelta(days=1): + # cntr+=1 + d1 = getDayByDateTime[lastDay] + d2 = getDayByDateTime[thisDay] + nextDay[d1] = d2 + previousDay[d2] = d1 + # print ("next day of " , getNiceDay[d1] , ' ' , getNiceDay[d2]) + lastDay = thisDay + +# print (cntr) +# cntr=0 +# for d in days+higherLeagueDayIds : +# for d2 in days+higherLeagueDayIds: +# if getDateTimeDay[d2]-getDateTimeDay[d]==datetime.timedelta(days=1): +# cntr+=1 +# nextDay[d]=d2 +# previousDay[d2]=d +# print ("next day of " , getNiceDay[d] , ' ' , getNiceDay[d2]) +# print (cntr) + + +cntr = 0 +cntr2 = 0 + +# print ("games",games) +# print (roundDays) + +seedTV = [] +dontPlay = [] +dontPlayGames = [] + +for bl in blockings: + if bl['type'] == "Hide": + dontPlay += [(bl['team_id'], bl['day_id'])] + +dontPlay += [(t, d) for d in days for t in teams if getDayMaxGames[d] == 0] + + +x = {(t1, t2, rd): 0 for t1 in teams for t2 in teams for rd in roundDays} +for (t1, t2) in games: + for rd in roundDays: + x[(t1, t2, rd)] = 1 + +for (t1, t2, rd) in x.keys(): + if blocked_arena[(t1, rd[1], "----")] and runMode == 'Improve' and not thisSeason.allowBlockingViosInImprove: + # cntr +=1 + # print ("FORBIDDING") + + x[(t1, t2, rd)] = 0 + +for (t, d) in dontPlay: + for t3 in teams: + if not t3 in [t1, t2]: + for rd in getRoundDaysByDay[d]: + x[(t, t3, rd)] = 0 + x[(t3, t, rd)] = 0 + +for (t1, t2, d) in dontPlayGames: + for rd in getRoundDaysByDay[d]: + x[(t1, t2, rd)] = 0 + +attendance = {(t1, t2, d): 0 for (t1, t2) in games for d in days} + +# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +x_round = {(t1, t2, r): 0 for t1 in teams for t2 in teams for r in rounds} + +for (t1, t2) in games: + # for rd in roundDays: + # if x[(t1, t2, rd)] == 1: + # if not evalRun: + # x[(t1, t2, rd)] = LpVariable('x_'+str(t1)+'_'+str(t2)+'_' + + # str(rd[0])+'_'+str(rd[1]), lowBound=0, upBound=1, cat=LpContinuous) + # cntr += 1 + # else: + # cntr += len(roundDays) + + for r in rounds: + x_round[(t1, t2, r)] = LpVariable('x_round_'+str(t1)+'_' + + str(t2)+'_'+str(r), lowBound=0, upBound=1, cat=LpContinuous) + # model2 += x_round[(t1, t2, r)] == sum([x[(t1, t2, rd)] + # for rd in getRoundDaysByRound[r]]), f'game_{t1}_{t2}_sum_of_days_equals_round_{r}' + + +t_prev_mirror_round = {(t, r): 0 for t in teams for r in rounds} + +for t1 in teams: + phaseLength = int(len(playRounds[t1])/nPhases+0.5) + # print ("phaseLength", phaseLength) + for i in range(len(playRounds[t1])): + # print (i, phaseLength) + if i >= phaseLength: + # print ("setting " , playRounds[t1], playRounds[t1][i-phaseLength]) + # print ("setting " , playRounds[t1][i], "->",playRounds[t1][i-phaseLength]) + t_prev_mirror_round[(t1, playRounds[t1][i]) + ] = playRounds[t1][i-phaseLength] + # t_prev_mirror_round[(t,playRounds[t1][i])]=5 + + + +if not evalRun: + for (t1, t2) in games: + for r in rounds: + if r > nRounds1 and thisSeason.symmetry: + prev_round = prev_mirror_round[r] + if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]: + prev_round = t_prev_mirror_round[t1, r] + + if prev_round > 0: + model2 += x_round[(t1, t2, r) + ] == x_round[(t2, t1, prev_round)], f"symmetric_{r}_{prev_round}" + + +homeInRound = {(t1, r): LpVariable('homeInRound_'+str(t1)+'_'+str(r), + lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds} +awayInRound = {(t1, r): LpVariable('awayInRound_'+str(t1)+'_'+str(r), + lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds} +gameInBasicRound = {(t1, t2, r): LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound=0, + upBound=defaultGameRepetions, cat=LpContinuous) for (t1, t2) in games for r in basicRounds} +homeInBasicRound = {(t1, r): LpVariable('homeInBasicRound_'+str(t1)+'_'+str( + r), lowBound=0, cat=LpContinuous) for t1 in teams for r in basicRounds} +awayInBasicRound = {(t1, r): LpVariable('awayInBasicRound_'+str(t1)+'_'+str( + r), lowBound=0, cat=LpContinuous) for t1 in teams for r in basicRounds} +break3InRound = {(t1, r): LpVariable('break3InRound_'+str(t1)+'_'+str(r), + lowBound=0, cat=LpContinuous) for t1 in teams for r in rounds} +# breakInRound= {(t1,r) : LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = LpContinuous) for t1 in teams for r in rounds} +# breaksTotal = LpVariable('breaksTotal', lowBound = 0, cat = LpContinuous) +pairingVio = {(pair['id'], d): LpVariable('pairingVio_'+str(pair['id'])+'_' + + str(d), lowBound=0, cat=LpContinuous) for pair in pairings for d in days} +derbyMissing = {d: LpVariable( + 'derbyMissing_'+str(d), lowBound=0, cat=LpContinuous) for d in days} + +breakVio = {(br['id'], t): LpVariable('breakVio_' + str(br['id'])+'_' + + str(t), lowBound=0, cat=LpContinuous) for br in breaks for t in teams} +blockingVio = {bl['id']: LpVariable( + 'blockingVio_' + str(bl['id']), lowBound=0, cat=LpContinuous) for bl in blockings} +# blockingVioTotal = LpVariable('blockingVioTotal', lowBound = 0, cat = LpContinuous) + +hawVio = {haw['id']: LpVariable( + 'hawVio' + str(haw['id']), lowBound=0, cat=LpContinuous) for haw in hawishes} +HawVioTooLess = {el: LpVariable('havviotooless_' + str(el), lowBound=0, cat=LpContinuous) + for haw in hawishes for el in elemHaWishes[haw['id']]} +HawVioTooMuch = {el: LpVariable('havviotoomuch_' + str(el), lowBound=0, cat=LpContinuous) + for haw in hawishes for el in elemHaWishes[haw['id']]} +# HawVioTotal = LpVariable('HawVioTotal', lowBound = 0, cat = LpContinuous) +encVio = {enc['id']: LpVariable( + 'encVio' + str(enc['id']), lowBound=0, cat=LpContinuous) for enc in encwishes} +encVioTooLess = {el: LpVariable('encViotooless' + str(enc['id'])+"_"+str( + el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]} +encVioTooMuch = {el: LpVariable('encViotoomuch' + str(enc['id'])+"_"+str( + el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]} +# encVioTotal = LpVariable('encVioTotal', lowBound = 0, cat = LpContinuous) +# broadVio = { d : LpVariable('broadVio'+ str(d) , lowBound = 0, cat = LpContinuous) for d in days} +broadVioTm = {(b.id, r): LpVariable('broadVioTm_' + str(b.id) + "_" + str(r), + lowBound=0, cat=LpContinuous) for b in broadcastingwishes for r in rounds} +gamesTooClose2 = {(t, r): LpVariable('gamesTooClose2_' + str(t) + '_' + + str(r), lowBound=0, cat=LpContinuous) for t in teams for r in rounds} +missingGamesVio = {(t1, t2): LpVariable('missingGamesVio_' + str(t1) + + '_'+str(t2), lowBound=0, cat=LpContinuous) for (t1, t2) in games} + +home = {} +away = {} +home_time = {} +away_time = {} +away_in_cluster = {} +away_in_cluster_day = {} + +getRoundDaysByBasicRound = {br: [rd for r in getRealRounds[br] + for rd in getRoundDaysByRound[r]] for br in basicRounds} +getMaxGameOnRoundDaysByBasicRound = {br: sum( + roundDaysMax[rd] for r in getRealRounds[br] for rd in getRoundDaysByRound[r]) for br in basicRounds} + +for t in realteams: + for d in days: + away[t, d] = lpSum([x[(t2, t, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable( + 'away'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous) + home[t, d] = lpSum([x[(t, t2, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable( + 'home'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous) + + +# for t in realteams: +# for r in rounds: +# if thisSeason.gamesPerRound == "one day": +# model2 += homeInRound[(t, r)] == lpSum([x_round[(t, t2, r)] +# for t2 in opponents[t] ]), f'team_{t}_home_in_round_{r}' +# model2 += awayInRound[(t, r)] == lpSum([x_round[(t2, t, r)] +# for t2 in opponents[t] ]), f'team_{t}_away_in_round_{r}' +# # model2 += homeInRound[(t, r)] == lpSum([x[(t, t2, rd)] +# # for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_home_in_round_{r}' +# # model2 += awayInRound[(t, r)] == lpSum([x[(t2, t, rd)] +# # for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_away_in_round_{r}' + +# model2 += homeInRound[(t, r)] + awayInRound[(t, r)] <= 1, f'homeInR_awayInR_{t}_{r}' +# if r >= 3: +# model2 += break3InRound[(t, r)] + 2 >= homeInRound[(t, r-2)] + \ +# homeInRound[(t, r-1)]+homeInRound[(t, r)], f'team_{t}_home_break3_in_round_{r}' +# model2 += break3InRound[(t, r)] + 2 >= awayInRound[(t, r-2)] + \ +# awayInRound[(t, r-1)]+awayInRound[(t, r)], f'team_{t}_away_break3_in_round_{r}' + +# model2 += lpSum(break3InRound[(t,r)] for r in rounds) <= 1 + + + +breakVioBalance = {t: LpVariable( + 'breakVioBalance_'+str(t), lowBound=0, cat=LpContinuous) for t in realteams} +numBreaks = {t: lpSum([breakVio[(bl['id'], t)] + for bl in breaks]) for t in realteams} + +succBreaks = [bl for bl in breaks if bl['round1']+1 == bl['round2']] + +# for t in realteams: +# for bl in breaks: +# bl_id = bl['id'] +# model2 += breakVio[(bl['id'], t)] + 1 >= homeInRound[t, +# bl['round1']] + homeInRound[t, bl['round2']], f'breakvio_home_{bl_id}_for_{t}' +# model2 += breakVio[(bl['id'], t)] + 1 >= awayInRound[t, +# bl['round1']] + awayInRound[t, bl['round2']], f'breakvio_away_{bl_id}_for_{t}' + +getDays[0] = [] + + +use_currentSolution = False + +currentGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} + + +# if use_currentSolution and len(currentSolution) !=len(fixedGames): + +teamGameCntr = {(t, r): 0 for t in teams for r in rounds} + + +# print ("days " , days) +for d in days: + minG = sum([roundDaysMin[rd] - deficientGames[rd] + for rd in getRoundDaysByDay[d]]) + maxG = sum([roundDaysMax[rd] + excessGames[rd] + for rd in getRoundDaysByDay[d]]) + # model2 += lpSum([home[(t, d)] for t in realteams]) <= maxG, f'max_games_on_day_{d}' + + # if len(getRoundDaysByDay[d]) > 1: + # for rd in getRoundDaysByDay[d]: + # model2 += lpSum([x[(t1, t2, rd)] for (t1, t2) in games] + # ) <= roundDaysMax[rd] + excessGames[rd] + +print("rounds ", rounds, nRounds) +for r in rounds: + # one game a round for everyone + for t1 in realteams: + if thisSeason.gamesPerRound == "one day": + # cnstr = lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)] + # for t2 in opponents[t1] for rd in getRoundDaysByRound[r]]) + cnstr = lpSum([x_round[(t1, t2, r)] + x_round[(t2, t1, r)] + for t2 in opponents[t1]]) + if len(cnstr) > 0: + model2 += cnstr <= 1, f'one_game_in_round_{r}_for_{t1}' + +# exit(0) +print("realteams ", realteams) + + + + +print("taking care of right numbers of games ... ") +cntr = 0 + +if not specialGameControl: + for (t1, t2) in realgames: + if undirectedGameCntr[(t1, t2)] == 0: + # model2 += lpSum([x_round[(t1, t2, r)] for r in rounds] + # ) == gameCntr[(t1, t2)] - missingGamesVio[(t1, t2)], f'let_{t1}_{t2}_play_gameCntr_times' + model2 += lpSum([x_round[(t1, t2, r)] for r in rounds] + ) == gameCntr[(t1, t2)], f'let_{t1}_{t2}_play_gameCntr_times' + + missingGamesVio[(t1, t2)].upBound = max( + 0, gameCntr[(t1, t2)]+undirectedGameCntr[(t1, t2)] - currentGameCntr[(t1, t2)]) + + +# %% + +breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \ + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] + for bl in breaks for t in importantteams]) +break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds]) + +missingGamesVioTotal = lpSum( + [missingGamesVio[(t1, t2)] for (t1, t2) in basicGames]) + +standardObjectives = 0 \ + + missingGamesVioTotal\ + # + gew['Breaks']*breakVioTotal \ + # + gew['Breaks']*2*break3VioTotal \ + +model2 += standardObjectives + +for v in model2.variables(): + v.cat = LpInteger + + +with open ("model2_xpress.txt", "w") as f: + f.write(model2.__repr__()) + +# %% + +model2.solve(XPRESS(msg=1)) + +home_dict = {} +away_dict = {} +missing_games = [] +for key in x_round.keys(): + if type(x_round[key]) != int and x_round[key].value() > 0: + home_dict[key[2],key[0]] = key[1] + away_dict[key[2],key[1]] = key[0] +for key in missingGamesVio.keys(): + if type(missingGamesVio[key]) != int and missingGamesVio[key].value() > 0: + missing_games.append((t_shortname[split_key[0]],t_shortname[split_key[1]])) + + +sol = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for r in rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in teams: + tname = t_shortname[t] + sol += f"" + for r in rounds: + if (r,t) in home_dict.keys(): + opponent = t_shortname[home_dict[(r,t)]] + sol += f"" + elif (r,t) in away_dict.keys(): + opponent = t_shortname[away_dict[(r,t)]] + sol += f"" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{r}
{tname}{opponent}{opponent}
\n" +sol += "


\n" +for game in missing_games: + sol += f'{game}
\n' + + +with open('xpress_sol.html', 'w') as f: + f.write(sol) +# %% + +# API_KEY = 'AJwxeJRRZtJ9rOKVGTV6ruvG9TvC1wnE' + +# from quantagonia.qubo import QuboModel +# from quantagonia.enums import HybridSolverConnectionType +# from quantagonia.runner import Runner +# from quantagonia.runner_factory import RunnerFactory +# from quantagonia.spec_builder import QuboSolverType, QUBOSpecBuilder + + +# spec = QUBOSpecBuilder() +# spec.set_time_limit(time_limit=600) + +# runner = RunnerFactory.getRunner(HybridSolverConnectionType.CLOUD, api_key=API_KEY) + + +# # %% + +# qubo_prob = QuboModel() + +# qubos = {} +# for var in model2.variables(): +# qubos[str(var)] = qubo_prob.addVariable(f"{var}", initial=0) + + +# for var in model2.objective: +# qubo_prob.objective += -1*qubos[str(var)] + +# # one_game_in_round +# for ckey,cval in model2.constraints.items(): +# if cval.sense == -1: +# vars_in_cons = cval.toDict()['coefficients'] +# for q in itertools.combinations(vars_in_cons,2): +# qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']] + + +# # let_play_gameCntr_times +# for ckey,cval in model2.constraints.items(): +# if cval.sense == 0: +# qubo_prob.objective += 1 +# vars_in_cons = cval.toDict()['coefficients'] +# for var in vars_in_cons: +# qubo_prob.objective += -1*-1*qubos[var['name']] +# for q in itertools.combinations(vars_in_cons,2): +# qubo_prob.objective += -1*2*qubos[q[0]['name']]*qubos[q[1]['name']] + + +# with open('model2_qubo.txt','w') as f: +# f.write(str(qubo_prob)) +# status = qubo_prob.solve(spec.getd(), runner=runner) + + +# # %% + +# home_dict = {} +# away_dict = {} +# missing_games = [] +# for key in qubo_prob.vars.keys(): +# if qubo_prob.vars[key].eval() > 0: +# split_key = key.split('_') +# if split_key[0] == 'x': +# home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3]) +# away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2]) +# elif split_key[0] == 'missingGamesVio': +# missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])])) + + +# sol = " \ +# \ +# " +# sol += "\n" +# sol += "\n" +# sol += "" +# for r in rounds: +# sol += f"" +# sol += "" +# sol += "\n" +# sol += "\n" +# for t in teams: +# tname = t_shortname[t] +# sol += f"" +# for r in rounds: +# if (r,t) in home_dict.keys(): +# opponent = t_shortname[home_dict[(r,t)]] +# sol += f"" +# elif (r,t) in away_dict.keys(): +# opponent = t_shortname[away_dict[(r,t)]] +# sol += f"" +# else: +# sol += "" +# sol += "" +# sol += "\n" +# sol += "
{r}
{tname}{opponent}{opponent}
\n" +# sol += "


\n" +# for game in missing_games: +# sol += f'{game}
\n' + + +# with open('qubo_sol.html', 'w') as f: +# f.write(sol) + + +# exit() + +# %% + +from ortools.sat.python import cp_model +"""Minimal CP-SAT example to showcase calling the solver.""" +# Creates the model. +model = cp_model.CpModel() + +# Creates the variables. +# x = model.NewBoolVar('x') +# y = model.NewBoolVar('y') +# z = model.NewBoolVar('z') + +sat = {} +for var in model2.variables(): + sat[str(var)] = model.NewBoolVar(f'var') + +# %% + +for ckey,cval in model2.constraints.items(): + cons_dict = cval.toDict() + vars = cons_dict['coefficients'] + sense = cons_dict['sense'] + constant = cons_dict['constant'] + if sense == -1: + model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant <= 0) + elif sense == 0: + model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant == 0) + elif sense == 1: + model.Add(sum(var['value']*sat[var['name']] for var in vars) + constant >= 0) + + +objective_terms = [] +# for var in model2.objective.toDict(): +# objective_terms.append(var['value']*sat[var['name']]) +model.Minimize(sum(objective_terms)) + +# %% + +# # Creates the constraints. +# # one_game_in_round +# for ckey,cval in model2.constraints.items(): +# if cval.sense == -1: +# vars_in_cons = cval.toDict()['coefficients'] +# model.AddAtMostOne(sat[i['name']] for i in vars_in_cons) +# # for q in itertools.combinations(vars_in_cons,2): +# # qubo_prob.objective += -1*qubos[q[0]['name']]*qubos[q[1]['name']] + +# # let_play_gameCntr_times +# for ckey,cval in model2.constraints.items(): +# if cval.sense == 0: +# vars_in_cons = cval.toDict()['coefficients'] +# model.AddExactlyOne(sat[i['name']] for i in vars_in_cons) + + +# objective_terms = [] +# for var in model2.objective: +# objective_terms.append(sat[str(var)]) +# model.Minimize(sum(objective_terms)) + +# %% + +print("STARTING SAT-SOLVER") +# Creates a solver and solves the model. +solver = cp_model.CpSolver() +solver.parameters.log_search_progress = True +print("STARTING SAT-SOLVER") +status = solver.Solve(model) + + +# %% + +home_dict = {} +away_dict = {} +missing_games = [] +for key in sat.keys(): + if solver.BooleanValue(sat[key]): + split_key = key.split('_') + if split_key[0] == 'x': + home_dict[int(split_key[4]),int(split_key[2])] = int(split_key[3]) + away_dict[int(split_key[4]),int(split_key[3])] = int(split_key[2]) + elif split_key[0] == 'missingGamesVio': + missing_games.append((t_shortname[int(split_key[1])],t_shortname[int(split_key[2])])) + + +sol = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for r in rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in teams: + tname = t_shortname[t] + sol += f"" + for r in rounds: + if (r,t) in home_dict.keys(): + opponent = t_shortname[home_dict[(r,t)]] + sol += f"" + elif (r,t) in away_dict.keys(): + opponent = t_shortname[away_dict[(r,t)]] + sol += f"" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{r}
{tname}{opponent}{opponent}
\n" +sol += "


\n" +for game in missing_games: + sol += f'{game}
\n' + + +with open('sat_sol.html', 'w') as f: + f.write(sol) +# %% +exit() + +if not evalRun: + for (t1, t2) in realgames: + # every pair plays each other in each phase once + for p in phases: + # phase violation in last phase possible if it contained more rounds than necessary + if p < len(phases)-1 or True: + if t_usePhases[t1] or t_usePhases[t2]: + if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]: + relDays = [] + phaseLength = int(len(playRounds[t1])/nPhases+0.5) + for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: + relDays += getDays[rr] + print("adding days of round ", rr, " to phase ", p) + else: + relDays = getDaysOfPhase[p] + model2 += lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)] + for d in relDays for rd in getRoundDaysByDay[d]]) <= 1 + # print (len(relDays),"reldays") + # print (getTeamById[t1],getTeamById[t2], sum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] )) + +if not evalRun: + for t in realteams: + if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases: + for p in phases: + rds = [r for r in rounds if getPhaseOfRound[r] == p] + nblocked = [r for r in noHomeRounds[t] if r in rds] + print(p, len(rds), len(nblocked), + rds, nblocked, getTeamById[t]) + for p in phases: + phaseLength = int(len(playRounds[t])/nPhases+0.5) + model2 += lpSum([home[(t, d)] + for d in getDaysOfPhase[p]]) <= int(phaseLength/2+1) + + if thisSeason.minRoundsBetweenGameOfSameTeams > 0: + for r in rounds: + if r + thisSeason.minRoundsBetweenGameOfSameTeams <= nRounds: + rds = [] + for r2 in range(r, r+thisSeason.minRoundsBetweenGameOfSameTeams+1): + rds += getRoundDaysByRound[r2] + rds2 = [] + for r2 in range(r, r+int(0.5*thisSeason.minRoundsBetweenGameOfSameTeams)): + rds2 += getRoundDaysByRound[r2] + # print ("ONLY ONE IN " , rds) + # print ("ONLY ONE IN " , rds2) + for t1 in realteams: + for t2 in realteams: + if t1 < t2 and (gameCntr[(t1, t2)]*gameCntr[(t2, t1)] > 0 or x_round[(t1, t2, r)] != 0): + # model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r] + model2 += sum([(x[(t1, t2, rd)]+x[(t2, t1, rd)]) + for rd in rds]) <= 1 + + +if not evalRun: + # max length home stands /trips + for r in range(1, nRounds-thisSeason.maxTourLength+1): + # print (" at least one home and away in rounds ") + # for r3 in range(r,r+thisSeason.maxTourLength+1): + # print (r3 ) + for t in teams: + if t not in noBreakLimitTeams: + # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1 + model2 += lpSum([awayInRound[t, r2] for r2 in range(r, r + + thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength + model2 += lpSum([homeInRound[t, r2] for r2 in range(r, r + + thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength + +print("check 1") + +# blockings +for bl in blockings: + if getDayById[bl['day_id']]['round'] != 0: + if thisSeason.useFeatureKickOffTime and bl['time'] != '----': + if bl['type'] in ["Home", "Hide"]: + model2 += blockingVio[bl['id'] + ] == home_time[bl['team_id'], bl['day_id'], bl['time']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2 += blockingVio[bl['id'] + ] == away_time[bl['team_id'], bl['day_id'], bl['time']] + # print ('FOUND AWAY BLOCKING ', bl) + else: + if bl['type'] in ["Home", "Hide"]: + model2 += blockingVio[bl['id'] + ] == home[bl['team_id'], bl['day_id']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2 += blockingVio[bl['id'] + ] == away[bl['team_id'], bl['day_id']] + # print ('FOUND AWAY BLOCKING ', bl) + + +print("check 2") + +hawOneVio = {} +hawForOneNotViolated = {} + + +def getStringFromSet(ss): + s2 = "" + for s in ss: + s2 += str(s)+"_" + return s2[:-1] + + + # hawishes +for haw in hawishes: + print(haw['reason']) + for el in elemHaWishes[haw['id']]: + if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']]) > 0: + if haw['homeAway'] == 'Home': + relGames = lpSum([home_time[t, d, tm] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + # print (haw['id'] ," haw : ", relGames, hawTimes[haw['id']]) + elif haw['homeAway'] == 'Away': + relGames = lpSum([away_time[t, d, tm] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + else: + # print(haw,el) + relGames = lpSum([home_time[t, d, tm] + away_time[t, d, tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \ + - lpSum([x_time[(t1, t2, rd, tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']] + for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd, tm) in x_time.keys()]) + + else: + if haw['homeAway'] == 'Home': + relGames = lpSum([home[t, d] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el]]) + elif haw['homeAway'] == 'Away': + relGames = lpSum([away[t, d] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el]]) + else: + relGames = lpSum([home[t, d] + away[t, d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1, t2, rd] for d in elemHaWishDays[el] + for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd) in x.keys()]) + if haw['minGames'] > 0: + model2 += relGames >= haw['minGames'] - HawVioTooLess[el] + # print ("adding min ha constraint") + if haw['maxGames'] >= 0: + model2 += relGames <= haw['maxGames'] + HawVioTooMuch[el] + # print ("adding max ha constraint") + + usedConstraintNames = [""] + if haw['forOneDay']: + # print (haw['forOneDay'], hawDays[haw['id']]) + # print ([el for el in elemHaWishes[haw['id']] ]) + # for el in elemHaWishes[haw['id']] : + # print("- " , elemHaWishDays[el] , elemHaWishTeams[el] ) + # print ([ (el, elemHaWishFirstDay[el]) for el in elemHaWishes[haw['id']] ]) + relTeamString = {el: getStringFromSet( + elemHaWishTeams[el]) for el in elemHaWishes[haw['id']]} + relTeams = set([relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print (relTeams) + for rt in relTeams: + rtname = rt + if len(rt) > 50: + scname = "" + while scname in usedConstraintNames: + ttt = bytes(rt + ''.join(random.choice(string.ascii_lowercase) + for i in range(10)), 'utf-8') + scname = hashlib.sha224(ttt).hexdigest() + rtname = scname[:10] + usedConstraintNames.append(rtname) + # print ("use rtname to encode wishes ", rtname) + + hawOneVio[(haw['id'], rt)] = LpVariable( + 'hawOneVio_'+str(haw['id'])+"_"+rtname, cat=LpContinuous) + for fd in hawDays[haw['id']]: + relWishes = [el for el in elemHaWishes[haw['id']] + if elemHaWishFirstDay[el] == fd] + # relWishes = [el for el in elemHaWishes[haw['id']] ] + # print (" -" , relWishes , [elemHaWishDays[el] for el in relWishes]) + hawForOneNotViolated[(haw['id'], rt, fd)] = 0 + if len(relWishes) > 0: + hawForOneNotViolated[(haw['id'], rt, fd)] = LpVariable( + 'hawForOneViolated_'+str(haw['id'])+"_"+rtname+"_"+str(fd), cat=LpBinary) + model2 += lpSum(HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el] + == rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'], rt, fd)]) + model2 += lpSum(hawForOneNotViolated[(haw['id'], rt, fd)] + for fd in hawDays[haw['id']]) >= haw['forOneDayNum']-hawOneVio[(haw['id'], rt)] + model2 += lpSum(hawOneVio[(haw['id'], rt)] + for rt in relTeams) == hawVio[haw['id']] + else: + # print (haw['forOneDay'], hawDays[haw['id']]) + model2 += hawVio[haw['id']] == lpSum(HawVioTooMuch[el]+HawVioTooLess[el] + for el in elemHaWishes[haw['id']]) + + if haw['prio'] == "Hard" and "HardHAWishesNotBreakable" in special_wishes_active: + model2 += hawVio[haw['id']] == 0 + print("WISH HARD", haw['reason']) + + +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +# print (hawDays) +# print (elemHaWishFirstDay) + +encOneVio = {} +encForOneNotViolated = {} +print("check 3") +# encwishes +for enc in encwishes: + print(enc) + for el in elemEncWishes[enc['id']]: + # print (enc) + # model2+= encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']] + + if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']]) > 0: + if enc['minGames'] > 0: + model2 += encVioTooLess[el] >= enc['minGames'] - sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for ( + t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games]) + if enc['maxGames'] >= 0: + # if enc['maxGames']==0: + print(enc['reason'], ' ', elemEncWishDays[el], ' ', enc['time'], + ' ', encTeams1[enc['id']], ' ', encTeams2[enc['id']]) + # print (sum([x_time[(t1,t2,rd, enc['time'])] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])) + model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for ( + t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games]) + else: + if enc['minGames'] > 0: + model2 += encVioTooLess[el] >= enc['minGames'] - sum([x[t1, t2, rd] for d in elemEncWishDays[el] + for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games]) + if enc['maxGames'] >= 0: + # if enc['maxGames']==0: + # print (enc['reason'], ' ', encDays[enc['id']], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x[t1, t2, rd] for d in elemEncWishDays[el] + for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games]) + + if enc['forOneDay']: + for ed in encDaySets[enc['id']]: + if len(ed) > 0: + relWishes = [el for el in elemEncWishes[enc['id']] + if elemEncWishDays[el] == ed] + print("###", ed, relWishes) + encForOneNotViolated[(enc['id'], ed[0])] = LpVariable( + 'encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=LpBinary) + model2 += sum(encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * ( + 1-encForOneNotViolated[(enc['id'], ed[0])]) + model2 += sum(encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len( + ed) > 0) >= enc['forOneDayNum']-encVio[enc['id']] + else: + model2 += encVio[enc['id']] == sum(encVioTooMuch[el]+encVioTooLess[el] + for el in elemEncWishes[enc['id']]) + + if enc['prio'] == "Hard" and "HardEncWishesNotBreakable" in special_wishes_active: + model2 += encVio[enc['id']] == 0 + print("WISH HARD", enc['reason']) + + +print("check 4") + + +# return "" +weekdayHomePref = {} +dayHomePref = {} +for t in teams: + tm = getTeamByName[getTeamById[t]] + weekdayHomePref[(t, 'Mon')] = tm['home_pref_mo'] + weekdayHomePref[(t, 'Tue')] = tm['home_pref_tu'] + weekdayHomePref[(t, 'Wed')] = tm['home_pref_we'] + weekdayHomePref[(t, 'Thu')] = tm['home_pref_th'] + weekdayHomePref[(t, 'Fri')] = tm['home_pref_fr'] + weekdayHomePref[(t, 'Sat')] = tm['home_pref_sa'] + weekdayHomePref[(t, 'Sun')] = tm['home_pref_su'] + for d in days: + dayHomePref[(t, d)] = weekdayHomePref[(t, getWeekDay[d])] + + +maxTravelDistance = max([distanceById[t1, t2] + for t1 in realteams for t2 in realteams]) +maxTravelDistance = max(1, maxTravelDistance) + +singleTripWeight = 50 +breakImbalanceTotal = lpSum([breakVioBalance[t] for t in realteams]) +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +HawVioTotal = lpSum([prioVal[haw['prio']] * hawVio[haw['id']] + for haw in hawishes]) +encVioTotal = lpSum([prioVal[enc['prio']] * encVio[enc['id']] + for enc in encwishes]) +seedVioTotal = lpSum([100*prioVal[enc['prio']] * encVio[enc['id']] + for enc in encwishes if enc['reason'] == "Seed Game"]) +# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes]) +broadVioTotal = lpSum([10 * broadVioTm[(b.id, r)] + for b in broadcastingwishes for r in rounds]) +breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \ + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] + for bl in breaks for t in importantteams]) +break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds]) +tooManyTop4InRowTotal = lpSum([10*tooManyTop4InRow[(t, r)] + for t in teams for r in rounds]) +pairingVioTotal = lpSum([5 * prioVal[pair['prio']] * pairingVio[(pair['id'], d)] + for pair in pairings for d in days]) +# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Home"]) +# print (blockings) +# for bl in blockings: +# if bl['type'] in ["Home"]: +# print (blocked_arena[(bl["team_id"],bl["day_id"],"----")], getTeamById[bl["team_id"]], getNiceDay[bl["day_id"]] , bl ) + +fulfBlocks = set([(bl["team_id"], getRoundByDay[bl["day_id"]]) for bl in blockings if bl['type'] in [ + "Home"] and blocked_arena[(bl["team_id"], bl["day_id"], "----")]]) + +blockingVioTotal2 = lpSum([-30 * homeInRound[tr] + for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode == 'Improve']) +blockingVioTotal = lpSum([100 * blockingVio[bl['id']] + for bl in blockings if bl['type'] in ["Home", "Hide"]]) + blockingVioTotal2 + +travelVioTotal = lpSum([100 * blockingVio[bl['id']] + for bl in blockings if bl['type'] == "Away"]) +derbiesMissingTotal = lpSum([30 * derbyMissing[d] for d in days]) +unpreferredTotal = lpSum( + [home[t, d] for t in teams for d in days if dayHomePref[(t, d)] == 0]) +# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions]) +missingGamesVioTotal = lpSum( + [2000000 * missingGamesVio[(t1, t2)] for (t1, t2) in realgames]) +# TODO - UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games]) +oldScenGamesTotal = lpSum([wg * x[t1, t2, rd] for (t1, t2, r, wg) + in otherScenGames for rd in getRoundDaysByRound[r]]) +totalAttendance = lpSum([attendance[(t1, t2, d)] * x[t1, t2, (r, d)] + for (t1, t2) in games for (r, d) in roundDays]) + +specialObjectives = 0 +specialWishItems = {sw: [] for sw in special_wishes_active} +specialWishVio = {} + + +optCameraMovement = "Standard" + +# tvkitproblem = {} +move2 = {} +newtrip2 = {} + + +standardObjectives = 1+gew['Home-/Away']*HawVioTotal\ + + gew['Home-/Away']*3*unpreferredTotal \ + + gew['Pairings']*pairingVioTotal \ + + gew['Blockings']*blockingVioTotal \ + + gew['Traveling']*travelVioTotal \ + + gew['Breaks']*breakVioTotal \ + + gew['Breaks']*2*break3VioTotal \ + + gew['Breaks']*1*breakImbalanceTotal \ + + 5*gew['Encounters']*encVioTotal \ + + 5*gew['Encounters']*seedVioTotal \ + + gew['Broadcasting']*broadVioTotal \ + + gew['Derbies']*derbiesMissingTotal \ + + 1.0*tooManyTop4InRowTotal\ + + missingGamesVioTotal\ + + oldScenGamesTotal\ + - 0.01*totalAttendance + + +model2 += standardObjectives + +# %% +script = thisSeason.optimizationScript +if script == '': + if useBasicGames: + script += "HEURISTIC\n" + script += "GAME;1-"+str(nRounds)+";0.1;200\n" + if thisSeason.groupBased: + for c in allConferences: + if not c.regional and c.teams.count() <= 12: + script += "GROUP;1-"+str(nRounds)+";0.05;30;"+c.name+"\n" + for cr in range(nPhases): + minIntRound = min((cr) * nRoundsPerPhase+1, nRounds) + maxIntRound = min((cr+1)*nRoundsPerPhase, nRounds) + + +script = script.replace('\r', '') +print("script") +print(script) +optSteps = [st.split(';') for st in script.split("\n")] +print(optSteps) + +# %% + +print("Model built now solving .... ") + +nRuns = 1 +maxSolveTime = 300 + +if thisSeason.groupBased: + maxSolveTime = 40 + +mipgap = 0.01 + +# print ("######## Testing") +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + + +use_LP_heuristic = False +print(runMode == 'New', useBasicGames, runPatternAssignmentFirst) + +nRuns = nPhases +# maxSolveTime = 120 +mipgap = 0.0 +# mipgap=0.95 + +# nRuns =1 +onlyReopt = True +onlyReopt = False +onlyFewTrips = False +singleTripWeight = 10 + +if onlyReopt: + nRuns = 0 + +maxIntRound = nRounds + + +missing_imp = [] +cntr = 0 + +for st in optSteps: + print(" - ", st) + cntr += 1 + if runMode == 'New' and st[0] == "HARDCONSTRAINTS": + for bl in blockings: + blockingVio[bl['id']].upBound = 0 + print("blocking tightened : ", + getTeamById[bl['team_id']], bl['day_id']) + for enc in encwishes: + if enc['reason'] == "Seed Game": + encVio[enc['id']].upBound = 0 + + if runMode == 'New' and len(st) >= 1 and st[0] in ["PATTERNS", "HOMEAWAY", "BASICGAME", "GAME", "GAMES", "GROUP", "TRIPS", "LP-HEURISTIC"]: + newRounds = [] + print() + optTarget = st[0] + if len(st) > 1: + for rf in st[1].split(","): + rr = rf.split("-") + if len(rr) == 1: + newRounds.append(min(nRounds, int(rr[0]))) + else: + for ii in range(int(rr[0]), min(nRounds, int(rr[1])+1)): + newRounds.append(ii) + newRoundsString = st[1] + else: + newRounds = rounds + newRoundsString = "1-"+str(nRounds) + + optsteptime = maxSolveTime + optstepgap = mipgap + + if len(st) >= 3 and st[2] != "": + optstepgap = float(st[2]) + if len(st) >= 4 and st[3] != "": + optsteptime = float(st[3]) + + print(newRounds) + + if st[0] == "HOMEAWAY": + for t in teams: + for r in newRounds: + homeInRound[(t, r)].cat = LpInteger + + if st[0] == "BASICGAME": + for (t1, t2) in games: + for r in newRounds: + gameInBasicRound[(t1, t2, r)].cat = LpInteger + + if st[0] in ["GAME", "GAMES"]: + for (t, t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t, t2, rd)]) + + print('########################') + print('# SOLVING MODEL '+optTarget+' FOR ROUNDS ' + newRoundsString + + ' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' #') + print('########################') + +# if solver == "CBC": +# model2.solve(PULP_CBC_CMD(fracGap=optstepgap, +# maxSeconds=optsteptime, threads=8, msg=1)) +# elif solver == "Gurobi": +# model2.solve(GUROBI(MIPGap=optstepgap, +# TimeLimit=optsteptime, msg=1, Method=2, NodeMethod=2)) +# else: +# # for debugging: +# # with open ("model2.txt", "w") as f: +# # f.write(model2.__repr__()) +# model2.solve(XPRESS(msg=1, targetGap=optstepgap, maxSeconds=optsteptime, options=[ +# "THREADS=12,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + +# if model2.status < 0: +# print("Status: ", model2.status) + +# if not lowerBoundFound: +# lowerBoundFound = value(model2.objective) + +# cntr_rnd = 0 + +# if st[0] == "HOMEAWAY": +# print(teams) +# print(newRounds) +# for t in teams: +# for r in newRounds: +# if homeInRound[(t, r)].value() > 0.9: +# print('fixing home ' + str(r) + ' : ' + +# getTeamById[t] + " " + str(homeInRound[(t, r)].value())) +# homeInRound[(t, r)].lowBound = 1 +# else: +# homeInRound[(t, r)].upBound = 0 + +# if st[0] == "BASICGAME": +# for r in newRounds: +# for (t1, t2) in games: +# if getTeamById[t1] != "-" and getTeamById[t2] != "-" and (t1, t2, r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1, t2, r)]) != int: +# if getVal(gameInBasicRound[(t1, t2, r)]) > 0.9: +# gameInBasicRound[(t1, t2, r)].lowBound = 1 +# else: +# gameInBasicRound[(t1, t2, r)].upBound = 0 + +# if st[0] in ["GAME", "GAMES"]: +# feedback = "Optimize games...." +# missing_imp = [] + +# for (t1, t2) in realgames: +# if missingGamesVio[(t1, t2)].value() > 0.9: +# feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str( +# missingGamesVio[(t1, t2)].value()) + '
\n' +# missing_imp += [(1, nRounds, [t1, t2], 50)] + +# print(missing_imp) +# print(feedback) +# print("number of assigned games : ", sum( +# [getVal(x[ttrd]) for ttrd in x.keys()])) + +# for (t1, t2) in games: +# for r in newRounds: +# for rd in getRoundDaysByRound[r]: +# if getVal(x[(t1, t2, rd)]) > 0.9: +# setLB(x[(t1, t2, rd)], 1) +# print("fixing ", t1, t2, rd, +# x[(t1, t2, rd)].lowBound) +# else: +# setUB(x[(t1, t2, rd)], 0) + +# for r in basicRounds: +# for t1 in realteams: +# homeInBasicRound[(t1, r)].lowBound = 0 +# homeInBasicRound[(t1, r)].upBound = 10 +# awayInBasicRound[(t1, r)].lowBound = 0 +# awayInBasicRound[(t1, r)].upBound = 10 +# for t2 in opponents[t1]: +# if (t1, t2) in games: +# gameInBasicRound[(t1, t2, r)].cat = LpContinuous +# gameInBasicRound[(t1, t2, r)].lowBound = 0 + +# for t in realteams: +# for r in newRounds: +# homeInRound[(t, r)].cat = LpContinuous +# for t2 in opponents[t]: +# for rd in getRoundDaysByRound[r]: +# if (t, t2) in games and getVal(x[(t, t2, rd)]) > 0.9: +# setLB(x[(t, t2, rd)], x[(t, t2, rd)].value()) +# currentSolution = [(t1, t2, r, d) for (t1, t2) in realgames for ( +# r, d) in roundDays if getVal(x[(t1, t2, (r, d))]) > 0.9] + + +# for r in rounds: +# for t in teams: +# homeInRound[(t, r)].lowBound = 0 +# homeInRound[(t, r)].upBound = 1 + +# for (t1, t2) in games: +# for r in rounds: +# for rd in getRoundDaysByRound[r]: +# makeIntVar(x[(t1, t2, rd)]) + +# print('Solved Again') +# print('NOW REOPT') + +# # %% + +# %% diff --git a/machine_learning/scripts/qubo/qubo_model2_from_notebook.py b/machine_learning/scripts/qubo/qubo_model2_from_notebook.py new file mode 100755 index 0000000..b176364 --- /dev/null +++ b/machine_learning/scripts/qubo/qubo_model2_from_notebook.py @@ -0,0 +1,1936 @@ +# %% +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") + + +# XPRESS ENVIRONMENT +os.environ['XPRESSDIR'] = "/opt/xpressmp_8.4" +os.environ['XPRESS'] = "/opt/xpressmp_8.4/bin" +os.environ['LD_LIBRARY_PATH'] = os.environ['XPRESSDIR']+"/lib:"+os.environ['LD_LIBRARY_PATH'] +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['PYTHONPATH'] +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.environ['CLASSPATH'] +os.environ['CLASSPATH'] =os.environ['XPRESSDIR']+"/lib/xprm.jar:"+os.environ['CLASSPATH'] +os.environ['PATH'] =os.environ['XPRESSDIR']+"/bin:"+os.environ['PATH'] + + +from leagues import settings +settings.DATABASES['default']['NAME'] = PROJECT_PATH+'/db.sqlite3' + +import django +django.setup() + +from scheduler.models import * +import operator +from scheduler.solver.functions import * +from common.functions import distanceInKmByGPS +import pandas as pd +import numpy as np +from django.db.models import Count, F, Value +from pulp import * + + +def makeIntVar(v): + if type(v) != int: + v.cat = LpInteger + # print ("int var ", v) + + +def setStart(v, vl): + if type(v) != int: + vl = float(vl) + v.start = vl + + +def setLB(v, vl): + if type(v) != int: + vl = float(vl) + v.lowBound = vl + # print ("set lb ", v , " = ", vl) + + +def setUB(v, vl): + if type(v) != int: + vl = float(vl) + v.upBound = vl + # print ("set ub ", v , " = ", vl) + + +def getVal(v): + if type(v) == int: + return v + else: + return v.value() + + +user_is_staff = True +runMode = 'New' +localsearch_time = 0 +solver = '?' + +s2 = 3 +thisScenario = Scenario.objects.get(id=s2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + + +mathModelName = thisLeague.name +if thisSeason.optimizationParameters != "": + mathModelName = thisSeason.optimizationParameters + +# %% + + +otherScenGames = [] +if thisSeason.improvementObjective != "--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective == "stick to old solutions": + impObjWeight = -5 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append( + (int(g[1]), int(g[2]), int(g[3]), impObjWeight)) + +getGlobalCountry = {gc['uefa']: gc['id'] + for gc in GlobalCountry.objects.values()} + +teamObjects = Team.objects.filter( + season=thisSeason, active=True).order_by('position').values() +teams = [] +realteams = [] +faketeams = [] +importantteams = [] +shortteams = [] +t_pos = {} +t_pot = {} +t_color = {} +t_country = {} +t_globalCountry = {} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity = {} +getTeamById = {} +getTeamIdByName = {} +getTeamIdByShortName = {} +getTeamByName = {} +getStadiumById = {} +teamByShort = {} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']] = t['name'] + getTeamIdByShortName[t['shortname']] = t['id'] + if t['name'] != '-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']] = t['country'] + t_globalCountry[t['id']] = t['globalCountry_id'] + if not t_globalCountry[t['id']] and t_country[t['id']] in getGlobalCountry.keys(): + t_globalCountry[t['id']] = getGlobalCountry[t_country[t['id']]] + t_pos[t['name']] = t['position'] + t_color[t['name']] = "lightyellow" + t_pot[t['id']] = t['pot'] + t_stadium[t['id']] = t['stadium'] + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_shortname[t['id']] = t['shortname'] + t_usePhases[t['id']] = thisScenario.usePhases + getTeamById[t['id']] = t['name'] + getStadiumById[t['id']] = t['stadium'] + getTeamIdByName[t['name']] = t['id'] + getTeamByName[t['name']] = t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient: + top4.append(t['id']) + +inactive_teams = [] +for t in Team.objects.filter(season=thisSeason, active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']] = t['attractivity'] + t_lon[t['id']] = t['longitude'] + t_lat[t['id']] = t['latitude'] + t_country[t['id']] = t['country'] + getTeamById[t['id']] = t['name'] + + +lowerBoundFound = False +distance = {} +attractivity = {} +distanceById = {} +distanceInDaysById = {} +stadium_names = set([t['stadium'] for t in teamObjects if t['stadium'] != '']) +stadium_id = {} +stadium_name = {} +sid = 0 +for stadium in stadium_names: + stadium_name[sid] = stadium + stadium_id[stadium] = sid + sid += 1 +stadiums = list(stadium_name.keys()) +teamsOfStadium = {st: [] for st in stadiums} + +travelDict = thisSeason.travelDict() +for t1 in teamObjects: + if t1['stadium'] != '': + teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'], t2['name'] + ] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'], t2['id']] = distance[t1['name'], t2['name']] + distanceInDaysById[t1['id'], t2['id']] = int( + distance[t1['name'], t2['name']]/350 + 0.99) + attractivity[t1['id'], t2['id']] = int( + 100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [d['id'] for d in dayObjects if d['round'] > 0] + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +higherTeamObjects = Team.objects.filter( + season__in=higherSeasons, active=True).values() +higherDayObjects = Day.objects.filter( + season__in=higherSeasons, maxGames__gte=1).values() +higherTeams = [t['id'] for t in higherTeamObjects] +higherTeamsOf = {t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']] = t['name'] + t_country[t['id']] = t['country'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print("Teams : ", teams) +print("VIP Teams : ", importantteams) +print("TOP Teams : ", top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums = ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t: 0 for t in teams} +conf_teams = {} +for c in Conference.objects.filter(scenario=s2, regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name] = [] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id] == 0 or len(cteams) < len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id] = c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +prioVal = {'A': 25, 'B': 5, 'C': 1, 'Hard': 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name] = prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name] = sw.float1 + sw_float2[sw.name] = sw.float2 + sw_int1[sw.name] = sw.int1 + sw_int2[sw.name] = sw.int2 + sw_text[sw.name] = sw.text +special_wishes = list(sw_prio.keys()) +special_wishes_active = [sw for sw in special_wishes if sw_prio[sw] > 0] + +print(special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active: + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print(noBreakLimitTeams) + noBreakLimitTeams = [ + t for t in teams if getTeamById[t] in noBreakLimitTeams] + print(noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter( + scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).values() +pairings_tmp3 = Pairing.objects.filter( + scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).values() +pairings = [] +for p in [p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams] + [p for p in pairings_tmp2] + [p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) +breaks = Break.objects.filter(season=thisSeason).values() +blockings_tmp = Blocking.objects.filter(scenario=s2, active=True).values() +blockings = [bl for bl in blockings_tmp if bl['team_id'] in teams] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']): t['name'] for t in timeObjects} +getIdByTime = {t['name']: str(t['id']) for t in timeObjects} + +blocked_arena = { + (t, d, tm): False for t in teams for d in days for tm in ["----"] + times} +hidden_arena = {(t, d, tm): False for t in teams for d in days for tm in [ + "----"] + times} +nBlockingHome = 0 +nBlockingAway = 0 + +for bl in blockings: + if bl['type'] in ["Hide"]: + hidden_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + + if bl['type'] in ["Home", "Hide"]: + nBlockingHome += 1 + blocked_arena[(bl['team_id'], bl['day_id'], bl['time'])] = True + else: + nBlockingAway += 1 + +nTeams = len(teams) +nPhases = thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} +undirectedGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} + +for t1 in teams: + for t2 in teams: + if t1 != t2: + gameCntr[(t1, t2)] += nPhases/2 + +for (t1, t2) in gameCntr.keys(): + if gameCntr[(t1, t2)] % 1 != 0: + undirectedGameCntr[(t1, t2)] = 1 + gameCntr[(t1, t2)] = int(gameCntr[(t1, t2)]) + +games = [gm for gm in gameCntr.keys() if gameCntr[gm] + + undirectedGameCntr[gm] > 0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter( + scenario=s2).values() + +gew = {} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']] = ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips'] > gew['Breaks']: + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl = mathModelName in ["Florida State League"] + +if len(games) == 0: + games = [(t1, t2) for t1 in teams for t2 in teams if t1 != t2] + specialGameControl = True + +realgames = [(t1, t2) for (t1, t2) in games if getTeamById[t1] + != "-" and getTeamById[t2] != "-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = (runMode == "Improve" and localsearch_time == 0 + and len(thisScenario.solutionlist()) == sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames])) + +opponents = {t: set([]) for t in realteams} + +for (t1, t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) + +# %% + +gmClusters = range(1, nTeams+2) + +if nPhases > 0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl: + gmClusters = range(1, 2) + +print("gmClusters", gmClusters) + + +gameClusterTeams = {c: [t for t in teams] for c in gmClusters} +gameClusters = [c for c in gameClusterTeams.keys() if len( + gameClusterTeams[c]) > 0] +biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters]) + +# %% +nPhases = min([gameCntr[(t1, t2)] + gameCntr[(t2, t1)] + + undirectedGameCntr[(t1, t2)] for (t1, t2) in realgames]) +tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize == "None" else int( + thisSeason.tripStartHeuristicGroupsize) +defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2 +nPhases = max(1, int(nPhases/defaultGameRepetions)) +phases = range(nPhases) + +useBasicGames = nPhases > 0 + +if not useBasicGames: + print('no basic games but biggest group size ', + biggestGroupSize, ' in nPhases ', nPhases) + +nRounds = thisSeason.nRounds +rounds = range(1, nRounds+1) + +# nRoundsPerPhase= 1 +# if nPhases>0: +nRoundsPerPhase = int(nRounds/nPhases) + +print('nRounds ', nRounds) +print('nPhases ', nPhases) +print('nRoundsPerPhase ', nRoundsPerPhase) +print('defaultGameRepetions ', defaultGameRepetions) +print('tripStartHeuristicGroupsize ', tripStartHeuristicGroupsize) + +getPhaseOfRound = {r: min(nPhases-1, int((r-1)/nRoundsPerPhase)) + for r in rounds} +getDaysOfPhase = {p: [] for p in phases} +getDays = {r: [] for r in rounds} +roundGamesMax = {r: 0 for r in rounds} +roundGamesMin = {r: 0 for r in rounds} +getDayById = {d['id']: d for d in dayObjects} +getDayByDateTime = {} +getNiceDayRaw = {d['id']: d['day'] for d in dayObjects} +getNiceDay = {d['id']: d['day'] for d in dayObjects} +getWeekDay = {d['id']: '' for d in dayObjects} +getDateTimeDay = {d['id']: '' for d in dayObjects} +getRoundByDay = {d['id']: d['round'] for d in dayObjects if d['round'] > 0} +getDayMinGames = {d['id']: d['minGames'] for d in dayObjects if d['round'] > 0} +getDayMaxGames = {d['id']: d['maxGames'] for d in dayObjects if d['round'] > 0} +getRoundsByDay = {d['id']: [] for d in dayObjects} +nDerbies = {d['id']: [d['nDerbies']] for d in dayObjects} +roundDays = [] +roundDaysMin = {} +roundDaysMax = {} +wds = {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun'} +for d in dayObjects: + if d['round'] > 0: + getRoundsByDay[d['id']].append(d['round']) + getDays[d['round']].append(d['id']) + roundDays.append((d['round'], d['id'])) + roundDaysMin[(d['round'], d['id'])] = d['minGames'] + roundDaysMax[(d['round'], d['id'])] = d['maxGames'] + roundGamesMax[d['round']] = min( + nTeams/2, roundGamesMax[d['round']]+d['maxGames']) + roundGamesMin[d['round']] += d['minGames'] + ph = getPhaseOfRound[d['round']] + getDaysOfPhase[ph].append(d['id']) + if d['round2'] > 0: + getRoundsByDay[d['id']].append(d['round2']) + getDays[d['round2']].append(d['id']) + roundDays.append((d['round2'], d['id'])) + roundDaysMin[(d['round2'], d['id'])] = d['minGames2'] + roundDaysMax[(d['round2'], d['id'])] = d['maxGames2'] + roundGamesMax[d['round2']] = min( + nTeams/2, roundGamesMax[d['round2']]+d['maxGames2']) + roundGamesMin[d['round2']] += d['minGames2'] + + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getDayByDateTime[dt] = d['id'] + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +countries = list(set([t_country[t] for t in teams])) + +getWeekDaysPerRound = {r: [getWeekDay[d] for d in getDays[r]] for r in rounds} + +wd = {"Mondays": 0, "Tuesdays": 1, "Wednesdays": 2, + "Thursdays": 3, "Fridays": 4, "Saturdays": 5, "Sundays": 6} +t_site_bestTimeSlots = {(t, d): [] for t in realteams for d in days} +prio_weight = {"A": 0, "B": 50, "C": 100} + + +toTime = False + + +earliestDay = {r: getDays[r][0] for r in rounds} +latestDay = {r: getDays[r][0] for r in rounds} +for r in rounds: + for d in getDays[r]: + dt = getDateTimeDay[d] + if dt > getDateTimeDay[latestDay[r]]: + latestDay[r] = d + if dt < getDateTimeDay[earliestDay[r]]: + earliestDay[r] = d + + +# %% +basicTeams = teams +basicGames = [(t1, t2) + for (t1, t2) in games if t1 in basicTeams and t2 in basicTeams] + +nBasicTeams = len(basicTeams) +nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9) +if nBasicTeamsPerCluster % 2 == 1: + nBasicTeamsPerCluster += 1 + + +nRounds1 = nBasicTeamsPerCluster-1 +nBasicRounds = nPhases * (nBasicTeamsPerCluster-1) + +if nBasicTeamsPerCluster == 1 or nBasicRounds == 1: + nBasicRounds = nRounds + nRounds1 = nRounds + +basicRounds = range(1, nBasicRounds+1) + +print("nPhases ", nPhases) +print("nGameClusters ", len(gameClusters)) +print("nBasicTeamsPerCluster ", nBasicTeamsPerCluster) +print("nBasicTeams ", nBasicTeams) +print("nBasicRounds ", nBasicRounds) +print("nRounds1 ", nRounds1) + + +stretch = nRounds/nBasicRounds +# print ("regionalPatternUse " , regionalPatternUse) +rounds1 = range(1, nRounds1+1) +nGames = nTeams*nRounds1 + + +getBasicRound = {r: int((r-1)/stretch)+1 for r in rounds} +getRealRounds = {br: [r for r in rounds if getBasicRound[r] == br] + for br in basicRounds} +# print ("stretch : " , stretch) + +getBasicDays = {r: [] for r in basicRounds} +for r in rounds: + getBasicDays[getBasicRound[r]] += (getDays[r]) + + +getRoundDaysByDay = {d: [rd for rd in roundDays if rd[1] == d] for d in days} +getRoundDaysByRound = { + r: [rd for rd in roundDays if rd[0] == r] for r in rounds} + +daysSorted = [] +for dt in sorted([getDateTimeDay[d] for d in days]): + daysSorted.append(getDayByDateTime[dt]) + +minRest = {(t, s1, s2): thisSeason.minDaysBetweenGames for t in teams for s1 in [ + 'A', 'H'] for s2 in ['A', 'H']} +for t in teamObjects: + minRest[(t['id'], 'H', 'H')] = max( + minRest[(t['id'], 'H', 'H')], t['minRest_HH']) + minRest[(t['id'], 'H', 'A')] = max( + minRest[(t['id'], 'H', 'A')], t['minRest_HA']) + minRest[(t['id'], 'A', 'H')] = max( + minRest[(t['id'], 'A', 'H')], t['minRest_AH']) + minRest[(t['id'], 'A', 'A')] = max( + minRest[(t['id'], 'A', 'A')], t['minRest_AA']) + +maxMinRest = {t: max([minRest[(t, s1, s2)] for s1 in ['A', 'H'] + for s2 in ['A', 'H']]) for t in teams} + +excessGames = {rd: -roundDaysMax[rd] for rd in roundDays} +deficientGames = {rd: roundDaysMin[rd] for rd in roundDays} + +excessGames = {rd: max(0, excessGames[rd]) for rd in roundDays} +deficientGames = {rd: max(0, deficientGames[rd]) for rd in roundDays} + +# %% + + +allowed_weekdays = {'--': [0, 1, 2, 3, 4, 5, 6], 'Mondays': [0], 'Tuesdays': [1], 'Wednesdays': [2], 'Thursdays': [3], 'Fridays': [4], + 'Saturdays': [5], 'Sundays': [6], 'Weekdays': [0, 1, 2, 3, 4], 'Weekends': [5, 6], 'Mon.-Thu.': [0, 1, 2, 3], 'Fri.-Sun.': [4, 5, 6]} + +hawishes = HAWish.objects.filter( + scenario=s2, active=True).order_by('reason').values() +hawTeams = {} +hawDays = {} +hawTimes = {} +hawRounds = {} +hawRoundsString = {} +for c in HAWish.objects.filter(scenario=s2): + # print () + # print (c.reason ) + hawDays[c.id] = [] + hawTeams[c.id] = [] + hawTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + if c.selection_mode == 0: + for t in c.teams.filter(active=True): + hawTeams[c.id].append(t.id) + elif c.selection_mode == 1: + for t in c.groups.all(): + for t2 in t.teams.filter(active=True): + hawTeams[c.id].append(t2.id) + elif c.selection_mode == 2: + cids = [cn.id for cn in c.countries.all()] + for t in teams: + if t_globalCountry[t] in cids: + hawTeams[c.id].append(t) + + if c.multidate: + hawDays[c.id] = [dd.id for dd in c.dates.all()] + # print ("multidate") + else: + if c.day and not c.day2: + hawDays[c.id].append(c.day.id) + # print('+ ',getDayById[e['day_id']]) + + if not c.day and c.day2: + hawDays[c.id].append(c.day2.id) + # print('+ ',getDayById[e['day2_id']]) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + if dt.weekday() in allowed_weekdays[c.weekdays]: + # print (hawDays[e['id']]) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + if c.day and c.day2: + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + +print("processing encounters") +encwishes = EncWish.objects.filter(scenario=s2, active=True).values() +encTeams1 = {} +encTeams2 = {} +encGroups = {} +encGames = {} +encTeams1String = {} +encTeams2String = {} +encDays = {e['id']: [] for e in encwishes} +encDaySets = {} +encTimes = {} +encRounds = {e['id']: [] for e in encwishes} +encRoundsString = {e['id']: "" for e in encwishes} +for c in EncWish.objects.filter(scenario=s2, active=True): + encTimes[c.id] = [str(dd.id) for dd in c.timeslots.all()] + encTeams1[c.id] = [] + encTeams2[c.id] = [] + encGroups[c.id] = [] + encTeams1String[c.id] = '' + encTeams2String[c.id] = '' + for t in c.encounterGroups.all(): + encGroups[c.id].append(t) + if c.useGroups: + for gr in c.teams1_groups.all(): + for t in gr.teams.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += gr.name + ', ' + for gr in c.teams2_groups.all(): + for t in gr.teams.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + else: + for t in c.teams1.filter(active=True): + encTeams1[c.id].append(t.id) + encTeams1String[c.id] += t.name + ', ' + for t in c.teams2.filter(active=True): + encTeams2[c.id].append(t.id) + encTeams2String[c.id] += t.name + ', ' + encTeams1String[c.id] = encTeams1String[c.id][:-2] + encTeams2String[c.id] = encTeams2String[c.id][:-2] + + if c.useEncounterGroups: + tmp_games = [(t1.id, t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all( + ) for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2] + tmp_games += [(t2.id, t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() + for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1 != t2 and ec.symmetry] + encGames[c.id] = [tmp_games] + else: + elemHomeTeams = [encTeams1[c.id]] + if c.forEachTeam1: + elemHomeTeams = [[t] for t in encTeams1[c.id]] + elemAwayTeams = [encTeams2[c.id]] + if c.forEachTeam2: + elemAwayTeams = [[t] for t in encTeams2[c.id]] + encGames[c.id] = [] + # print ("NEW ENC ", elemHomeTeams,elemAwayTeams) + for elh in elemHomeTeams: + for ela in elemAwayTeams: + # print (" --- ENC ", elh,ela) + tmp_games = [(t1, t2) for t1 in elh for t2 in ela if t1 != t2] + if c.symmetry: + tmp_games += [(t1, t2) + for t1 in ela for t2 in elh if t1 != t2] + encGames[c.id].append(tmp_games) + + if c.multidate: + encDays[c.id] = [dd.id for dd in c.dates.all()] + else: + if c.day: + day1 = getDateTimeDay[c.day.id] + + if c.day and not c.day2: + encDays[c.id].append(c.day.id) + + if not c.day and c.day2: + encDays[c.id].append(c.day2.id) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + if c.day and c.day2: + # day1= parse(c.day.day) + # day2= parse(c.day2.day) + day1 = getDateTimeDay[c.day.id] + day2 = getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if day1 <= dt and dt <= day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + encDaySets[c.id] = [] + elemDays = [encDays[c.id]] + if c.forEachDay or c.forOneDay: + elemDays = [[d] for d in encDays[c.id]] + + lastDaySet = [] + for d in elemDays: + tmpDays = d + if (c.forEachDay or c.forOneDay) and c.timeframe != 0: + tmpDays = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if c.timeframe > 0: + day2 = day1 + datetime.timedelta(days=c.timeframe-1) + # print (e) + # print (day1, day2) + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and dt <= day2: + tmpDays.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and getRoundByDay[d3] < r1 + (-c.timeframe): + tmpDays.append(d3) + # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe']) + # for d4 in elemEncWishDays[cntr]: + # print (" - " ,getDayById[d4]['day']) + + if len([d for d in tmpDays if d not in lastDaySet]) > 0: + encDaySets[c.id].append(tmpDays) + lastDaySet = tmpDays + # print("-.--- NEW DAYS", tmpDays) + + for ds in encDaySets[c.id]: + for d3 in ds: + encRounds[c.id] += getRoundsByDay[d3] + encRounds[c.id] = sorted(list(set(encRounds[c.id]))) + for r in encRounds[c.id]: + encRoundsString[c.id] += str(r)+"_" + if encRoundsString[c.id] != "": + encRoundsString[c.id] = encRoundsString[c.id][:-1] + # print (encRoundsString[c.id] , " # " ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds) + + +onlyEarlyDays = [] +onlyLateDays = [] + + +alwaysConsiderAllGames = not mathModelName in [ + "Florida State League", "UEFA NL"] + + +elemEncWishes = {e['id']: [] for e in encwishes} +elemEncWishGames = {} +elemEncWishDays = {} + +cntr = 0 +for e in encwishes: + for eg in encGames[e['id']]: + for ed in encDaySets[e['id']]: + if len(eg) > 0 and len(ed) > 0: + cntr += 1 + elemEncWishes[e['id']].append(cntr) + elemEncWishGames[cntr] = eg + elemEncWishDays[cntr] = ed + + +# print (elemEncWishGames) +# print (elemEncWishDays) + + +encRelRoundsMin = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} +encRelRoundsMax = {el: [] + for enc in encwishes for el in elemEncWishes[enc['id']]} + +for enc in encwishes: + # print (e) + # print ("ENC !! " , enc['reason'] ) + for el in elemEncWishes[enc['id']]: + relDaysSet = set(elemEncWishDays[el]) + for r in basicRounds: + if len(relDaysSet.intersection(set(getBasicDays[r]))) > 0: + frac = len(relDaysSet.intersection( + set(getBasicDays[r]))) / len(getBasicDays[r]) + # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac) + if frac > 0: + encRelRoundsMin[el].append(r) + if frac == 1: + encRelRoundsMax[el].append(r) + +nElemEncWishes = sum([len(elemEncWishes[enc['id']]) for enc in encwishes]) + +# print (encRelRoundsMin) + +elemHaWishes = {e['id']: [] for e in hawishes} +elemHaWishTeams = {} +elemHaWishDays = {} +elemHaWishFirstDay = {} + +cntr = 1 +for e in hawishes: + elemTeams = [hawTeams[e['id']]] + if e['forEachTeam']: + elemTeams = [[t] for t in hawTeams[e['id']]] + + elemDays = [hawDays[e['id']]] + if e['forEachDay'] or e['forOneDay']: + elemDays = [[d] for d in hawDays[e['id']]] + + elemHaWishes[e['id']] = [] + allElemDays = [] + thisDaySet = [] + lastDaySet = [] + for d in elemDays: + # print (e) + if (e['forEachDay'] or e['forOneDay']) and e['timeframe'] != 1: + thisDaySet = [] + # day1= parse(getDayById[d[0]]['day']) + day1 = getDateTimeDay[d[0]] + if e['timeframe'] > 1: + day2 = day1 + datetime.timedelta(days=e['timeframe']-1) + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt = getDateTimeDay[d3] + if day1 <= dt and dt <= day2: + thisDaySet.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + dt = getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1 <= dt and getRoundByDay[d3] < r1 + (-e['timeframe']): + thisDaySet.append(d3) + print(" ROUND HA WISH ", e, thisDaySet, e['timeframe']) + else: + thisDaySet = d + + # only create wish id new day set is superset + if len([d for d in thisDaySet if d not in lastDaySet]) > 0: + for t in elemTeams: + cntr += 1 + elemHaWishes[e['id']].append(cntr) + elemHaWishTeams[cntr] = t + elemHaWishDays[cntr] = thisDaySet.copy() + elemHaWishFirstDay[cntr] = d[0] + lastDaySet = thisDaySet.copy() + allElemDays += thisDaySet + + hawRounds[e['id']] = [] + for d3 in set(allElemDays): + hawRounds[e['id']] += getRoundsByDay[d3] + hawRounds[e['id']] = sorted(list(set(hawRounds[e['id']]))) + hawRoundsString[e['id']] = "" + for r in hawRounds[e['id']]: + hawRoundsString[e['id']] += str(r)+"_" + if hawRoundsString[e['id']] != "": + hawRoundsString[e['id']] = hawRoundsString[e['id']][:-1] + +nElemHaWishes = sum([len(elemHaWishes[enc['id']]) for enc in hawishes]) + +gameAttractivity = {(t1, t2): attractivity[t1, t2] + for (t1, t2) in games if t1 < t2} + + +sorted_gameAttractivity = sorted( + gameAttractivity.items(), key=operator.itemgetter(1)) + +nGames = len(games) +topGames = [sorted_gameAttractivity[i] for i in range( + int(0.8*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] +goodGames = [sorted_gameAttractivity[i] for i in range( + int(0.6*0.5*nGames), min(len(sorted_gameAttractivity), int(0.5*nGames)))] + +broadcastingwishes = BroadcastingWish.objects.filter(scenario=s2) + + +nonBlocked = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +travelDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +hideDays = {(t, r): len(getBasicDays[r]) for r in basicRounds for t in teams} +nonBlockedRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +travelRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} +hideRounds = {(t, r): len(getDays[r]) for r in rounds for t in teams} + +for bl in blockings: + bl_val = 1.0 if bl['time'] == "----" else 0.5 + # if bl['time']!="----": + # print (bl, bl_val , getDayById[bl['day_id']] ,getDayById[bl['day_id']]['round'] ,getDayById[bl['day_id']]['round'] !=0 ) + if getDayById[bl['day_id']]['round'] != 0: + if bl['type'] in ["Home", "Hide"]: + nonBlocked[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + nonBlockedRounds[( + bl['team_id'], getDayById[bl['day_id']]['round'])] -= bl_val + # print ('FOUND HOME BLOCKING ', bl) + if bl['type'] in ["Away", "Hide"]: + travelDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + travelRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + if bl['type'] in ["Hide"]: + hideDays[( + bl['team_id'], getBasicRound[getDayById[bl['day_id']]['round']])] -= bl_val + hideRounds[(bl['team_id'], getDayById[bl['day_id']] + ['round'])] -= bl_val + # print ('FOUND AWAY BLOCKING ', bl) + +noPlayRounds = {t: [r for r in rounds if nonBlockedRounds[( + t, r)] + travelRounds[(t, r)] == 0] for t in teams} +noHomeRounds = { + t: [r for r in rounds if nonBlockedRounds[(t, r)] == 0] for t in teams} +# print("nonBlockedRounds" ,nonBlockedRounds) +# print("noHomeRounds" ,noHomeRounds) +# print("noPlayRounds" ,noPlayRounds) + +playRounds = {t: sorted( + [r for r in rounds if hideRounds[(t, r)] > 0]) for t in teams} + + +# scale blocking of basic round to [0,1] +for t in teams: + for r in basicRounds: + if len(getBasicDays[r]) > 0: + nonBlocked[(t, r)] /= len(getBasicDays[r]) + travelDays[(t, r)] /= len(getBasicDays[r]) + if getTeamById[t] == "AX Armani Exchange Milan": + print(r, getRealRounds[r], + nonBlocked[(t, r)], nonBlocked[(t, r)]) + +for t in teams: + if mathModelName == "UEFA NL" and noPlayRounds[t] in [[3, 4]]: + t_usePhases[t] = False + print("No need for phases ", getTeamById[t]) + for t2 in opponents[t]: + t_usePhases[t2] = False + print(" -- also no need for phases ", getTeamById[t2]) + if getTeamById[t] == "AX Armani Exchange Milan": + t_usePhases[t] = False + print("setting t_usePhases of ", getTeamById[t], t_usePhases[t]) + +runPatternAssignmentFirst = False + + +chosenGames = [] + +useFullModel1 = False + +usePatterns = not mathModelName in ["NHL", "LNR"] +usePatterns = not mathModelName in ["NHL"] + + +balanceBreaks = mathModelName == "LNR" +haSymmetric = not mathModelName in ["NHL", "LNR"] +haSymmetric = not mathModelName in [ + "NHL", "Ligue 1", "Costa Rica Premier League"] +haSymmetric = not mathModelName in ["NHL"] +# haSymmetric = not mathModelName in ["NHL" , "LNR"] +if thisSeason.symmetry: + haSymmetric = True + +use2BreakPatterns = False + +if thisSeason.initBreaks >= 2: + use2BreakPatterns = True + +half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1 + + +prev_mirror_round = {r: 0 if r <= nRounds1 else r - + nRounds1+half_symmetry_offset for r in basicRounds} + + +for r in basicRounds: + if r > nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1): + prev_mirror_round[r] = int( + (r-1)/nRounds1-1)*nRounds1 + half_symmetry_offset+1-prev_mirror_round[r] % nRounds1 + + +getPhaseOfBasicRound = { + br: getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds} +getBasicRoundsOfPhase = {ph: [ + br for br in basicRounds if getPhaseOfBasicRound[br] == ph] for ph in phases} + +if use2BreakPatterns or nTeams > 200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams > 0: + useFullModel1 = True +useFullModel1 = True + + +preplan_phases = phases +preplan_phases = [0] +if not haSymmetric: + preplan_phases = phases + +fixedGamesRounds = [] + +starweight = {t: 0.0 for t in teams} +for t1 in teams: + for t2 in teams: + starweight[t1] += distance[getTeamById[t1], getTeamById[t2]] + +available_days = {(t, d): 1 for t in teams for d in days} + + +for bl in blockings: + if bl['type'] in ["Home", "Hide"] and bl['time'] == '----': + available_days[bl['team_id'], bl['day_id']] = 0 + +t_blocked_at = {(t, r): sum([available_days[t, d] + for d in getDays[r]]) == 0 for t in teams for r in rounds} + +# %% + +model2 = LpProblem( + "League_Scheduling_Model_--_Schedule_Games_"+str(thisScenario.id), LpMinimize) + + +nextDay = {d: -1 for d in days} +previousDay = {d: -1 for d in days} + +# cntr=0 +lastDay = parse("01.01.1990") +for thisDay in sorted(getDayByDateTime.keys()): + if thisDay-lastDay == datetime.timedelta(days=1): + # cntr+=1 + d1 = getDayByDateTime[lastDay] + d2 = getDayByDateTime[thisDay] + nextDay[d1] = d2 + previousDay[d2] = d1 + # print ("next day of " , getNiceDay[d1] , ' ' , getNiceDay[d2]) + lastDay = thisDay + +# print (cntr) +# cntr=0 +# for d in days+higherLeagueDayIds : +# for d2 in days+higherLeagueDayIds: +# if getDateTimeDay[d2]-getDateTimeDay[d]==datetime.timedelta(days=1): +# cntr+=1 +# nextDay[d]=d2 +# previousDay[d2]=d +# print ("next day of " , getNiceDay[d] , ' ' , getNiceDay[d2]) +# print (cntr) + + +cntr = 0 +cntr2 = 0 + +# print ("games",games) +# print (roundDays) + +seedTV = [] +dontPlay = [] +dontPlayGames = [] + +for bl in blockings: + if bl['type'] == "Hide": + dontPlay += [(bl['team_id'], bl['day_id'])] + +dontPlay += [(t, d) for d in days for t in teams if getDayMaxGames[d] == 0] + + +x = {(t1, t2, rd): 0 for t1 in teams for t2 in teams for rd in roundDays} +for (t1, t2) in games: + for rd in roundDays: + x[(t1, t2, rd)] = 1 + +for (t1, t2, rd) in x.keys(): + if blocked_arena[(t1, rd[1], "----")] and runMode == 'Improve' and not thisSeason.allowBlockingViosInImprove: + # cntr +=1 + # print ("FORBIDDING") + + x[(t1, t2, rd)] = 0 + +for (t, d) in dontPlay: + for t3 in teams: + if not t3 in [t1, t2]: + for rd in getRoundDaysByDay[d]: + x[(t, t3, rd)] = 0 + x[(t3, t, rd)] = 0 + +for (t1, t2, d) in dontPlayGames: + for rd in getRoundDaysByDay[d]: + x[(t1, t2, rd)] = 0 + +attendance = {(t1, t2, d): 0 for (t1, t2) in games for d in days} + +# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +x_round = {(t1, t2, r): 0 for t1 in teams for t2 in teams for r in rounds} + +for (t1, t2) in games: + for rd in roundDays: + if x[(t1, t2, rd)] == 1: + if not evalRun: + x[(t1, t2, rd)] = LpVariable('x_'+str(t1)+'_'+str(t2)+'_' + + str(rd[0])+'_'+str(rd[1]), lowBound=0, upBound=1, cat=LpContinuous) + cntr += 1 + else: + cntr += len(roundDays) + + for r in rounds: + x_round[(t1, t2, r)] = LpVariable('x_round_'+str(t1)+'_' + + str(t2)+'_'+str(r), lowBound=0, upBound=1, cat=LpContinuous) + model2 += x_round[(t1, t2, r)] == sum([x[(t1, t2, rd)] + for rd in getRoundDaysByRound[r]]), f'game_{t1}_{t2}_sum_of_days_equals_round_{r}' + + +t_prev_mirror_round = {(t, r): 0 for t in teams for r in rounds} + +for t1 in teams: + phaseLength = int(len(playRounds[t1])/nPhases+0.5) + # print ("phaseLength", phaseLength) + for i in range(len(playRounds[t1])): + # print (i, phaseLength) + if i >= phaseLength: + # print ("setting " , playRounds[t1], playRounds[t1][i-phaseLength]) + # print ("setting " , playRounds[t1][i], "->",playRounds[t1][i-phaseLength]) + t_prev_mirror_round[(t1, playRounds[t1][i]) + ] = playRounds[t1][i-phaseLength] + # t_prev_mirror_round[(t,playRounds[t1][i])]=5 + + + +if not evalRun: + for (t1, t2) in games: + for r in rounds: + if r > nRounds1 and thisSeason.symmetry: + prev_round = prev_mirror_round[r] + if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]: + prev_round = t_prev_mirror_round[t1, r] + + if prev_round > 0: + model2 += x_round[(t1, t2, r) + ] == x_round[(t2, t1, prev_round)], f"symmetric_{r}_{prev_round}" + + +homeInRound = {(t1, r): LpVariable('homeInRound_'+str(t1)+'_'+str(r), + lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds} +awayInRound = {(t1, r): LpVariable('awayInRound_'+str(t1)+'_'+str(r), + lowBound=0, upBound=1, cat=LpContinuous) for t1 in teams for r in rounds} +gameInBasicRound = {(t1, t2, r): LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound=0, + upBound=defaultGameRepetions, cat=LpContinuous) for (t1, t2) in games for r in basicRounds} +homeInBasicRound = {(t1, r): LpVariable('homeInBasicRound_'+str(t1)+'_'+str( + r), lowBound=0, cat=LpContinuous) for t1 in teams for r in basicRounds} +awayInBasicRound = {(t1, r): LpVariable('awayInBasicRound_'+str(t1)+'_'+str( + r), lowBound=0, cat=LpContinuous) for t1 in teams for r in basicRounds} +break3InRound = {(t1, r): LpVariable('break3InRound_'+str(t1)+'_'+str(r), + lowBound=0, cat=LpContinuous) for t1 in teams for r in rounds} +# breakInRound= {(t1,r) : LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = LpContinuous) for t1 in teams for r in rounds} +# breaksTotal = LpVariable('breaksTotal', lowBound = 0, cat = LpContinuous) +pairingVio = {(pair['id'], d): LpVariable('pairingVio_'+str(pair['id'])+'_' + + str(d), lowBound=0, cat=LpContinuous) for pair in pairings for d in days} +derbyMissing = {d: LpVariable( + 'derbyMissing_'+str(d), lowBound=0, cat=LpContinuous) for d in days} + +breakVio = {(br['id'], t): LpVariable('breakVio_' + str(br['id'])+'_' + + str(t), lowBound=0, cat=LpContinuous) for br in breaks for t in teams} +blockingVio = {bl['id']: LpVariable( + 'blockingVio_' + str(bl['id']), lowBound=0, cat=LpContinuous) for bl in blockings} +# blockingVioTotal = LpVariable('blockingVioTotal', lowBound = 0, cat = LpContinuous) + +hawVio = {haw['id']: LpVariable( + 'hawVio' + str(haw['id']), lowBound=0, cat=LpContinuous) for haw in hawishes} +HawVioTooLess = {el: LpVariable('havviotooless_' + str(el), lowBound=0, cat=LpContinuous) + for haw in hawishes for el in elemHaWishes[haw['id']]} +HawVioTooMuch = {el: LpVariable('havviotoomuch_' + str(el), lowBound=0, cat=LpContinuous) + for haw in hawishes for el in elemHaWishes[haw['id']]} +# HawVioTotal = LpVariable('HawVioTotal', lowBound = 0, cat = LpContinuous) +encVio = {enc['id']: LpVariable( + 'encVio' + str(enc['id']), lowBound=0, cat=LpContinuous) for enc in encwishes} +encVioTooLess = {el: LpVariable('encViotooless' + str(enc['id'])+"_"+str( + el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]} +encVioTooMuch = {el: LpVariable('encViotoomuch' + str(enc['id'])+"_"+str( + el), lowBound=0, cat=LpContinuous) for enc in encwishes for el in elemEncWishes[enc['id']]} +# encVioTotal = LpVariable('encVioTotal', lowBound = 0, cat = LpContinuous) +# broadVio = { d : LpVariable('broadVio'+ str(d) , lowBound = 0, cat = LpContinuous) for d in days} +broadVioTm = {(b.id, r): LpVariable('broadVioTm_' + str(b.id) + "_" + str(r), + lowBound=0, cat=LpContinuous) for b in broadcastingwishes for r in rounds} +gamesTooClose2 = {(t, r): LpVariable('gamesTooClose2_' + str(t) + '_' + + str(r), lowBound=0, cat=LpContinuous) for t in teams for r in rounds} +missingGamesVio = {(t1, t2): LpVariable('missingGamesVio_' + str(t1) + + '_'+str(t2), lowBound=0, cat=LpContinuous) for (t1, t2) in games} + +home = {} +away = {} +home_time = {} +away_time = {} +away_in_cluster = {} +away_in_cluster_day = {} + +getRoundDaysByBasicRound = {br: [rd for r in getRealRounds[br] + for rd in getRoundDaysByRound[r]] for br in basicRounds} +getMaxGameOnRoundDaysByBasicRound = {br: sum( + roundDaysMax[rd] for r in getRealRounds[br] for rd in getRoundDaysByRound[r]) for br in basicRounds} + +for t in realteams: + for d in days: + away[t, d] = lpSum([x[(t2, t, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable( + 'away'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous) + home[t, d] = lpSum([x[(t, t2, rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or LpVariable( + 'home'+str(t)+'_'+str(d), lowBound=0, upBound=0, cat=LpContinuous) + + +for t in realteams: + for r in rounds: + if thisSeason.gamesPerRound == "one day": + model2 += homeInRound[(t, r)] == lpSum([x[(t, t2, rd)] + for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_home_in_round_{r}' + model2 += awayInRound[(t, r)] == lpSum([x[(t2, t, rd)] + for t2 in opponents[t] for rd in getRoundDaysByRound[r]]), f'team_{t}_away_in_round_{r}' + + model2 += homeInRound[(t, r)] + awayInRound[(t, r)] <= 1 + # if r >= 3: + # model2 += break3InRound[(t, r)] + 2 >= homeInRound[(t, r-2)] + \ + # homeInRound[(t, r-1)]+homeInRound[(t, r)], f'team_{t}_home_break_in_round_{r}' + # model2 += break3InRound[(t, r)] + 2 >= awayInRound[(t, r-2)] + \ + # awayInRound[(t, r-1)]+awayInRound[(t, r)], f'team_{t}_away_break_in_round_{r}' + + + + +breakVioBalance = {t: LpVariable( + 'breakVioBalance_'+str(t), lowBound=0, cat=LpContinuous) for t in realteams} +numBreaks = {t: lpSum([breakVio[(bl['id'], t)] + for bl in breaks]) for t in realteams} + +succBreaks = [bl for bl in breaks if bl['round1']+1 == bl['round2']] + +# for t in realteams: +# for bl in breaks: +# bl_id = bl['id'] +# model2 += breakVio[(bl['id'], t)] + 1 >= homeInRound[t, +# bl['round1']] + homeInRound[t, bl['round2']], f'breakvio_home_{bl_id}_for_{t}' +# model2 += breakVio[(bl['id'], t)] + 1 >= awayInRound[t, +# bl['round1']] + awayInRound[t, bl['round2']], f'breakvio_away_{bl_id}_for_{t}' + +getDays[0] = [] + + +use_currentSolution = False + +currentGameCntr = {(t1, t2): 0 for t1 in teams for t2 in teams} + + +# if use_currentSolution and len(currentSolution) !=len(fixedGames): + +teamGameCntr = {(t, r): 0 for t in teams for r in rounds} + + +# print ("days " , days) +for d in days: + minG = sum([roundDaysMin[rd] - deficientGames[rd] + for rd in getRoundDaysByDay[d]]) + maxG = sum([roundDaysMax[rd] + excessGames[rd] + for rd in getRoundDaysByDay[d]]) + model2 += lpSum([home[(t, d)] for t in realteams]) <= maxG, f'max_games_on_day_{d}' + + # if len(getRoundDaysByDay[d]) > 1: + # for rd in getRoundDaysByDay[d]: + # model2 += lpSum([x[(t1, t2, rd)] for (t1, t2) in games] + # ) <= roundDaysMax[rd] + excessGames[rd] + +print("rounds ", rounds, nRounds) +for r in rounds: + # one game a round for everyone + for t1 in realteams: + if thisSeason.gamesPerRound == "one day": + cnstr = lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)] + for t2 in opponents[t1] for rd in getRoundDaysByRound[r]]) + if len(cnstr) > 0: + model2 += cnstr <= 1, f'one_game_in_round_{r}_for_{t1}' + +# exit(0) +print("realteams ", realteams) + + + + +print("taking care of right numbers of games ... ") +cntr = 0 + +if not specialGameControl: + for (t1, t2) in realgames: + if undirectedGameCntr[(t1, t2)] == 0: + model2 += lpSum([x[(t1, t2, rd)] for rd in roundDays] + ) == gameCntr[(t1, t2)] - missingGamesVio[(t1, t2)], f'let_{t1}_{t2}_play_gameCntr_times' + + missingGamesVio[(t1, t2)].upBound = max( + 0, gameCntr[(t1, t2)]+undirectedGameCntr[(t1, t2)] - currentGameCntr[(t1, t2)]) + + +# %% + +# breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \ +# 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] +# for bl in breaks for t in importantteams]) +# break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds]) +missingGamesVioTotal = lpSum( + [2000000 * missingGamesVio[(t1, t2)] for (t1, t2) in realgames]) + +standardObjectives = 0 \ + + missingGamesVioTotal\ + # + gew['Breaks']*breakVioTotal \ + # + gew['Breaks']*2*break3VioTotal \ + +model2 += standardObjectives + + +for v in model2.variables(): + v.cat = LpInteger + + +with open ("model2.txt", "w") as f: + f.write(model2.__repr__()) + + +model2.solve(XPRESS(msg=1)) + +# print(model2.sol_status) +# for var in model2.variables(): +# if var.value() and var.value() > 0: +# print(var,var.value()) + + + +# %% + +sum([1 for k in x_round.keys() if type(x_round[k]) != int and x_round[k].value() > 0]) + +games_dict = defaultdict(lambda:[]) +for key in x_round.keys(): + if type(x_round[key]) != int and x_round[key].value() > 0: + games_dict[key[2]].append((key[0],key[1])) + + + + + + + +# %% +exit() + +if not evalRun: + for (t1, t2) in realgames: + # every pair plays each other in each phase once + for p in phases: + # phase violation in last phase possible if it contained more rounds than necessary + if p < len(phases)-1 or True: + if t_usePhases[t1] or t_usePhases[t2]: + if thisSeason.groupBased and len(noPlayRounds[t1]) > 0 and noPlayRounds[t1] == noPlayRounds[t2]: + relDays = [] + phaseLength = int(len(playRounds[t1])/nPhases+0.5) + for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: + relDays += getDays[rr] + print("adding days of round ", rr, " to phase ", p) + else: + relDays = getDaysOfPhase[p] + model2 += lpSum([x[(t1, t2, rd)] + x[(t2, t1, rd)] + for d in relDays for rd in getRoundDaysByDay[d]]) <= 1 + # print (len(relDays),"reldays") + # print (getTeamById[t1],getTeamById[t2], sum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] )) + +if not evalRun: + for t in realteams: + if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases: + for p in phases: + rds = [r for r in rounds if getPhaseOfRound[r] == p] + nblocked = [r for r in noHomeRounds[t] if r in rds] + print(p, len(rds), len(nblocked), + rds, nblocked, getTeamById[t]) + for p in phases: + phaseLength = int(len(playRounds[t])/nPhases+0.5) + model2 += lpSum([home[(t, d)] + for d in getDaysOfPhase[p]]) <= int(phaseLength/2+1) + + if thisSeason.minRoundsBetweenGameOfSameTeams > 0: + for r in rounds: + if r + thisSeason.minRoundsBetweenGameOfSameTeams <= nRounds: + rds = [] + for r2 in range(r, r+thisSeason.minRoundsBetweenGameOfSameTeams+1): + rds += getRoundDaysByRound[r2] + rds2 = [] + for r2 in range(r, r+int(0.5*thisSeason.minRoundsBetweenGameOfSameTeams)): + rds2 += getRoundDaysByRound[r2] + # print ("ONLY ONE IN " , rds) + # print ("ONLY ONE IN " , rds2) + for t1 in realteams: + for t2 in realteams: + if t1 < t2 and (gameCntr[(t1, t2)]*gameCntr[(t2, t1)] > 0 or x_round[(t1, t2, r)] != 0): + # model2 += sum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r] + model2 += sum([(x[(t1, t2, rd)]+x[(t2, t1, rd)]) + for rd in rds]) <= 1 + + +if not evalRun: + # max length home stands /trips + for r in range(1, nRounds-thisSeason.maxTourLength+1): + # print (" at least one home and away in rounds ") + # for r3 in range(r,r+thisSeason.maxTourLength+1): + # print (r3 ) + for t in teams: + if t not in noBreakLimitTeams: + # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1 + model2 += lpSum([awayInRound[t, r2] for r2 in range(r, r + + thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength + model2 += lpSum([homeInRound[t, r2] for r2 in range(r, r + + thisSeason.maxTourLength+1)]) <= thisSeason.maxTourLength + +print("check 1") + +# blockings +for bl in blockings: + if getDayById[bl['day_id']]['round'] != 0: + if thisSeason.useFeatureKickOffTime and bl['time'] != '----': + if bl['type'] in ["Home", "Hide"]: + model2 += blockingVio[bl['id'] + ] == home_time[bl['team_id'], bl['day_id'], bl['time']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2 += blockingVio[bl['id'] + ] == away_time[bl['team_id'], bl['day_id'], bl['time']] + # print ('FOUND AWAY BLOCKING ', bl) + else: + if bl['type'] in ["Home", "Hide"]: + model2 += blockingVio[bl['id'] + ] == home[bl['team_id'], bl['day_id']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2 += blockingVio[bl['id'] + ] == away[bl['team_id'], bl['day_id']] + # print ('FOUND AWAY BLOCKING ', bl) + + +print("check 2") + +hawOneVio = {} +hawForOneNotViolated = {} + + +def getStringFromSet(ss): + s2 = "" + for s in ss: + s2 += str(s)+"_" + return s2[:-1] + + + # hawishes +for haw in hawishes: + print(haw['reason']) + for el in elemHaWishes[haw['id']]: + if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']]) > 0: + if haw['homeAway'] == 'Home': + relGames = lpSum([home_time[t, d, tm] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + # print (haw['id'] ," haw : ", relGames, hawTimes[haw['id']]) + elif haw['homeAway'] == 'Away': + relGames = lpSum([away_time[t, d, tm] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + else: + # print(haw,el) + relGames = lpSum([home_time[t, d, tm] + away_time[t, d, tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \ + - lpSum([x_time[(t1, t2, rd, tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']] + for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd, tm) in x_time.keys()]) + + else: + if haw['homeAway'] == 'Home': + relGames = lpSum([home[t, d] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el]]) + elif haw['homeAway'] == 'Away': + relGames = lpSum([away[t, d] for d in elemHaWishDays[el] + for t in elemHaWishTeams[el]]) + else: + relGames = lpSum([home[t, d] + away[t, d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1, t2, rd] for d in elemHaWishDays[el] + for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1, t2, rd) in x.keys()]) + if haw['minGames'] > 0: + model2 += relGames >= haw['minGames'] - HawVioTooLess[el] + # print ("adding min ha constraint") + if haw['maxGames'] >= 0: + model2 += relGames <= haw['maxGames'] + HawVioTooMuch[el] + # print ("adding max ha constraint") + + usedConstraintNames = [""] + if haw['forOneDay']: + # print (haw['forOneDay'], hawDays[haw['id']]) + # print ([el for el in elemHaWishes[haw['id']] ]) + # for el in elemHaWishes[haw['id']] : + # print("- " , elemHaWishDays[el] , elemHaWishTeams[el] ) + # print ([ (el, elemHaWishFirstDay[el]) for el in elemHaWishes[haw['id']] ]) + relTeamString = {el: getStringFromSet( + elemHaWishTeams[el]) for el in elemHaWishes[haw['id']]} + relTeams = set([relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print (relTeams) + for rt in relTeams: + rtname = rt + if len(rt) > 50: + scname = "" + while scname in usedConstraintNames: + ttt = bytes(rt + ''.join(random.choice(string.ascii_lowercase) + for i in range(10)), 'utf-8') + scname = hashlib.sha224(ttt).hexdigest() + rtname = scname[:10] + usedConstraintNames.append(rtname) + # print ("use rtname to encode wishes ", rtname) + + hawOneVio[(haw['id'], rt)] = LpVariable( + 'hawOneVio_'+str(haw['id'])+"_"+rtname, cat=LpContinuous) + for fd in hawDays[haw['id']]: + relWishes = [el for el in elemHaWishes[haw['id']] + if elemHaWishFirstDay[el] == fd] + # relWishes = [el for el in elemHaWishes[haw['id']] ] + # print (" -" , relWishes , [elemHaWishDays[el] for el in relWishes]) + hawForOneNotViolated[(haw['id'], rt, fd)] = 0 + if len(relWishes) > 0: + hawForOneNotViolated[(haw['id'], rt, fd)] = LpVariable( + 'hawForOneViolated_'+str(haw['id'])+"_"+rtname+"_"+str(fd), cat=LpBinary) + model2 += lpSum(HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el] + == rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'], rt, fd)]) + model2 += lpSum(hawForOneNotViolated[(haw['id'], rt, fd)] + for fd in hawDays[haw['id']]) >= haw['forOneDayNum']-hawOneVio[(haw['id'], rt)] + model2 += lpSum(hawOneVio[(haw['id'], rt)] + for rt in relTeams) == hawVio[haw['id']] + else: + # print (haw['forOneDay'], hawDays[haw['id']]) + model2 += hawVio[haw['id']] == lpSum(HawVioTooMuch[el]+HawVioTooLess[el] + for el in elemHaWishes[haw['id']]) + + if haw['prio'] == "Hard" and "HardHAWishesNotBreakable" in special_wishes_active: + model2 += hawVio[haw['id']] == 0 + print("WISH HARD", haw['reason']) + + +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +# print (hawDays) +# print (elemHaWishFirstDay) + +encOneVio = {} +encForOneNotViolated = {} +print("check 3") +# encwishes +for enc in encwishes: + print(enc) + for el in elemEncWishes[enc['id']]: + # print (enc) + # model2+= encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']] + + if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']]) > 0: + if enc['minGames'] > 0: + model2 += encVioTooLess[el] >= enc['minGames'] - sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for ( + t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games]) + if enc['maxGames'] >= 0: + # if enc['maxGames']==0: + print(enc['reason'], ' ', elemEncWishDays[el], ' ', enc['time'], + ' ', encTeams1[enc['id']], ' ', encTeams2[enc['id']]) + # print (sum([x_time[(t1,t2,rd, enc['time'])] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])) + model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x_time[(t1, t2, rd, tm)] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for ( + t1, t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1, t2) in games]) + else: + if enc['minGames'] > 0: + model2 += encVioTooLess[el] >= enc['minGames'] - sum([x[t1, t2, rd] for d in elemEncWishDays[el] + for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games]) + if enc['maxGames'] >= 0: + # if enc['maxGames']==0: + # print (enc['reason'], ' ', encDays[enc['id']], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + model2 += encVioTooMuch[el] >= -enc['maxGames'] + sum([x[t1, t2, rd] for d in elemEncWishDays[el] + for rd in getRoundDaysByDay[d] for (t1, t2) in elemEncWishGames[el] if (t1, t2) in games]) + + if enc['forOneDay']: + for ed in encDaySets[enc['id']]: + if len(ed) > 0: + relWishes = [el for el in elemEncWishes[enc['id']] + if elemEncWishDays[el] == ed] + print("###", ed, relWishes) + encForOneNotViolated[(enc['id'], ed[0])] = LpVariable( + 'encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=LpBinary) + model2 += sum(encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * ( + 1-encForOneNotViolated[(enc['id'], ed[0])]) + model2 += sum(encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len( + ed) > 0) >= enc['forOneDayNum']-encVio[enc['id']] + else: + model2 += encVio[enc['id']] == sum(encVioTooMuch[el]+encVioTooLess[el] + for el in elemEncWishes[enc['id']]) + + if enc['prio'] == "Hard" and "HardEncWishesNotBreakable" in special_wishes_active: + model2 += encVio[enc['id']] == 0 + print("WISH HARD", enc['reason']) + + +print("check 4") + + +# return "" +weekdayHomePref = {} +dayHomePref = {} +for t in teams: + tm = getTeamByName[getTeamById[t]] + weekdayHomePref[(t, 'Mon')] = tm['home_pref_mo'] + weekdayHomePref[(t, 'Tue')] = tm['home_pref_tu'] + weekdayHomePref[(t, 'Wed')] = tm['home_pref_we'] + weekdayHomePref[(t, 'Thu')] = tm['home_pref_th'] + weekdayHomePref[(t, 'Fri')] = tm['home_pref_fr'] + weekdayHomePref[(t, 'Sat')] = tm['home_pref_sa'] + weekdayHomePref[(t, 'Sun')] = tm['home_pref_su'] + for d in days: + dayHomePref[(t, d)] = weekdayHomePref[(t, getWeekDay[d])] + + +maxTravelDistance = max([distanceById[t1, t2] + for t1 in realteams for t2 in realteams]) +maxTravelDistance = max(1, maxTravelDistance) + +singleTripWeight = 50 +breakImbalanceTotal = lpSum([breakVioBalance[t] for t in realteams]) +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +HawVioTotal = lpSum([prioVal[haw['prio']] * hawVio[haw['id']] + for haw in hawishes]) +encVioTotal = lpSum([prioVal[enc['prio']] * encVio[enc['id']] + for enc in encwishes]) +seedVioTotal = lpSum([100*prioVal[enc['prio']] * encVio[enc['id']] + for enc in encwishes if enc['reason'] == "Seed Game"]) +# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes]) +broadVioTotal = lpSum([10 * broadVioTm[(b.id, r)] + for b in broadcastingwishes for r in rounds]) +breakVioTotal = lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] for bl in breaks for t in realteams]) + \ + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'], t)] + for bl in breaks for t in importantteams]) +break3VioTotal = lpSum([2*break3InRound[t, r] for t in teams for r in rounds]) +tooManyTop4InRowTotal = lpSum([10*tooManyTop4InRow[(t, r)] + for t in teams for r in rounds]) +pairingVioTotal = lpSum([5 * prioVal[pair['prio']] * pairingVio[(pair['id'], d)] + for pair in pairings for d in days]) +# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Home"]) +# print (blockings) +# for bl in blockings: +# if bl['type'] in ["Home"]: +# print (blocked_arena[(bl["team_id"],bl["day_id"],"----")], getTeamById[bl["team_id"]], getNiceDay[bl["day_id"]] , bl ) + +fulfBlocks = set([(bl["team_id"], getRoundByDay[bl["day_id"]]) for bl in blockings if bl['type'] in [ + "Home"] and blocked_arena[(bl["team_id"], bl["day_id"], "----")]]) + +blockingVioTotal2 = lpSum([-30 * homeInRound[tr] + for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode == 'Improve']) +blockingVioTotal = lpSum([100 * blockingVio[bl['id']] + for bl in blockings if bl['type'] in ["Home", "Hide"]]) + blockingVioTotal2 + +travelVioTotal = lpSum([100 * blockingVio[bl['id']] + for bl in blockings if bl['type'] == "Away"]) +derbiesMissingTotal = lpSum([30 * derbyMissing[d] for d in days]) +unpreferredTotal = lpSum( + [home[t, d] for t in teams for d in days if dayHomePref[(t, d)] == 0]) +# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions]) +missingGamesVioTotal = lpSum( + [2000000 * missingGamesVio[(t1, t2)] for (t1, t2) in realgames]) +# TODO - UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games]) +oldScenGamesTotal = lpSum([wg * x[t1, t2, rd] for (t1, t2, r, wg) + in otherScenGames for rd in getRoundDaysByRound[r]]) +totalAttendance = lpSum([attendance[(t1, t2, d)] * x[t1, t2, (r, d)] + for (t1, t2) in games for (r, d) in roundDays]) + +specialObjectives = 0 +specialWishItems = {sw: [] for sw in special_wishes_active} +specialWishVio = {} + + +optCameraMovement = "Standard" + +# tvkitproblem = {} +move2 = {} +newtrip2 = {} + + +standardObjectives = 1+gew['Home-/Away']*HawVioTotal\ + + gew['Home-/Away']*3*unpreferredTotal \ + + gew['Pairings']*pairingVioTotal \ + + gew['Blockings']*blockingVioTotal \ + + gew['Traveling']*travelVioTotal \ + + gew['Breaks']*breakVioTotal \ + + gew['Breaks']*2*break3VioTotal \ + + gew['Breaks']*1*breakImbalanceTotal \ + + 5*gew['Encounters']*encVioTotal \ + + 5*gew['Encounters']*seedVioTotal \ + + gew['Broadcasting']*broadVioTotal \ + + gew['Derbies']*derbiesMissingTotal \ + + 1.0*tooManyTop4InRowTotal\ + + missingGamesVioTotal\ + + oldScenGamesTotal\ + - 0.01*totalAttendance + + +model2 += standardObjectives + +# %% +script = thisSeason.optimizationScript +if script == '': + if useBasicGames: + script += "HEURISTIC\n" + script += "GAME;1-"+str(nRounds)+";0.1;200\n" + if thisSeason.groupBased: + for c in allConferences: + if not c.regional and c.teams.count() <= 12: + script += "GROUP;1-"+str(nRounds)+";0.05;30;"+c.name+"\n" + for cr in range(nPhases): + minIntRound = min((cr) * nRoundsPerPhase+1, nRounds) + maxIntRound = min((cr+1)*nRoundsPerPhase, nRounds) + + +script = script.replace('\r', '') +print("script") +print(script) +optSteps = [st.split(';') for st in script.split("\n")] +print(optSteps) + +# %% + +print("Model built now solving .... ") + +nRuns = 1 +maxSolveTime = 300 + +if thisSeason.groupBased: + maxSolveTime = 40 + +mipgap = 0.01 + +# print ("######## Testing") +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + + +use_LP_heuristic = False +print(runMode == 'New', useBasicGames, runPatternAssignmentFirst) + +nRuns = nPhases +# maxSolveTime = 120 +mipgap = 0.0 +# mipgap=0.95 + +# nRuns =1 +onlyReopt = True +onlyReopt = False +onlyFewTrips = False +singleTripWeight = 10 + +if onlyReopt: + nRuns = 0 + +maxIntRound = nRounds + + +missing_imp = [] +cntr = 0 + +for st in optSteps: + print(" - ", st) + cntr += 1 + if runMode == 'New' and st[0] == "HARDCONSTRAINTS": + for bl in blockings: + blockingVio[bl['id']].upBound = 0 + print("blocking tightened : ", + getTeamById[bl['team_id']], bl['day_id']) + for enc in encwishes: + if enc['reason'] == "Seed Game": + encVio[enc['id']].upBound = 0 + + if runMode == 'New' and len(st) >= 1 and st[0] in ["PATTERNS", "HOMEAWAY", "BASICGAME", "GAME", "GAMES", "GROUP", "TRIPS", "LP-HEURISTIC"]: + newRounds = [] + print() + optTarget = st[0] + if len(st) > 1: + for rf in st[1].split(","): + rr = rf.split("-") + if len(rr) == 1: + newRounds.append(min(nRounds, int(rr[0]))) + else: + for ii in range(int(rr[0]), min(nRounds, int(rr[1])+1)): + newRounds.append(ii) + newRoundsString = st[1] + else: + newRounds = rounds + newRoundsString = "1-"+str(nRounds) + + optsteptime = maxSolveTime + optstepgap = mipgap + + if len(st) >= 3 and st[2] != "": + optstepgap = float(st[2]) + if len(st) >= 4 and st[3] != "": + optsteptime = float(st[3]) + + print(newRounds) + + if st[0] == "HOMEAWAY": + for t in teams: + for r in newRounds: + homeInRound[(t, r)].cat = LpInteger + + if st[0] == "BASICGAME": + for (t1, t2) in games: + for r in newRounds: + gameInBasicRound[(t1, t2, r)].cat = LpInteger + + if st[0] in ["GAME", "GAMES"]: + for (t, t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t, t2, rd)]) + + print('########################') + print('# SOLVING MODEL '+optTarget+' FOR ROUNDS ' + newRoundsString + + ' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' #') + print('########################') + +# if solver == "CBC": +# model2.solve(PULP_CBC_CMD(fracGap=optstepgap, +# maxSeconds=optsteptime, threads=8, msg=1)) +# elif solver == "Gurobi": +# model2.solve(GUROBI(MIPGap=optstepgap, +# TimeLimit=optsteptime, msg=1, Method=2, NodeMethod=2)) +# else: +# # for debugging: +# # with open ("model2.txt", "w") as f: +# # f.write(model2.__repr__()) +# model2.solve(XPRESS(msg=1, targetGap=optstepgap, maxSeconds=optsteptime, options=[ +# "THREADS=12,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + +# if model2.status < 0: +# print("Status: ", model2.status) + +# if not lowerBoundFound: +# lowerBoundFound = value(model2.objective) + +# cntr_rnd = 0 + +# if st[0] == "HOMEAWAY": +# print(teams) +# print(newRounds) +# for t in teams: +# for r in newRounds: +# if homeInRound[(t, r)].value() > 0.9: +# print('fixing home ' + str(r) + ' : ' + +# getTeamById[t] + " " + str(homeInRound[(t, r)].value())) +# homeInRound[(t, r)].lowBound = 1 +# else: +# homeInRound[(t, r)].upBound = 0 + +# if st[0] == "BASICGAME": +# for r in newRounds: +# for (t1, t2) in games: +# if getTeamById[t1] != "-" and getTeamById[t2] != "-" and (t1, t2, r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1, t2, r)]) != int: +# if getVal(gameInBasicRound[(t1, t2, r)]) > 0.9: +# gameInBasicRound[(t1, t2, r)].lowBound = 1 +# else: +# gameInBasicRound[(t1, t2, r)].upBound = 0 + +# if st[0] in ["GAME", "GAMES"]: +# feedback = "Optimize games...." +# missing_imp = [] + +# for (t1, t2) in realgames: +# if missingGamesVio[(t1, t2)].value() > 0.9: +# feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str( +# missingGamesVio[(t1, t2)].value()) + '
\n' +# missing_imp += [(1, nRounds, [t1, t2], 50)] + +# print(missing_imp) +# print(feedback) +# print("number of assigned games : ", sum( +# [getVal(x[ttrd]) for ttrd in x.keys()])) + +# for (t1, t2) in games: +# for r in newRounds: +# for rd in getRoundDaysByRound[r]: +# if getVal(x[(t1, t2, rd)]) > 0.9: +# setLB(x[(t1, t2, rd)], 1) +# print("fixing ", t1, t2, rd, +# x[(t1, t2, rd)].lowBound) +# else: +# setUB(x[(t1, t2, rd)], 0) + +# for r in basicRounds: +# for t1 in realteams: +# homeInBasicRound[(t1, r)].lowBound = 0 +# homeInBasicRound[(t1, r)].upBound = 10 +# awayInBasicRound[(t1, r)].lowBound = 0 +# awayInBasicRound[(t1, r)].upBound = 10 +# for t2 in opponents[t1]: +# if (t1, t2) in games: +# gameInBasicRound[(t1, t2, r)].cat = LpContinuous +# gameInBasicRound[(t1, t2, r)].lowBound = 0 + +# for t in realteams: +# for r in newRounds: +# homeInRound[(t, r)].cat = LpContinuous +# for t2 in opponents[t]: +# for rd in getRoundDaysByRound[r]: +# if (t, t2) in games and getVal(x[(t, t2, rd)]) > 0.9: +# setLB(x[(t, t2, rd)], x[(t, t2, rd)].value()) +# currentSolution = [(t1, t2, r, d) for (t1, t2) in realgames for ( +# r, d) in roundDays if getVal(x[(t1, t2, (r, d))]) > 0.9] + + +# for r in rounds: +# for t in teams: +# homeInRound[(t, r)].lowBound = 0 +# homeInRound[(t, r)].upBound = 1 + +# for (t1, t2) in games: +# for r in rounds: +# for rd in getRoundDaysByRound[r]: +# makeIntVar(x[(t1, t2, rd)]) + +# print('Solved Again') +# print('NOW REOPT') + +# # %% + +# %% diff --git a/machine_learning/scripts/script.py b/machine_learning/scripts/script.py new file mode 100755 index 0000000..18de6df --- /dev/null +++ b/machine_learning/scripts/script.py @@ -0,0 +1,122 @@ +# %% +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' + + + +import django +django.setup() + +from scheduler.models import * +from pulp import * + +from django.urls import get_resolver +from django_tex.shortcuts import render_to_pdf + + +from common import urls +from referees import urls +from qualifiers import urls + +from django.core.handlers.base import BaseHandler +from django.test.client import RequestFactory +from scheduler.widgets import widget_context_games_reduced + + + +# %% + +scenario = Scenario.objects.get(id=14) + +dayObjects = Day.objects.filter(season=scenario.season) +rounds = dict(scenario.gamesOfRound()).keys() +teams = scenario.teams_dict() +timeslots = {str(t.id):t.name for t in scenario.season.timeslots.all()} + +getTeamById = {} +teamObj = Team.objects.filter(season=scenario.season) + +for t in teamObj: + getTeamById[str(t.id)] = t + if (t.logo.name.split('.')[-1] != 'png'): + os.rename(f'{t.logo.path}',f'{t.logo.path}.png') + t.logo.name = f'{t.logo.name}.png' + t.save() + +# %% + +getDayById = {} +for d in dayObjects: + getDayById[d.id] = d + +list_games = [] +list_rounds = {r:defaultdict(lambda:[]) for r in rounds} +list_days = defaultdict(lambda:[]) +games = {} +cntr = 0 + +x_pos = 0 +y_pos = 0 +last_y_pos = 0 +page_break = 9999 + +for r in rounds: + games[r] = widget_context_games_reduced(scenario,r) + games_per_round = 0 + + if last_y_pos + sum([len(i) for i in games[1]['gamesOfDay'].values()]) > 60: + page_break = x_pos + last_y_pos = 0 + x_pos = 0 + + y_pos = last_y_pos+10 + + for day,gamesPerDay in games[r]['gamesOfDay'].items(): + if not gamesPerDay: + continue + for game in gamesPerDay: + games_per_round += 1 + cntr += 1 + t1 = getTeamById[game[1]] + t2 = getTeamById[game[2]] + game_details = (f'{cntr}','',f'\includegraphics[width=0.2cm]{{{t1.logo.path}}}',f'{t1.shortname}', f'\includegraphics[width=0.2cm]{{{t2.logo.path}}}', f'{t2.shortname}',x_pos % 5,y_pos,game[4]) + list_games.append(game_details) + list_rounds[r][(day,str(getDayById[day]))].append(game_details) + list_days[str(getDayById[day])].append(game_details) + y_pos += 1 + y_pos += 3 + x_pos += 1 + + if x_pos % 5 == 0: + last_y_pos = y_pos + + if x_pos >= page_break: + last_y_pos = 0 + x_pos = 0 + + + +# %% + + +template_name = 'latex/season_overview.tex' +context = { + 'scenario': scenario, + 'rounds': list_rounds, + 'page_break':page_break, +} +response = render_to_pdf(None, template_name, context, filename='test.pdf') + + + +with open("response.pdf", "wb") as f: + f.write(response.content) +# %% + diff --git a/qualifiers/scripts/debug.py b/qualifiers/scripts/debug.py new file mode 100755 index 0000000..d5966eb --- /dev/null +++ b/qualifiers/scripts/debug.py @@ -0,0 +1,165 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + + +# %% + +scenario = Scenario.objects.get(id=1) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append((1, "Manchester City FC (ENG)", 145.000, "Pot 1",5,4)) +new_teams.append((2, "FC Bayern München (GER)", 136.000, "Pot 1",5,5)) +new_teams.append((3, "Liverpool FC (ENG)", 123.000, "Pot 1",5,5)) +new_teams.append((4, "Real Madrid CF (ESP)", 121.000, "Pot 1",5,5)) +new_teams.append((5, "Paris Saint-Germain (FRA)", 112.000, "Pot 1",5,5)) +new_teams.append((6, "Manchester United FC (ENG)", 104.000, "Pot 1",5,5)) +new_teams.append((7, "FC Barcelona (ESP)", 98.000, "Pot 1",5,5)) +new_teams.append((8, "FC Internazionale Milano (ITA)", 96.000, "Pot 1",4,5)) +new_teams.append((9, "Sevilla FC (ESP)", 91.000, "Pot 1",3,4)) +new_teams.append((10, "Borussia Dortmund (GER)", 86.000, "Pot 2",4,4)) +new_teams.append((11, "Club Atlético de Madrid (ESP)", 85.000, "Pot 2",4,4)) +new_teams.append((12, "RB Leipzig (GER)", 84.000, "Pot 2",3,3)) +new_teams.append((13, "SL Benfica (POR)", 82.000, "Pot 2",2,5)) +new_teams.append((14, "SSC Napoli (ITA)", 81.000, "Pot 2",3,4)) +new_teams.append((15, "FC Porto (POR)", 81.000, "Pot 2",3,5)) +new_teams.append((16, "Arsenal FC (ENG)", 76.000, "Pot 2",4,4)) +new_teams.append((17, "FC Shakhtar Donetsk (UKR)", 63.000, "Pot 2",1,5)) +new_teams.append((18, "FC Salzburg (AUT)", 59.000, "Pot 2",2,5)) +new_teams.append((19, "Atalanta BC (ITA)", 55.500, "Pot 3",2,3)) +new_teams.append((20, "Feyenoord (NED)", 51.000, "Pot 3",2,4)) +new_teams.append((21, "AC Milan (ITA)", 50.000, "Pot 3",3,5)) +new_teams.append((22, "SC Braga (POR)", 44.000, "Pot 3",2,3)) +new_teams.append((23, "PSV Eindhoven (NED)", 43.000, "Pot 3",2,4)) +new_teams.append((24, "S.S. Lazio (ITA)", 42.000, "Pot 3",2,3)) +new_teams.append((25, "FK Crvena zvezda (SRB)", 42.000, "Pot 3",1,5)) +new_teams.append((26, "F.C. Copenhagen (DEN)", 40.500, "Pot 3",1,4)) +new_teams.append((27, "BSC Young Boys (SUI)", 34.500, "Pot 3",1,4)) +new_teams.append((28, "Real Sociedad de Fútbol (ESP)", 33.000, "Pot 4",2,3)) +new_teams.append((29, "Olympique de Marseille (FRA)", 33.000, "Pot 4",3,4)) +new_teams.append((30, "Galatasaray A.Ş. (TUR)", 31.500, "Pot 4",1,5)) +new_teams.append((31, "Celtic FC (SCO)", 31.000, "Pot 4",2,3)) +new_teams.append((32, "Qarabağ FK (AZE)", 25.000, "Pot 4",1,5)) +new_teams.append((33, "Newcastle United FC (ENG)", 21.914, "Pot 4",3,3)) +new_teams.append((34, "1. FC Union Berlin (GER)", 17.000, "Pot 4",2,3)) +new_teams.append((35, "R. Antwerp FC (BEL)", 17.000, "Pot 4",3,3)) +new_teams.append((36, "RC Lens (FRA)", 12.232, "Pot 4",2,3)) + + +CET_minus_1 = ['ENG','POR','SCO'] +CET_plus_1 = ['TUR','AZE','ISR','UKR'] + +# %% + +for conf in Conference.objects.filter(scenario=scenario).exclude(name__in=['HARD Constraints','SOFT Constraints']): + conf.teams.clear() + +Team.objects.filter(season=scenario.season).update(active=False) +for t in new_teams: + team_name = t[1].split('(')[0].strip() + team_country = t[1].split('(')[1].split(')')[0].strip() + global_coeff = t[4] + domestic_coeff = t[5] + teamObj = Team.objects.filter(name=team_name) + if teamObj: + Conference.objects.filter(scenario=scenario,name="UCL").first().teams.add(teamObj.first()) + Conference.objects.filter(scenario=scenario,name=t[3]).first().teams.add(teamObj.first()) + + if global_coeff in [3,4,5]: + Conference.objects.filter(scenario=scenario,name=f"Global Coeff {global_coeff}").first().teams.add(teamObj.first()) + + if domestic_coeff in [3,5]: + Conference.objects.filter(scenario=scenario,name=f"Domestic coeff {domestic_coeff}").first().teams.add(teamObj.first()) + + if team_country in CET_minus_1: + Conference.objects.filter(scenario=scenario,name="CET-1").first().teams.add(teamObj.first()) + elif team_country in CET_plus_1: + Conference.objects.filter(scenario=scenario,name="CET+1").first().teams.add(teamObj.first()) + else: + Conference.objects.filter(scenario=scenario,name="CET").first().teams.add(teamObj.first()) + + teamObj.update(active=True) + teamObj.update(attractivity=global_coeff) + teamObj.update(position=t[0]) + pass + else: + print(t,"->", team_name) + + + + + +# %% diff --git a/qualifiers/scripts/performance.py b/qualifiers/scripts/performance.py new file mode 100755 index 0000000..4b5a4fe --- /dev/null +++ b/qualifiers/scripts/performance.py @@ -0,0 +1,72 @@ +# %% +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'] = {} + + +import django +django.setup() + +from scheduler.models import * +from pulp import * +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 + +import random +import time +import json +import csv + +# %% + +from django.contrib.sessions.models import Session + + +scenario = Scenario.objects.get(id=8938) + +# %% + + + +from scheduler.solver.tasks.optimize import optimize + +s2 = scenario.id +user_name = 'md' +user_is_staff = True +localsearch_time = 30 +RUN_ENV = 'local' +SOLVER = 'xpress' + + +sol = optimize(task=None, s2=s2, user_name=user_name, user_is_staff=user_is_staff, + runMode='Improve', localsearch_time=localsearch_time, RUN_ENV=RUN_ENV, solver=SOLVER) + + + + + + + +# %% diff --git a/referees/scripts/script.py b/referees/scripts/script.py new file mode 100644 index 0000000..c5b425e --- /dev/null +++ b/referees/scripts/script.py @@ -0,0 +1,91 @@ +# %% +PROJECT_PATH = '/home/md/Work/ligalytics/leagues_develop/' +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 referees.helpers import use_referees, import_deb_delegates, seed_rounds_from_days, generate_distance_matrix + +from referees.models import RefGame, Role + + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt +from datetime import timedelta +# %% + +scenario = Scenario.objects.get(id=24) + +# %% + +# use_referees(scenario.id) + + + +import_deb_delegates(scenario) + + +generate_distance_matrix(scenario.season.id) +# %% + +# season = scenario.season + + + +# %% + +# seed_rounds_from_days(scenario.season.id) + +# Role.objects.all().delete() +# Role.objects.create(season=season, name="Referee", min_required=2, max_required=2) +# Role.objects.create(season=season,name="Linesmen", min_required=2, max_required=2) +# %% \ No newline at end of file diff --git a/referees/scripts/script_wales.py b/referees/scripts/script_wales.py new file mode 100755 index 0000000..c50199f --- /dev/null +++ b/referees/scripts/script_wales.py @@ -0,0 +1,127 @@ +# %% +PROJECT_PATH = '/home/md/Work/ligalytics/leagues_develop/' +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' + +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, Day, CountryClash, Country + +from qualifiers.draws import groupTeams, optimize_inversions4 +from scheduler.solver.tasks.optimize import optimize +from common.functions import distanceInMilesByGPS + +from referees.helpers import use_referees, import_deb_delegates, seed_rounds_from_days, generate_distance_matrix + +from referees.models import * + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt +from dateutil import parser +from datetime import timedelta +import googlemaps +import pandas as pd +# %% + +scenario = Scenario.objects.get(id=4) +season= scenario.season + +# %% +# from referees.optimize import optimize_referees, optimize_referees_reopt, optimize_delegates, optimize_wales +# sol=optimize_wales(scenario.id, "md", True, RUN_ENV='NO_CELERY') + +# %% + +tt = time.time() +for d in Delegate.objects.filter(season=season).prefetch_related('assignments'): +# for d in Delegate.objects.filter(season=season).prefetch_related('assignments').only('id'): +# for d in Delegate.objects.filter(season=season): + for a in d.assignments.all(): + x = a.travel + + +print(time.time()-tt) + + +# %% + +# categories = Category.objects.filter(season=season,requirements__in=Requirement.objects.filter(season=season)) +# delegates = Delegate.objects.filter(season=season,active=True,category__in=categories) + +# Delegate.objects.filter(season=season).exclude(id__in=delegates.values('id')).update(active=False) + +# for d in Delegate.objects.filter(season=season): +# if d.location: +# d.latitude = d.location.latitude +# d.longitude = d.location.longitude +# d.save() + +# %% + +# for d in Delegate.objects.filter(season=season): +# d.name = f"Person {d.name}" +# d.save() +# # %% +# competitions = Competition.objects.filter(season=season) +# days = Day.objects.filter(season=season).exclude(round=7) +# # %% + + +# for d in Delegate.objects.filter(season=season): +# d.coefficient = d.category.order +# d.save() + + +# from referees.optimize import optimize_referees, optimize_referees_reopt, optimize_delegates, optimize_wales +# sol=optimize_wales(2, "md", True, RUN_ENV='NO_CELERY') +# # %% + +# ranks = [ +# 'Elite Group One', +# 'Elite Group Two', +# '2', +# '3', +# '4A', +# '4B', +# '4C', +# '4D', +# 'AJR', +# 'CJR', +# 'International AR (FIFA List)', +# '1AR', +# '1ART', +# '2AR', +# '3AR', +# ] + +# for i,r in enumerate(ranks): +# Category.objects.filter(name=r).update(order=i+1) + +# # %% + + +# for d in Delegate.objects.filter(season=season): +# group,_ = DelGroup.objects.get_or_create(season=season, name=d.category) +# group.delegates.add(d) + + + +# %% diff --git a/referees/scripts/seed_wales.py b/referees/scripts/seed_wales.py new file mode 100755 index 0000000..127d51b --- /dev/null +++ b/referees/scripts/seed_wales.py @@ -0,0 +1,323 @@ +# %% +PROJECT_PATH = '/home/md/Work/ligalytics/leagues_develop/' +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, Day, CountryClash, Country + +from qualifiers.draws import groupTeams, optimize_inversions4 +from scheduler.solver.tasks.optimize import optimize +from common.functions import distanceInMilesByGPS +import datetime + + +from referees.helpers import use_referees, import_deb_delegates, seed_rounds_from_days, generate_distance_matrix + +from referees.models import * + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt +from dateutil import parser +from datetime import timedelta +import googlemaps +import pandas as pd +# %% + +gmaps = googlemaps.Client(key='AIzaSyB76EhR4OqjdXHQUiTkHZC0Svx_7cPGqyU') +scenario = Scenario.objects.get(id=4) +season= scenario.season + +# %% + + +# def seed_stadiums(): + +# df = pd.read_excel('referees/fixtures/wales/List_of_stadiums1700843463152.xlsx') + +# Location.objects.filter(season=scenario.season).delete() +# for index, row in df.iterrows(): +# name = row['Name'] +# country = row['Country'] +# city= row['Place'] +# longitude = row['invisible_label_longitude'] +# latitude = row['invisible_label_latitude'] +# country_obj = Country.objects.filter(season=scenario.season,name=country).first() + +# if not pd.isna(row['invisible_label_latitude']) and not pd.isna(row['invisible_label_longitude']): +# Location.objects.create(name=row['Name'],city=city,country=country_obj, latitude=row['invisible_label_latitude'], longitude=row['invisible_label_longitude'], season=season) +# else: +# geocode_result = gmaps.geocode(f"{country}+{city}+{name}") +# if len(geocode_result) > 0: +# lat = geocode_result[0]['geometry']['location']['lat'] +# lng = geocode_result[0]['geometry']['location']['lng'] +# Location.objects.create(name=row['Name'],city=city,country=country_obj, latitude=lat, longitude=lng, season=season) + +# seed_stadiums() +# %% + +# def seed_delegates(): +# df = pd.read_excel('referees/fixtures/wales/Anonymised Referee Registrations for Ligalytics.xlsx') +# country = Country.objects.get(season=season,name='Wales') + +# Delegate.objects.filter(season=season).delete() +# Role.objects.filter(season=season).delete() +# Role.objects.create(order=1,name='Referee', season=season) +# Role.objects.create(order=2,name='Assistant Referee 1', season=season) +# Role.objects.create(order=3,name='Assistant Referee 2', season=season) +# Role.objects.create(order=4,name='Fourth Official', season=season) +# for index, row in df.iterrows(): +# name = row['Dummy ID'] +# city = row['Home Town'] +# category = row['Refеree category'] + + +# cat = Category.objects.filter(name=category, season=season).first() +# if not cat: +# cat = Category.objects.create(name=category, season=season) + +# location = Location.objects.filter(name=city, country=country).first() + +# if not location: +# geocode_result = gmaps.geocode(f"{country}+{city}") + +# if len(geocode_result) > 0: +# lat = geocode_result[0]['geometry']['location']['lat'] +# lng = geocode_result[0]['geometry']['location']['lng'] +# location = Location.objects.create(name=city,city=city,country=country, latitude=lat, longitude=lng, season=season, type=3) +# else: +# print("No geocode for ", city) + + +# # role = Role.objects.filter(season=season,name=category).first() +# # if not role: +# # role = Role.objects.create(name=category, season=season) + +# delegate = Delegate.objects.create(name=name, country=country, location=location, category=cat, season=season) + +# seed_delegates() +# %% + +# def seed_teams_and_games(): +# matches = pd.read_excel('referees/fixtures/wales/v2/Matches Test December 2023.xlsx') #.set_index('Name') +# country = Country.objects.get(season=season,name='Wales') +# Team.objects.filter(season=season).delete() +# Day.objects.filter(season=season).delete() +# Round.objects.filter(season=season).delete() +# Game.objects.filter(season=season).delete() +# RefGame.objects.filter(scenario=scenario).delete() +# Competition.objects.filter(season=season).delete() +# for index, row in matches.iterrows(): +# if pd.isna(row['ID']): +# continue + +# id = row['ID'] +# descr = row['Match description'] +# home = row['Home team'] +# away = row['Away team'] + +# date_format = '%d.%m.%Y %H:%M:%S' +# matchdate = datetime.datetime.strptime(row['Match date (GMT)'], date_format) +# competition_name = row['Name'] +# rank = row['Rank'] +# round = row['Round'] +# facility = row['Facility'] +# place = row['Facility place name'] +# if pd.isna(rank): +# rank = 0 + +# if len(descr.split('-')) > 4: +# print("No team for ", descr) +# continue + +# competition = Competition.objects.filter(name=competition_name, season=season).first() +# if not competition: +# competition = Competition.objects.create(name=competition_name, season=season, rank=rank) + +# location = Location.objects.filter(name=facility, season=season).first() +# if not location: +# print("No location for ", facility) +# geocode_result = gmaps.geocode(f"{country}+{place}+{facility}") + +# if len(geocode_result) > 0: +# lat = geocode_result[0]['geometry']['location']['lat'] +# lng = geocode_result[0]['geometry']['location']['lng'] +# location = Location.objects.create(name=facility,city=place,country=country, latitude=lat, longitude=lng, season=season, type=1) +# else: +# print("No geocode for ", facility) + + +# matchdescr = descr.split(':')[0][:-1].strip() + +# homeTeam = Team.objects.filter(name=matchdescr.split('-')[0], season=season).first() +# if not homeTeam: +# homeTeam = Team.objects.create(name=matchdescr.split('-')[0],shortname=home, country=Country.objects.get(season=season,name='Wales'), location=location,latitude=location.latitude,longitude=location.longitude, season=season) +# awayTeam = Team.objects.filter(name=matchdescr.split('-')[1], season=season).first() +# if not awayTeam: +# awayTeam =Team.objects.create(name=matchdescr.split('-')[1],shortname=away, country=Country.objects.get(season=season,name='Wales'), season=season) + +# day = Day.objects.filter(date=matchdate.date(), day=matchdate.strftime('%Y-%m-%d'), round=1, season=season).first() +# if not day: +# day = Day.objects.create(date=matchdate.date(), day=matchdate.strftime('%Y-%m-%d'), round=1, season=season) + +# timeslot = TimeSlot.objects.filter(start=matchdate.time(), season=season).first() +# if not timeslot: +# timeslot = TimeSlot.objects.create(start=matchdate.time(), end=(matchdate+timedelta(hours=2)).time(), season=season, name=matchdate.time().strftime("%H%M")) + + +# roundObj = Round.objects.filter(name=round,number=round, season=season).first() +# if not roundObj: +# roundObj = Round.objects.create(name=round, number=round, season=season) +# roundObj.days.add(day) + + +# game = RefGame.objects.filter(location=location,round=round, homeTeam=homeTeam, awayTeam=awayTeam,scenario=scenario, competition=competition).first() +# if not game: +# RefGame.objects.create(location=location,round=round, homeTeam=homeTeam, awayTeam=awayTeam, day=day,timeslot=timeslot, scenario=scenario, competition=competition) + +# seed_teams_and_games() + +# # %% +# def seed_requirements(): +# requirements = pd.read_excel('referees/fixtures/wales/v2/Competition Ranks.xlsx',skiprows=1) + +# Requirement.objects.filter(season=season).delete() +# for index, row in requirements.iterrows(): +# comp = row['Competition Name'] +# rank = row['Competition Rank'] +# ref = row['Referee'] +# ref_cats = row['Category Required to Appoint As Ref'] +# assistant1 = row['Assistant Referee 1'] +# assistant2 = row['Assistant Referee 2'] +# assistant_cats = row['Category Required to appoint as Assistant Referee'] +# official = row['Fourth Official'] +# official_cats = row['Category Required to Appoint as Fourth Official'] +# max_dist = row['Maximum Distance for 1 Match Official to Travel (Miles)'] + +# try: +# max_dist = float(max_dist) +# except: +# max_dist = 0.0 + +# ref_categories = [] +# if not pd.isna(ref_cats): +# ref_categories = [ +# Category.objects.filter(name=cat, season=season).first() for cat in ref_cats.split(';') +# ] + +# assistant_categories = [] +# if not pd.isna(assistant_cats): +# assistant_categories = [ +# Category.objects.filter(name=cat, season=season).first() for cat in assistant_cats.split(';') +# ] + +# official_categories = [] +# if not pd.isna(official_cats): +# official_categories = [ +# Category.objects.filter(name=cat, season=season).first() for cat in official_cats.split(';') +# ] + + +# competition = Competition.objects.filter(name=comp, season=season).first() + +# if competition: +# competition.rank = rank +# competition.save() +# if ref_categories: +# req = Requirement.objects.create(season=season, competition=competition, role=Role.objects.get(name='Referee', season=season), max_distance=max_dist) +# for cat in ref_categories: +# req.categories.add(cat) + +# if assistant_categories: +# req1 = Requirement.objects.create(season=season, competition=competition, role=Role.objects.get(name='Assistant Referee 1', season=season), max_distance=max_dist) +# req2 = Requirement.objects.create(season=season, competition=competition, role=Role.objects.get(name='Assistant Referee 2', season=season), max_distance=max_dist) +# for cat in assistant_categories: +# req1.categories.add(cat) +# req2.categories.add(cat) + +# if official_categories: +# req = Requirement.objects.create(season=season, competition=competition, role=Role.objects.get(name='Fourth Official', season=season), max_distance=max_dist) +# for cat in official_categories: +# req.categories.add(cat) + + + +# seed_requirements() + + +# %% + + + +availabilities = pd.read_excel('referees/fixtures/wales/Anonymised Referee Unavailability for Ligalytics.xlsx') + +# %% +availabilities['Date from'] = pd.to_datetime(availabilities['Date from'], format='%d.%m.%Y', errors='coerce') +availabilities['Date to'] = pd.to_datetime(availabilities['Date to'], format='%d.%m.%Y', errors='coerce') + + +# %% + +# %% +DelBlocking.objects.filter(season=season).delete() +for index, row in availabilities.iterrows(): + start = row['Date from'] + end = row['Date to'] + dummy = row['Dummy ID'] + if pd.isna(end): + end = start + if start >= datetime.datetime(2023,12,1) and end <= datetime.datetime(2024,1,1): + for i in range((end-start).days+1): + day = start + datetime.timedelta(days=i) + available = (row[day.weekday()+5] == 'X') + if not available: + delegate = Delegate.objects.filter(name=f"Person {dummy}", season=season).first() + dayObj = Day.objects.filter(day=day.strftime('%Y-%m-%d'), season=season).first() + if delegate and dayObj: + DelBlocking.objects.create(delegate=delegate, day=dayObj, season=season) + print(f"Blocked {delegate} on {dayObj}") + +# %% diff --git a/stats/scripts/script.py b/stats/scripts/script.py new file mode 100644 index 0000000..097c07c --- /dev/null +++ b/stats/scripts/script.py @@ -0,0 +1,234 @@ +# %% + + +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'] = {} +import django +django.setup() + +from scheduler.models import * +from common.models import * +from common.stats.models import * +import csv +import datetime +# %% +# MOST ACTIVE + + +login_dict = defaultdict(lambda:0) +browsers = defaultdict(lambda:0) +devices = defaultdict(lambda:0) + +with open('analyze_access.log', newline='') as csvfile: + reader = csv.reader(csvfile) + for row in reader: + date = datetime.datetime.strptime(row[0], '%Y-%m-%d %H:%M:%S') + splitstr = row[1].split('LOGGED IN:') + if len(splitstr) > 1: + kind = splitstr[0].split('INFO')[1].strip() + user = splitstr[1].split('from')[0].strip() + plattform = splitstr[1].split('with (') + if len(plattform) > 1: + plattform = plattform[1].split('/')[0].strip() + login_dict[(kind,user)] += 1 + + + if 'Firefox' in row[1]: + browsers['Firefox'] += 1 + if 'Chrome' in row[1]: + browsers['Chrome'] += 1 + if 'Safari' in row[1]: + browsers['Safari'] += 1 + if 'Edge' in row[1]: + browsers['Edge'] += 1 + if 'Opera' in row[1]: + browsers['Opera'] += 1 + if 'MSIE' in row[1]: + browsers['MSIE'] += 1 + if 'Trident' in row[1]: + browsers['Trident'] += 1 + if 'Netscape' in row[1]: + browsers['Netscape'] += 1 + if 'Mozilla' in row[1]: + browsers['Mozilla'] += 1 + if 'AppleWebKit' in row[1]: + browsers['AppleWebKit'] += 1 + if 'Gecko' in row[1]: + browsers['Gecko'] += 1 + if 'KHTML' in row[1]: + browsers['KHTML'] += 1 + + + if 'iPhone' in row[1]: + devices['iPhone'] += 1 + elif 'PC' in row[1]: + devices['PC'] += 1 + elif 'K' in row[1]: + devices['Android'] += 1 + elif 'Other' in row[1]: + devices['other'] += 1 + + + +browsers = sorted(browsers.items(), key=lambda x:x[1], reverse=True)[:10] +devices = sorted(devices.items(), key=lambda x:x[1], reverse=True)[:10] + + +# %% +login_dict = sorted(login_dict.items(), key=lambda x:x[1], reverse=True) +# %% + + + +# Monday = 0 +weekday_dict = defaultdict(lambda:[0,0,0,0,0,0,0]) +weekday_ranking = [0,0,0,0,0,0,0] +hours_dict = defaultdict(lambda:[0]*24) +hours_ranking = [0]*24 +clicks_per_day = defaultdict(lambda:defaultdict(lambda:0)) +active_days = defaultdict(lambda:set()) +active_hours = defaultdict(lambda:set()) +wishes_added = defaultdict(lambda:0) +logouts = defaultdict(lambda:0) +solutions = defaultdict(lambda:0) +days = defaultdict(lambda:0) +deletions = defaultdict(lambda:0) +downloads = defaultdict(lambda:0) +actions = defaultdict(lambda:0) +active_teams = defaultdict(lambda:0) +active_clubs = defaultdict(lambda:0) + +with open('analyze_info.log', newline='') as csvfile: + reader = csv.reader(csvfile) + for row in reader: + date = datetime.datetime.strptime(row[0], '%Y-%m-%d %H:%M:%S') + verbose = row[1].split('INFO') + if len(verbose) > 1: + user = verbose[1].split(':')[0].strip() + kind = verbose[1].split(':')[1].strip() + if user == 'md': + user = 'martin' + weekday_dict[user][date.weekday()] += 1 + weekday_ranking[date.weekday()] += 1 + hours_dict[user][date.hour] += 1 + hours_ranking[date.hour] += 1 + actions[user] += 1 + + clicks_per_day[user][date.strftime("%Y-%m-%d")] += 1 + active_days[user].add(date.strftime("%Y-%m-%d")) + active_hours[user].add(date.strftime("%Y-%m-%d %H")) + + if '/add/' in kind: + wishes_added[user] += 1 + if kind == '/accounts/logout/': + logouts[user] += 1 + + if kind == '/schedule_sol/': + solutions[user] += 1 + + if 'day' in kind: + days[user] += 1 + + if 'delete' in kind: + deletions[user] += 1 + + if 'download' in kind: + downloads[user] += 1 + + if user == "SINGLETEAM": + active_teams[kind.split('/')[0].strip()] += 1 + + if user == "CLUB": + active_clubs[kind.split('/')[0].strip()] += 1 + + + +downloads = sorted(downloads.items(), key=lambda x:x[1], reverse=True)[:10] +wishes_added = sorted(wishes_added.items(), key=lambda x:x[1], reverse=True)[:10] +logouts = sorted(logouts.items(), key=lambda x:x[1], reverse=True)[:10] +solutions = sorted(solutions.items(), key=lambda x:x[1], reverse=True)[:10] +days = sorted(days.items(), key=lambda x:x[1], reverse=True)[:10] +deletions = sorted(deletions.items(), key=lambda x:x[1], reverse=True)[:10] +active_days = [(user,len(active_days[user])) for user in active_days] +active_days = sorted(active_days, key=lambda x:x[1], reverse=True)[:10] +active_hours = [(user,len(active_hours[user])) for user in active_hours] +active_hours = sorted(active_hours, key=lambda x:x[1], reverse=True)[:10] +actions = sorted(actions.items(), key=lambda x:x[1], reverse=True)[:10] +active_teams = sorted(active_teams.items(), key=lambda x:x[1], reverse=True)[:10] +active_clubs = sorted(active_clubs.items(), key=lambda x:x[1], reverse=True)[:10] + +for user in clicks_per_day: + clicks_per_day[user] = sorted(clicks_per_day[user].items(), key=lambda x:x[1], reverse=True) +# %% + + + + +staff_user = ['martin','stephanwestphal','dirk','philipp','Kilian'] + +actions = { + u[0]:u[1] for u in actions +} +active = { + u[0]:u[1] for u in active_days +} +for user in staff_user: + print(user) + print(actions[user]//active[user]) + +# for s in staff_user: +# print(s) +# print(weekday_dict[s]) +# print(hours_dict[s]) +# print('') +# # %% +# commits_per_hour = "{"0":15,"1":9,"2":5,"3":6,"4":3,"5":1,"6":22,"7":28,"8":63,"9":111,"10":161,"11":194,"12":160,"13":141,"14":145,"15":150,"16":145,"17":131,"18":85,"19":93,"20":110,"21":112,"22":76,"23":34}" +# commits_per_weekday = "{"Sunday":135,"Monday":338,"Tuesday":341,"Wednesday":388,"Thursday":388,"Friday":302,"Saturday":108}" + + +# commits_per_hour = json.loads(commits_per_hour.replace('"','"')) +# commits_per_weekday = json.loads(commits_per_weekday.replace('"','"')) + +# %% +import json +with open('trello.json') as f: + data = json.load(f) + +created = defaultdict(lambda:0) +for a in data['actions']: + if a['type'] == 'createCard': + created[a['memberCreator']['fullName']] += 1 +# %% + +# MOST SCENARIOS +scenarios = {} +for league in League.objects.all(): + scenarios[league.name] = Scenario.objects.filter(season__league=league,lastComputation__gte=datetime.datetime(2023,1,1)).count() + +scenarios = sorted(scenarios.items(), key=lambda x:x[1], reverse=True) +# %% + +# MOST WISHES +wishes = {} +for league in League.objects.all(): + for scenario in Scenario.objects.filter(season__league=league,lastComputation__gte=datetime.datetime(2023,1,1)): + wishes[league.name] = HAWish.objects.filter(scenario=scenario).count()+EncWish.objects.filter(scenario=scenario).count()+Blocking.objects.filter(scenario=scenario).count()+Pairing.objects.filter(scenario=scenario).count() + +wishes = sorted(wishes.items(), key=lambda x:x[1], reverse=True)[:10] +# %% diff --git a/uefa/scripts/afc_chl23_seeder.py b/uefa/scripts/afc_chl23_seeder.py new file mode 100755 index 0000000..4677695 --- /dev/null +++ b/uefa/scripts/afc_chl23_seeder.py @@ -0,0 +1,200 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + +gmaps = googlemaps.Client( + key='AIzaSyB76EhR4OqjdXHQUiTkHZC0Svx_7cPGqyU') + +# %% + +scenario = Scenario.objects.get(id=10) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append(("Group A", "Paxtakor Taschkent, Uzbekistan (UZB)",)) +new_teams.append(("Group A", "al-Fayha FC, Saudi Arabia (KSA)",)) +new_teams.append(("Group A", "Ahal FK, Turkmenistan (TKM)",)) +new_teams.append(("Group A", "al Ain Club, United Arab Emirates (UAE)",)) +new_teams.append(("Group B", "al-Sadd SC, Qatar (QAT)",)) +new_teams.append(("Group B", "Nasaf Karschi, Uzbekistan (UZB)",)) +new_teams.append(("Group B", "al-Faisaly, Jordan (JOR)",)) +new_teams.append(("Group B", "Sharjah FC, United Arab Emirates (UAE)",)) +new_teams.append(("Group C", "Ittihad FC, Saudi Arabia (KSA)",)) +new_teams.append(("Group C", "Sepahan FC, Iran (IRN)",)) +new_teams.append(("Group C", "al-Quwa al-Dschawiya, Iraq (IRQ)",)) +new_teams.append(("Group C", "AGMK FC, Uzbekistan (UZB)",)) +new_teams.append(("Group D", "al-Hilal, Saudi Arabia (KSA)",)) +new_teams.append(("Group D", "Nassaji Mazandaran, Iran (IRN)",)) +new_teams.append(("Group D", "Mumbai City FC, India (IND)",)) +new_teams.append(("Group D", "Navbahor Namangan, Uzbekistan (UZB)",)) +new_teams.append(("Group E", "Persepolis Teheran, Iran (IRN)",)) +new_teams.append(("Group E", "al-Duhail SC, Qatar (QAT)",)) +new_teams.append(("Group E", "FC Istiklol, Tajikistan (TJK)",)) +new_teams.append(("Group E", "al-Nassr FC, Saudi Arabia (KSA)",)) +new_teams.append(("Group F", "Jeonbuk Hyundai Motors, South Korea (KOR)",)) +new_teams.append(("Group F", "Bangkok United, Thailand (THA)",)) +new_teams.append(("Group F", "Lion City Sailors, Singapore (SGP)",)) +new_teams.append(("Group F", "Kitchee SC, Hong Kong (HKG)",)) +new_teams.append(("Group G", "Yokohama F. Marinos, Japan (JPN)",)) +new_teams.append(("Group G", "Shandong Taishan, China (CHN)",)) +new_teams.append(("Group G", "Kaya FC-Iloilo, Philippines (PHI)",)) +new_teams.append(("Group G", "Incheon United, South Korea (KOR)",)) +new_teams.append(("Group H", "Buriram United, Thailand (THA)",)) +new_teams.append(("Group H", "Ventforet Kofu, Japan (JPN)",)) +new_teams.append(("Group H", "Melbourne City FC, Australia (AUS)",)) +new_teams.append(("Group H", "Zhejiang Professional FC, China (CHN)",)) +new_teams.append(("Group I", "Ulsan Hyundai, South Korea (KOR)",)) +new_teams.append(("Group I", "Kawasaki Frontale, Japan (JPN)",)) +new_teams.append(("Group I", "Johor Darul Ta’zim FC, Malaysia (MAS)",)) +new_teams.append(("Group I", "BG Pathum United FC, Thailand (THA)",)) +new_teams.append(("Group J", "Wuhan Three Towns FC, China (CHN)",)) +new_teams.append(("Group J", "Pohang Steelers, South Korea (KOR)",)) +new_teams.append(("Group J", "Hà Nội FC, Vietnam (VIE)",)) +new_teams.append(("Group J", "Urawa Red Diamonds, Japan (JPN)",)) + + +# %% + +pot_converter = { + "A": 1, + "B": 2, + "C": 3, + "D": 4, + "E": 5, + "F": 6, + "G": 7, + "H": 8, + "I": 9, + "J": 10, +} + +Conference.objects.filter(scenario=scenario).delete() +for g in pot_converter.keys(): + Conference.objects.create(scenario=scenario, name=f"Group {g}", display_group=True, collapseInView=False) +Conference.objects.create(scenario=scenario, name='West', collapseInView=True, display_group=False) +Conference.objects.create(scenario=scenario, name='East', collapseInView=True, display_group=False) + +pot = 1 +for i,t in enumerate(new_teams): + group = t[0] + team_name = t[1].split(",")[0] + team_country = t[1].split(",")[1].split("(")[0].strip() + team_country_code = t[1].split(",")[1].split("(")[1].split(")")[0].strip() + + teamObj = Team.objects.filter(name=team_name, season=scenario.season).first() + + if not teamObj: + gcountry = GlobalCountry.objects.filter(uefa=team_country_code) + if not gcountry: + gcountry = GlobalCountry.objects.filter(alpha3=team_country_code) + if not gcountry: + print("\t", "NOT FOUND") + + geocode_result = gmaps.geocode(team_name+" "+team_country) + if len(geocode_result) > 0: + location = geocode_result[0]['geometry']['location'] + print("\t", location) + else: + location = {'lat': 0, 'lng': 0} + print("\t", "NOT FOUND") + + teamObj = Team.objects.create( + name=team_name, + country=team_country_code, + season=scenario.season, + active=True, + latitude=location['lat'], + longitude=location['lng'], + ) + + + teamObj.shortname = teamObj.name[:5] + teamObj.position = i+1 + teamObj.pot = pot + teamObj.save() + pot += 1 + if pot > 4: + pot = 1 + + Conference.objects.get(scenario=scenario, name=group).teams.add(teamObj) + + if pot_converter[group.split(" ")[1]] <= 5: + Conference.objects.get(scenario=scenario, name="West").teams.add(teamObj) + else: + Conference.objects.get(scenario=scenario, name="East").teams.add(teamObj) + + + +# %% diff --git a/uefa/scripts/django_script.py b/uefa/scripts/django_script.py new file mode 100755 index 0000000..5c1b83b --- /dev/null +++ b/uefa/scripts/django_script.py @@ -0,0 +1,54 @@ +# %% + +# Wichtiger Header + Xpress + +""" +############################################################################################################## +""" + +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' + + +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() + + +""" +############################################################################################################## +""" + +# %% + + + +# Ab hier dann der eigene Code + + +from scheduler.models import * + + +scenario = Scenario.objects.first() +print(scenario.name) + + +# %% diff --git a/uefa/scripts/ijs_u911_seedgames.py b/uefa/scripts/ijs_u911_seedgames.py new file mode 100755 index 0000000..37423df --- /dev/null +++ b/uefa/scripts/ijs_u911_seedgames.py @@ -0,0 +1,174 @@ +# %% +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 + +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 + +scenario = Scenario.objects.get(id=8506) +season = scenario.season +days = Day.objects.filter(season=scenario.season) +teams = Team.objects.filter(season=scenario.season) +federation = FederationMember.objects.filter(season=scenario.season).first().federation +stadiums = Stadium.objects.filter(federation=federation) +stadiumtimeslotblockings = StadiumTimeSlotBlocking.objects.filter(stadiumTimeSlot__stadium__in=stadiums) + +# %% + +getTeamByName = {} +for t in teams: + + if t.name == "AHOUD Devils Nijmegen U9": + name = "Nijmegen Devils" + elif t.name == "Unis Flyers Heerenveen U7/U9/U11": + name="UNIS Flyers Heerenveen" + elif t.name == "IJCU Utrecht U9/U11": + name="Utrecht Dragons" + else: + name = t.name.split("U7")[0].split("U9")[0].split("U11")[0].strip() + getTeamByName[name] = t + +getStadiumByName = {} +for s in stadiums: + if s.name == "IJssportcentrum Eindhoven (kleine hal)": + name = "IJssportcentrum Eindhoven (Kleine hal)" + elif s.name == "Sportiom": + name = "Den Bosch" + else: + name = s.name + getStadiumByName[name] = s + + +# %% + +stsb = [] +with open('LOS-ScheduleImportTemplate_EN-U9.csv', newline='') as csvfile: + reader = csv.reader(csvfile) + next(reader, None) + next(reader, None) + for row in reader: + # print(row) + + raw_dayobj = datetime.datetime.strptime(row[3],'%m/%d/%Y') + raw_time = row[4] + raw_stadium= row[7] + raw_team1 = row[8].split("U9")[0].strip() + raw_team2 = row[9].split("U9")[0].strip() + + # day + day = days.filter(day=raw_dayobj.strftime('%Y-%m-%d')).first() + if not day: + print("\tERROR",day) + break + + # teams + team1 = getTeamByName.get(raw_team1, None) + team2 = getTeamByName.get(raw_team2,None) + + if not team1 or not team2: + print("\tERROR",team1,team2) + break + + # stadium + stadium = None + for s in getStadiumByName.keys(): + if raw_stadium in s: + stadium = getStadiumByName[s] + break + + if not stadium: + print("\tERROR",stadium) + break + + # timeslot + weekday = raw_dayobj.strftime('%A')+"s" + start = datetime.time(int(raw_time.split(":")[0]),int(raw_time.split(":")[1])) + end = datetime.time(int(raw_time.split(":")[0])+2 % 24,int(raw_time.split(":")[1])) + timeslot = StadiumTimeSlot.objects.filter(stadium=stadium,weekday=weekday,start=start,end=end) + if timeslot.count() > 1: + print("ERROR",stadium,weekday,start,end) + break + else: + timeslot = timeslot.first() + if not timeslot: + club = StadiumTimeSlot.objects.filter(stadium=stadium).first().club + print("Missing",stadium,weekday,start,end) + timeslot = StadiumTimeSlot.objects.create( + stadium=stadium, + club=club, + weekday=weekday, + start=start, + end=end + ) + + + + stsb.append(StadiumTimeSlotBlocking(stadiumTimeSlot = timeslot, + homeTeam = team1, + awayTeam = team2, + day = day.day, + ) + ) + + +StadiumTimeSlotBlocking.objects.bulk_create(stsb) + +# %% + + + + diff --git a/uefa/scripts/script copy.py b/uefa/scripts/script copy.py new file mode 100755 index 0000000..bdd8f4e --- /dev/null +++ b/uefa/scripts/script copy.py @@ -0,0 +1,214 @@ +# %% +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, XPRESS_PY +import xpress as xp +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 common.functions import getRandomHexColor +from scheduler.models import Season, Scenario, Team, DayObj, CountryClash, Country + +from qualifiers.draws import groupTeams, optimize_inversions4 +from scheduler.solver.tasks.optimize import optimize + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt +from datetime import timedelta + +# %% + + + + + + +# import requests + +# data = json.loads(b'{\r\n "season": "UCL 24/25",\r\n "team": 52277,\r\n "pots": [1,2,3,4],\r\n "games":[[50051, 52747], [50138, 50051], [50051, 50147]]\r\n}') +# headers = {'Content-type': 'application/json', 'Authorization': 'R6v1e9Q5W8aS3b7C4x2KpZqL9yFmXnDz'} + +# requests.post('http://localhost:8000/draws/checker/',json=data, headers=headers) + + + + +# %% +# # scenario.sol_solution +scenario = Scenario.objects.get(id=1) + +"""GUROBI""" + + +scenario_id = scenario.id +fixed_games = [] +print("\n") +ttt = time.time() +tt = time.time() +scenario = Scenario.objects.get(id=scenario_id) +teamObjects = Team.objects.filter(season=scenario.season,active=True).exclude(name='-').values() +teams = [t['id'] for t in teamObjects] +t_pot = {t['id'] : t['pot'] for t in teamObjects} +t_name = {t['id'] : t['name'] for t in teamObjects} +t_country = {t['id'] : t['countryObj_id'] for t in teamObjects if t['countryObj_id'] } +t_master = {t['id'] : None for t in teamObjects} +pots = sorted(list(set(t_pot.values()))) +p_teams = { p:[t for t in teams if t_pot[t]==p] for p in pots } +countries = [c.id for c in scenario.season.countries.all()] +teams_from_country = { c:[t for t in teams if t_country[t]==c] for c in countries } +# print ("CHECKING", len(fixed_games) , "games!") + +drawFound = False +for draw in Draw.objects.filter(season=scenario.season, active=True ): + drawFound = True + # print ("DRAW ",draw) + +supergroups = [] +sg_teams = {} +sg_groups = {} +sg_pots = {} +sg_games_per_team = {} +sg_games_against_pot = {} +for sg in SuperGroup.objects.filter(draw = draw): + supergroups.append(sg.id) + # sg_teams[sg.id] = [ t.id for t in sg.teams.all()] + # sg_groups[sg.id] = [ t.id for t in sg.groups.all()] + sg_teams[sg.id] = sg.teams.values_list('id',flat=True) + sg_groups[sg.id] = sg.groups.values_list('id',flat=True) + + maxpot = max([ t_pot[t] for t in sg_teams[sg.id]]) + sg_pots[sg.id]=range(1, maxpot+1) + sg_games_per_team[sg.id]= sg.gamesPerTeam + sg_games_against_pot[sg.id]= sg_games_per_team[sg.id]/maxpot + for t in sg_teams[sg.id]: + t_master[t]=sg.id + + + possible_opponents = { (t,p): [t2 for t2 in sg_teams[sg.id] if t_country[t2]!=t_country[t] and t_pot[t2]==p] for t in sg_teams[sg.id] for p in pots} + + + +print("\t1",time.time()-tt) +tt = time.time() + +model = xp.problem(name='Draws', sense=xp.minimize) +model.setControl ('outputlog', 0) + +x = {} + +gameVarExists= { (t1,t2): False for t1 in teams for t2 in teams} + +for sg in supergroups: + for t1 in sg_teams[sg]: + for t2 in sg_teams[sg]: + if t_country[t1] != t_country[t2]: + # x[t1, t2] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2),lowBound=0, upBound=1, cat=pulp.LpInteger) + x[t1, t2] = xp.var(ub=1, vartype=xp.integer) + gameVarExists[(t1,t2)]=True + model.addVariable(x) + # REQUIREMENTS + for t in sg_teams[sg]: + for p in sg_pots[sg]: + if sg_games_against_pot[sg]==2: + model.addConstraint(xp.Sum(x[t,t2] for t2 in p_teams[p] if gameVarExists[(t,t2)]) == 1) + model.addConstraint(xp.Sum(x[t2,t] for t2 in p_teams[p] if gameVarExists[(t2,t)]) == 1) + else: + model.addConstraint(xp.Sum(x[t,t2] for t2 in p_teams[p] if gameVarExists[(t,t2)]) <= 1) + model.addConstraint(xp.Sum(x[t2,t] for t2 in p_teams[p] if gameVarExists[(t2,t)]) <= 1) + model.addConstraint(xp.Sum(x[t2,t] + x[t,t2] for t2 in p_teams[p] if gameVarExists[(t,t2)]) == sg_games_against_pot[sg]) + if p%2==0: + # exactly one home game against pots 1,2, one against pot 3,4 one against pots 5,6 + model.addConstraint(xp.Sum(x[t,t2] for t2 in p_teams[p-1]+p_teams[p] if gameVarExists[(t,t2)]) == 1) + + for c in countries: + if c != t_country[t]: + tcnt = [t2 for t2 in teams_from_country[c] if t_master[t]==t_master[t2]] + if len(tcnt)>draw.max_opponents_from_same_country: + model.addConstraint(xp.Sum(x[t,t2]+x[t2,t] for t2 in teams_from_country[c] if t_master[t]==t_master[t2] and gameVarExists[(t,t2)]) <= draw.max_opponents_from_same_country) + + +print("\t2",time.time()-tt) +tt = time.time() + +for cl in draw.clashes.filter(type="Game").prefetch_related('countries','teams'): + # cl_countries = [ c.id for c in cl.countries.all() ] + cl_countries = cl.countries.values_list('id',flat=True) + cl_teams = list(cl.teams.values_list('id',flat=True)) + [ t for t in teams if t_country[t] in cl_countries ] + cl_teams = list(set(cl_teams)) + # print("CLASH" ,cl, cl_countries , cl_teams , [ (t_name[t] , t_country[t]) for t in cl_teams ] ) + if cl.minTeams>0: + model.addConstraint(xp.Sum(x[t1,t2] for t1 in cl_teams for t2 in cl_teams if gameVarExists[(t1,t2)]) >= cl.minTeams) + # print ("min implemented") + if len(cl_teams)>1: + model.addConstraint(xp.Sum(x[t1,t2] for t1 in cl_teams for t2 in cl_teams if gameVarExists[(t1,t2)]) <= cl.maxTeams) + # print ("max implemented") + +print("\t3",time.time()-tt) +# FIXATIONS +for (t1,t2) in fixed_games: + if gameVarExists[(t1,t2)]: + model += x[t1,t2] == 1 + else: + print ("GAME DOES NOT EXIST!!") + print( False) + +print("\t3",time.time()-tt) +# for (t1,t2) in [(t1,t2) for (t1,t2) in x.keys() if t1 < t2]: +for (t1,t2) in [(t1,t2) for (t1,t2) in x.keys() if t1 < t2]: + model.addConstraint(x[t1,t2] + x[t2,t1] <= 1) + # model.addConstraint(x[t1,t2] + x[t2,t1] <= 1) + +print("\t4",time.time()-tt) +# model.solve(XPRESS(msg=0,timeLimit=120,keepFiles=0)) +tt = time.time() + +# model.solve(XPRESS_PY(msg=0,timeLimit=120)) +model.solve() +# model.solve(GUROBI(msg=0,timeLimit=120)) + +# model.solve(PULP_CBC_CMD(timeLimit = 120, threads = 8,msg=0)) + +print("\t5",time.time()-tt) +print("TIME",time.time()-ttt) \ No newline at end of file diff --git a/uefa/scripts/script.py b/uefa/scripts/script.py new file mode 100755 index 0000000..2dd59bc --- /dev/null +++ b/uefa/scripts/script.py @@ -0,0 +1,301 @@ +# %% +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 common.functions import getRandomHexColor +from scheduler.models import Season, Scenario, Team, DayObj, CountryClash, Country + +from qualifiers.draws import groupTeams, optimize_inversions4 +from scheduler.solver.tasks.optimize import optimize + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt +from datetime import timedelta + +# %% + + + + + + +import requests + +data = json.loads(b'{\r\n "season": "UCL 24/25",\r\n "team": 52277,\r\n "pots": [1,2,3,4],\r\n "games":[[50051, 52747], [50138, 50051], [50051, 50147]]\r\n}') +headers = {'Content-type': 'application/json', 'Authorization': 'R6v1e9Q5W8aS3b7C4x2KpZqL9yFmXnDz'} + +requests.post('http://localhost:8000/draws/checker/',json=data, headers=headers) + + + + +# %% + + + + + + + +# %% + + +scenario = Scenario.objects.get(id=9340) + +scenario.sol_solution + + +# %% +season = scenario.season + + + +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,prio__in=['Hard','A']) +pairings_dependencies = Pairing.objects.filter(scenario=scenario,active=True,team2__in=higherTeams,prio__in=['Hard','A']) +days = Day.objects.filter(season=scenario.season, maxGames__gt=0) +blockings = Blocking.objects.filter(scenario=scenario) +seedgames = EncWish.objects.filter(scenario=scenario,active=True,reason__contains='SEED') +encwishes = EncWish.objects.filter(scenario=scenario,active=True,prio__in=['Hard','A'],timeframe=1,forOneDay=False,forOneDayNum=1) +hawishes = HAWish.objects.filter(scenario=scenario,active=True,prio__in=['Hard','A'],forEachDay=False,forOneDay=False) +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.team1.id,p.team2.id):prioVal[p.prio] for p in pairings +} +encwish_prio = { + p.id:prioVal[p.prio] for p in encwishes +} +hawish_prio = { + p.id:prioVal[p.prio] for p in hawishes +} +dependency_prio = { + (p.team1.id,p.team2.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]] + + + + +# BASICWISHES +# for e in hawishes.order_by('prio'): +# if e.timeslots.count() > 0: +# continue +# elemTeams = [[t] for t in e.get_teams()] if e.forEachTeam else [e.get_teams()] +# elemRounds = [set([f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" for d in e.get_days()])] +# if e.minGames > 0: +# for teamset in elemTeams: +# for roundset in elemRounds: +# if e.homeAway == "Home": +# print("ADDED HA MIN",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.minGames) +# elif e.homeAway == "Away": +# print("ADDED HA MIN",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.minGames) +# else: +# print("ADDED HA MIN",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.minGames) +# if e.maxGames >= 0: +# for teamset in elemTeams: +# for roundset in elemRounds: +# if e.homeAway == "Home": +# print("ADDED HA MAX",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.maxGames) +# elif e.homeAway == "Away": +# print("ADDED HA MAX",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.maxGames) +# else: +# print("ADDED HA MAX",e.id,e.prio,e.homeAway,e.reason,teamset,roundset,e.maxGames) + +for e in encwishes.filter(id=134891).order_by('prio'): + if e.timeslots.count() > 0: + continue + elemTeams1 = [[t] for t in e.get_teams1()] if e.forEachTeam1 else [e.get_teams1()] + elemTeams2 = [[t] for t in e.get_teams2()] if e.forEachTeam2 else [e.get_teams2()] + + + if all(t.id in uel_teams for t in e.get_teams1()): + elemRounds = [[f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}"] for d in e.get_days() if f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" in uel_rounds] if e.forEachDay else \ + [set([f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" for d in e.get_days() if f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" in uel_rounds])] + elif all(t.id in uecl_teams for t in e.get_teams1()): + elemRounds = [[f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}"] for d in e.get_days() if f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" in uecl_rounds] if e.forEachDay else \ + [set([f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" for d in e.get_days() if f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" in uecl_rounds])] + else: + elemRounds = [[f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}"] for d in e.get_days()] if e.forEachDay else \ + [set([f"{d.date.isocalendar()[0]}-{d.date.isocalendar()[1]}" for d in e.get_days()])] + + + + if e.minGames > 0: + if e.useEncounterGroups: + for i,roundset in enumerate(elemRounds): + encounters = Encounter.objects.filter(encounterGroup__in=e.encounterGroups.all()) + if e.symmetry: + print("ADDED ENC (SYMMETRY) MIN",e.id,e.prio,e.reason,[(t1,t2) for enc in encounters for t1 in enc.homeTeams.all() for t2 in enc.awayTeams.all()],roundset,e.minGames,e.symmetry,i) + else: + print("ADDED ENC MIN",e.id,e.prio,e.reason,[(t1,t2) for enc in encounters for t1 in enc.homeTeams.all() for t2 in enc.awayTeams.all()],roundset,e.minGames,e.symmetry,i) + + else: + for teamset1 in elemTeams1: + for teamset2 in elemTeams2: + for i,roundset in enumerate(elemRounds): + print(roundset) + print(teamset1) + print(teamset2) + if e.symmetry: + print("\tADDED ENC (SYMMETRY) MIN",e.id,e.prio,e.reason,teamset1,teamset2,roundset,e.minGames,e.symmetry,i) + else: + print("\tADDED ENC MIN",e.id,e.prio,e.reason,teamset1,teamset2,roundset,e.minGames,e.symmetry,i) + if e.maxGames >= 0: + if e.useEncounterGroups: + for i,roundset in enumerate(elemRounds): + print(roundset) + encounters = Encounter.objects.filter(encounterGroup__in=e.encounterGroups.all()) + for enc in encounters: + print(enc) + if e.symmetry: + print("\tADDED ENC (SYMMETRY) MAX",e.id,e.prio,e.reason,[(t1,t2) for enc in encounters for t1 in enc.homeTeams.all() for t2 in enc.awayTeams.all()],roundset,e.maxGames,e.symmetry,i) + else: + print("\tADDED ENC MAX",e.id,e.prio,e.reason,[(t1,t2) for enc in encounters for t1 in enc.homeTeams.all() for t2 in enc.awayTeams.all()],roundset,e.maxGames,e.symmetry,i) + else: + for teamset1 in elemTeams1: + for teamset2 in elemTeams2: + for i,roundset in enumerate(elemRounds): + print(roundset) + print(teamset1) + print(teamset2) + if e.symmetry: + print("\tADDED ENC (SYMMETRY) MAX",e.id,e.prio,e.reason,teamset1,teamset2,roundset,e.maxGames,e.symmetry,i) + else: + print("\tADDED ENC MAX",e.id,e.prio,e.reason,teamset1,teamset2,roundset,e.maxGames,e.symmetry,i) + +# %% diff --git a/uefa/scripts/solve_uefa24.py b/uefa/scripts/solve_uefa24.py new file mode 100755 index 0000000..187d945 --- /dev/null +++ b/uefa/scripts/solve_uefa24.py @@ -0,0 +1,173 @@ +# %% +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 scheduler.solver.tasks.optimize_submodels import ueluecl24_basicmodell_v2 + +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 + + +scenario = Scenario.objects.get(id=9051) +# scenario = Scenario.objects.get(id=9368) +# scenario = Scenario.objects.get(id=1) +season = scenario.season + +s2 = scenario.id +user_name = 'md' +user_is_staff = True +runMode = 'New' +localsearch_time = 0 +RUN_ENV = 'local' +SOLVER = 'xpress' + + +# Conference.objects.filter(scenario=scenario,name__in=["UEL","UECL"]).update(reopt=True) + +sol = optimize(task=None, s2=s2, user_name=user_name, user_is_staff=user_is_staff, + runMode=runMode, localsearch_time=localsearch_time, RUN_ENV=RUN_ENV, solver=SOLVER) + + + +# ueluecl24_basicmodell_v2(SOLVER,scenario,[],[],maxTime=3600) + + +# %% + +# hawishes = HAWish.objects.filter(scenario=scenario) + +# # elemHaWishes ={e['id'] : [] for e in hawishes} +# # elemHaWishTeams={} +# # elemHaWishDays ={} +# # elemHaWishFirstDay ={} +# # elemHaWishNum ={} + +# cntr =1 +# for e in hawishes: +# elemTeams = [[t] for t in e.get_teams()] if e.forEachTeam else [e.get_teams()] +# elemRounds = [[d] for d in e.get_rounds()] if e.forEachDay else [e.get_rounds()] + +# if e.homeAway == "HOME": +# if e.minGames > 0: +# for teamset in elemTeams: +# for roundset in elemRounds: +# model += lpSum([x[r,t.id,t2.id] for t in teamset for r in roundset for t2 in teams if (r,t.id,t2.id) in x.keys()]) >= e.minGames - seedViolation[seed.id] + + + + + +# # elemHaWishes[e['id']]=[] +# # allElemDays=[] +# # thisDaySet=[] +# # lastDaySet = [] +# # # print("elemDays",elemDays) +# # for d in elemDays : +# # # print (e) +# # if (e['forEachDay'] or e['forOneDay']) and e['timeframe']!=1: +# # thisDaySet=[] +# # # day1= parse(getDayById[d[0]]['day']) +# # day1= getDateTimeDay[d[0]] +# # if e['timeframe']>1: +# # day2=day1 + datetime.timedelta(days=e['timeframe']-1) +# # for d3 in days: +# # # dt = parse(getDayById[d3]['day']) +# # dt= getDateTimeDay[d3] +# # if day1<=dt and dt<=day2 : +# # thisDaySet.append(d3) +# # else: +# # r1 = getDayById[d[0]]['round'] +# # for d3 in days: +# # dt= getDateTimeDay[d3] +# # # dt = parse(getDayById[d3]['day']) +# # if day1<=dt and r1 <= getRoundByDay[d3] and getRoundByDay[d3]< r1 + (-e['timeframe']) : +# # thisDaySet.append(d3) +# # # print (" ROUND HA WISH ", e['reason'], thisDaySet, e['timeframe']) +# # else: +# # thisDaySet=d + +# # # only create wish id new day set is superset +# # if len([d for d in thisDaySet if d not in lastDaySet])>0: +# # for t in elemTeams: +# # cntr+=1 +# # elemHaWishes[e['id']].append(cntr) +# # elemHaWishTeams[cntr]=t +# # elemHaWishDays[cntr]=thisDaySet.copy() +# # elemHaWishFirstDay[cntr]=d[0] +# # elemHaWishNum[cntr]=1 +# # if e['maxGames'] ==0: +# # elemHaWishNum[cntr]=len(elemHaWishDays[cntr])*len(elemHaWishTeams[cntr]) +# # if e['minGames'] > 0: +# # elemHaWishNum[cntr]=e['minGames'] +# # lastDaySet = thisDaySet.copy() +# # allElemDays+= thisDaySet + +# # hawRounds[e['id']]=[] +# # for d3 in set(allElemDays): +# # hawRounds[e['id']]+=getRoundsByDay[d3] +# # hawRounds[e['id']]=sorted(list(set(hawRounds[e['id']]))) +# # hawRoundsString[e['id']]= "" +# # for r in hawRounds[e['id']]: +# # hawRoundsString[e['id']]+= str(r)+"_" +# # if hawRoundsString[e['id']]!="": +# # hawRoundsString[e['id']]=hawRoundsString[e['id']][:-1] +# # e['nWishes'] = sum ( elemHaWishNum[ee] for ee in elemHaWishes[e['id']] ) + +# # if e['forOneDay']: +# # e['nWishes'] = e['forOneDayNum'] * len(elemTeams) + +# # nElemHaWishes= sum(w['nWishes'] for w in hawishes) + +# # %% diff --git a/uefa/scripts/solve_uefa24_create_graph.py b/uefa/scripts/solve_uefa24_create_graph.py new file mode 100755 index 0000000..a488366 --- /dev/null +++ b/uefa/scripts/solve_uefa24_create_graph.py @@ -0,0 +1,280 @@ +# %% +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 * +from pulp import * +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 + +import random +import time +import json +import csv +import networkx as nx +import matplotlib.pyplot as plt + +from django.contrib.sessions.models import Session + + +scenario = Scenario.objects.get(id=9360) + + + + +from scheduler.solver.tasks.optimize import optimize + +s2 = scenario.id +user_name = 'md' +user_is_staff = True +runMode = 'Improve' +localsearch_time = 60 +RUN_ENV = 'local' +SOLVER = 'xpress' + +teams = Team.objects.filter(season=scenario.season,active=True,conference__name="UECL").distinct() + + +gamereqs = GameRequirement.objects.filter(scenario=scenario) + + +# sol = optimize(task=None, s2=s2, user_name=user_name, user_is_staff=user_is_staff, +# runMode=runMode, localsearch_time=localsearch_time, RUN_ENV=RUN_ENV, solver=SOLVER) + + +def create_graph(pot=None): + def hex_to_rgb(value): + value = value.lstrip('#') + lv = len(value) + return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + + + hex_colors = { + 1: "#ffff99", + 2: "#99ccff", + 3: "#FFcc99", + 4: "#CCFFCC", + 5: "#CCCCFF", + 6: "#9999FF", + } + + rgb_colors = {} + for c in hex_colors: + rgb_colors[c] = hex_to_rgb(hex_colors[c]) + + G = nx.Graph() + node_colors = {} + node_label = {} + for t in teams: + node_label[t] = f"{t.shortname}" + if pot is None: + node_color = rgb_colors[t.pot] + node_colors[node_label[t]] = hex_colors[t.pot] + else: + node_color = rgb_colors[pot[t]] + node_colors[node_label[t]] = hex_colors[pot[t]] + G.add_node(node_label[t], + viz = {"color": {"a":0, "r": node_color[0], "g": node_color[1], "b": node_color[2]}, 'size':20}) + + # for n in nx.nodes(G): + # G.nodes[n]["viz"] = {} + # G.nodes[n]["viz"]["color"] = colors[] + + for gq in gamereqs: + if gq.team1 in teams and gq.team2 in teams: + G.add_edge(node_label[gq.team1],node_label[gq.team2], capacity = 1.0) + + SubG = [G.subgraph(c).copy() for c in nx.connected_components(G)] + + # nx.draw(SubG[0], with_labels=True, font_weight='bold') + sub = SubG[0] + + nx.draw(sub, with_labels=True, + node_size=400, font_size=6, node_color=[node_colors[g] for g in list(sub.nodes())]) + pos = nx.nx_agraph.graphviz_layout(sub) + nx.draw_networkx_edge_labels( + sub, pos=pos, edge_labels={e: "" for e in list(sub.edges())}, font_color='black', font_size=6 + ) + nx.write_gexf(sub, "test.gexf") + + + distance = {} + + max_path = 0 + for t in teams: + path = nx.shortest_path(G, source=t.shortname) + for p in path: + + + if len(path[p]) <= 2: + length = 0 + elif len(path[p]) == 3: + length = 1 + else: + length = 2 + + distance[(teams.get(shortname=t.shortname),teams.get(shortname=p))] = length + + + + + + # cut_value, partition = nx.minimum_cut(G, "Manch", "y") + + return distance + +# %% + + +distance = create_graph() + +# %% + +# import xpress as xp + +# xp.controls.outputlog = 1 +# model = xp.problem(name='Draws', sense=xp.minimize) +# model.setControl('maxtime' , 600) + + +# distance = {(t1,t2): 1 for t1 in teams for t2 in teams} +# for g in gamereqs: +# if (g.team1,g.team2) in distance.keys(): +# distance[(g.team1,g.team2)] = 0 + +# groups = range(1,3) + +# x = {} +# y = {} +# for g in groups: +# for t in teams: +# x[t,g] = xp.var(vartype=xp.binary) +# for t1,t2 in distance.keys(): +# y[t1,t2] = xp.var(vartype=xp.binary) + + +# model.addVariable(x) +# model.addVariable(y) + +# for t1,t2 in y.keys(): +# for g in groups: +# model.addConstraint(y[t1,t2] >= x[t1,g] + x[t2,g] - 1) + +# for t in teams: +# model.addConstraint(xp.Sum(x[t,g] for g in groups) == 1) + +# for g in groups: +# model.addConstraint(xp.Sum(x[t,g] for t in teams) == len(teams)/len(groups)) + +# model.setObjective(xp.Sum(y[t1,t2]*distance[(t1,t2)] for (t1,t2) in y.keys())) + +# start_time = time.time() +# model.solve() +# comp_time = time.time()-start_time + + +teams = Team.objects.filter(season=scenario.season,active=True).distinct() + +import xpress as xp + +xp.controls.outputlog = 1 +model = xp.problem(name='Draws', sense=xp.minimize) +model.setControl('maxtime' , 600) + + +groups = range(1,5) + +x = {} +y = {} +for g in groups: + for t in teams: + x[t,g] = xp.var(vartype=xp.binary) + for gr in gamereqs: + y[gr.team1,gr.team2,g] = xp.var(vartype=xp.binary) + +model.addVariable(x) +model.addVariable(y) + +for t1,t2,g in y.keys(): + model.addConstraint(y[t1,t2,g] >= (x[t1,g] - x[t2,g])) + model.addConstraint(y[t1,t2,g] >= (x[t2,g] - x[t1,g])) + +for t in teams: + model.addConstraint(xp.Sum(x[t,g] for g in groups) == 1) + +for g in groups: + model.addConstraint(xp.Sum(x[t,g] for t in teams) == len(teams)/len(groups)) + +model.setObjective(xp.Sum(y[key] for key in y.keys())) + +start_time = time.time() +model.solve() +comp_time = time.time()-start_time + + + +# %% +pot = {} +group_list = {g:[] for g in groups} +for g in groups: + print("GROUP",g) + for t in teams: + if model.getSolution(x[t,g]) > 0: + print("\t",t.id) + group_list[g].append(t.id) + pot[t] = g + print("") + + + +# %% + + + +# %% + +print(group_list) + + +# %% + +create_graph(pot) \ No newline at end of file diff --git a/uefa/scripts/uefa24_conflicts_analyze.py b/uefa/scripts/uefa24_conflicts_analyze.py new file mode 100755 index 0000000..50fe53e --- /dev/null +++ b/uefa/scripts/uefa24_conflicts_analyze.py @@ -0,0 +1,705 @@ +# %% +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=9402) + + +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,maxSeconds=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 = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for d in all_rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in ucl_home: + sol += f"" + for d in all_rounds: + if d in ucl_rounds: + if d in ucl_home[t]: + fontcolor = 'black' + bgcolor = 'lightsteelblue' + sol += f"" + elif d in ucl_away[t]: + fontcolor = 'black' + bgcolor = 'lightyellow' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "" +for t in uel_teams: + tname = getTeamByID[t].name + tshortname = getTeamByID[t].shortname + tcountry = getTeamByID[t].country + tpot = getTeamByID[t].pot + sol += f"" + 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"" + elif (d,t) in away_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightyellow' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "" +for t in uecl_teams: + tname = getTeamByID[t].name + tshortname = getTeamByID[t].shortname + tcountry = getTeamByID[t].country + tpot = getTeamByID[t].pot + sol += f"" + for d in all_rounds: + if d in uecl_rounds: + # if (d,t) in viol_dict.keys(): + # fontcolor = 'white' + # bgcolor = 'darkred' + # sol += f"" + if (d,t) in home_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightsteelblue' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in away_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightyellow' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{d} - {getDateByIso[d].date()}
UCL{t.id}Pot {t.pot}{t.country}{t.name}{t.shortname}{team_pairing[t.id]}HA
UEL12345678
UEL{t}Pot {tpot}{tcountry}{tname}{tshortname}{team_pairing[t]}{home_dict[(d,t)]}{away_dict[(d,t)]}{viol_dict[(d,t)]}
UECL123456
UECL{t}Pot {tpot}{tcountry}{tname}{tshortname}{team_pairing[t]}{viol_dict[(d,t)]}{home_dict[(d,t)]}{away_dict[(d,t)]}{viol_dict[(d,t)]}
\n" + +with open(f'ueluecl24_debug.html', 'w') as f: + f.write(sol) + + + + +# %% diff --git a/uefa/scripts/uefa24_debugmodel_1.py b/uefa/scripts/uefa24_debugmodel_1.py new file mode 100755 index 0000000..2ca41b8 --- /dev/null +++ b/uefa/scripts/uefa24_debugmodel_1.py @@ -0,0 +1,705 @@ +# %% +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=9306) + + +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,maxSeconds=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 = " \ + \ +" +sol += "\n" +sol += "\n" +sol += "" +for d in all_rounds: + sol += f"" +sol += "" +sol += "\n" +sol += "\n" +for t in ucl_home: + sol += f"" + for d in all_rounds: + if d in ucl_rounds: + if d in ucl_home[t]: + fontcolor = 'black' + bgcolor = 'lightsteelblue' + sol += f"" + elif d in ucl_away[t]: + fontcolor = 'black' + bgcolor = 'lightyellow' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "" +for t in uel_teams: + tname = getTeamByID[t].name + tshortname = getTeamByID[t].shortname + tcountry = getTeamByID[t].country + tpot = getTeamByID[t].pot + sol += f"" + 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"" + elif (d,t) in away_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightyellow' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "" +for t in uecl_teams: + tname = getTeamByID[t].name + tshortname = getTeamByID[t].shortname + tcountry = getTeamByID[t].country + tpot = getTeamByID[t].pot + sol += f"" + for d in all_rounds: + if d in uecl_rounds: + # if (d,t) in viol_dict.keys(): + # fontcolor = 'white' + # bgcolor = 'darkred' + # sol += f"" + if (d,t) in home_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightsteelblue' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in away_dict.keys(): + fontcolor = 'black' + bgcolor = 'lightyellow' + if (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + elif (d,t) in viol_dict.keys(): + fontcolor = 'white' + bgcolor = 'darkred' + sol += f"" + else: + sol += "" + else: + sol += "" + sol += "" +sol += "\n" +sol += "
{d} - {getDateByIso[d].date()}
UCL{t.id}Pot {t.pot}{t.country}{t.name}{t.shortname}{team_pairing[t.id]}HA
UEL12345678
UEL{t}Pot {tpot}{tcountry}{tname}{tshortname}{team_pairing[t]}{home_dict[(d,t)]}{away_dict[(d,t)]}{viol_dict[(d,t)]}
UECL123456
UECL{t}Pot {tpot}{tcountry}{tname}{tshortname}{team_pairing[t]}{viol_dict[(d,t)]}{home_dict[(d,t)]}{away_dict[(d,t)]}{viol_dict[(d,t)]}
\n" + +with open(f'ueluecl24_debug.html', 'w') as f: + f.write(sol) + + + + +# %% diff --git a/uefa/scripts/uefa24_research.py b/uefa/scripts/uefa24_research.py new file mode 100755 index 0000000..916fcee --- /dev/null +++ b/uefa/scripts/uefa24_research.py @@ -0,0 +1,7636 @@ +# %% +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"] + +POSTGRES_ENGINE=django.db.backends.postgresql_psycopg2 +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_USER=leagues_user +POSTGRES_PASSWORD=ligalytics +POSTGRES_DB=leagues_db + +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 + + +from django.template.loader import render_to_string +from django.core.files.storage import FileSystemStorage +from django.utils.translation import gettext_lazy as _ + +# from gurobipy import * +import pulp +from pulp import lpSum, value, XPRESS_PY, XPRESS, GUROBI, PULP_CBC_CMD +from math import sqrt,pow,sin,cos,atan2,pi, ceil +from collections import defaultdict +import timeit +import datetime +import operator +import random +import time +import copy +# from os import dup, dup2, close, path +import os +from importlib import import_module +import builtins as __builtin__ +import logging +import networkx as nx +import json +import string +import hashlib + +from scheduler.models import * +from leagues.celery import celery, TASK_TIME_LIMIT +from leagues.settings import PULP_FOLDER +from scheduler.helpers import serialize_scenario, report_solverstatus +from scheduler.solver.functions import * +from scheduler.solver.tasks.optimize_localsearch import smartNeighbor +from scheduler.solver.tasks.optimize_submodels import ucl24_basicmodell, ueluecl24_basicmodell, ueluecl24_basicmodell_v2 + +# from research.learners import AttendanceLearner +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestRegressor +from sklearn.ensemble import GradientBoostingRegressor +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=9306) +scenario = Scenario.objects.get(id=9368) + +s2 = scenario.id +user_name = 'md' +user_is_staff = True +runMode = 'Improve' +localsearch_time = 60 +RUN_ENV = 'local' +solver = 'xpress' + + +# %% +thisScenario = Scenario.objects.get(id = s2) +thisSeason = thisScenario.season +thisLeague = thisSeason.league + +mathModelName=thisLeague.name +if thisSeason.optimizationParameters!="": + mathModelName=thisSeason.optimizationParameters + +start_time = time.time() + +writeProgress("Computation started !!", thisScenario.id, 3) + +lengthScale = 1.0 if thisSeason.lengthUnit=="km" else 0.62 + + +def scaledDistanceString (dd): + return str(int(lengthScale*dd+0.5))+ " "+ thisSeason.lengthUnit + +otherScenGames=[] +if thisSeason.improvementObjective!="--": + for otherScenario in Scenario.objects.filter(season=thisSeason): + impObjWeight = 5 + if thisSeason.improvementObjective=="stick to old solutions": + impObjWeight = -5 + if thisSeason.improvementObjective=="stick to old solutions heavily": + impObjWeight = -50 + if otherScenario.solutionlist() != [['']]: + for g in otherScenario.solutionlist(): + otherScenGames.append((int(g[1]),int(g[2]),int(g[3]), impObjWeight )) + +getGlobalCountry={ gc['uefa']: gc['id'] for gc in GlobalCountry.objects.values() } + +teamObjects = Team.objects.filter(season=thisSeason,active=True).order_by('position').values() +teams=[] +realteams=[] +faketeams=[] +importantteams=[] +shortteams=[] +t_pos={} +t_pot={} +t_color={} +t_country={} +t_stadium = {} +t_shortname = {} +t_usePhases = {} +t_lon = {} +t_lat = {} +t_attractivity ={} +t_logo ={} +getTeamById={} +getTeamIdByName={} +getTeamIdByShortName={} +getTeamByName={} +getStadiumById={} +teamByShort={} +top4 = [] +for t in teamObjects: + # print (t['name'], t['id']) + teams.append(t['id']) + shortteams.append(t['shortname']) + teamByShort[t['shortname']]= t['name'] + getTeamIdByShortName[t['shortname']]=t['id'] + if t['name']!='-': + realteams.append(t['id']) + else: + faketeams.append(t['id']) + if t['very_important']: + importantteams.append(t['id']) + t_country[t['id']]= t['countryObj_id'] + t_pos[t['name']]= t['position'] + t_color[t['name']]="lightyellow" + t_pot[t['id']]= t['pot'] + t_stadium[t['id']]=t['stadium'] + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_shortname[t['id']]=t['shortname'] + t_logo[t['id']]="" + if t['logo']: + t_logo[t['id']]="" + t_usePhases[t['id']]= thisScenario.usePhases + getTeamById[t['id']]=t['name'] + getStadiumById[t['id']]=t['stadium'] + getTeamIdByName[t['name']]=t['id'] + getTeamByName[t['name']]=t + if t['attractivity'] >= thisSeason.topTeamMinCoefficient : + top4.append(t['id']) + +inactive_teams=[] +for t in Team.objects.filter(season=thisSeason,active=False).order_by('position').values(): + inactive_teams.append(t['id']) + t_attractivity[t['id']]=t['attractivity'] + t_lon[t['id']]=t['longitude'] + t_lat[t['id']]=t['latitude'] + t_country[t['id']]= t['countryObj_id'] + getTeamById[t['id']]=t['name'] + +countryObjects = Country.objects.filter(season=thisSeason).order_by('name') +countries = [c.id for c in countryObjects] +cn_name = {c.id : c.name for c in countryObjects} +cn_shortname = {c.id : c.shortname for c in countryObjects} + +NAS15=[c for c in countries if cn_shortname[c] in ["ESP","GER","ENG","ITA","FRA"]] + +lowerBoundFound = False +distance ={} +attractivity ={} +distanceById={} +distanceInDaysById={} +# stadium_names = set([t['stadium'] for t in teamObjects if t['stadium']!='']) +# stadium_id={} +# stadium_name={} +# sid=0 +# for stadium in stadium_names : +# stadium_name[sid]=stadium +# stadium_id[stadium]=sid +# sid+=1 +# stadiums = list(stadium_name.keys()) + +travelDict= thisSeason.travelDict() +for t1 in teamObjects: + # if t1['stadium']!='': + # teamsOfStadium[stadium_id[t1['stadium']]].append(t1['id']) + for t2 in teamObjects: + # distance[t1['name'],t2['name']] = distanceInKm(t1,t2) + # print (t1['name'],t2['name'],distance[t1['name'],t2['name']] ," -> ", travelDict[t1['id']][t2['id']]['distance'] ) + distance[t1['name'],t2['name']] = travelDict[t1['id']][t2['id']]['distance'] + distanceById[t1['id'],t2['id']] = distance[t1['name'],t2['name']] + distanceInDaysById[t1['id'],t2['id']] = int(distance[t1['name'],t2['name']]/350 +0.99) + attractivity[t1['id'],t2['id']] = int(100*t1['attractivity']*t2['attractivity']) + + +dayObjects = Day.objects.filter(season=thisSeason).values() +days = [ d['id'] for d in dayObjects if d['round']>0 ] +nDays = len(days) +getNiceDayRaw = { d['id'] : d['day'] for d in dayObjects} + +higherSeasons = thisSeason.higherSeasons() +higherGames = thisSeason.higherGames() + +this_season_team_names = list(getTeamByName.keys()) + +for d in thisSeason.higherDays(): + getNiceDayRaw[d.id] = d.day + +higherTeamObjects = Team.objects.filter(season__in=higherSeasons,active=True).values() +higherDayObjects = Day.objects.filter(season__in=higherSeasons,maxGames__gte=1).values() +higherTeams = [ t['id'] for t in higherTeamObjects] +higherTeamsOf={ t: [] for t in teams} +for t in higherTeamObjects: + getTeamById[t['id']]=t['name'] + t_country[t['id']]=t['countryObj_id'] + if t['name'] in this_season_team_names: + higherTeamsOf[getTeamIdByName[t['name']]].append(t['id']) + +print ("Teams : " , teams) +print ("VIP Teams : " , importantteams) +print ("TOP Teams : " , top4) + +allConferences = Conference.objects.filter(scenario=s2) +sharedStadiums = False +for ff in thisSeason.federationmember.all(): + sharedStadiums= ff.federation.sharedStadiums + +# print ("\nGroups : ") +t_conference = {t : 0 for t in teams } +conf_teams={} +for c in Conference.objects.filter(scenario=s2,regional=False).order_by('name'): + # print ("A" , c) + cteams = c.teams.filter(active=True) + conf_teams[c.name]=[] + for t in cteams: + conf_teams[c.name].append(t.id) + # print ("group for ", t) + # if t_conference[t.id]!=0: + # print (getTeamById[t.id] , " in several groups") + if t_conference[t.id]==0 or len(cteams)< len(t_conference[t.id].teams.filter(active=True)): + t_conference[t.id]=c + # print (" is " , c ) +# for t in set([t for t in teams if t_conference[t]==0 ]): + # print (" no group " , getTeamById[ t]) +# return '' + +displayConferences=Conference.objects.filter(scenario=s2, display_group=True) +displayGroups ={} +if thisSeason.groupBased: + displayGroups ={ c.id : [t.id for t in c.teams.filter(active=True)] for c in displayConferences} + for c in displayConferences: + displayGroups[c.id]=[] + for t in c.teams.filter(active=True): + displayGroups[c.id].append(t.id) + t_conference[t.id]=c +else: + displayGroups['All'] = realteams + +stadiums = Stadium.objects.filter(season=thisSeason) +stadium_name={ s.id : s.name for s in stadiums } + +teamsOfStadium ={ st.id:[] for st in stadiums } + +teamsWithSameStadium = {t: [] for t in realteams} +getStadiumsByTeam = {t: [] for t in realteams} +getTeamsByStadium = {s.id: [] for s in stadiums} +getStadiumAvailability = { (s.id,d): 1 for s in stadiums for d in days} +for s in stadiums: + for sp in s.stadiumpreferences.all(): + getStadiumsByTeam[sp.team.id].append(sp) + getTeamsByStadium[s.id].append(sp) + for sb in s.stadiumBlockings.filter(scenario=s2): + getStadiumAvailability[s.id,sb.day.id]=0 + +for t in realteams: + for s in getStadiumsByTeam[t]: + teamsWithSameStadium[t]+=[t2.team.id for t2 in getTeamsByStadium[s.stadium.id] if t2.team.id!=t] + teamsWithSameStadium[t] = list(set(teamsWithSameStadium[t])) + +prioVal ={'A': 25 , 'B': 5 , 'C': 1, 'Hard' : 1000} + +sw_prio = {} +sw_float1 = {} +sw_float2 = {} +sw_int1 = {} +sw_int2 = {} +sw_text = {} +for sw in SpecialWish.objects.filter(scenario=s2): + sw_prio[sw.name]=prioVal[sw.prio] if sw.active else 0 + sw_float1[sw.name]=sw.float1 + sw_float2[sw.name]=sw.float2 + sw_int1[sw.name]=sw.int1 + sw_int2[sw.name]=sw.int2 + sw_text[sw.name]=sw.text +special_wishes=list(sw_prio.keys()) +special_wishes_active = [ sw for sw in special_wishes if sw_prio[sw]>0 ] + +print (special_wishes_active) + +noBreakLimitTeams = [] +if "allowManyBreaksInRow" in special_wishes_active : + noBreakLimitTeams = sw_text["allowManyBreaksInRow"].split(",") + print (noBreakLimitTeams) + noBreakLimitTeams = [t for t in teams if getTeamById[t] in noBreakLimitTeams] + print (noBreakLimitTeams) + +pairings_tmp = Pairing.objects.filter(scenario=s2, active=True).values() +pairings_tmp2 = Pairing.objects.filter(scenario__is_published=True, team1__id__in=teams, team2__id__in=higherTeams, active=True).exclude(scenario__season=thisSeason).values() +pairings_tmp3 = Pairing.objects.filter(scenario__is_published=True, team2__id__in=teams, team1__id__in=higherTeams, active=True).exclude(scenario__season=thisSeason).values() +pairings = [] +for p in [ p for p in pairings_tmp if p['team1_id'] in teams and p['team2_id'] in teams+higherTeams ] + [ p for p in pairings_tmp2 ] + [ p for p in pairings_tmp3]: + if p not in pairings: + pairings.append(p) + +pairTagweight = {} +for pair in pairings: + pairTagweight[pair['id']] = Pairing.objects.get(id=pair['id']).tag_weight() + if pair['dist'] in [3,7]: + pair['nWishes'] = thisSeason.nRounds + else: + pair['nWishes'] = nDays +nElemPairings= sum(pair['nWishes'] for pair in pairings) + +breaks = Break.objects.filter(season=thisSeason).values() +tagBlockWeight = { t.id : t.blocking_weight for t in Tag.objects.filter(season=thisSeason) } +blockings_tmp = Blocking.objects.filter(scenario=s2,active=True).values() +blockings_tmp = [ bl for bl in blockings_tmp if bl['team_id'] in teams ] + +for bl in blockings_tmp: + bl['weight'] =1 if bl['tag_id'] == None else tagBlockWeight[bl['tag_id']] + +blockings = [ bl for bl in blockings_tmp if bl['weight'] >=0 ] +goodHomes = [ bl for bl in blockings_tmp if bl['weight'] <0 ] + +# times = ['Early', 'Late'] +timeObjects = TimeSlot.objects.filter(season=thisSeason).values() +times = [ str(t['id']) for t in timeObjects] +getTimeById = {str(t['id']):t['name'] for t in timeObjects} +getTimeStartById = {str(t['id']):t['start'] for t in timeObjects} +getIdByTime = {t['name']:str(t['id']) for t in timeObjects} + +blocked_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +hidden_arena = {(t,d, tm) : False for t in teams for d in days for tm in ["----"] + times } +nBlockingHome=0 +nBlockingAway=0 + +for bl in blockings: + if bl['type'] in ["Hide","Game"]: + hidden_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + + if bl['type'] in ["Home","Hide","Game"]: + if bl['tag_id']==None: + blocked_arena[(bl['team_id'],bl['day_id'], bl['time'] )]=True + nBlockingHome+=1 + else: + nBlockingAway+=1 + +nTeams=len(teams) +nPhases =thisSeason.numPhases +groupView = thisSeason.groupBased + +gameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} +undirectedGameCntr={(t1,t2) : 0 for t1 in teams for t2 in teams} + +if thisSeason.useFeatureOpponentMatrix: + gameRequirements = GameRequirement.objects.filter(scenario=s2, team1__active=True, team2__active=True) + for gm in gameRequirements: + if thisSeason.undirectedGames: + undirectedGameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + undirectedGameCntr[(gm.team2.id,gm.team1.id)]+=gm.number + # print ( "found undirected game " , (gm.team1.id,gm.team2.id) , gm.number ) + else: + gameCntr[(gm.team1.id,gm.team2.id)]+=gm.number + # print ( "found directed game " , (gm.team1.id,gm.team2.id) , gm.number ) +else: + for t1 in teams: + for t2 in teams: + if t1!=t2: + gameCntr[(t1,t2)]+=nPhases/2 + + for c in Conference.objects.filter(scenario=s2): + cteams = c.teams.filter(active=True) + for t1 in cteams: + for t2 in cteams: + if t1!=t2: + gameCntr[(t1.id,t2.id)]+=c.deltaGames/2 + + for (t1,t2) in gameCntr.keys(): + if gameCntr[(t1,t2)]%1!=0 : + undirectedGameCntr[(t1,t2)]=1 + gameCntr[(t1,t2)] = int(gameCntr[(t1,t2)]) + +games = [ gm for gm in gameCntr.keys() if gameCntr[gm]+undirectedGameCntr[gm]>0] + +objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values() + +gew={} +gew['Trips'] = 5 +gew['Derbies'] = 5 +gew['Pairings'] = 5 +for ow in objectiveFunctionWeights: + gew[ow['name']]= ow['use'] * ow['prio'] + +objectivePrio = 'Breaks' +if gew['Trips']>gew['Breaks'] : + objectivePrio = 'Trips' + +print("objectivePrio:", objectivePrio) + +specialGameControl= mathModelName in ["Florida State League"] + +if len(games)==0: + games = [ (t1,t2) for t1 in teams for t2 in teams if t1!=t2 ] + specialGameControl=True + +realgames= [(t1,t2) for (t1,t2) in games if getTeamById[t1]!="-" and getTeamById[t2]!="-"] + +# nur wenn die solutionlist alle spiele enthält, wird dieser modus aktiviert +evalRun = ( runMode == "Improve" and localsearch_time==0 + and len(thisScenario.solutionlist())==sum([gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in realgames]) ) + +opponents = { t: set([]) for t in realteams} + +for (t1,t2) in games: + if t1 in realteams and t2 in realteams: + opponents[t1].add(t2) + opponents[t2].add(t1) + +model7 = pulp.LpProblem(f"{PULP_FOLDER}/FindGameClusters_"+str(thisScenario.id), pulp.LpMinimize) + +G = nx.Graph(realgames) +gmClusters = range(1,nTeams+2 - len(nx.maximal_matching(G))) + +if nPhases>0 and not thisSeason.useFeatureOpponentMatrix and not specialGameControl: + gmClusters = range(1,2) + +print ("gmClusters", gmClusters) + +cntr=0 +gameClusterTeams = {} +# gameClusterTeams = { c : [] for c in gmClusters } + +t_incluster = {t : [] for t in realteams} +G = nx.Graph(realgames) +for c in nx.find_cliques(G): + cntr+=1 + gameClusterTeams[cntr]=list(c) + for t in gameClusterTeams[cntr]: + t_incluster[t].append(cntr) +for t in realteams: + cntr+=1 + gameClusterTeams[cntr]=[t] + t_incluster[t].append(cntr) + +gmClusters = gameClusterTeams.keys() + +groupUsed7 = { c : pulp.LpVariable('x7_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in gmClusters} + +for t in realteams: + model7+= lpSum([ groupUsed7[c] for c in t_incluster[t]]) ==1 + +model7+= lpSum([ groupUsed7[c] for c in groupUsed7.keys()]) + +if solver == "CBC": + model7.solve(PULP_CBC_CMD(threads = 8,msg=1)) +elif solver == "Gurobi": + model7.solve(GUROBI(MIPGap=0.01,msg=1)) +else: + model7.solve(XPRESS(msg=1, targetGap=0.01, maxSeconds = 100, keepFiles=True)) + +gameClusters= [c for c in gmClusters if groupUsed7[c].value()>0.01] + +for c in gameClusters: + print ("CLUSTER " , c , gameClusterTeams[c]) + +biggestGroupSize = max([len(gameClusterTeams[c]) for c in gameClusters]) + +print ("biggestGroupSize", biggestGroupSize) + +nPhases = min( [ gameCntr[(t1,t2)]+ gameCntr[(t2,t1)] + undirectedGameCntr[(t1,t2)] for (t1,t2) in realgames ]) +tripStartHeuristicGroupsize = 1 if thisSeason.tripStartHeuristicGroupsize=="None" else int(thisSeason.tripStartHeuristicGroupsize) +defaultGameRepetions = 1 if not mathModelName in ["NHL", "NBA"] else 2 +nPhases=max(1,int(nPhases/defaultGameRepetions)) +phases = range(nPhases) + +useBasicGames = nPhases>0 + +if not useBasicGames: + print ('no basic games but biggest group size ', biggestGroupSize, ' in nPhases ' , nPhases ) + +nRounds = thisSeason.nRounds +rounds=range(1,nRounds+1) + +# nRoundsPerPhase= 1 +# if nPhases>0: +nRoundsPerPhase= int(nRounds/nPhases) + +print ('nRounds ',nRounds) +print ('nPhases ',nPhases) +print ('nRoundsPerPhase ',nRoundsPerPhase) +print ('defaultGameRepetions ',defaultGameRepetions) +print ('tripStartHeuristicGroupsize ',tripStartHeuristicGroupsize) + +script =thisSeason.optimizationScript +if script == '' : + if useBasicGames: + if not thisSeason.useFeatureOpponentMatrix: + script += "HEURISTIC\n" + script += "GAME;1-"+str(nRounds)+";0.1;200\n" + # if nRounds <=10: + # script += "GAME;1;"+str(nRounds)+"\n" + # else: + if thisSeason.groupBased: + for c in allConferences: + if not c.regional and c.teams.count() <= 12: + # print ("### ", c, c.teams.count()) + # print (c, lpSum([1 for c in c.teams.all()])) + script += "GROUP;1-"+str(nRounds)+";0.05;30;"+c.name+"\n" + for cr in range(nPhases): + minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # script += "GROUP;"+ str(minIntRound) + ";"+str(maxIntRound)+";"+str(c.id)+"\n" + + # for cr in range(nPhases): + # minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + # maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # if len(newRounds)>1: + # script += "GAME;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + # else: + # for cr in range(nPhases): + # minIntRound= min( (cr) * nRoundsPerPhase+1, nRounds) + # maxIntRound= min( (cr+1)*nRoundsPerPhase, nRounds) + # newRounds= range(minIntRound,maxIntRound+1) + # # if nRounds1 > 9 : + # # script += "HOMEAWAY;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + # # if not useMinRounds: + # # script += "BASICGAME;"+ str(cr*(nTeams-1)+1) + ";"+str((cr+1)*(nTeams-1))+"\n" + # if len(newRounds)>1 and False: + # script += "GAME;"+ str(minIntRound) + ";"+str(maxIntRound)+"\n" + +script=script.replace('\r', '') +print ("script") +print (script) +optSteps = [ st.split(';') for st in script.split("\n")] +print (optSteps) + +script =thisSeason.improvementScript +if script == '' : + if thisSeason.groupBased: + if nRounds >16: + script += "ROUNDS\n" + script += "GROUPS\n" + else: + # if solver != "CBC": + # script += "TEAMSROUNDS\n" + # script += "PAIRS\n" + script += "TEAMS\n" + # script += "ROUNDS\n" + # if solver != "CBC": + # script += "TEAMSROUNDS\n" + +impScript=[] +script=script.replace('\r', '') +print (script) +if localsearch_time>0: + for stall in script.split("\n") : + st_tok = stall.split(";") + print (st_tok) + st=st_tok[0] + impTime = int(st_tok[2]) if len(st_tok) > 2 else -1 + if (st=="GROUPS"): + print ("ADDING GROUPS") + onlyUseReoptGroups = False + for c in allConferences: + if c.reopt: + onlyUseReoptGroups = True + if impTime==-1: + impTime = 30 + for c in allConferences: + tms = [t.id for t in c.teams.filter(active=True)] + print ("C " , tms) + if (len(tms) <= 8 or c.reopt) and (not onlyUseReoptGroups or c.reopt): + print ("C " , tms) + if impTime==-1: + impTime = 30 + for tt in tms: + print ( c.id, " TEAM " , getTeamById[tt]) + # impScript.append((st,1,nRounds, tms, max(30,1.2*nRounds))) + if nRounds <= 50 or len(tms)<=3: + # impScript.append((st,1,int(0.75*nRounds),tms, max(group_time,30), "")) + impScript.append((st,1,nRounds,tms, impTime, "")) + else: + impScript.append((st,1,int(0.5*nRounds),tms,impTime, "")) + impScript.append((st,int(0.25*nRounds),int(0.75*nRounds),tms,impTime, "")) + impScript.append((st,int(0.5*nRounds),nRounds, tms, impTime, "")) + # impScript.append((st,1,int(0.75*nRounds),tms, max(group_time,300), "")) + # impScript.append((st,int(0.25*nRounds),nRounds, tms, max(group_time,300), "")) + # else: + # if len(tms) <= 8: + # impScript.append((st,1,int(0.5*nRounds),tms, 30)) + # impScript.append((st,int(0.5*nRounds),nRounds, tms, 30)) + # else: + # impScript.append((st,1,int(0.25*nRounds),tms, 30)) + # impScript.append((st,int(0.25*nRounds),int(0.5*nRounds),tms, 30)) + # impScript.append((st,int(0.5*nRounds),int(0.75*nRounds),tms, 30)) + # impScript.append((st,int(0.75*nRounds),nRounds, tms, 30)) + + + + if (st=="TEAMS"): + print ("ADDING TEAMS") + for t in teams: + nts=[t] + if solver != "CBC" and gew['Trips']>=0 and nTeams<=76: + na = 2 if nTeams*nRounds >200 else 3 + na= random.randint(1,na) + if len(st_tok)>1: + na=int(st_tok[1])-1 + for i in range(na): + # for i in range(4): + nts.append(teams[random.randint(0,nTeams-1)]) + # print ("adding ", i , nts) + nts=list(set(nts)) + if impTime==-1: + impTime = len(nts)*15 + impScript.append((st,1,nRounds,nts, impTime, "")) + + if impTime==-1: + impTime = 30 + + if (st=="ROUNDS"): + na=5 + if len(st_tok)>1: + na=int(st_tok[1])-1 + for r in rounds: + if r%5==1 or nRounds<10: + nrs = random.randint(na,max(na,int(nRounds/6))) + #dont make time window too big + nrs = na + impScript.append((st,r,min(nRounds,r+nrs ),teams, impTime, "")) + + if (st=="ENCOUNTERS"): + na=10 + if len(st_tok)>1: + na=int(st_tok[1])-1 + for r in rounds: + if r%5==1 and r+na <=nRounds : + impScript.append((st,r,r+na,teams, impTime , "STICKY_HOME") ) + + if (st=="HOMEAWAY"): + impScript.append((st,1,nRounds,teams, impTime , "STICKY_ENCOUNTER") ) + + + if (st=="SMART_TEAMS"): + impTeams = int(st_tok[1]) if len(st_tok) > 1 else 3 + impScript.append((st,1,nRounds,impTeams, impTime,"")) + if (st=="SMART_ROUNDS"): + impRounds = int(st_tok[1]) if len(st_tok) > 1 else 5 + impScript.append((st,1,impRounds,nTeams, impTime,"")) + + if impTime==-1: + impTime = 20 + + if (st=="TEAMSROUNDS"): + for t in teams: + nts=[t] + for i in range(8 +2*(t%2)): + nts.append(teams[random.randint(0,nTeams-1)]) + nts=list(set(nts)) + st = random.randint( 1, nRounds-10 ) + impScript.append((st,st,min(st+12,nRounds),nts, impTime, "")) + # st = random.randint( 1, nRounds-18 ) + # impScript.append((st,st,min(st+18,nRounds),nts, 120, "")) + + if (st=="PAIRS"): + for p in pairings: + impScript.append((st,1,nRounds, [p['team1_id'],p['team2_id']], impTime), "") + + +random.shuffle(impScript) +if runMode=='Improve': + impScript= [("INIT",1,nRounds, [], 2000, "")] + impScript + +getPhaseOfRound = { r : min( nPhases-1 , int((r-1)/nRoundsPerPhase)) for r in rounds } + +if thisSeason.lastRoundOfPhaseOne>0: + getPhaseOfRound = { r : 0 if r<=thisSeason.lastRoundOfPhaseOne else 1 for r in rounds } + +getDaysOfPhase = { p : [] for p in phases } +getDays = { r : [] for r in rounds} +roundGamesMax = { r : 0 for r in rounds} +roundGamesMin = { r : 0 for r in rounds} +getDayById = { d['id'] : d for d in dayObjects} +getDayByDateTime = { } +getNiceShortDay = { d['id'] : d['day'] for d in dayObjects} +getNiceDay = { d['id'] : d['day'] for d in dayObjects} +getWeekDay = { d['id'] : '' for d in dayObjects} +getDateTimeDay = { d['id'] : '' for d in dayObjects} +getRoundByDay = { d['id'] : d['round'] for d in dayObjects if d['round']>=0 } +getDayMinGames = { d['id'] : d['minGames'] for d in dayObjects if d['round']>0 } +getDayMaxGames = { d['id'] : d['maxGames'] for d in dayObjects if d['round']>0 } +getRoundsByDay = { d['id'] : [] for d in dayObjects } +nDerbies = { d['id'] : [d['nDerbies']] for d in dayObjects} +roundDays =[] +roundDaysMin ={ } +roundDaysMax ={ } +wds= {0:'Mon', 1:'Tue', 2:'Wed', 3:'Thu', 4:'Fri', 5:'Sat', 6:'Sun'} +for d in dayObjects: + if d['round']>0 : + getRoundsByDay[d['id']].append(d['round']) + getDays[d['round']].append(d['id']) + roundDays.append((d['round'],d['id'])) + roundDaysMin[(d['round'],d['id'])]=d['minGames'] + roundDaysMax[(d['round'],d['id'])]=d['maxGames'] + roundGamesMax[d['round']]=min(nTeams/2, roundGamesMax[d['round']]+d['maxGames'] ) + roundGamesMin[d['round']]+=d['minGames'] + ph = getPhaseOfRound[d['round']] + getDaysOfPhase[ph].append(d['id']) + if d['round2']>0: + getRoundsByDay[d['id']].append(d['round2']) + getDays[d['round2']].append(d['id']) + roundDays.append((d['round2'],d['id'])) + roundDaysMin[(d['round2'],d['id'])]=d['minGames2'] + roundDaysMax[(d['round2'],d['id'])]=d['maxGames2'] + roundGamesMax[d['round2']]=min(nTeams/2, roundGamesMax[d['round2']]+d['maxGames2'] ) + roundGamesMin[d['round2']]+=d['minGames2'] + + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getDayByDateTime[dt] = d['id'] + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getNiceShortDay[d['id']] = str(dt.strftime('%d.%m.%y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +for d in higherDayObjects: + getNiceDayRaw[d['id']] = d['day'] + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + getWeekDay[d['id']] = str(wds[dt.weekday()]) + +teamCountries = list(set( [t_country[t] for t in teams ])) +countries = [c for c in countries if c in teamCountries] + +getWeekDaysPerRound = { r : [ getWeekDay[d] for d in getDays[r]] for r in rounds} + +wd = {"Mondays":0 , "Tuesdays":1 , "Wednesdays":2 , "Thursdays":3 , "Fridays":4 , "Saturdays":5 , "Sundays":6} +t_site_bestTimeSlots = { (t,d): [] for t in realteams for d in days } +prio_weight = { "A" : 0 , "B" : 50 , "C" : 100} + + +stadiumTimeSlotPreferences = StadiumTimeSlotPreference.objects.filter(team__id__in=teams ) +theseStadiums= set([ stsp.stadiumTimeSlot.stadium for stsp in stadiumTimeSlotPreferences ]) + +teamsInStadium ={st.id: set([]) for st in theseStadiums } +getSharedStadiumName ={st.id: st.name for st in theseStadiums } +for stsp in stadiumTimeSlotPreferences: + teamsInStadium[stsp.stadiumTimeSlot.stadium.id].add(stsp.team.id) +lonelyTeams = [] +for st in teamsInStadium.keys(): + if len(teamsInStadium[st])==1: + lonelyTeams+=teamsInStadium[st] + +incompatible_timslots = {} +getStadiumTimeSlot= {} +for st in theseStadiums: + theseTimeSlots = st.stadiumtimeslots.all().values() + for ts1 in theseTimeSlots: + getStadiumTimeSlot[ts1['id']]=ts1 + incompatible_timslots[ts1['id']]=set([]) + if ts1["end"]<=ts1["start"]: + ts1["end"]=datetime.time(ts1["start"].hour + 2, ts1["start"].minute) + for ts1 in theseTimeSlots: + for ts2 in theseTimeSlots: + if ts1["weekday"]==ts2["weekday"] and ts1["start"]<=ts2["start"] and ts2["start"]1: + # print (t_site_bestTimeSlots[(t,d)]) + t_site_bestTimeSlots[(t,d)]=sorted(t_site_bestTimeSlots[(t,d)]) + t_site_bestTimeSlots[(t,d)]=t_site_bestTimeSlots[(t,d)][:1] + # print (" -> " , t_site_bestTimeSlots[(t,d)]) + +# for (t,d) in t_site_bestTimeSlots.keys(): +# if len(t_site_bestTimeSlots[(t,d)])>0: +# print(getTeamById[t], getNiceDay[d], t_site_bestTimeSlots[(t,d)]) + +toTime=False +higherLeagueDayIds= [] +upperAndLowerLeagueIds = [] +higherLeagueGetRoundByDay= {d['id']:d['round'] for d in higherDayObjects} + +for d in higherDayObjects: + getRoundByDay[d['id']] = 0 + +for d in higherDayObjects: + dt = parse(d['day']) + getDateTimeDay[d['id']] = dt + if dt not in getDayByDateTime.keys(): + getDayByDateTime[dt] = d['id'] + higherLeagueDayIds.append(d['id']) + getNiceDay[d['id']] = str(dt.strftime('%a, %b %d, %Y')) + # getRoundByDay[d['id']] = d['round'] + else: + upperAndLowerLeagueIds.append(getDayByDateTime[dt]) + thisLeagueDay=getDayByDateTime[dt] + # print ("found day in both ", getNiceDay[thisLeagueDay] , " putting in round " , getRoundByDay[thisLeagueDay] ) + for d2 in higherDayObjects : + if higherLeagueGetRoundByDay[d2['id']] == higherLeagueGetRoundByDay[d['id']]: + getRoundByDay[d2['id']] = getRoundByDay[thisLeagueDay] + # print ("assigning day ", d2['id'] , d2['day'] , " to round " , getRoundByDay[thisLeagueDay] ) + +getHigherDaysByRound= { r: [d for d in higherLeagueDayIds if getRoundByDay[d]==r ] for r in rounds } +getHigherDaysByRound[0]= [] +# print (getHigherDaysByRound) + +for p in pairings: + p["days"]=days+higherLeagueDayIds + if p["first_day_id"]: + p["days"]=[d for d in p["days"] if getNiceDayRaw[p["first_day_id"]]<=getNiceDayRaw[d] ] + if p["last_day_id"]: + p["days"]=[d for d in p["days"] if getNiceDayRaw[d]<=getNiceDayRaw[p["last_day_id"]] ] + print (p, p["days"]) + +earliestDay={r: getDays[r][0] for r in rounds } +latestDay={r: getDays[r][0] for r in rounds } +for r in rounds : + for d in getDays[r]: + dt=getDateTimeDay[d] + if dt>getDateTimeDay[latestDay[r]]: + latestDay[r]=d + if dt0: + # nm=max(c for c in clusters )+1 + # clusters.append(nm) + # cluster_teams[nm]=[t] + # t_cluster[t]=nm + +# print ("clusters: ",clusters) +# print ("t_clusters: ",t_clusters) +# print ("cluster_teams: ",cluster_teams) + + +cluster_distances = {} + +c_weight = { c: 1 for c in clusters } + +for c1 in clusters: + for c2 in clusters: + dist=0 + for t1 in cluster_teams[c1]: + for t2 in cluster_teams[c2]: + dist+= distanceById[t1,t2] + cluster_distances[c1,c2]= int(dist /(max(1,len(cluster_teams[c1]))*max(1,len(cluster_teams[c2])))) + + if cluster_distances[c1,c1]<100: + c_weight[c1]= 1.3 + + print ("CLUSTER ", c1) + for t in cluster_teams[c1]: + print ("--",getTeamById[t]) + print ("---> Av Distance: ", c1 ,cluster_distances[c1,c1]) + +c_lat = { c: sum([t_lat[t] for t in cluster_teams[c] ])/max(1,len(cluster_teams[c])) for c in clusters } +c_lon = { c: sum([t_lon[t] for t in cluster_teams[c] ])/max(1,len(cluster_teams[c])) for c in clusters } +tripToClusterSaving = { (t,c): 2.0*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c])-cluster_distances[c,c] for t in teams for c in clusters } + +for c in regionalConferences: + print (c) + # print (c.teamlist()) + cteams = c.teams.filter(active=True) + for t in cteams: + print (' - ' , t) + regionalteams.add(t.id) + +regionalGroupSizes = [ len( c.teams.filter(active=True)) for c in regionalConferences ] +if len(regionalGroupSizes)>0: + maxRegionalGroupSize = max([ len( c.teams.filter(active=True)) for c in regionalConferences ] ) +else: + maxRegionalGroupSize =0 +print ("maxRegionalGroupSize " , maxRegionalGroupSize) +print ("maxTourLength " , thisSeason.maxTourLength) + +regionalParent={t : 0 for t in teams} +regionalKids={t : [] for t in teams} + +regionalPatternUse=tripStartHeuristicGroupsize>1 + +print("regionalPatternUse", regionalPatternUse) + +f_team={ t:t for t in teams } +if regionalPatternUse : + teamPairs = [ (t1,t2) for (t1,t2) in games if t10.9] + + + # for (t1,t2) in reg_edges: + # print ([getTeamById[t1],getTeamById[t2]] , distanceInKmByGPS(t_lon[t1],t_lat[t1],t_lon[t2],t_lat[t2]) , "\t " , not "-" in [getTeamById[t1],getTeamById[t2]]) + # for (t1,t2) in chosenEdges: + # print (" chosen " , getTeamById[t1],getTeamById[t2] , distanceInKmByGPS(t_lon[t1],t_lat[t1],t_lon[t2],t_lat[t2]) , "\t " , not "-" in [getTeamById[t1],getTeamById[t2]] ) + + + print (chosenEdges) + print (comp.value()) + + + model_reg = pulp.LpProblem(f"{PULP_FOLDER}/Clustering_--_Regional_"+str(thisScenario.id), pulp.LpMinimize) + reg_x = {(t,c) : pulp.LpVariable('reg_x_'+str(t)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in teams for c in reg_clusters} + for c in reg_clusters: + model_reg+= lpSum([ reg_x[(t,c)] for t in teams ])<=reg_clustersize + model_reg+= lpSum([ reg_x[(t,c)] for t in teams ])>=2 + (t1,t2) = chosenEdges[c] + compatibility = sum([1 for d in days if not blocked_arena[(t1,d, "----" )] and not blocked_arena[(t2,d, "----" )] ]) + print ("\nINIT CLUSTER ", getTeamById[t1], ", ", getTeamById[t2] , " ", compatibility ) + for t in [t1,t2]: + sst= " -- " + for d in days: + if blocked_arena[(t,d, "----" )]: + sst+= "#" + else: + sst+= " " + sst+= " " + getTeamById[t] + print (sst) + model_reg+= reg_x[(t1,c)] ==1 + model_reg+= reg_x[(t2,c)] ==1 + c_center_lat[c]= 0.5*(t_lat[t1] + t_lat[t2]) + c_center_lon[c]= 0.5*(t_lon[t1] + t_lon[t2]) + + + for t in teams : + model_reg+= lpSum([ reg_x[(t,c)] for c in reg_clusters])==1 + + for i in range(5): + model_reg+= lpSum([ distanceInKmByGPS(t_lon[t],t_lat[t],c_center_lon[c],c_center_lat[c]) * reg_x[(t,c)] for t in teams for c in reg_clusters]) + + if solver == "CBC": + model_reg.solve(PULP_CBC_CMD()) + elif solver == "Gurobi": + model_reg.solve(GUROBI(MIPGap=0.0, TimeLimit=120, msg=1)) + else: + model_reg.solve(XPRESS(msg=1,maxSeconds=120, keepFiles=True)) + + for c in reg_clusters: + # print ("CLUSTER ", c) + # for t in teams: + # if reg_x[(t,c)].value()>0.01: + # print ( getTeamById[t], reg_x[(t,c)].value() , t_lat[t] , t_lon[t] ) + c_center_lat[c]= 0.5*c_center_lat[c] + 0.5*sum ([ t_lat[t] *reg_x[(t,c)].value() for t in teams]) / sum ([reg_x[(t,c)].value() for t in teams]) + c_center_lon[c]= 0.5*c_center_lon[c] + 0.5*sum ([ t_lon[t] *reg_x[(t,c)].value() for t in teams]) / sum ([reg_x[(t,c)].value() for t in teams]) + # print ("new center ",c_center_lat[c], c_center_lon[c] ) + # print () + + reg_cluster_teams = { c : [ t for t in teams if reg_x[(t,c)].value()>0.9 ] for c in reg_clusters } + print (reg_clusters) + + useRegionalGroupsForTravel= thisSeason.nicename.find("LIGA NACIONAL")>=0 + if useRegionalGroupsForTravel: + reg_cluster_teams={} + i=0 + for c in cluster_teams.values(): + if len(c)>0: + reg_cluster_teams[i]=c + i+=1 + reg_clusters=reg_cluster_teams.keys() + + for c in reg_clusters: + print ("CLUSTER ", c) + for t in reg_cluster_teams[c]: + print ( getTeamById[t], t_lat[t] , t_lon[t] ) + father = reg_cluster_teams[c][0] + basicTeams.append(father) + for t in reg_cluster_teams[c][1:]: + if t in realteams: + regionalParent[t]=father + regionalKids[father].append(t) + f_team[t]=father + + print ("ergebnis") + print (reg_cluster_teams) + print (regionalParent) + print (regionalKids) + print () + + # print ("ALL TEAMS REGIONAL ") + # for c in regionalConferences: + # father =0 + # for t in c.teams.filter(active=True): + # if father ==0: + # father =t.id + # basicTeams.append(t.id) + # else: + # regionalParent[t.id]=father + # regionalKids[father].append(t.id) + # f_team[t.id]=father +else : + basicTeams = teams + +basicGames = [ (t1,t2) for (t1,t2) in games if t1 in basicTeams and t2 in basicTeams ] + +# print("basicGames",basicGames) +# print (f_team) +# print ("basicTeams", basicTeams) +# print ('parents :') +# print (regionalParent) +# print (regionalKids) + +# for t in basicTeams: +# if len(regionalKids[t])>0: +# print (getTeamById[t]) +# for t2 in regionalKids[t]: +# print (" " ,getTeamById[ t2] , t_conference [t]==t_conference[t2]) +# miniGameSequenceLength = 1 + +nBasicTeams = len (basicTeams) +nBasicTeamsPerCluster = int(nBasicTeams/len(gameClusters)+0.9) +if nBasicTeamsPerCluster%2==1: + nBasicTeamsPerCluster+=1 + +playSwissTable = "UCL24Patterns" in special_wishes_active +playSwissTable=False + +if playSwissTable: + nBasicTeamsPerCluster=nTeams + +nRounds1 = nBasicTeamsPerCluster-1 +nBasicRounds= nPhases * (nBasicTeamsPerCluster-1) + +if nBasicTeamsPerCluster==1 or nBasicRounds==1 or playSwissTable: + nBasicRounds=nRounds + nRounds1=nRounds + +basicRounds = range (1,nBasicRounds+1) + +print ("nPhases ", nPhases) +print ("nGameClusters ", len(gameClusters)) +print ("nBasicTeamsPerCluster ", nBasicTeamsPerCluster) +print ("nBasicTeams ", nBasicTeams) +print ("nBasicRounds ", nBasicRounds) +print ("nRounds1 ", nRounds1) + +stretch = nRounds/nBasicRounds +# print ("regionalPatternUse " , regionalPatternUse) +rounds1=range(1,nRounds1+1) +nGames=nTeams*nRounds1 + + +getBasicRound={ r : int ((r-1)/stretch)+1 for r in rounds} +getRealRounds = { br : [ r for r in rounds if getBasicRound[r]==br ] for br in basicRounds} +# print ("stretch : " , stretch) + +getBasicDays= {r : [] for r in basicRounds} +for r in rounds: + getBasicDays[getBasicRound[r]]+=(getDays[r]) + +competitions = {} +for d in Day.objects.filter(season=thisSeason): + for comp in d.internationalCompetitions.all(): + dtlb = getDateTimeDay[d.id] - datetime.timedelta(days=comp.nDaysNotPlayBefore) + dtub = getDateTimeDay[d.id] + datetime.timedelta(days=comp.nDaysNotPlayAfter) + for d2 in days: + # dt =parse(getDayById[d2]['day']) + dt =getDateTimeDay[d2] + if dtlb<=dt and dt<=dtub: + for t in comp.teams.filter(active=True): + # print ("adding ", (t.id,d2) , " for " , comp.name , dtlb , " <= ", dt , " <= ", dtub ) + competitions[(t.id,d2)]=comp.name + +getRoundDaysByDay = {d: [ rd for rd in roundDays if rd[1]==d ] for d in days} +getRoundDaysByRound = {r: [rd for rd in roundDays if rd[0]==r ] for r in rounds} + +daysSorted =[] +for dt in sorted([ getDateTimeDay[d] for d in days]): + daysSorted.append ( getDayByDateTime[dt] ) + +minRest = { (t,s1,s2) : thisSeason.minDaysBetweenGames for t in teams for s1 in ['A','H'] for s2 in ['A','H']} +for t in teamObjects: + minRest[(t['id'],'H','H')] = max(minRest[(t['id'],'H','H')], t['minRest_HH'] ) + minRest[(t['id'],'H','A')] = max(minRest[(t['id'],'H','A')], t['minRest_HA'] ) + minRest[(t['id'],'A','H')] = max(minRest[(t['id'],'A','H')], t['minRest_AH'] ) + minRest[(t['id'],'A','A')] = max(minRest[(t['id'],'A','A')], t['minRest_AA'] ) + +maxMinRest = { t : max( [ minRest[(t,s1,s2)] for s1 in ['A','H'] for s2 in ['A','H'] ] ) for t in teams } + +conflictDays = { (t,d) : [d] for d in days+higherLeagueDayIds for t in teams} +for t in teams: + for d1 in days+higherLeagueDayIds: + diffRounds =False + for d2 in days+higherLeagueDayIds: + # print (getRoundsByDay[d1],getRoundsByDay[d2],getRoundsByDay[d1]!=getRoundsByDay[d2]) + if getDateTimeDay[d2]>getDateTimeDay[d1] and getDateTimeDay[d2]-getDateTimeDay[d1] <= datetime.timedelta(days=maxMinRest[t]): + # print (getDateTimeDay[d1],getDateTimeDay[d2], getRoundByDay[d1], getRoundByDay[d2],getDateTimeDay[d2] > getDateTimeDay[d1] , getDateTimeDay[d2]-getDateTimeDay[d1] , datetime.timedelta(days=thisSeason.minDaysBetweenGames)) + conflictDays[(t,d1)].append(d2) + if d1 in higherLeagueDayIds+upperAndLowerLeagueIds or d2 in higherLeagueDayIds+upperAndLowerLeagueIds or getRoundsByDay[d1]!=getRoundsByDay[d2]: + diffRounds=True + + if not diffRounds: + conflictDays[(t,d1)]=[] + if len(higherTeamsOf[t])>0 and d1 in upperAndLowerLeagueIds: + conflictDays[(t,d1)]=[d1] + +# print (thisScenario.sol_solution) + +varietyNetWorks=[] +networkName ={} +networkFavTeams ={} +for bn in BroadcastingNetwork.objects.filter(scenario=s2): + networkName[bn.id] =bn.name + networkFavTeams[bn.id] = [ t.id for t in bn.teams.all() ] + if bn.variety: + varietyNetWorks.append(bn.id) + + +fixedGames2 = [] +for l in thisScenario.fixedgameslist(): + # print (l) + if int(l[0]) in getRoundsByDay.keys() and int(l[1]) in realteams and int(l[2]) in realteams : + fixedGames2.append([int(l[1]),int(l[2]),int(l[0]) ]) + +fixedDays=[] +if thisScenario.sol_fixed_days!= "": + fixedDays = [ int(g) for g in thisScenario.sol_fixed_days.split('_')] + + +fixedRounds=[] +if thisScenario.sol_fixed!='': + fixedRounds = [ int(g) for g in thisScenario.sol_fixed.split('_')] + +fixedGames=[] +currentSolution=[] +currentAwayLocation = { (t,d) : False for t in teams for d in days} +currentKickoffTimes = defaultdict(lambda:None) + +excessGames = { rd: -roundDaysMax[rd] for rd in roundDays } +deficientGames = { rd: roundDaysMin[rd] for rd in roundDays } + +fixedBroadCastingSlots = [] + +for l in thisScenario.solutionlist(): + # print ("adding " , l , len(l)>=4 , int(l[0]) in getRoundsByDay.keys() ) + if len(l)>=4 and int(l[0]) in getRoundsByDay.keys() and int(l[1]) in realteams and int(l[2]) in realteams : + # print (" ++++ ") + h_id= int(l[1]) + a_id= int(l[2]) + r_id= int(l[3]) + d_id= int(l[0]) + # if len(l)>6 and l[6]!="None" and [h_id,a_id,d_id] in fixedGames2: + if r_id not in getRoundsByDay[d_id] and len(getRoundsByDay[d_id])>0: + print ("WRONG ROUND", d_id , " not in round " , r_id, " putting it in round " , getRoundsByDay[d_id][0]) + r_id = getRoundsByDay[d_id][0] + if (r_id,d_id) in roundDays: + excessGames[(r_id,d_id)]+=1 + deficientGames[(r_id,d_id)]-=1 + nf = [h_id, a_id, d_id] + for tm in ["----"] + times : + if blocked_arena[(h_id,d_id,tm)] : + print ("Allowing ",tm , "usage of blocked arena of " , getTeamById[h_id] , " at " , getNiceDay[d_id] ) + blocked_arena[(h_id,d_id,tm)]=False + + if len(intersection(getRoundsByDay[d_id], fixedRounds))>0 : + fixedGames.append(nf) + if d_id in fixedDays : + if not nf in fixedGames2: + fixedGames2.append(nf) + if len(l)>=4: + currentSolution.append([ h_id, a_id, r_id,d_id]) + currentAwayLocation[(a_id,d_id)]=h_id + if len(l) >= 5: + if l[4].isnumeric() and l[4] in times: + currentKickoffTimes[(h_id,a_id,d_id)] = l[4] + elif l[4] in getIdByTime.keys(): + currentKickoffTimes[(h_id,a_id,d_id)] = getIdByTime[l[4]] + else: + currentKickoffTimes[(h_id,a_id,d_id)] = None + else: + if len(times)>0: + currentKickoffTimes[(h_id,a_id,d_id)] = times[0] + + if len(l)>6 and l[6]!="None" and (evalRun or d_id in fixedDays or [h_id,a_id,d_id] in fixedGames2): + for nw in l[6].split(";"): + if int(nw) in networkName.keys(): + fixedBroadCastingSlots.append((h_id,a_id,d_id,int(nw))) + +excessGames = { rd: max(0,excessGames[rd]) for rd in roundDays } +deficientGames = { rd: max(0,deficientGames[rd]) for rd in roundDays } + +print ("currentSolution:") +print (len(currentSolution)) +print ("fixedDays :",len(fixedDays)) +print ("fixedGames (nur runde ist wichtig):",len(fixedGames)) +print ("fixedGames2 (datum ist wichtig):",len(fixedGames2)) + +# print ("fixedGames",fixedGames) +# for ff in fixedGames: +# print (getNiceDay[ff[2]], "\t",getTeamById[ff[0]],"\t",getTeamById[ff[1]] ) +# print ("fixedGames2",fixedGames2, thisScenario.sol_fixed_games) +# for ff in fixedGames2: +# print (getNiceDay[ff[2]], "\t",getTeamById[ff[0]],"\t",getTeamById[ff[1]] ) + +tripElements={t:[] for t in teams} +trip_minDays={} +trip_maxDays={} +trip_prio={} +trip_weekdays={} +for te in TripElement.objects.filter(scenario=s2,active=True): + trip_minDays[te.id]=te.minDaysBetweenGames + trip_maxDays[te.id]=te.maxDaysBetweenGames + trip_prio[te.id]=te.prio + trip_weekdays[te.id]=[ dd[:3] for dd in te.possible_start_weekdays() ] + vt1= [ tt.id for tt in te.visited_teams_first.all()] + vt2= [ tt.id for tt in te.visited_teams_second.all()] + vt3= [ tt.id for tt in te.visited_teams_third.all()] + vt4= [ tt.id for tt in te.visited_teams_fourth.all()] + for tf in te.travelling_teams.all(): + tripElements[tf.id].append((te.id,vt1,vt2,vt3,vt4)) + +allowed_weekdays={'--' : [0,1,2,3,4,5,6], 'Mondays':[0], 'Tuesdays':[1], 'Wednesdays':[2], 'Thursdays':[3], 'Fridays':[4], 'Saturdays':[5],'Sundays':[6], 'Weekdays':[0,1,2,3,4], 'Weekends':[5,6], 'Mon.-Thu.':[0,1,2,3] , 'Fri.-Sun.':[4,5,6] } + +hawishes = HAWish.objects.filter(scenario=s2,active=True).order_by('prio').values() +hawTeams = {} +hawDays = {} +hawTimes = {} +hawRounds = {} +hawRoundsString = {} +hawTagweight = {} +for c in HAWish.objects.filter(scenario=s2): + hawTagweight[c.id] = c.tag_weight() + # print () + # print (c.reason ) + hawDays[c.id] = [] + hawTeams[c.id] = [t.id for t in c.get_teams()] + hawTimes[c.id]= [ str(dd.id) for dd in c.timeslots.all() ] + + if c.multidate: + hawDays[c.id]= [ dd.id for dd in c.dates.all() ] + # print ("multidate") + else: + if c.day and not c.day2: + hawDays[c.id].append(c.day.id) + # print('+ ',getDayById[e['day_id']]) + + if not c.day and c.day2: + hawDays[c.id].append(c.day2.id) + # print('+ ',getDayById[e['day2_id']]) + + if not c.day and not c.day2: + for d in days: + dt = getDateTimeDay[d] + if dt.weekday() in allowed_weekdays[c.weekdays]: + # print (hawDays[e['id']]) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + + if c.day and c.day2: + day1= getDateTimeDay[c.day.id] + day2= getDateTimeDay[c.day2.id] + for d in days: + dt = getDateTimeDay[d] + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + if day1<=dt and dt<=day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + # print (day1, "<=" , dt , "<=", day2 , " " , day1<=dt and dt<=day2 ) + hawDays[c.id].append(d) + # print('+ ',getDayById[d]) + +print ("processing encounters") +encwishes = EncWish.objects.filter(scenario=s2,active=True).order_by('prio').values() +encTeams1 = {} +encTeams2 = {} +encGroups = {} +encGames = {} +encTeams1String = {} +encTeams2String = {} +encDays = { e['id'] : [] for e in encwishes} +encDaySets={} +encTimes = {} +encRounds = { e['id'] : [] for e in encwishes} +encRoundsString = { e['id'] : "" for e in encwishes} +encTagweight = {} +for enc in encwishes: + enc["seed"]= enc['reason'].find("Seed Game")>=0 or enc['reason'].find("SEED")>=0 +for c in EncWish.objects.filter(scenario=s2,active=True): + encTagweight[c.id] = c.tag_weight() + encTimes[c.id]= [ str(dd.id) for dd in c.timeslots.all() ] + encTeams1[c.id] = [] + encTeams2[c.id] = [] + encGroups[c.id] = [] + encTeams1String[c.id] = '' + encTeams2String[c.id] = '' + if c.useEncounterGroups: + for t in c.encounterGroups.all(): + encGroups[c.id].append(t) + else: + encTeams1[c.id] = [t.id for t in c.get_teams1()] + encTeams2[c.id] = [t.id for t in c.get_teams2()] + + if c.useEncounterGroups: + tmp_games = [(t1.id,t2.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1!=t2 ] + tmp_games += [(t2.id,t1.id) for eg in encGroups[c.id] for ec in eg.encounter_set.all() for t1 in ec.homeTeams.all() for t2 in ec.awayTeams.all() if t1!=t2 and ec.symmetry] + if c.minDistance>0: + print ("before " , tmp_games) + tmp_games=[(t1,t2) for (t1,t2) in tmp_games if distanceById[t1,t2]>=c.minDistance ] + print ("after " , tmp_games) + tmp_games=list(set(tmp_games)) + encGames[c.id] = [tmp_games] + else: + elemHomeTeams= [encTeams1[c.id]] + if c.forEachTeam1: + elemHomeTeams= [ [t] for t in encTeams1[c.id]] + elemAwayTeams= [ encTeams2[c.id]] + if c.forEachTeam2: + elemAwayTeams= [ [t] for t in encTeams2[c.id]] + encGames[c.id]= [] + # print ("NEW ENC ", c.reason ) + # print (" - ", elemHomeTeams,elemAwayTeams ) + for elh in elemHomeTeams: + for ela in elemAwayTeams: + # print (" --- ENC ", elh,ela) + tmp_games= [(t1,t2) for t1 in elh for t2 in ela if t1!=t2 ] + # tmp_games= [(t1,t2) for t1 in elh for t2 in ela if t1!=t2 and (t1,t2) in games] + if c.symmetry: + tmp_games += [(t1,t2) for t1 in ela for t2 in elh if t1!=t2 ] + tmp_games=list(set(tmp_games)) + if c.minDistance>0: + print ("before " , tmp_games) + tmp_games=[(t1,t2) for (t1,t2) in tmp_games if distanceById[t1,t2]>=c.minDistance ] + print ("after " , tmp_games) + if len(tmp_games)>0: + encGames[c.id].append(tmp_games) + + if c.multidate: + encDays[c.id]= [ dd.id for dd in c.dates.all() ] + else: + if c.day: + day1= getDateTimeDay[c.day.id] + + if c.day and not c.day2: + encDays[c.id].append(c.day.id) + + if not c.day and c.day2: + encDays[c.id].append(c.day2.id) + + if not c.day and not c.day2: + for d in days: + dt= getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + if c.day and c.day2: + # day1= parse(c.day.day) + # day2= parse(c.day2.day) + day1= getDateTimeDay[c.day.id] + day2= getDateTimeDay[c.day2.id] + for d in days: + dt= getDateTimeDay[d] + # dt = parse(getDayById[d]['day']) + if day1<=dt and dt<=day2 and dt.weekday() in allowed_weekdays[c.weekdays]: + encDays[c.id].append(d) + + encDaySets[c.id]=[] + elemDays= [ encDays[c.id]] + if c.forEachDay or c.forOneDay: + elemDays= [ [d] for d in encDays[c.id]] + + lastDaySet=[] + for d in elemDays : + tmpDays=d + if (c.forEachDay or c.forOneDay) and c.timeframe!=0: + tmpDays = [] + # day1= parse(getDayById[d[0]]['day']) + day1= getDateTimeDay[d[0]] + if c.timeframe>0: + day2= day1 + datetime.timedelta(days=c.timeframe-1) + # print (e) + # print (day1, day2) + for d3 in days: + dt= getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1<=dt and dt<=day2 : + tmpDays.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt= getDateTimeDay[d3] + if day1<=dt and getRoundByDay[d3]< r1 + (-c.timeframe) : + tmpDays.append(d3) + # print (" ROUNDWISH ", e, elemEncWishDays[cntr], e['timeframe']) + # for d4 in elemEncWishDays[cntr]: + # print (" - " ,getDayById[d4]['day']) + + if len([ d for d in tmpDays if d not in lastDaySet ])>0: + encDaySets[c.id].append(tmpDays) + lastDaySet = tmpDays + # print("-.--- NEW DAYS", tmpDays) + + for ds in encDaySets[c.id]: + for d3 in ds: + encRounds[c.id]+= getRoundsByDay[d3] + encRounds[c.id]=sorted(list(set(encRounds[c.id]))) + for r in encRounds[c.id]: + encRoundsString[c.id]+= str(r)+"_" + if encRoundsString[c.id]!="": + encRoundsString[c.id]=encRoundsString[c.id][:-1] + # print (encRoundsString[c.id] , " # " ,c.affected_rounds , encRoundsString[c.id] != c.affected_rounds) + + + +# print(encDays) +# print (encGames) +# print (encDaySets) +# print (encRounds) +# print (encRoundsString) +# return ("") + +seed_games= [] +for enc in encwishes : + if not enc['useEncounterGroups'] and not enc['multidate'] and enc['seed'] and len(encTeams1[enc['id']])==1 and len(encTeams2[enc['id']])==1 and enc['day_id']: + seed_games.append((encTeams1[enc['id']][0] ,encTeams2[enc['id']][0] ,enc['day_id'])) + # if blocked_arena[(encTeams1[enc['id']][0], enc['day_id'], "----" )]: + # print ("SEED GAME IN " , getTeamById[ encTeams1[enc['id']][0]] , " at day ", getNiceDay[enc['day_id']]) + +# must_home = {t : sorted([ r for (h,a,r) in seed_games if h==t]) for t in teams} +# must_away = {t : sorted([ r for (h,a,r) in seed_games if h==a]) for t in teams} +# print (seed_games) +# print ("must_home",must_home) +# print ("must_away",must_away) +# exit(0) + +if runMode=='Improve' and len(thisScenario.solutionlist())>0 : + oldGameCntr={gm :0 for gm in games } + for gm in currentSolution: + if not (gm[0],gm[1]) in oldGameCntr.keys(): + print ("ALERT ", (gm[0],gm[1]) ," not in ", oldGameCntr.keys() ) + else: + oldGameCntr[(gm[0],gm[1])]+=1 + + unsatisfied_seed_games= [ (h_id,a_id,d_id) for (h_id,a_id,d_id) in seed_games if currentAwayLocation[(a_id,d_id)]!=h_id ] + missing_gms = [ (t1,t2) for (t1,t2) in realgames if oldGameCntr[(t1,t2)]8: + random.shuffle(impScript2) + impScript= impScript[:1] + seed_imp+ impScript2 + + print ("missing_imp", missing_imp) + print ("seed_imp", seed_imp) + + release_blocking_games = missing_gms + if len( missing_gms)>0: + release_blocking_games += realgames + + for (t1,t2) in release_blocking_games: + for d in days: + if not hidden_arena[(t1,d, "----" )] : + blocked_arena[(t1,d, "----" )]=False + # print ("FREIGABE ", getTeamById[t1], getNiceDay[ d]) + + + +onlyEarlyDays= [] +onlyLateDays= [] + +if mathModelName=="NBA" and nRounds >34: + onlyLateDays=[d for d in days if not getWeekDay[d] in ["Sat", "Sun"] and not getNiceDay[d] in ["Fri, Nov 29, 2019", "Wed, Dec 25, 2019" ,"Tue, Dec 31, 2019", "Mon, Jan 20, 2020", "Mon, Jan 24, 2020"] + ["Fri, Dec 25, 2020" ,"Thu, Dec 31, 2020", "Mon, Jan 18, 2021" ] ] + +# if thisSeason.useFeatureOpponentMatrix: +# for gm in gameRequirements: +# # print (gm) +# if gm.number> 0.5*nPhases: +# extragames[(gm.team1.id,gm.team2.id)]= gm.number - 0.5*nPhases + +alwaysConsiderAllGames = not mathModelName in ["Florida State League" , "UEFA NL"] + +# print (conferences) +conferencewishes = ConferenceWish.objects.filter(scenario=s2).values() + + +# print ("encGroups" ,encGroups) + + + +elemEncWishes ={e['id'] : [] for e in encwishes} +elemEncWishGames={} +elemEncWishDays={} +elemEncWishNum ={} + +cntr=0 +for e in encwishes: + for eg in encGames[e['id']] : + for ed in encDaySets[e['id']]: + if (len(eg)>0 and len(ed)>0) or enc['minGames']>0: + cntr+=1 + elemEncWishes[e['id']].append(cntr) + elemEncWishGames[cntr] = eg + elemEncWishDays[cntr] = ed + elemEncWishNum[cntr] = 1 + if e['maxGames'] ==0: + elemEncWishNum[cntr]=len(elemEncWishDays[cntr])*len(elemEncWishGames[cntr]) + if e['minGames'] > 0: + elemEncWishNum[cntr]=e['minGames'] + + +# print (elemEncWishGames) +# print (elemEncWishDays) + + +encRelRoundsMin = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]} +encRelRoundsMax = { el :[] for enc in encwishes for el in elemEncWishes[enc['id']]} + +for enc in encwishes: + # print (e) + # print ("ENC !! " , enc['reason'] ) + for el in elemEncWishes[enc['id']]: + relDaysSet = set(elemEncWishDays[el]) + for r in basicRounds : + if len(relDaysSet.intersection(set(getBasicDays[r])))>0: + frac = len(relDaysSet.intersection(set(getBasicDays[r]))) / len (getBasicDays[r]) + # print (len(relDaysSet.intersection(set(getBasicDays[r]))) , len (getBasicDays[r]) , frac) + if frac>0: + encRelRoundsMin[el].append(r) + if frac==1: + encRelRoundsMax[el].append(r) + + enc['nWishes'] = sum ( elemEncWishNum[ee] for ee in elemEncWishes[enc['id']] ) + if enc['forOneDay']: + enc['nWishes'] = enc['forOneDayNum'] * len([g for g in encGames[enc['id']] if len(encGames[enc['id']])>0]) + +nElemEncWishes= sum(w['nWishes'] for w in encwishes) + +# print (encRelRoundsMin) + +elemHaWishes ={e['id'] : [] for e in hawishes} +elemHaWishTeams={} +elemHaWishDays ={} +elemHaWishFirstDay ={} +elemHaWishNum ={} + +cntr =1 +for e in hawishes: + elemTeams= [ hawTeams[e['id']]] + if e['forEachTeam']: + elemTeams= [ [t] for t in hawTeams[e['id']]] + + elemDays= [ hawDays[e['id']]] + if e['forEachDay'] or e['forOneDay'] : + elemDays= [ [d] for d in hawDays[e['id']]] + + elemHaWishes[e['id']]=[] + allElemDays=[] + thisDaySet=[] + lastDaySet = [] + # print("elemDays",elemDays) + for d in elemDays : + # print (e) + if (e['forEachDay'] or e['forOneDay']) and e['timeframe']!=1: + thisDaySet=[] + # day1= parse(getDayById[d[0]]['day']) + day1= getDateTimeDay[d[0]] + if e['timeframe']>1: + day2=day1 + datetime.timedelta(days=e['timeframe']-1) + for d3 in days: + # dt = parse(getDayById[d3]['day']) + dt= getDateTimeDay[d3] + if day1<=dt and dt<=day2 : + thisDaySet.append(d3) + else: + r1 = getDayById[d[0]]['round'] + for d3 in days: + dt= getDateTimeDay[d3] + # dt = parse(getDayById[d3]['day']) + if day1<=dt and r1 <= getRoundByDay[d3] and getRoundByDay[d3]< r1 + (-e['timeframe']) : + thisDaySet.append(d3) + # print (" ROUND HA WISH ", e['reason'], thisDaySet, e['timeframe']) + else: + thisDaySet=d + + # only create wish id new day set is superset + if len([d for d in thisDaySet if d not in lastDaySet])>0: + for t in elemTeams: + cntr+=1 + elemHaWishes[e['id']].append(cntr) + elemHaWishTeams[cntr]=t + elemHaWishDays[cntr]=thisDaySet.copy() + elemHaWishFirstDay[cntr]=d[0] + elemHaWishNum[cntr]=1 + if e['maxGames'] ==0: + elemHaWishNum[cntr]=len(elemHaWishDays[cntr])*len(elemHaWishTeams[cntr]) + if e['minGames'] > 0: + elemHaWishNum[cntr]=e['minGames'] + lastDaySet = thisDaySet.copy() + allElemDays+= thisDaySet + + hawRounds[e['id']]=[] + for d3 in set(allElemDays): + hawRounds[e['id']]+=getRoundsByDay[d3] + hawRounds[e['id']]=sorted(list(set(hawRounds[e['id']]))) + hawRoundsString[e['id']]= "" + for r in hawRounds[e['id']]: + hawRoundsString[e['id']]+= str(r)+"_" + if hawRoundsString[e['id']]!="": + hawRoundsString[e['id']]=hawRoundsString[e['id']][:-1] + e['nWishes'] = sum ( elemHaWishNum[ee] for ee in elemHaWishes[e['id']] ) + + if e['forOneDay']: + e['nWishes'] = e['forOneDayNum'] * len(elemTeams) + +nElemHaWishes= sum(w['nWishes'] for w in hawishes) +# # print ("nElemHaWishes",nElemHaWishes) +# print ("hawRounds",hawRounds) +# print ("hawRoundsString",hawRoundsString) + + + +# encGroups = EncGroup.objects.filter(scenario=s2).values() +# derbyRestrictions = DerbyRestriction.objects.filter(scenario=s2).values() +# fairnessCons = FairnessCon.objects.filter(scenario=s2).values() +# englishWeeks = EnglishCon.objects.filter(scenario=s2).values() +# objectiveFunctionWeights = ObjectiveFunctionWeight.objects.filter(scenario=s2).values() + +# gew={} +# for ow in objectiveFunctionWeights: +# gew[ow['name']]= ow['use'] * ow['prio'] + +# print(attractivity) +# allow={} +# allow["ATL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["BKN"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["BOS"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["CHA"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["CHI"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "NYK", "OKC", "PHI", "SAS", "TOR", "WAS"] +# allow["CLE"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["DAL"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["DEN"]=["DAL", "DEN", "GSW", "LAC", "LAL", "MEM", "MIL", "MIN", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["DET"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "TOR", "WAS"] +# allow["GSW"]=["DEN", "GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["HOU"]=["ATL", "CHA", "CHI", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["IND"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "TOR", "WAS"] +# allow["LAC"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["LAL"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["MEM"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHI", "SAS", "UTA", "WAS"] +# allow["MIA"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "HOU", "IND", "MEM", "MIA", "MIL", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["MIL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "NYK", "OKC", "PHI", "SAS", "TOR", "WAS"] +# allow["MIN"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "PHI", "PHX", "SAS", "UTA"] +# allow["NOP"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["NYK"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["OKC"]=["ATL", "CHA", "CHI", "CLE", "DAL", "DEN", "DET", "HOU", "IND", "MEM", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["ORL"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DAL", "DET", "HOU", "IND", "MEM", "MIA", "MIL", "NOP", "NYK", "OKC", "ORL", "PHI", "SAS", "WAS"] +# allow["PHI"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "ORL", "PHI", "TOR", "WAS"] +# allow["PHX"]=["DAL", "DEN", "GSW", "HOU", "LAC", "LAL", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["POR"]=["GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["SAC"]=["DEN", "GSW", "LAC", "LAL", "PHX", "POR", "SAC", "UTA"] +# allow["SAS"]=["ATL", "CHA", "CHI", "DAL", "DEN", "HOU", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "OKC", "ORL", "PHX", "SAS", "UTA"] +# allow["TOR"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIL", "MIN", "NYK", "PHI", "TOR", "WAS"] +# allow["UTA"]=["DAL", "DEN", "GSW", "LAC", "LAL", "MIN", "OKC", "PHX", "POR", "SAC", "SAS", "UTA"] +# allow["WAS"]=["ATL", "BKN", "BOS", "CHA", "CHI", "CLE", "DET", "IND", "MEM", "MIA", "MIL", "MIN", "NOP", "NYK", "ORL", "PHI", "TOR", "WAS"] + +# print ("DISTANCES NBA") + +# shortnames = allow.keys() +# for testDist in [1825,1850,1875]: +# maxdist =0 +# mindist =1000000 +# cntr=0 +# for t in shortnames: +# for t2 in shortnames: +# thisdist = distance[teamByShort[t],teamByShort[t2]] +# if t2 in allow[t]: +# maxdist = max(maxdist,thisdist) +# if thisdist > testDist: +# # print ("close : " ,teamByShort[t] , teamByShort[t2] , thisdist ) +# cntr+=1 +# else: +# mindist = min(mindist,thisdist) +# if thisdist < testDist: +# # print ("far : " ,teamByShort[t] , teamByShort[t2] , thisdist ) +# cntr+=1 +# print ("mismatch at " , testDist ," : ", cntr) + +# print ("maxdist ", maxdist) +# print ("mindist ", mindist) + + + +# gameCntr = { (t1,t2) :0 for t1 in shortnames for t2 in shortnames } +# wantedGames =[('TOR','NOP'), ('LAC','LAL'), ('CHA','CHI'), ('ORL','CLE'), ('IND','DET'), ('PHI','BOS'), ('MIA','MEM'), ('BKN','MIN'), ('SAS','NYK'), ('DAL','WAS'), ('UTA','OKC'), ('POR','DEN'), ('PHX','SAC'), ('DET','ATL'), ('HOU','MIL'), ('GSW','LAC'), ('CHA','MIN'), ('BOS','TOR'), ('BKN','NYK'), ('MEM','CHI'), ('NOP','DAL'), ('OKC','WAS'), ('DEN','PHX'), ('SAC','POR'), ('LAL','UTA'), ('MIL','MIA'), ('DET','PHI'), ('NYK','BOS'), ('ATL','ORL'), ('CLE','IND'), ('HOU','NOP'), ('CHI','TOR'), ('SAS','WAS'), ('UTA','SAC'), ('PHX','LAC'), ('OKC','GSW'), ('MEM','BKN'), ('MIN','MIA'), ('DAL','POR'), ('LAL','CHA'), ('NYK','CHI'), ('DET','IND'), ('TOR','ORL'), ('ATL','PHI'), ('MIL','CLE'), ('NOP','GSW'), ('HOU','OKC'), ('SAS','POR'), ('SAC','DEN'), ('PHX','UTA'), ('LAC','CHA'), ('MIA','ATL'), ('DEN','DAL'), ('LAL','MEM'), ('CLE','CHI'), ('PHI','MIN'), ('ORL','NYK'), ('TOR','DET'), ('BKN','IND'), ('BOS','MIL'), ('WAS','HOU'), ('OKC','POR'), ('SAC','CHA'), ('UTA','LAC'), ('GSW','PHX'), ('ATL','MIA'), ('NOP','DEN'), ('LAC','SAS'), ('IND','CLE'), ('BKN','HOU'), ('ORL','MIL'), ('BOS','NYK'), ('CHI','DET'), ('DAL','LAL'), ('SAC','UTA'), ('GSW','SAS'), ('OKC','NOP'), ('DET','BKN'), ('ORL','DEN'), ('WAS','MIN'), ('MEM','PHX'), ('MIL','TOR'), ('GSW','CHA'), ('POR','PHI'), ('IND','CHI'), ('MIA','HOU'), ('NYK','SAC'), ('SAS','LAL'), ('CLE','DAL'), ('LAC','UTA'), ('WAS','DET'), ('BKN','NOP'), ('MEM','HOU'), ('MIN','MIL'), ('PHX','PHI'), ('GSW','POR'), ('CLE','BOS'), ('CHA','IND'), ('ATL','SAS'), ('CHI','LAL'), ('OKC','ORL'), ('DEN','MIA'), ('DET','NYK'), ('IND','WAS'), ('ATL','CHI'), ('HOU','GSW'), ('TOR','SAC'), ('MEM','MIN'), ('DAL','ORL'), ('UTA','PHI'), ('LAC','MIL'), ('CHA','BOS'), ('SAS','OKC'), ('PHX','MIA'), ('LAC','POR'), ('WAS','CLE'), ('IND','DET'), ('ORL','MEM'), ('ATL','SAC'), ('MIN','GSW'), ('NOP','TOR'), ('DAL','NYK'), ('UTA','MIL'), ('DEN','PHI'), ('POR','BKN'), ('LAL','MIA'), ('SAS','BOS'), ('CHA','NOP'), ('MEM','DAL'), ('OKC','GSW'), ('CHI','HOU'), ('MIN','DEN'), ('PHI','CHA'), ('ORL','IND'), ('OKC','MIL'), ('NYK','CLE'), ('PHX','BKN'), ('POR','ATL'), ('LAL','TOR'), ('DET','MIN'), ('BOS','DAL'), ('NOP','HOU'), ('SAS','MEM'), ('LAC','TOR'), ('GSW','UTA'), ('PHI','CLE'), ('IND','OKC'), ('MIA','DET'), ('CHI','NYK'), ('DEN','ATL'), ('UTA','BKN'), ('PHX','LAL'), ('SAC','POR'), ('CHA','MEM'), ('ORL','PHI'), ('HOU','LAC'), ('BOS','WAS'), ('MIN','SAS'), ('LAL','GSW'), ('POR','TOR'), ('CLE','MIA'), ('MIL','CHI'), ('NYK','DAL'), ('NOP','LAC'), ('PHX','ATL'), ('DEN','BKN'), ('CHA','DET'), ('ORL','SAS'), ('HOU','IND'), ('OKC','PHI'), ('MEM','UTA'), ('MIN','WAS'), ('GSW','BOS'), ('LAL','SAC'), ('CHI','BKN'), ('IND','MIL'), ('NYK','CHA'), ('MIN','HOU'), ('MIA','NOP'), ('SAS','POR'), ('DAL','TOR'), ('LAC','ATL'), ('CLE','PHI'), ('SAC','BOS'), ('MEM','DEN'), ('ORL','WAS'), ('NOP','GSW'), ('LAL','ATL'), ('NYK','CLE'), ('TOR','CHA'), ('BKN','IND'), ('CHI','MIL'), ('HOU','POR'), ('DAL','SAS'), ('PHX','BOS'), ('UTA','MIN'), ('LAC','OKC'), ('MEM','GSW'), ('NOP','POR'), ('SAC','PHX'), ('LAL','OKC'), ('PHI','NYK'), ('WAS','SAS'), ('BKN','CHA'), ('MIA','CLE'), ('DAL','GSW'), ('ATL','MIL'), ('TOR','ORL'), ('CHI','DET'), ('MIN','UTA'), ('DEN','HOU'), ('LAC','BOS'), ('MIL','POR'), ('PHX','NOP'), ('DET','ATL'), ('WAS','CHA'), ('BKN','SAC'), ('OKC','LAL'), ('CHI','MIA'), ('PHI','SAS'), ('DAL','CLE'), ('DEN','BOS'), ('UTA','GSW'), ('LAC','HOU'), ('MIN','PHX'), ('CHA','CHI'), ('IND','ORL'), ('PHI','MIA'), ('NYK','SAS'), ('ATL','TOR'), ('MEM','LAL'), ('CLE','POR'), ('MIL','DET'), ('UTA','NOP'), ('HOU','DAL'), ('NYK','BKN'), ('WAS','SAC'), ('DEN','PHX'), ('LAC','NOP'), ('CLE','BKN'), ('IND','MEM'), ('DET','ORL'), ('MIA','CHA'), ('ATL','MIN'), ('TOR','PHI'), ('BOS','SAC'), ('CHI','POR'), ('MIL','UTA'), ('SAS','LAL'), ('GSW','OKC'), ('DAL','LAC'), ('DEN','WAS'), ('BOS','BKN'), ('CHA','DET'), ('CLE','ORL'), ('PHI','SAC'), ('IND','UTA'), ('TOR','NYK'), ('MIL','ATL'), ('MEM','LAC'), ('HOU','MIA'), ('SAS','MIN'), ('PHX','WAS'), ('NOP','LAL'), ('POR','OKC'), ('GSW','CHI'), ('BKN','BOS'), ('DET','CHA'), ('ORL','TOR'), ('CLE','MIL'), ('NYK','PHI'), ('IND','ATL'), ('MIA','GSW'), ('OKC','NOP'), ('MEM','UTA'), ('SAS','LAC'), ('PHX','DAL'), ('POR','CHI'), ('LAL','WAS'), ('SAC','DEN'), ('PHI','IND'), ('HOU','ATL'), ('MIL','CHA'), ('BKN','MIA'), ('NYK','BOS'), ('MIN','MEM'), ('LAL','DAL'), ('NOP','OKC'), ('DET','SAS'), ('ORL','GSW'), ('TOR','UTA'), ('LAC','WAS'), ('CHA','PHX'), ('PHI','UTA'), ('ATL','GSW'), ('MEM','IND'), ('MIL','NYK'), ('SAC','CHI'), ('CLE','DET'), ('WAS','ORL'), ('NOP','DAL'), ('TOR','MIA'), ('SAS','HOU'), ('DEN','LAL'), ('LAC','POR'), ('CHA','GSW'), ('DET','MIL'), ('ORL','PHX'), ('ATL','BKN'), ('BOS','MIA'), ('OKC','IND'), ('CHI','MEM'), ('DAL','MIN'), ('UTA','LAL'), ('POR','SAC'), ('WAS','PHI'), ('NYK','DEN'), ('TOR','HOU'), ('NOP','PHX'), ('CHA','BKN'), ('DET','IND'), ('CLE','ORL'), ('BOS','DEN'), ('CHI','GSW'), ('OKC','MIN'), ('MIA','WAS'), ('MIL','LAC'), ('SAS','SAC'), ('POR','LAL'), ('DAL','NOP'), ('PHI','CLE'), ('NYK','IND'), ('HOU','PHX'), ('UTA','MEM'), ('BKN','DEN'), ('CHA','ATL'), ('MIA','CHI'), ('WAS','LAC'), ('PHI','TOR'), ('DAL','SAC'), ('POR','OKC'), ('LAL','MIN'), ('IND','LAC'), ('BOS','CLE'), ('NOP','DET'), ('MIL','ORL'), ('HOU','SAC'), ('CHI','TOR'), ('PHX','MIN'), ('UTA','OKC'), ('GSW','MEM'), ('PHI','DEN'), ('CHA','WAS'), ('MIA','ATL'), ('POR','NYK'), ('IND','BOS'), ('CLE','HOU'), ('TOR','LAC'), ('ORL','LAL'), ('BKN','CHA'), ('CHI','ATL'), ('MIN','UTA'), ('PHX','MEM'), ('MIL','NOP'), ('SAC','OKC'), ('GSW','NYK'), ('BOS','PHI'), ('SAS','CLE'), ('DET','DAL'), ('DEN','POR'), ('ORL','HOU'), ('PHI','NOP'), ('ATL','IND'), ('CHI','CHA'), ('MIN','LAC'), ('MIA','LAL'), ('MEM','MIL'), ('SAC','NYK'), ('UTA','GSW'), ('PHX','SAS'), ('TOR','BKN'), ('CHI','LAC'), ('MEM','WAS'), ('MIL','CLE'), ('DAL','MIA'), ('HOU','DET'), ('DEN','OKC'), ('NOP','ORL'), ('IND','CHA'), ('ATL','LAL'), ('BKN','PHI'), ('DEN','NYK'), ('GSW','SAC'), ('DET','WAS'), ('TOR','CLE'), ('OKC','CHI'), ('MIL','DAL'), ('MEM','MIA'), ('HOU','SAS'), ('PHX','POR'), ('IND','LAL'), ('CHA','SAC'), ('NYK','ATL'), ('NOP','BKN'), ('UTA','ORL'), ('LAC','PHX'), ('CLE','CHA'), ('WAS','CHI'), ('DET','TOR'), ('PHI','MIA'), ('OKC','MEM'), ('MIN','NOP'), ('DAL','BOS'), ('DEN','ORL'), ('POR','GSW'), ('ATL','UTA'), ('MIL','LAL'), ('SAS','BKN'), ('LAC','HOU'), ('CLE','MEM'), ('IND','SAC'), ('BOS','DET'), ('TOR','WAS'), ('PHI','DAL'), ('MIA','NYK'), ('OKC','PHX'), ('DEN','MIN'), ('POR','ORL'), ('GSW','NOP'), ('CHA','UTA'), ('BKN','ATL'), ('DET','CHI'), ('PHI','WAS'), ('NYK','MIL'), ('MEM','SAC'), ('SAS','LAC'), ('PHX','HOU'), ('POR','MIN'), ('TOR','DAL'), ('BOS','CHA'), ('MIL','IND'), ('OKC','LAC'), ('LAL','DEN'), ('CLE','ATL'), ('ORL','CHI'), ('DET','PHI'), ('IND','TOR'), ('NYK','WAS'), ('MIA','UTA'), ('MEM','SAS'), ('PHX','DEN'), ('SAC','HOU'), ('POR','NOP'), ('GSW','MIN'), ('TOR','BOS'), ('PHI','MIL'), ('GSW','HOU'), ('LAL','LAC'), ('DEN','NOP'), ('DET','WAS'), ('OKC','MEM'), ('BKN','NYK'), ('DAL','SAS'), ('SAC','MIN'), ('UTA','POR'), ('BOS','CLE'), ('CHA','OKC'), ('ORL','PHI'), ('ATL','MIL'), ('MIA','IND'), ('GSW','PHX'), ('DEN','MEM'), ('NOP','IND'), ('BOS','TOR'), ('CHI','ATL'), ('HOU','BKN'), ('MIN','CLE'), ('WAS','NYK'), ('MIA','PHI'), ('GSW','DAL'), ('SAS','DET'), ('MIL','ORL'), ('SAC','PHX'), ('POR','LAL'), ('LAC','UTA'), ('TOR','OKC'), ('MEM','CHA'), ('NOP','HOU'), ('DEN','SAC'), ('LAL','DAL'), ('ORL','ATL'), ('WAS','MIA'), ('MIN','BKN'), ('CHI','MIL'), ('UTA','DET'), ('POR','PHX'), ('CHA','BOS'), ('IND','PHI'), ('SAC','LAC'), ('TOR','CLE'), ('HOU','DEN'), ('SAS','GSW'), ('OKC','DAL'), ('WAS','ORL'), ('NYK','POR'), ('MIL','MIN'), ('LAL','PHX'), ('CLE','CHA'), ('IND','DEN'), ('MIA','TOR'), ('MIN','GSW'), ('CHI','UTA'), ('DAL','BKN'), ('SAS','OKC'), ('SAC','MEM'), ('LAC','DET'), ('BOS','ATL'), ('ORL','MIA'), ('WAS','POR'), ('HOU','PHI'), ('PHX','NYK'), ('LAL','NOP'), ('LAC','MEM'), ('BKN','TOR'), ('ORL','UTA'), ('ATL','IND'), ('CLE','OKC'), ('CHI','BOS'), ('WAS','DEN'), ('DAL','CHA'), ('GSW','DET'), ('MIL','SAS'), ('SAC','NOP'), ('LAC','NYK'), ('MIA','POR'), ('CLE','MIN'), ('PHX','MEM'), ('LAL','DET'), ('ORL','BKN'), ('WAS','BOS'), ('CHA','IND'), ('PHI','OKC'), ('ATL','DEN'), ('NOP','UTA'), ('DAL','CHI'), ('SAS','MIL'), ('SAC','GSW'), ('CLE','DET'), ('TOR','POR'), ('BKN','OKC'), ('MEM','MIN'), ('PHX','SAC'), ('LAL','NYK'), ('IND','MIA'), ('BOS','SAS'), ('CHA','TOR'), ('ORL','WAS'), ('DAL','DEN'), ('ATL','HOU'), ('NOP','CHI'), ('UTA','NYK'), ('GSW','MIL'), ('PHI','BOS'), ('DET','CLE'), ('MIN','POR'), ('OKC','HOU'), ('WAS','ATL'), ('BKN','MIA'), ('CHI','IND'), ('NYK','NOP'), ('MEM','SAS'), ('DAL','LAL'), ('UTA','CHA'), ('PHX','ORL'), ('SAC','MIL'), ('LAC','GSW'), ('HOU','MIN'), ('DET','CHI'), ('BOS','NOP'), ('OKC','LAL'), ('DAL','PHI'), ('DEN','CLE'), ('POR','MIL'), ('NYK','MIA'), ('WAS','UTA'), ('BKN','ATL'), ('MEM','GSW'), ('TOR','SAS'), ('PHX','CHA'), ('DEN','LAC'), ('DET','NOP'), ('IND','PHI'), ('BOS','CHI'), ('MIN','OKC'), ('POR','CHA'), ('SAC','ORL'), ('LAL','CLE'), ('ATL','PHX'), ('BKN','UTA'), ('MEM','HOU'), ('MIL','NYK'), ('LAC','CLE'), ('GSW','DAL'), ('PHI','BKN'), ('BOS','DET'), ('MIA','SAS'), ('MIN','IND'), ('OKC','TOR'), ('CHI','WAS'), ('DEN','CHA'), ('HOU','POR'), ('SAC','DAL'), ('LAL','ORL'), ('NYK','PHX'), ('MIL','BOS'), ('NOP','UTA'), ('GSW','DEN'), ('LAC','ORL'), ('PHI','CHI'), ('IND','MIN'), ('TOR','WAS'), ('MEM','CLE'), ('OKC','MIA'), ('SAS','ATL'), ('DAL','POR'), ('NOP','LAC'), ('BKN','MIL'), ('BOS','PHX'), ('ATL','DET'), ('NYK','PHI'), ('CHI','CLE'), ('MIN','TOR'), ('HOU','LAL'), ('GSW','ORL'), ('OKC','POR'), ('UTA','SAC'), ('SAS','MIA'), ('DEN','IND'), ('WAS','DET'), ('ATL','TOR'), ('BKN','PHI'), ('MIL','CHI'), ('MEM','NOP'), ('CLE','NYK'), ('HOU','OKC'), ('CHA','ORL'), ('MIA','SAC'), ('BOS','LAL'), ('MIN','DEN'), ('UTA','IND'), ('PHX','SAS'), ('POR','GSW'), ('DAL','LAC'), ('ORL','OKC'), ('DET','SAC'), ('ATL','LAC'), ('NYK','LAL'), ('BOS','MEM'), ('TOR','PHI'), ('MIA','WAS'), ('HOU','DEN'), ('CHI','MIN'), ('NOP','SAS'), ('PHX','IND'), ('GSW','UTA'), ('CLE','WAS'), ('BKN','LAL'), ('POR','DAL'), ('CHA','MIL'), ('ORL','BOS'), ('DET','MEM'), ('NYK','TOR'), ('OKC','ATL'), ('NOP','DEN'), ('MIN','HOU'), ('MIA','LAC'), ('CHI','SAC'), ('SAS','PHX'), ('GSW','IND'), ('UTA','DAL'), ('DET','BKN'), ('CLE','CHI'), ('MIN','OKC'), ('PHI','LAL'), ('DEN','HOU'), ('SAS','TOR'), ('NYK','BKN'), ('NOP','BOS'), ('ORL','LAC'), ('MEM','PHX'), ('ATL','WAS'), ('POR','IND'), ('DET','CLE'), ('MIA','ORL'), ('OKC','DAL'), ('MIN','SAC'), ('CHI','SAS'), ('UTA','HOU'), ('CHA','NYK'), ('TOR','ATL'), ('MIA','BOS'), ('PHI','GSW'), ('CLE','NOP'), ('MEM','DEN'), ('MIL','WAS'), ('DAL','PHX'), ('LAL','LAC'), ('IND','CHI'), ('BKN','DET'), ('NYK','MEM'), ('SAS','UTA'), ('POR','HOU'), ('SAC','OKC'), ('WAS','CHA'), ('CLE','TOR'), ('ATL','PHI'), ('BOS','GSW'), ('LAC','SAC'), ('DEN','UTA'), ('DET','TOR'), ('BKN','CHI'), ('HOU','DAL'), ('NOP','MEM'), ('MIL','DEN'), ('PHX','OKC'), ('LAL','POR'), ('LAC','MIN'), ('ORL','MIA'), ('IND','NYK'), ('WAS','BKN'), ('CLE','GSW'), ('DAL','ATL'), ('BOS','PHI'), ('SAS','CHA'), ('SAC','LAL'), ('POR','UTA'), ('DET','DEN'), ('HOU','NOP'), ('MIL','PHX'), ('TOR','CHI'), ('IND','DAL'), ('WAS','GSW'), ('CLE','NYK'), ('CHA','ORL'), ('ATL','BOS'), ('MIA','PHI'), ('BKN','PHX'), ('MEM','DET'), ('SAC','MIN'), ('LAC','SAS'), ('NOP','MIL'), ('HOU','CHA'), ('DEN','POR'), ('LAL','SAS'), ('DET','PHX'), ('BKN','GSW'), ('TOR','IND'), ('BOS','ORL'), ('MIN','ATL'), ('OKC','CLE'), ('DAL','MEM'), ('UTA','DEN'), ('LAC','MIA'), ('NYK','ORL'), ('CHI','NOP'), ('MIL','PHI'), ('POR','SAS'), ('LAL','HOU'), ('WAS','DAL'), ('PHI','MEM'), ('BOS','ATL'), ('OKC','DET'), ('IND','TOR'), ('PHX','HOU'), ('SAC','MIA'), ('UTA','POR'), ('ORL','MIL'), ('CHA','DAL'), ('DET','NYK'), ('TOR','BKN'), ('IND','NOP'), ('MIN','LAC'), ('GSW','LAL'), ('PHX','DEN'), ('SAC','SAS'), ('OKC','BOS'), ('PHI','CHI'), ('WAS','MEM'), ('ATL','NYK'), ('HOU','UTA'), ('CLE','LAC'), ('POR','MIA'), ('ORL','ATL'), ('IND','BKN'), ('DET','CHA'), ('TOR','MIN'), ('MIL','SAC'), ('DAL','UTA'), ('DEN','SAS'), ('GSW','MIA'), ('LAL','PHX'), ('WAS','CHI'), ('PHI','LAC'), ('NOP','POR'), ('OKC','SAS'), ('HOU','BOS'), ('CLE','ATL'), ('ORL','DET'), ('IND','MIL'), ('BKN','TOR'), ('NYK','WAS'), ('MIN','CHA'), ('MEM','POR'), ('DAL','SAC'), ('PHX','GSW'), ('UTA','MIA'), ('DEN','LAL'), ('BOS','LAC'), ('NOP','OKC'), ('DET','MIL'), ('ATL','MIA'), ('PHI','BKN'), ('CHI','CHA'), ('SAC','MEM'), ('GSW','HOU'), ('WAS','CLE'), ('ORL','DAL'), ('NYK','IND'), ('TOR','PHX'), ('MIN','BOS'), ('OKC','DEN'), ('UTA','SAS'), ('LAL','MEM'), ('POR','NOP'), ('LAC','SAC'), ('CHA','BKN'), ('ATL','DAL'), ('MIA','CLE'), ('CHI','PHX'), ('MIL','PHI'), ('UTA','HOU'), ('LAL','BOS'), ('TOR','IND'), ('DEN','MIN'), ('OKC','SAS'), ('CHI','WAS'), ('GSW','NOP'), ('POR','DET'), ('PHI','ATL'), ('CLE','MIA'), ('WAS','MIL'), ('BKN','ORL'), ('HOU','NYK'), ('DAL','MIN'), ('UTA','PHX'), ('LAC','MEM'), ('IND','CHA'), ('TOR','MIL'), ('CHI','OKC'), ('DEN','DET'), ('POR','BOS'), ('LAL','NOP'), ('GSW','SAC'), ('WAS','BKN'), ('CHA','NYK'), ('CLE','PHI'), ('SAS','DAL'), ('MIA','MIN'), ('ATL','ORL'), ('HOU','MEM'), ('PHX','LAC'), ('UTA','BOS'), ('PHI','NYK'), ('IND','POR'), ('OKC','SAC'), ('GSW','LAL'), ('ORL','MIN'), ('ATL','BKN'), ('TOR','CHA'), ('NOP','CLE'), ('MIA','DAL'), ('MIL','OKC'), ('MEM','SAC'), ('PHX','DET'), ('UTA','WAS'), ('LAC','DEN'), ('NYK','CHI'), ('MIA','BKN'), ('ATL','POR'), ('CLE','IND'), ('MEM','LAL'), ('BOS','HOU'), ('SAS','ORL'), ('PHX','GSW'), ('CHA','MIL'), ('MIN','DAL'), ('LAC','PHI'), ('SAC','DET'), ('DEN','TOR'), ('NOP','LAL'), ('GSW','WAS'), ('NYK','HOU'), ('ORL','POR'), ('CLE','UTA'), ('ATL','MEM'), ('MIA','MIL'), ('CHI','DAL'), ('SAS','IND'), ('CHA','SAS'), ('BOS','BKN'), ('OKC','LAC'), ('NOP','MIN'), ('DEN','GSW'), ('PHX','TOR'), ('LAL','PHI'), ('SAC','WAS'), ('CLE','BOS'), ('DET','OKC'), ('MIA','ORL'), ('BKN','MEM'), ('NYK','UTA'), ('MIN','CHI'), ('DAL','NOP'), ('MIL','IND'), ('POR','WAS'), ('CHA','DEN'), ('HOU','LAC'), ('SAC','PHI'), ('GSW','TOR'), ('WAS','ATL'), ('NYK','OKC'), ('BKN','SAS'), ('CHI','IND'), ('NOP','MIA'), ('MIN','ORL'), ('BOS','UTA'), ('DAL','MEM'), ('PHX','POR'), ('LAL','MIL'), ('CHA','HOU'), ('DET','UTA'), ('CLE','DEN'), ('MEM','ATL'), ('GSW','PHI'), ('POR','SAC'), ('BKN','CHI'), ('LAC','LAL'), ('MIN','NOP'), ('PHX','MIL'), ('BOS','OKC'), ('DAL','IND'), ('WAS','MIA'), ('HOU','ORL'), ('NYK','DET'), ('CLE','SAS'), ('SAC','TOR'), ('ATL','CHA'), ('DEN','MIL'), ('UTA','TOR'), ('IND','BOS'), ('WAS','NYK'), ('CHI','CLE'), ('SAS','DAL'), ('HOU','MIN'), ('MEM','ORL'), ('POR','PHX'), ('LAL','BKN'), ('GSW','LAC'), ('MIA','CHA'), ('PHI','DET'), ('ATL','NYK'), ('OKC','UTA'), ('DAL','DEN'), ('SAC','NOP'), ('ORL','CHI'), ('MIL','BOS'), ('POR','MEM'), ('GSW','BKN'), ('LAL','HOU'), ('CHA','CLE'), ('BOS','WAS'), ('OKC','MIN'), ('MIA','NYK'), ('SAS','DEN'), ('UTA','NOP'), ('LAC','BKN'), ('DAL','PHX'), ('ATL','CLE'), ('TOR','DET'), ('PHI','IND'), ('MIA','CHI'), ('MIL','GSW'), ('SAS','MIN'), ('UTA','MEM'), ('LAC','NOP'), ('POR','HOU'), ('ORL','CHA'), ('WAS','OKC'), ('CHI','BOS'), ('SAC','BKN'), ('LAL','DEN'), ('PHI','WAS'), ('NOP','ATL'), ('TOR','GSW'), ('MIL','MIA'), ('SAS','MEM'), ('UTA','LAL'), ('LAC','DAL'), ('DET','ORL'), ('NYK','CHA'), ('HOU','CLE'), ('MEM','OKC'), ('SAC','DAL'), ('POR','MIN'), ('PHI','TOR'), ('IND','GSW'), ('BOS','NYK'), ('ATL','OKC'), ('BKN','WAS'), ('CHI','MIA'), ('NOP','SAS'), ('DEN','LAC'), ('PHX','MIN'), ('LAL','UTA'), ('ORL','CLE'), ('CHA','PHI'), ('MIL','MEM'), ('HOU','SAC'), ('POR','DAL'), ('DET','GSW'), ('IND','MIA'), ('TOR','BOS'), ('ATL','WAS'), ('OKC','DEN'), ('SAS','CHI'), ('UTA','MIN'), ('LAC','PHX'), ('BKN','BOS'), ('PHI','ATL'), ('IND','CLE'), ('CHA','LAL'), ('ORL','SAC'), ('NYK','GSW'), ('HOU','CHI'), ('WAS','MIL'), ('MEM','NOP'), ('PHX','DAL'), ('TOR','DEN'), ('DET','LAL'), ('MIN','POR'), ('NOP','SAC'), ('SAS','UTA'), ('WAS','BOS'), ('NYK','LAC'), ('IND','PHX'), ('MIA','OKC'), ('BKN','ORL'), ('CHI','DEN'), ('MIL','DET'), ('DAL','HOU'), ('CHA','POR'), ('CLE','SAC'), ('TOR','LAL'), ('NOP','MEM'), ('MIN','PHI'), ('UTA','SAS'), ('ORL','IND'), ('BKN','LAC'), ('WAS','PHX'), ('DET','POR'), ('MIA','DEN'), ('NYK','TOR'), ('MEM','BOS'), ('MIL','HOU'), ('GSW','ATL'), ('CLE','LAL'), ('OKC','CHA'), ('CHI','PHI'), ('MIN','SAS'), ('DAL','UTA'), ('SAC','ATL'), ('ORL','BKN'), ('IND','HOU'), ('DET','LAC'), ('PHI','PHX'), ('BOS','POR'), ('NOP','NYK'), ('MIL','WAS'), ('DEN','SAS'), ('BKN','CLE'), ('CHA','LAC'), ('WAS','LAL'), ('CHI','NYK'), ('MIA','PHX'), ('MEM','TOR'), ('GSW','OKC'), ('UTA','ATL'), ('DAL','MIL'), ('DET','HOU'), ('BOS','MIN'), ('ORL','NOP'), ('PHI','POR'), ('GSW','SAS'), ('SAC','IND'), ('CHA','MIA'), ('CLE','PHX'), ('TOR','MEM'), ('BKN','POR'), ('MIN','LAL'), ('UTA','CHI'), ('DEN','OKC'), ('LAC','IND'), ('PHI','HOU'), ('ATL','NOP'), ('GSW','DEN'), ('SAC','SAS'), ('ORL','CHA'), ('WAS','NOP'), ('BKN','DET'), ('BOS','MIA'), ('MIN','DAL'), ('MEM','NYK'), ('OKC','PHX'), ('UTA','CLE'), ('MIL','TOR'), ('LAL','IND'), ('HOU','GSW'), ('SAC','LAC'), ('POR','UTA'), ('IND','BKN'), ('DET','MIA'), ('WAS','PHI'), ('ATL','CHA'), ('TOR','MIL'), ('NYK','MIN'), ('BOS','ORL'), ('MEM','DAL'), ('SAS','GSW'), ('DEN','CHI'), ('PHX','CLE'), ('LAC','OKC'), ('SAC','LAL'), ('CHA','ATL'), ('PHI','ORL'), ('BOS','MIL'), ('NYK','MIA'), ('IND','WAS'), ('PHX','CHI'), ('BKN','DAL'), ('DEN','UTA'), ('MIN','DET'), ('SAS','NOP'), ('HOU','TOR'), ('SAC','CLE'), ('POR','MEM'), ('LAL','OKC'), ('WAS','TOR'), ('ATL','DET'), ('MIA','IND'), ('PHI','MIL'), ('OKC','BKN'), ('NOP','CHA'), ('MIN','PHX'), ('DAL','HOU'), ('SAS','SAC'), ('DEN','MEM'), ('POR','CLE'), ('UTA','LAC'), ('LAL','CHI'), ('BOS','IND'), ('CHA','TOR'), ('NYK','ORL'), ('HOU','SAS'), ('LAC','CHI'), ('GSW','CLE'), ('MIA','DET'), ('MIL','BKN'), ('NOP','PHX'), ('MIN','SAC'), ('POR','DEN'), ('LAL','GSW'), ('ORL','BOS'), ('TOR','ATL'), ('OKC','NYK'), ('HOU','WAS'), ('SAS','PHI'), ('DAL','DET'), ('IND','ORL'), ('NOP','PHI'), ('CHA','WAS'), ('CLE','MIL'), ('CHI','BKN'), ('MIA','BOS'), ('MEM','OKC'), ('LAC','GSW'), ('DEN','SAC'), ('PHX','UTA'), ('SAS','HOU'), ('TOR','NYK'), ('MIL','ATL'), ('LAL','MIN'), ('CLE','BKN'), ('DET','BOS'), ('CHA','MIA'), ('IND','SAS'), ('CHI','ORL'), ('MEM','PHI'), ('HOU','PHX'), ('OKC','UTA'), ('NOP','WAS'), ('DEN','DAL'), ('LAC','MIN'), ('GSW','POR'), ('NYK','DET'), ('MIA','TOR'), ('UTA','DEN'), ('LAL','SAC'), ('PHI','CHA'), ('BOS','CHI'), ('ATL','CLE'), ('WAS','IND'), ('HOU','MEM'), ('BKN','MIL'), ('SAS','NOP'), ('MIN','NYK'), ('DAL','OKC'), ('ORL','TOR'), ('SAC','GSW'), ('POR','LAC'), ('PHX','LAL')] +# wantedEncounters = set(wantedGames) +# unwantedEncounters = [ (t1,t2) for t1 in shortnames for t2 in shortnames if not (t1,t2) in wantedEncounters ] +# # print ("unwanted ", unwantedEncounters) + +# for (t1, t2) in wantedGames: +# gameCntr[(t1,t2)]+=1 + +# # for t1 in shortnames: +# # for t2 in shortnames: +# # print (gameCntr) + +# playOnce = [ e for e in wantedEncounters if gameCntr[e] ==1] +# playTwice = [ e for e in wantedEncounters if gameCntr[e] ==2] + +# t_cc = { t : [ c for c in confTeams.keys() if t in confTeams[c]] for t in teams } +# print (t_cc) + +# interconferencegames = { (c1,c2) : 0 for c1 in confTeams.keys() for c2 in confTeams.keys() } + +# for c in confTeams.keys(): +# print (confTeams[c]) + +# for (tn1, tn2) in playTwice: +# print (teamByShort[tn1] , teamByShort[tn2]) +# t1 = getTeamIdByName[teamByShort[tn1]] +# t2 = getTeamIdByName[teamByShort[tn2]] +# for c1 in t_cc[t1]: +# for c2 in t_cc[t2]: +# interconferencegames[(c1,c2)]+=2 + + +# for (c1,c2) in interconferencegames.keys(): +# print (confName[c1] , " - ", confName[c2], " : ", interconferencegames[(c1,c2)]) + +# # getTeamIdByName +# # print (playOnce) +# # print (playTwice) + + +# for t in teams: +# for c in clusters: +# # tripToClusterSaving[(t,c)]= cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) +# print (cluster_distances[c,c], "-2*",distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) , cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) ) +# print (" " , tripToClusterSaving[(t,c)] , tripToClusterSaving[t,c] ) + +# for t in teams: +# for c in clusters: +# print (getTeamById[t], " to ", c, " : " , cluster_distances[c,c] , "-2* " ,distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]) , cluster_distances[c,c] -2.0*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c]), cluster_distances[c,c] -2*distanceInKmByGPS(t_lon[t],t_lat[t], c_lon[c], c_lat[c])) +# for t2 in cluster_teams[c]: +# print ("--",getTeamById[t2]) +# print ("---> " ,tripToClusterSaving[(t,c)]) + + + +# print (games) +# print (attractivity) +# for (t1,t2) in games: +# print (t1,t2) +# print (attractivity[t1,t2] ) +gameAttractivity = { (t1,t2) : attractivity[t1,t2] for (t1,t2) in games if t10 ]) for t in teams } + + +# scale blocking of basic round to [0,1] +for t in teams: + for r in basicRounds: + if len(getBasicDays[r])>0: + nonBlocked[(t,r)]/=len(getBasicDays[r]) + travelDays[(t,r)]/=len(getBasicDays[r]) + if getTeamById[t]=="AX Armani Exchange Milan": + print (r , getRealRounds[r] , nonBlocked[(t,r)] , nonBlocked[(t,r)] ) + +for t in teams: + if mathModelName=="UEFA NL" and noPlayRounds[t] in [ [3,4] ]: + t_usePhases[t]= False + print ("No need for phases " , getTeamById[t]) + for t2 in opponents[t]: + t_usePhases[t2]= False + print (" -- also no need for phases " , getTeamById[t2]) + if getTeamById[t]=="AX Armani Exchange Milan": + t_usePhases[t]= False + print ("setting t_usePhases of ", getTeamById[t],t_usePhases[t] ) + +if "noPhaseForTeams" in special_wishes_active and sw_text["noPhaseForTeams"].strip()!="": + noPhaseTeams = [st.strip() for st in sw_text["noPhaseForTeams"].split(",")] + for t in teams: + if getTeamById[t] in noPhaseTeams: + t_usePhases[t]= False + +runHeuristicModelFirst=False + +if runMode=='New' and optSteps[0][0]=='HEURISTIC' and not thisSeason.groupBased and len(fixedGames)+len(fixedGames2)==0 : + runHeuristicModelFirst=True + +chosenGames = [ ] + +useFullModel1= False + +usePatterns= not mathModelName in ["NHL", "LNR"] +usePatterns= not mathModelName in ["NHL"] + + +balanceBreaks = mathModelName=="LNR" +haSymmetric = not mathModelName in ["NHL" , "LNR"] +haSymmetric = not mathModelName in ["NHL", "Ligue 1", "Costa Rica Premier League"] +haSymmetric = not mathModelName in ["NHL"] +# haSymmetric = not mathModelName in ["NHL" , "LNR"] +if thisSeason.symmetry: + haSymmetric=True + +use2BreakPatterns = False + +# if thisSeason.initBreaks>=2 and objectivePrio != 'Trips' : +if thisSeason.initBreaks>=2 : + use2BreakPatterns = True +# use2BreakPatterns = False + +# if objectivePrio == 'Trips' and not regionalPatternUse: +# use2BreakPatterns = False + + + +# basicRounds= range(1,40) +# half_symmetry_offset = 1 if mathModelName in [ "TOP 14"] else 0 +half_symmetry_offset = 0 if thisSeason.symmetry or use2BreakPatterns or not thisSeason.startWithBreakAllowed or not thisSeason.endWithBreakAllowed else 1 + +# if mathModelName in [ "Ligue 1"]: +# half_symmetry_offset = 4 + +# print (half_symmetry_offset , thisSeason.symmetry , use2BreakPatterns) + + +prev_mirror_round= { r : 0 if r<=nRounds1 else r-nRounds1+half_symmetry_offset for r in basicRounds } + + +# for r in basicRounds: +# print (r, " --> " , prev_mirror_round[r] ) +# print ("") + + +for r in basicRounds: + if r>nRounds1 and int((r-1)/nRounds1) == int((prev_mirror_round[r]-1)/nRounds1): + # prev_mirror_round[r]-=nRounds1 + prev_mirror_round[r] = int((r-1)/nRounds1-1)*nRounds1 +half_symmetry_offset+1-prev_mirror_round[r]%nRounds1 + +if mathModelName in [ "LNR"] and False: + prev_mirror_round[14]=4 + prev_mirror_round[15]=3 + prev_mirror_round[16]=2 + prev_mirror_round[17]=1 + prev_mirror_round[18]=13 + prev_mirror_round[19]=12 + prev_mirror_round[20]=11 + prev_mirror_round[21]=10 + + prev_mirror_round[22]=9 + prev_mirror_round[23]=8 + prev_mirror_round[24]=7 + prev_mirror_round[25]=6 + prev_mirror_round[26]=5 + + +# for r in basicRounds: +# print (r, " --> " , prev_mirror_round[r] ) + + +getPhaseOfBasicRound={ br : getPhaseOfRound[getRealRounds[br][0]] for br in basicRounds if len(getRealRounds[br])>0 } +for br in basicRounds: + if br not in getPhaseOfBasicRound.keys(): + getPhaseOfBasicRound[br]= getPhaseOfBasicRound[br-1] + +getBasicRoundsOfPhase={ ph : [ br for br in basicRounds if getPhaseOfBasicRound[br]==ph ] for ph in phases } + +# print (getPhaseOfBasicRound) +# print (getBasicRoundsOfPhase) + + +if use2BreakPatterns or nTeams >200 or not usePatterns or thisSeason.minRoundsBetweenGameOfSameTeams>0 : + useFullModel1=True +useFullModel1=True + + +preplan_phases = phases +preplan_phases= [0] +if not haSymmetric: + preplan_phases = phases + +fixedGamesRounds=[] + +starweight={t : 0.0 for t in teams} +for t1 in teams : + for t2 in teams : + starweight[t1]+=distance[getTeamById[t1],getTeamById[t2]] + +# if thisSeason.minBreaks or len(fixedGames)==0: + +available_days={ (t,d) : min(1,getDayMaxGames[d]) for t in teams for d in days } + +for bl in blockings: + if bl['type'] in ["Home","Hide","Game"] and bl['time']=='----' and bl['tag_id'] == None : + available_days[ bl['team_id'],bl['day_id']] =0 + +t_blocked_at={ (t,r) : sum([available_days[t,d] for d in getDays[r]])==0 for t in teams for r in rounds} + +if runHeuristicModelFirst: + + debug= True + debug= False + comptime = 500 + gap = 0.05 + if debug: + comptime = 30 + gap = 0.5 + + possGames = optimize_model1(comptime,gap) + print (possGames) + print (len(possGames)) + chosenGames, unChosenGames = optimize_model4(possGames) + + print (len(chosenGames) , " chosen games" , chosenGames ) + print (len(unChosenGames) , "unchosen games : ", unChosenGames) + + # exit(0) + # print (len(chosenGames)) + + fineDistributionModel4 = False + fineDistributionModel4 = nBasicTeams=10 ] + +# for t in teams: +# print (t, getTeamById[t], [ r for (r,d) in usingRDs[t]], t_conference[t]) + +# print (TVteams) +# print ("CHECKPOINT ", 5) + +# print (seedTV) +# print (dontPlay) + +x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +for (t1,t2) in games: + for rd in roundDays: + x[(t1,t2,rd)]=1 + +for (t1,t2,rd) in x.keys(): + if blocked_arena[(t1,rd[1],"----")] and runMode=='Improve' and not thisSeason.allowBlockingViosInImprove: + # cntr +=1 + # print ("FORBIDDING") + + x[(t1,t2,rd)]=0 + +for (t1,t2,d,channel) in seedTV: + if gameCntr[(t1,t2)]==1: + for (r,d2) in roundDays: + if d != d2 : + # cntr +=1 + x[(t1,t2,(r,d2))]=0 + + for t3 in teams: + if not t3 in [t1,t2]: + for rd in getRoundDaysByDay[d]: + x[(t1,t3,rd)]=0 + x[(t2,t3,rd)]=0 + x[(t3,t1,rd)]=0 + x[(t3,t2,rd)]=0 + # cntr +=4 + +for (t,d) in dontPlay: + for t3 in teams: + if not t3 in [t1,t2]: + for rd in getRoundDaysByDay[d]: + x[(t,t3,rd)]=0 + x[(t3,t,rd)]=0 + +for (t1,t2,d) in dontPlayGames: + for rd in getRoundDaysByDay[d]: + x[(t1,t2,rd)]=0 + +attendance = { (t1,t2,d) : 0 for (t1,t2) in games for d in days } +if thisSeason.useFeaturePrediction: + # learner = AttendanceLearner(thisSeason.id) + # attendance = learner.predict_games(attendance.keys()) + oldestTrainingGame= parse("2015-01-01") + oldestTrainingGame=oldestTrainingGame.date() + games_train = Game.objects.filter(season=thisSeason).exclude(historic_season=None) + print ( " all games",len(games_train), ) + games_train = [g for g in games_train if g.date>oldestTrainingGame ] + print ( " important games",len(games_train), ) + + games_predict = attendance.keys() + + X= [] + y =[] + homeAttendances = {t: [] for t in teams+inactive_teams} + for game in games_train: + if game.attendance>0: + homeAttendances[game.homeTeam.id].append(game.attendance) + for t in teams+inactive_teams: + homeAttendances[t]=sorted(homeAttendances[t]) + # print (getTeamById[t], homeAttendances[t]) + if len(homeAttendances[t])==0: + homeAttendances[t]=[7500] + maxAttendance = {t: homeAttendances[t][-1] for t in teams+inactive_teams } + # maxAttendance = {t: homeAttendances[t][int(0.9*(len(homeAttendances[t])))] for t in teams+inactive_teams } + medianAttendance = {t: homeAttendances[t][int(0.5*(len(homeAttendances[t])))] for t in teams+inactive_teams } + loyalty = {t: medianAttendance[t]/maxAttendance[t] for t in teams+inactive_teams } + + for t in teams+inactive_teams: + print (getTeamById[t], loyalty[t] , medianAttendance[t] , maxAttendance[t] , ) + + def getFeatureVector(t1,t2,d): + ds = distanceInKmByGPS(t_lon[t1],t_lat[t1], t_lon[t2], t_lat[t2]) + # print ( d.day , d.month , d.year , " -> " , (d.month+5)%12 , " -> " , int((d.month+4)%12/2) , d.month==12 and d.day >15 , d.weekday() , d.weekday() in [4,5,6]) + # print ( d.year," -> " , d.year-2015 ) + gm={ + # 'homeTeam_id': t1, 'awayTeam_id': t2, + # 'month': d.month, + 'year': d.year-2015 , + 'month': int((d.month+4)%12/2) , + 'weekday': d.weekday() , + 'weekend': d.weekday() in [4,5,6], + 'holiday': d.month==12 and d.day >15 , + 'summer': d.month in [6,7,8,9], + 'winter': d.month in [12,1,2], + 'home_attractivity': t_attractivity[t1], 'away_attractivity': t_attractivity[t2], + 'attractivity_ratio': t_attractivity[t2]/max(1,t_attractivity[t1]), + 'home_lat': t_lat[t1] , 'home_lon': t_lon[t1] , + 'away_lat': t_lat[t2], 'away_lon': t_lon[t2], + 'distance' : ds , + 'distance_frac' : ds/medianAttendance[t2] , + 'loyalty_home' : loyalty[t1] , + 'medianAttendance_home' : 0.001*medianAttendance[t1] , + 'maxAttendance_home' : 0.001*maxAttendance[t1] , + # 'medianAttendance_away' : 0.001*medianAttendance[t2] , + # 'maxAttendance_away' : 0.001*maxAttendance[t2] , + } + for t in teams+inactive_teams: + if t1 == t: + gm["home_"+str(t1)]=True + if t2 == t: + gm["away_"+str(t2)]=False + for c in countries: + if t_country[t1] == c: + gm["home_country_"+str(t1)]=True + if t_country[t2] == c: + gm["away_country_"+str(t2)]=False + return list(gm.values()) + + for game in games_train: + X.append(getFeatureVector(game.homeTeam.id, game.awayTeam.id,game.date) ) + y.append(game.attendance) + + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=1) + + rf_regressor = RandomForestRegressor(n_estimators = 200 , random_state = 42) + # score on training 0.959517674767985 + # score on test 0.8494965525219991 + # rf_regressor = GradientBoostingRegressor(n_estimators = 200 , random_state = 42) + # score on training 0.9022637786547619 + # score on test 0.8047414718789097 + rf_regressor.fit(X_train,y_train) + score = rf_regressor.score(X_train,y_train) + print ("score on training " , score) + score = rf_regressor.score(X_test,y_test) + print ("score on test " , score) + + X= [] + y =[] + for (t1,t2,d) in attendance.keys(): + X.append(getFeatureVector(t1,t2,getDateTimeDay[d]) ) + + attendance = dict(zip(games_predict,rf_regressor.predict(X))) + + for g in games_predict: + # print (g, int (attendance[g]) , maxAttendance[g[0]], int (attendance[g]) < maxAttendance[g[0]], min ( int (attendance[g]) , maxAttendance[g[0]])) + attendance[g] = min ( int (attendance[g]) , maxAttendance[g[0]]) + +# print ( sum (x[ttrd] for ttrd in x.keys()) , len(games)*len(roundDays)) + +# x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} +x_round= {(t1,t2,r) : 0 for t1 in teams for t2 in teams for r in rounds} +x_time= {(t1,t2,rd,tm) : 0 for t1 in teams for t2 in teams for rd in roundDays for tm in times} +if evalRun: + x= {(t1,t2,rd) : 0 for t1 in teams for t2 in teams for rd in roundDays} + for (t1,t2,r,d) in currentSolution: + x[(t1,t2,(r,d))]=1 + if currentKickoffTimes[(t1,t2,d)] in times: + x_time[(t1, t2, (r, d), currentKickoffTimes[(t1,t2,d)])]=1 + else: + for tm in times: + x_time[(t1,t2,(r,d),tm)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(r)+'_'+str(d)+'_'+tm , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + # print ("setting " , t1,t2,r,d,currentKickoffTimes[(t1,t2,d)]) + +for (t1,t2) in games: + for rd in roundDays: + # print ("make var " ,t1,t2,rd) + if x[(t1,t2,rd)]==1 : + # if not blocked_arena[(t1,rd[1],"----")] or runMode=='New': + if not evalRun: + x[(t1,t2,rd)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1]), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + cntr +=1 + if thisSeason.useFeatureKickOffTime: + for tm in times: + x_time[(t1,t2,rd,tm)] = pulp.LpVariable('x_'+str(t1)+'_'+str(t2)+'_'+str(rd[0])+'_'+str(rd[1])+'_'+tm , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + else: + cntr+=len(roundDays) + + for r in rounds: + x_round[(t1,t2,r)]= pulp.LpVariable('x_round_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + model2+= x_round[(t1,t2,r)] == lpSum([x[(t1,t2,rd)] for rd in getRoundDaysByRound[r]]) + + +t_prev_mirror_round ={(t,r) : 0 for t in teams for r in rounds} + +for t1 in teams: + phaseLength= int(len(playRounds[t1])/nPhases+0.5) + # print ("phaseLength", phaseLength) + for i in range(len(playRounds[t1])): + # print (i, phaseLength) + if i >=phaseLength: + # print ("setting " , playRounds[t1], playRounds[t1][i-phaseLength]) + # print ("setting " , playRounds[t1][i], "->",playRounds[t1][i-phaseLength]) + t_prev_mirror_round[(t1,playRounds[t1][i])]=playRounds[t1][i-phaseLength] + # t_prev_mirror_round[(t,playRounds[t1][i])]=5 + + +# for tr in t_prev_mirror_round.keys(): +# print( "t_prev_mirror_round " ,tr , t_prev_mirror_round[tr]) + +# print (prev_mirror_round) + + +if not evalRun: + for (t1,t2) in games: + for r in rounds: + if r > nRounds1 and thisSeason.symmetry : + prev_round =prev_mirror_round[r] + if thisSeason.groupBased and len(noPlayRounds[t1])>0 and noPlayRounds[t1]==noPlayRounds[t2]: + prev_round=t_prev_mirror_round[t1,r] + + if prev_round>0: + model2+= x_round[(t1,t2,r)] == x_round[(t2,t1,prev_round)] + # print ("x_round[(",t1,",",t2,",",r,")] == x_round[(",t2,t1,prev_mirror_round[r],")]") + # print ( t1,t2,r , " -> ",prev_mirror_round[r]) + + +# for (t1,t2) in games: +# # every pair plays each other in each phase once +# for p in phases: +# if p0 and noPlayRounds[t1]==noPlayRounds[t2]: +# relDays = [] +# phaseLength= int(len(playRounds[t1])/nPhases+0.5) +# for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: +# relDays+=getDays[rr] +# print ("adding days of round ", rr , " to phase ", p) +# print ("reldays", relDays) +# else: +# relDays = getDaysOfPhase[p] +# model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ) <= 1 + + + +# for r in rounds: + # if r > nRounds1 and (thisSeason.symmetry or haSymmetric): + # print ( r , " -> ",prev_mirror_round[r]) +# print (len(games)) + + + + +for (t1,t2,(r,d)) in x.keys(): + if thisSeason.useFeatureKickOffTime: + for tm in times : + if blocked_arena[(t1,d,tm)] and runMode=='Improve': + x_time[(t1,t2,(r,d), tm)]=0 + # print ("forbidding tm ", t1,t2,r,d, tm) + cntr2+=1 + # print ("FORBIDDING") + + +print (" .... got rid of " , len(x.keys())-cntr, " + " ,cntr2, " vars") + + + + +for (t1,t2,d,channel) in seedTV: + print (t1,t2,d , gameCntr[(t1,t2)], gameCntr[(t1,t2)]==1) + model2+= lpSum([x[(t1,t2,rd)] for rd in getRoundDaysByDay[d] ])==1 + +# model2+= lpSum( x[rr] for rr in x.keys() ) +# model2.solve(XPRESS(msg=1,maxSeconds = 25, keepFiles=True)) +# return "" + +if thisSeason.lastRoundSync: + if thisSeason.useFeatureKickOffTime: + lastRoundPlayed= {(g,rd,tm1) : pulp.LpVariable('x_'+str(g)+'_'+str(rd[1])+'_'+tm1 , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for g in displayGroups.keys() for rd in finalRoundDays for tm1 in times} + else: + lastRoundPlayed= {(g,rd) : pulp.LpVariable('x_'+str(g)+'_'+str(rd[1]) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for g in displayGroups.keys() for rd in finalRoundDays } +homeInRound= {(t1,r) : pulp.LpVariable('homeInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +awayInRound= {(t1,r) : pulp.LpVariable('awayInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +gameInBasicRound= {(t1,t2,r) : pulp.LpVariable('gameInBasicRound_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = defaultGameRepetions, cat = pulp.LpContinuous) for (t1,t2) in games for r in basicRounds} +homeInBasicRound= {(t1,r) : pulp.LpVariable('homeInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds} +awayInBasicRound= {(t1,r) : pulp.LpVariable('awayInBasicRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in basicRounds} +break3InRound= {(t1,r) : pulp.LpVariable('break3InRound_'+str(t1)+'_'+str(r), lowBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +# breakInRound= {(t1,r) : pulp.LpVariable('breakInRound_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds} +# breaksTotal = pulp.LpVariable('breaksTotal', lowBound = 0, cat = pulp.LpContinuous) + +tripToSingleTripElement = {(t1,d,c) : pulp.LpVariable('tripToSingleTripElement_'+str(t1)+'_'+str(d)+'_'+str(c), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) for t1 in teams for d in days for (c,v1,v2,v3,v4) in tripElements[t1] if getWeekDay[d] in trip_weekdays[c]} +tripToCluster = {(t1,r,c) : pulp.LpVariable('tripToCluster_'+str(t1)+'_'+str(r)+'_'+str(c), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) for t1 in teams for r in rounds for c in clusters if not c in t_clusters[t1] and r= 1 and i==j : + patterns[2*j-1,i]=1-patterns[2*j-1,i] + patterns[2*j,i]= 1-patterns[2*j-1,i] + + forbiddenPatterns=[] + if use2BreakPatterns : + p_cntr=numPatterns + for b1 in rounds1: + for b2 in rounds1: + if b1+2<=b2 and b1>1 and b2-b11: + np = 1-patterns[p_cntr-1,r-1] + if r in [b1,b2]: + np=1-np + patterns[p_cntr-1,r]=np + patterns[p_cntr,r]=1-np + patternRange.append(p_cntr-1) + patternRange.append(p_cntr) + + for p in [p_cntr-1 ,p_cntr]: + p_nhome_games = sum ([patterns[p,d] for d in rounds1 ]) + print ( " - ", 100+p , " :" ,end="") + for d in rounds1: + print (patterns[p,d] , end="" ) + if p_nhome_games < nTeams/2-1 or p_nhome_games > nTeams/2 : + print (" ** BAD ** ", end="") + forbiddenPatterns.append(p) + print (" sum : " , sum ([patterns[p,d] for d in rounds1 ])) + + for j in range(1,numPatterns+1): + print (j , " : " ,end="") + for i in range (2,int(numPatterns/2)+1): + print (patterns[j,i],end="") + print("") + + forbiddenStarterPatterns = [ p for p in patternRange if not thisSeason.startWithBreakAllowed and patterns[p,1]==patterns[p,2]] + forbiddenEndPatterns = [ p for p in patternRange if not thisSeason.endWithBreakAllowed and patterns[p,nRounds1-1]==patterns[p,nRounds1]] + forbiddenPatterns+=forbiddenStarterPatterns+forbiddenEndPatterns + + if mathModelName in ["DFB3"] and len(teams)>18: + forbiddenPatterns += [3,4,7,8,11,12,15,16,19,20,23,24,27,28] + patternRange=[p for p in patternRange if p not in forbiddenPatterns] + print (forbiddenPatterns) + print (patternRange) + print ("getPhaseOfRound",getPhaseOfRound) + print ("getBasicRound",getBasicRound) + + assignPattern2={(p,t1,ph) : pulp.LpVariable('assignPattern2_'+str(t1)+'_'+str(p)+'_'+str(ph), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for p in patternRange for t1 in realteams for ph in phases} + usePattern2={(p,ph) : pulp.LpVariable('usePat2_'+str(p) + '_' + str(ph), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for p in patternRange for ph in phases} + for ph in phases: + if runMode == 'New': + for p in patternRange: + model2+= usePattern2[(p,ph)]== lpSum([ assignPattern2[p,t,ph] for t in realteams]) + if p%2==0: + model2+= usePattern2[(p,ph)]==usePattern2[p-1,ph] + # print ("complementaries " , p, p-1) + for t in realteams: + # every team one pattern + model2 += lpSum( assignPattern2[(p,t,ph)] for p in patternRange)==1 , "one_pattern_for_2_%s_%s"%(t,ph) + + # for r in rounds: + # if r==1 or getBasicRound[r-1] != getBasicRound[r]: + # model2 += lpSum( homeInRound[(t,r2)] for r2 in rounds if getBasicRound[r2]==getBasicRound[r]) == lpSum(assignPattern2[(p,t,getPhaseOfRound[r])] * patterns[p,r-getPhaseOfRound[r]*nRounds1] for p in patternRange) + + for r in basicRounds: + model2 += lpSum( homeInRound[(t,r2)] for r2 in getRealRounds[r]) == lpSum(assignPattern2[(p,t,getPhaseOfBasicRound[r])] * patterns[p,r-getPhaseOfBasicRound[r]*nRounds1] for p in patternRange) + +# for cs in currentSolution: + # setLB(x[(cs[0],cs[1],(cs[2],cs[3]))],1) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=20,msg=1)) + +for t in higherTeams : + for d in days+higherLeagueDayIds: + away[t,d] = 0 + home[t,d] = 0 + if thisSeason.useFeatureKickOffTime: + for tm in times: + away_time[t,d,tm] = 0 + home_time[t,d,tm] = 0 + +print ("READING UPPER GAMES", higherLeagueDayIds) + +for (dy,t1,t2,rd,tm) in higherGames: + dy2 = getDayByDateTime[getDateTimeDay[dy]] + home[t1,dy2] = 1 + away[t2,dy2] = 1 + if tm in getIdByTime.keys(): + home_time[t1,dy2,getIdByTime[tm]] = 1 + away_time[t2,dy2,getIdByTime[tm]] = 1 + +for t in realteams: + for d in higherLeagueDayIds: + away[t,d] = 0 + home[t,d] = 0 + if thisSeason.useFeatureKickOffTime: + for tm in times: + away_time[t,d,tm] = 0 + home_time[t,d,tm] = 0 + + for d in days: + away[t,d] = lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('away'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) + home[t,d] = lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByDay[d]]) or pulp.LpVariable('home'+str(t)+'_'+str(d), lowBound = 0, upBound = 0, cat = pulp.LpContinuous) + if thisSeason.useFeatureKickOffTime: + for t2 in opponents[t]: + # print (t,t2,getTeamById[t], getTeamById[t2], (t,t2) in games ) + # if (t,t2) in games: + for rd in getRoundDaysByDay[d]: + # TODO: HOTFIX + if (x[(t2,t,rd)] != 0): + model2+= x[(t2,t,rd)] == lpSum([ x_time[(t2,t,rd,tm)] for tm in times]) + for tm in times: + away_time[t,d,tm] = lpSum([x_time[(t2,t,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ]) + home_time[t,d,tm] = lpSum([x_time[(t,t2,rd,tm)] for t2 in opponents[t] for rd in getRoundDaysByDay[d] ]) + + for c in clusters: + away_in_cluster_day[t,d,c] = lpSum([x[(t2,t,rd)] for t2 in cluster_teams[c] for rd in getRoundDaysByDay[d] if t2 in opponents[t]]) + +for t in realteams: + tms = [t] + higherTeamsOf[t] + print (tms) + for d in days+higherLeagueDayIds : + model2 += home[(t,d)]+away[(t,d)] <= 1 + if len(higherTeamsOf[t])>0 and d in upperAndLowerLeagueIds: + model2 += lpSum([ home[(t1,d)]+away[(t1,d)] for t1 in tms ]) <= 1 + gamesTooClose[t,d] + + if conflictDays[(t,d)]: + # print (t,getNiceDay[d],conflictDays[(t,d)]) + # for d2 in conflictDays[(t,d)]: + # if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')] ): + # print (" - " ,getNiceDay[d2] ,getDateTimeDay[d2]-getDateTimeDay[d] , datetime.timedelta(days=minRest[(t,'A','A')]) ) + # model2 += lpSum([home[(t,d2)]+away[(t,d2)] for d2 in conflictDays[(t,d)]]) <= 1+ gamesTooClose[t,d] + # if len(tms)>1: + # print (lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','H')] )] )) + model2 += lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','H')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ home[(t1,d)] for t1 in tms ]) +lpSum([ away[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','A')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ away[(t1,d)] for t1 in tms ]) +lpSum([ home[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')] )] ) <= 1+ gamesTooClose[t,d] + model2 += lpSum([ away[(t1,d)] for t1 in tms ]) +lpSum([ away[(t1,d2)] for d2 in conflictDays[(t,d)] for t1 in tms if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','A')] )] ) <= 1+ gamesTooClose[t,d] +# model2 += home[(t,d)]+lpSum([ away[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'H','A')]) ] ) <= 1+ gamesTooClose[t,d] +# model2 += away[(t,d)]+lpSum([ home[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','H')]) ] ) <= 1+ gamesTooClose[t,d] +# model2 += away[(t,d)]+lpSum([ away[(t,d2)] for d2 in conflictDays[(t,d)] if d2!=d and getDateTimeDay[d2]-getDateTimeDay[d] <= datetime.timedelta(days=minRest[(t,'A','A')]) ] ) <= 1+ gamesTooClose[t,d] + + +for t in realteams: + for r in rounds: + # awayInRound[t,r] = lpSum([x[(t2,t,dr)] for t2 in teams for dr in getRoundDaysByRound[r]]) + # homeInRound[t,r] = lpSum([x[(t,t2,dr)] for t2 in teams for dr in getRoundDaysByRound[r]]) + + if thisSeason.gamesPerRound=="one day": + # model2 += homeInRound[(t,r)] == lpSum([home[(t,d)] for (r2,d) in getRoundDaysByRound[r]]) + model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]]) + model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,rd)] for t2 in opponents[t] for rd in getRoundDaysByRound[r]]) + else: + model2 += homeInRound[(t,r)] == lpSum([x[(t,t2,getRoundDaysByRound[r][0])] for t2 in opponents[t] ]) + model2 += awayInRound[(t,r)] == lpSum([x[(t2,t,getRoundDaysByRound[r][0])] for t2 in opponents[t] ]) + + model2 += homeInRound[(t,r)] + awayInRound[(t,r)] <= 1 + if r>=3: + model2 += break3InRound[(t,r)] +2>= homeInRound[(t,r-2)]+homeInRound[(t,r-1)]+homeInRound[(t,r)] + model2 += break3InRound[(t,r)] +2>= awayInRound[(t,r-2)]+awayInRound[(t,r-1)]+awayInRound[(t,r)] + + if r>1: + if thisSeason.gamesPerRound=="one day": + model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in (getRoundDaysByRound[r-1]+getRoundDaysByRound[r]) if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)] + else: + model2 += lpSum([x[(t,t4,d)]+x[(t4,t,d)] for t4 in top4 for d in [getRoundDaysByRound[r-1][0],getRoundDaysByRound[r][0]] if t4 in opponents[t] ]) <= 1+tooManyTop4InRow[(t,r)] + + for c in clusters: + if thisSeason.gamesPerRound=="one day": + away_in_cluster[t,r,c] = lpSum([x[(t2,t,rd)] for t2 in cluster_teams[c] for rd in getRoundDaysByRound[r] if t2 in opponents[t] ]) + else: + away_in_cluster[t,r,c] = lpSum([x[(t2,t,getRoundDaysByRound[r][0])] for t2 in cluster_teams[c] if t2 in opponents[t] ]) + + +print ("fixedGamesRounds:" ,fixedGamesRounds) +for (t1,t2,r) in fixedGamesRounds: + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[r] ] )==1 + print ("fix ", t1,t2,r, lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[r]] )) + + +# initTripsLate = False +initTripsLate = True +onlyFewTrips= False +cntr =0 +if thisSeason.useFeatureTrips and not initTripsLate: + if thisSeason.tripMode=="Clusters": + if useDailyTrips: + for d1 in days: + otherTripDays = [ d2 for d2 in days if getDateTimeDay[d1]< getDateTimeDay[d2] and getDateTimeDay[d2]<=getDateTimeDay[d1]+datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1) and (len(getRoundsByDay[d1])*len(getRoundsByDay[d2])>1 or list(getRoundsByDay[d1])!=list(getRoundsByDay[d2]) )] + + for t in realteams: + for c in clusters: + if gew['Trips']>0 and not c in t_clusters[t] and len(otherTripDays)>0: + tripToClusterDaily[(t,d1,c)].upBound=1 + # tripToClusterDaily[(t,d1,c)] = pulp.LpVariable('tripToClusterDaily_'+str(t)+'_'+str(d1)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + model2 += tripToClusterDaily[(t,d1,c)] <= away_in_cluster_day[t,d1,c] + model2 += tripToClusterDaily[(t,d1,c)] <= lpSum([away_in_cluster_day[t,d2,c] for d2 in otherTripDays ]) + # print ('considering trip of ' , getTeamById[t], ' in days ' , getNiceDay[d1] , getNiceDay[otherTripDays[0]] , otherTripDays,' to cluster ' , c , cluster_teams[c]) + cntr +=2 + else : + for r in rounds: + otherTripRounds = [r2 for r2 in rounds if r2>r and getDateTimeDay[latestDay[r]]<= getDateTimeDay[earliestDay[r2]] and getDateTimeDay[earliestDay[r2]]-getDateTimeDay[latestDay[r]]<=datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1)] + # print ("trip rounds ", r, otherTripRounds) + for t in realteams: + # forbid same opponents on successive days + for t2 in realteams: + if t!=t2 and r>1 and forbidRepetitions: + model2 += lpSum([ (x[(t,t2,rd )]+x[(t2,t,rd)]) for rd in getRoundDaysByRound[r-1]+getRoundDaysByRound[r] ]) <= 1 + + for c in clusters: + if len(otherTripRounds)>0 and gew['Trips']>0 and not c in t_clusters[t] : + tripToCluster[(t,r,c)].upBound=1 + model2 += tripToCluster[(t,r,c)] <= away_in_cluster[t,r,c] + model2 += tripToCluster[(t,r,c)] <= lpSum([away_in_cluster[t,r2,c] for r2 in otherTripRounds ]) + # print ('considering trip of ' , getTeamById[t], ' in rounds ' , r , otherTripRounds,' to cluster ' , c ) + cntr +=2 + + # else : + # model2 += tripToCluster[(t,r,c)] == 0 + else: + tripDayPairs = {} + for d1 in days: + for d2 in days: + daysBetween = (getDateTimeDay[d2]-getDateTimeDay[d1]).days -1 + if daysBetween>=0 and getDayMaxGames[d1]>0 and getDayMaxGames[d2]>0 and daysBetween<=thisSeason.maxDistanceWithinTrip and getRoundByDay[d1]!=getRoundByDay[d2]: + # print (d1,d2,daysBetween, getNiceDay[d1], getNiceDay[d2], ) + if d1 not in tripDayPairs.keys(): + tripDayPairs[d1]=[] + tripDayPairs[d1].append((d2, daysBetween)) + + + for t in teams: + # print (getTeamById[t]) + # print (getTeamById[t],tripElements) + for (c,v1,v2,v3,v4) in tripElements[t]: + # if getTeamById[t]=="Regatas": + # print ("POSS TRIP ",getTeamById[t], (c, [ getTeamById[t3] for t3 in v1 ] , v2, [ getTeamById[t3] for t3 in v2 ] , [ getTeamById[t3] for t3 in v3 ] , [ getTeamById[t3] for t3 in v4 ]), trip_minDays[c], trip_maxDays[c]) + for d1 in tripDayPairs.keys(): + if getWeekDay[d1] in trip_weekdays[c] and ( not onlyFewTrips or t%6==d%6 ) : + otherTripDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]] + inBetweenDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween<=trip_minDays[c] ] + otherTripDays3 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + otherTripDays4 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays3 and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + # print ("possible? ", getTeamById[t] , getNiceDay[d1], otherTripDays,otherTripDays3,otherTripDays4) + if len(otherTripDays)>0 and (thisSeason.tripLength <3 or len (v3)==0 or len(otherTripDays3)>0) and (thisSeason.tripLength <4 or len (v4)==0 or len(otherTripDays4)>0) : + # print ("building trip for " , getTeamById[t], " starting" , getNiceDay[d1] , [ getNiceDay[d] for d in otherTripDays ], [ getNiceDay[d] for d in otherTripDays3 ] , [ getNiceDay[d] for d in otherTripDays4 ] ) + tripToSingleTripElement[(t,d1,c)].upBound=1 + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v1 for rd in getRoundDaysByDay[d1]]) + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + for d3 in inBetweenDays: + model2 += tripToSingleTripElement[(t,d1,c)] <= 1- home[t,d3] + model2 += tripToSingleTripElement[(t,d1,c)] <= 1-lpSum([ x[(t2,t,rd)] for t2 in realteams if t2 not in v2 for rd in getRoundDaysByDay[d3]]) + # if getTeamById[t]=="Regatas": + # print (" ", getNiceDay[d1], [ getNiceDay[d2] for d2 in inBetweenDays ] ,[ getNiceDay[d2] for d2 in otherTripDays ] ," <= ", [ (t2,t,rd) for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=3 and len(v3)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v3 for d2 in set(otherTripDays3) for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=4 and len(v4)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v4 for d2 in set(otherTripDays4) for rd in getRoundDaysByDay[d2]]) + if (len(v3)>0 and len(otherTripDays3)==0) or (len(v4)>0 and len(otherTripDays4)==0) : + tripToSingleTripElement[(t,d1,c)].upBound=0 + else: + cntr +=2 + +print ("built ", cntr , " trip constraints early") + +if sharedStadiums: + useStadiumTimeSlot = { (t,d,s) : pulp.LpVariable('useStadiumTimeSlot_'+str(t)+"_"+str(d)+"_"+str(s) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous ) for (t,d) in t_site_bestTimeSlots.keys() for (p,s) in t_site_bestTimeSlots[(t,d)]} + nonIceGame = { (t,d) : pulp.LpVariable('nonIceGame_'+str(t)+"_"+str(d) , lowBound = 0, upBound = 1, cat = pulp.LpContinuous ) for (t,d) in home.keys()} + for (t,d) in t_site_bestTimeSlots.keys(): + if len(t_site_bestTimeSlots[t,d])==0 : + model2 += home[(t,d)] == 0 + nonIceGame[(t,d)] + else: + model2 += home[(t,d)] == lpSum([useStadiumTimeSlot[(t,d,s)] for (p,s) in t_site_bestTimeSlots[t,d]])+ nonIceGame[(t,d)] + if "AllowGamesWithoutIce" not in special_wishes_active: + model2 += nonIceGame[(t,d)] == 0 + + getStadiumTimeSlotsPerDay={ (st.id,d) : [] for st in theseStadiums for d in days } + # for s in sorted(getStadiumTimeSlot.keys()): + # print (s,getStadiumTimeSlot[s]) + for (t,d,s) in useStadiumTimeSlot.keys(): + getStadiumTimeSlotsPerDay[getStadiumTimeSlot[s]['stadium_id'],d].append((t,s)) + # print (t,d,s) + # print (getStadiumTimeSlot[s]) + + for st in theseStadiums: + for d in days : + if len(getStadiumTimeSlotsPerDay[st.id,d])>1: + # print ("CONFLICTS?",st, getNiceDay[d], getStadiumTimeSlotsPerDay[st.id,d]) + conflicts_all = [ sorted(list(incompatible_timslots[s])) for t,s in getStadiumTimeSlotsPerDay[st.id,d]] + conflicts =[] + for c in conflicts_all: + if c not in conflicts: + conflicts.append(c) + print ("conflicts ", conflicts) + for c in conflicts: + conflicting_slots = [useStadiumTimeSlot[(t,d,s)] for t,s in getStadiumTimeSlotsPerDay[st.id,d] if s in c ] + # print ("not at same time " ,conflicting_slots) + if len(conflicting_slots)>1: + model2 += lpSum(conflicting_slots) <=1 + print ( "############## constraint built ") + +# cntr =0 +# for t in teams: +# for r in rounds: +# for c in real_clusters: +# if r>1 and runMode=='Improve': +# if gew['Trips']>0 and t_cluster[t]!=c \ +# and parse(getNiceDay[earliestDay[r]])-parse(getNiceDay[latestDay[r-1]])<=datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1): +# model2 += tripToCluster[(t,r-1,c)] <= away_in_cluster[t,r-1,c] +# model2 += tripToCluster[(t,r-1,c)] <= away_in_cluster[t,r,c] +# tripToCluster[(t,r-1,c)].upBound=1 +# # print ('considering trip of ' , getTeamById[t], ' in round ' , r , ' to cluster ' , c ) +# cntr +=2 +# else: +# model2 += tripToCluster[(t,r-1,c)] == 0 + +# if r==nRounds : +# model2 += tripToCluster[(t,r,c)] == 0 +# print ("built ", cntr , " trip constraints") + +if thisSeason.useFeatureKickOffTime: + for t in realteams: + for d in onlyEarlyDays: + model2+= home_time[t,d,getIdByTime["Late"]]==0 + model2+= away_time[t,d,getIdByTime["Late"]]==0 + for d in onlyLateDays: + model2+= home_time[t,d,getIdByTime["Early"]]==0 + model2+= away_time[t,d,getIdByTime["Early"]]==0 + + +breakVioBalance = { t : pulp.LpVariable('breakVioBalance_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in realteams } +numBreaks = { t : lpSum([ breakVio[(bl['id'],t)] for bl in breaks ]) for t in realteams } +numBreaks1 = { t : lpSum([ breakVio[(bl['id'],t)] for bl in breaks if bl['round2']<=nRounds1 ]) for t in realteams } + + +succBreaks = [ bl for bl in breaks if bl['round1']+1==bl['round2'] ] + +if thisSeason.forbidDoubleBreaks: + for bl1 in succBreaks: + for bl2 in succBreaks: + if bl1['round2']+1==bl2['round1']: + for t in realteams: + model2+= homeInRound[t,bl1['round1']] + homeInRound[t,bl1['round2']] + awayInRound[t,bl2['round1']] + awayInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] + model2+= awayInRound[t,bl1['round1']] + awayInRound[t,bl1['round2']] + homeInRound[t,bl2['round1']] + homeInRound[t,bl2['round2']] <= 3 + 0.2* breakVio[(bl1['id'],t)] + +for t in realteams: + for bl in breaks: + # print (getTeamById[t], realteams, getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ) + # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t,t2,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ]) + # model2+= breakVio[(bl['id'],t)] + 1 >= lpSum([x[(t2,t,rd)] for t2 in realteams for rd in getRoundDaysByRound[bl['round1']]+getRoundDaysByRound[bl['round2']] ]) + model2+= breakVio[(bl['id'],t)] + 1 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']] + model2+= breakVio[(bl['id'],t)] + 1 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']] + if bl['round2'] + 1 <= nRounds: + model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= homeInRound[t,bl['round1']] + homeInRound[t,bl['round2']] + homeInRound[t,bl['round2']+1] + model2+= 0.2+breakVio[(bl['id'],t)] + 2 >= awayInRound[t,bl['round1']] + awayInRound[t,bl['round2']] + awayInRound[t,bl['round2']+1] + + if balanceBreaks: + model2+= numBreaks[t] <= lpSum([ numBreaks[t2] for t2 in realteams])/len(realteams) + breakVioBalance[t] + for t2 in teams: + if half_symmetry_offset==0: + model2+= numBreaks[t2] <= 4 + model2+= numBreaks[t2] >= 3 + model2+= numBreaks1[t2] <= 2 + else: + model2+= numBreaks[t2] <= 5 + model2+= numBreaks[t2] >= 1 + print ("balancing Breaks ") + + for bl in breaks: + model2+= breakVio[(bl['id'],t)] <= 2 - homeInRound[t,bl['round1']] - awayInRound[t,bl['round2']] + model2+= breakVio[(bl['id'],t)] <= 2 - awayInRound[t,bl['round1']] - homeInRound[t,bl['round2']] + + + + if not evalRun: + if not thisSeason.minBreaks and len(breaks)>0 and not balanceBreaks: + for p in phases: + # print ("Don't allow breaks for ",t, " in phase ", p) + model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks if getPhaseOfRound[bl['round1']]==p and getPhaseOfRound[bl['round2']]==p]) <= thisSeason.initBreaks + + if mathModelName in [ "Ligue 1"]: + model2+= lpSum([breakVio[(bl['id'],t)] for bl in breaks ]) <= 4 + + if not thisSeason.startWithBreakAllowed: + model2+= homeInRound[t,1] + homeInRound[t,2] <= 1 + model2+= awayInRound[t,1] + awayInRound[t,2] <= 1 + if not thisSeason.endWithBreakAllowed: + print ("NO BREAK FOR " , t , " in rounds " ,nRounds-1, nRounds) + model2+= homeInRound[t,nRounds-1] + homeInRound[t,nRounds] <= 1 + model2+= awayInRound[t,nRounds-1] + awayInRound[t,nRounds] <= 1 + +# print ('\n') +# for p in phases: +# for d in getDaysOfPhase[p] : +# print (p,d, getRoundDaysByDay[d]) + +# return '' + +if len (fixedGamesRounds)==0: + for br in basicRounds: + imp_rds= getRoundDaysByBasicRound[br] if thisSeason.gamesPerRound=="one day" else [getRoundDaysByBasicRound[br][0]] + # print (br, imp_rds) + for t1 in realteams: + model2 += homeInBasicRound[(t1,br)] == lpSum([x[(t1,t2,rd)] for t2 in opponents[t1] for rd in imp_rds]) + model2 += awayInBasicRound[(t1,br)] == lpSum([x[(t2,t1,rd)] for t2 in opponents[t1] for rd in imp_rds]) + for t2 in opponents[t1]: + if (t1,t2) in games: + model2 += gameInBasicRound[(t1,t2,br)] == lpSum([ x[(t1,t2,rd)] for rd in imp_rds ]) + + # print ("§§§",t1,t2,br,gameInBasicRound[(t1,t2,br)].upBound ) + +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.3, TimeLimit=180,msg=1)) + + + +getDays[0] =[] + +# return 2 +# for d in days+higherLeagueDayIds: +# for stadium in stadiums: +# factor = 0.001 if len(teamsOfStadium[stadium])==2 else 1.0 +# # print (stadium, teamsOfStadium[stadium] , len(teamsOfStadium[stadium])) +# model2 += lpSum([home[t,d] for t in teamsOfStadium[stadium]]) <= 1 + factor*tooManyHomesInStadium[(stadium,d)] + + + +if thisSeason.useFeaturePairings: + + # identify Fr.-Mo. weekends + weekends = [] + sorted_days = sorted(days+higherLeagueDayIds, key=lambda x:getDateTimeDay[x]) + oneDay= datetime.timedelta(days=1) + lastDay=-1 + lastWeekday ="Thu" + currentWeekend=[] + for d in sorted_days: + newWeekend = lastWeekday in ["Tue","Wed","Thu"] or getDateTimeDay[d]-getDateTimeDay[lastDay]>3*oneDay + if newWeekend: + if len(currentWeekend)>0: + weekends.append(currentWeekend) + currentWeekend=[] + if getWeekDay[d] in ["Fri", "Sat", "Sun", "Mon"]: + currentWeekend.append(d) + lastDay=d + lastWeekday=getWeekDay[d] + if len(currentWeekend)>0: + weekends.append(currentWeekend) + + for pair in pairings: + pDaysSets = [] + pTeams = [pair['team1_id'], pair['team2_id']] + factor = 1.0 + if pair['dist'] in [10,11] : + # restrict to the weekend which overlap with the interval + pDaysSets = [ w for w in weekends] + pDaysSets = [ w for w in pDaysSets if not pair["first_day_id"] or not getNiceDayRaw[w[-1]] < getNiceDayRaw[pair["first_day_id"]]] + pDaysSets = [ w for w in pDaysSets if not pair["last_day_id"] or not getNiceDayRaw[pair["last_day_id"]]<= getNiceDayRaw[w[0]]] + else: + for d in pair["days"]: + pDays = [d] + if pair['dist'] in [1,5,8,9] and nextDay[d]!=-1 and (nextDay[d] in days+higherLeagueDayIds) : + pDays.append(nextDay[d]) + if pair['dist'] in [8,9] and nextnextDay[d]!=-1 and (nextnextDay[d] in days+higherLeagueDayIds) : + pDays.append(nextnextDay[d]) + if pair['dist'] in [3,7]: + pDays=getDays[getRoundByDay[d]] + getHigherDaysByRound[getRoundByDay[d]] + # print ( getNiceDay[d], getRoundByDay[d], getDays[getRoundByDay[d]] , " +" ,getHigherDaysByRound[getRoundByDay[d]]) + if len(pDays)>0 and pDays not in pDaysSets and not ((pair['dist']==5 and len(pDays)!=2) or (pair['dist']==9 and len(pDays)!=3)): + pDaysSets.append(pDays.copy()) + # print ("+++ ") + # else: + # print ("--- ") + + # if pair['prio'] =='A': + # factor = 0.1 + for pDays in pDaysSets: + d= pDays[0] + # print ("Treating day set starting with day " , getNiceDay[d], " ", pDays) + if pair['dist'] in [2,6] and thisSeason.useFeatureKickOffTime: + for tm in times: + if pair['type']== "Home": + if pair['dist']==2: + model2 += lpSum([home_time[t,d,tm] for t in pTeams]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += home_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + model2 += home_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + if pair['type']== "Away": + if pair['dist']==2: + model2 += lpSum([away_time[t,d,tm] for t in pTeams]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += away_time[pair['team1_id'],d,tm] - away_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + model2 += away_time[pair['team2_id'],d,tm] - away_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + if pair['type']== "Home and Away": + if pair['dist']==2: + model2 += lpSum([home_time[t,d,tm] + away_time[t,d,tm] for t in pTeams]) <= 1 + pairingVio[(pair['id'],d)] + else: + model2 += home_time[pair['team1_id'],d,tm] + away_time[pair['team1_id'],d,tm] - home_time[pair['team2_id'],d,tm] - away_time[pair['team2_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + model2 += home_time[pair['team2_id'],d,tm] + away_time[pair['team2_id'],d,tm] - home_time[pair['team1_id'],d,tm] - away_time[pair['team1_id'],d,tm] <= factor*pairingVio[(pair['id'],d)] + + else: + if pair['type']== "Home": + if pair['dist'] in [0,1,8,3,10]: + model2 += lpSum([home[t,dd] for t in pTeams for dd in pDays ]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += lpSum([home[pair['team1_id'],dd] - home[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + model2 += lpSum([home[pair['team2_id'],dd] - home[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + if pair['type']== "Away": + if pair['dist'] in [0,1,8,3,10]: + model2 += lpSum([away[t,dd] for t in pTeams for dd in pDays ]) <= 1 + factor*pairingVio[(pair['id'],d)] + else: + model2 += lpSum([away[pair['team1_id'],dd] - away[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + model2 += lpSum([away[pair['team2_id'],dd] - away[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + if pair['type']== "Home and Away": + if pair['dist'] in [0,1,8,3,10]: + model2 += lpSum([home[t,dd] + away[t,dd] for t in pTeams for dd in pDays]) <= 1 + pairingVio[(pair['id'],d)] + else: + model2 += lpSum([home[pair['team1_id'],dd] + away[pair['team1_id'],dd] - home[pair['team2_id'],dd] - away[pair['team2_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + model2 += lpSum([home[pair['team2_id'],dd] + away[pair['team2_id'],dd] - home[pair['team1_id'],dd] - away[pair['team1_id'],dd] for dd in pDays]) <= factor*pairingVio[(pair['id'],d)] + + +# print ("fixedGames:" ,fixedGames) +for (t1,t2,d) in fixedGames: + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + # if not thisSeason.useFeatureKickOffTime or not currentKickoffTimes[(t1,t2,d)] : + # model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + # else: + # model2+= lpSum ( [x_time[(t1,t2,rd,currentKickoffTimes[(t1,t2,d)])] for rd in getRoundDaysByRound[getRoundByDay[d]] ] )==1 - fixedGameVio[(t1,t2,d)] + if not thisScenario.allowFixedVio: + model2+= fixedGameVio[(t1,t2,d)]==0 + print ("HARD FIXING TO ROUND", getRoundByDay[d], t1,t2,d ) + else: + print ("SOFT FIXING TO ROUND", getRoundByDay[d], t1,t2,d ) + +for (t1,t2,d) in fixedGames2: + if len([rd for rd in getRoundDaysByDay[d] if (t1,t2,rd) in x.keys()]) >0 : + if not thisSeason.useFeatureKickOffTime or not currentKickoffTimes[(t1,t2,d)] : + model2+= lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByDay[d]] )== 1 - fixedGame2Vio[(t1,t2,d)] + else: + model2+= lpSum ( [x_time[(t1,t2,rd,currentKickoffTimes[(t1,t2,d)])] for rd in getRoundDaysByDay[d]] )== 1 - fixedGame2Vio[(t1,t2,d)] + if not thisScenario.allowFixedVio: + model2+= fixedGame2Vio[(t1,t2,d)]==0 + print ("HARD FIXING " , t1,t2,d , (t1,t2,d) in fixedGameVio.keys(), getRoundDaysByDay[d] , getRoundByDay[d] ) + else: + print ("SOFT FIXING " , t1,t2,d , (t1,t2,d) in fixedGameVio.keys(), getRoundDaysByDay[d] ) + +# return '' +# use_currentSolution= False +# for (t1,t2,d) in fixedRoundGames: +# # if thisScenario.allowFixedVio: +# # model2 += lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]]] )==1 - fixedRoundGameVio[(t1,t2,d)] +# # else: +# model2 += lpSum ( [x[(t1,t2,rd)] for rd in getRoundDaysByRound[getRoundByDay[d]]] )==1 + +# # TESTING + + +use_currentSolution= False + +currentGameCntr = { (t1,t2) :0 for t1 in teams for t2 in teams } + +if runMode=='Improve' and len(currentSolution) > len(fixedGames) + len(fixedGames2 ) : + use_currentSolution= True + +# if use_currentSolution and len(currentSolution) !=len(fixedGames): + +teamGameCntr = { (t,r) :0 for t in teams for r in rounds } + +if use_currentSolution: + for cs in currentSolution: + if len(cs)>=4: + t1=cs[0] + t2=cs[1] + r=cs[2] + d=cs[3] + if (t1,t2,(r,d)) in x.keys(): + currentGameCntr[(t1,t2)] +=1 + setLB(x[(t1,t2,(r,d))],1) + teamGameCntr[t1,r]+=1 + teamGameCntr[t2,r]+=1 + # if t1==16770: + # print ("fixing " , t1 , t2, r ,d) + +# for (t,r) in teamGameCntr.keys(): +# if teamGameCntr[(t,r)]!=1: +# print (getTeamById[t], " plays " ,teamGameCntr[(t,r)] , " games in round ", r ) + + +# sing for 16770 16766 0 2 + +# for ttr in x.keys(): +# makeIntVar(x[ttr]) + + +# if len(currentSolution) ==len(fixedGames): +# print ("alles fixieren") + +# print ("TESTING") +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) +# print ("TESTING DONE") + + + +# print ("days " , days) +for d in days: + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games for rd in getRoundDaysByDay[d] if distance[getTeamById[t1],getTeamById[t2]]<=thisSeason.maxDistanceDerby ]) >= nDerbies[d] - derbyMissing[d] + minG = sum([ roundDaysMin[rd] - deficientGames[rd] for rd in getRoundDaysByDay[d]]) + maxG = sum([ roundDaysMax[rd] + excessGames[rd] for rd in getRoundDaysByDay[d]]) + # print (getNiceDay[d] , maxG) + # if minG<3 : + if minG>0 : + model2 += lpSum([ home[(t,d)] for t in realteams]) >= minG + model2 += lpSum([ home[(t,d)] for t in realteams]) <= maxG + + if len(getRoundDaysByDay[d])>1: + for rd in getRoundDaysByDay[d]: + if roundDaysMin[rd]>0: + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] - deficientGames[rd] + # model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) >= roundDaysMin[rd] + print ("At least ",roundDaysMin[rd] , " on " , rd) + model2 += lpSum([x[(t1,t2,rd)] for (t1,t2) in games ] ) <= roundDaysMax[rd] + excessGames[rd] + +print ("rounds " , rounds, nRounds) +for r in rounds: + # one game a round for everyone + for t1 in realteams: + if thisSeason.gamesPerRound=="one day": + cnstr = lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1] for rd in getRoundDaysByRound[r]] ) + if len(cnstr) > 0: + model2 += cnstr <= 1 + else: + print ( getRoundDaysByRound[r], opponents[t1]) + if len(getRoundDaysByRound[r])>0: + for rd in getRoundDaysByRound[r]: + if rd==getRoundDaysByRound[r][0]: + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for t2 in opponents[t1]] ) <= 1 + else: + for t2 in opponents[t1]: + model2 += x[(t1,t2,rd)] == x[(t1,t2,getRoundDaysByRound[r][0])] + # print ("linking ", getRoundDaysByRound[r][0] , " to ", rd , " game " , t1, t2) + if len(getRoundDaysByRound[r])>1 and False: + for rd in getRoundDaysByRound[r]: + for t1 in realteams: + model2 += lpSum([home[t1,rd[1]] + away[t1,rd[1]]] ) <= 1 + +# exit(0) +print ("realteams " , realteams) + +print ("taking care of right numbers of games ... " ) +cntr= 0 + +if not specialGameControl: + for (t1,t2) in realgames: + if undirectedGameCntr[(t1,t2)]==0: + model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)] - missingGamesVio[(t1,t2)] + else: + # model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays] ) == gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - missingGamesVio[(t1,t2)] + model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] for rd in roundDays] ) + model2 += missingGamesVio[(t1,t2)] >= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] - lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for rd in roundDays] ) <= gameCntr[(t1,t2)]+gameCntr[(t2,t1)]+undirectedGameCntr[(t1,t2)] + + missingGamesVio[(t1,t2)].upBound= max(0,gameCntr[(t1,t2)]+undirectedGameCntr[(t1,t2)] - currentGameCntr[(t1,t2)]) + + # print (t1,t2,gameCntr[(t1,t2)], currentGameCntr[(t1,t2)] , lpSum([x[(t1,t2,rd)] for rd in roundDays] ) ) + +if thisSeason.useFeatureOpponentMatrix and False: + gameCntr = { (t1,t2) : 0 for t1 in teams for t2 in teams } + for gm in gameRequirements: + gameCntr[(gm.team1.id,gm.team2.id)] = gm.number + if gm.number>0: + # print ("++++ " ,gm) + if gm.number <= 0.5*nPhases or currentGameCntr[(gm.team1.id,gm.team2.id)]==gm.number: + model2 += lpSum([x[(gm.team1.id,gm.team2.id,rd)] for rd in roundDays] ) == gm.number + # print ("EQU " , gm) + else: + model2 += lpSum([x[(gm.team1.id,gm.team2.id,rd)] for rd in roundDays] ) == gm.number - missingGamesVio[(gm.team1.id,gm.team2.id)] + # print ("MAX " , gm) + model2 += missingGamesVio[(gm.team1.id,gm.team2.id)] <= gm.number -0.5*nPhases + print ("games still missing for " , gm.team1.id, gm.team2.id, currentGameCntr[(gm.team1.id,gm.team2.id)] , gm.number) + +print ("... done" ) + +# print (missingGamesVio.keys()) + + +if not evalRun: + for (t1,t2) in realgames: + # every pair plays each other in each phase once + for p in phases: + if p0 and noPlayRounds[t1]==noPlayRounds[t2]: + relDays = [] + phaseLength= int(len(playRounds[t1])/nPhases+0.5) + for rr in playRounds[t1][p*phaseLength:(p+1)*phaseLength]: + relDays+=getDays[rr] + print ("adding days of round ", rr , " to phase ", p) + else: + relDays = getDaysOfPhase[p] + model2 += lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] ) <= 1 + # print (len(relDays),"reldays") + # print (getTeamById[t1],getTeamById[t2], lpSum([x[(t1,t2,rd)] + x[(t2,t1,rd)] for d in relDays for rd in getRoundDaysByDay[d] ] )) + +if not evalRun : + for t in realteams: + if t_usePhases[t] and thisSeason.distributeHomeGamesEvenlyOverPhases: + for p in phases: + rds = [r for r in rounds if getPhaseOfRound[r]==p ] + nblocked =[ r for r in noHomeRounds[t] if r in rds] + print (p, len(rds), len(nblocked), rds,nblocked, getTeamById[t]) + for p in phases: + phaseLength= int(len(playRounds[t])/nPhases+0.5) + model2 += lpSum([ home[(t,d)] for d in getDaysOfPhase[p] ] ) <= int(phaseLength/2+1) + + if thisSeason.minRoundsBetweenGameOfSameTeams>0: + for r in rounds: + if r +thisSeason.minRoundsBetweenGameOfSameTeams <=nRounds: + rds = [ ] + for r2 in range(r,r+thisSeason.minRoundsBetweenGameOfSameTeams+1): + rds+=getRoundDaysByRound[r2] + rds2 = [ ] + for r2 in range(r,r+int (0.5*thisSeason.minRoundsBetweenGameOfSameTeams)): + rds2+=getRoundDaysByRound[r2] + # print ("ONLY ONE IN " , rds) + # print ("ONLY ONE IN " , rds2) + for t1 in realteams: + for t2 in realteams: + if t10 or x_round[(t1,t2,r)]!=0) : + # model2 += lpSum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + gamesTooClose2[t1,r] + model2 += lpSum([ (x[(t1,t2, rd)]+x[(t2,t1,rd)]) for rd in rds ]) <= 1 + + +if thisSeason.lastRoundSync: + for g in displayGroups.keys(): + affectedRounds = set([r for (r,d) in finalRoundDays]) + print ("affectedRounds", affectedRounds) + if thisSeason.useFeatureKickOffTime: + print ("sync time ", g, " " , displayGroups[g], " " , finalRoundDays, " ", times) + for r in affectedRounds: + model2 += lpSum([lastRoundPlayed[(g,rd,tm)] for rd in finalRoundDays for tm in times if rd[0]==r ]) == 1 + # model2 += lpSum([lastRoundPlayed[(g,rd,tm)] for rd in finalRoundDays for tm in times]) == len(affectedRounds) + for t in displayGroups[g]: + for rd in finalRoundDays: + for tm in times: + model2+= home_time[t,rd[1],tm]+ away_time[t,rd[1],tm] <= lastRoundPlayed[(g,rd,tm)] + else: + # print ("sync time", g, " " , displayGroups[g], " " , finalRoundDays) + for r in affectedRounds: + model2 += lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays if rd[0]==r ]) == 1 + # model2 += lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays ]) == len(affectedRounds) + print (lpSum([lastRoundPlayed[(g,rd)] for rd in finalRoundDays ]) , " ==" , len(affectedRounds)) + for t in displayGroups[g]: + for rd in finalRoundDays: + model2+= home[(t,rd[1])] + away[(t,rd[1])] <= lastRoundPlayed[(g,rd)] + # print ("home[(",t,rd[1],")] + away[(",t,rd[1],")] <= lastRoundPlayed[(",g,rd,")])") + + +if not evalRun: + # max length home stands /trips + for r in range (1,nRounds-thisSeason.maxTourLength+1): + # print (" at least one home and away in rounds ") + # for r3 in range(r,r+thisSeason.maxTourLength+1): + # print (r3 ) + for t in teams: + if t not in noBreakLimitTeams: + # model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) >=1 + model2 += lpSum([awayInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength + model2 += lpSum([homeInRound[t,r2] for r2 in range(r,r+thisSeason.maxTourLength+1)]) <=thisSeason.maxTourLength + +print ("check 1") + + # blockings +for bl in blockings: + if getDayById[bl['day_id']]['round'] !=0: + if thisSeason.useFeatureKickOffTime and bl['time']!='----': + if bl['type'] in ["Home", "Hide","Game"]: + model2+= blockingVio[bl['id']]== home_time[bl['team_id'], bl['day_id'],bl['time']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2+= blockingVio[bl['id']]== away_time[bl['team_id'], bl['day_id'],bl['time']] + # print ('FOUND AWAY BLOCKING ', bl) + else: + if bl['type'] in ["Home", "Hide","Game"]: + model2+= blockingVio[bl['id']]== home[bl['team_id'], bl['day_id']] + # print ('FOUND HOME BLOCKING ', bl) + else: + model2+= blockingVio[bl['id']]== away[bl['team_id'], bl['day_id']] + # print ('FOUND AWAY BLOCKING ', bl) + + +print ("check 2") + +hawOneVio={} +hawForOneNotViolated={} + +def getStringFromSet(ss): + s2 ="" + for s in ss: + s2+=str(s)+"_" + return s2[:-1] + + # hawishes +for haw in hawishes: + print (haw['reason']) + for el in elemHaWishes[haw['id']]: + if thisSeason.useFeatureKickOffTime and len(hawTimes[haw['id']])>0: + if haw['homeAway']=='Home': + relGames = lpSum([home_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + # print (haw['id'] ," haw : ", relGames, hawTimes[haw['id']]) + elif haw['homeAway']=='Away': + relGames = lpSum([away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) + else : + # print(haw,el) + relGames = lpSum([home_time[t,d,tm] + away_time[t,d,tm] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] for tm in hawTimes[haw['id']]]) \ + - lpSum([x_time[(t1,t2,rd,tm)] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for tm in hawTimes[haw['id']] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd,tm) in x_time.keys()]) + + else: + if haw['homeAway']=='Home': + relGames = lpSum([home[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ]) + elif haw['homeAway']=='Away': + relGames = lpSum([away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el] ]) + else : + relGames = lpSum([home[t,d] + away[t,d] for d in elemHaWishDays[el] for t in elemHaWishTeams[el]]) - lpSum([x[t1,t2,rd] for d in elemHaWishDays[el] for rd in getRoundDaysByDay[d] for t1 in elemHaWishTeams[el] for t2 in elemHaWishTeams[el] if (t1,t2,rd) in x.keys() ]) + if haw['minGames'] >0 : + model2+= relGames >= haw['minGames'] - HawVioTooLess[el] + # print ("adding min ha constraint") + if haw['maxGames'] >=0 : + model2+= relGames <= haw['maxGames'] + HawVioTooMuch[el] + # print ("adding max ha constraint") + + + usedConstraintNames = [""] + if haw['forOneDay']: + # print (haw['forOneDay'], hawDays[haw['id']]) + # print ([el for el in elemHaWishes[haw['id']] ]) + # for el in elemHaWishes[haw['id']] : + # print("- " , elemHaWishDays[el] , elemHaWishTeams[el] ) + # print ([ (el, elemHaWishFirstDay[el]) for el in elemHaWishes[haw['id']] ]) + relTeamString = { el : getStringFromSet(elemHaWishTeams[el]) for el in elemHaWishes[haw['id']] } + relTeams = set([ relTeamString[el] for el in elemHaWishes[haw['id']]]) + # print (relTeams) + for rt in relTeams: + rtname = rt + if len(rt)>50 : + scname="" + while scname in usedConstraintNames: + ttt = bytes(rt+ ''.join(random.choice(string.ascii_lowercase) for i in range(10)), 'utf-8') + scname = hashlib.sha224(ttt).hexdigest() + rtname = scname[:10] + usedConstraintNames.append(rtname) + # print ("use rtname to encode wishes ", rtname) + + hawOneVio[(haw['id'], rt)]= pulp.LpVariable('hawOneVio_'+str(haw['id'])+"_"+rtname, cat=pulp.LpContinuous) + for fd in hawDays[haw['id']]: + relWishes = [el for el in elemHaWishes[haw['id']] if elemHaWishFirstDay[el]== fd] + # relWishes = [el for el in elemHaWishes[haw['id']] ] + # print (" -" , relWishes , [elemHaWishDays[el] for el in relWishes]) + hawForOneNotViolated[(haw['id'], rt, fd)]= 0 + if len(relWishes)>0: + hawForOneNotViolated[(haw['id'], rt, fd)]= pulp.LpVariable('hawForOneViolated_'+str(haw['id'])+"_"+rtname+"_"+str(fd), cat=pulp.LpBinary) + model2 += lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in relWishes if relTeamString[el]==rt) <= 1000 * (1-hawForOneNotViolated[(haw['id'],rt, fd)]) + model2 += lpSum( hawForOneNotViolated[(haw['id'], rt, fd)] for fd in hawDays[haw['id']] ) >= haw['forOneDayNum']-hawOneVio[(haw['id'],rt)] + model2 += lpSum( hawOneVio[(haw['id'], rt)] for rt in relTeams ) == hawVio[haw['id']] + else: + # print (haw['forOneDay'], hawDays[haw['id']]) + model2 += hawVio[haw['id']]== lpSum( HawVioTooMuch[el]+HawVioTooLess[el] for el in elemHaWishes[haw['id']]) + + + if haw['prio']=="Hard" and "HardHAWishesNotBreakable" in special_wishes_active: + model2+= hawVio[haw['id']] ==0 + print ("WISH HARD" ,haw['reason']) + + +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +# print (hawDays) +# print (elemHaWishFirstDay) + +encOneVio={} +encForOneNotViolated={} +print ("check 3") +# encwishes +for enc in encwishes: + # print (enc) + for el in elemEncWishes[enc['id']]: + # print (enc) + # model2+= encVio[enc['id']] == 1 - x[enc['team1_id'], enc['team2_id'], enc['day_id']] + + if thisSeason.useFeatureKickOffTime and len(encTimes[enc['id']])>0: + if enc['minGames'] >0 : + model2+= encVioTooLess[el] >= enc['minGames'] - lpSum([ x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ]) + if enc['maxGames'] >=0 : + # if enc['maxGames']==0: + print (enc['reason'], ' ', elemEncWishDays[el], ' ',enc['time'], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + # print (lpSum([x_time[(t1,t2,rd, enc['time'])] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ])) + model2+= encVioTooMuch[el] >= -enc['maxGames'] + lpSum([x_time[(t1,t2,rd, tm )] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] for tm in encTimes[enc['id']] if (t1,t2) in games ]) + else: + if enc['minGames'] >0 : + model2+= encVioTooLess[el] >= enc['minGames'] - lpSum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ]) + if enc['maxGames'] >=0 : + # if enc['maxGames']==0: + # print (enc['reason'], ' ', encDays[enc['id']], ' ', encTeams1[enc['id']] , ' ',encTeams2[enc['id']] ) + model2+= encVioTooMuch[el] >= -enc['maxGames'] + lpSum([ x[t1,t2,rd] for d in elemEncWishDays[el] for rd in getRoundDaysByDay[d] for (t1,t2) in elemEncWishGames[el] if (t1,t2) in games ]) + + if enc['forOneDay']: + for ed in encDaySets[enc['id']]: + if len(ed)>0: + relWishes = [ el for el in elemEncWishes[enc['id']] if elemEncWishDays[el] == ed ] + print ("###",ed, relWishes) + encForOneNotViolated[(enc['id'], ed[0])]= pulp.LpVariable('encForOneNotViolated_'+str(enc['id'])+"_"+str(ed[0]), cat=pulp.LpBinary) + model2 += lpSum( encVioTooMuch[el]+encVioTooLess[el] for el in relWishes) <= 1000 * (1-encForOneNotViolated[(enc['id'], ed[0])]) + model2 += lpSum( encForOneNotViolated[(enc['id'], ed[0])] for ed in encDaySets[enc['id']] if len(ed)>0 ) >= enc['forOneDayNum']-encVio[enc['id']] + else: + model2 += encVio[enc['id']]== lpSum( encVioTooMuch[el]+encVioTooLess[el] for el in elemEncWishes[enc['id']]) + + + if enc['prio']=="Hard" and "HardEncWishesNotBreakable" in special_wishes_active: + model2+= encVio[enc['id']] ==0 + print ("WISH HARD" ,enc['reason']) + + +print ("check 4") + + +for cf in conferencewishes: + print (confTeams[cf['conference_id']]) + model2 += lpSum([x[t1,t2, rd] for t1 in confTeams[cf['conference_id']] for t2 in confTeams[cf['conference_id']] for rd in getRoundDaysByDay[cf['day_id']] if (t1,t2, rd) in x.keys() ]) <= cf['maxGames'] + confVio[cf['id']] + model2 += lpSum([x[t1,t2, rd] for t1 in confTeams[cf['conference_id']] for t2 in confTeams[cf['conference_id']] for rd in getRoundDaysByDay[cf['day_id']] if (t1,t2, rd) in x.keys() ]) >= cf['minGames'] - confVio[cf['id']] + print (cf , " : " , confTeams[cf['conference_id']]) + if thisSeason.groupBased: + model2+= confVio[cf['id']]==0 + +b_slots={(b.id,r) : [] for b in broadcastingwishes for r in rounds} +if thisSeason.useFeatureKickOffTime: + for b in broadcastingwishes: + b_weekdays = [sl.weekday[:3] for sl in b.slots.all() ] + for r in rounds: + if len( [wd for wd in b_weekdays if wd in getWeekDaysPerRound[r]]) == len(b_weekdays): + b_slots[(b.id,r)] = [ (d,str(sl.timeslot.id),sl.quality) for sl in b.slots.all() for d in getDays[r] if getWeekDay[d]==sl.weekday[:3]] + if len(b_slots[(b.id,r)])>0: + model2 += lpSum([ x_time[g[0][0],g[0][1],(r,d),tm]+x_time[g[0][1],g[0][0],(r,d),tm] for g in topGames for (d,tm,q) in b_slots[(b.id,r)]]) >= b.minGames - broadVioTm[(b.id,r)] + model2 += lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)]]) >= b.minGames - 0.5*broadVioTm[(b.id,r)] + if b.network.id in networkFavTeams.keys() : + model2 += lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)] if t1 in networkFavTeams[b.network.id] or t2 in networkFavTeams[b.network.id] ]) >= b.minGames - 0.1*broadVioTm[(b.id,r)] + # if b.network.name== "Super Sport": + # print (" - " ,r , lpSum([ x_time[t1,t2,(r,d),tm] for (t1,t2) in games for (d,tm,q) in b_slots[(b.id,r)] if t1 in networkFavTeams[b.network.id] or t2 in networkFavTeams[b.network.id] ])) + +# return "" +weekdayHomePref={} +dayHomePref={} +for t in teams: + tm = getTeamByName[getTeamById[t]] + weekdayHomePref[(t,'Mon')]=tm['home_pref_mo'] + weekdayHomePref[(t,'Tue')]=tm['home_pref_tu'] + weekdayHomePref[(t,'Wed')]=tm['home_pref_we'] + weekdayHomePref[(t,'Thu')]=tm['home_pref_th'] + weekdayHomePref[(t,'Fri')]=tm['home_pref_fr'] + weekdayHomePref[(t,'Sat')]=tm['home_pref_sa'] + weekdayHomePref[(t,'Sun')]=tm['home_pref_su'] + for d in days : + dayHomePref[(t,d)]=weekdayHomePref[(t,getWeekDay[d])] + + +maxTravelDistance = max([ distanceById[t1,t2] for t1 in realteams for t2 in realteams ]) +maxTravelDistance = max(1,maxTravelDistance) + +singleTripWeight=50 +breakImbalanceTotal= lpSum([ breakVioBalance[t] for t in realteams]) +tripSavedTotal2= ( lpSum([ singleTripWeight *tripToSingleTripElement[(t,d,c)] for (t,d,c) in tripToSingleTripElement.keys() ]) + +( lpSum([ c_weight[c] *tripToClusterSaving[(t,c)] *tripToCluster[(t,r,c)] for (t,r,c) in tripToCluster.keys() ]) + +lpSum([ c_weight[c] *tripToClusterSaving[(t,c)] *tripToClusterDaily[(t,d,c)] for (t,d,c) in tripToClusterDaily.keys()]))/maxTravelDistance ) +# HawVioTotal=lpSum([prioVal[haw['prio']] * (HawVioTooLess[el]+HawVioTooMuch[el]) for haw in hawishes for el in elemHaWishes[haw['id']]]) +HawVioTotal=lpSum([prioVal[haw['prio']] * hawTagweight[haw['id']]* hawVio[haw['id']] for haw in hawishes]) +encVioTotal=lpSum([prioVal[enc['prio']] * encTagweight[enc['id']] * encVio[enc['id']] for enc in encwishes]) +seedVioTotal=lpSum([100*prioVal[enc['prio']] * encVio[enc['id']] for enc in encwishes if enc['seed'] ]) +confVioTotal=lpSum([prioVal[conf['prio']] * confVio[conf['id']] for conf in conferencewishes]) +# broadVioTotal=lpSum([ 10 * broadVio[d] for d in days]) + lpSum([ 10 * broadVioTm[b.id] for b in broadcastingwishes]) +broadVioTotal=lpSum([ 10 * broadVioTm[(b.id,r)] for b in broadcastingwishes for r in rounds]) +breakVioTotal=lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in realteams]) + 2*lpSum([prioVal[bl['prio']]*breakVio[(bl['id'],t)] for bl in breaks for t in importantteams]) +break3VioTotal=lpSum([2*break3InRound[t,r] for t in teams for r in rounds]) +tooManyTop4InRowTotal=lpSum([ 10*tooManyTop4InRow[(t,r)] for t in teams for r in rounds]) +pairingVioTotal=lpSum([ 5 *prioVal[pair['prio']] * pairTagweight[pair['id']] * pairingVio[(pair['id'],d)] for pair in pairings for d in days+higherLeagueDayIds]) +# blockingVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Home"]) +# print (blockings) +# for bl in blockings: + # if bl['type'] in ["Home"]: + # print (blocked_arena[(bl["team_id"],bl["day_id"],"----")], getTeamById[bl["team_id"]], getNiceDay[bl["day_id"]] , bl ) + +fulfBlocks =set([(bl["team_id"], getRoundByDay[bl["day_id"]]) for bl in blockings if bl['type'] in ["Home"] and blocked_arena[(bl["team_id"],bl["day_id"],"----")]] ) + +blockingVioTotal2=lpSum([ -30 * homeInRound[tr] for tr in fulfBlocks if thisSeason.allowBlockingViosInImprove and runMode=='Improve'] ) +blockingVioTotal=lpSum([ 100 * bl['weight']*blockingVio[bl['id']] for bl in blockings if bl['type'] in ["Home", "Hide","Game"]]) +blockingVioTotal2 +goodHomesTotal=lpSum([ 100 * gh['weight']* home[gh['team_id'],gh['day_id']]for gh in goodHomes ]) +travelVioTotal=lpSum([ 100 * blockingVio[bl['id']] for bl in blockings if bl['type']=="Away"]) +gamesTooCloseTotal=lpSum([ 120 * gamesTooClose[(t,d)] for t in teams for d in days+higherLeagueDayIds if conflictDays[(t,d)]]) + lpSum([ 120 * gamesTooClose2[(t,r)] for t in teams for r in rounds ]) +derbiesMissingTotal=lpSum([ 30 * derbyMissing[d] for d in days]) +tooManyHomesInStadiumTotal=lpSum([ 110 * tooManyHomesInStadium[(stadium.id, d)] for stadium in stadiums for d in days+higherLeagueDayIds]) +unpreferredTotal=lpSum([ home[t, d] for t in teams for d in days if dayHomePref[(t,d)]==0 ]) +# competitionVioTotal=lpSum([ 100 * competitionVio[(c,d,t)] for (c,d,t) in competitions]) +competitionVioTotal=lpSum([ 100 * (home[t,d]+ away[t,d]) for (t,d) in competitions.keys()]) +fixedGameVioTotal=lpSum([ 10000 * fixedGameVio[(t1,t2,d)] for (t1,t2,d) in fixedGames]) + lpSum([ 10000 * fixedGame2Vio[(t1,t2,d)] for (t1,t2,d) in fixedGames2]) +missingGamesVioTotal=lpSum([ 2000000 * missingGamesVio[(t1,t2)] for (t1,t2) in realgames]) +# TODO - UNDO CHANGES: missingGamesVioTotal=lpSum([ 2000 * missingGamesVio[(t1,t2)] for (t1,t2) in games]) +oldScenGamesTotal=lpSum([ wg * x[t1,t2,rd] for (t1,t2,r,wg) in otherScenGames for rd in getRoundDaysByRound[r]] ) +totalAttendance=lpSum([ attendance[(t1,t2,d)] * x[t1,t2,(r,d)] for (t1,t2) in games for (r,d) in roundDays ] ) + +specialObjectives = 0 +specialWishItems ={ sw:[] for sw in special_wishes_active} +specialWishVio ={} + + +optCameraMovement = "Standard" +if thisLeague.name in ["Indian Super League"] and thisSeason.name != "2021": + optCameraMovement = "TV-Kits" +# if thisLeague.name in ["Indian Premier League"] and thisSeason.useFeatureBroadcasting: +# optCameraMovement = "Stadiums" + +# tvkitproblem = {} +move2 = {} +newtrip2 = {} +if optCameraMovement == "TV-Kits": + networkIds=networkName.keys() + TV_day_pairs = [ (d1, d2) for d1 in days for d2 in days if getDateTimeDay[d1]+datetime.timedelta(days=1)=getDateTimeDay[d2] -datetime.timedelta(days=20) ] + movements = [ (t1,d1,t2,d2) for t1 in realteams for t2 in realteams for (d1,d2) in TV_day_pairs if getDateTimeDay[d2]-getDateTimeDay[d1] >= datetime.timedelta(days=distanceInDaysById[(t1,t2)]+1 ) ] + + if thisLeague.name in ["Indian Super League"] and thisSeason.name not in ["2021", "2022"]: + minDays = { ('Ben', 'Ben'): 0, ('Ben', 'Che'): 1, ('Ben', 'FC '): 2, ('Ben', 'Nor'): 12, ('Ben', 'Jam'): 6, ('Ben', 'Ker'): 3, ('Ben', 'Moh'): 6, ('Ben', 'Eas'): 6, ('Ben', 'Mum'): 3, ('Ben', 'Hyd'): 2, ('Ben', 'Odi'): 5, ('Ben', 'Pun'): 7, + ('Che', 'Ben'): 1, ('Che', 'Che'): 0, ('Che', 'FC '): 3, ('Che', 'Nor'): 11, ('Che', 'Jam'): 5, ('Che', 'Ker'): 3, ('Che', 'Moh'): 5, ('Che', 'Eas'): 5, ('Che', 'Mum'): 4, ('Che', 'Hyd'): 2, ('Che', 'Odi'): 4, ('Che', 'Pun'): 7, + ('FC ', 'Ben'): 2, ('FC ', 'Che'): 3, ('FC ', 'FC '): 0, ('FC ', 'Nor'): 14, ('FC ', 'Jam'): 7, ('FC ', 'Ker'): 3, ('FC ', 'Moh'): 7, ('FC ', 'Eas'): 7, ('FC ', 'Mum'): 2, ('FC ', 'Hyd'): 2, ('FC ', 'Odi'): 5, ('FC ', 'Pun'): 6, + ('Nor', 'Ben'): 12,('Nor', 'Che'): 11,('Nor', 'FC '): 14, ('Nor', 'Nor'): 0, ('Nor', 'Jam'): 5, ('Nor', 'Ker'): 14,('Nor', 'Moh'): 5, ('Nor', 'Eas'): 5, ('Nor', 'Mum'): 12, ('Nor', 'Hyd'): 10, ('Nor', 'Odi'): 6, ('Nor', 'Pun'): 8, + ('Jam', 'Ben'): 6, ('Jam', 'Che'): 5, ('Jam', 'FC '): 7, ('Jam', 'Nor'): 5, ('Jam', 'Jam'): 0, ('Jam', 'Ker'): 10, ('Jam', 'Moh'): 1, ('Jam', 'Eas'):1, ('Jam', 'Mum'): 6, ('Jam', 'Hyd'): 5, ('Jam', 'Odi'): 2, ('Jam', 'Pun'): 5, + ('Ker', 'Ben'): 3, ('Ker', 'Che'): 3, ('Ker', 'FC '): 3, ('Ker', 'Nor'): 14, ('Ker', 'Jam'): 10, ('Ker', 'Ker'): 0, ('Ker', 'Moh'): 10,('Ker', 'Eas'): 10, ('Ker', 'Mum'): 6, ('Ker', 'Hyd'): 5, ('Ker', 'Odi'): 8, ('Ker', 'Pun'): 11, + ('Moh', 'Ben'): 6, ('Moh', 'Che'): 5, ('Moh', 'FC '): 7, ('Moh', 'Nor'): 5, ('Moh', 'Jam'): 1, ('Moh', 'Ker'): 10, ('Moh', 'Moh'): 0, ('Moh', 'Eas'): 0, ('Moh', 'Mum'): 6, ('Moh', 'Hyd'): 5, ('Moh', 'Odi'): 2, ('Moh', 'Pun'): 5, + ('Eas', 'Ben'): 6, ('Eas', 'Che'): 5, ('Eas', 'FC '): 7, ('Eas', 'Nor'): 5, ('Eas', 'Jam'): 1, ('Eas', 'Ker'): 10, ('Eas', 'Moh'): 0, ('Eas', 'Eas'): 0, ('Eas', 'Mum'): 6, ('Eas', 'Hyd'): 5, ('Eas', 'Odi'): 2, ('Eas', 'Pun'): 5, + ('Mum', 'Ben'): 3, ('Mum', 'Che'): 4, ('Mum', 'FC '): 2, ('Mum', 'Nor'): 12, ('Mum', 'Jam'): 6, ('Mum', 'Ker'): 6, ('Mum', 'Moh'): 6, ('Mum', 'Eas'): 6, ('Mum', 'Mum'): 0, ('Mum', 'Hyd'): 3, ('Mum', 'Odi'): 5, ('Mum', 'Pun'): 5, + ('Hyd', 'Ben'): 2, ('Hyd', 'Che'): 2, ('Hyd', 'FC '): 2, ('Hyd', 'Nor'): 10, ('Hyd', 'Jam'): 5, ('Hyd', 'Ker'): 5, ('Hyd', 'Moh'): 5, ('Hyd', 'Eas'): 5, ('Hyd', 'Mum'): 3, ('Hyd', 'Hyd'): 0, ('Hyd', 'Odi'): 4, ('Hyd', 'Pun'): 5, + ('Odi', 'Ben'): 5, ('Odi', 'Che'): 4, ('Odi', 'FC '): 5, ('Odi', 'Nor'): 6, ('Odi', 'Jam'): 2, ('Odi', 'Ker'): 8, ('Odi', 'Moh'): 2, ('Odi', 'Eas'): 2, ('Odi', 'Mum'): 5, ('Odi', 'Hyd'): 4, ('Odi', 'Odi'): 0, ('Odi', 'Pun'): 6, + ('Pun', 'Ben'): 7, ('Pun', 'Che'): 7, ('Pun', 'FC '): 6, ('Pun', 'Nor'): 8, ('Pun', 'Jam'): 5, ('Pun', 'Ker'): 11, ('Pun', 'Moh'): 5, ('Pun', 'Eas'): 5, ('Pun', 'Mum'): 5, ('Pun', 'Hyd'): 5, ('Pun', 'Odi'): 6, ('Pun', 'Pun'): 0, + } + + minDays = { (t1,t2) : minDays[(getTeamById[t1][:3],getTeamById[t2][:3])] for t1 in teams for t2 in teams } + movements = [(t1,d1,t2,d2) for (t1,d1,t2,d2) in movements if (((getDateTimeDay[d2]-getDateTimeDay[d1]).total_seconds()/(3600*24))-1) >= minDays[t1,t2]] + + pred2 ={ (t,d) :[] for t in realteams for d in days } + succ2 ={ (t,d) :[] for t in realteams for d in days } + move2 = { (t1,d1,t2,d2) : pulp.LpVariable('move2_'+str(t1)+'_'+str(d1)+'_'+str(t2)+'_'+str(d2), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,d1,t2,d2) in movements } + unserved_tv = { (t1,d1) : pulp.LpVariable('unserved_tv'+str(t1)+'_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in realteams for d1 in days } + newtrip2 = { (t1,d1) : pulp.LpVariable('newtrip2_'+str(t1)+'_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in realteams for d1 in days } + tooManyTrips2 = pulp.LpVariable('tooManyTrips2', lowBound = 0, cat = pulp.LpContinuous) + + for (t1,d1,t2,d2) in movements: + # print (getTeamById[t1] , getTeamById[t2] , distanceById[t1,t2] , distanceInDaysById[t1,t2] , getNiceDay[d1] , getNiceDay[d2] ) + pred2[(t2,d2)].append( (t1,d1,t2,d2)) + succ2[(t1,d1)].append( (t1,d1,t2,d2)) + + # if thisLeague.name in ["Indian Super League"]: + # for (t,d) in newtrip2.keys(): + # # model2+= home[(t,d)] == lpSum( movement[tdtd] for tdtd in pred2[(t,d)] ) + # model2+= home[(t,d)] >= lpSum( move2[tdtd] for tdtd in succ2[(t,d)] ) + # model2+= home[(t,d)] == lpSum( move2[tdtd] for tdtd in pred2[(t,d)] ) + newtrip2 [(t,d)] + unserved_tv[(t,d)] + # model2+= lpSum( move2[tdtd] for tdtd in move2.keys() if tdtd[0] == t and tdtd[1] == d) <= 1 - unserved_tv[(t,d)] + # model2+= lpSum( unserved_tv[k] for k in unserved_tv.keys()) <= 1 + # model2+= tooManyTrips2 <= 0 + # else: + for (t,d) in newtrip2.keys(): + # model2+= home[(t,d)] == lpSum( movement[tdtd] for tdtd in pred2[(t,d)] ) + model2+= home[(t,d)] >= lpSum( move2[tdtd] for tdtd in succ2[(t,d)] ) + model2+= home[(t,d)] == lpSum( move2[tdtd] for tdtd in pred2[(t,d)] ) + newtrip2 [(t,d)] + tv_phases = [ [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],[16,17,18,19,20,21,22]] + for tvp in tv_phases: + model2+= home[(t,d)] == lpSum( newtrip2[(t,d)] for (t,d) in newtrip2.keys() if getRoundByDay[d] in tvp ) <= len(networkIds) + tooManyTrips2 + + # model2+= home[(t,d)] <= lpSum(home[(t2,d2)] for t2 in realteams for d2 in otherDays if getDateTimeDay[d2]<=getDateTimeDay[d]- datetime.timedelta(days=distanceInDaysById[(t,t2)]) ) +tvkitproblem[(t,d)] + specialObjectives += 0.01*lpSum( distanceById[t1,t2]*move2[(t1,d1,t2,d2)] for (t1,d1,t2,d2) in movements)+ 100000* tooManyTrips2 + +jamshedpurVio={} +if thisLeague.name in ["Indian Super League"]: + jamshedpur = getTeamByName["Jamshedpur FC"]['id'] + print ("Jamshedpur FC" , jamshedpur) + jamshedpurVio = { d1 : pulp.LpVariable('jamshedpurVio_'+str(d1), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for d1 in days } + for d in days: + fourdays = [d2 for d2 in days if getDateTimeDay[d2]>=getDateTimeDay[d] and getDateTimeDay[d2]<=getDateTimeDay[d]+datetime.timedelta(days=3) ] + otherday = [d2 for d2 in fourdays if getDateTimeDay[d2]==getDateTimeDay[d]+datetime.timedelta(days=3) ] + if len(otherday)>0: + otherday=otherday[0] + print (getNiceDay[d], fourdays , getNiceDay[otherday] ) + for t in realteams: + if t!=jamshedpur: + # model2+= lpSum( x[jamshedpur,t, rd] for d2 in fourdays for rd in getRoundDaysByDay[d2] ) + lpSum( home[t,d2] + away[t,d2] for d2 in fourdays ) <= 2 + jamshedpurVio[d] + model2+= lpSum( x[jamshedpur,t, rd] for rd in getRoundDaysByDay[d] ) + home[t,otherday] + away[t,otherday] <= 1 + jamshedpurVio[d] + model2+= lpSum( x[jamshedpur,t, rd] for rd in getRoundDaysByDay[otherday] ) + home[t,d] + away[t,d] <= 1 + jamshedpurVio[d] + else: + model2+= home[t,d] + away[t,otherday] <= 1 + jamshedpurVio[d] + model2+= away[t,d] + home[t,otherday] <= 1 + jamshedpurVio[d] + + specialObjectives += 1000*lpSum( jamshedpurVio[d] for d in days) + + + + +if "AlwaysEnoughRestDays" in special_wishes_active: + model2+=lpSum([ gamesTooClose[(t,d)] for t in teams for d in days if conflictDays[(t,d)]]) ==0 + +if "NoPairingVio" in special_wishes_active: + # model2+=pairingVioTotal==0 + hardpairs = [ p for p in pairings if p['prio']=="Hard" ] + if len(hardpairs)>0: + model2+=lpSum([ pairingVio[(pair['id'],d)] for pair in pairings for d in days+higherLeagueDayIds if pair['prio']=="Hard"])==0 + +if "NoBreaks" in special_wishes_active: + model2+=breakVioTotal==0 + +if "SyncDaysInGroups" in special_wishes_active: + for (t1,t2) in games: + for d in days: + model2+= home[(t1,d)]+away[(t1,d)] == home[(t2,d)]+away[(t2,d)] + +if "austrianSymmetry" in special_wishes_active: + for (t1,t2) in games: + if (t2,t1) in games: + for (r1,r2) in [(1,7),(2,8),(3,9),(4,10),(5,6)] : + model2+= lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[r1]]) == lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[r2]]) + + +# if "3RestDaysBetweenMD2andMD3" in special_wishes_active: +# print ("3RestDaysBetweenMD2andMD3") +# sw_type="3RestDaysBetweenMD2andMD3" +# # for (r2,d2) in getRoundDaysByRound[2] : +# # for (r3,d3) in getRoundDaysByRound[3] +# # print (getDateTimeDay[d2],getDateTimeDay[d3]) +# dpairs = [(d2, d3) for (r2,d2) in getRoundDaysByRound[2] for (r3,d3) in getRoundDaysByRound[3] if getDateTimeDay[d3]-getDateTimeDay[d2]== datetime.timedelta(days=sw_int1[sw_type]+1) ] +# for (d2,d3) in dpairs: +# for t in realteams: +# model2 += home[(t,d2)]+away[(t,d2)]==home[(t,d3)] +away[(t,d3)] +# print (getDateTimeDay[d2],getDateTimeDay[d3]) +# + +if thisSeason.useFeatureStadiums: + stadium_teams = [ t for t in realteams if len(getStadiumsByTeam[t])>0 ] + playInStadium = { (t,d,sp.stadium.id) : pulp.LpVariable('playInStadium_'+str(t)+'_'+str(d)+'_'+str(sp.stadium.id), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in stadium_teams for d in days for sp in getStadiumsByTeam[t] } + + for d in days : + for t in stadium_teams: + print ([ playInStadium[t,d,sp.stadium.id] for sp in getStadiumsByTeam[t] ]) + model2+=lpSum([ playInStadium[t,d,sp.stadium.id] for sp in getStadiumsByTeam[t] ])== home[(t,d)] + for s in stadiums: + print ([ playInStadium[t,d,sp.stadium.id] for sp in getStadiumsByTeam[t] ]) + model2+=lpSum([ playInStadium[sp.team.id,d,s.id] for sp in s.stadiumpreferences.all() ]) <= getStadiumAvailability[(s.id,d)] +tooManyHomesInStadium[(s.id,d)] + + specialObjectives += -1 *lpSum([ prioVal[sp.prio]* playInStadium[t,d,sp.stadium.id] for t in stadium_teams for d in days for sp in getStadiumsByTeam[t] ]) + +print (special_wishes_active) + +if "minHomeAttractivity" in special_wishes_active: + # showAttractivity = min([d.attractivity for d in days]) < max([d.attractivity for d in days]) + sw_type="minHomeAttractivity" + for t in realteams: + specialWishItems[sw_type].append((t)) + specialWishVio[(sw_type,t)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t), lowBound=0, cat=pulp.LpContinuous) + model2 += lpSum([ home[t,d]*getDayById[d]['attractivity'] for d in days ]) >= sw_int1[sw_type] - specialWishVio[(sw_type,t)] + specialObjectives += lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t)] for t in realteams ]) + +if "alwaysFixedRestDaysWhenPossible" in special_wishes_active: + print ("alwaysFixedRestDaysWhenPossible") + sw_type="alwaysFixedRestDaysWhenPossible" + dpairs=[] + for r in rounds: + if r= 0] + african_teams = [t for t in realteams if t_lat[t] < 0] + alwaysTripStarterRounds = [1,8,10,11,12,14,16,18] + + # print ("european_teams", european_teams) + # print ("african_teams", african_teams) + + europeanTripStarter = { (t,r) : pulp.LpVariable('europeanTripStarter_'+str(t)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in african_teams for r in rounds } + for t in african_teams: + # print ("BUILD CONSTRAINT FOR " , getTeamById[t]) + for mm in ["min","max"]: + specialWishItems[sw_type].append((t,mm)) + specialWishVio[(sw_type,t,mm)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+'_'+mm, lowBound=0, cat=pulp.LpContinuous) + + for r in rounds: + if r in alwaysTripStarterRounds: + model2 += lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r]]) == europeanTripStarter[t,r] + # print (r, lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r]])) + else: + model2 += lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r]]) \ + - lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r-1]]) <= europeanTripStarter[t,r] + model2 += europeanTripStarter[t,r] <= lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r]]) + model2 += europeanTripStarter[t,r] <= 1-lpSum([ x[t1,t,rd] for t1 in european_teams for rd in getRoundDaysByRound[r-1]]) + model2 += lpSum([ europeanTripStarter[t,r] for r in rounds ]) <= sw_int1[sw_type] + specialWishVio[(sw_type,t,"max")] + model2 += lpSum([ europeanTripStarter[t,r] for r in rounds ]) >= sw_int2[sw_type] - specialWishVio[(sw_type,t,"min")] + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,mm)] for (t,mm) in specialWishItems[sw_type] ]) + + +if "RecoverBeforeDistantGame" in special_wishes_active: + sw_type="RecoverBeforeDistantGame" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= home[(t,d)] + lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for d2 in nextDays for rd in getRoundDaysByDay[d2] ]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", lpSum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "RecoverAfterDistantGame" in special_wishes_active: + sw_type="RecoverAfterDistantGame" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0 and (thisSeason.league.name != "ICE Hockey League" or getWeekDay[d]=="Fri" ): + if len(nextDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) + lpSum([ home[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", lpSum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "RecoverAfterDistantGame2" in special_wishes_active: + sw_type="RecoverAfterDistantGame2" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0 and (thisSeason.league.name != "ICE Hockey League" or getWeekDay[d]=="Fri" ): + if len(nextDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + # print ("\n",getTeamById[t], ":", [ (getTeamById[t2] , distanceById[t,t2]) for t2 in distantTeams[t] ]) + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) + lpSum([ home[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + # print ("GAMES :", lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ])) + # print ("HOMES :", lpSum([ home[(t,d2)] for d2 in nextDays])) + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + +if "NoSingleDistantGame" in special_wishes_active: + sw_type="NoSingleDistantGame" + distantTeams = { t: [ t2 for t2 in realteams if distanceById[t,t2]> sw_int1[sw_type] and t2!=t] for t in realteams } + for d in days: + otherDays = [ d2 for d2 in days if d!=d2 and getDateTimeDay[d2]-getDateTimeDay[d]<= datetime.timedelta(days=sw_int2[sw_type]-1) and getDateTimeDay[d]-getDateTimeDay[d2]<= datetime.timedelta(days=sw_int2[sw_type]-1) ] + if len(otherDays)>0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for rd in getRoundDaysByDay[d] ]) - lpSum([ x[t1,t,rd] for t1 in distantTeams[t] for d2 in otherDays for rd in getRoundDaysByDay[d2] ]) <= specialWishVio[(sw_type,t,d)] + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + # # print (specialObjectives) + +if "halfSeasonNumGames" in special_wishes_active: + sw_type="halfSeasonNumGames" + specialWishItems[sw_type]=days + sw_nGames = sum([ gameCntr[gm]+0.5*undirectedGameCntr[gm] for gm in games]) + specialIn2ndHalf={} + for d in days: + # variable is 1 if day belongs to second half + sw_days_before = [ d2 for d2 in days if getDateTimeDay[d2]getDateTimeDay[d]] + specialIn2ndHalf[(d)]= pulp.LpVariable('specialIn2ndHalf_'+str(d), lowBound=0, upBound=1, cat=pulp.LpInteger) + specialWishVio[(sw_type,d)]= pulp.LpVariable('specialWishVio_'+sw_type+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + sw_gamesSoFar = lpSum([ home[(t,d2)] for t in teams for d2 in sw_days_before]) + sw_gamesAfter = lpSum([ home[(t,d2)] for t in teams for d2 in sw_days_after]) + if len(sw_gamesSoFar) <= 0.5* len(sw_gamesAfter): + specialIn2ndHalf[d]=0 + elif len(sw_gamesAfter) <= 0.5* len(sw_gamesSoFar): + specialIn2ndHalf[d]=1 + else: + specialIn2ndHalf[(d)]= pulp.LpVariable('specialIn2ndHalf_'+str(d), lowBound=0, upBound=1, cat=pulp.LpInteger) + model2+= specialIn2ndHalf[sw_days_before[-1]] <= specialIn2ndHalf[d] + + model2+= sw_gamesAfter >= 0.5*sw_nGames*(1-specialIn2ndHalf[d]) - specialWishVio[(sw_type,d)] + model2+= sw_gamesSoFar >= 0.5*sw_nGames*(specialIn2ndHalf[d])- specialWishVio[(sw_type,d)] + for t in teams: + sw_home_before = lpSum([ home[(t,d2)] for d2 in sw_days_before]) + sw_away_before = lpSum([ away[(t,d2)] for d2 in sw_days_before]) + sw_home_after = lpSum([ home[(t,d2)] for d2 in sw_days_after]) + sw_away_after = lpSum([ away[(t,d2)] for d2 in sw_days_after]) + model2+= sw_home_after+sw_away_after >= sw_int1[sw_type]*(1-specialIn2ndHalf[d]) - 5*specialWishVio[(sw_type,d)] + model2+= sw_home_after >= sw_int2[sw_type]*(1-specialIn2ndHalf[d]) - 5*specialWishVio[(sw_type,d)] + model2+= sw_home_before+sw_away_before >= sw_int1[sw_type]*(specialIn2ndHalf[d]) - 5*specialWishVio[(sw_type,d)] + model2+= sw_home_before >= sw_int2[sw_type]*(specialIn2ndHalf[d]) - 5*specialWishVio[(sw_type,d)] + + specialObjectives += 10000*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,d)] for d in specialWishItems[sw_type] ]) + +if "RestDaysAfterLateGame" in special_wishes_active: + sw_type="RestDaysAfterLateGame" + for d in days: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + specialWishItems[sw_type]+=[(t,d) for t in realteams] + for t in realteams: + specialWishVio[(sw_type,t,d)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t)+"_"+str(d), lowBound=0, cat=pulp.LpContinuous) + model2+= home_time[(t,d,getIdByTime["Late"])]+away_time[(t,d,getIdByTime["Late"])] + lpSum([ home[(t,d2)]+away[(t,d2)] for d2 in nextDays]) <= 1 + specialWishVio[(sw_type,t,d)] + specialObjectives += 10*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t,d)] for (t,d) in specialWishItems[sw_type] ]) + # print (specialObjectives) + + +if "EveryPotOnceAtHome" in special_wishes_active: + pots=sorted(list(set(t_pot[t] for t in realteams))) + for t in realteams: + for p in pots: + model2+= lpSum([ x[t,t2,rd] for t2 in realteams for rd in roundDays if t_pot[t2] == p] ) <= 1 + # print ( getTeamById[t] , "pot", p , ":", lpSum([ x[t,t2,rd] for t2 in realteams for rd in roundDays if t_pot[t2] == p] ) , " <= 1 " ) + +if "playWeekendsCompletelyHomeOrAway" in special_wishes_active: + sw_type="playWeekendsCompletelyHomeOrAway" + weekendDays = [d for d in days if getWeekDay[d] in ["Fri"]] + specialHomeAwayOnWeekend={} + for d in weekendDays: + nextDays = [ d2 for d2 in days if getDateTimeDay[d]0: + relrd =[ rd for d in getDaysOfPhase[p-1]+getDaysOfPhase[p] for rd in getRoundDaysByDay[d]] + for (t1,t2) in games: + model2+= lpSum( x[(t1,t2,rd)] for rd in relrd ) <= 1 + +standardObjectives=1+gew['Home-/Away']*HawVioTotal\ + +gew['Home-/Away']*3*unpreferredTotal \ + +gew['Pairings']*pairingVioTotal \ + +gew['Availabilities']*blockingVioTotal \ + +gew['Availabilities']*goodHomesTotal \ + +gew['Availabilities']*travelVioTotal \ + +gew['Availabilities']*gamesTooCloseTotal \ + -5*gew['Trips']*tripSavedTotal2 \ + +gew['Breaks']*breakVioTotal \ + +gew['Breaks']*2*break3VioTotal \ + +gew['Breaks']*1*breakImbalanceTotal \ + +5*gew['Encounters']*encVioTotal \ + +5*gew['Encounters']*seedVioTotal \ + +gew['Conferences']*confVioTotal \ + +gew['Availabilities']*tooManyHomesInStadiumTotal \ + +gew['Broadcasting']*broadVioTotal \ + +gew['Derbies']*derbiesMissingTotal \ + +5*competitionVioTotal \ + +1.0*tooManyTop4InRowTotal\ + +fixedGameVioTotal\ + +missingGamesVioTotal\ + +oldScenGamesTotal\ + -0.01*totalAttendance + +if sharedStadiums: + standardObjectives+= lpSum ( prio_weight[p] * useStadiumTimeSlot[(t,d,s)] for (t,d) in t_site_bestTimeSlots.keys() for (p,s) in t_site_bestTimeSlots[(t,d)]) \ + +100000* lpSum([ nonIceGame[(t,d)] for (t,d) in nonIceGame.keys()]) + + +model2+= standardObjectives + +# model2+= fixedGameVioTotal +missingGamesVioTotal +# model2+= fixedGameVioTotal +# print ("TESTING") +# for ttr in x.keys(): +# makeIntVar(x[ttr]) +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) +# print ("TESTING DONE") + + +global_coeff = {t['id'] : int(t['attractivity']) for t in teamObjects } +domestic_coeff = {t['id'] : int(0.1+10*(t['attractivity']-int(t['attractivity']))) for t in teamObjects } + +def quality_of_game(t1, t2): + return global_coeff[t1]*global_coeff[t2] + +def quality_of_game_dom(t1, t2 , country): + if t_country[t1]==country: + return domestic_coeff[t1]*global_coeff[t2] + else: + return global_coeff[t1]*domestic_coeff[t2] + + +print ("Broadcasting " , gew['Broadcasting']) + +if thisSeason.useFeatureBackToBack: + critical_day_pairs = [ (d1,d2) for r in [r1 for r1 in rounds if r1>1] for d1 in getDays[r-1] for d2 in getDays[r] if getDateTimeDay[d2]-getDateTimeDay[d1]==datetime.timedelta(days=1) ] + nextCritical ={ d1 : False for d1 in days} + for (d1,d2) in critical_day_pairs: + nextCritical[d1]=d2 + + badBackToBack= { (t,d1) : pulp.LpVariable('badBackToBack_'+str(t)+"_"+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for (d1,d2) in critical_day_pairs } + back2backBlocks = [] + color_weight = { "Y" : 0.003 , "R" : 0.05 , "X" : 1.0 , } + + +show_TV_markets = False + +# START SPECIAL CONSTRAINTS + +# hat leider nicht geklappt : +# specialOpt = False +# optFileName = "optimize_" + thisSeason.league.name.replace(' ', '_') +# if path.exists("scheduler/"+optFileName+".py"): +# specialOpt = import_module('scheduler.'+optFileName) + +# if specialOpt: +# specialOpt.enhanceModel2(model2) +# with open("scheduler/"+optFileName+".py") as f: exec(f.read()) + +if mathModelName=="NHL": + startDoubleGame= { (t1,t2,r) : pulp.LpVariable('startDoubleGame'+str(t1)+"_"+str(t2)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for (t1,t2) in games for r in rounds if r1300] + # print (bad_travels) + # for (t1,t2,c) in bad_travels: + # print (getTeamById[t1] , " - " , getTeamById[t2]) + + # print(thisSeason.name[-3:]) + + thisCountry ="" + for t in realteams: + thisCountry=t_country[t] + # print (getTeamById[t], t_country[t] , t_conference[t]) + # print (thisCountry) + + for (t1,t2,r) in startDoubleGame.keys(): + model2 += startDoubleGame[(t1,t2,r)] <= x_round[(t1,t2,r)] + model2 += startDoubleGame[(t1,t2,r)] <= x_round[(t1,t2,r+1)] + + specialObjectives+= -100* lpSum( [ startDoubleGame[ttr] for ttr in startDoubleGame.keys()]) + + if thisCountry=="United States": + for (t1,t2) in games: + for r in rounds: + if r>=3: + model2 += x_round[(t1,t2,r-2)]+x_round[(t1,t2,r-1)]+x_round[(t1,t2,r)]<=2 + # print ("no 3 in row " , t1, t2, r-2 , r) + + bad_travels = [ (t1,t2,"X") for t1 in realteams for t2 in realteams if distanceById[t1,t2]>1300 and t_conference[t1]==t_conference[t2]] + # print (len(bad_travels)) + # for (t1,t2,c) in bad_travels: + # print (getTeamById[t1] , " - " , getTeamById[t2]) + + back2backBlocks += [ ([t1],[t2],1.0) for (t1,t2,c) in bad_travels] + + contractable=True + while contractable: + contractable=False + for ((tms1_a,tms2_a,w_a),(tms1_b,tms2_b,w_b)) in [ (b1,b2) for b1 in back2backBlocks for b2 in back2backBlocks if b1!=b2 and b1[2]==b2[2] and t_conference[b1[0][0]]==t_conference[b2[0][0]] ]: + if min([ distanceById[t1,t2] for t1 in tms1_a for t2 in tms2_b]) > 1300 and min([ distanceById[t1,t2] for t1 in tms1_b for t2 in tms2_a]) > 1300 : + # print ("unify ", b1,b2) + back2backBlocks.remove((tms1_a,tms2_a,w_a)) + back2backBlocks.remove((tms1_b,tms2_b,w_b)) + back2backBlocks.append((list(set(tms1_a+tms1_b)), list(set(tms2_a+tms2_b)), w_a)) + contractable=True + break; + + # print (len(back2backBlocks)," back2backBlocks") + # for (tms1,tms2,w) in back2backBlocks: + # print ("") + # print (tms1,tms2,w) + # for t1 in set(tms1): + # print ("from ", getTeamById[t1]) + # for t2 in set(tms2): + # print ("to ", getTeamById[t2]) + # print (len(bad_travels), "bad_travels") + # print (len(back2backBlocks),"back2backBlocks") + + else: + # startDoubleGame + for r in rounds: + if r>=5: + for (t1,t2) in games: + if gameCntr[(t2,t1)]>0 : + model2 += lpSum(x_round[(t1,t2,r2)] + x_round[(t2,t1,r2)] for r2 in [r-4,r-3,r-2,r-1,r])<=3 + else: + model2 += lpSum(x_round[(t1,t2,r2)] for r2 in [r-4,r-3,r-2,r-1,r])<=3 + # if r>2 and r <10: + # model2 += x_round[(t1,t2,r-1)] <= x_round[(t1,t2,r-2)] + x_round[(t1,t2,r)] + + if r>=9: + for t1 in teams : + model2 += lpSum(homeInRound[(t1,r2)] for r2 in rounds if r2>=r-8 and r2<=r)>=1 + model2 += lpSum(awayInRound[(t1,r2)] for r2 in rounds if r2>=r-8 and r2<=r)>=1 + + east_teams = [ t for t in realteams if t_lon[t]>= -87] + center_teams = [ t for t in realteams if -87 > t_lon[t] and t_lon[t]>=-100 ] + west_teams = [ t for t in realteams if t_lon[t]< -113] + back2backBlocks += [ (east_teams, center_teams+west_teams,1.0), (center_teams+west_teams,east_teams,1.0) ] + + +if mathModelName=="Champions Hockey League": + show_TV_markets = True + # toTime={(c,d) : ["n/a"] for c in countries for d in days} + # for d in days: + # for (c,tms) in [ ("SWE", ["18:05/19:05","20:35"]), ("FIN",["19:00"]), + # ("CZE",["17:00 or 17:30", "19:30 or 20:00"]), ("SVK",["17:00 or 17:30", "19:30 or 20:00"]), + # ("HUN" , ["19:00"]), + # ("AUT",["20:20"]), + # ("SUI",["19:45"]), + # ("GER",["18:00"]), + # ("NOR",["18:00"]), + # ("DEN",["18:00"]), + # ("POL",["18:00"]), + # ("SLO",["19:15"]), + # ("FRA",["20:00"]), + # ("SCO",["20:00 or 20:30"]), + # ]: + # toTime[(c,d)]=tms + + # for (cntries,nds,tms) in [ + # (["SWE"],["2022-09-03","2022-09-04"],["14:35","15:05"]), + # (["CZE","SVK"],["2022-09-03","2022-09-04"],["16:00","18:30"]), + # (["CZE","SVK"],["2022-09-10"],["15:00 or 16:00","17:30 or 18:30"]), + # (["CZE","SVK"],["2022-09-11",],["15:00 or 16:00","18:30"]), + # (["HUN"],["2022-09-03","2022-09-04","2022-09-10","2022-09-11"],["18:00"]), + # (["AUT"],["2022-09-02"],["19:30"]),(["AUT"],["2022-09-09"],["18:00"]), + # (["SUI"],["2022-09-01"],["20:00"]), + # (["SUI"],["2022-09-03","2022-09-10"],["14:30/15:00"]), + # (["SUI"],["2022-09-04","2022-09-11"],["16:30"]), + # (["GER"],["2022-09-01","2022-09-02",],["20:15"]), + # (["GER"],["2022-09-03","2022-09-10",],["17:00"]), + # (["GER"],["2022-09-04",],["15:00 or 19:00"]), + # (["GER"],["2022-09-08",],["19:00","20:00"]), + # (["GER"],["2022-09-09",],["16:45"]), + # (["GER"],["2022-09-11",],["?"]), + # (["NOR"],["2022-09-08"],["no pref."]), + # (["NOR"],["2022-10-04","2022-10-05","2022-10-10","2022-10-11",],["18:30"]), + # (["DEN"],["2022-09-02","2022-09-09",],["19:30"]), + # (["DEN"],["2022-09-08"],["no pref."]), + # (["SLO"],["2022-09-08"],["20:00"]), + # (["SLO"],["2022-09-03"],["18:00"]), + # (["SLO"],["2022-09-04"],["17:30"]), + # (["SLO"],["2022-09-08","2022-09-09",],["18:00 or 20:00"]), + # (["SLO"],["2022-09-10","2022-09-11",],["18:00"]), + # ] : + # if getNiceDayRaw[d] in nds: + # for c in cntries: + # toTime[(c,d)]=tms + # else: + # print (getNiceDayRaw[d], nds, getNiceDayRaw[d] in nds) + + # critical_day_pairs_CHL = [ (d1,d2) for d1 in set(getDays[1]+getDays[3]) for d2 in set(getDays[2]+getDays[4]) if getDateTimeDay[d2]-getDateTimeDay[d1]==datetime.timedelta(days=2) ] + + # confusingDays = [ d for d in getDays[1]+getDays[2] if d in getDays[3]+getDays[4]] + # for (t1,t2) in games: + # print (getTeamById[t1], " " ,getTeamById[t2], lpSum([ x[(t1,t2,rd)] for r2 in [1,2,3] for rd in getRoundDaysByRound[r2]]) ) + # model2+= lpSum([ x[(t1,t2,rd)] + x[(t2,t1,rd)] for r2 in [1,2,3] for rd in getRoundDaysByRound[r2]]) <= 1 + # model2+= lpSum([ x[(t1,t2,rd)] + x[(t2,t1,rd)] for r2 in [4,5,6] for rd in getRoundDaysByRound[r2]]) <= 1 + + # for d in confusingDays: + # rnds = [1,2] if t_pot[t]!=5 else [3,4] + # for r2 in rnds: + # if (r2,d) in roundDays: + # setUB(x[(t1,t2,(r2,d))],0) + # # print ("forbidding " ,r2 ,d, " on day " , getNiceDay[d]) + + + # for t in teams : + # # model2+= lpSum([ home[(t,d1)] + away[t,d1] for d1 in confusingDays ]) == 2 + # for (d1,d2) in critical_day_pairs_CHL: + # model2 += home[(t,d1)]==home[(t,d2)] + # model2 += away[(t,d1)]==away[(t,d2)] + # # model2 += homeInRound[(t1,1)] == homeInRound[(t1,2)] + + +if mathModelName=="Florida State League": + notEnoughHomes= { t : pulp.LpVariable('notEnoughHomes_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in teams } + notEnoughAways= { t : pulp.LpVariable('notEnoughAways_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in teams } + for t in teams: + # model2+= lpSum( [ homeInRound[t,r] for r in rounds]) >= 20 + model2+= lpSum( [ home[t,d] for d in days]) >= 70 - notEnoughHomes[t] + model2+= lpSum( [ away[t,d] for d in days]) >= 70 - notEnoughAways[t] + model2+= lpSum( [ home[t,d] for d in days]) <= 70 + notEnoughHomes[t] + model2+= lpSum( [ away[t,d] for d in days]) <= 70 + notEnoughAways[t] + # print ("doing special stuff ", thisSeason.gamesPerRound=="one day") + + + getConference={} + for c in confTeams.keys(): + if len(confTeams[c])>2: + for t in confTeams[c]: + getConference[t]=c + + visited_unbalanced_MINLB= {(t1,t2) : pulp.LpVariable('visited_too_less_'+str(t1)+'_'+str(t2), lowBound = 0, cat = pulp.LpContinuous) for (t1,t2) in games } + for (t1,t2) in games: + # print (t1 , t2, getRoundDaysByRound[1][0], distance[getTeamById[t1],getTeamById[t2]] , getConference[t1]==getConference[t2] ) + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) >=1 + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) >=2- visited_unbalanced_MINLB[(t1,t2)] + model2+= lpSum([x[(t1,t2,getRoundDaysByRound[r][0])] for r in rounds]) <=3+ visited_unbalanced_MINLB[(t1,t2)] + # exit(0) + + # model2+= x[(13859,13860,(1, 26627))] >=1 + + # for r in rounds: + # print (r, getRoundDaysByRound[r] , len(getRoundDaysByRound[r])) + + # visited_too_less_MINLB= { (t1,t2) : not_visited * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games } + travelCost_Driving_MINLB= { (t1,t2,r) : distance[getTeamById[t1],getTeamById[t2]] * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games for r in rounds } + travelCost_Hotel_MINLB = { (t1,t2,r) : len(getRoundDaysByRound[r]) * (getConference[t1]!=getConference[t2]) * x[(t1,t2,getRoundDaysByRound[r][0])] for (t1,t2) in games for r in rounds } + + travelCost_Total_MINLB = lpSum([0.001*travelCost_Driving_MINLB[(t1,t2,r)]+0.2*travelCost_Hotel_MINLB[(t1,t2,r)] for (t1,t2) in games for r in rounds]) + + total_visited_unbalanced_MINLB = lpSum([visited_unbalanced_MINLB[(t1,t2)] for (t1,t2) in games]) + + notEnoughGames_MINLB = lpSum([notEnoughHomes[t] + notEnoughAways[t] for t in teams]) + + # specialObjectives+=travelCost_Total_MINLB+10*visited_too_less_MINLB + specialObjectives+=100*( 0.1*travelCost_Total_MINLB+0.2*total_visited_unbalanced_MINLB+2*notEnoughGames_MINLB) + + +if mathModelName in ["ALPS" ,"ICE Hockey League" , "ICEYSL"]: + + if mathModelName != "ALPS" and False: + firstTwoPhases= [ (r,d) for (r,d) in roundDays if r<=26 ] + secondTwoPhases= [ (r,d) for (r,d) in roundDays if r>= 27 ] + for (t1,t2) in games: + # print (t1 , t2, getRoundDaysByRound[1][0], distance[getTeamById[t1],getTeamById[t2]] , getConference[t1]==getConference[t2] ) + for rrr in [firstTwoPhases, secondTwoPhases]: + model2+= lpSum([x[(t1,t2,rd)] for rd in rrr]) <=1 + + # trips_ICE_raw={ + # 'DEC': [['AVS', 'VIC'],[ 'ZNO', 'BWL'],[ 'IBC', 'G99', 'KAC'],['VSV', 'HKO']], + # 'AVS': [['DEC', 'HCI', 'HCB', 'PUS', 'RBS']], + # 'HCB': [['ZNO', 'VIC'],['IBC', 'AVS']], + # 'G99': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'BWL': [['HCB', 'PUS', 'DEC']], + # 'ZNO': [['HCB', 'PUS' 'DEC', 'HCI']], + # 'VSV': [['HCB', 'PUS'],[ 'DEC', 'HCI'],[ 'VIC', 'AVS'],[ 'VIC', 'IBC', 'ZNO']], + # 'KAC': [['PUS', 'HCB'],[ 'HCI', 'DEC']], + # 'RBS': [], + # 'VIC': [['HCB', 'PUS'],['DEC', 'HCI']], + # 'HCI': [['VIC', 'AVS'],[ 'BWL', 'ZNO'],[ 'BWL', 'IBC'],[ 'VSV', 'KAC', 'HKO']], + # 'IBC': [['HCB', 'PUS'],[ 'DEC', 'HCI'],[ 'HKO', 'VSV']], + # 'HKO': [['HCB', 'PUS'],[ 'DEC', 'HCI']], + # 'PUS': []} + + + # trips_ICE_raw={ + # 'DEC': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ],[ 'IBC', 'G99', 'KAC'],['VSV', 'HKO']], + # 'AVS': [['DEC', 'HCI', 'HCB', 'PUS', 'RBS']], + # 'HCB': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ]], + # 'G99': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'BWL': [['HCB', 'PUS', 'DEC']], + # 'ZNO': [['HCB', 'PUS','DEC', 'HCI']], + # 'VSV': [['HCB', 'PUS', 'DEC', 'HCI'],[ 'VIC', 'AVS', 'VIC', 'IBC', 'ZNO']], + # 'KAC': [['PUS', 'HCB', 'HCI', 'DEC']], + # 'RBS': [], + # 'VIC': [['HCB', 'PUS','DEC', 'HCI']], + # 'HCI': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ],[ 'VSV', 'KAC', 'HKO']], + # 'IBC': [['HCB', 'PUS', 'DEC', 'HCI'],[ 'HKO', 'VSV']], + # 'HKO': [['HCB', 'PUS', 'DEC', 'HCI']], + # 'PUS': [['BWL', 'IBC' , 'AVS' , 'ZNO' , 'VIC' ]]} + + + trips_ICE_raw={ + 'DEC': [['AVS', 'VIC'],['IBC', 'ZNO'],['G99', 'HKO']], + 'AVS': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HCB': [['AVS', 'VIC'],['IBC', 'ZNO']], + 'G99': [], + 'BWL': [], + 'ZNO': [['DEC', 'HCI'],['HCB', 'PUS']], + 'VSV': [], + 'KAC': [], + 'RBS': [], + 'VIC': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HCI': [['AVS', 'VIC'],['IBC', 'ZNO']], + 'IBC': [['DEC', 'HCI'],['HCB', 'PUS']], + 'HKO': [], + 'PUS': [['AVS', 'VIC'],['IBC', 'ZNO']]} + + # importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "AVS", "HCB", "DEC", "PUS", "IBC" , "HCI" ] ] + # importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "AVS", "HCB", "DEC", "PUS", "IBC" , "HCI" , "VIC" ] ] + + firstRoundOfChristmas=30 + lastRoundOfChristmas=36 + badweekdayGames=0 + + if mathModelName == "ALPS" : + trips_ICE_raw={ + 'EHC': [['JES', 'KFT'],['VCS', 'SWL']], + 'ECB': [['JES', 'KFT'],['VCS', 'SWL']], + 'VEU': [['JES', 'KFT'],['VCS', 'SWL']], + 'VCS': [['ECB', 'EHC'],['VEU', 'KEC'],['ASH', 'SGC', 'RIT', 'GHE', 'WSV', 'FAS', 'HCM']], + 'JES': [['ECB', 'EHC'], ['VEU', 'KEC']], + 'KFT': [['ECB', 'EHC'], ['VEU', 'KEC']], + 'ASH': [['VCS', 'SWL']], + 'SGC': [['VCS', 'SWL']], + 'RIT': [['VCS', 'SWL']], + 'GHE': [['VCS', 'SWL']], + 'WSV': [['VCS', 'SWL']], + 'FAS': [['VCS', 'SWL']], + 'HCM': [['VCS', 'SWL']] + } + badweekdayGames = lpSum([ distanceById[t1,t2]* distanceById[t1,t2] * distanceById[t1,t2] /8000000 * x[t1,t2,(r,d)] for (t1,t2) in games for (r,d) in roundDays if getWeekDay[d]=="Thu" and distanceById[t1,t2]>250 ]) + firstRoundOfChristmas=25 + lastRoundOfChristmas=29 + firstRoundOfChristmas=27 + lastRoundOfChristmas=28 + + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "EHC", "ECB", "VEU", "VCS", "JES", "KFT", "ASH", "SGC", "RIT", "GHE", "WSV", "FAS", "HCM" ] ] + + if mathModelName == "ICE Hockey League" : + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in ["HCB", "AVS", "DEC", "PUS", "IBC" , "ZNO" , "HCI" , "VIC"] ] + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in [ "DEC", "AVS", "HCB", "VIC", "HCI", "IBC", "PUS"] ] + + + if mathModelName == "ICEYSL" : + trips_ICE_raw={ t_shortname[t] : [] for t in teams } + trips_ICE_raw["HCI"] = [['NHA', 'VSV'],['IHC', 'OHE'], ['EAS', 'EAO']] + importantTravellers = [getTeamIdByShortName[ts2] for ts2 in ["HCI"] ] + badweekdayGames = lpSum([(x[t1,t2,(r,d)]) for (t1,t2) in games for (r,d) in roundDays if getWeekDay[d] in [ "Wed", "Thu" ] and distanceById[t1,t2]>200 ]) + + short_dist = { t : sorted([ (distanceById[t,t2] ,t2) for t2 in teams if t2!=t ]) for t in teams } + maxDist4 = { t : max(210, short_dist[t][3][0]) for t in teams } + + # for t in teams : + # print (getTeamById[t] , maxDist4[t], short_dist[t]) + # for ds,t2 in short_dist[t]: + # print (" - " , getTeamById[t2] , ds) + + + toofarForXmas = [ (t1,t2,(r,d)) for (t1,t2) in games for (r,d) in roundDays if r>= firstRoundOfChristmas and (r<=lastRoundOfChristmas or r>=51 ) and distanceById[t1,t2]> maxDist4[t2]+1 ] + wayToofarForXmas = [ (t1,t2,(r,d)) for (t1,t2) in games for (r,d) in roundDays if r>= firstRoundOfChristmas and r<=lastRoundOfChristmas and distanceById[t1,t2]> 300 ] + + # kac dec feh sind wichtig + # vci 2 trips hcidec pusboz + + cntr = 0 + trips_ICE = {} + for ts in trips_ICE_raw.keys(): + t= getTeamIdByShortName[ts] + for tp in trips_ICE_raw[ts]: + tps = [getTeamIdByShortName[ts2] for ts2 in tp] + trips_ICE[cntr]=(t,tps) + # print (trips_ICE) + print ("TRIP", cntr, tp, tps) + cntr+=1 + # for t2 in tps: + # print (" - " , getTeamById [t2] , " " , distanceById[t,t2] , "km") + print () + + print (trips_ICE) + + important_trips_ICE = [ cn for cn in trips_ICE.keys() if trips_ICE[cn][0] in importantTravellers ] + trips_ICE_by_traveller ={t: [ cn for cn in trips_ICE.keys() if trips_ICE[cn][0] == t ] for t in teams } + print (trips_ICE_by_traveller) + + + day1pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=1) and getRoundByDay[d1] != getRoundByDay[d2] ] + + toughWeekend_ICE = { (t,d1,d2) : pulp.LpVariable('toughWeekend_ICE_'+str(t)+'_'+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for t in realteams for (d1,d2) in day1pairs } + + for d1,d2 in day1pairs: + print (getNiceDay[d1], getNiceDay[d2]) + for t in realteams: + print (t,d1,d2 , getTeamById[t], getNiceDay[d1],getNiceDay[d2]) + model2+= home[(t,d1)] + lpSum([x[(t1,t,rd)] for t1 in teams for rd in getRoundDaysByDay[d2] if distanceById[t,t1]>150] ) <=1+toughWeekend_ICE[t,d1,d2] + model2+= home[(t,d2)] + lpSum([x[(t1,t,rd)] for t1 in teams for rd in getRoundDaysByDay[d1] if distanceById[t,t1]>150] ) <=1+toughWeekend_ICE[t,d1,d2] + + traveling_ICE = { (tr,d1,d2) : pulp.LpVariable('traveling_ICE_'+str(tr)+'_'+str(d1) , lowBound = 0, cat = pulp.LpContinuous) for tr in trips_ICE.keys() for (d1,d2) in day1pairs } + notEnoughTravel = { t : pulp.LpVariable('important_travel_'+str(t) , lowBound = 0, cat = pulp.LpContinuous) for t in importantTravellers } + + for (tr,d1,d2) in traveling_ICE.keys(): + t2,tms = trips_ICE[tr] + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d1]]) + # if t2 in [getTeamIdByShortName["DEC"] ,getTeamIdByShortName["HCB"]] and nextDay[d2]!=-1: + if t2 in [] and nextDay[d2]!=-1: + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d2]+getRoundDaysByDay[nextDay[d2]]]) + else: + model2+= traveling_ICE[(tr,d1,d2)] <= lpSum([x[(t1,t2,rd)] for t1 in tms for rd in getRoundDaysByDay[d2]]) + + if mathModelName == "ALPS": + for t in realteams: + for d1,d2 in day1pairs: + model2+= away[(t,d1)] <= lpSum([ traveling_ICE[(tr,d1,d2)] for tr in trips_ICE_by_traveller[t] ]) + toughWeekend_ICE[t,d1,d2] + print ( "away ",t , getTeamById[t] , " only if travelling ", getNiceDay[d1] , d2) + + for t in importantTravellers: + model2+= lpSum([ traveling_ICE[(tr,d1,d2)] for tr in trips_ICE_by_traveller[t] for (d1,d2) in day1pairs]) >=2 - notEnoughTravel[t] + + notEnoughTravelTotal = lpSum([ notEnoughTravel[t] for t in notEnoughTravel.keys() ]) + badXmas = lpSum([ 0.01*distanceById[t1,t2] *x[(t1,t2,rd)] for (t1,t2,rd) in toofarForXmas ]) + 10 * lpSum([ x[ttrd] for ttrd in wayToofarForXmas ]) + toughWeekends = lpSum([ toughWeekend_ICE[tdd] for tdd in toughWeekend_ICE.keys() ]) + + # specialObjectives += 0.2* gew['Trips'] * (-100* lpSum([traveling_ICE[tdd] for tdd in traveling_ICE.keys()]) - 300 * lpSum([traveling_ICE[(tr,d1,d2)] for (tr,d1,d2) in traveling_ICE.keys() if tr in important_trips_ICE ])) + 100*badXmas + 1000 *toughWeekends + specialObjectives += ( 0.2* gew['Trips'] * ( + -0 * lpSum([traveling_ICE[tdd] for tdd in traveling_ICE.keys()]) + - 400 * lpSum([traveling_ICE[(tr,d1,d2)] for (tr,d1,d2) in traveling_ICE.keys() if tr in important_trips_ICE ]) + ) + + 100 * badXmas + + 1000 * toughWeekends + + 1000*notEnoughTravelTotal + # + 50*badweekdayGames + + 200*badweekdayGames + ) + + for (t,r,c) in tripToCluster.keys(): + model2 += tripToCluster[(t,r,c)] == 0 + + +if mathModelName=="NBA": + + for (t1,t2,d,channel) in seedTV: + if previousDay[d]!=-1: + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Late"])] for (t3,t4) in games for rd in getRoundDaysByDay[previousDay[d]] if t1 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Early"])] for rd in getRoundDaysByDay[d]]) <=1 + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Late"])] for (t3,t4) in games for rd in getRoundDaysByDay[previousDay[d]] if t2 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Early"])] for rd in getRoundDaysByDay[d]]) <=1 + if nextDay[d]!=-1: + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Early"])] for (t3,t4) in games for rd in getRoundDaysByDay[nextDay[d]] if t1 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Late"])] for rd in getRoundDaysByDay[d]]) <=1 + model2 +=lpSum([ x_time[(t3,t4,rd,getIdByTime["Early"])] for (t3,t4) in games for rd in getRoundDaysByDay[nextDay[d]] if t2 in [t3,t4] ])+ lpSum([ x_time[(t1,t2,rd,getIdByTime["Late"])] for rd in getRoundDaysByDay[d]]) <=1 + + badRepeater= { (t,r) : pulp.LpVariable('badRepeater_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + tooLongTrip_NBA= { (t,r) : pulp.LpVariable('tooLongTrip_NBA_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + eastWestTrip_NBA= { (t,r) : pulp.LpVariable('eastWestTrip_NBA_'+str(t)+"_"+str(r) , lowBound = 0, cat = pulp.LpContinuous) for t in teams for r in rounds } + + if nRounds >= 100: + for t in realteams: + if r>=3: + model2 += break3InRound[(t,r)] +2>= lpSum([ homeInRound[(t,r2)] + awayInRound[(t,r2)] for r2 in [r-2,r-1,r]]) + + userepeater= False + if userepeater: + for t in realteams: + print ("building repeater contraints for " , t) + for r in rounds: + # forbid same opponents on successive days + for t2 in realteams: + if t2: + model2 += lpSum([ (x[(t,t2,rd )]+x[(t2,t,rd)]) for rd in getRoundDaysByRound[r-2]+getRoundDaysByRound[r-1]+getRoundDaysByRound[r] ]) <= 1 + badRepeater[(t,r)] + # badRepeater[(t,r)].upBound=0 + if r>4: + model2 += lpSum([ home [(t,d )] for (r2,d) in roundDays if r2>=r-4 and r2<=r ]) >= 1 - tooLongTrip_NBA[(t,r)] + model2 += lpSum([ away [(t,d )] for (r2,d) in roundDays if r2>=r-4 and r2<=r ]) >= 1 - tooLongTrip_NBA[(t,r)] + + b2bmatrix = {"BOS": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "TOR": {"BOS": "Y", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "Y", "CHA": "Y", "ATL": "Y", "ORL": "R", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "NYK": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "BKN": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "R", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "PHI": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "G", "MEM": "Y", "NOP": "Y", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "WAS": {"BOS": "G", "TOR": "Y", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "Y", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CHA": {"BOS": "G", "TOR": "Y", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "Y", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "ATL": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "Y", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "ORL": {"BOS": "Y", "TOR": "R", "NYK": "Y", "BKN": "Y", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "Y", "CLE": "Y", "IND": "G", "CHI": "R", "MIL": "R", "MEM": "Y", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "Y", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIA": {"BOS": "Y", "TOR": "R", "NYK": "Y", "BKN": "Y", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "Y", "CLE": "Y", "IND": "Y", "CHI": "R", "MIL": "R", "MEM": "Y", "NOP": "G", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "R", "MIN": "R", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DET": {"BOS": "G", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "Y", "NOP": "R", "HOU": "R", "DAL": "R", "SAS": "R", "OKC": "Y", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CLE": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "R", "DAL": "Y", "SAS": "R", "OKC": "Y", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "IND": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "Y", "DAL": "Y", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "X", "UTA": "X", "PHX": "X", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "CHI": {"BOS": "G", "TOR": "G", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "R", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIL": {"BOS": "G", "TOR": "G", "NYK": "G", "BKN": "G", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MEM": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "G", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "NOP": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "Y", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "G", "MIA": "G", "DET": "G", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "R", "UTA": "R", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "HOU": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "Y", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "Y", "CLE": "Y", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "R", "UTA": "R", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DAL": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "Y", "CLE": "G", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "Y", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "SAS": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "Y", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "R", "CLE": "Y", "IND": "G", "CHI": "Y", "MIL": "Y", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "Y", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "OKC": {"BOS": "R", "TOR": "R", "NYK": "R", "BKN": "R", "PHI": "R", "WAS": "R", "CHA": "G", "ATL": "G", "ORL": "Y", "MIA": "Y", "DET": "G", "CLE": "Y", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "G", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "Y", "PHX": "Y", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "MIN": {"BOS": "Y", "TOR": "Y", "NYK": "Y", "BKN": "Y", "PHI": "G", "WAS": "Y", "CHA": "G", "ATL": "G", "ORL": "R", "MIA": "R", "DET": "G", "CLE": "G", "IND": "G", "CHI": "G", "MIL": "G", "MEM": "G", "NOP": "Y", "HOU": "Y", "DAL": "G", "SAS": "Y", "OKC": "G", "MIN": "G", "DEN": "Y", "UTA": "Y", "PHX": "R", "LAC": "X", "LAL": "X", "GSW": "X", "SAC": "X", "POR": "X"}, + "DEN": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "Y", "MIL": "G", "MEM": "Y", "NOP": "Y", "HOU": "G", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "R", "LAL": "R", "GSW": "Y", "SAC": "Y", "POR": "R"}, + "UTA": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "R", "MIL": "Y", "MEM": "Y", "NOP": "R", "HOU": "Y", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "G", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "PHX": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "R", "MIL": "R", "MEM": "R", "NOP": "R", "HOU": "Y", "DAL": "G", "SAS": "G", "OKC": "G", "MIN": "Y", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "Y"}, + "LAC": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "LAL": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "GSW": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "Y", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "SAC": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "G", "UTA": "G", "PHX": "G", "LAC": "G", "LAL": "G", "GSW": "G", "SAC": "G", "POR": "G"}, + "POR": {"BOS": "X", "TOR": "X", "NYK": "X", "BKN": "X", "PHI": "X", "WAS": "X", "CHA": "X", "ATL": "X", "ORL": "X", "MIA": "X", "DET": "X", "CLE": "X", "IND": "X", "CHI": "X", "MIL": "X", "MEM": "X", "NOP": "X", "HOU": "X", "DAL": "X", "SAS": "X", "OKC": "X", "MIN": "X", "DEN": "Y", "UTA": "G", "PHX": "G", "LAC": "Y", "LAL": "Y", "GSW": "G", "SAC": "G", "POR": "G"} + } + + bad_travels = [ (getTeamIdByName[teamByShort[t2]],getTeamIdByName[teamByShort[t1]],b2bmatrix[t1][t2]) for t1 in b2bmatrix.keys() for t2 in b2bmatrix.keys() if b2bmatrix[t1][t2]!="G"] + + east_teams = [ t for t in realteams if t_lon[t]>= -87] + center_teams = [ t for t in realteams if -87 > t_lon[t] and t_lon[t]>=-100 ] + mountain_teams = [ t for t in realteams if -100 > t_lon[t] and t_lon[t]>=-113 ] + west_teams = [ t for t in realteams if t_lon[t]< -113] + + # print (len (east_teams)) + # print (len (center_teams)) + # print (len (mountain_teams)) + # for t in mountain_teams: + # print (" mt " , getTeamById[t]) + # print (len (west_teams)) + + + back2backBlocks += [ (east_teams, mountain_teams+west_teams,1.0), (mountain_teams+west_teams,east_teams,1.0), + (center_teams,west_teams,1.0) ,(west_teams,center_teams,1.0), + ([getTeamIdByName[teamByShort[ts]] for ts in ["MEM","NOP","HOU","DAL","SAS","OKC","MIN"]],[getTeamIdByName[teamByShort[ts]] for ts in ["BOS", "TOR", "NYK", "BKN", "PHI", "WAS"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["BOS", "TOR", "NYK", "BKN", "PHI", "WAS"]], [getTeamIdByName[teamByShort[ts]] for ts in ["NOP","HOU","DAL","SAS","OKC"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["DEN", "UTA", "PHX"]], [getTeamIdByName[teamByShort[ts]] for ts in ["CHI","MIL","MEM","NOP","HOU"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["ORL", "MIA"]], [getTeamIdByName[teamByShort[ts]] for ts in ["TOR","MIN"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["NOP", "HOU", "DAL", "SAS"]], [getTeamIdByName[teamByShort[ts]] for ts in ["DET","CLE"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in ["TOR", "CHI", "MIL", "MIN","OKC"]], [getTeamIdByName[teamByShort[ts]] for ts in ["ORL","MIA"]],0.05), + ([getTeamIdByName[teamByShort[ts]] for ts in [ "CHI", "MIL", "MEM","NOP"]], [getTeamIdByName[teamByShort[ts]] for ts in ["UTA","PHX"]],0.05), + ] + + playingAwayEast = { (t,r) : lpSum([ x[(t1,t,rd )] for rd in getRoundDaysByRound[r] for t1 in east_teams ]) for r in rounds for t in realteams } + playingAwayWest = { (t,r) : lpSum([ x[(t1,t,rd )] for rd in getRoundDaysByRound[r] for t1 in west_teams+mountain_teams ]) for r in rounds for t in realteams } + + for t in realteams: + for r in rounds: + if r>1: + model2 += playingAwayEast[(t,r-1)] + playingAwayWest[(t,r)] <=1 + eastWestTrip_NBA[(t,r)] + model2 += playingAwayWest[(t,r-1)] + playingAwayEast[(t,r)] <=1 + eastWestTrip_NBA[(t,r)] + + + for t in teams: + for r in rounds: + if r>2: + model2 += awayInRound[(t,r-1)] <= awayInRound[(t,r-2)] + awayInRound[(t,r)] + 3*eastWestTrip_NBA[(t,r)] + + + for t in west_teams+mountain_teams: + for r in rounds: + if r>2: + model2 += playingAwayEast[(t,r-1)] <= playingAwayEast[(t,r-2)] + playingAwayEast[(t,r)] + 0.3*eastWestTrip_NBA[(t,r)] + + for t in east_teams: + for r in rounds: + if r>2: + model2 += playingAwayWest[(t,r-1)] <= playingAwayWest[(t,r-2)] + playingAwayWest[(t,r)] + 0.3*eastWestTrip_NBA[(t,r)] + + + # for t1 in teams: + # print ("bad travel in ", getTeamById[t1] , bad_travel_in[t1] ) + # print ("bad travel out ", getTeamById[t1] , bad_travel_out[t1] ) + + # distant_teams = { t: [] for t in realteams} + + # for t in east_teams: + # print ("EAST " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= mountain_teams+west_teams + # for t in center_teams: + # print ("CENTER " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= west_teams + # for t in mountain_teams: + # print ("MOUNTAIN " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= east_teams + # for t in west_teams: + # print ("WEST " , getTeamById[t], t_lon[t]) + # # distant_teams[t]= center_teams+east_teams + + # print ("building back to back contraints for " , t) + badRepeater_Total_NBA = lpSum([badRepeater[(t,r)] for t in teams for r in rounds]) + tooLongTrip_Total_NBA = lpSum([tooLongTrip_NBA[(t,r)] for t in teams for r in rounds]) + eastWestTrip_Total_NBA = lpSum([eastWestTrip_NBA[(t,r)] for t in teams for r in rounds]) + specialObjectives+=10*( badRepeater_Total_NBA)+100*tooLongTrip_Total_NBA + 100*eastWestTrip_Total_NBA + + +if mathModelName=="UCL24" : + isUEL = thisSeason.nicename[:3]=="UEL" + show_TV_markets = True + + if runMode=="New" and not playSwissTable: + # solver warm-start with a feasible assignment + premodel_time = time.time() + res_objective, res_games = ucl24_basicmodell(solver,thisScenario,fixedGames,fixedGames2) + + if user_name == "Simulator": + sol_simulation = { + 'ucl24_objective':res_objective, + 'ucl24_time':time.time()-premodel_time + } + + for game in res_games: + # x_round[game].lowBound = 1 + setLB(x_round[game],1) + + + uefa_games = [ (t1,t2) for t1 in teams for t2 in teams if t14 ] + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for d in days } + best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,t2) in uefa_interesting_games for d in days } + best_global_early = { d : pulp.LpVariable('best_global_early_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for d in days } + + uefa_interesting_domestic_games = {c:[] for c in NAS15} + uefa_interesting_domestic_teams = {c:set([]) for c in NAS15} + + topDomDist = { c : 0 for c in countries } + lowDomDist = { c : 0 for c in countries } + + tuesdays = [ d for d in days if getWeekDay[d]=="Tue"] + wednesdays = [ d for d in days if getWeekDay[d]=="Wed"] + if "SpreadDomTopTueWed" in special_wishes_active or "SpreadDomLowTueWed" in special_wishes_active or "AvoidTwoDomTopMD" in special_wishes_active: + topDomesticGames = { c: [] for c in countries} + lowDomesticGames = { c: [] for c in countries} + for (t1,t2) in uefa_games: + if t_pot[t1]==1: + topDomesticGames[t_country[t2]].append((t1,t2)) + if t_pot[t2]==1: + topDomesticGames[t_country[t1]].append((t1,t2)) + if t_pot[t1]==4: + lowDomesticGames[t_country[t2]].append((t1,t2)) + if t_pot[t2]==4: + lowDomesticGames[t_country[t1]].append((t1,t2)) + + if "SpreadDomTopTueWed" in special_wishes_active: + topDomDist = { c : pulp.LpVariable('topDomDist_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for c in countries } + for c in countries: + model2+= lpSum([x[t1,t2,rd] for (t1,t2) in topDomesticGames[c] for d in tuesdays for rd in getRoundDaysByDay[d]]) >= 0.5*len(topDomesticGames[c]) - topDomDist[c] + model2+= lpSum([x[t1,t2,rd] for (t1,t2) in topDomesticGames[c] for d in wednesdays for rd in getRoundDaysByDay[d]]) >= 0.5*len(topDomesticGames[c]) - topDomDist[c] + + if "SpreadDomLowTueWed" in special_wishes_active: + lowDomDist = { c : pulp.LpVariable('lowDomDist_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for c in countries } + for c in countries: + model2+= lpSum([x[t1,t2,rd] for (t1,t2) in lowDomesticGames[c] for d in tuesdays for rd in getRoundDaysByDay[d]]) >= 0.5*len(lowDomesticGames[c]) - lowDomDist[c] + model2+= lpSum([x[t1,t2,rd] for (t1,t2) in lowDomesticGames[c] for d in wednesdays for rd in getRoundDaysByDay[d]]) >= 0.5*len(lowDomesticGames[c]) - lowDomDist[c] + + if "AvoidTwoDomTopMD" in special_wishes_active: + twoDomTop = { c : pulp.LpVariable('twoDomTop_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for c in countries } + if sw_int1["AvoidTwoDomTopMD"] in rounds: + for c in countries: + model2+= lpSum([x[t1,t2,rd] for (t1,t2) in topDomesticGames[c] for rd in getRoundDaysByRound[sw_int1["AvoidTwoDomTopMD"]]]) <= 1 + twoDomTop[c] + print (" " , c , " NO MORE THAN 1 TOP GAME IN ROUND " , sw_int1["AvoidTwoDomTopMD"] ,":" , [x[t1,t2,rd] for (t1,t2) in topDomesticGames[c] for rd in getRoundDaysByRound[sw_int1["AvoidTwoDomTopMD"]]] , [(getTeamById[t1],getTeamById[t2],rd , x[t1,t2,rd] ) for (t1,t2) in topDomesticGames[c] for rd in getRoundDaysByRound[sw_int1["AvoidTwoDomTopMD"]]] ) + + # for c in countries: + # print (c) + # for (t1,t2) in topDomesticGames[c]: + # print (" +++ ", getTeamById[t1] , " - " , getTeamById[t2] ) + # for (t1,t2) in lowDomesticGames[c]: + # print (" --- ", getTeamById[t1] , " - " , getTeamById[t2] ) + + for (t1,t2) in uefa_games: + if quality_of_game_dom(t1, t2,t_country[t1])>=10 and t_country[t1] in NAS15: + uefa_interesting_domestic_games[t_country[t1]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t1]].add(t1) + if quality_of_game_dom(t2, t1,t_country[t2])>=10 and t_country[t2] in NAS15: + uefa_interesting_domestic_games[t_country[t2]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t2]].add(t2) + + print ("cool domestic ", uefa_interesting_domestic_games) + for c in NAS15: + print (c) + for (t1,t2) in uefa_interesting_domestic_games[c]: + print (getTeamById[t1], getTeamById[t2] , quality_of_game_dom(t1, t2, c),quality_of_game_dom(t2, t1, c)) + for t1 in uefa_interesting_domestic_teams[c]: + print (getTeamById[t1]) + # exit(0) + best_dom= {(c,t1,t2,d) : pulp.LpVariable('best_dom_'+str(c)+'_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for c in NAS15 for (t1,t2) in uefa_interesting_domestic_games[c] for d in days } + best_dom_early = { (c,d) : pulp.LpVariable('best_dom_early_'+str(d) + '_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for d in days for c in NAS15 } + + + if thisSeason.nicename[:3]=="UCL" : + for (t1,t2) in uefa_interesting_games : + for (r,d) in roundDays: + model2 += best_glo[(t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games]) == 1 + rd =getRoundDaysByDay[d] + for t in teams: + model2+= lpSum( [ quality_of_game(t1,t2)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_games if t in [t1,t2]]) \ + <= lpSum( [ best_glo[(t1,t2,d)]*quality_of_game(t1,t2) for (t1,t2) in uefa_interesting_games ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_games : + rd= getRoundDaysByDay[d][0] + model2 += best_glo[(t1,t2,d) ] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_global_early[d] + + + # TODO + # Avoid playing early the match with the highest domestic coefficient in NAs 1-5 if it is 10 or more à priority B + # nas 5 fran + + for c in NAS15: + for (t1,t2) in uefa_interesting_domestic_games[c]: + for (r,d) in roundDays: + model2 += best_dom[(c,t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_dom[(c,t1,t2,d)] for (t1,t2) in uefa_interesting_domestic_games[c]]) <= 1 + rd =getRoundDaysByDay[d] + for t in uefa_interesting_domestic_teams[c]: + model2+= lpSum( [ quality_of_game_dom(t1,t2,c)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_domestic_games[c] if t in [t1,t2]]) \ + <= lpSum( [ best_dom[(c,t1,t2,d)]*quality_of_game_dom(t1,t2,c) for (t1,t2) in uefa_interesting_domestic_games[c] ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_domestic_games[c]: + rd= getRoundDaysByDay[d][0] + model2 += best_dom[(c,t1,t2,d)] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_dom_early[(c,d)] + + specialObjectives= (0.01*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys()]) + # Avoid early matches with a global coefficient of 16 and more (60) + +0.6*0.2*sw_prio["AvoidEarlyGlobal"]*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)>=sw_int1["AvoidEarlyGlobal"]]) + # Maximise the average global coefficient of early matches (should stay under 16) à priority C + -0.1*sw_prio["MaxAverageEarlyGlobal"]*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)=sw_int1["AvoidEarlyDomestic"] or quality_of_game_dom(t1,t2,t_country[t2])>=sw_int1["AvoidEarlyDomestic"] ]) + -0.1*lpSum([quality_of_game(t1,t2) * best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games for d in days ]) + +0.5*0.04*sw_prio["AvoidEarlyHighDomestic"]*lpSum([best_global_early[d] for d in days ]) + +5*0.04*sw_prio["AvoidEarlyHighGlobal"]*lpSum([best_dom_early[(c,d)] for c in NAS15 for d in days ]) + ) + + if "SpreadDomTopTueWed" in special_wishes_active: + specialObjectives+= 2*0.04*sw_prio["SpreadDomTopTueWed"]*lpSum([topDomDist[c] for c in countries ]) + + if "SpreadDomLowTueWed" in special_wishes_active: + specialObjectives+= 2*0.04*sw_prio["SpreadDomLowTueWed"]*lpSum([lowDomDist[c] for c in countries ]) + + if "AvoidTwoDomTopMD" in special_wishes_active: + specialObjectives+= 2*0.04*sw_prio["AvoidTwoDomTopMD"]*lpSum([twoDomTop[c] for c in countries ]) + + + model2+= standardObjectives +specialObjectives + + + if "UCL24Patterns" in special_wishes_active: + pos_patterns = [ + [1,0,1,1,0,0,1,0 ], + [1,0,1,0,1,0,1,0 ], + [1,0,1,0,1,0,0,1 ], + [1,0,1,0,0,1,1,0 ], + [1,0,1,0,0,1,0,1 ], + [1,0,0,1,1,0,1,0 ], + [1,0,0,1,1,0,0,1 ], + [1,0,0,1,0,1,1,0 ], + [1,0,0,1,0,1,0,1 ], + ] + + # add reverse patterns (all those starting with 0,1 ) + pos_patterns+=[ [ 1-p[i] for i in range(8)] for p in pos_patterns ] + print ("pos_patterns",pos_patterns) + + positions = range (len(pos_patterns)) + # pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for p in positions } + pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in teams for p in positions } + + for t in teams: + model2 += lpSum( [ pos[t,p] for p in positions ]) ==1 + for r in rounds: + model2 += homeInRound[t,r]<= lpSum( [ pos[t,p] for p in positions if pos_patterns[p][r-1]==1 ]) + model2 += awayInRound[t,r]<= lpSum( [ pos[t,p] for p in positions if pos_patterns[p][r-1]==0 ]) + + + if runMode!='Improve' and "UCL24Patterns" in special_wishes_active and "extraUEFAPatternRun" in special_wishes_active : + for p in positions: + if pos_patterns[p][0]==1: + for t in teams: + pos[t,p].cat = pulp.LpInteger + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.2, TimeLimit=120, msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds=60, keepFiles=True, options=["THREADS=12,DEFAULTALG=4,DETERMINISTIC=0,CUTSTRATEGY=0"])) + + for p in positions: + for t in teams: + if pos[(t,p)].value()>0.9: + model2 += pos[(t,p)] == pos[(t,p)].value() + print(p,t, pos_patterns[p]) + + +if mathModelName=="UEL24" : + + if runMode=="New" and not playSwissTable: + # solver warm-start with a feasible assignment + premodel_time = time.time() + res_objective, res_games = ueluecl24_basicmodell_v2(solver,thisScenario,fixedGames,fixedGames2) + + if user_name == "Simulator": + sol_simulation = { + 'ucl24_objective':res_objective, + 'ucl24_time':time.time()-premodel_time + } + + for game in res_games: + # x_round[game].lowBound = 1 + setLB(x_round[game],1) + + isUEL = True + show_TV_markets = True + + positions = [] + + uefa_games = games + uefa_interesting_games = [ (t1,t2) for (t1,t2) in uefa_games if quality_of_game(t1,t2)>4 ] + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for d in days } + best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,t2) in uefa_interesting_games for d in days } + best_global_early = { d : pulp.LpVariable('best_global_early_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for d in days } + + uefa_interesting_domestic_games = {c:[] for c in NAS15} + uefa_interesting_domestic_teams = {c:set([]) for c in NAS15} + + for (t1,t2) in uefa_games: + if quality_of_game_dom(t1, t2, t_country[t1])>=10 and t_country[t1] in NAS15: + uefa_interesting_domestic_games[t_country[t1]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t1]].add(t1) + if quality_of_game_dom(t1, t2, t_country[t2])>=10 and t_country[t2] in NAS15: + uefa_interesting_domestic_games[t_country[t2]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t2]].add(t2) + + for cid in confName.keys(): + if confName[cid]=="UEL": + for t in confTeams[cid]: + # print ("UEL", getTeamById[t]) + # model2 += homeInRound[t,1]+homeInRound[t,2]<=1 + # model2 += homeInRound[t,7]+homeInRound[t,8]<=1 + for d in days: + if getDayById[d]['day']=="2024-12-19": + # print (" - not gonna play home on ", getNiceDay[d]) + model2 += home[(t,d)]==0 + + if confName[cid]=="UECL": + for t in confTeams[cid]: + # print ("UECL", getTeamById[t]) + model2 += homeInRound[t,1]==0 + # model2 += homeInRound[t,2]+homeInRound[t,3]<=1 + # model2 += homeInRound[t,4]+homeInRound[t,5]<=1 + # model2 += homeInRound[t,6]+homeInRound[t,7]<=1 + model2 += homeInRound[t,8]==0 + for d in days: + if getDayById[d]['day'] in ["2024-09-25", "2024-09-26", "2025-01-23", "2025-01-30"]: + # print (" - not gonna play home on ", getNiceDay[d]) + model2 += home[(t,d)]==0 + + +if mathModelName=="UEFA" : + + isUEL = thisSeason.nicename[:3]=="UEL" + + for sw in ["UsePosition14", "UseClassicWeekdayPatterns", "Sync1256", "UseClassicSchedules", "UseRedBlueGroups", "MaxAverageEarlyGlobal", "AvoidEarlyDomestic", "AvoidEarlyGlobal", "AvoidEarlyHighDomestic", "AvoidEarlyHighGlobal", "EachOnce", "3breaks", "firstlegs", "NoThreeTuesdaysWednesdays", "OneTuesdayOneWednesday", "everyEncounterOnceEarly"]: + if sw not in sw_prio.keys(): + sw_prio[sw]=0 + sw_int1[sw]=0 + + show_TV_markets = True + + # model2+=pairingVioTotal==0 + # Sogar hart : Avoid playing early the match with the highest global coefficient of the night (if two matches have the same, one late is enough) à priority B + + use_classic_schedule= sw_prio['UseClassicSchedules']+sw_prio['UseClassicWeekdayPatterns']>0 + + if thisSeason.nicename[:3]=="UEL" : + for t in teams: + # model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for d in days ]) == 3 + print ("3 early games for ", getTeamById[t]) + if use_classic_schedule: + model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[2] ]) == lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[3] ]) + model2 += lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[2] ]) == lpSum([home_time[t,d,getIdByTime["Early"]] + away_time[t,d,getIdByTime["Early"]] for (r,d) in getRoundDaysByRound[5] ]) + + + # for pair in pairings: + # t1 = pair['team1_id'] + # t2 = pair['team2_id'] + # # print ("+++ " ,t_conference[t1] , t_conference[t2] ) + # if t_conference[t1].name[:3] != t_conference[t2].name[:3] : + # print ("+++ " ,t_conference[t1] , t_conference[t2], " ", getTeamById[t1] , getTeamById[t2]) + + uefa_games = [ (t1,t2) for t1 in teams for t2 in teams if t14 ] + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for d in days } + best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t1,t2) in uefa_interesting_games for d in days } + best_global_early = { d : pulp.LpVariable('best_global_early_'+str(d), lowBound = 0, cat = pulp.LpContinuous) for d in days } + + uefa_interesting_domestic_games = {c:[] for c in NAS15} + uefa_interesting_domestic_teams = {c:set([]) for c in NAS15} + + for (t1,t2) in uefa_games: + if quality_of_game_dom(t1, t2, t_country[t1])>=10 and t_country[t1] in NAS15: + uefa_interesting_domestic_games[t_country[t1]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t1]].add(t1) + if quality_of_game_dom(t1, t2, t_country[t2])>=10 and t_country[t2] in NAS15: + uefa_interesting_domestic_games[t_country[t2]].append((t1,t2)) + uefa_interesting_domestic_teams[t_country[t2]].add(t2) + + # print ("cool domestic ", uefa_interesting_domestic_games) + # for c in NAS15: + # print (c) + # for (t1,t2) in uefa_interesting_domestic_games[c]: + # print (getTeamById[t1], getTeamById[t2] , quality_of_game_dom(t1, t2),quality_of_game_dom(t2, t1)) + # for t1 in uefa_interesting_domestic_teams[c]: + # print (getTeamById[t1]) + # exit(0) + best_dom= {(c,t1,t2,d) : pulp.LpVariable('best_dom_'+str(c)+'_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for c in NAS15 for (t1,t2) in uefa_interesting_domestic_games[c] for d in days } + best_dom_early = { (c,d) : pulp.LpVariable('best_dom_early_'+str(d) + '_'+str(c), lowBound = 0, cat = pulp.LpContinuous) for d in days for c in NAS15 } + + + if thisSeason.nicename[:3]=="UCL" : + for (t1,t2) in uefa_interesting_games : + for (r,d) in roundDays: + model2 += best_glo[(t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games]) == 1 + rd =getRoundDaysByDay[d] + for t in teams: + model2+= lpSum( [ quality_of_game(t1,t2)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_games if t in [t1,t2]]) \ + <= lpSum( [ best_glo[(t1,t2,d)]*quality_of_game(t1,t2) for (t1,t2) in uefa_interesting_games ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_games : + rd= getRoundDaysByDay[d][0] + model2 += best_glo[(t1,t2,d) ] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_global_early[d] + + + # TODO + # Avoid playing early the match with the highest domestic coefficient in NAs 1-5 if it is 10 or more à priority B + # nas 5 fran + + for c in NAS15: + for (t1,t2) in uefa_interesting_domestic_games[c]: + for (r,d) in roundDays: + model2 += best_dom[(c,t1,t2,d) ] <= x[(t1,t2,(r,d)) ] + x[(t2,t1,(r,d)) ] + + for d in days: + model2+= lpSum( [ best_dom[(c,t1,t2,d)] for (t1,t2) in uefa_interesting_domestic_games[c]]) <= 1 + rd =getRoundDaysByDay[d] + for t in uefa_interesting_domestic_teams[c]: + model2+= lpSum( [ quality_of_game_dom(t1,t2,c)*(x[t1,t2,rd]+ x[t2,t1,rd]) for rd in getRoundDaysByDay[d] for (t1,t2) in uefa_interesting_domestic_games[c] if t in [t1,t2]]) \ + <= lpSum( [ best_dom[(c,t1,t2,d)]*quality_of_game_dom(t1,t2,c) for (t1,t2) in uefa_interesting_domestic_games[c] ]) + + # Avoid the match with the highest global coefficient of the night to be played early (if two matches have the same, one late is enough) (preference)") + for (t1,t2) in uefa_interesting_domestic_games[c]: + rd= getRoundDaysByDay[d][0] + model2 += best_dom[(c,t1,t2,d)] <= x_time[(t1,t2,rd,getIdByTime["Late"])] + x_time[(t2,t1,rd,getIdByTime["Late"])] + best_dom_early[(c,d)] + + + # forall (r in ROUNDS, c in NA14COUNTRIES | 1=1) do + # lpSum(t1, t2 in TEAMS | COUNTRY(t1)=c ) best_dom(t1,t2,r) =1 + # forall (t1 in TEAMS | COUNTRY(t1)=c ) do + # sum (t2 in TEAMS| GROUP(t1)=GROUP(t2) and t1<>t2 and quality_of_game_dom(t1,t2)>4 ) quality_of_game_dom(t1,t2)*(x(t1,t2,r)+x(t2,t1,r)) <= lpSum(t3,t4 in TEAMS| COUNTRY(t3)=c and GROUP(t3)=GROUP(t4) and t3<>t4) quality_of_game_dom(t3,t4)*best_dom(t3,t4,r) + # end-do + # end-do + + + # writeln ("Avoid the match with the highest domestic coefficient to be played early in NAs 1-5 if it is 10 or more (preference)") + # forall (c in NA14COUNTRIES, r in ROUNDS ) do + # ! Avoid the match with the highest domestic coefficient in NAs 1-5 if it is 10 or more 50 + # ! - put the best domestic coeff match late, if value >10 + + # forall (t1,t2 in TEAMS | exists(x(t1,t2,r)) and COUNTRY(t1)=c ) + # if ( quality_of_game_dom(t1,t2) >=10 ) then + # best_dom(t1,t2,r) <= y(t1,t2,r,1)+ y(t2,t1,r,1) + best_dom_early(r,c) + # end-if + + + specialObjectives= (0.01*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys()]) + # Avoid early matches with a global coefficient of 16 and more (60) + +0.6*0.2*sw_prio["AvoidEarlyGlobal"]*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)>=sw_int1["AvoidEarlyGlobal"]]) + # Maximise the average global coefficient of early matches (should stay under 16) à priority C + -0.1*sw_prio["MaxAverageEarlyGlobal"]*lpSum([quality_of_game(t1,t2) * x_time[(t1,t2,rd,getIdByTime["Early"])] for (t1,t2,rd) in x.keys() if quality_of_game(t1,t2)=sw_int1["AvoidEarlyDomestic"] or quality_of_game_dom(t1,t2,t_country[t2])>=sw_int1["AvoidEarlyDomestic"] ]) + -0.1*lpSum([quality_of_game(t1,t2) * best_glo[(t1,t2,d)] for (t1,t2) in uefa_interesting_games for d in days ]) + +0.5*0.04*sw_prio["AvoidEarlyHighDomestic"]*lpSum([best_global_early[d] for d in days ]) + +5*0.04*sw_prio["AvoidEarlyHighGlobal"]*lpSum([best_dom_early[(c,d)] for c in NAS15 for d in days ]) + ) + + model2+= standardObjectives +specialObjectives + + + # todo : MORE PATTERNS + positions = range (1,9) + # pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for p in positions } + pos= {(t,p) : pulp.LpVariable('pos_'+str(t)+'_'+str(p), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t in teams for p in positions } + + for c in displayGroups.keys(): + for p in positions: + model2+= lpSum( [ pos[t,p] for t in displayGroups[c]]) <= 1 + model2+= lpSum( [ pos[t,1]-pos[t,2] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,3]-pos[t,4] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,5]-pos[t,6] for t in displayGroups[c]]) == 0 + model2+= lpSum( [ pos[t,7]-pos[t,8] for t in displayGroups[c]]) == 0 + + + for (t1,t2) in uefa_games: + # Each team has to play each opponent on a Tuesday and on a Wednesday + print (getTeamById[t1], getTeamById[t2]) + if sw_prio['OneTuesdayOneWednesday']>0 and len([d for d in days if getWeekDay[d]=="Tue"])>0: + model2+= lpSum([(x[t1,t2,(r,d)]+x[t2,t1,(r,d)]) for (r,d) in roundDays if getWeekDay[d]=="Tue"]) == 1 + + if use_classic_schedule: + # model2 += pos[t1,4] + pos[t2,1] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[1]]) + # model2 += pos[t2,4] + pos[t1,1] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[1]]) + + # model2 += pos[t1,1] + pos[t2,2] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[2]]) + # model2 += pos[t2,1] + pos[t1,2] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[2]]) + + # model2 += pos[t1,3] + pos[t2,1] - 1 <= lpSum([x[t1,t2,rd] for rd in getRoundDaysByRound[3]]) + # model2 += pos[t2,3] + pos[t1,1] - 1 <= lpSum([x[t2,t1,rd] for rd in getRoundDaysByRound[3]]) + + model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,4] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[1]]) + model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,2] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[2]]) + # model2 += lpSum([ pos[t,p] for t in [t1,t2] for p in [1,3] ])-1 <= lpSum([(x[t1,t2,rd]+x[t2,t1,rd]) for rd in getRoundDaysByRound[3]]) + + model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[1]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[5]]) + model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[2]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[6]]) + # model2 += lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[3]]) == lpSum([x[t1,t2,rd]+x[t2,t1,rd] for rd in getRoundDaysByRound[4]]) + + # every encounter once early once late + if sw_prio['everyEncounterOnceEarly']>0 : + model2+= lpSum([( x_time[(t1,t2,rd,getIdByTime["Early"])] + x_time[(t2,t1,rd,getIdByTime["Early"])] ) for rd in roundDays ]) == 1 + + + # implement classic Tue/Wed patterns + # 1 2 3 4 5 6 7 8 9 10 11 12 + # x x x x x x + if sw_prio['UseClassicWeekdayPatterns']>0: + # print ("using red/blue") + uefa_group_is_blue = {c : pulp.LpVariable('uefa_groupcolor_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in displayGroups.keys() } + bluetuesdays = [ d for d in days if getRoundByDay[d] in [1,4,6] and getWeekDay[d]=="Tue"] + bluewednesdays = [ d for d in days if getRoundByDay[d] in [2,3,5] and getWeekDay[d]=="Wed" ] + model2+= lpSum([uefa_group_is_blue[c] for c in displayGroups.keys()]) == 0.5*len(displayGroups.keys()) + for t in teams: + for d in bluetuesdays: + model2+= home[(t,d)]+away[(t,d)] == uefa_group_is_blue[t_conference[t].id] + for d in bluewednesdays: + model2+= home[(t,d)]+away[(t,d)] == uefa_group_is_blue[t_conference[t].id] + + if sw_prio['Sync1256']>0: + for (t1,t2) in uefa_games: + for d in days: + # groups play same days in rounds 1,2,5,6 + if getRoundByDay[d] in [1,2,5,6] + [3,4]: + model2+= home[(t1,d)]+away[(t1,d)] == home[(t2,d)]+away[(t2,d)] + + for t in teams: + model2 += lpSum( [ pos[t,p] for p in positions ]) ==1 + model2 += homeInRound[t,1]== pos[t,2] +pos[t,4] +pos[t,5] +pos[t,7] + model2 += homeInRound[t,2]== pos[t,1] +pos[t,3] + pos[t,6] +pos[t,8] + model2 += homeInRound[t,3]== pos[t,2] +pos[t,3] + pos[t,6] +pos[t,7] + model2 += homeInRound[t,4]== pos[t,1] +pos[t,4] +pos[t,5] +pos[t,8] + model2 += homeInRound[t,5]== pos[t,1] +pos[t,3] +pos[t,5] +pos[t,7] + model2 += homeInRound[t,6]== pos[t,2] +pos[t,4] + pos[t,6] +pos[t,8] + if use_classic_schedule or sw_prio['UsePosition14']: + model2 += pos[t,5]==0 + model2 += pos[t,6]==0 + model2 += pos[t,7]==0 + model2 += pos[t,8]==0 + print ("forbidding " ,t, 5,6,7,8) + # else: + # model2 += pos[t,1]==0 + # model2 += pos[t,2]==0 + # model2 += pos[t,3]==0 + # model2 += pos[t,4]==0 + + if sw_prio['NoThreeTuesdaysWednesdays']>0 : + for t in teams: + model2+= lpSum([ home[t,d]+away[t,d] for (r,d) in roundDays if getWeekDay[d]=="Tue" and r<=2]) == 1 + model2+= lpSum([ home[t,d]+away[t,d] for (r,d) in roundDays if getWeekDay[d]=="Tue" and r>=5]) == 1 + + + print ("EXTRA UEFA UCL/UEL run") + writeProgress("Running special model ", thisScenario.id,10) + + print (confTeams.keys()) + print (confName) + # for cname in ["Group A" ,"Group B" ,"UCL" , "UEL Group D" ,"UEL Group A" ,"UEL Group B" , "UEL Group C", "UEL Group E", "UEL Group F", "UEL" , "UECL" ]: + for cname in ["Group A" ,"Group B" ,"UCL" , "UEL Group D" ,"UEL Group A" ,"UEL Group B" , "UEL" , "UECL" ]: + # for cname in ["UCL", "UEL", "UECL"]: + print ("TRYING ", cname) + for c in confTeams.keys(): + # print (confName[c],cname,confName[c]==cname) + if confName[c]==cname : + print () + print () + print (confName[c]) + print ("confTeams" , confTeams[c]) + for p in positions: + for t in confTeams[c]: + pos[t,p].cat = pulp.LpInteger + + if runMode!='Improve' and False: + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.2, TimeLimit=120, msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds=60, keepFiles=True)) + + # posTeam = { p : t for p in positions for t in confTeams[c] if pos[(t,p)].value()>0.9 } + # print ("posTeam",posTeam) + for p in positions: + for t in confTeams[c]: + if pos[(t,p)].value()>0.9: + model2 += pos[(t,p)] == pos[(t,p)].value() + print(p,t) + + for (t1,t2) in uefa_games: + if t1 in confTeams[c]: + for rd in roundDays: + if isUEL: + makeIntVar(x_time[t1,t2,rd,getIdByTime["Early"]]) + else: + makeIntVar(x[t1,t2,rd]) + + + # if solver == "CBC": + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # elif solver == "Gurobi": + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120, msg=1)) + # else: + # model2.solve(XPRESS(msg=1,maxSeconds=60, keepFiles=True)) + + # for (t1,t2) in uefa_games: + # if t1 in confTeams[c]: + # for rd in roundDays: + # if getVal(x[t1,t2,rd]): + # setLB(x[t1,t2,rd],1) + + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # model2+= lpSum( [ - pos[t,p] for p in positions ]) + + # if solver == "CBC": + # model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + # elif solver == "Gurobi": + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + # else: + # model2.solve(XPRESS(msg=1,maxSeconds = 80, keepFiles=True)) + + # for (t,p) in pos.keys(): + # if pos[(t,p)].value()>0.9: + # print ("position ", p , " for " , t) + # model2 += pos[(t,p)] == pos[(t,p)].value() + + # best_glo= {(t1,t2,d) : pulp.LpVariable('best_glo_'+str(t1)+'_'+str(t2)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + if False: + for d in days: + print (getNiceDay[d] ) + for (t1,t2) in uefa_interesting_games: + if best_glo[(t1,t2,d)].value()>0.01: + print (" Best GLO ",quality_of_game(t1,t2), getTeamById[t1] , getTeamById[t2] , 0.01*int(100*best_glo[(t1,t2,d)].value()) ) + print () + for c in NAS15: + print ( "checking " , c, uefa_interesting_domestic_games[c] ) + for (t1,t2) in uefa_interesting_domestic_games[c]: + print ( " -- " , getTeamById[t1] , getTeamById[t2] ) + print ( " --- " , (c,t1,t2,d) in best_dom.keys() ) + print ( " ---- " , best_dom[(c,t1,t2,d)].value() , best_dom[(c,t1,t2,d)].value() ==None ) + if not (c,t1,t2,d) in best_dom.keys() : + print (c,t1,t2,d, " not in " , best_dom) + if best_dom[(c,t1,t2,d)].value() and best_dom[(c,t1,t2,d)].value()>0.01: + print (" Best DOM ",c ,getTeamById[t1] , getTeamById[t2] , 0.01*int(100*best_dom[(c,t1,t2,d)].value())) + + +if mathModelName=="EuroLeague Basketball" : + show_TV_markets = True + + day2pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=2) and getRoundByDay[d1] <37] + # trip_ebl= {(t,d) : pulp.LpVariable('trip_'+str(t)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t in teams for (d,d2) in day2pairs} + + if thisSeason.name=="2023": + day2pairs = [ (d1,d2) for (d1,d2) in day2pairs if getRoundByDay[d1]!=26 ] + + print ("day2pairs", day2pairs) + + doubleWeekendDays = { d1 : [d1,d2,d3,d4] for (d1,d2) in day2pairs for (d3,d4) in day2pairs if getDateTimeDay[d3]-getDateTimeDay[d1] ==datetime.timedelta(days=1) } + print ("doubleWeekendDays", doubleWeekendDays) + + criticalDayPairs = [ (d1,d2) ] + + getDW = { getDayById[dw]["round"] : dw for dw in doubleWeekendDays.keys() } + + elb_rounds = getDW.keys() + elb_rounds1 = [ r for r in elb_rounds if r <= nRounds1] + elb_rounds2 = [ r for r in elb_rounds if r > nRounds1] + + t_bl1= { t: [r for r in rounds if r<=17 and t_blocked_at[(t,r)]==0 ] for t in teams } + t_bl2= { t: [r for r in rounds if r>17 and t_blocked_at[(t,r)]==0 ] for t in teams } + t_bl3= { t: [ t_blocked_at[t,r]+t_blocked_at[t,r+1] for r in elb_rounds ] for t in teams } + for t in teams: + print (getTeamById[t], "\t", t_bl3[t] , len(t_bl1[t]) , len(t_bl2[t]) , t_bl1[t] , t_bl2[t] ) + + criticalAvailabilities = {r: [] for r in elb_rounds} + for d1 in doubleWeekendDays.keys(): + dd = doubleWeekendDays[d1] + for day in [dd[0],dd[3]]: + criticalAvailabilities[ getRoundByDay[day] ] =[t for t in teams if available_days[t,day]==0] + + canTravel={t: elb_rounds for t in teams } + + for r in elb_rounds : + print ("criticalAvailabilities", r, [ getTeamById[t] for t in criticalAvailabilities[r]]) + + # closeTeams_EBL_raw={'ALBA Berlin': ['FC Bayern Munich', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'Zenit St. Petersburg', 'CSKA Moscow', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'AS Monaco'], 'Anadolu Efes Istanbul': ['Maccabi FOX Tel Aviv', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'CSKA Moscow', 'Crvena Zvezda mts Belgrade', 'UNICS Kazan', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan'], 'AS Monaco': ['LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'FC Barcelona', 'Real Madrid', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'TD Systems Baskonia Vitoria-Gasteiz'], 'AX Armani Exchange Milan': ['LDLC ASVEL Villeurbanne', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'Zalgiris Kaunas'], 'Crvena Zvezda mts Belgrade': ['ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'Zalgiris Kaunas', 'Maccabi FOX Tel Aviv', 'FC Barcelona', 'CSKA Moscow', 'AS Monaco'], 'CSKA Moscow': ['Zenit St. Petersburg', 'Zalgiris Kaunas', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'UNICS Kazan', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'FC Barcelona': ['Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus'], 'FC Bayern Munich': ['ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'AS Monaco', 'Zenit St. Petersburg', 'CSKA Moscow', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Fenerbahce Beko Istanbul': ['Maccabi FOX Tel Aviv', 'Anadolu Efes Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'CSKA Moscow', 'UNICS Kazan', 'Crvena Zvezda mts Belgrade', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan'], 'LDLC ASVEL Villeurbanne': ['AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'FC Barcelona', 'Real Madrid', 'TD Systems Baskonia Vitoria-Gasteiz', 'AS Monaco', 'Zalgiris Kaunas'], 'Maccabi FOX Tel Aviv': ['Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'Crvena Zvezda mts Belgrade'], 'Olympiacos Piraeus': ['Panathinaikos OPAP Athens', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi FOX Tel Aviv', 'Crvena Zvezda mts Belgrade', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AS Monaco'], 'Panathinaikos OPAP Athens': ['Olympiacos Piraeus', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul', 'Maccabi FOX Tel Aviv', 'Crvena Zvezda mts Belgrade', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AS Monaco'], 'Real Madrid': ['TD Systems Baskonia Vitoria-Gasteiz', 'FC Barcelona', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus', 'AS Monaco'], 'TD Systems Baskonia Vitoria-Gasteiz': ['Real Madrid', 'FC Barcelona', 'AS Monaco', 'LDLC ASVEL Villeurbanne', 'AX Armani Exchange Milan', 'FC Bayern Munich', 'ALBA Berlin', 'Crvena Zvezda mts Belgrade', 'Panathinaikos OPAP Athens', 'Olympiacos Piraeus'], 'UNICS Kazan': ['CSKA Moscow', 'Zenit St. Petersburg', 'Zalgiris Kaunas', 'Crvena Zvezda mts Belgrade', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Zalgiris Kaunas': ['Zenit St. Petersburg', 'CSKA Moscow', 'UNICS Kazan', 'ALBA Berlin', 'FC Bayern Munich', 'AX Armani Exchange Milan', 'LDLC ASVEL Villeurbanne', 'Crvena Zvezda mts Belgrade', 'Anadolu Efes Istanbul', 'Fenerbahce Beko Istanbul'], 'Zenit St. Petersburg': ['CSKA Moscow', 'UNICS Kazan', 'Zalgiris Kaunas', 'ALBA Berlin', 'FC Bayern Munich']} + closeTeams_EBL_raw= { + 'ALBA BERLIN': ['FC BAYERN MUNICH','CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE', + 'ZALGIRIS KAUNAS', 'LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN', + 'AS MONACO','PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS','ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL',], + 'ANADOLU EFES ISTANBUL': ['MACCABI PLAYTIKA TEL AVIV', 'FENERBAHCE BEKO ISTANBUL', 'OLYMPIACOS PIRAEUS', + 'PANATHINAIKOS OPAP ATHENS', 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE', + 'ZALGIRIS KAUNAS', 'ALBA BERLIN','FC BAYERN MUNICH', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN'], + 'AS MONACO': ['LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA','EA7 EMPORIO ARMANI MILAN', 'FC BAYERN MUNICH', 'FC BARCELONA', + 'REAL MADRID', 'ALBA BERLIN', 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE','PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS', 'BASKONIA VITORIA-GASTEIZ', 'VALENCIA BASKET', ], + 'EA7 EMPORIO ARMANI MILAN': ['VIRTUS SEGAFREDO BOLOGNA','LDLC ASVEL VILLEURBANNE', 'FC BAYERN MUNICH', 'ALBA BERLIN', 'CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE', + 'ZALGIRIS KAUNAS','PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS', 'FC BARCELONA', 'REAL MADRID', 'BASKONIA VITORIA-GASTEIZ', 'VALENCIA BASKET','AS MONACO'], + 'CRVENA ZVEZDA BELGRADE': [ 'PARTIZAN BELGRADE', 'ALBA BERLIN', 'FC BAYERN MUNICH', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN', + 'LDLC ASVEL VILLEURBANNE','ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL', 'OLYMPIACOS PIRAEUS', 'PANATHINAIKOS OPAP ATHENS','ZALGIRIS KAUNAS','MACCABI PLAYTIKA TEL AVIV' , 'AS MONACO'], + 'FC BARCELONA': ['REAL MADRID', 'BASKONIA VITORIA-GASTEIZ', 'VALENCIA BASKET', 'AS MONACO', 'EA7 EMPORIO ARMANI MILAN', 'LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA', + 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE', "FC BAYERN MUNICH"], + 'FC BAYERN MUNICH': ['ALBA BERLIN', 'CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE','ZALGIRIS KAUNAS', + 'AS MONACO', 'EA7 EMPORIO ARMANI MILAN', 'LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA', 'PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS', + 'FC BARCELONA', 'REAL MADRID', 'BASKONIA VITORIA-GASTEIZ', 'VALENCIA BASKET','ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL' ], + 'FENERBAHCE BEKO ISTANBUL': ['MACCABI PLAYTIKA TEL AVIV', 'ANADOLU EFES ISTANBUL', 'OLYMPIACOS PIRAEUS', + 'PANATHINAIKOS OPAP ATHENS', 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE', + 'ZALGIRIS KAUNAS', 'ALBA BERLIN','FC BAYERN MUNICH', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN'], + 'LDLC ASVEL VILLEURBANNE': [ 'AS MONACO', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN','FC BAYERN MUNICH','ALBA BERLIN', + 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE','PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS', + 'FC BARCELONA', 'REAL MADRID', 'VALENCIA BASKET', 'BASKONIA VITORIA-GASTEIZ', ], + 'MACCABI PLAYTIKA TEL AVIV': ['ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL', 'PARTIZAN BELGRADE', 'CRVENA ZVEZDA BELGRADE','PANATHINAIKOS OPAP ATHENS' , 'OLYMPIACOS PIRAEUS', ], + 'OLYMPIACOS PIRAEUS': [ 'PANATHINAIKOS OPAP ATHENS', 'ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL', 'MACCABI PLAYTIKA TEL AVIV', + 'CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE','VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN','FC BAYERN MUNICH', + 'ALBA BERLIN','LDLC ASVEL VILLEURBANNE','AS MONACO'], + 'PANATHINAIKOS OPAP ATHENS': [ 'OLYMPIACOS PIRAEUS', 'ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL', 'MACCABI PLAYTIKA TEL AVIV', + 'CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE','VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN','FC BAYERN MUNICH', + 'ALBA BERLIN','LDLC ASVEL VILLEURBANNE','AS MONACO'], + 'PARTIZAN BELGRADE' : [ 'CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE', 'ALBA BERLIN', 'FC BAYERN MUNICH', 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN', + 'LDLC ASVEL VILLEURBANNE','ANADOLU EFES ISTANBUL', 'FENERBAHCE BEKO ISTANBUL', 'OLYMPIACOS PIRAEUS', 'PANATHINAIKOS OPAP ATHENS','ZALGIRIS KAUNAS','MACCABI PLAYTIKA TEL AVIV' , 'AS MONACO'], + 'REAL MADRID': ['BASKONIA VITORIA-GASTEIZ', 'FC BARCELONA','VALENCIA BASKET', 'LDLC ASVEL VILLEURBANNE', + 'VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN','FC BAYERN MUNICH','CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE','AS MONACO' ], + 'BASKONIA VITORIA-GASTEIZ': [ 'REAL MADRID', 'FC BARCELONA', 'VALENCIA BASKET','LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA', + 'EA7 EMPORIO ARMANI MILAN', 'AS MONACO','FC BAYERN MUNICH' ], + 'VALENCIA BASKET': [ 'REAL MADRID', 'FC BARCELONA', 'BASKONIA VITORIA-GASTEIZ','LDLC ASVEL VILLEURBANNE', 'VIRTUS SEGAFREDO BOLOGNA', + 'EA7 EMPORIO ARMANI MILAN', 'AS MONACO','FC BAYERN MUNICH' ], + 'VIRTUS SEGAFREDO BOLOGNA': [ 'REAL MADRID', 'FC BARCELONA','VALENCIA BASKET' , 'BASKONIA VITORIA-GASTEIZ','LDLC ASVEL VILLEURBANNE', + 'EA7 EMPORIO ARMANI MILAN', 'AS MONACO','FC BAYERN MUNICH' , 'ALBA BERLIN','CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE', 'OLYMPIACOS PIRAEUS', 'PANATHINAIKOS OPAP ATHENS'], + 'ZALGIRIS KAUNAS': ['ALBA BERLIN','FC BAYERN MUNICH','VIRTUS SEGAFREDO BOLOGNA', 'EA7 EMPORIO ARMANI MILAN','CRVENA ZVEZDA BELGRADE', 'PARTIZAN BELGRADE','ANADOLU EFES ISTANBUL', + 'FENERBAHCE BEKO ISTANBUL', 'LDLC ASVEL VILLEURBANNE','AS MONACO'] + } + + closeTeams_EBL= { getTeamIdByName[tn] : [ getTeamIdByName[tn2] for tn2 in closeTeams_EBL_raw[tn] ] for tn in closeTeams_EBL_raw.keys() } + + singleTrips_EBL = [ (t1,t2,r) for t2 in teams for t1 in closeTeams_EBL[t2] for r in elb_rounds ] + + print(elb_rounds) + + # for t2 in teams: + # for t1 in closeTeams_EBL[t2]: + # # if not t2 in closeTeams_EBL[t1]: + # # print (getTeamById[t2] ," not in list of ", getTeamById[t1]) + # for r in canHostSingle[t1]: + # print (" single trip in round " ,r , " : ", t1 , t2 , "possible" ) + + print (len (singleTrips_EBL) , " single trips") + + + conferences_ebl = [c for c in Conference.objects.filter(scenario=s2,regional=False).exclude(name='REOPT')] + neighbors_ebl = [c for c in Conference.objects.filter(scenario=s2,regional=True).exclude(name='REOPT') ] + + print ("conferences_ebl", conferences_ebl) + print ("neighbors_ebl", neighbors_ebl) + + getConference={} + getNeighborId={} + for c in conferences_ebl: + if len(c.teams.all())>3: + for t in c.teams.filter(active=True): + getConference[t.id]=c.id + else: + for (d1,d2) in day2pairs: + print ("forbid games for " , c.name, " in days" , d1,d2) + # model2 += lpSum( [x[t1.id, t2.id, rd] for t1 in c.teams.all() for t2 in c.teams.all() for rd in getRoundDaysByDay[d1]+getRoundDaysByDay[d2] if t1.id!=t2.id ]) == 0 + + + + getTeamsOfNeighbor={} + getNameOfNeighbor={} + getConfId={} + for c in neighbors_ebl: + getTeamsOfNeighbor[c.id]=[] + print (c.id, c.name) + getConfId[c.name]=c.id + getNameOfNeighbor[c.id]=c.name + for t in c.teams.filter(active=True): + getNeighborId[t.id]=c.id + getTeamsOfNeighbor[c.id].append(t.id) + print ( " - " , t.id , "\t",canTravel[t.id], "\t", " " , t.name) + # otherConference= { t: [ t2 for t2 in teams if getConference[t]!=getConference[t2] ] for t in teams } + # otherNeighbor= { t: [ t2 for t2 in teams if getNeighborId[t]!=getNeighborId[t2] ] for t in teams if t in getNeighborId.keys()} + + # print ("otherConference = ", otherConference) + # print ("otherNeighbor = ", otherNeighbor) + + + getConfOfNeighbor={} + getNeighborsInConf={ c : set([]) for c in set(getConference.values()) } + for t in teams : + # print (getTeamById[t]) + # print ("-",getNeighborId[t]) + # print ("-",getConference[t]) + + + getConfOfNeighbor[getNeighborId[t]]=getConference[t] + getNeighborsInConf[getConference[t]].add(getNeighborId[t]) + print (getConference[t],getNeighborId[t], getTeamById[t] ) + + # tripCands= + + print ("getConfOfNeighbor = ", getConfOfNeighbor) + print ("getNeighborsInConf = ", getNeighborsInConf) + + possTrips = {} + + for g1 in neighbors_ebl: + for g2 in neighbors_ebl: + if getConfOfNeighbor[g1.id]!=getConfOfNeighbor[g2.id] : + for t1 in getTeamsOfNeighbor[g1.id]: + for t2 in getTeamsOfNeighbor[g2.id]: + for r in elb_rounds: + if (t2,r,g1.id) not in possTrips.keys(): + possTrips[t2,r,g1.id]=[] + possTrips[t2,r,g1.id].append(t1) + + + remtrg= [ trg for trg in possTrips.keys() if len(possTrips[trg])==1] + + for trg in remtrg : + del possTrips[trg] + + # for trg in possTrips.keys(): + # print ("++" , trg, possTrips[trg]) + + trips_ebl = [ (t1,t2,r) for (t2,r,g) in possTrips.keys() for t1 in possTrips[(t2,r,g)]] + + games_ebl= set([(t1,t2) for (t1,t2,r) in trips_ebl + singleTrips_EBL ]) + + far_teams = [(t1,t2) for (t1,t2) in games_ebl if t1 not in closeTeams_EBL[t2]] + far_teams = {t : [ t1 for (t1,t2) in far_teams if t2==t] for t in teams} + + # print (far_games) + + # for t in teams: + # print (getTeamById[t], " :") + # for t2 in far_games[t]: + # print (" - " , getTeamById[t2]) + + + fixed_travels= [] + # fixed_travels= [(283, 15, 'A'), (296, 6, 'A'), (289, 24, 'A'), (293, 31, 'A'), (299, 20, 'A'), (287, 3, 'A'), (288, 10, 'A'), (297, 24, 'A'), (281, 24, 'B'), (283, 6, 'B'), (289, 15, 'B'), (293, 3, 'B'), (299, 6, 'B'), (297, 20, 'B'), (281, 6, 'C'), (296, 31, 'C'), (287, 10, 'C'), (288, 24, 'C'), (286, 20, 'D'), (291, 31, 'D'), (295, 24, 'D'), (282, 20, 'D'), (290, 3, 'D'), (298, 10, 'D'), (294, 15, 'D'), (300, 20, 'D'), (286, 6, 'E'), (291, 3, 'E'), (295, 10, 'E'), (282, 24, 'E'), (298, 31, 'E'), (284, 20, 'E'), (294, 3, 'E'), (300, 10, 'E'), (290, 15, 'F'), (284, 3, 'F')] + # fixed_travels= [ (getTeamById[t2],r,g) for (t2,r,g) in fixed_travels] + # fixed_travels= [('Zenit St. Petersburg', 24, 'A'), ('CSKA Moscow', 10, 'A'), ('Fenerbahce Beko Istanbul', 31, 'A'), ('Anadolu Efes Istanbul', 20, 'A'), ('Panathinaikos OPAP Athens', 3, 'A'), ('Olympiacos Piraeus', 24, 'A'), ('Crvena Zvezda mts Belgrade', 10, 'A'), ('UNICS Kazan', 20, 'B'), ('Zenit St. Petersburg', 15, 'B'), ('CSKA Moscow', 6, 'B'), ('Maccabi FOX Tel Aviv', 3, 'B'), ('Anadolu Efes Istanbul', 3, 'B'), ('Olympiacos Piraeus', 31, 'B'), ('Crvena Zvezda mts Belgrade', 6, 'B'), ('UNICS Kazan', 24, 'C'), ('Maccabi FOX Tel Aviv', 24, 'C'), ('Fenerbahce Beko Istanbul', 10, 'C'), ('Panathinaikos OPAP Athens', 10, 'C'), ('Real Madrid', 6, 'D'), ('TD Systems Baskonia Vitoria-Gasteiz', 6, 'D'), ('LDLC ASVEL Villeurbanne', 20, 'D'), ('AX Armani Exchange Milan', 10, 'D'), ('FC Bayern Munich', 3, 'D'), ('ALBA Berlin', 10, 'D'), ('Real Madrid', 31, 'E'), ('TD Systems Baskonia Vitoria-Gasteiz', 15, 'E'), ('FC Barcelona', 6, 'E'), ('AS Monaco', 10, 'E'), ('LDLC ASVEL Villeurbanne', 24, 'E'), ('AX Armani Exchange Milan', 31, 'E'), ('Zalgiris Kaunas', 6, 'E'), ('ALBA Berlin', 20, 'E'), ('FC Barcelona', 15, 'F'), ('AS Monaco', 24, 'F'), ('Zalgiris Kaunas', 3, 'F'), ('FC Bayern Munich', 15, 'F')] + + print (len (games_ebl)) + + xxx_travel_rounds = sorted( list(elb_rounds) + [ r+1 for r in elb_rounds ]) + + xxx_rounds = xxx_travel_rounds + # xxx_rounds = rounds + # xxx_rounds = sorted( list(elb_rounds) + [ r+1 for r in elb_rounds ]) + + travel_to_cluster= {(t2,r,g) : pulp.LpVariable('travel_to_cluster_'+str(t2)+'_'+str(r)+'_'+str(g), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for (t2,r,g) in possTrips.keys()} + + if runMode=="New" and len(fixedRounds)==0: + + model8 = pulp.LpProblem(f"{PULP_FOLDER}/League_Scheduling_Model_--_Assign_EBL_Trips_"+str(thisScenario.id), pulp.LpMinimize) + + xxx = { (t1,t2,r) : pulp.LpVariable('xxx_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for t1 in teams for t2 in teams for r in xxx_rounds } + toomany = { (t1,r) : pulp.LpVariable('toomany_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) for t1 in teams for r in rounds } + + for (t1,t2,r) in (singleTrips_EBL+trips_ebl) : + xxx[(t1,t2,r)].cat=pulp.LpInteger + xxx[(t1,t2,r)].upBound=1 + + home_ebl = { (t1,r) : lpSum([ xxx[(t1,t2,r)] for t2 in teams ]) for t1 in teams for r in xxx_rounds} + away_ebl = { (t2,r) : lpSum([ xxx[(t1,t2,r)] for t1 in teams ]) for t2 in teams for r in xxx_rounds} + + print (home_ebl.keys()) + + for t1 in teams: + for t2 in teams: + model8+= lpSum([ xxx[(t1,t2,r)]+xxx[(t2,t1,r)] for r in xxx_rounds if r<=17 ]) <= 1 + model8+= lpSum([ xxx[(t1,t2,r)]+xxx[(t2,t1,r)] for r in xxx_rounds if r>=18 ]) <= 1 + + model8+=lpSum([ xxx[(t1,t2,r)] for (t1,t2) in games_ebl for r in xxx_rounds ]) + + # do not visit two teams in doubleweeks which would result in a B2B + for r in elb_rounds: + for t in teams: + lhs1 = [ xxx[(t1,t,r)] for t1 in criticalAvailabilities[r] if t!=t1] + lhs2= [ xxx[(t2,t,r+1)] for t2 in criticalAvailabilities[r+1] if t!=t2] + if len(lhs1)*len(lhs2)>0: + model8+= lpSum( lhs1+lhs2 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r] and len(lhs2)>0: + model8+= home_ebl[(t,r)] + lpSum( lhs2 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r+1] and len(lhs1)>0: + model8+= home_ebl[(t,r+1)] + lpSum( lhs1 ) <= 1 + toomany[(t,r)] + if t in criticalAvailabilities[r] and t in criticalAvailabilities[r+1] : + model8+= home_ebl[(t,r)] + home_ebl[(t,r+1)] <= 1 + toomany[(t,r)] + + for t1 in teams: + for r in xxx_rounds: + model8+=xxx[(t1,t1,r)]==0 + # one game a day1 + model8+= home_ebl[(t1,r)] + away_ebl[(t1,r)] ==1 + # half of the games played at home + if len(xxx_rounds) < len(rounds): + model8+= lpSum ( [ home_ebl[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + + for (t1,t2) in games_ebl: + model8+=lpSum([ xxx[(t1,t2,r)] for r in xxx_rounds ]) <=1 + if (t2,t1) in games_ebl and t117 ]) <=1 + model8+=lpSum([ xxx[(t1,t2,r)]+ xxx[(t2,t1,r)] for r in xxx_rounds if r>=15 and r<=20 ]) <=1 + + # model8+=lpSum([ xxx[(t1,t2,r)] for (t1,t2) in games_ebl for r in xxx_rounds ]) + + # no_travel_elb = [3,27,31] + # no_travel_elb = [27] + # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , + # ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , + # ("ALBA Berlin", 27 ) ] + # # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , ("ALBA Berlin", 27 ), ("Anadolu Efes Istanbul", 27 ) ] + # no_travel_elb = [ ("AX Armani Exchange Milan", 13 ) , ("AX Armani Exchange Milan", 9 ) , ("Baskonia Vitoria-Gasteiz", 9 ) , + # ("Partizan Nis Belgrade", 3 ) , ("ALBA Berlin", 3 ) ,("Crvena Zvezda mts Belgrade", 22) , ("ALBA Berlin", 22 ) , + # ("Anadolu Efes Istanbul", 27 ), ("Zalgiris Kaunas", 27 ) ] + no_travel_elb = [] + fixed_home = [] + fixed_min_home = [] + fixed_home += [('ALBA BERLIN', [2,8,12,27]), ('REAL MADRID', [3,4,5,7,9,10,14,16]), ('ZALGIRIS KAUNAS', [8,9,13,16,30,31])] + fixed_min_home += [ ('ZALGIRIS KAUNAS', [4,8,9,13,16] ,4)] + for r in elb_rounds: + for t in teams: + if getTeamById[t] not in [ 'ZALGIRIS KAUNAS'] : + ss1 = sum ( t_blocked_at[t,r2] for r2 in [r-2,r-1] ) + ss2 = sum ( t_blocked_at[t,r2] for r2 in [r-1,r+2] ) + ss3 = sum ( t_blocked_at[t,r2] for r2 in [r+2,r+3] ) + if r>3 and sum ( t_blocked_at[t,r2] for r2 in [r-3,r-2,r-1])==3: + fixed_home.append((getTeamById[t],[r])) + if r+4<=nRounds and sum ( t_blocked_at[t,r2] for r2 in [r+2,r+3,r+4])==3: + fixed_home.append((getTeamById[t],[r+1])) + + print (r, ss1,ss2,ss3, getTeamById[t]) + urt = max( t_blocked_at[t,r-2]+t_blocked_at[t,r-1] , t_blocked_at[t,r-1]+t_blocked_at[t,r+2] , t_blocked_at[t,r+2]+t_blocked_at[t,r+3] ) + if urt ==2 : + print ("DO NOT TRAVEL ") + no_travel_elb.append((getTeamById[t],r)) + + print ("no_travel_elb",no_travel_elb) + print ("fixed_home",fixed_home) + + split_travel= True + travel_elb ={} + for t in teams: + # every team travels to other clusters twice + model8+= lpSum( [travel_to_cluster[ (t2,r,g)] for (t2,r,g) in possTrips.keys() if t==t2 ])== 2 + for r in elb_rounds: + travel_elb[(t,r)] = lpSum( [travel_to_cluster[ (t2,r2,g)] for (t2,r2,g) in possTrips.keys() if t==t2 and r==r2 ]) + print ("TRAVEL 1 ", t, r, travel_elb[(t,r)]) + model8+= away_ebl[(t,r)] + away_ebl[(t,r+1)] <= 1 + travel_elb[(t,r)] + model8+= lpSum([ xxx[(t1,t,r)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + model8+= lpSum([ xxx[(t1,t,r+1)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + + # model8+= travel_elb[(t,9)] + travel_elb[(t,13)] == 1 + # model8+= travel_elb[(t,18)] + travel_elb[(t,22)] == 1 + if split_travel: + model8+=lpSum([ travel_elb[(t,r)] for r in elb_rounds if r<=17 ]) ==1 + + for (t,rr) in fixed_home: + for r in rr: + if r in elb_rounds: + model8+= home_ebl[(getTeamIdByName[t],r)]==1 + + for (t,rr,m) in fixed_min_home: + model8+= lpSum( [home_ebl[(getTeamIdByName[t],r)] for r in rr])>=m + + for (t,r) in no_travel_elb : + model8+= travel_elb[(getTeamIdByName[t],r)] ==0 + print ("no travel", getTeamIdByName[t],t,r) + + for (t2,r,g) in travel_to_cluster.keys(): + model8+= travel_to_cluster[ (t2,r,g)] <= lpSum( [xxx[(t1,t2,r)] for t1 in getTeamsOfNeighbor[g] ] ) + model8+= travel_to_cluster[ (t2,r,g)] <= lpSum( [xxx[(t1,t2,r+1)] for t1 in getTeamsOfNeighbor[g] ] ) + + for (t2,r,g) in fixed_travels: + print ( "fixed_travel " , t2,r,g) + model8 += travel_to_cluster[(getTeamIdByName[t2],r,getConfId[g])]==1 + + + # model8 += home_ebl [(getTeamIdByName["FC Bayern Munich"],24)] + home_ebl[(getTeamIdByName["FC Bayern Munich"],25)] <= 1 + # model8 += home_ebl [(getTeamIdByName["ALBA Berlin"],31)] + home_ebl [(getTeamIdByName["ALBA Berlin"],32)] >=1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],6)] + home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],7)] >=1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],10)] ==1 + # model8 += home_ebl [(getTeamIdByName["Maccabi FOX Tel Aviv"],11)] ==1 + # model8 += home_ebl [(getTeamIdByName["Zalgiris Kaunas"],21)] ==1 + # model8 += home_ebl [(getTeamIdByName["AS Monaco"],4)] ==1 + # model8 += home_ebl [(getTeamIdByName["AS Monaco"],6)]+ home_ebl [(getTeamIdByName["AS Monaco"],7)] >=1 + + for (t1,t2,r) in xxx.keys(): + if t_blocked_at[(t1,r)] and getTeamById[t2]!="ALBA BERLIN" and False: + model8 += xxx[(t1,t2,r)]==0 + +# TEAMS;5;120 +# TEAMSROUNDS +# ROUNDS;10 +# GROUPS + + + + # model8+=lpSum([ xxx[(t1,t2,r)] for t1 in teams for t2 in teams for r in xxx_rounds ]) + # model8.solve(XPRESS(msg=1,maxSeconds = 160, options=["THREADS=12"], keepFiles=True)) + # for r in xxx_rounds : + # print ("Round ", r) + # for (t1,t2) in games_ebl : + # if xxx[(t1,t2,r)].value()>0.5: + # print (" " ,getTeamById[t1], " - " , getTeamById[t2] ) + # exit(0) + + blockvios_ebl=lpSum([ xxx[(t1,t2,r)] for (t1,t2,r) in xxx.keys() if t_blocked_at[(t1,r)] ]) + usegames_ebl=lpSum([ xxx[(t1,t2,r)] for (t1,t2,r) in xxx.keys() if (t1,t2) in games_ebl ]) + toomanyaways = lpSum([ toomany[(t,r)] for (t,r) in toomany.keys() ]) + + model8+= usegames_ebl == len(teams) /2*len(xxx_rounds) + + # model8+= blockvios_ebl ==0 + model8+= blockvios_ebl + toomanyaways + + if solver == "CBC": + model8.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 240, threads = 8,msg=1)) + elif solver == "Gurobi": + model8.solve(GUROBI( TimeLimit=240,msg=1)) + else: + model8.solve(XPRESS(msg=1,maxSeconds = 160, options=["THREADS=12"], keepFiles=True)) + + for r in xxx_rounds : + print ("Round ", r) + for (t1,t2) in games_ebl : + if xxx[(t1,t2,r)].value()>0.5: + print (" ",xxx[(t1,t2,r)].value() ,getTeamById[t1], " - " , getTeamById[t2] ) + + print ("blockvios_ebl=", blockvios_ebl.value()) + for t1 in teams: + bls = [ r for r in xxx_rounds for t2 in teams if xxx[(t1,t2,r)].value()>0.9 and t_blocked_at[(t1,r)] ] + if len(bls)>0: + print ( "BLOCKED " , getTeamById[t1] , bls) + print ("toomanyaways=", toomanyaways.value()) + + fixed_travels = [ ( getTeamById[t2],r, getNameOfNeighbor[g]) for (t2,r,g) in possTrips.keys() if travel_to_cluster[(t2,r,g)].value()>0.1 ] + print ("fixed_travels=", fixed_travels) + + for t1 in teams: + model2+= lpSum ( [ homeInRound[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + tooFarViolation = { (t1,r) : pulp.LpVariable('tooFarViolation_'+str(t1)+'_'+str(r), lowBound = 0, upBound = 10, cat = pulp.LpContinuous) for t1 in teams for r in xxx_rounds } + + for (t,r) in travel_elb.keys(): + if travel_elb[(t,r)].value()>=0.9: + print ("TRAVEL ",r, getTeamById[t], travel_elb[(t,r)].value()) + else: + for t1 in far_teams[t]: + model2+= x_round[(t1,t,r)]==0 + model2+= x_round[(t1,t,r+1)]== 0 + model2+= x_round[(t1,t,r+1)]<= tooFarViolation[(t,r+1)] + model2+= awayInRound[(t,r)]+awayInRound[(t,r+1)] <=1 + + print (len (travel_elb.keys())) + + + specialObjectives = 1000 * lpSum([ tooFarViolation[(t,r)] for (t,r) in tooFarViolation.keys() ]) + + + for (t,r) in toomany.keys(): + if toomany[(t,r)].value()>0.9: + print ("too many " ,r, " ", getTeamById[t]) + + + for (t2,r,g) in fixed_travels: + model2+= lpSum( [x_round[(t1,getTeamIdByName[t2],r)] for t1 in getTeamsOfNeighbor[getConfId[g]] ] ) ==1 + model2+= lpSum( [x_round[(t1,getTeamIdByName[t2],r+1)] for t1 in getTeamsOfNeighbor[getConfId[g]] ] ) ==1 + + for r in xxx_rounds: + print () + print ("ROUND ", r) + for (t1,t2) in games_ebl: + if xxx[(t1,t2,r)].value()>0.1: + bcked = "#### " if t_blocked_at[(t1,r)] else " - " + print (getTeamById[t1] , bcked , getTeamById[t2] , " ", xxx[(t1,t2,r)].value()) + model2+= x_round[(t1,t2,r)]==1 + + print (blockvios_ebl.value()) + print (toomanyaways.value()) + + else: + print ("++++++++++ IMPROVE SOLUTION ++++++++++") + for t1 in teams: + # half of the games played at home + model2+= lpSum ( [ homeInRound[(t1,r)] for r in xxx_rounds ]) == 0.5*len(xxx_rounds) + + travel_elb ={} + for t in teams: + # every team travels to other clusters twice + model2+= lpSum( [travel_to_cluster[ (t2,r,g)] for (t2,r,g) in possTrips.keys() if t==t2 ])== 2 + for r in elb_rounds: + travel_elb[(t,r)] = lpSum( [travel_to_cluster[ (t2,r2,g)] for (t2,r2,g) in possTrips.keys() if t==t2 and r==r2 ]) + print ("TRAVEL 1 ", t, r, travel_elb[(t,r)]) + model2+= awayInRound[(t,r)] + awayInRound[(t,r+1)] <= 1 + travel_elb[(t,r)] + model2+= lpSum([ x_round[(t1,t,r)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + model2+= lpSum([ x_round[(t1,t,r+1)] for t1 in far_teams[t] ]) <= travel_elb[(t,r)] + + for (t2,r,g) in travel_to_cluster.keys(): + model2+= travel_to_cluster[ (t2,r,g)] <= lpSum( [x_round[(t1,t2,r)] for t1 in getTeamsOfNeighbor[g] ] ) + model2+= travel_to_cluster[ (t2,r,g)] <= lpSum( [x_round[(t1,t2,r+1)] for t1 in getTeamsOfNeighbor[g] ] ) + +if mathModelName=="UEFA NL" : + + if thisSeason.name=="2024" and False: + + clashes_raw =[] + clashes_raw +=['ARM-AZE','UKR-RUS','GIB-ESP','KOS-BIH','KOS-SRB','KOS-RUS','UKR-BLR'] + # clashes_raw +=['SRB-MKD','SRB-BIH','SRB-ALB','SRB-CRO','MKD-ALB','MKD-GRE','BIH-CRO','TUR-CYP','TUR-ARM','KOS-ARM','KOS-AZE','KOS-BLR','KOS-CYP','KOS-GEO','KOS-GRE','KOS-ISR','KOS-KAZ','KOS-MDA','KOS-ROU','KOS-RUS','KOS-SVK','KOS-ESP','KOS-UKR'] + clashes = [ (c[:3],c[4:]) for c in clashes_raw if c[:3] in cn_shortname.values() and c[4:] in cn_shortname.values() ] + print ("clashes", clashes) + + print ( thisSeason.name=="2024") + + american_teams = [t for t in teams if t_lon[t] <=-20] + european_teams = [t for t in teams if t_lon[t] >-20] + + print ("american_teams", american_teams) + print ("european_teams", european_teams) + + usedWeekdays = set([getWeekDay[d] for d in days]) + # print (getNiceDay[d['id']], getWeekDay[d['id']]) + + # print (t_conference) + + day3pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=3) ] + print (day3pairs) + + # possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0]] + possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0] and not (t_country[t1],t_country[t2]) in clashes and not (t_country[t2],t_country[t1]) in clashes ] + nopossGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if not (t1,t2) in possGames_UEFA ] + + # possGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1!=t2 and t_conference[t1].name[0]==t_conference[t2].name[0]] + # nopossGames_UEFA = [(t1,t2) for t1 in teams for t2 in teams if t1==t2 or t_conference[t1].name[0]!=t_conference[t2].name[0]] + + print ("") + print (len(possGames_UEFA), "possGames_UEFA") + print (len(nopossGames_UEFA), "nopossGames_UEFA") + print ("") + + # for (t1,t2) in possGames_UEFA: + # print (getTeamById[t1] , " might play ", getTeamById[t2] , t_conference[t1].name[0] , t_conference[t2].name[0]) + + for (t1,t2) in nopossGames_UEFA: + for rd in roundDays: + if (t1,t2,rd) in x.keys(): + setUB(x[(t1,t2,rd)],0) + + # model2 += lpSum([x[(t1,t2,rd)] for rd in roundDays]) == 0 + print (getTeamById[t1] , " will not play ", getTeamById[t2] , t_conference[t1].name[0] , t_conference[t2].name[0]) + + + for (t1,t2) in possGames_UEFA: + if t10: + model2 += lpSum([x[(t1,t2,(r,d))] for (t1,t2,(r,d)) in all_american_games_A ]) ==12 + if len(all_american_games_B)>0: + model2 += lpSum([x[(t1,t2,(r,d))] for (t1,t2,(r,d)) in all_american_games_B ]) == 4 + + for (t1,t2,(r,d)) in all_american_games_A: + if r in [1,2,7,8]: + model2 += x[(t1,t2,(r,d))]==0 + + for (t1,t2,(r,d)) in all_american_games_B: + if r in [3,4,5,6,7,8]: + model2 += x[(t1,t2,(r,d))]==0 + + + + + for t in teams: + if not getTeamById[t] in ["Iceland", "Faroe Islands"]: + for rn in [3,4,5,6,7,8]: + model2 += break3InRound[(t,rn)]== 0 + # if getTeamById[t]=="Ukraine": + # model2 += awayInRound[(t1,7)]== 1 + # model2 += awayInRound[(t1,8)]== 1 + + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in teams for rd in roundDays if t_conference[t]!=t_conference[t2]]) == 4 + model2 += lpSum([home[(t,d)] for d in days ]) == 4 + + for (d1,d2) in day3pairs: + model2 += home[(t,d1)]+away[(t,d1)] == home[(t,d2)]+ away[(t,d2)] + + if t_conference[t].name[0]=="A" : + for pp in ["A1","A2"]: + model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 4 + # 2 home matches against pot 1, 2 home matches against pot 2 and 2 away matches against pot 1, 2 away matches against pot 2 + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 2 + + # if t in american_teams: + # model2 += lpSum([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() + + # if t1 in american_teams and t2 in american_teams and r in [3,4,5,6]] [x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 4 + + + + # model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) >= 2 + + # For league B, the 16 European teams are split in 4 pots and the last four CONMEBOL teams (VEN, PAR, ECU, BOL) join them. + # The swiss system is still used and each team must play two teams from each pot (8MDs). + # For the CONMEBOL teams, two different splits are possible: one team per pot or 2 teams in the two first pots. + # Could you please try both options and see which one is the most convenient? + # In addition, the CONMEBOL teams must play each other but only over one international window + # (let's say in September 2024) as they don't have enough opponents to play 4 MDs against each other. + if t_conference[t].name[0]=="B" : + for pp in ["B1","B2","B3","B4"]: + model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 2 + # 1 home match against one team from each pot and 1 away match against one team from each pot + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) == 1 + # model2 += lpSum([x[(t,t2,rd)] + x[(t2,t,rd)] for rd in roundDays for t2 in teams if t_conference[t2].name==pp]) >= 1 + + # For league C, the format is different and it is not a swiss system anymore. + # A proposal would be to have 4 groups of 4 teams but to pair 2 groups together (1A and 1B together, 2A and 2B together). + # The four teams from one group (1A) would play home and away the four teams from the other group (1B). + # In the excel table, you will see the group proposal. + + for (c1,c2) in [("C1A","C1B"), ("C2A","C2B")]: + if t_conference[t].name==c1: + for t2 in teams : + if t_conference[t2].name==c2: + model2 += lpSum([x[(t,t2,rd)] for rd in roundDays]) == 1 + model2 += lpSum([x[(t2,t,rd)] for rd in roundDays]) == 1 + model2 += lpSum([x[(t,t2,(r,d))] + x[(t2,t,(r,d))] for (r,d) in roundDays if r<=4]) == 1 + + + # use every weekday at least once: + # for wd in usedWeekdays: + # model2 += lpSum([home[(t,d)] + away[(t,d)] for d in days if getWeekDay[d]==wd ]) >= 1 + + # for t2 in teams: + # if gameCntr[(t,t2)]+gameCntr[(t2,t)]>0: + # print("adding" , t , t2) + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for rd in roundDays if t!=t2]) <= 1 + # if t_conference[t].name[0]!=t_conference[t2].name[0]: + # model2 += lpSum([x[(t,t2,rd)] for rd in roundDays if t!=t2]) == 0 + # # print (getTeamById[t] , " does not play ", getTeamById[t2] , t_conference[t].name[0] , t_conference[t2].name[0] ) + + # # else: + # # print("ignoring" , t , t2) + + # print ("TESTING") + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + # print ("TESTING DONE") + + + print (t_conference) + + + # for t in american_teams: + # for (r,d) in getRoundDaysByRound[3]: + # if t!= american_teams[0]: + # model2 += home[(t,d)]+away[(t,d)] == home[(american_teams[0],d)]+away[(american_teams[0],d)] + + # for r in [3,4,5,6]: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in getRoundDaysByRound[r] if t!=t2]) == 1 + # for r in [1,2,7,8]: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in getRoundDaysByRound[r] if t!=t2]) == 0 + + # league_A_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ] + # league_B_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" ] + # league_C_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="C" and t_conference[t2].name[0]=="C" ] + league_A1_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" if r in [3,4,5,6] ]) + league_A_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ]) + league_B_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" ]) + league_B1_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="B" and t_conference[t2].name[0]=="B" if r <=4 ]) + league_C_games = set([(t1,t2,r) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="C" and t_conference[t2].name[0]=="C" ]) + + # all_american_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in american_teams and t2 in american_teams and r in [3,4,5,6]] + # half_american_games = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in american_teams != t2 in american_teams and r in [1,2,7,8]] + # all_european_games1 = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in european_teams and t2 in european_teams and r in [1,2,3,4]] + # all_european_games2 = [(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t1 in european_teams and t2 in european_teams and r in [5,6,7,8]] + + if runMode=='New': + + # print (len(league_A_games)) + # print (len(league_B_games)) + # print (len(league_C_games)) + + + optIterations = [] + if len(league_C_games)>0: + optIterations+=[(league_C_games, " league_C_games ","rounds")] + optIterations+=[(league_A1_games, " league_A1_games ","rounds" )] + optIterations+=[(league_A_games, " league_A_games " ,"rounds")] + optIterations+=[(league_B1_games, " league_B1_games " ,"rounds")] + optIterations+=[(league_B_games, " league_B_games " ,"rounds")] + else: + league_A1_games = set([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" if r in [3,4,5,6] ]) + league_A_games = set([(t1,t2,(r,d)) for (t1,t2,(r,d)) in x.keys() if t_conference[t1].name[0]=="A" and t_conference[t2].name[0]=="A" ]) + optIterations+=[(league_A1_games, " league_A1_games " ,"days")] + optIterations+=[(league_A_games, " league_A_games " , "days")] + + + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ), ( half_american_games, " HALF AMERICAN " ) , ( all_european_games1, " all_european_games1 " ) , ( all_european_games2, " all_european_games2 " ) ] : + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ), ( all_european_games1, " all_european_games1 " ) , ( all_european_games2, " all_european_games2 " ) ] : + # for gms,txt in [ ( all_american_games, " ALL AMERICAN " ) ] : + for gms,txt,gran in optIterations : + print ("!!!!!!! PRE RUN "+txt+"!!!!!!") + print (len(gms)) + for rr in gms: + if gran=="rounds": + makeIntVar(x_round[rr]) + else: + makeIntVar(x[rr]) + + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.1, TimeLimit=240,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 140, options=["THREADS=12"], keepFiles=True)) + + for rr in gms: + if gran=="rounds": + model2+= x_round[rr] ==getVal(x_round[rr]) + if getVal(x_round[rr]) >0.1: + print (rr , getVal(x_round[rr])) + else: + model2+= x[rr] ==getVal(x[rr]) + if getVal(x[rr]) >0.1: + print (rr , getVal(x[rr])) + # for t in european_teams: + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in roundDays ]) >= 1 + # model2 += lpSum([x[(t,t2,rd)] +x[(t2,t,rd)] for t2 in american_teams for rd in roundDays ]) <= 2 + + + else : + + # model2+=gamesTooCloseTotal==0 + specialObjectives+=1000*gamesTooCloseTotal + + # closeTeams_UEFA = { "Kazakhstan" : [ "Andorra", "England","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + closeTeams_UEFA = { "Kazakhstan" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", + "Northern Ireland", "Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + "Azerbaijan" : ["Gibraltar", "Iceland", "Portugal"], + "Iceland" : ["Armenia", "Cyprus", "Georgia", "Israel"] } + + # Label to represent both teams (Kazakstan/Modolva - Cyprus/Estonia) to carry both constrains of each pair. + # closeTeams_UEFA = { "KAZ/MDA 1" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + # "KAZ/MDA 2" : [ "Andorra", "England","France","Faroe Islands","Gibraltar", "Iceland","Malta", "Northern Ireland","Portugal","Republic of Ireland","Scotland","Spain","Wales" ] , + # "Azerbaijan" : ["Gibraltar", "Iceland", "Portugal"], + # "Iceland" : ["Armenia", "CYP/EST 1", "CYP/EST 2", "Georgia", "Israel"] } + + # for tn1 in closeTeams_UEFA.keys(): + # for tn2 in closeTeams_UEFA[tn1]: + # t1 = getTeamIdByName[tn1] + # t2 = getTeamIdByName[tn2] + # print (t1, t2 , t_conference[t1] ,t_conference[t2] , t_conference[t1]==t_conference[t2] , tn1, tn2 ) + + closeTeams_UEFA = [ (getTeamIdByName[tn1],getTeamIdByName[tn2]) for tn1 in closeTeams_UEFA.keys() for tn2 in closeTeams_UEFA[tn1] if t_conference[getTeamIdByName[tn1]]==t_conference[getTeamIdByName[tn2]] ] + print ("closeTeams_UEFA", [(getTeamById[t1] ,getTeamById[t2]) for (t1,t2) in closeTeams_UEFA]) + + # for (tn1,tn2) in closeTeams_UEFA: + # t1 = getTeamById[tn1] + # t2 = getTeamById[tn2] + # print (t1, t2 , t_conference[tn1] ,t_conference[tn2] , t_conference[tn1]==t_conference[tn2] , tn1, tn2 ) + + + # 1234 56 + two_starter_uefa= [5] + mid_starter_uefa= [2,3] + three_starter_uefa= [1] + + two_starter_uefa= [1,3,5,7,9] + mid_starter_uefa= [] + three_starter_uefa= [] + # for t in teams: + # if noPlayRounds[t] == [7,8]: + # two_starter_uefa= [7,9] + # mid_starter_uefa= [2,5] + # three_starter_uefa= [1,4] + + print ("two_starter_uefa", two_starter_uefa) + print ("three_starter_uefa", three_starter_uefa) + + # TODO + # no back to back in md 3 and 4 + # no break in md 5/6 + # max 3 rest days between games + + day3pairs =[ (d1,d2) for d1 in days for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] ==datetime.timedelta(days=3) ] + day34pairs ={ d1 : [d2 for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] in [datetime.timedelta(days=di) for di in [3,4]]] for d1 in days } + day345pairs ={ d1 : [d2 for d2 in days if getDateTimeDay[d2]-getDateTimeDay[d1] in [datetime.timedelta(days=di) for di in [3,4,5]]] for d1 in days } + + closeDays_UEFA = two_starter_uefa+mid_starter_uefa+three_starter_uefa + closeDays_UEFA = [] + + if "3DaysForDistantTeams" in special_wishes_active: + sw_type="3DaysForDistantTeams" + for (t1,t2) in closeTeams_UEFA: + # model2 += lpSum( [ x[t1,t2,rd] + x[t2,t1,rd] for rd in getRoundDaysByRound[4]] ) ==1 + # model2 += lpSum( [ x[t1,t2,rd] + x[t2,t1,rd] for rd in getRoundDaysByRound[1]] ) ==1 + for (d1,d2) in day3pairs: + specialWishVio[(sw_type,t1,t2,d1)]= pulp.LpVariable('specialWishVio_'+sw_type+'_'+str(t1)+"_"+str(t2)+"_"+str(d1), lowBound=0, cat=pulp.LpContinuous) + specialWishItems[sw_type].append((t1,t2,d1)) + for t3 in teams: + if t_conference[t1]==t_conference[t3] and not t3 in [t1,t2]: + print ("taking care of " , getTeamById[t3] , "'s trip to ", getTeamById[t1] , " and ", getTeamById[t2] ) + model2 += lpSum( [ x[t,t3,rd] for t in [t1,t2] for rd in getRoundDaysByDay[d1]+getRoundDaysByDay[d2] ] ) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t1,d1] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByDay[d2]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t1,d2] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByDay[d1]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t2,d1] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByDay[d2]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + model2 += home[t2,d2] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByDay[d1]]) <=1 + specialWishVio[(sw_type,t1,t2,d1)] + specialObjectives += 1000*lpSum([ sw_prio[sw_type]* specialWishVio[(sw_type,t1,t2,d1)] for (t1,t2,d1) in specialWishItems[sw_type] ]) + + + # for (t1,t2) in closeTeams_UEFA: + # for rn in closeDays_UEFA: + # for t3 in teams: + # if t_conference[t1]==t_conference[t3] and not t3 in [t1,t2]: + # # print ("taking care of " , getTeamById[t3] , "'s trip to ", getTeamById[t1] , " and ", getTeamById[t2] ) + # model2 += lpSum( [ x[t1,t3,d] + x[t2,t3,d] for d in getRoundDaysByRound[rn]+getRoundDaysByRound[rn+1] ] ) <=1 + + # # print ("I don't care about " , getTeamById[t1] , "'s trip to ", getTeamById[t2]) + # # print ("taking care of " , getTeamById[t1] , "'s trip to ", getTeamById[t2]) + # model2 += homeInRound[(t1,rn)] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[rn+1]]) <=1 + # model2 += homeInRound[(t1,rn+1)] + lpSum([x[t2,t1, rd] for rd in getRoundDaysByRound[rn]]) <=1 + # # print ("taking care of " , getTeamById[t2] , "'s trip to ", getTeamById[t1]) + # # if getTeamById[t2]!="France": + # model2 += homeInRound[(t2,rn)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn+1]]) <=1 + # model2 += homeInRound[(t2,rn+1)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn]]) <=1 + # # else: + # # if rn>1: + # # model2 += homeInRound[(t2,rn-1)] + lpSum([x[t1,t2, rd] for rd in getRoundDaysByRound[rn]]) + homeInRound[(t2,rn+1)] <=2 + + print ("getRoundDaysByRound", getRoundDaysByRound) + + conferences6 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==6] + conferences5 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==5] + conferences4 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==4] + conferences3 = [c for c in Conference.objects.filter(scenario=s2,regional=False) if len(c.teams.filter(active=True))==3] + + # print ("day34pairs",day34pairs) + # print (conferences4) + # print ("confs 3 " , len(conferences3)) + # print ("confs 4 " , len(conferences4)) + # print ("confs 5 " , len(conferences5)) + # print ("confs 6 " , len(conferences6)) + + # alltms =set([]) + + c4= {(c.id,d) : pulp.LpVariable('c4_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences4 for d in days} + c5= {(c.id,d) : pulp.LpVariable('c5_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences5 for d in days} + c6= {(c.id,d) : pulp.LpVariable('c6_'+str(c)+'_'+str(d), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for c in conferences6 for d in days} + + + travelControl_UEFA= False + travelControl_UEFA= True + dontPlayOnGamesList_UEFA= ["2022-06-04", "2022-06-05", "2022-06-08", "2022-06-11", "2022-06-12"] + if runMode!='Improve': + dontPlayOnGamesList_UEFA+= ["2022-06-02"]+ ["2022-06-13"] + ["2022-06-06","2022-06-09"] + dontPlayOnGamesList_UEFA = [ parse(dd) for dd in dontPlayOnGamesList_UEFA ] + print("dontPlayOnGamesList_UEFA",dontPlayOnGamesList_UEFA) + dontPlayOnGamesList_UEFA = [ getDayByDateTime[dd] for dd in dontPlayOnGamesList_UEFA if dd in getDayByDateTime.keys()] + print("dontPlayOnGamesList_UEFA",dontPlayOnGamesList_UEFA) + if travelControl_UEFA : + for c in conferences4: + cteams= c.teams.filter(active=True) + # print ("checking group" , c ) + for t in cteams: + # print ("checking team" , t ) + for (t1,t2) in closeTeams_UEFA: + if t.id==t1: + for d1 in sorted(dontPlayOnGamesList_UEFA ): + model2 += c4[(t_conference[t1].id,d1)] == 0 + print (c.name , " cannot play on ", d1 , " " , getNiceDay[d1]) + + for c in conferences3+conferences4: + cteams= c.teams.filter(active=True) + for t1 in cteams: + for t2 in cteams: + if t2.id < t1.id: + model2 += lpSum( [ x[t1.id,t2.id,d] + x[t2.id,t1.id,d] for d in getRoundDaysByRound[3]+getRoundDaysByRound[4] ]) <=1 + + forbidBackToBack = True + + # when are the next games allowed to be played + compNextGames = { 1:[4,5], 2:[5,6], 3:[6,7], + 4:[7,8,9], 5:[8,9,10], 6:[9,10], + 7:[10,11,12], + 8:[11,12], 9:[12,13], 10:[13], + 14:[17,18], 15:[18,19], 16:[19], + } + + compNextGames = { 1:[4,5,6], 2:[5,6], 3:[6], + 4:[7,8,9], 5:[7,8,9], 6:[7,8,9], + + 7:[10,11,12], 8:[11,12], 9:[12], + 10:[13,14,15], 11:[13,14,15], 12:[13,14,15], + + 13:[16,17,18], 14:[17,18], 15:[18], + # 16:[], 17:[], 18:[], + } + + if "forbid4DaysOff" in special_wishes_active : + compNextGames[1]=[4,5] + compNextGames[7]=[10,11] + compNextGames[13]=[16,17] + + # if runMode!='Improve': + # compNextGames[14]=[17] + # compNextGames[15]=[18] + + if "3RestDaysBetweenMD2andMD3" in special_wishes_active: + compNextGames[4]=[8] + compNextGames[5]=[9] + compNextGames[6]=[10] + + usedWeekdays = set([getWeekDay[d] for d in days]) + + for c in conferences4: + cteams= c.teams.filter(active=True) + t1 = cteams.first() + for t2 in cteams: + # no b2b between rounds 3 and 4 + # if t2.id != t1.id : + # model2 += lpSum( [ x[t1.id,t2.id,d] + x[t2.id,t1.id,d] for d in getRoundDaysByRound[3]+getRoundDaysByRound[4] ]) <=1 + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] == c4[(c.id,d1)] + # adjust pattern abcbca + # for t1 in cteams: + # for t2 in cteams: + # if t1.id != t2.id: + # model2 += lpSum( [ x[t1.id,t2.id,d] for d in getRoundDaysByRound[2]]) == lpSum( [ x[t2.id,t1.id,d] for d in getRoundDaysByRound[4]]) + # model2 += lpSum( [ x[t1.id,t2.id,d] for d in getRoundDaysByRound[3]]) == lpSum( [ x[t2.id,t1.id,d] for d in getRoundDaysByRound[5]]) + # for (d1,d2) in day3pairs: + # if getRoundByDay[d1]>=5: + # model2 += c4[(c.id,d1)] == c4[(c.id,d2)] + for di in compNextGames.keys(): + model2 += c4[(c.id,daysSorted[di-1])] <= lpSum([c4[(c.id,daysSorted[di2-1])] for di2 in compNextGames[di]]) + + for c in conferences6: + cteams= c.teams.filter(active=True) + for t2 in cteams: + # alltms.add(t2.id) + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] <= c6[(c.id,d1)] + + if runMode!='Improve' or "alwaysExactlyTwoRestDays" in special_wishes_active: + for (d1,d2) in day3pairs: + model2 += c6[(c.id,d1)] == c6[(c.id,d2)] + else: + for d1 in day34pairs.keys(): + if len(day34pairs[d1])>0: + model2 += c6[(c.id,d1)] <= lpSum([c6[(c.id,d2)] for d2 in day34pairs[d1]]) + + for r in rounds: + model2 += lpSum( [ c6[(c.id,d)] for d in getDays[r] ] ) == 1 + + model2 += lpSum( [ c6[(c.id,d)] for d in days ] ) == 10 + + if "everyWeekdayAtLeastOnce" in special_wishes_active: + for wd in usedWeekdays: + model2 += lpSum( [ c6[(c.id,d)] for d in days if getWeekDay[d]==wd] ) >= 1 + + c5_repeater={} + for c in conferences5: + cteams= c.teams.filter(active=True) + for t2 in cteams: + # alltms.add(t2.id) + for d1 in days: + model2 += home[(t2.id,d1)]+away[(t2.id,d1)] <= c5[(c.id,d1)] + if runMode!='Improve' or "alwaysExactlyTwoRestDays" in special_wishes_active: + for (d1,d2) in day3pairs: + model2 += c5[(c.id,d1)] == c5[(c.id,d2)] + else: + for d1 in day34pairs.keys(): + if len(day34pairs[d1])>0: + model2 += c5[(c.id,d1)] <= lpSum([c5[(c.id,d2)] for d2 in day34pairs[d1]]) + + for r in rounds: + model2 += lpSum( [ c5[(c.id,d)] for d in getDays[r] ] ) == 1 + + model2 += lpSum( [ c5[(c.id,d)] for d in days ] ) == 10 + + if "everyWeekdayAtLeastOnce" in special_wishes_active: + for wd in usedWeekdays: + model2 += lpSum( [ c5[(c.id,d)] for d in days if getWeekDay[d]==wd] ) >= 1 + + for t1 in cteams: + for t2 in cteams: + if t1.id= -1 + + + # t_in_French_Group={t4 : False for t4 in teams} + # for c in allConferences: + # tms = [t.id for t in c.teams.filter(active=True)] + # print ("C " , tms) + # franceFound = False + # for tt in tms: + # if getTeamById[tt]== "France": + # franceFound = True + # if franceFound: + # for tt in tms: + # t_in_French_Group[tt]= True + # print ("found french friend " , tt , getTeamById[tt]) + # + + + # for t in teams: + # if not t_usePhases[t] and not t_in_French_Group[t]: + # model2 += lpSum([c5_repeater[(t1,t2)] for (t1,t2) in c5_repeater.keys() if t in [t1,t2]]) <=1 + + + if len(conferences4)>0 : + for d1 in days: + model2 += lpSum( [ c4[(c.id,d1)] for c in conferences4 ] ) >=0 + model2 += lpSum( [ c4[(c.id,d1)] for c in conferences4 ] ) <=5 + + # todo: no 3*5 c4 on days 1,2,6 -> spacing for c3 possible + # todo: no 3*5 c4 on days 1,5,6 -> spacing for c3 possible + + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [1,2,6]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [1,5,6]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [8,9,13]] ) <=14 + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 for di in [8,12,13]] ) <=14 + # for di in [1,2,3,4,5,6, 8,9,10,11,12,13]: + # model2 += lpSum( [ c4[(c.id,daysSorted[di-1])] for c in conferences4 ] ) >=3 + + + if runMode!='Improve': + print ("EXTRA UEFA NL run") + writeProgress("Running special model ", thisScenario.id,10) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 40, options=["THREADS=12"], keepFiles=True)) + + for (c,d) in c4.keys(): + model2 += c4[(c,d)] == c4[(c,d)].value() + if c4[(c,d)].value() >0.9: + print ("c4 " , c, getNiceDay[d] ) + + + for (c,d) in c5.keys(): + model2 += c5[(c,d)] == c5[(c,d)].value() + if c5[(c,d)].value() >0.01: + print (c, d, c5[(c,d)].value() ) + + +print ("thisSeason.useFeatureBackToBack", thisSeason.useFeatureBackToBack) +if thisSeason.useFeatureBackToBack: + print ("critical_day_pairs", critical_day_pairs) + print ("back2backBlocks", back2backBlocks) + fiveDayFourRounds=[] + for d in days: + fiveDays = [ d2 for d2 in days if getDateTimeDay[d]<= getDateTimeDay[d2] and getDateTimeDay[d2]-getDateTimeDay[d]<=datetime.timedelta(days=4) ] + fourRounds = [] + for d2 in fiveDays: + fourRounds+=[ r2 for r2 in getRoundsByDay[d2] ] + fourRounds=list(set(fourRounds)) + # print (d, fiveDays,fourRounds , len(fourRounds)>=4) + if len(fourRounds)>=4: + fiveDayFourRounds.append(fiveDays) + + bad_travel_in = { t : [] for t in teams} + bad_travel_out = { t : [] for t in teams} + for (t1,t2,c) in bad_travels: + bad_travel_in[t2].append((t1,color_weight[c])) + bad_travel_out[t1].append((t2,color_weight[c])) + + for fdfr in fiveDayFourRounds: + for t in realteams: + model2 += lpSum([ home[(t,d)] + away[(t,d)] for d in fdfr ]) <= 3 + 0.001*badBackToBack[(t,fdfr[0])] + # print(t, " cannot play more than three times in ", fdfr) + + for (d1,d2) in critical_day_pairs: + # print ("no time zone crossings for at days " , getNiceDay[d1]," -> ",getNiceDay[d2],nextCritical[d2]) + for t in realteams: + + # print ("counting b2b for ", getTeamById[t]) + model2 += home[(t,d1)]+home[(t,d2)]+away[(t,d1)]+away[(t,d2)] <= 1 + badBackToBack[(t,d1)] + rd1s=getRoundDaysByDay[d1] + rd2s=getRoundDaysByDay[d2] + + if nRounds <= 72 and nextCritical[d2]: + # print ("no 3 series for " , t , " at days " , d1,d2,nextCritical[d2]) + model2 += lpSum([ home[(t,d)] + away[(t,d)] for d in [d1,d2,nextCritical[d2]] ]) <= 2 + 0.0001*badBackToBack[(t,d1)] + + for (tms1,tms2,w) in back2backBlocks: + model2 += lpSum([x[(t1,t,rd1)] for rd1 in rd1s for t1 in tms1 if gameCntr[(t1,t)]>0]) + lpSum([x[(t2,t,rd2)] for rd2 in rd2s for t2 in tms2 if gameCntr[(t2,t)]>0 ]) <= 1 + 0.001/w*badBackToBack[(t,d1)] + + if currentAwayLocation[(t,d1)] and currentAwayLocation[(t,d2)]: + t1 = currentAwayLocation[(t,d1)] + for (t2,w) in bad_travel_out[t1]: + if t2==currentAwayLocation[(t,d2)]: + print ("TRAVEL BACK TO BACK FOUND WITH WEIGHT ", w, ":", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[currentAwayLocation[(t,d1)]] , " -> " , getTeamById[currentAwayLocation[(t,d2)]] ) + model2 += lpSum([x[(t1,t,rd1)] for rd1 in rd1s ]) + lpSum([x[(t2,t,rd2)] for rd2 in rd2s ]) <= 1 + 0.001/w*badBackToBack[(t,d1)] + + # todo : add red teams to distant_teams here to take care that trips do not start and end with bad b2b + # print ("after home game no away game at " , rd1s, rd2s, "for ", t , " in ", distant_teams[t]) + if not blocked_arena[(t,d1,"----")]: + model2 += home[(t,d1)] + lpSum([ wg*x[(t2,t,rd2)] for rd2 in rd2s for (t2,wg) in bad_travel_out[t] if gameCntr[(t2,t)]>0 ]) <= 1 + 0.001*badBackToBack[(t,d1)] + if not blocked_arena[(t,d2,"----")]: + # if d1==32363 and getTeamById[t]=="Cleveland Cavaliers": + # print (" ++++ ",getTeamById[t] , rd1s, bad_travel_in[t] ) + # for t33 in bad_travel_in[t]: + # print("+++++ " ,t33, getTeamById[t33] , gameCntr[(t33,t)]) + # print (" ++++ ",lpSum([ wg*x[(t2,t,rd1)] for rd1 in rd1s for (t2,wg) in bad_travel_in[t] if gameCntr[(t2,t)]>0 ]) ) + model2 += lpSum([ wg*x[(t2,t,rd1)] for rd1 in rd1s for (t2,wg) in bad_travel_in[t] if gameCntr[(t2,t)]>0 ]) + home[(t,d2)] <= 1 + 0.001*badBackToBack[(t,d1)] + + badBackToBack_Total = lpSum([badBackToBack[(t,d1)] for t in teams for (d1,d2) in critical_day_pairs]) + specialObjectives+= 50*gew['Breaks']* badBackToBack_Total +# END SPECIAL CONSTRAINTS + +model2+= standardObjectives +specialObjectives + +print("Model built now solving .... ") + +nRuns =1 +maxSolveTime = 300 + +if thisSeason.groupBased: + maxSolveTime = 40 + +mipgap=0.01 + +# print ("######## Testing") +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) + + +use_LP_heuristic= False +print (runMode=='New' , useBasicGames , runHeuristicModelFirst) + +if runMode=='New' and useBasicGames and runHeuristicModelFirst: + print ('Coupling Home Away to patterns', use_LP_heuristic) + + if use_LP_heuristic: + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=maxSolveTime,msg=1)) + roundedGames = { (t1,t2,r) : gameInBasicRound[(t1,t2,r)].value() for r in rounds1 for t1 in realteams for t2 in realteams if t1!=t2 } + + for g in roundedGames: + if roundedGames[g]>0: + print (g, " : " ,roundedGames[g]) + + model5 = pulp.LpProblem(f"{PULP_FOLDER}/League Scheduling Model -- LP heuristic_"+str(thisScenario.id), pulp.LpMinimize) + x5={(t1,t2,r) : pulp.LpVariable('x4_'+str(t1)+'_'+str(t2)+'_'+str(r), lowBound = 0, upBound = 1, cat = pulp.LpInteger) for (t1,t2,r) in roundedGames.keys() } + for t1 in realteams: + for t2 in realteams: + if t10.1: + print (r, " " , t1," - " , t2, " : " , roundedGames[(t1,t2,r)]) + gameInBasicRound[(t1,t2,r)].lowBound = 1 + gameInBasicRound[(t1,t2,r)].upBound = 1 + gameInBasicRound[(t2,t1,r+nTeams-1)].lowBound = 1 + gameInBasicRound[(t2,t1,r+nTeams-1)].upBound = 1 + + else: + # use2BreakPatterns + undecidedGames = gameCntr.copy() + for t in realteams: + for r in basicRounds: + if r > 0 and runHeuristicModelFirst and False: + # print ("homeInBasicRound[(",str(t),",",str(r), " ] == " , int(homePat[(t,r)].value())) + # print ("awayInBasicRound[(",str(t),",",str(r), " ] == " , 1-homePat[(t,r)].value()) + # homeInBasicRound[(t,r)].lowBound = homePat[(t,r)].value() + homeInBasicRound[(t,r)].upBound = homePat[(t,r)].value() + # awayInBasicRound[(t,r)].lowBound = 1-homePat[(t,r)].value() + awayInBasicRound[(t,r)].upBound = awayPat[(t,r)].value() + + for (t1,t2) in games: + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = 1 + gameInBasicRound[(t1,t2,r)].upBound = 0 + + cntr =0 + for (t1,t2,r) in chosenGames: + # print (t1,t2,r) + homers = [t1] + regionalKids[t1] + awayers = [t2] + regionalKids[t2] + # print (" chosen game " ,r, homers , awayers, getRoundDaysByBasicRound[r] , getMaxGameOnRoundDaysByBasicRound[r],defaultGameRepetions) + # print (" chosen game " , getMaxGameOnRoundDaysByBasicRound[r]) + theseRounds = sorted(list(set([ r2 for (r2,d) in getRoundDaysByBasicRound[r]]))) + if len(theseRounds)>0: + fr = theseRounds[0] + lr = theseRounds[-1] + # print ( " RDS " , theseRounds , theseRounds[0], min([ r2 for (r2,d) in getRoundDaysByBasicRound[r]]) ) + for t11 in homers: + for t22 in awayers: + #print (" - chosen game " , getTeamById[t11], getTeamById[t22], getRoundDaysByBasicRound[r] , getMaxGameOnRoundDaysByBasicRound[r]) + # model2 += gameInBasicRound[(t11,t22,r)] >= 1 - 0.9*missingGamesVio[(t11,t22)] + if (t11,t22,r) in gameInBasicRound.keys(): + gameInBasicRound[(t11,t22,r)].lowBound = defaultGameRepetions-1 + gameInBasicRound[(t11,t22,r)].upBound = defaultGameRepetions + # print ("setting ",(t11,t22,r) , defaultGameRepetions-1, defaultGameRepetions ) + + if tripStartHeuristicGroupsize>=2 and False: + model2+= x_round[(t11,t22,fr)] == x_round[(t11,t22,fr+1)] + model2+= x_round[(t11,t22,fr+2)] == x_round[(t11,t22,fr+3)] + # print ("setting " , t11,t22, " twice") + # model2+= x_round[(t1,t2,lr)] == x_round[(t1,t2,lr-1)] + + # print ( t11,t22, (t11,t22) in undecidedGames, undecidedGames) + undecidedGames[(t11,t22)]-=defaultGameRepetions + cntr+=1 + else: + print ("PROBLEM " , theseRounds, homers, awayers, r) + print (cntr, " games set") + # print (len(undecidedGames), " undecided Games :", undecidedGames) + for (t1,t2) in games: + # if gameCntr[(t1,t2)]>0: + # print ("to be scheduled : " , getTeamById[ t1], " --- " , getTeamById[ t2] , gameCntr[(t1,t2)]) + if undecidedGames[(t1,t2)]!=0 and undecidedGames[(t1,t2)]!=-undirectedGameCntr[(t1,t2)]: + print ("not scheduled yet " , getTeamById[ t1], " --- " , getTeamById[ t2] , undecidedGames[(t1,t2)]) + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].upBound = defaultGameRepetions + + for ttr in x.keys(): + makeIntVar(x[ttr]) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 120, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=0.3, TimeLimit=180,msg=1)) + else: + model2.solve(XPRESS(msg=1,maxSeconds = 120, options=["THREADS=12"], keepFiles=True)) + + for ttr in x.keys(): + if getVal(x[ttr])>0.9: + # print ( "SETTING GAME ", ttr, getVal(x[ttr]) ) + setLB(x[ttr],1) + + for (t1,t2) in games: + for r in basicRounds: + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = defaultGameRepetions + # if gameInBasicRound[(t1,t2,r)].value()>0 and gameInBasicRound[(t1,t2,r)].value()<2 : + # print ("??",getTeamById[t1],getTeamById[t2],r, gameInBasicRound[(t1,t2,r)].value()) + + + + +# # TEST START +# for t1 in realteams: +# for t2 in realteams: +# if t1!=t2: +# for r in basicRounds: +# gameInBasicRound[(t1,t2,r)].lowBound = 0 +# gameInBasicRound[(t1,t2,r)].upBound = 0 + +# cntr =0 +# undecidedGames = [ (t1,t2) for t1 in realteams for t2 in realteams if t1!=t2 ] +# for (t1,t2,r) in chosenGames: +# homers = [t1] + regionalKids[t1] +# awayers = [t2] + regionalKids[t2] +# for t11 in homers: +# for t22 in awayers: +# gameInBasicRound[(t11,t22,r)].lowBound = 1 +# gameInBasicRound[(t11,t22,r)].upBound = 1 +# undecidedGames.remove((t11,t22)) +# cntr+=1 +# print (cntr, " games set") +# print (len(undecidedGames), " undecided Games :", undecidedGames) +# for (t1,t2) in undecidedGames: +# for r in basicRounds: +# gameInBasicRound[(t1,t2,r)].upBound = 1 + +# for ttr in x.keys(): +# x[ttr].cat= LpInteger + +# if solver == "CBC": +# model2.solve(PULP_CBC_CMD(fracGap = 0.0, maxSeconds = 40, threads = 8,msg=1)) +# elif solver == "Gurobi": +# model2.solve(GUROBI(MIPGap=0.0, TimeLimit=120,msg=1)) +# else: +# model2.solve(XPRESS(msg=1,maxSeconds = 25, keepFiles=True)) + +# for ttr in x.keys(): +# if x[ttr].value()>0.01: +# print ( ttr, x[ttr].value() ) + + +# # TEST END + + +else: + nRuns=nPhases + # maxSolveTime = 120 + mipgap=0.0 + # mipgap=0.95 + +# nRuns =1 +onlyReopt= True +onlyReopt= False +onlyFewTrips= False +singleTripWeight =10 + +if onlyReopt: + nRuns=0 + +oldKPIs = { k[0]:k[1] for k in [kk.split("__") for kk in thisScenario.sol_kpis.split("___")]} + +maxIntRound=nRounds + +print (optSteps) + +missing_imp=[] +cntr =0 +for st in optSteps: + print (" - " ,st ) + cntr +=1 + if runMode=='New' and st[0] == "HARDCONSTRAINTS": + for bl in blockings: + blockingVio[bl['id']].upBound=0 + print ("blocking tightened : " , getTeamById [bl['team_id']] , bl['day_id'] ) + for enc in encwishes: + if enc['seed']: + encVio[enc['id']].upBound=0 + + + if runMode=='New' and len(st)>=1 and st[0] in ["PATTERNS","HOMEAWAY", "BASICGAME", "GAME","GAMES", "GROUP", "TRIPS", "LP-HEURISTIC"]: + newRounds = [] + print() + optTarget = st[0] + if len(st)>1 : + for rf in st[1].split(","): + rr= rf.split("-") + if len(rr)==1 : + newRounds.append(min(nRounds,int(rr[0]))) + else: + for ii in range (int(rr[0]), min(nRounds, int(rr[1]))+1): + newRounds.append(ii) + newRoundsString = st[1] + else : + newRounds = rounds + newRoundsString = "1-"+str(nRounds) + + optsteptime = maxSolveTime + optstepgap = mipgap + + if len(st)>=3 and st[2]!="": + optstepgap = float(st[2]) + if len(st)>=4 and st[3]!="": + optsteptime = float(st[3]) + + print (newRounds) + + # if not thisSeason.minBreaks and runMode=='Improve': + if st[0] == "LP-HEURISTIC": + print("STARTING LP HEURISTIC") + + if st[0] == "PATTERNS": + for (p,t,ph) in assignPattern2.keys(): + if ph==0 or not thisSeason.symmetry: + assignPattern2[(p,t,ph)].cat = pulp.LpInteger + + + if st[0] == "HOMEAWAY": + for t in teams: + for r in newRounds: + homeInRound[(t,r)].cat = pulp.LpInteger + + if st[0] == "BASICGAME": + for (t1,t2) in games: + for r in newRounds: + gameInBasicRound[(t1,t2,r)].cat = pulp.LpInteger + + if st[0] in ["GAME","GAMES"]: + for (t,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t,t2,rd)]) + # if thisSeason.gamesPerRound=="one day": + # makeIntVar(x[(t,t2,getRoundDaysByRound[r][0])]) + # print ("makeintvar " ,t1,t2, getRoundDaysByRound[r][0],getRoundDaysByRound[r]) + # else: + # for rd in getRoundDaysByRound[r]: + # makeIntVar(x[(t,t2,rd)]) + + getSingleTripElementsByRound = { r : [] for r in rounds} + if st[0] in ["TRIP","TRIPS"]: + singleTripWeight =1000 + # onlyFewTrips= True + for (t1,d,c) in tripToSingleTripElement.keys(): + getSingleTripElementsByRound[getRoundByDay[d]].append((t1,d,c)) + for r in newRounds: + for tdc in getSingleTripElementsByRound[r]: + makeIntVar(tripToSingleTripElement[tdc]) + + if st[0] == "GROUP" and len(st)>=5: + cfname = st[4].strip() + if cfname in conf_teams.keys(): + optTarget += " "+ cfname + print ("checking GROUP", st[4].strip()) + for (t1,t2) in games: + optstepgap = mipgap + if t1 in conf_teams[cfname] and t2 in conf_teams[cfname]: + print ("REOPT GROUP ", cfname , t1,t2) + for r in newRounds: + makeIntVar(x_round[(t1,t2,r)]) + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t1,t2,rd)]) + + + # optsteptime= 30 + + print ('########################') + print ('# SOLVING MODEL '+optTarget+' FOR ROUNDS '+ newRoundsString+' USING GAP ' + str(optstepgap) + ' and MAXTIME ' + str(optsteptime) + ' SOLVER '+solver+' #') + print ('########################') + writeProgress("Optimize "+st[0]+" for rounds " + newRoundsString, thisScenario.id, int( cntr/len(optSteps)*100 )) + + if RUN_ENV == 'celery' and task: + task.update_state(state='Solving Model '+str(st[0]), meta={'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = optstepgap, maxSeconds = optsteptime, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=optstepgap, TimeLimit=optsteptime,msg=1, Method=2,NodeMethod=2)) + elif solver == "Xpress": + model2.solve(XPRESS(msg=1,targetGap=optstepgap, maxSeconds = optsteptime, options=["THREADS=12,DEFAULTALG=4,DETERMINISTIC=0,CUTSTRATEGY=0"], keepFiles=True)) + else: + # for debugging: + # with open ("model2.txt", "w") as f: + # f.write(model2.__repr__()) + model2.solve(XPRESS_PY(msg=1,gapRel=optstepgap, timeLimit = optsteptime)) + + if model2.status<0: + print("Status: " , model2.status) + + if model2.status in [0,-1,-2]: + report_solverstatus(user_name,thisScenario) + + if solver != "xpress": + writeProgress("Model infeasible .", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print( {'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print( 'Model could not be solved') + + if not lowerBoundFound: + if solver == "XPRESS_PY": + lowerBoundFound= XPRESS_PY.getAttribute(XPRESS_PY, model2, "bestbound") + print("LOWER BOUND " ,lowerBoundFound ) + + cntr_rnd =0 + if st[0] == "LP-HEURISTIC": + for (t1,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.65: + setLB(x[(t1,t2,rd)],1) + print ("rounding up " , getTeamById[t1], "-" ,getTeamById[t2], rd , getVal(x[(t1,t2,rd)])) + cntr_rnd +=1 + # print (t1,t2,rd) + # else: + # setUB(x[(t1,t2,rd)],0) + # # print(t1,t2 , " no game ") + # # x[(t1,t2,rd)].upBound = 0 + print ("Totally rounded up " , cntr_rnd) + + + if st[0] == "PATTERNS": + for (p,t,ph) in assignPattern2.keys(): + if ph==0 or not thisSeason.symmetry: + if assignPattern2[(p,t,ph)].value() >0.9 : + print ('fixing pattern '+ str(p) + ' : '+ getTeamById[t] +" in phase " + str(ph)) + assignPattern2[(p,t,ph)].lowBound = 1 + else: + assignPattern2[(p,t,ph)].upBound = 0 + + + if st[0] == "HOMEAWAY": + print (teams) + print (newRounds) + for t in teams: + for r in newRounds: + if homeInRound[(t,r)].value() >0.9 : + print ('fixing home '+ str(r) + ' : '+ getTeamById[t] +" " + str(homeInRound[(t,r)].value())) + homeInRound[(t,r)].lowBound = 1 + else: + homeInRound[(t,r)].upBound = 0 + + + if st[0] == "BASICGAME": + for r in newRounds: + for (t1,t2) in games: + if getTeamById[t1]!="-" and getTeamById[t2]!="-" and (t1,t2,r) in gameInBasicRound.keys() and type(gameInBasicRound[(t1,t2,r)])!= int: + if getVal(gameInBasicRound[(t1,t2,r)])>0.9: + gameInBasicRound[(t1,t2,r)].lowBound = 1 + else: + gameInBasicRound[(t1,t2,r)].upBound = 0 + # print (r, ' : ', getTeamById[t1], ' - ',getTeamById[t2] ) + + if st[0] in ["GAME","GAMES"]: + # crappyGames = [ g for g in fixedGames if fixedGameVio[g].value() >0.9 ] + feedback = "Optimize games...." + missing_imp=[] + for (t1,t2,d) in fixedGames: + if fixedGameVio[(t1,t2,d)].value() >0.9: + feedback += 'Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + '
' + for (t1,t2,d) in fixedGames2: + if fixedGame2Vio[(t1,t2,d)].value() >0.9: + feedback += 'Not fixed :' +getDayById[d]['day'] +' : '+getTeamById[t1] + ' - ' + getTeamById[t2] + '
' + for (t1,t2) in realgames: + if missingGamesVio[(t1,t2)].value() >0.9: + feedback += 'Game missing : '+getTeamById[t1] + ' - ' + getTeamById[t2] + ' ' + str(missingGamesVio[(t1,t2)].value()) + '
\n' + missing_imp += [(1,nRounds,[t1,t2], 50)] + + print (missing_imp) + print (feedback) + print ("number of assigned games : " , sum([getVal(x[ttrd]) for ttrd in x.keys()])) + + for (t1,t2) in games: + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.9: + setLB(x[(t1,t2,rd)],1) + print ("fixing " ,t1,t2,rd, x[(t1,t2,rd)].lowBound ) + else: + setUB(x[(t1,t2,rd)],0) + # print(t1,t2 , " no game ") + # x[(t1,t2,rd)].upBound = 0 + + # for (t1,t2,rd) in x.keys(): + # if getVal(x[(t1,t2,rd)])>0.9: + # print ("setLB(x[",t1,",",t2, "," , rd , "],1)") + + if st[0] in ["TRIP","TRIPS"]: + for r in newRounds: + for tdc in getSingleTripElementsByRound[r]: + if getVal(tripToSingleTripElement[tdc])>0.9: + setLB(tripToSingleTripElement[tdc],1) + print ("fixing " ,tdc, tripToSingleTripElement[tdc] ) + else: + setUB(tripToSingleTripElement[tdc],0) + + if st[0] == "GROUP" and len(st)>=5: + print ("fixing GROUP", st[4].strip()) + for (t1,t2) in games: + if t_conference[t1]!=0 and t_conference[t2]!=0 and st[4].strip() in [ t_conference[t2].name, t_conference[t1].name] : + # print ("checking in group ", t1,t2) + for r in newRounds: + for rd in getRoundDaysByRound[r]: + if getVal(x[(t1,t2,rd)])>0.99: + # if x[(t1,t2,rd)].value()>0.99: + setLB(x[(t1,t2,rd)],1) + # print ("fixing in GROUP",t1,t2,rd) + # print (t1,t2,rd) + else: + setUB(x[(t1,t2,rd)],0) + + for r in basicRounds: + for t1 in realteams: + homeInBasicRound[(t1,r)].lowBound = 0 + homeInBasicRound[(t1,r)].upBound = 10 + awayInBasicRound[(t1,r)].lowBound = 0 + awayInBasicRound[(t1,r)].upBound = 10 + for t2 in opponents[t1]: + if (t1,t2) in games: + gameInBasicRound[(t1,t2,r)].cat = pulp.LpContinuous + gameInBasicRound[(t1,t2,r)].lowBound = 0 + + debug = False + if debug: + print (str(blockingVioTotal.value()) , " violated Blockings out of " , nBlockingHome ) + for bl in blockings : + if (blockingVio[bl['id']].value()>0.9) and bl['type'] in ["Home", "Hide","Game"] : + print (bl ) + + print (str(travelVioTotal.value()) , " violated Travel Restrictions out of " , nBlockingAway ) + for bl in blockings : + if (blockingVio[bl['id']].value()>0.9) and bl['type']=='Away' : + print (bl ) + + if gew['Breaks'] > 0: + print (str(breakVioTotal.value()) , " Breaks :" ) + for bl in breaks : + for t in realteams: + if (breakVio[(bl['id'],t)].value()>0.9) : + print (bl, ' ' , str(t) ) + + if gew['Home-/Away'] > 0: + print ("Violated HA-Wishes out of " , len(hawishes) ) + for haw in hawishes : + for el in elemHaWishes[haw['id']]: + if (HawVioTooLess[el].value()+HawVioTooMuch[el].value() >0.9) : + print (haw) + + if gew['Encounters'] > 0: + print ("Violated Encounter-Wishes out of " , nElemEncWishes ) + for enc in encwishes : + if (encVio[enc['id']].value()>0.9) : + print (enc) + + + for t in realteams: + for r in newRounds: + homeInRound[(t,r)].cat = pulp.LpContinuous + for t2 in opponents[t]: + for rd in getRoundDaysByRound[r]: + # if x[(t,t2,d)].value() >0.1 : + # print ('looking '+ str(r) + ' : '+ getTeamById[t] + ' - '+ getTeamById[t2] + " " + str(x[(t,t2,d)].value())) + if (t,t2) in games and getVal(x[(t,t2,rd)]) >0.9 : + # print ('fixing '+ str(rd) + ' : '+ getTeamById[t] + ' - '+ getTeamById[t2] ) + setLB(x[(t,t2,rd)], x[(t,t2,rd)].value()) + currentSolution =[ (t1,t2,r,d) for (t1,t2) in realgames for (r,d) in roundDays if getVal(x[(t1,t2,(r,d))]) >0.9 ] + +# print ("####################") +# print ("testing feasibility 2") +# print ("####################") +# model2.solve(XPRESS(msg=1,maxSeconds = 10 , keepFiles=True)) +# print ("####################") +# print ("testing done") +# print ("####################") + +print (impScript) + +for r in rounds: + for t in teams: + homeInRound[(t,r)].lowBound = 0 + homeInRound[(t,r)].upBound = 1 + +for (t1,t2) in games: + for r in rounds: + for rd in getRoundDaysByRound[r]: + makeIntVar(x[(t1,t2,rd)]) + +if runMode!='Improve': + if mathModelName=="NBA": + print ("buidling badRepeaters") + badRepeaters = [ (t,r) for (t,r) in badRepeater.keys() if badRepeater[(t,r)].value()>0.9] + for (t,r) in badRepeaters : + print ("bad repeater :",r, getTeamById[t]) + + if thisSeason.useFeatureBackToBack: + print ("buidling badBackToBackers") + badBackToBackers = [ (t,r) for (t,r) in badBackToBack.keys() if badBackToBack[(t,r)].value()>0.9] + print ("done buidling badBackToBackers") + for (t,r) in badBackToBackers : + print ("bad back to back :", int(badBackToBack[(t,r)].value()) , getNiceDay[r], getTeamById[t] ) + +tightenBlockings = nTeams>20 and nRounds>60 +if runMode=='Improve': + if tightenBlockings: + for bl in blockings: + if blockingVio[bl['id']].value()==0: + blockingVio[bl['id']].upBound=0 + print ("blocking tightened : " , bl['team'] , bl['day'] ) + +print ('Solved Again') +print ('NOW REOPT') + +mipgap=0.05 + + +starweights= sorted([ starweight[t] for t in teams], reverse = True) + +localsearch_time = max(0,min(localsearch_time, 0.9*TASK_TIME_LIMIT-(time.time()-start_time))) + +if runMode == 'New' and localsearch_time == 0: + localsearch = False +else: + localsearch = True + +# localsearch_time=10 + +print (starweights) + +nFarTeams = 1 +if gew['Trips'] > 5: + # nFarTeams += gew['Trips'] -5 + nFarTeams = nTeams + +nFarTeams = min(nFarTeams,len(starweights)-1) + +print ("nFarTeams ", nFarTeams) +print (starweight) + +farTeams = [ t for t in teams if starweight[t] > starweights[nFarTeams] ] +print ("farTeams ", farTeams) + +# cntr=0 +# for (t,t1,t2,r) in tripSaving.keys(): + # if t in farTeams or t1 in farTeams or t2 in farTeams : + # # print ("consider trip " , (t,t1,t2,r) ) + # cntr+=4 + # tripSaving[(t,t1,t2,r)].upBound =1 + # model2 += tripSaving[(t,t1,t2,r)] <= lpSum([ (x[(t1,t,(r,d) )]+x[(t2,t,(r,d))]) for d in [ latestDay[r] ] ]) + # model2 += tripSaving[(t,t1,t2,r)] <= lpSum([ (x[(t1,t,(r+1,d))]+x[(t2,t,(r+1,d))]) for d in [earliestDay[r+1]] ]) + # model2 += tripSaving[(t,t1,t2,r)] <= lpSum([ (x[(t1,t,(r,latestDay[r]))]+x[(t1,t,(r+1,earliestDay[r+1]))]) ]) + # model2 += tripSaving[(t,t1,t2,r)] <= lpSum([ (x[(t2,t,(r,latestDay[r]))]+x[(t2,t,(r+1,earliestDay[r+1]))]) ]) +# print (str(cntr) + ' constraints added' ) + +local_start =time.time() + +last_objective =value(model2.objective) + +forbidRepetitions=True +forbidRepetitions=False + +useDailyTrips = nRounds*nTeams <=200 + +print ("useDailyTrips" ,useDailyTrips , nRounds , nTeams ) + +if localsearch: + cntr =0 + if thisSeason.useFeatureTrips and initTripsLate: + if thisSeason.tripMode=="Clusters": + if useDailyTrips: + for d1 in days: + otherTripDays = [ d2 for d2 in days if getDateTimeDay[d1]< getDateTimeDay[d2] and getDateTimeDay[d2]<=getDateTimeDay[d1]+datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1) and (len(getRoundsByDay[d1])*len(getRoundsByDay[d2])>1 or list(getRoundsByDay[d1])!=list(getRoundsByDay[d2]) )] + + for t in realteams: + for c in clusters: + if gew['Trips']>0 and not c in t_clusters[t] and len(otherTripDays)>0: + tripToClusterDaily[(t,d1,c)].upBound=1 + # tripToClusterDaily[(t,d1,c)] = pulp.LpVariable('tripToClusterDaily_'+str(t)+'_'+str(d1)+'_'+str(c), lowBound = 0, upBound = 1, cat = pulp.LpContinuous) + model2 += tripToClusterDaily[(t,d1,c)] <= away_in_cluster_day[t,d1,c] + model2 += tripToClusterDaily[(t,d1,c)] <= lpSum([away_in_cluster_day[t,d2,c] for d2 in otherTripDays ]) + # print ('considering trip of ' , getTeamById[t], ' in days ' , getNiceDay[d1] , getNiceDay[otherTripDays[0]] , otherTripDays,' to cluster ' , c , cluster_teams[c]) + cntr +=2 + else : + for r in rounds: + otherTripRounds = [r2 for r2 in rounds if r2>r and getDateTimeDay[latestDay[r]]<= getDateTimeDay[earliestDay[r2]] and getDateTimeDay[earliestDay[r2]]-getDateTimeDay[latestDay[r]]<=datetime.timedelta(days=thisSeason.maxDistanceWithinTrip+1)] + # print ("trip rounds ", r, otherTripRounds) + for t in realteams: + # forbid same opponents on successive days + for t2 in realteams: + if t!=t2 and r>1 and forbidRepetitions: + model2 += lpSum([ (x[(t,t2,rd )]+x[(t2,t,rd)]) for rd in getRoundDaysByRound[r-1]+getRoundDaysByRound[r] ]) <= 1 + + for c in clusters: + if len(otherTripRounds)>0 and gew['Trips']>0 and not c in t_clusters[t] : + tripToCluster[(t,r,c)].upBound=1 + model2 += tripToCluster[(t,r,c)] <= away_in_cluster[t,r,c] + model2 += tripToCluster[(t,r,c)] <= lpSum([away_in_cluster[t,r2,c] for r2 in otherTripRounds ]) + # print ('considering trip of ' , getTeamById[t], ' in rounds ' , r , otherTripRounds,' to cluster ' , c ) + cntr +=2 + + # else : + # model2 += tripToCluster[(t,r,c)] == 0 + else: + tripDayPairs = {} + for d1 in days: + for d2 in days: + daysBetween = (getDateTimeDay[d2]-getDateTimeDay[d1]).days -1 + if daysBetween>=0 and getDayMaxGames[d1]>0 and getDayMaxGames[d2]>0 and daysBetween<=thisSeason.maxDistanceWithinTrip and getRoundByDay[d1]!=getRoundByDay[d2]: + # print (d1,d2,daysBetween, getNiceDay[d1], getNiceDay[d2], ) + if d1 not in tripDayPairs.keys(): + tripDayPairs[d1]=[] + tripDayPairs[d1].append((d2, daysBetween)) + + + for t in teams: + # print (getTeamById[t]) + # print (getTeamById[t],tripElements) + for (c,v1,v2,v3,v4) in tripElements[t]: + # if getTeamById[t]=="Regatas": + # print ("POSS TRIP ",getTeamById[t], (c, [ getTeamById[t3] for t3 in v1 ] , v2, [ getTeamById[t3] for t3 in v2 ] , [ getTeamById[t3] for t3 in v3 ] , [ getTeamById[t3] for t3 in v4 ]), trip_minDays[c], trip_maxDays[c]) + for d1 in tripDayPairs.keys(): + if getWeekDay[d1] in trip_weekdays[c] and ( not onlyFewTrips or t%6==d%6 ) : + otherTripDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]] + inBetweenDays = [d2 for (d2,daysBetween) in tripDayPairs[d1] if daysBetween<=trip_minDays[c] ] + otherTripDays3 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + otherTripDays4 = list(set([d2 for d11 in tripDayPairs.keys() for (d2,daysBetween) in tripDayPairs[d11] if d11 in otherTripDays3 and daysBetween>=trip_minDays[c] and daysBetween<=trip_maxDays[c]])) + # print ("possible? ", getTeamById[t] , getNiceDay[d1], otherTripDays,otherTripDays3,otherTripDays4) + if len(otherTripDays)>0 and (thisSeason.tripLength <3 or len (v3)==0 or len(otherTripDays3)>0) and (thisSeason.tripLength <4 or len (v4)==0 or len(otherTripDays4)>0) : + # print ("building trip for " , getTeamById[t], " starting" , getNiceDay[d1] , [ getNiceDay[d] for d in otherTripDays ], [ getNiceDay[d] for d in otherTripDays3 ] , [ getNiceDay[d] for d in otherTripDays4 ] ) + tripToSingleTripElement[(t,d1,c)].upBound=1 + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v1 for rd in getRoundDaysByDay[d1]]) + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + for d3 in inBetweenDays: + model2 += tripToSingleTripElement[(t,d1,c)] <= 1- home[t,d3] + model2 += tripToSingleTripElement[(t,d1,c)] <= 1-lpSum([ x[(t2,t,rd)] for t2 in realteams if t2 not in v2 for rd in getRoundDaysByDay[d3]]) + # if getTeamById[t]=="Regatas": + # print (" ", getNiceDay[d1], [ getNiceDay[d2] for d2 in inBetweenDays ] ,[ getNiceDay[d2] for d2 in otherTripDays ] ," <= ", [ (t2,t,rd) for t2 in v2 for d2 in otherTripDays for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=3 and len(v3)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v3 for d2 in set(otherTripDays3) for rd in getRoundDaysByDay[d2]]) + if thisSeason.tripLength >=4 and len(v4)>0: + model2 += tripToSingleTripElement[(t,d1,c)] <= lpSum([ x[(t2,t,rd)] for t2 in v4 for d2 in set(otherTripDays4) for rd in getRoundDaysByDay[d2]]) + if (len(v3)>0 and len(otherTripDays3)==0) or (len(v4)>0 and len(otherTripDays4)==0) : + tripToSingleTripElement[(t,d1,c)].upBound=0 + else: + cntr +=2 + + print ("built ", cntr , " trip constraints late") + + for t1 in realteams: + for r in rounds: + homeInRound[(t1,r)].lowBound = 0 + homeInRound[(t1,r)].upBound = 1 + awayInRound[(t1,r)].lowBound = 0 + awayInRound[(t1,r)].upBound = 1 + for rd in roundDays: + for t2 in opponents[t1]: + setLB(x[(t1,t2,rd)],0) + + for (t1,t2) in games: + for r in rounds: + if (t1,t2,r) in gameInBasicRound.keys(): + gameInBasicRound[(t1,t2,r)].lowBound = 0 + gameInBasicRound[(t1,t2,r)].upBound = 10 + for rd in getRoundDaysByRound[r]: + setLB(x[(t1,t2,rd)],0) + setUB(x[(t1,t2,rd)],1) + + mipgap=0.00 + # if thisSeason.useFeatureTrips and thisLeague.name not in ["EuroLeague Basketball", "La Liga Argentina"] : + # mipgap=0.02 + + maxSolveTime= 2000 + nReopt=4 + nReopt=1*nTeams+5 + for (t1,t2) in games: + for rd in roundDays: + setLB(x[(t1,t2,rd)],0) + + # restore solution just created + print ("RESTORING OLD SOLUTION !!!") + if runMode=='New' : + use_currentSolution= True + + # print ("TESTING") + # model2+= lpSum([ fixedGameVio[(t1,t2,d)] for (t1,t2,d) in fixedGames]) + # model2+= standardObjectives + # for ttr in x.keys(): + # makeIntVar(x[ttr]) + # model2.solve(GUROBI(MIPGap=0.0, TimeLimit=40,msg=1)) + # for (t1,t2,d) in fixedGames: + # if fixedGameVio[(t1,t2,d)].value() >0.1: + # print ("Games missing : " , getTeamById[t1] , getTeamById[t2] , getNiceDay[d]) + # print ("TESTING DONE9") + + + # print ("\n###############\nTIME TAKEN SO FAR : " , time.time()-start_time, "\n##################\n") + + # startSolution = [(41994, 42001, 3, 69446), (41994, 42172, 6, 69452), (41994, 42177, 8, 69456), (41994, 42235, 2, 69445), (42002, 42085, 2, 69444), (42002, 42203, 3, 69447), (42002, 42181, 7, 69454), (42002, 42065, 5, 69451), (42173, 42175, 8, 69456), (42173, 41998, 2, 69445), (42173, 42166, 5, 69450), (42173, 42030, 6, 69453), (42001, 42002, 8, 69456), (42001, 42008, 4, 69449), (42001, 42179, 2, 69445), (42001, 42236, 6, 69453), (41999, 42173, 4, 69448), (41999, 42176, 7, 69454), (41999, 42006, 2, 69445), (41999, 42009, 6, 69453), (42085, 42040, 5, 69451), (42085, 41997, 1, 69442), (42085, 42158, 4, 69449), (42085, 42082, 7, 69455), (42040, 41999, 1, 69442), (42040, 41996, 6, 69452), (42040, 42039, 8, 69456), (42040, 42238, 3, 69447), (42205, 41994, 1, 69441), (42205, 42036, 4, 69448), (42205, 42207, 7, 69455), (42205, 42180, 6, 69452), (42175, 42205, 3, 69447), (42175, 42004, 1, 69441), (42175, 42041, 7, 69454), (42175, 42237, 6, 69453), (41998, 41994, 4, 69449), (41998, 42036, 5, 69450), (41998, 42179, 7, 69455), (41998, 42082, 1, 69443), (42036, 42085, 6, 69452), (42036, 42176, 3, 69447), (42036, 42166, 1, 69443), (42036, 42238, 7, 69455), (42008, 41999, 8, 69456), (42008, 41996, 3, 69446), (42008, 42177, 6, 69452), (42008, 42009, 1, 69442), (42004, 42001, 7, 69454), (42004, 42008, 5, 69450), (42004, 42039, 3, 69447), (42004, 42065, 2, 69444), (42203, 42040, 2, 69444), (42203, 41998, 8, 69456), (42203, 42158, 6, 69452), (42203, 42030, 4, 69448), (41997, 42175, 5, 69451), (41997, 42172, 2, 69445), (41997, 42181, 4, 69449), (41997, 42237, 7, 69455), (42176, 42002, 6, 69453), (42176, 41997, 8, 69456), (42176, 42207, 4, 69448), (42176, 42180, 1, 69443), (42172, 42173, 1, 69441), (42172, 42203, 5, 69451), (42172, 42006, 7, 69454), (42172, 42235, 4, 69448), (41996, 42205, 5, 69451), (41996, 42004, 8, 69456), (41996, 42041, 4, 69449), (41996, 42236, 2, 69445), (42166, 41994, 7, 69455), (42166, 42172, 3, 69446), (42166, 42158, 2, 69445), (42166, 42235, 6, 69453), (42041, 42205, 2, 69445), (42041, 41997, 3, 69446), (42041, 42166, 8, 69456), (42041, 42065, 6, 69453), (42181, 42001, 5, 69450), (42181, 41998, 3, 69446), (42181, 42207, 2, 69444), (42181, 42030, 8, 69456), (42006, 42173, 3, 69447), (42006, 41996, 1, 69443), (42006, 42181, 6, 69453), (42006, 42009, 8, 69456), (42177, 42040, 7, 69455), (42177, 42176, 2, 69444), (42177, 42039, 5, 69451), (42177, 42237, 3, 69447), (42039, 42175, 4, 69449), (42039, 42008, 2, 69444), (42039, 42179, 6, 69452), (42039, 42180, 7, 69455), (42207, 42085, 8, 69456), (42207, 42004, 6, 69453), (42207, 42177, 1, 69442), (42207, 42082, 3, 69447), (42179, 42002, 4, 69448), (42179, 42203, 1, 69442), (42179, 42006, 5, 69451), (42179, 42236, 8, 69456), (42158, 41999, 3, 69446), (42158, 42036, 8, 69456), (42158, 42041, 1, 69443), (42158, 42238, 5, 69450), (42235, 42002, 1, 69441), (42235, 42176, 5, 69450), (42235, 42158, 7, 69455), (42235, 42009, 3, 69446), (42082, 42175, 2, 69444), (42082, 41997, 6, 69452), (42082, 42166, 4, 69449), (42082, 42065, 8, 69456), (42065, 42001, 1, 69443), (42065, 41996, 7, 69454), (42065, 42177, 4, 69448), (42065, 42236, 3, 69447), (42180, 41999, 5, 69451), (42180, 42172, 8, 69456), (42180, 42179, 3, 69446), (42180, 42238, 2, 69444), (42030, 42085, 3, 69446), (42030, 42008, 7, 69454), (42030, 42207, 5, 69450), (42030, 42237, 2, 69445), (42236, 42040, 4, 69448), (42236, 42203, 7, 69454), (42236, 42181, 1, 69442), (42236, 42082, 5, 69450), (42237, 41994, 5, 69451), (42237, 42004, 4, 69448), (42237, 42039, 1, 69441), (42237, 42235, 8, 69456), (42009, 42173, 7, 69454), (42009, 42036, 2, 69444), (42009, 42041, 5, 69450), (42009, 42180, 4, 69449), (42238, 42205, 8, 69456), (42238, 41998, 6, 69452), (42238, 42006, 4, 69449), (42238, 42030, 1, 69441)] + # startSolution = [(41994, 42001, 5, 69450), (41994, 42172, 6, 69452), (41994, 42177, 8, 69456), (41994, 42235, 2, 69445), (42002, 42085, 2, 69444), (42002, 42203, 3, 69447), (42002, 42181, 7, 69454), (42002, 42065, 5, 69451), (42173, 42175, 8, 69456), (42173, 41998, 2, 69445), (42173, 42166, 5, 69451), (42173, 42030, 6, 69453), (42001, 42002, 8, 69456), (42001, 42008, 4, 69448), (42001, 42179, 6, 69453), (42001, 42236, 2, 69445), (41999, 42173, 4, 69448), (41999, 42176, 7, 69454), (41999, 42006, 2, 69445), (41999, 42009, 6, 69453), (42085, 42040, 7, 69455), (42085, 41997, 3, 69447), (42085, 42158, 4, 69449), (42085, 42082, 1, 69443), (42040, 41999, 1, 69442), (42040, 41996, 6, 69452), (42040, 42039, 8, 69456), (42040, 42238, 3, 69446), (42205, 41994, 1, 69441), (42205, 42036, 4, 69449), (42205, 42207, 5, 69450), (42205, 42180, 7, 69455), (42175, 42205, 3, 69446), (42175, 42004, 1, 69442), (42175, 42041, 7, 69455), (42175, 42237, 5, 69450), (41998, 41994, 3, 69446), (41998, 42036, 1, 69443), (41998, 42179, 5, 69450), (41998, 42082, 7, 69455), (42036, 42085, 6, 69452), (42036, 42176, 3, 69447), (42036, 42166, 2, 69444), (42036, 42238, 7, 69454), (42008, 41999, 8, 69456), (42008, 41996, 3, 69446), (42008, 42177, 6, 69452), (42008, 42009, 1, 69443), (42004, 42001, 7, 69454), (42004, 42008, 5, 69451), (42004, 42039, 3, 69446), (42004, 42065, 2, 69445), (42203, 42040, 2, 69444), (42203, 41998, 8, 69456), (42203, 42158, 6, 69453), (42203, 42030, 4, 69448), (41997, 42175, 6, 69453), (41997, 42172, 7, 69455), (41997, 42181, 4, 69448), (41997, 42237, 2, 69444), (42176, 42002, 6, 69453), (42176, 41997, 8, 69456), (42176, 42207, 4, 69448), (42176, 42180, 1, 69442), (42172, 42173, 1, 69441), (42172, 42203, 5, 69450), (42172, 42006, 4, 69449), (42172, 42235, 8, 69456), (41996, 42205, 2, 69445), (41996, 42004, 4, 69449), (41996, 42041, 5, 69450), (41996, 42236, 8, 69456), (42166, 41994, 7, 69455), (42166, 42172, 3, 69447), (42166, 42158, 1, 69442), (42166, 42235, 4, 69449), (42041, 42205, 6, 69453), (42041, 41997, 1, 69443), (42041, 42166, 8, 69456), (42041, 42065, 4, 69448), (42181, 42001, 3, 69447), (42181, 41998, 6, 69452), (42181, 42207, 2, 69444), (42181, 42030, 8, 69456), (42006, 42173, 3, 69446), (42006, 41996, 1, 69443), (42006, 42181, 5, 69451), (42006, 42009, 8, 69456), (42177, 42040, 4, 69449), (42177, 42176, 2, 69444), (42177, 42039, 5, 69451), (42177, 42237, 7, 69454), (42039, 42175, 4, 69448), (42039, 42008, 7, 69455), (42039, 42179, 2, 69445), (42039, 42180, 6, 69453), (42207, 42085, 8, 69456), (42207, 42004, 6, 69452), (42207, 42177, 1, 69442), (42207, 42082, 3, 69447), (42179, 42002, 4, 69448), (42179, 42203, 1, 69441), (42179, 42006, 7, 69454), (42179, 42236, 3, 69447), (42158, 41999, 5, 69451), (42158, 42036, 8, 69456), (42158, 42041, 3, 69446), (42158, 42238, 2, 69444), (42235, 42002, 1, 69441), (42235, 42176, 5, 69451), (42235, 42158, 7, 69454), (42235, 42009, 3, 69446), (42082, 42175, 2, 69444), (42082, 41997, 5, 69450), (42082, 42166, 6, 69452), (42082, 42065, 8, 69456), (42065, 42001, 1, 69441), (42065, 41996, 7, 69455), (42065, 42177, 3, 69447), (42065, 42236, 6, 69452), (42180, 41999, 3, 69446), (42180, 42172, 2, 69444), (42180, 42179, 8, 69456), (42180, 42238, 5, 69450), (42030, 42085, 5, 69450), (42030, 42008, 2, 69445), (42030, 42207, 7, 69455), (42030, 42237, 3, 69447), (42236, 42040, 5, 69451), (42236, 42203, 7, 69454), (42236, 42181, 1, 69443), (42236, 42082, 4, 69449), (42237, 41994, 4, 69449), (42237, 42004, 8, 69456), (42237, 42039, 1, 69442), (42237, 42235, 6, 69453), (42009, 42173, 7, 69454), (42009, 42036, 5, 69451), (42009, 42041, 2, 69445), (42009, 42180, 4, 69448), (42238, 42205, 8, 69456), (42238, 41998, 4, 69449), (42238, 42006, 6, 69452), (42238, 42030, 1, 69441)] + # currentSolution = startSolution + startSolution = currentSolution + + + model2 += standardObjectives +specialObjectives + + for (t1,t2,(r,d)) in x.keys(): + setLB(x[(t1,t2,(r,d))],0) + if (t1,t2,r,d) in currentSolution or [t1,t2,r,d] in currentSolution: + setLB(x[(t1,t2,(r,d))],1) + + model2.solve(XPRESS_PY(msg=1,gapRel=0.05,options=["MAXTIME=600"], warmStart=True)) + + init_violated_hawishes = [] + init_violated_encwishes = [] + for haw in HAWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(hawVio[haw.id].value(),2) > 0: + print("INIT HA-Wish violated : " , haw.prio, haw.reason , round(hawVio[haw.id].value(),2)) + init_violated_hawishes.append(haw) + for enc in EncWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(encVio[enc.id].value(),2) > 0: + print("INIT Encounter Wish violated : " ,enc.prio, enc.reason , round(encVio[enc.id].value(),2)) + init_violated_encwishes.append(enc) + + + sample_teams = 20 + sample_rounds = 5 + fixTeams = [] + # model2_bak = copy.deepcopy(model2) + counter = 0 + f = None + for key in x_round.keys(): + setLB(x_round[key],0) + for key in x.keys(): + setLB(x[key],0) + + while(True): + print("I",counter,fixTeams) + f = open(f'debug_{counter}.html','w') + print ("\n###############\nCUSTOM RUN\n##################\n") + f.write("
###############
CUSTOM RUN
##################
") + nFixations = 0 + fixedVars = defaultdict(lambda:defaultdict(lambda:0)) + for key in x_round.keys(): + setLB(x_round[key],0) + for (t1,t2,(r,d)) in x.keys(): + if (t1,t2,r,d) in currentSolution or [t1,t2,r,d] in currentSolution: + setStart(x[(t1,t2,(r,d))],1) + if (counter == 0) or (t1 in fixTeams and t2 in fixTeams and r in fixRounds): + setLB(x_round[(t1,t2,r)],1) + fixedVars[t1][r] = 1 + fixedVars[t2][r] = 1 + nFixations += 1 + else: + setStart(x[(t1,t2,(r,d))],0) + + print(f"FIXED {nFixations} Variables") + print("NOW SOLVING") + if counter == 0: + gapRel = 0 + else: + gapRel = 0.03 + model2.solve(XPRESS_PY(msg=1,gapRel=gapRel,options=["MAXTIME=7200"], warmStart=True)) + f.write(f"Objective: {value(model2.objective)}
") + + if counter == 0: + start_objective = value(model2.objective) + + + currentSolution =[ (t1,t2,r,d) for (t1,t2) in games for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9 ] + for haw in HAWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(hawVio[haw.id].value(),2) > 0: + print("HA-Wish violated : " , haw.prio, haw.reason , round(hawVio[haw.id].value(),2)) + f.write(f"HA-Wish violated : {haw.prio} {haw.reason} {round(hawVio[haw.id].value(),2)}
") + for enc in EncWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(encVio[enc.id].value(),2) > 0: + print("Encounter Wish violated : " ,enc.prio, enc.reason , round(encVio[enc.id].value(),2)) + f.write(f"Encounter Wish violated : {enc.prio} {enc.reason} {round(encVio[enc.id].value(),2)}
") + print ("\n###############\n EVAL RUN\n##################\n") + + + f.write("
###############
EVAL RUN
##################
") + model2 += standardObjectives +specialObjectives + + for (t1,t2,(r,d)) in x.keys(): + setLB(x_round[(t1,t2,r)],0) + if (t1,t2,r,d) in currentSolution or [t1,t2,r,d] in currentSolution: + if (counter == 0) or (t1 in fixTeams and t2 in fixTeams and r in fixRounds): + # setLB(x_round[(t1,t2,r)],1) + pass + else: + setStart(x[(t1,t2,(r,d))],1) + else: + setStart(x[(t1,t2,(r,d))],0) + + + + model2.solve(XPRESS_PY(msg=1,gapRel=gapRel,options=["MAXTIME=7200"], warmStart=True)) + + f.write(f"Objective: {value(model2.objective)}
") + + + + season = thisScenario.season + days = season.day_days.all() + dates = season.getDatetimeObjects() + + days_header = { + 'months': [], + 'days': [], + } + prevY = "" + prevM = "" + countM = 0 + for d in dates: + a, b, m, d, y = d.strftime('%a;%b;%m;%d;%Y').split(";") + + if prevM == "" or prevM == b: + countM += 1 + prevM = b + prevY = y + if b != prevM: + days_header['months'].append((f"{prevM} {prevY}",countM)) + countM = 1 + prevM = b + prevY = y + + days_header['days'].append((f"{a}, {d}.{m}.",1)) + + days_header['months'].append((prevM,countM)) + + + widget_rounds = [ + {'name':r, 'id':-r} + for r in range(1,season.nRounds+1) + ] + teams = season.scheduler_teams.filter(active=True) + + groups = {} + if season.groupBased: + for c in Conference.objects.filter(scenario=scenario,display_group=True).order_by('name'): + groups[c.name] = c.teams.all() + + getTeamById = { + team.id: team for team in teams + } + + home_dict = defaultdict(lambda:defaultdict(lambda:"")) + home_dict2 = defaultdict(lambda:defaultdict(lambda:"")) + common_home_dict = defaultdict(lambda:defaultdict(lambda:"")) + away_dict = defaultdict(lambda:defaultdict(lambda:"")) + away_dict2 = defaultdict(lambda:defaultdict(lambda:"")) + common_away_dict = defaultdict(lambda:defaultdict(lambda:"")) + + for game in [(d,t1,t2,r) for (t1,t2,r,d) in startSolution]: + home_dict[int(game[0])][int(game[1])] = getTeamById[int(game[2])].shortname + away_dict[int(game[0])][int(game[2])] = getTeamById[int(game[1])].shortname + home_dict[-int(game[3])][int(game[1])] = getTeamById[int(game[2])].shortname + away_dict[-int(game[3])][int(game[2])] = getTeamById[int(game[1])].shortname + + for game in [(d,t1,t2,r) for (t1,t2,r,d) in currentSolution]: + home_dict2[int(game[0])][int(game[1])] = getTeamById[int(game[2])].shortname + away_dict2[int(game[0])][int(game[2])] = getTeamById[int(game[1])].shortname + home_dict2[-int(game[3])][int(game[1])] = getTeamById[int(game[2])].shortname + away_dict2[-int(game[3])][int(game[2])] = getTeamById[int(game[1])].shortname + + if home_dict[int(game[0])][int(game[1])] == getTeamById[int(game[2])].shortname: + common_home_dict[int(game[0])][int(game[1])] = getTeamById[int(game[2])].shortname + if home_dict[-int(game[3])][int(game[1])] == getTeamById[int(game[2])].shortname: + common_home_dict[-int(game[3])][int(game[1])] = getTeamById[int(game[2])].shortname + + if away_dict[int(game[0])][int(game[2])] == getTeamById[int(game[1])].shortname: + common_away_dict[int(game[0])][int(game[2])] = getTeamById[int(game[1])].shortname + if away_dict[-int(game[3])][int(game[2])] == getTeamById[int(game[1])].shortname: + common_away_dict[-int(game[3])][int(game[2])] = getTeamById[int(game[1])].shortname + + context = { + "groups":groups, + "teams":teams, + "days":days, + "days_header":days_header, + "rounds":widget_rounds, + "home_dict":home_dict, + "away_dict":away_dict, + "home_dict2":home_dict2, + "away_dict2":away_dict2, + "common_home_dict":common_home_dict, + "common_away_dict":common_away_dict, + "fixedVars":fixedVars, + } + f.write(render_to_string("widgets/w_comparison_matrix.html", context)) + + + + if value(model2.objective) < start_objective - 1: + f.write(f"FOUND BETTER SOLUTION {value(model2.objective)}") + print("FOUND BETTER SOLUTION", value(model2.objective)) + print(currentSolution) + print("#############################") + currentSolution =[ (t1,t2,r,d) for (t1,t2) in games for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9 ] + violated_hawishes = [] + violated_encwishes = [] + for haw in HAWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(hawVio[haw.id].value(),2) > 0: + print("HA-Wish violated : " , haw.prio, haw.reason , round(hawVio[haw.id].value(),2)) + f.write(f"HA-Wish violated : {haw.prio} {haw.reason} {round(hawVio[haw.id].value(),2)}
") + violated_hawishes.append(haw) + for enc in EncWish.objects.filter(scenario=s2,active=True,prio__in=["Hard","A"]).order_by('prio'): + if round(encVio[enc.id].value(),2) > 0: + print("Encounter Wish violated : " ,enc.prio, enc.reason , round(encVio[enc.id].value(),2)) + f.write(f"Encounter Wish violated : {enc.prio} {enc.reason} {round(encVio[enc.id].value(),2)}
") + violated_encwishes.append(enc) + + startSolution = currentSolution + start_objective = value(model2.objective) + init_violated_hawishes = violated_hawishes + init_violated_encwishes = violated_encwishes + + else: + f.write(f"USING STARTSOLUTION") + print(f"USING STARTSOLUTION") + currentSolution = startSolution + violated_hawishes = init_violated_hawishes + violated_encwishes = init_violated_encwishes + + f.close() + + + newObjectives = None + for haw in random.sample(violated_hawishes,min(2,len(violated_hawishes))): + # for haw in violated_hawishes: + print("Selected HA-Wish: " , haw.prio, haw.reason) + if newObjectives: + newObjectives += hawVio[haw.id]*10000 + else: + newObjectives = hawVio[haw.id]*10000 + + for enc in random.sample(violated_encwishes,min(2,len(violated_encwishes))): + # for enc in violated_encwishes: + print("Selected Encounter Wish: " ,enc.prio, enc.reason) + if newObjectives: + newObjectives += encVio[enc.id]*10000 + else: + newObjectives = encVio[enc.id]*10000 + + model2 += standardObjectives +specialObjectives + newObjectives + print ("\n###############\n END OF \n##################\n") + + if random.randint(0,1) == 0: + fixTeams = random.sample(realteams,sample_teams) + fixRounds = rounds + else: + fixTeams = realteams + fixRounds = random.sample([1,2,3,4,5,6,7],sample_rounds) + + + + # if counter == 1: + # exit() + + counter += 1 + exit() + + + + + + + + + + + + + newRounds = [] + localSearchResults = [] + while localsearch and len(impScript)>0: + print ( (time.time() - local_start) , " " , localsearch_time, " ", (time.time() - local_start) < localsearch_time) + for (impType, firstNewRound,lastNewRound, newTeams, maxTime, par) in impScript: + if localsearch: + # if impType != "LOCALAI": + # print ("trying ") + # if len(newRounds) == 0: + # print(lastNewRound) + # newRounds = range (firstNewRound,lastNewRound+1) + if impType == "INIT": + impScript = impScript[1:] + elif impType in ["SMART_TEAMS", "SMART_ROUNDS"]: + newTeams, newRounds = smartNeighbor(impType, int(newTeams), int(lastNewRound), model2, x, hawishes, elemHaWishes, HawVioTooLess, HawVioTooMuch, + elemHaWishTeams,elemHaWishDays, encwishes, elemEncWishes, encVioTooLess, + encVioTooMuch, prioVal, elemEncWishGames,elemEncWishDays,breaks,realteams, + breakVio,nRounds,currentSolution,getTeamById) + if len(newRounds) == 0: + newRounds = range (firstNewRound,nRounds+1) + else: + newRounds = range (firstNewRound,lastNewRound+1) + + + stadium_sharers = [] + for t3 in newTeams: + if t3 in realteams: + stadium_sharers+=teamsWithSameStadium[t3] + + print ("newTeams ", newTeams) + print ("newTeams++ ",stadium_sharers) + if len(stadium_sharers)<=3: + newTeams = list(set(newTeams+stadium_sharers)) + print ("allTeams ", newTeams) + print (newRounds, newTeams, maxTime) + print ((time.time() - local_start) > localsearch_time) + print ("STOPCOMPUTATION",Scenario.objects.get(id=thisScenario.id).stopComputation) + # if time is up then do a last run sticking to currentSolution + if (time.time() - local_start) > localsearch_time or Scenario.objects.get(id=thisScenario.id).stopComputation: + print ("Time is up, restoring best solution found so far\n\n") + localsearch=False + # firstNewRound=1 + # lastNewRound=nRounds, + newTeams=[] + newRounds=[] + maxTime=2000 + newDays=[] + for r in newRounds: + for r2 in rounds : + if r==r2 or (thisSeason.symmetry and (r2-r)%(nTeams-1)==0 ): + # print ("adding round " , r2) + newDays+=getDays[r2] + + # print (newDays) + print ("###################################################") + print ("REOPT {} ROUNDS {} FOR TEAMS {} IN AT MOST {}s".format(impType,newRounds,[getTeamById[t] for t in newTeams],maxTime)) + print ("###################################################") + print ( " seconds ", (time.time() - local_start)) + if localsearch_time >0 : + pro=min(95,10+ int(90*((time.time() - local_start) )/ localsearch_time ) ) + print ("§§§§§§§§" ,pro, ' ', (time.time() - local_start) , ' / ', localsearch_time ) + else: + pro = 95 + ntstring ='' + for t in newTeams: + ntstring += getTeamById[t] + ', ' + lo = "" + if isinstance(last_objective,(int,float)): + lo= " (" + str(int(0.01+last_objective-1)) +")" + # print (task.request) + # writeProgress("Reoptimize " +str(firstNewRound)+ " - " + str(lastNewRound ) + " for teams " + ntstring[:-2] + lo, thisScenario.id ,pro) + if user_is_staff: + writeProgress("Reoptimize " +str(newRounds) + " for teams " + ntstring[:-2] + lo, thisScenario.id ,pro) + else: + writeProgress("Reoptimizing ... " + lo, thisScenario.id ,pro) + + print ("len(currentSolution) " ,len(currentSolution), use_currentSolution) + + for (t1,t2,r,d) in currentSolution: + if (t1,t2,(r,d)) in x.keys(): + if ((t1 in newTeams or t2 in newTeams) and d in newDays) or not use_currentSolution: + setLB(x[(t1,t2,(r,d))],0) + setStart(x[(t1,t2,(r,d))],1) + if par=="STICKY_HOME" and random.random()<1.0: + model2+= home[(t1,d)]>=1 + # setLB(home[(t1,d)],1) + print ( getTeamById[t1] , " stays home on " , getNiceDay[d]) + # print (type(home[(t1,d)])) + if par=="STICKY_ENCOUNTER" and random.random()<1.0: + model2+= x[(t1,t2,(r,d))]+ x[(t2,t1,(r,d))] ==1 + # setLB(home[(t1,d)],1) + print ( getTeamById[t1] , " and " , getTeamById[t2] , " still play on " , getNiceDay[d]) + # print (type(home[(t1,d)])) + + # print ("RELEASING ", (t1,t2,d)) + else: + setLB(x[(t1,t2,(r,d))],1) + # print ("FIXXING ", (t1,t2,d)) + + # if RUN_ENV == 'celery' and task: + # task.update_state(state='PHASE-8', meta={'timestamp':time.time(),'objective':99999999}) + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = mipgap, maxSeconds = maxTime, threads = 8,msg=1)) + elif solver == "Gurobi": + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=maxTime, warmStart=False)) + else: + try: + model2.solve(XPRESS(msg=1,targetGap=mipgap,maxSeconds = -maxTime, options=["THREADS=12,DEFAULTALG=4,DETERMINISTIC=0,CUTSTRATEGY=0"], warmStart=True, keepFiles=True)) + except: + print(" NOT SOLVED ") + model2.status = pulp.LpStatusNotSolved + + # print ("RRRR") + # for (t1,t2,r) in x_round.keys(): + # if getVal(x_round[(t1,t2,r)])>0.1: + # print (t1,t2, getTeamById[t1], getTeamById[t2], r, getVal(x_round[(t1,t2,r)])) + + print ( "model2.status=",model2.status , pulp.LpStatus[model2.status]) + print ( "model2.objective=",value(model2.objective) ) + print ( " => ",model2.status in [-1,-2] or not value(model2.objective) ) + + # new xpress/pulp sometimes gives status -3 (undefined) even though the solution is fine!! + if model2.status in [-1,-2] or not value(model2.objective): + if localsearch: + continue + elif solver != "xpress" or not value(model2.objective): + writeProgress("Model could not be solved within " + str(maxTime)+" seconds ....", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print( {'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print( 'Model could not be solved') + + if RUN_ENV == 'celery' and task: + # reset_stdout(f,filename) + task.update_state(state='Reopt Rounds', meta={'timestamp':time.time(),'objective':value(model2.objective), "user ": user_name, "league ": str(thisLeague)}) + + new_currentSolution =[ (t1,t2,r,d) for (t1,t2) in realgames for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9999 ] + + print ("status", model2.status, pulp.LpStatus[model2.status], len(new_currentSolution), len(currentSolution), len(new_currentSolution)>=len(currentSolution), not value(model2.objective), value(model2.objective), last_objective) + + newLSR = LocalSearchResult(season=thisSeason, + created_at=datetime.datetime.now(), + searchType=impType, + nTeams=len(newTeams), + nRounds=len(newRounds), + maxTime=maxTime, + objective=value(model2.objective), + lastObjective=last_objective or 0, + nElemEncWishes=nElemEncWishes, + nElemHaWishes=nElemHaWishes, + nBlockings=(nBlockingHome+nBlockingAway), + nGames=nGames, + nCols=model2.numVariables(), + nRows=model2.numConstraints(), + runtime=model2.solutionTime) + print ( "model2.status=",model2.status ) + print (specialGameControl or len(new_currentSolution)>=len(currentSolution) , (solver != "xpress" or model2.status not in [-1,-2]) , ( not last_objective or value(model2.objective) <= last_objective+0.01)) + if (specialGameControl or len(new_currentSolution)>=len(currentSolution)) and (solver != "xpress" or model2.status not in [-1,-2]) and ( not last_objective or value(model2.objective) <= last_objective+0.01): + print ("now using this solution with objective value " + str(value(model2.objective)) ) + for (t1,t2,r,d) in currentSolution: + if (t1 in newTeams or t2 in newTeams) and d in newDays and False: + setStart(x[(t1,t2,(r,d))],0) + # x[(t1,t2,(r,d))].start=0 + currentSolution =[ (t1,t2,r,d) for (t1,t2) in games for (r,d) in roundDays if t1!=t2 and getVal(x[(t1,t2,(r,d))]) >0.9 ] + currentSolution =[ (t1,t2,r,d) for (t1,t2,r,d) in currentSolution if getTeamById[t1]!="-" and getTeamById[t2]!="-" ] + currentLocation = { (t,d) : False for t in teams for d in days} + for (t1,t2,r,d) in currentSolution: + currentLocation[(t2,d)]=t1 + currentLocation[(t1,d)]=t1 + + print ("length of current solution " , len (currentSolution)) + last_objective=value(model2.objective) + print ("Home-/Away : " , gew['Home-/Away'], "*",HawVioTotal.value()) + print ("Pairings :" , gew['Pairings'], "*", pairingVioTotal.value()) + print ("Blockings :" , gew['Availabilities'], "*", blockingVioTotal.value() ,"+ " , blockingVioTotal2.value() ) + print ("goodHomesTotal :" , gew['Availabilities'], "*", goodHomesTotal.value() ) + print ("travelVioTotal :" , gew['Availabilities'], "*", travelVioTotal.value()) + print ("gamesTooCloseTotal :" , gew['Availabilities'], "*", gamesTooCloseTotal.value()) + print ("tripSavedTotal2 : -5*" , gew['Trips'], "*", tripSavedTotal2.value()) + print ("breakVioTotal :" , gew['Breaks'], "*", breakVioTotal.value()) + print ("break3VioTotal :" , gew['Breaks'], "*", break3VioTotal.value()) + print ("encVioTotal : 5*" , gew['Encounters'], "*", encVioTotal.value()) + print ("seedVioTotal : 5* " , gew['Encounters'], "*", seedVioTotal.value()) + print ("confVioTotal :" , gew['Conferences'], "*", confVioTotal.value()) + print ("tooManyHomesInStadiumTotal :" , gew['Availabilities'], "*", tooManyHomesInStadiumTotal.value()) + print ("broadVioTotal :" , gew['Broadcasting'], "*", broadVioTotal.value()) + print ("derbiesMissingTotal :" , gew['Derbies'], "*", derbiesMissingTotal.value()) + print ("competitionVioTotal : 5 *", competitionVioTotal.value()) + print ("tooManyTop4InRowTotal : 0.0 *", tooManyTop4InRowTotal.value()) + print ("fixedGameVioTotal :" , gew['Availabilities'], "*", fixedGameVioTotal.value()) + print ("missingGamesVioTotal :" , gew['Availabilities'], "*", missingGamesVioTotal.value()) + print ("totalAttendance : -0.01*", totalAttendance.value()) + print ("oldScenGamesTotal : ", -oldScenGamesTotal.value()) + print ("specialObjectives :" , specialObjectives.value() if specialObjectives else 0) + if thisSeason.useFeatureBackToBack: + greyCntr =0 + redCntr =0 + yellowCntr =0 + allCntr=0 + for (d1,d2) in critical_day_pairs: + print ("no time zone crossings for at days " , getNiceDay[d1],"->",getNiceDay[d2],nextCritical[d2]) + for t in realteams: + if currentLocation[(t,d1)] and currentLocation[(t,d2)] : + t1 = currentLocation[(t,d1)] + t2 = currentLocation[(t,d2)] + allCntr+=1 + if (t2,1.0) in bad_travel_out[t1]: + print ("GREY BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + greyCntr +=1 + if (t2,0.05) in bad_travel_out[t1]: + print ("RED BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + redCntr +=1 + if (t2,0.003) in bad_travel_out[t1]: + print ("YELLOW BACK TO BACK FOUND IN TOUR :", getNiceDay[d1] ,getTeamById[t] , " -> " , getTeamById[t1] , " -> " , getTeamById[t2] ) + yellowCntr +=1 + + print ("badBackToBack_Total ", badBackToBack_Total.value()) + b2bvios = [(getTeamById[t],getNiceDay[d], int(0.5+badBackToBack[(t,d)].value())) for t in teams for d in days if (t,d) in badBackToBack.keys() and badBackToBack[(t,d)].value()>0.1 ] + greenB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1] + yellowB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==3] + redB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==50] + greyB2B = [ (t1,t2,w) for (t1,t2,w) in b2bvios if w==1000] + print ( len(greyB2B) , " + ",len(redB2B) , " + ",len(yellowB2B) , " + ",len(greenB2B)) + print ( greyCntr , " + ", redCntr , " + ", yellowCntr , " + green = ", allCntr) + print (greyB2B+redB2B) + + if mathModelName=="NBA": + print ("badRepeater_Total_NBA ", badRepeater_Total_NBA.value()) + print ("tooLongTrip_Total_NBA ", tooLongTrip_Total_NBA.value()) + print ("eastWestTrip_Total_NBA ", eastWestTrip_Total_NBA.value()) + + newRounds = [] + if impType != 'INIT': + localSearchResults.append(newLSR) + + LocalSearchResult.objects.bulk_create(localSearchResults) + +mipgap=0.05 + +if thisSeason.useFeatureKickOffTime and not evalRun: + for (t1,t2) in games : + for rd in roundDays: + if getVal(x[(t1,t2,rd)]) >0.99: + model2+= x[(t1,t2,rd)]==1 + for tm in times: + makeIntVar(x_time[(t1,t2,rd,tm)]) + # makeIntVar(x_time[(t1,t2,rd,getIdByTime["Early"])]) + + writeProgress("Refining times ", thisScenario.id , 99) + + print ("######################") + print ("## REFINING TIMES ####") + print ("######################") + + if solver == "CBC": + model2.solve(PULP_CBC_CMD(fracGap = mipgap, maxSeconds = 600, threads = 8,msg=1)) + elif solver == "Gurobi": + # mipgap = 1.0 + model2.solve(GUROBI(MIPGap=mipgap, TimeLimit=600)) + else: + try: + model2.solve(XPRESS(msg=1,maxSeconds = 60, targetGap=mipgap ,options=["THREADS=12"], keepFiles=True)) + except: + model2.status = pulp.LpStatusNotSolved + + if model2.status<0 or not value(model2.objective): + if solver!="xpress" or not value(model2.objective): + writeProgress("Model could not be solved within " + str(maxTime)+" seconds ....", thisScenario.id,0) + if RUN_ENV == 'celery' and task: + print( {'timestamp':time.time(),'objective':-1, "user ": user_name, "league ": str(thisLeague)}) + else: + print( 'Model could not be solved') + + if RUN_ENV == 'celery' and task: + task.update_state(state='Reopt Rounds', meta={'timestamp':time.time(),'objective':value(model2.objective), "user ": user_name, "league ": str(thisLeague)}) diff --git a/uefa/scripts/uefa24_simulator.py b/uefa/scripts/uefa24_simulator.py new file mode 100755 index 0000000..1fb3b48 --- /dev/null +++ b/uefa/scripts/uefa24_simulator.py @@ -0,0 +1,208 @@ +# %% +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.optimizer import optimize_2phases +from scheduler.solver.tasks.optimize import optimize +from draws.solver.optimize_draws import ucl24_ha_matrix, ucl24_opponent_matrix + + +from django.template.loader import render_to_string +from django.core.files.storage import FileSystemStorage +from django.utils.translation import gettext_lazy as _ + +# from gurobipy import * +import pulp +from pulp import lpSum, value, XPRESS_PY, XPRESS, GUROBI, PULP_CBC_CMD +from math import sqrt,pow,sin,cos,atan2,pi, ceil +from collections import defaultdict +import timeit +import datetime +import operator +import random +import time +import copy +# from os import dup, dup2, close, path +import os +from importlib import import_module +import builtins as __builtin__ +import logging +import networkx as nx +import json +import string +import hashlib + +from scheduler.models import * +from leagues.celery import celery, TASK_TIME_LIMIT +from leagues.settings import PULP_FOLDER +from scheduler.helpers import serialize_scenario, report_solverstatus +from scheduler.solver.functions import * +from scheduler.solver.tasks.optimize_localsearch import smartNeighbor +from scheduler.solver.tasks.optimize_submodels import ucl24_basicmodell, ueluecl24_basicmodell, ueluecl24_basicmodell_v2 +from scheduler.helpers import copy_scenario +from draws.solver.optimize_draws import ucl24_ha_matrix + +# from research.learners import AttendanceLearner +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestRegressor +from sklearn.ensemble import GradientBoostingRegressor +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() + + +# basescenario = Scenario.objects.get(id=9306) +basescenario = Scenario.objects.get(id=9279) +# scenario = Scenario.objects.get(id=9308) + +s2 = basescenario.id +user_name = 'md' +user_is_staff = True +runMode = 'New' +localsearch_time = 60 +RUN_ENV = 'local' +SOLVER = 'xpress' + +# %% +Scenario.objects.filter(season=basescenario.season,name__contains="Copy of BaseScenario").delete() +season = basescenario.season +print("START SIMULATION") +for counter in range(1,50): + name = f"Copy of BaseScenario {counter:03d}" + scenario = copy_scenario(basescenario, name=name, comment="") + print(f"COPY SCENARIO {name}") + + scenario.last_modified = timezone.now() + scenario.last_modified_by = None + scenario.save() + + # %% + # print("RANDOM DRAW") + # GameRequirement.objects.filter(scenario=scenario).delete() + # if season.optimizationParameters == 'UCL24': + # ucl24_ha_matrix(scenario.id) + # elif season.optimizationParameters == "UEL24": + # ucl24_ha_matrix(scenario.id,"UEL") + # ucl24_opponent_matrix(scenario,"UECL") + # print("DONE") + + # %% + + # # UPDATE TOPGAMES + # encgroups = EncGroup.objects.filter(scenario=scenario) + # Encounter.objects.filter(scenario=scenario).delete() + + # for game in GameRequirement.objects.filter(scenario=scenario): + # if game.team1.attractivity >= 4 or game.team2.attractivity >= 4: + # if game.team1.attractivity >= 4 and game.team2.attractivity >= 4: + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="Top Games")) + # elif game.team1.attractivity >= 4: + # if game.team1.countryObj.shortname == "ENG": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="England top games")) + # if game.team1.countryObj.shortname == "FRA": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="France top games")) + # if game.team1.countryObj.shortname == "GER": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="German top games")) + # if game.team1.countryObj.shortname == "ITA": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="Italian top games")) + # if game.team1.countryObj.shortname == "ESP": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="Spain top games")) + # elif game.team2.attractivity >= 4: + # if game.team2.countryObj.shortname == "ENG": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="England top games")) + # if game.team2.countryObj.shortname == "FRA": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="France top games")) + # if game.team2.countryObj.shortname == "GER": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="German top games")) + # if game.team2.countryObj.shortname == "ITA": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="Italian top games")) + # if game.team2.countryObj.shortname == "ESP": + # enc = Encounter(scenario=scenario,encounterGroup=encgroups.get(name="Spain top games")) + + # enc.save() + # enc.homeTeams.add(game.team1) + # enc.awayTeams.add(game.team2) + + print("STARTING OPTIMIZATION") + + try: + + sol,kpis = optimize(task=None,s2=scenario.id, user_name=user_name, user_is_staff=user_is_staff, + runMode='New', localsearch_time=0, RUN_ENV=RUN_ENV, solver=SOLVER) + + print("########################################") + print("AFTER NEW RUN") + print(kpis) + print("########################################") + + + sol,kpis = optimize(task=None,s2=scenario.id, user_name=user_name, user_is_staff=user_is_staff, + runMode='Improve', localsearch_time=localsearch_time, RUN_ENV=RUN_ENV, solver=SOLVER) + + + print("########################################") + print("AFTER IMPROVE") + print(kpis) + print("########################################") + + except: + print("\t\tERROR") + pass + + diff --git a/uefa/scripts/uefa_ucl24_seeder.py b/uefa/scripts/uefa_ucl24_seeder.py new file mode 100755 index 0000000..e2720b5 --- /dev/null +++ b/uefa/scripts/uefa_ucl24_seeder.py @@ -0,0 +1,220 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + + +# %% + +scenario = Scenario.objects.get(id=7) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append((1, "Manchester City FC (ENG)", 145.000, "Pot 1",5,4)) +new_teams.append((2, "FC Bayern München (GER)", 136.000, "Pot 1",5,5)) +new_teams.append((3, "Liverpool FC (ENG)", 123.000, "Pot 1",5,5)) +new_teams.append((4, "Real Madrid CF (ESP)", 121.000, "Pot 1",5,5)) +new_teams.append((5, "Paris Saint-Germain (FRA)", 112.000, "Pot 1",5,5)) +new_teams.append((6, "Manchester United FC (ENG)", 104.000, "Pot 1",5,5)) +new_teams.append((7, "FC Barcelona (ESP)", 98.000, "Pot 1",5,5)) +new_teams.append((8, "FC Internazionale Milano (ITA)", 96.000, "Pot 1",4,5)) +new_teams.append((9, "Sevilla FC (ESP)", 91.000, "Pot 1",3,4)) +new_teams.append((10, "Borussia Dortmund (GER)", 86.000, "Pot 2",4,4)) +new_teams.append((11, "Club Atlético de Madrid (ESP)", 85.000, "Pot 2",4,4)) +new_teams.append((12, "RB Leipzig (GER)", 84.000, "Pot 2",3,3)) +new_teams.append((13, "SL Benfica (POR)", 82.000, "Pot 2",2,5)) +new_teams.append((14, "SSC Napoli (ITA)", 81.000, "Pot 2",3,4)) +new_teams.append((15, "FC Porto (POR)", 81.000, "Pot 2",3,5)) +new_teams.append((16, "Arsenal FC (ENG)", 76.000, "Pot 2",4,4)) +new_teams.append((17, "FC Shakhtar Donetsk (UKR)", 63.000, "Pot 2",1,5)) +new_teams.append((18, "FC Salzburg (AUT)", 59.000, "Pot 2",2,5)) +new_teams.append((19, "Atalanta BC (ITA)", 55.500, "Pot 3",2,3)) +new_teams.append((20, "Feyenoord (NED)", 51.000, "Pot 3",2,4)) +new_teams.append((21, "AC Milan (ITA)", 50.000, "Pot 3",3,5)) +new_teams.append((22, "SC Braga (POR)", 44.000, "Pot 3",2,3)) +new_teams.append((23, "PSV Eindhoven (NED)", 43.000, "Pot 3",2,4)) +new_teams.append((24, "S.S. Lazio (ITA)", 42.000, "Pot 3",2,3)) +new_teams.append((25, "FK Crvena zvezda (SRB)", 42.000, "Pot 3",1,5)) +new_teams.append((26, "F.C. Copenhagen (DEN)", 40.500, "Pot 3",1,4)) +new_teams.append((27, "BSC Young Boys (SUI)", 34.500, "Pot 3",1,4)) +new_teams.append((28, "Real Sociedad de Fútbol (ESP)", 33.000, "Pot 4",2,3)) +new_teams.append((29, "Olympique de Marseille (FRA)", 33.000, "Pot 4",3,4)) +new_teams.append((30, "Galatasaray A.Ş. (TUR)", 31.500, "Pot 4",1,5)) +new_teams.append((31, "Celtic FC (SCO)", 31.000, "Pot 4",2,3)) +new_teams.append((32, "Qarabağ FK (AZE)", 25.000, "Pot 4",1,5)) +new_teams.append((33, "Newcastle United FC (ENG)", 21.914, "Pot 4",3,3)) +new_teams.append((34, "1. FC Union Berlin (GER)", 17.000, "Pot 4",2,3)) +new_teams.append((35, "R. Antwerp FC (BEL)", 17.000, "Pot 4",3,3)) +new_teams.append((36, "RC Lens (FRA)", 12.232, "Pot 4",2,3)) + + +CET_minus_1 = ['ENG','POR','SCO'] +CET_plus_1 = ['TUR','AZE','ISR','UKR'] + +# %% + + +for i in range(1,6): + Conference.objects.get_or_create(scenario=scenario,name=f"Global Coeff {i}") + Conference.objects.get_or_create(scenario=scenario,name=f"Domestic Coeff {i}") +Conference.objects.get_or_create(scenario=scenario,name="CET") +Conference.objects.get_or_create(scenario=scenario,name="CET-1") +Conference.objects.get_or_create(scenario=scenario,name="CET+1") + +for conf in Conference.objects.filter(scenario=scenario).exclude(name__in=['HARD Constraints','SOFT Constraints']): + conf.teams.clear() + conf.collapseInView = True + conf.save() + + + +Team.objects.filter(season=scenario.season).update(active=False) +for t in new_teams: + + team_name = t[1].split('(')[0].strip() + team_country = t[1].split('(')[1].split(')')[0].strip() + # abbreviation = t[2] + global_coeff = t[4] + domestic_coeff = t[5] + pot = int(t[3].split(' ')[1].strip()) + pos = int(t[0]) + competition = "UCL" + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + if teamObj: + + pass + else: + print(t,"->", team_name) + gteam = GlobalTeam.objects.filter(name=team_name) + if gteam: + teamObj = Team.objects.create(season=scenario.season, + name=team_name, + attractivity=global_coeff+0.1*domestic_coeff, + position=pos, + pot=pot, + latitude=gteam.first().latitude, + longitude=gteam.first().longitude, + country=gteam.first().country, + active=True) + print("\tCreated team from global", team_name) + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + else: + print("\tTeam not found", team_name) + continue + + + Conference.objects.filter(scenario=scenario,name=competition).first().teams.add(teamObj.first()) + Conference.objects.filter(scenario=scenario,name=f"{t[3]}").first().teams.add(teamObj.first()) + + if global_coeff in range(1,6): + Conference.objects.filter(scenario=scenario,name=f"Global Coeff {global_coeff}").first().teams.add(teamObj.first()) + + if domestic_coeff in range(1,6): + Conference.objects.filter(scenario=scenario,name=f"Domestic Coeff {domestic_coeff}").first().teams.add(teamObj.first()) + + if team_country in CET_minus_1: + Conference.objects.filter(scenario=scenario,name="CET-1").first().teams.add(teamObj.first()) + elif team_country in CET_plus_1: + Conference.objects.filter(scenario=scenario,name="CET+1").first().teams.add(teamObj.first()) + else: + Conference.objects.filter(scenario=scenario,name="CET").first().teams.add(teamObj.first()) + + teamObj.update(active=True) + teamObj.update(attractivity=global_coeff+0.1*domestic_coeff) + teamObj.update(coefficient=5-pot) + teamObj.update(position=pos) + teamObj.update(pot=pot) + + +for conf in Conference.objects.filter(scenario=scenario): + for t in conf.teams.filter(active=False): + conf.teams.remove(t) + +for haw in HAWish.objects.filter(scenario=scenario): + for t in haw.teams.filter(active=False): + haw.teams.remove(t) + +for enc in EncWish.objects.filter(scenario=scenario): + for t in enc.teams1.filter(active=False): + enc.teams1.remove(t) + for t in enc.teams2.filter(active=False): + enc.teams1.remove(t) + +for pair in Pairing.objects.filter(scenario=scenario): + if pair.team1.active==False or pair.team2.active==False: + pair.delete() + + + +# %% diff --git a/uefa/scripts/uefa_uecl24_seeder.py b/uefa/scripts/uefa_uecl24_seeder.py new file mode 100755 index 0000000..bd85f34 --- /dev/null +++ b/uefa/scripts/uefa_uecl24_seeder.py @@ -0,0 +1,190 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + + +# %% + +scenario = Scenario.objects.get(id=3) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append(("Pot 1", 1, "Tottenham Hotspur (ENG)", 80.000, 5,5 )) +new_teams.append(("Pot 1", 2, "Eintracht Frankfurt (GER)", 77.000, 5,5 )) +new_teams.append(("Pot 1", 3, "Club Brugge (BEL)", 54.000, 2,5 )) +new_teams.append(("Pot 1", 4, "FC Basel 1893 (SUI)", 53.000, 3,5 )) +new_teams.append(("Pot 1", 5, "KAA Gent (BEL)", 37.500, 1,4 )) +new_teams.append(("Pot 1", 6, "Fenerbahçe SK (TUR)", 30.000, 3,5 )) +new_teams.append(("Pot 2", 7, "AS Monaco FC (FRA)", 29.000, 4,5 )) +new_teams.append(("Pot 2", 8, "CFR 1907 Cluj (ROU)", 27.500, 1,5 )) +new_teams.append(("Pot 2", 9, "FC Midtjylland (DEN)", 25.500, 1,5 )) +new_teams.append(("Pot 2", 10, "PAOK FC (GRE)", 25.000, 1,3 )) +new_teams.append(("Pot 2", 11, "ŠK Slovan Bratislava (SVK)", 24.500, 1,5 )) +new_teams.append(("Pot 2", 12, "Maccabi Tel-Aviv FC (ISR)", 24.000, 1,5 )) +new_teams.append(("Pot 3", 13, "FC Viktoria Plzeň (CZE)", 22.000, 2,4 )) +new_teams.append(("Pot 3", 14, "PFC Ludogorets 1945 (BUL)", 21.000, 1,5 )) +new_teams.append(("Pot 3", 15, "FK Bodø/Glimt (NOR)", 20.000, 2,5 )) +new_teams.append(("Pot 3", 16, "CA Osasuna (ESP)", 18.599, 3,3 )) +new_teams.append(("Pot 3", 17, "KRC Genk (BEL)", 18.000, 1,4 )) +new_teams.append(("Pot 3", 18, "FC Bologna (ITA)", 16.385, 3,3 )) +new_teams.append(("Pot 4", 19, "FC Zorya Luhansk (UKR)", 16.000, 1,3 )) +new_teams.append(("Pot 4", 20, "FC Astana (KAZ)", 14.000, 1,5 )) +new_teams.append(("Pot 4", 21, "Beşiktaş (TUR)", 14.000, 3,5 )) +new_teams.append(("Pot 4", 22, "FK Žalgiris Vilnius (LTU)", 11.000, 1,5 )) +new_teams.append(("Pot 4", 23, "HJK Helsinki (FIN)", 11.000, 1,5 )) +new_teams.append(("Pot 4", 24, "FC Flora Tallinn (EST)", 10.500, 1,5 )) +new_teams.append(("Pot 5", 25, "Legia Warszawa (POL)", 11.000, 1,5 )) +new_teams.append(("Pot 5", 26, "FC Spartak Trnava (SVK)", 10.500, 1,4 )) +new_teams.append(("Pot 5", 27, "NK Olimpija Ljubljana (SVN)", 9.000, 1,5 )) +new_teams.append(("Pot 5", 28, "HŠK Zrinjski (BIH)", 8.500, 1,5 )) +new_teams.append(("Pot 5", 29, "Dnipro-1 (UKR)", 8.000, 1,3 )) +new_teams.append(("Pot 5", 30, "KÍ Klaksvík (FRO)", 8.000, 1,5 )) +new_teams.append(("Pot 6", 31, "Aberdeen FC (SCO)", 8.000, 2,3 )) +new_teams.append(("Pot 6", 32, "FK Čukarički (SRB)", 6.475, 1,5 )) +new_teams.append(("Pot 6", 33, "FC Lugano (SUI)", 6.335, 1,3 )) +new_teams.append(("Pot 6", 34, "Breidablik (ISL)", 6.000, 1,5 )) +new_teams.append(("Pot 6", 35, "FC Nordsjælland (DEN)", 5.565, 1,5 )) +new_teams.append(("Pot 6", 36, "Ballkani (KOS)", 3.000, 1,5 )) + + + + +CET_minus_1 = ['ENG','POR','SCO'] +CET_plus_1 = ['TUR','AZE','ISR','UKR'] + +# %% + +for conf in Conference.objects.filter(scenario=scenario).exclude(name__in=['HARD Constraints','SOFT Constraints']): + conf.teams.clear() + conf.collapseInView = True + conf.save() + +Team.objects.filter(season=scenario.season).update(active=False) +for t in new_teams: + team_name = t[2].split('(')[0].strip() + team_country = t[2].split('(')[1].split(')')[0].strip() + global_coeff = t[4] + domestic_coeff = t[5] + pot = int(t[0].split(' ')[1].strip()) + pos = int(t[1]) + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + if teamObj: + + pass + else: + print(t,"->", team_name) + gteam = GlobalTeam.objects.filter(name=team_name) + if gteam: + new_t = Team.objects.create(season=scenario.season, + name=team_name, + attractivity=global_coeff, + position=pos, + pot=pot, + latitude=gteam.first().latitude, + longitude=gteam.first().longitude, + country=gteam.first().country, + active=True) + print("\tCreated team from global", team_name) + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + else: + print("\tTeam not found", team_name) + continue + + + Conference.objects.filter(scenario=scenario,name="UECL").first().teams.add(teamObj.first()) + Conference.objects.filter(scenario=scenario,name=t[0]).first().teams.add(teamObj.first()) + + # if global_coeff in [3,4,5]: + # Conference.objects.filter(scenario=scenario,name=f"Global Coeff {global_coeff}").first().teams.add(teamObj.first()) + + # if domestic_coeff in [3,5]: + # Conference.objects.filter(scenario=scenario,name=f"Domestic Coeff {domestic_coeff}").first().teams.add(teamObj.first()) + + if team_country in CET_minus_1: + Conference.objects.filter(scenario=scenario,name="CET-1").first().teams.add(teamObj.first()) + elif team_country in CET_plus_1: + Conference.objects.filter(scenario=scenario,name="CET+1").first().teams.add(teamObj.first()) + else: + Conference.objects.filter(scenario=scenario,name="CET").first().teams.add(teamObj.first()) + + teamObj.update(active=True) + teamObj.update(attractivity=global_coeff) + teamObj.update(position=pos) + teamObj.update(pot=pot) + + + + +# %% diff --git a/uefa/scripts/uefa_uel24_seeder.py b/uefa/scripts/uefa_uel24_seeder.py new file mode 100755 index 0000000..b3fb65f --- /dev/null +++ b/uefa/scripts/uefa_uel24_seeder.py @@ -0,0 +1,232 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + + +# %% + +scenario = Scenario.objects.get(id=4) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append(("Pot 1",1, "Roma", "AS Roma (ITA) ", 97.000, 5,5, "UEL")) +new_teams.append(("Pot 1",2, "Ajax", "AFC Ajax (NED)", 89.000, 5,5 , "UEL")) +new_teams.append(("Pot 1",3, "Villa", "Villarreal CF (ESP)", 82.000, 5,5 , "UEL")) +new_teams.append(("Pot 1",4, "Bayer", "Bayer 04 Leverkusen (GER)", 72.000, 4,4 , "UEL")) +new_teams.append(("Pot 1",5, "GNK", "GNK Dinamo (CRO) ", 55.000, 2,5 , "UEL")) +new_teams.append(("Pot 1",6, "Ranger", "Rangers FC (SCO)", 54.000, 4,5 , "UEL")) +new_teams.append(("Pot 1",7, "Sporti", "Sporting Clube de Portugal (POR)", 52.500, 4,5 , "UEL")) +new_teams.append(("Pot 1",8, "Slavia", "SK Slavia Praha (CZE)", 52.000, 2,5 , "UEL")) +new_teams.append(("Pot 1",9, "WestHam", "West Ham United FC (ENG)", 50.000, 4,4 , "UEL")) +new_teams.append(("Pot 2",10, "Alkmaa", "AZ Alkmaar (NED)", 47.500, 2,3 , "UEL")) +new_teams.append(("Pot 2",11, "Stade", "Stade Rennais FC (FRA) ", 44.000, 3,5 , "UEL")) +new_teams.append(("Pot 2",12, "Olymp", "Olympiacos FC (GRE)", 39.000, 2,5 , "UEL")) +new_teams.append(("Pot 2",13, "Betis", "Real Betis Balompié (ESP)", 37.000, 3,4 , "UEL")) +new_teams.append(("Pot 2",14, "Linz", "LASK Linz (AUT)", 36.000, 1,4 , "UEL")) +new_teams.append(("Pot 2",15, "Kyiv", "FC Dynamo Kyiv (UKR)", 35.000, 3,5 , "UEL")) +new_teams.append(("Pot 2",16, "Lille", "LOSC Lille (FRA)", 30.000, 3,4 , "UEL")) +new_teams.append(("Pot 2",17, "Feren", "Ferencvárosi TC (HUN)", 27.000, 1,5 , "UEL")) +new_teams.append(("Pot 2",18, "Molde", "Molde FK (NOR)", 24.000, 1,5 , "UEL")) +new_teams.append(("Pot 3",19, "Bright", "Brighton & Hove Albion (ENG)", 21.914, 3,3 , "UEL")) +new_teams.append(("Pot 3",20, "Aston", "Aston Villa FC (ENG)", 21.914, 4,4 , "UEL")) +new_teams.append(("Pot 3",21, "Fiore", "ACF Fiorentina (ITA)", 20.000, 4,4 , "UEL")) +new_teams.append(("Pot 3",22, "Tiras", "FC Sheriff Tiraspol (MDA) ", 19.500, 1,5 , "UEL")) +new_teams.append(("Pot 3",23, "St.Gil", "Union Saint-Gilloise (BEL)", 19.000, 1,4 , "UEL")) +new_teams.append(("Pot 3",24, "Rapid", "SK Rapid Wien (AUT)", 18.500, 2,5 , "UEL")) +new_teams.append(("Pot 3",25, "Freib", "SC Freiburg (GER)", 16.496, 4,4 , "UEL")) +new_teams.append(("Pot 3",26, "Sparta", "AC Sparta Praha (CZE)", 14.000, 2,5 , "UEL")) +new_teams.append(("Pot 3",27, "Haifa", "Maccabi Haifa FC (ISR)", 13.000, 2,5 , "UEL")) +new_teams.append(("Pot 4",28, "Sturm", "SK Sturm Graz (AUT)", 12.500, 1,5 , "UEL")) +new_teams.append(("Pot 4",29, "Toul", "Toulouse FC (FRA)", 12.232, 3,4 , "UEL")) +new_teams.append(("Pot 4",30, "Athen", "AEK Athens FC (GRE)", 11.000, 1,4 , "UEL")) +new_teams.append(("Pot 4",31, "Topola", "FK TSC Bačka Topola (SRB)", 6.475, 1,5 , "UEL")) +new_teams.append(("Pot 4",32, "Serve", "Servette FC (SUI) ", 6.335, 1,4 , "UEL")) +new_teams.append(("Pot 4",33, "Panat", "Panathinaikos FC (GRE) ", 5.045, 1,5 , "UEL")) +new_teams.append(("Pot 4",34, "Raków", "Raków Czestochowa (POL) ", 5.000, 1,3 , "UEL")) +new_teams.append(("Pot 4",35, "Aris", "Aris Limassol FC (CYP)", 4.895, 1,5 , "UEL")) +new_teams.append(("Pot 4",36, "Häcke", "BK Häcken (SWE)", 4.750, 1,5 , "UEL")) + +new_teams.append(("Pot 1", 1, "Hotsp", "Tottenham Hotspur (ENG)", 80.000, 5,5 , "UECL")) +new_teams.append(("Pot 1", 2, "Frank", "Eintracht Frankfurt (GER)", 77.000, 5,5 , "UECL")) +new_teams.append(("Pot 1", 3, "Brugg", "Club Brugge (BEL)", 54.000, 2,5 , "UECL")) +new_teams.append(("Pot 1", 4, "Basel", "FC Basel 1893 (SUI)", 53.000, 3,5 , "UECL")) +new_teams.append(("Pot 1", 5, "Gent", "KAA Gent (BEL)", 37.500, 1,4 , "UECL")) +new_teams.append(("Pot 1", 6, "Fener", "Fenerbahçe SK (TUR)", 30.000, 3,5 , "UECL")) +new_teams.append(("Pot 2", 7, "Monac", "AS Monaco FC (FRA)", 29.000, 4,5 , "UECL")) +new_teams.append(("Pot 2", 8, "Cluj", "CFR 1907 Cluj (ROU)", 27.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 9, "Midty", "FC Midtjylland (DEN)", 25.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 10, "PAOK", "PAOK FC (GRE)", 25.000, 1,3 , "UECL")) +new_teams.append(("Pot 2", 11, "Brati", "ŠK Slovan Bratislava (SVK)", 24.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 12, "Macca", "Maccabi Tel-Aviv FC (ISR)", 24.000, 1,5 , "UECL")) +new_teams.append(("Pot 3", 13, "Plzeň", "FC Viktoria Plzeň (CZE)", 22.000, 2,4 , "UECL")) +new_teams.append(("Pot 3", 14, "Ludog", "PFC Ludogorets 1945 (BUL)", 21.000, 1,5 , "UECL")) +new_teams.append(("Pot 3", 15, "Glimt", "FK Bodø/Glimt (NOR)", 20.000, 2,5 , "UECL")) +new_teams.append(("Pot 3", 16, "Osaun", "CA Osasuna (ESP)", 18.599, 3,3 , "UECL")) +new_teams.append(("Pot 3", 17, "Genk", "KRC Genk (BEL)", 18.000, 1,4 , "UECL")) +new_teams.append(("Pot 3", 18, "Bolog", "FC Bologna (ITA)", 16.385, 3,3 , "UECL")) +new_teams.append(("Pot 4", 19, "Luhan", "FC Zorya Luhansk (UKR)", 16.000, 1,3 , "UECL")) +new_teams.append(("Pot 4", 20, "Astana", "FC Astana (KAZ)", 14.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 21, "Beşikt", "Beşiktaş (TUR)", 14.000, 3,5 , "UECL")) +new_teams.append(("Pot 4", 22, "Vilni", "FK Žalgiris Vilnius (LTU)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 23, "Helsi", "HJK Helsinki (FIN)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 24, "Flora", "FC Flora Tallinn (EST)", 10.500, 1,5 , "UECL")) +new_teams.append(("Pot 5", 25, "Legia", "Legia Warszawa (POL)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 5", 26, "Trna", "FC Spartak Trnava (SVK)", 10.500, 1,4 , "UECL")) +new_teams.append(("Pot 5", 27, "Ljubl", "NK Olimpija Ljubljana (SVN)", 9.000, 1,5 , "UECL")) +new_teams.append(("Pot 5", 28, "Zrinj", "HŠK Zrinjski (BIH)", 8.500, 1,5 , "UECL")) +new_teams.append(("Pot 5", 29, "Dnipr", "Dnipro-1 (UKR)", 8.000, 1,3 , "UECL")) +new_teams.append(("Pot 5", 30, "Klaks", "KÍ Klaksvík (FRO)", 8.000, 1,5 , "UECL")) +new_teams.append(("Pot 6", 31, "Aberd", "Aberdeen FC (SCO)", 8.000, 2,3 , "UECL")) +new_teams.append(("Pot 6", 32, "Čukar", "FK Čukarički (SRB)", 6.475, 1,5 , "UECL")) +new_teams.append(("Pot 6", 33, "Lugano", "FC Lugano (SUI)", 6.335, 1,3 , "UECL")) +new_teams.append(("Pot 6", 34, "Breida", "Breidablik (ISL)", 6.000, 1,5 , "UECL")) +new_teams.append(("Pot 6", 35, "Nordsj", "FC Nordsjælland (DEN)", 5.565, 1,5 , "UECL")) +new_teams.append(("Pot 6", 36, "Ballk", "Ballkani (KOS)", 3.000, 1,5 , "UECL")) + + + +CET_minus_1 = ['ENG','POR','SCO'] +CET_plus_1 = ['TUR','AZE','ISR','UKR'] + +# %% + + +for conf in Conference.objects.filter(scenario=scenario): + conf.teams.clear() + conf.collapseInView = True + conf.save() + +Team.objects.filter(season=scenario.season).update(active=False) +for t in new_teams: + + team_name = t[3].split('(')[0].strip() + team_country = t[3].split('(')[1].split(')')[0].strip() + abbreviation = t[4] + global_coeff = t[5] + domestic_coeff = t[6] + pot = int(t[0].split(' ')[1].strip()) + pos = int(t[1]) + competition = t[7] + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + if teamObj: + + pass + else: + print(t,"->", team_name) + gteam = GlobalTeam.objects.filter(name=team_name) + if gteam: + teamObj = Team.objects.create(season=scenario.season, + name=team_name, + attractivity=global_coeff+0.1*domestic_coeff, + position=pos, + pot=pot, + latitude=gteam.first().latitude, + longitude=gteam.first().longitude, + country=gteam.first().country, + active=True) + print("\tCreated team from global", team_name) + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + else: + print("\tTeam not found", team_name) + continue + + + Conference.objects.filter(scenario=scenario,name=competition).first().teams.add(teamObj.first()) + Conference.objects.filter(scenario=scenario,name=f"{competition}-{t[0]}").first().teams.add(teamObj.first()) + + # if global_coeff in [3,4,5]: + # Conference.objects.filter(scenario=scenario,name=f"Global Coeff {global_coeff}").first().teams.add(teamObj.first()) + + # if domestic_coeff in [3,5]: + # Conference.objects.filter(scenario=scenario,name=f"Domestic Coeff {domestic_coeff}").first().teams.add(teamObj.first()) + + if team_country in CET_minus_1: + Conference.objects.filter(scenario=scenario,name="CET-1").first().teams.add(teamObj.first()) + elif team_country in CET_plus_1: + Conference.objects.filter(scenario=scenario,name="CET+1").first().teams.add(teamObj.first()) + else: + Conference.objects.filter(scenario=scenario,name="CET").first().teams.add(teamObj.first()) + + teamObj.update(active=True) + teamObj.update(attractivity=global_coeff) + teamObj.update(position=pos) + teamObj.update(pot=pot) + if teamObj.first().shortname == "": + teamObj.update(shortname=abbreviation) + + + + +# %% diff --git a/uefa/scripts/uefa_ueluecl24_seeder.py b/uefa/scripts/uefa_ueluecl24_seeder.py new file mode 100755 index 0000000..99b709e --- /dev/null +++ b/uefa/scripts/uefa_ueluecl24_seeder.py @@ -0,0 +1,259 @@ +# %% +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 django.shortcuts import HttpResponseRedirect +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.urls import reverse +from django.core.files.storage import FileSystemStorage +from django.core.mail import send_mail +from django_tex.shortcuts import render_to_pdf + +from celery.result import AsyncResult +import googlemaps +import timeit +import random +import json +import builtins as __builtin__ +import csv + +from leagues.celery import celery +from leagues.settings import EMAIL_DEFAULT_FROM, EMAIL_DEFAULT_TO +from leagues.settings import RUN_ENV, INSTANCE, DEBUG +from common.tasks import log_telegram +from common.functions import * +from scheduler.models import * +from scheduler.helpers import * +from scheduler.widgets import widget_context_kpis +from scheduler.solver.optimizer import optimize_2phases, optimize_sequentially +import scheduler.solver.optimizer as so +from draws.solver import optimize_draws + +import time as timer + +from qualifiers.helpers import import_globals + + + +# %% + +scenario = Scenario.objects.get(id=9) +# import_globals(scenario.season.id) +# teams = scenario.season.scheduler_teams.all() + + + + +# %% + +new_teams = [] +new_teams.append(("Pot 1",1, "Roma", "AS Roma (ITA) ", 97.000, 5,5, "UEL")) +new_teams.append(("Pot 1",2, "Ajax", "AFC Ajax (NED)", 89.000, 5,5 , "UEL")) +new_teams.append(("Pot 1",3, "Villa", "Villarreal CF (ESP)", 82.000, 5,5 , "UEL")) +new_teams.append(("Pot 1",4, "Bayer", "Bayer 04 Leverkusen (GER)", 72.000, 4,4 , "UEL")) +new_teams.append(("Pot 1",5, "GNK", "GNK Dinamo (CRO) ", 55.000, 2,5 , "UEL")) +new_teams.append(("Pot 1",6, "Ranger", "Rangers FC (SCO)", 54.000, 4,5 , "UEL")) +new_teams.append(("Pot 1",7, "Sporti", "Sporting Clube de Portugal (POR)", 52.500, 4,5 , "UEL")) +new_teams.append(("Pot 1",8, "Slavia", "SK Slavia Praha (CZE)", 52.000, 2,5 , "UEL")) +new_teams.append(("Pot 1",9, "WestHam", "West Ham United FC (ENG)", 50.000, 4,4 , "UEL")) +new_teams.append(("Pot 2",10, "Alkmaa", "AZ Alkmaar (NED)", 47.500, 2,3 , "UEL")) +new_teams.append(("Pot 2",11, "Stade", "Stade Rennais FC (FRA) ", 44.000, 3,5 , "UEL")) +new_teams.append(("Pot 2",12, "Olymp", "Olympiacos FC (GRE)", 39.000, 2,5 , "UEL")) +new_teams.append(("Pot 2",13, "Betis", "Real Betis Balompié (ESP)", 37.000, 3,4 , "UEL")) +new_teams.append(("Pot 2",14, "Linz", "LASK Linz (AUT)", 36.000, 1,4 , "UEL")) +new_teams.append(("Pot 2",15, "Kyiv", "FC Dynamo Kyiv (UKR)", 35.000, 3,5 , "UEL")) +new_teams.append(("Pot 2",16, "Lille", "LOSC Lille (FRA)", 30.000, 3,4 , "UEL")) +new_teams.append(("Pot 2",17, "Feren", "Ferencvárosi TC (HUN)", 27.000, 1,5 , "UEL")) +new_teams.append(("Pot 2",18, "Molde", "Molde FK (NOR)", 24.000, 1,5 , "UEL")) +new_teams.append(("Pot 3",19, "Bright", "Brighton & Hove Albion (ENG)", 21.914, 3,3 , "UEL")) +new_teams.append(("Pot 3",20, "Aston", "Aston Villa FC (ENG)", 21.914, 4,4 , "UEL")) +new_teams.append(("Pot 3",21, "Fiore", "ACF Fiorentina (ITA)", 20.000, 4,4 , "UEL")) +new_teams.append(("Pot 3",22, "Tiras", "FC Sheriff Tiraspol (MDA) ", 19.500, 1,5 , "UEL")) +new_teams.append(("Pot 3",23, "St.Gil", "Union Saint-Gilloise (BEL)", 19.000, 1,4 , "UEL")) +new_teams.append(("Pot 3",24, "Rapid", "SK Rapid Wien (AUT)", 18.500, 2,5 , "UEL")) +new_teams.append(("Pot 3",25, "Freib", "SC Freiburg (GER)", 16.496, 4,4 , "UEL")) +new_teams.append(("Pot 3",26, "Sparta", "AC Sparta Praha (CZE)", 14.000, 2,5 , "UEL")) +new_teams.append(("Pot 3",27, "Haifa", "Maccabi Haifa FC (ISR)", 13.000, 2,5 , "UEL")) +new_teams.append(("Pot 4",28, "Sturm", "SK Sturm Graz (AUT)", 12.500, 1,5 , "UEL")) +new_teams.append(("Pot 4",29, "Toul", "Toulouse FC (FRA)", 12.232, 3,4 , "UEL")) +new_teams.append(("Pot 4",30, "Athen", "AEK Athens FC (GRE)", 11.000, 1,4 , "UEL")) +new_teams.append(("Pot 4",31, "Topola", "FK TSC Bačka Topola (SRB)", 6.475, 1,5 , "UEL")) +new_teams.append(("Pot 4",32, "Serve", "Servette FC (SUI) ", 6.335, 1,4 , "UEL")) +new_teams.append(("Pot 4",33, "Panat", "Panathinaikos FC (GRE) ", 5.045, 1,5 , "UEL")) +new_teams.append(("Pot 4",34, "Raków", "Raków Czestochowa (POL) ", 5.000, 1,3 , "UEL")) +new_teams.append(("Pot 4",35, "Aris", "Aris Limassol FC (CYP)", 4.895, 1,5 , "UEL")) +new_teams.append(("Pot 4",36, "Häcke", "BK Häcken (SWE)", 4.750, 1,5 , "UEL")) + +new_teams.append(("Pot 1", 1, "Hotsp", "Tottenham Hotspur (ENG)", 80.000, 5,5 , "UECL")) +new_teams.append(("Pot 1", 2, "Frank", "Eintracht Frankfurt (GER)", 77.000, 5,5 , "UECL")) +new_teams.append(("Pot 1", 3, "Brugg", "Club Brugge (BEL)", 54.000, 2,5 , "UECL")) +new_teams.append(("Pot 1", 4, "Basel", "FC Basel 1893 (SUI)", 53.000, 3,5 , "UECL")) +new_teams.append(("Pot 1", 5, "Gent", "KAA Gent (BEL)", 37.500, 1,4 , "UECL")) +new_teams.append(("Pot 1", 6, "Fener", "Fenerbahçe SK (TUR)", 30.000, 3,5 , "UECL")) +new_teams.append(("Pot 2", 7, "Monac", "AS Monaco FC (FRA)", 29.000, 4,5 , "UECL")) +new_teams.append(("Pot 2", 8, "Cluj", "CFR 1907 Cluj (ROU)", 27.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 9, "Midty", "FC Midtjylland (DEN)", 25.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 10, "PAOK", "PAOK FC (GRE)", 25.000, 1,3 , "UECL")) +new_teams.append(("Pot 2", 11, "Brati", "ŠK Slovan Bratislava (SVK)", 24.500, 1,5 , "UECL")) +new_teams.append(("Pot 2", 12, "Macca", "Maccabi Tel-Aviv FC (ISR)", 24.000, 1,5 , "UECL")) +new_teams.append(("Pot 3", 13, "Plzeň", "FC Viktoria Plzeň (CZE)", 22.000, 2,4 , "UECL")) +new_teams.append(("Pot 3", 14, "Ludog", "PFC Ludogorets 1945 (BUL)", 21.000, 1,5 , "UECL")) +new_teams.append(("Pot 3", 15, "Glimt", "FK Bodø/Glimt (NOR)", 20.000, 2,5 , "UECL")) +new_teams.append(("Pot 3", 16, "Osaun", "CA Osasuna (ESP)", 18.599, 3,3 , "UECL")) +new_teams.append(("Pot 3", 17, "Genk", "KRC Genk (BEL)", 18.000, 1,4 , "UECL")) +new_teams.append(("Pot 3", 18, "Bolog", "FC Bologna (ITA)", 16.385, 3,3 , "UECL")) +new_teams.append(("Pot 4", 19, "Luhan", "FC Zorya Luhansk (UKR)", 16.000, 1,3 , "UECL")) +new_teams.append(("Pot 4", 20, "Astana", "FC Astana (KAZ)", 14.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 21, "Beşikt", "Beşiktaş (TUR)", 14.000, 3,5 , "UECL")) +new_teams.append(("Pot 4", 22, "Vilni", "FK Žalgiris Vilnius (LTU)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 23, "Helsi", "HJK Helsinki (FIN)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 4", 24, "Flora", "FC Flora Tallinn (EST)", 10.500, 1,5 , "UECL")) +new_teams.append(("Pot 5", 25, "Legia", "Legia Warszawa (POL)", 11.000, 1,5 , "UECL")) +new_teams.append(("Pot 5", 26, "Trna", "FC Spartak Trnava (SVK)", 10.500, 1,4 , "UECL")) +new_teams.append(("Pot 5", 27, "Ljubl", "NK Olimpija Ljubljana (SVN)", 9.000, 1,5 , "UECL")) +new_teams.append(("Pot 5", 28, "Zrinj", "HŠK Zrinjski (BIH)", 8.500, 1,5 , "UECL")) +new_teams.append(("Pot 5", 29, "Dnipr", "Dnipro-1 (UKR)", 8.000, 1,3 , "UECL")) +new_teams.append(("Pot 5", 30, "Klaks", "KÍ Klaksvík (FRO)", 8.000, 1,5 , "UECL")) +new_teams.append(("Pot 6", 31, "Aberd", "Aberdeen FC (SCO)", 8.000, 2,3 , "UECL")) +new_teams.append(("Pot 6", 32, "Čukar", "FK Čukarički (SRB)", 6.475, 1,5 , "UECL")) +new_teams.append(("Pot 6", 33, "Lugano", "FC Lugano (SUI)", 6.335, 1,3 , "UECL")) +new_teams.append(("Pot 6", 34, "Breida", "Breidablik (ISL)", 6.000, 1,5 , "UECL")) +new_teams.append(("Pot 6", 35, "Nordsj", "FC Nordsjælland (DEN)", 5.565, 1,5 , "UECL")) +new_teams.append(("Pot 6", 36, "Ballk", "Ballkani (KOS)", 3.000, 1,5 , "UECL")) + + + +CET_minus_1 = ['ENG','POR','SCO'] +CET_plus_1 = ['TUR','AZE','ISR','UKR'] + +# %% + +for i in range(1,6): + Conference.objects.get_or_create(scenario=scenario,name=f"Global Coeff {i}") + Conference.objects.get_or_create(scenario=scenario,name=f"Domestic Coeff {i}") +Conference.objects.get_or_create(scenario=scenario,name="CET") +Conference.objects.get_or_create(scenario=scenario,name="CET-1") +Conference.objects.get_or_create(scenario=scenario,name="CET+1") + +for conf in Conference.objects.filter(scenario=scenario): + conf.teams.clear() + conf.collapseInView = True + conf.save() + +Team.objects.filter(season=scenario.season).update(active=False) +for t in new_teams: + + team_name = t[3].split('(')[0].strip() + team_country = t[3].split('(')[1].split(')')[0].strip() + abbreviation = t[2] + global_coeff = t[5] + domestic_coeff = t[6] + pot = int(t[0].split(' ')[1].strip()) + pos = int(t[1]) + competition = t[7] + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + if teamObj: + + pass + else: + print(t,"->", team_name) + gteam = GlobalTeam.objects.filter(name=team_name) + if gteam: + teamObj = Team.objects.create(season=scenario.season, + name=team_name, + attractivity=global_coeff+0.1*domestic_coeff, + position=pos, + pot=pot, + latitude=gteam.first().latitude, + longitude=gteam.first().longitude, + country=gteam.first().country, + active=True) + print("\tCreated team from global", team_name) + teamObj = Team.objects.filter(season=scenario.season,name=team_name) + else: + print("\tTeam not found", team_name) + continue + + + Conference.objects.filter(scenario=scenario,name=competition).first().teams.add(teamObj.first()) + Conference.objects.filter(scenario=scenario,name=f"{competition}-{t[0]}").first().teams.add(teamObj.first()) + + if global_coeff in range(1,6): + Conference.objects.filter(scenario=scenario,name=f"Global Coeff {global_coeff}").first().teams.add(teamObj.first()) + + if domestic_coeff in range(1,6): + Conference.objects.filter(scenario=scenario,name=f"Domestic Coeff {domestic_coeff}").first().teams.add(teamObj.first()) + + if team_country in CET_minus_1: + Conference.objects.filter(scenario=scenario,name="CET-1").first().teams.add(teamObj.first()) + elif team_country in CET_plus_1: + Conference.objects.filter(scenario=scenario,name="CET+1").first().teams.add(teamObj.first()) + else: + Conference.objects.filter(scenario=scenario,name="CET").first().teams.add(teamObj.first()) + + teamObj.update(active=True) + teamObj.update(attractivity=global_coeff+0.1*domestic_coeff) + if competition == "UEL": + teamObj.update(coefficient=5-pot) + if competition == "UECL": + teamObj.update(coefficient=7-pot) + teamObj.update(position=pos) + teamObj.update(pot=pot) + if teamObj.first().shortname == "": + teamObj.update(shortname=abbreviation) + + +for conf in Conference.objects.filter(scenario=scenario): + for t in conf.teams.filter(active=False): + conf.teams.remove(t) + +for haw in HAWish.objects.filter(scenario=scenario): + for t in haw.teams.filter(active=False): + haw.teams.remove(t) + +for enc in EncWish.objects.filter(scenario=scenario): + for t in enc.teams1.filter(active=False): + enc.teams1.remove(t) + for t in enc.teams2.filter(active=False): + enc.teams1.remove(t) + +for pair in Pairing.objects.filter(scenario=scenario): + if pair.team1.active==False or pair.team2.active==False: + pair.delete() + + +# %%