From 47cb23ce1f991c21ceb9273cf4bed717a09abd9a Mon Sep 17 00:00:00 2001 From: erdgeist Date: Mon, 26 May 2025 15:55:29 +0200 Subject: Kickoff commit --- augment.py | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 augment.py (limited to 'augment.py') diff --git a/augment.py b/augment.py new file mode 100644 index 0000000..d96d511 --- /dev/null +++ b/augment.py @@ -0,0 +1,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)) -- cgit v1.2.3