Compare commits

..

2 Commits

Author SHA1 Message Date
f7b1267c98 Dienstaufwand und paralleles Rechnen 2026-02-01 21:48:03 +01:00
9f7d3c6d4a Formatierung Ausgabe 2026-02-01 20:33:11 +01:00
3 changed files with 51 additions and 35 deletions

View File

@ -118,28 +118,30 @@ class ElterndienstAusgabe:
positive_praef_tage = {tag for tag, präf in praeferenzen_dienst.items() if präf == 1}
if positive_praef_tage: # Es gibt positive Präferenzen
# Prüfe ob ALLE zugeteilten Dienste an nicht-präferierten Tagen sind
for tag in zugeteilte_tage:
if tag not in positive_praef_tage:
# Dienst wurde an nicht-präferiertem Tag zugeteilt
verletzungen[eltern][dienst]['positiv_nicht_erfuellt'] += 1
# Tabelle ausgeben
print(f"\n{'Eltern':<20} ", end='')
# Tabelle ausgeben (verbesserte Spaltenformatierung)
col_width = 14 # Breite pro Dienst-Spalte (sichtbar)
name_col = 20
# Header
print(f"\n{'Eltern':<{name_col}}", end='')
for dienst in self.daten.dienste:
print(f"{dienst.kuerzel:>12}", end='')
print(f"{dienst.kuerzel:^{col_width}}", end='')
print()
print(f"{'':20} ", end='')
for dienst in self.daten.dienste:
print(f"{'neg, pos':>12}", end='')
print(f"{'':{name_col}}", end='')
for _ in self.daten.dienste:
print(f"{'neg, pos':^{col_width}}", end='')
print()
print("-" * (20 + 12 * len(self.daten.dienste)))
print("-" * (name_col + col_width * len(self.daten.dienste)))
gesamt_negativ = defaultdict(int)
gesamt_positiv = defaultdict(int)
for eltern in sorted(self.daten.eltern):
print(f"{eltern:<20} ", end='')
print(f"{eltern:<{name_col}}", end='')
for dienst in self.daten.dienste:
neg = verletzungen[eltern][dienst]['negativ']
pos = verletzungen[eltern][dienst]['positiv_nicht_erfuellt']
@ -147,22 +149,28 @@ class ElterndienstAusgabe:
gesamt_negativ[dienst] += neg
gesamt_positiv[dienst] += pos
# Farbcodierung
# Inhalt vor Padding erstellen
cell = f"{neg:>3}, {pos:>3}"
cell_padded = cell.center(col_width)
# Farbcodierung (erst nach Padding anwenden)
farbe = ""
reset = ""
if neg > 0 or pos > 0:
farbe = "\033[91m" if neg > 0 else "\033[93m" # Rot für negativ, Gelb für positiv
reset = "\033[0m"
print(f"{farbe}{neg:>3}, {pos:>3}{reset:>6}", end='')
print(f"{farbe}{cell_padded}{reset}", end='')
print()
# Summenzeile
print("-" * (20 + 12 * len(self.daten.dienste)))
print(f"{'SUMME':<20} ", end='')
print("-" * (name_col + col_width * len(self.daten.dienste)))
print(f"{'SUMME':<{name_col}}", end='')
for dienst in self.daten.dienste:
neg = gesamt_negativ[dienst]
pos = gesamt_positiv[dienst]
cell = f"{neg:>3}, {pos:>3}"
cell_padded = cell.center(col_width)
farbe = ""
reset = ""
@ -170,7 +178,7 @@ class ElterndienstAusgabe:
farbe = "\033[91m" if neg > 0 else "\033[93m"
reset = "\033[0m"
print(f"{farbe}{neg:>3}, {pos:>3}{reset:>6}", end='')
print(f"{farbe}{cell_padded}{reset}", end='')
print()
print("\nLegende:")

View File

