summaryrefslogtreecommitdiff
path: root/augment.py
blob: d96d51104ecc211d4840622776ca23f1f54de0ee (plain)
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import json
from pprint import pprint
from random import randrange, randint

league_count = 4
tabelle = []

with open('data.json') as f:
    data = json.load(f)

ident = 0
for o in data["orte"]:
    o["id"] = ident
    o["raucher"] = not randrange(2)
    o["teams"] = []
    ident += 1

ident = 0
for t in data["teams"]:

    t["id"] = ident
    t["tag"] = randrange(7)
    t["liga"] = randrange(league_count)
    t["nichtraucher"] = not randrange(4)

    team_nr = t["nichtraucher"]

    found = False
    while not found:
        heimort = randrange(len(data["orte"]))
        ersatzort = randrange(len(data["orte"]))

        if data["orte"][ersatzort]["raucher"]:
            continue

        ort_nr = not data["orte"][heimort]["raucher"]

        if team_nr != ort_nr:
            continue

        if team_nr:
            ersatzort = -1

        found = True

    t["ersatzort"] = ersatzort
    t["heimort"] = heimort
    data["orte"][heimort]["teams"].append(ident)

    ident += 1

# Count necessary spieltage
max_spieltage = 0
for league in range(league_count):
    pairings_count = len([t for t in data["teams"] if t["liga"] == league])
    if pairings_count % 2:
        pairings_count += 1
    max_spieltage = max(2 * (pairings_count - 1), max_spieltage);

print( "Max Spieltage: ", max_spieltage)

# Fill Tabelle with our demo data
ident = 0
for league in range(league_count):
    league_teams = [t for t in data["teams"] if t["liga"] == league]

    for team_a in league_teams:
        game_count = 0

        for team_b in league_teams:
            if team_a["id"] == team_b["id"]: continue

            # If Heimspiel-Team is smokers and the Gastteam is not, the alternative
            # location needs to be chosen
            ort = next((ort for ort in data["orte"] if ort["id"] == team_a["heimort"]), None)
            if team_b["nichtraucher"] and ort["raucher"]:
                ort = next((ort for ort in data["orte"] if ort["id"] == team_a["ersatzort"]), None)

            tabelle.append({"team_a": team_a["id"], "team_b": team_b["id"], "liga": league, "ort": ort["id"], "tag": team_a["tag"], "id": ident})
            ident += 1
            game_count += 1

        while game_count < max_spieltage / 2:
            tabelle.append({"team_a": team_a["id"], "team_b": -1, "liga": league, "ort": -1, "tag": -1, "id": ident})
            ident += 1
            tabelle.append({"team_a": -1, "team_b": team_a["id"], "liga": league, "ort": -1, "tag": -1, "id": ident})
            ident += 1
            game_count += 1

rueckrundenstart = max_spieltage // 2

from ortools.sat.python import cp_model
model = cp_model.CpModel()

variables = {game["id"]: model.new_int_var(0, max_spieltage - 1, f"game{game['id']}") for game in tabelle}

# Make sure each team only plays once each spieltag
for team in data["teams"]:
    # team_games = [game["id"] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]]
    # print (len(team_games), team_games, team)
    model.add_all_different([variables[game["id"]] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]])

# Make sure each ort is only used once per spieltag+week
for ort in data["orte"]:
    ort_games = [game for game in tabelle if game["ort"] == ort["id"]]
    for tag in list({game["tag"] for game in ort_games}):
        model.add_all_different([variables[game["id"]] for game in ort_games if game["tag"] == tag ])

# Make sure that Rueckrundenspiele only happen at Rueckrunde
for game in tabelle:
    # Don't force this constraint on wildcard games
    if game["ort"] == -1: continue

    # Find Rueckspiel
    rueckspiel = next((candidate for candidate in tabelle if candidate["team_a"] == game["team_b"] and candidate["team_b"] == game["team_a"]), None)

    va = variables[game["id"]]
    vb = variables[rueckspiel["id"]]

    runde_a = model.new_int_var(0, 1, "tmpa")
    runde_b = model.new_int_var(0, 1, "tmpb")

    model.add_division_equality(runde_a, va, rueckrundenstart)
    model.add_division_equality(runde_b, vb, rueckrundenstart)
    model.add(runde_a != runde_b)

# Make sure that Heimspiele and Gastspiele alternate
violations = []
for team in data["teams"]:
    heimspiele = [candidate for candidate in tabelle if candidate["team_a"] == team["id"] ]

    for heimspiel in heimspiele:
        for conflict in heimspiele:
            if heimspiel["id"] == conflict["id"]: continue

            va = variables[heimspiel["id"]]
            vb = variables[conflict["id"]]

            tmpdiff_a = model.new_int_var(-max_spieltage, max_spieltage, "tmpa")
            tmpdiff_b = model.new_int_var(-max_spieltage, max_spieltage, "tmpb")

            model.add(tmpdiff_a == va - vb)
            model.add(tmpdiff_b == vb - va)

            tmpconsequtive_a = model.new_bool_var("tmpa")
            tmpconsequtive_b = model.new_bool_var("tmpb")

            model.add(tmpdiff_a == 1).only_enforce_if(tmpconsequtive_a)
            model.add(tmpdiff_a != 1).only_enforce_if(tmpconsequtive_a.Not())

            model.add(tmpdiff_b == 1).only_enforce_if(tmpconsequtive_b)
            model.add(tmpdiff_b != 1).only_enforce_if(tmpconsequtive_b.Not())

            is_consecutive = model.new_bool_var('is_consecutive')

            model.add_bool_or([tmpconsequtive_a, tmpconsequtive_b]).OnlyEnforceIf(is_consecutive)
            model.add_bool_and([tmpconsequtive_a.Not(), tmpconsequtive_b.Not()]).OnlyEnforceIf(is_consecutive.Not())

            violations.append(is_consecutive)

model.minimize(sum(violations))

print ("All set, solving")

solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 30.0
solver.parameters.random_seed = randint(0, 1_000_000)
status = solver.solve(model)
print(status, solver.status_name())

from sys import exit
if status == cp_model.INFEASIBLE:
    exit(-1)

for game in tabelle:
    game["spieltag"] = solver.value(variables[game["id"]])

# tabelle.sort(key=lambda game: game["spieltag"])

# PRINT OUT RESULTS AS TABLE
all_teams = {team["id"]: team for team in data["teams"]}
all_teams[-1] = {"name": "*"}
all_orte = {ort["id"]: ort for ort in data["orte"]}
all_orte[-1] = {"name": "*"}

for league in range(league_count):
    league_tabelle = [game for game in tabelle if game["liga"] == league]
    league_tabelle.sort(key=lambda game: game["spieltag"])

    for game in league_tabelle:
        print(game["spieltag"], all_teams[game["team_a"]]["name"], "::", all_teams[game["team_b"]]["name"], "::", all_orte[game["ort"]]["name"])

#print( json.dumps(data))
#print( json.dumps(tabelle))