summaryrefslogtreecommitdiff
path: root/augment.py
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2025-05-26 15:55:29 +0200
committererdgeist <erdgeist@erdgeist.org>2025-05-26 15:55:29 +0200
commit47cb23ce1f991c21ceb9273cf4bed717a09abd9a (patch)
tree90f6e3299abf5ec632123513fc80959f3336e15e /augment.py
Kickoff commit
Diffstat (limited to 'augment.py')
-rw-r--r--augment.py194
1 files changed, 194 insertions, 0 deletions
diff --git a/augment.py b/augment.py
new file mode 100644
index 0000000..d96d511
--- /dev/null
+++ b/augment.py
@@ -0,0 +1,194 @@
1import json
2from pprint import pprint
3from random import randrange, randint
4
5league_count = 4
6tabelle = []
7
8with open('data.json') as f:
9 data = json.load(f)
10
11ident = 0
12for o in data["orte"]:
13 o["id"] = ident
14 o["raucher"] = not randrange(2)
15 o["teams"] = []
16 ident += 1
17
18ident = 0
19for t in data["teams"]:
20
21 t["id"] = ident
22 t["tag"] = randrange(7)
23 t["liga"] = randrange(league_count)
24 t["nichtraucher"] = not randrange(4)
25
26 team_nr = t["nichtraucher"]
27
28 found = False
29 while not found:
30 heimort = randrange(len(data["orte"]))
31 ersatzort = randrange(len(data["orte"]))
32
33 if data["orte"][ersatzort]["raucher"]:
34 continue
35
36 ort_nr = not data["orte"][heimort]["raucher"]
37
38 if team_nr != ort_nr:
39 continue
40
41 if team_nr:
42 ersatzort = -1
43
44 found = True
45
46 t["ersatzort"] = ersatzort
47 t["heimort"] = heimort
48 data["orte"][heimort]["teams"].append(ident)
49
50 ident += 1
51
52# Count necessary spieltage
53max_spieltage = 0
54for league in range(league_count):
55 pairings_count = len([t for t in data["teams"] if t["liga"] == league])
56 if pairings_count % 2:
57 pairings_count += 1
58 max_spieltage = max(2 * (pairings_count - 1), max_spieltage);
59
60print( "Max Spieltage: ", max_spieltage)
61
62# Fill Tabelle with our demo data
63ident = 0
64for league in range(league_count):
65 league_teams = [t for t in data["teams"] if t["liga"] == league]
66
67 for team_a in league_teams:
68 game_count = 0
69
70 for team_b in league_teams:
71 if team_a["id"] == team_b["id"]: continue
72
73 # If Heimspiel-Team is smokers and the Gastteam is not, the alternative
74 # location needs to be chosen
75 ort = next((ort for ort in data["orte"] if ort["id"] == team_a["heimort"]), None)
76 if team_b["nichtraucher"] and ort["raucher"]:
77 ort = next((ort for ort in data["orte"] if ort["id"] == team_a["ersatzort"]), None)
78
79 tabelle.append({"team_a": team_a["id"], "team_b": team_b["id"], "liga": league, "ort": ort["id"], "tag": team_a["tag"], "id": ident})
80 ident += 1
81 game_count += 1
82
83 while game_count < max_spieltage / 2:
84 tabelle.append({"team_a": team_a["id"], "team_b": -1, "liga": league, "ort": -1, "tag": -1, "id": ident})
85 ident += 1
86 tabelle.append({"team_a": -1, "team_b": team_a["id"], "liga": league, "ort": -1, "tag": -1, "id": ident})
87 ident += 1
88 game_count += 1
89
90rueckrundenstart = max_spieltage // 2
91
92from ortools.sat.python import cp_model
93model = cp_model.CpModel()
94
95variables = {game["id"]: model.new_int_var(0, max_spieltage - 1, f"game{game['id']}") for game in tabelle}
96
97# Make sure each team only plays once each spieltag
98for team in data["teams"]:
99 # team_games = [game["id"] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]]
100 # print (len(team_games), team_games, team)
101 model.add_all_different([variables[game["id"]] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]])
102
103# Make sure each ort is only used once per spieltag+week
104for ort in data["orte"]:
105 ort_games = [game for game in tabelle if game["ort"] == ort["id"]]
106 for tag in list({game["tag"] for game in ort_games}):
107 model.add_all_different([variables[game["id"]] for game in ort_games if game["tag"] == tag ])
108
109# Make sure that Rueckrundenspiele only happen at Rueckrunde
110for game in tabelle:
111 # Don't force this constraint on wildcard games
112 if game["ort"] == -1: continue
113
114 # Find Rueckspiel
115 rueckspiel = next((candidate for candidate in tabelle if candidate["team_a"] == game["team_b"] and candidate["team_b"] == game["team_a"]), None)
116
117 va = variables[game["id"]]
118 vb = variables[rueckspiel["id"]]
119
120 runde_a = model.new_int_var(0, 1, "tmpa")
121 runde_b = model.new_int_var(0, 1, "tmpb")
122
123 model.add_division_equality(runde_a, va, rueckrundenstart)
124 model.add_division_equality(runde_b, vb, rueckrundenstart)
125 model.add(runde_a != runde_b)
126
127# Make sure that Heimspiele and Gastspiele alternate
128violations = []
129for team in data["teams"]:
130 heimspiele = [candidate for candidate in tabelle if candidate["team_a"] == team["id"] ]
131
132 for heimspiel in heimspiele:
133 for conflict in heimspiele:
134 if heimspiel["id"] == conflict["id"]: continue
135
136 va = variables[heimspiel["id"]]
137 vb = variables[conflict["id"]]
138
139 tmpdiff_a = model.new_int_var(-max_spieltage, max_spieltage, "tmpa")
140 tmpdiff_b = model.new_int_var(-max_spieltage, max_spieltage, "tmpb")
141
142 model.add(tmpdiff_a == va - vb)
143 model.add(tmpdiff_b == vb - va)
144
145 tmpconsequtive_a = model.new_bool_var("tmpa")
146 tmpconsequtive_b = model.new_bool_var("tmpb")
147
148 model.add(tmpdiff_a == 1).only_enforce_if(tmpconsequtive_a)
149 model.add(tmpdiff_a != 1).only_enforce_if(tmpconsequtive_a.Not())
150
151 model.add(tmpdiff_b == 1).only_enforce_if(tmpconsequtive_b)
152 model.add(tmpdiff_b != 1).only_enforce_if(tmpconsequtive_b.Not())
153
154 is_consecutive = model.new_bool_var('is_consecutive')
155
156 model.add_bool_or([tmpconsequtive_a, tmpconsequtive_b]).OnlyEnforceIf(is_consecutive)
157 model.add_bool_and([tmpconsequtive_a.Not(), tmpconsequtive_b.Not()]).OnlyEnforceIf(is_consecutive.Not())
158
159 violations.append(is_consecutive)
160
161model.minimize(sum(violations))
162
163print ("All set, solving")
164
165solver = cp_model.CpSolver()
166solver.parameters.max_time_in_seconds = 30.0
167solver.parameters.random_seed = randint(0, 1_000_000)
168status = solver.solve(model)
169print(status, solver.status_name())
170
171from sys import exit
172if status == cp_model.INFEASIBLE:
173 exit(-1)
174
175for game in tabelle:
176 game["spieltag"] = solver.value(variables[game["id"]])
177
178# tabelle.sort(key=lambda game: game["spieltag"])
179
180# PRINT OUT RESULTS AS TABLE
181all_teams = {team["id"]: team for team in data["teams"]}
182all_teams[-1] = {"name": "*"}
183all_orte = {ort["id"]: ort for ort in data["orte"]}
184all_orte[-1] = {"name": "*"}
185
186for league in range(league_count):
187 league_tabelle = [game for game in tabelle if game["liga"] == league]
188 league_tabelle.sort(key=lambda game: game["spieltag"])
189
190 for game in league_tabelle:
191 print(game["spieltag"], all_teams[game["team_a"]]["name"], "::", all_teams[game["team_b"]]["name"], "::", all_orte[game["ort"]]["name"])
192
193#print( json.dumps(data))
194#print( json.dumps(tabelle))