@ -15,16 +15,17 @@ from csv_io import EingabeParser
class Dienst:
"""Repräsentiert einen Diensttyp mit allen seinen Eigenschaften"""
def __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1) -> None:
def __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1, aufwand: int = 1) -> None:
self.kuerzel: str = kuerzel
self.name: str = name
self.personen_anzahl: int = personen_anzahl
self.aufwand: int = aufwand
def __str__(self) -> str:
return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en)"
return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en), Aufwand={self.aufwand}"
def __repr__(self) -> str:
return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl})"
return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl}, {self.aufwand})"
def braucht_mehrere_personen(self) -> bool:
"""Gibt True zurück, wenn mehr als eine Person benötigt wird"""
@ -49,11 +50,11 @@ class ElterndienstplanerDaten:
def __init__(self) -> None:
# Dienste als Liste definieren
self.dienste: List[Dienst] = [
Dienst('F', 'Frühstücksdienst', 1),
Dienst('P', 'Putznotdienst', 1),
Dienst('E', 'Essensausgabenotdienst', 1),
Dienst('K', 'Kochen', 1),
Dienst('A', 'Elternabend', 2)
Dienst('F', 'Frühstücksdienst', 1, aufwand=3),
Dienst('P', 'Putznotdienst', 1, aufwand=1),
Dienst('E', 'Essensausgabenotdienst', 1, aufwand=1),
Dienst('K', 'Kochen', 1, aufwand=3),
Dienst('A', 'Elternabend', 2, aufwand=2)
]
# Datenstrukturen

View File

@ -8,6 +8,7 @@ Datum: Dezember 2025
import sys
import pulp
import multiprocessing
from datetime import timedelta, date
from collections import defaultdict
from typing import Dict, List, Tuple, DefaultDict, Optional
@ -355,14 +356,16 @@ class Elterndienstplaner:
lowBound=0)
# Zähle tatsächliche Dienste gewichtet mit dem Aufwand des Dienstes
tatsaechliche_dienste_gesamt = pulp.lpSum(
x[eltern, tag, dienst]
dienst.aufwand * x[eltern, tag, dienst]
for tag in self.daten.planungszeitraum
for dienst in self.daten.dienste
if (eltern, tag, dienst) in x
)
ziel_gesamt = sum(ziel_dienste[eltern][dienst] for dienst in self.daten.dienste)
# Zielgesamt ebenfalls mit Dienst-Aufwand gewichtet
ziel_gesamt = sum(ziel_dienste[eltern][dienst] * dienst.aufwand for dienst in self.daten.dienste)
prob += (tatsaechliche_dienste_gesamt - ziel_gesamt <=
fairness_abweichung_gesamt[eltern])
@ -393,21 +396,23 @@ class Elterndienstplaner:
for eltern in self.daten.eltern:
for dienst in self.daten.dienste:
objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst])
objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst])
# Skaliere diensttyp-spezifische Fairness mit dem Aufwand des Dienstes
objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst] * dienst.aufwand)
objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst] * dienst.aufwand)
# Gesamt-Fairness (bereits dienstabhängig in den Constraints) — keine zusätzliche Mean-Skalierung mehr
objective_terms.append(gewicht_f3_global * fairness_abweichung_gesamt_global[eltern])
objective_terms.append(gewicht_f4_lokal * fairness_abweichung_gesamt_lokal[eltern])
# P1: Bevorzugte Dienste
# P1: Bevorzugte Dienste (stärker für aufwändigere Dienste)
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
if (eltern, tag, dienst) in x and praef == 1:
objective_terms.append(-5 * x[eltern, tag, dienst])
objective_terms.append(-10 * dienst.aufwand * x[eltern, tag, dienst])
# P2: Abgelehnte Dienste
# P2: Abgelehnte Dienste (stärker für aufwändigere Dienste)
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
if (eltern, tag, dienst) in x and praef == -1:
objective_terms.append(25 * x[eltern, tag, dienst])
objective_terms.append(20 * dienst.aufwand * x[eltern, tag, dienst])
if objective_terms:
prob += pulp.lpSum(objective_terms)
@ -480,13 +485,15 @@ class Elterndienstplaner:
solver = None
try:
print("Versuche CBC Solver...")
solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=60)
except:
cpu_count = multiprocessing.cpu_count()
threads = max(1, cpu_count - 1)
print(f"Versuche CBC Solver mit {threads} Threads...")
solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=20, threads=threads)
except Exception:
try:
print("Versuche GLPK Solver...")
solver = pulp.GLPK_CMD(msg=0)
except:
except Exception:
print("Kein spezifizierter Solver verfügbar, verwende Standard.")
solver = None