diff options
author | erdgeist <erdgeist@erdgeist.org> | 2025-05-26 15:55:29 +0200 |
---|---|---|
committer | erdgeist <erdgeist@erdgeist.org> | 2025-05-26 15:55:29 +0200 |
commit | 47cb23ce1f991c21ceb9273cf4bed717a09abd9a (patch) | |
tree | 90f6e3299abf5ec632123513fc80959f3336e15e /augment.py |
Kickoff commit
Diffstat (limited to 'augment.py')
-rw-r--r-- | augment.py | 194 |
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 @@ | |||
1 | import json | ||
2 | from pprint import pprint | ||
3 | from random import randrange, randint | ||
4 | |||
5 | league_count = 4 | ||
6 | tabelle = [] | ||
7 | |||
8 | with open('data.json') as f: | ||
9 | data = json.load(f) | ||
10 | |||
11 | ident = 0 | ||
12 | for o in data["orte"]: | ||
13 | o["id"] = ident | ||
14 | o["raucher"] = not randrange(2) | ||
15 | o["teams"] = [] | ||
16 | ident += 1 | ||
17 | |||
18 | ident = 0 | ||
19 | for 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 | ||
53 | max_spieltage = 0 | ||
54 | for 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 | |||
60 | print( "Max Spieltage: ", max_spieltage) | ||
61 | |||
62 | # Fill Tabelle with our demo data | ||
63 | ident = 0 | ||
64 | for 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 | |||
90 | rueckrundenstart = max_spieltage // 2 | ||
91 | |||
92 | from ortools.sat.python import cp_model | ||
93 | model = cp_model.CpModel() | ||
94 | |||
95 | variables = {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 | ||
98 | for 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 | ||
104 | for 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 | ||
110 | for 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 | ||
128 | violations = [] | ||
129 | for 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 | |||
161 | model.minimize(sum(violations)) | ||
162 | |||
163 | print ("All set, solving") | ||
164 | |||
165 | solver = cp_model.CpSolver() | ||
166 | solver.parameters.max_time_in_seconds = 30.0 | ||
167 | solver.parameters.random_seed = randint(0, 1_000_000) | ||
168 | status = solver.solve(model) | ||
169 | print(status, solver.status_name()) | ||
170 | |||
171 | from sys import exit | ||
172 | if status == cp_model.INFEASIBLE: | ||
173 | exit(-1) | ||
174 | |||
175 | for 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 | ||
181 | all_teams = {team["id"]: team for team in data["teams"]} | ||
182 | all_teams[-1] = {"name": "*"} | ||
183 | all_orte = {ort["id"]: ort for ort in data["orte"]} | ||
184 | all_orte[-1] = {"name": "*"} | ||
185 | |||
186 | for 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)) | ||