Strafen können durch benannte Profile festgelegt werden
This commit is contained in:
@@ -72,8 +72,9 @@ the same layout:
|
|||||||
|
|
||||||
The GA cycle and penalty scheme are read directly from `$PLATZ_CFG/zyklus.cfg` and
|
The GA cycle and penalty scheme are read directly from `$PLATZ_CFG/zyklus.cfg` and
|
||||||
`$PLATZ_CFG/strafen.cfg` (independent of `--indir`). There is no `platz.cfg` indirection any
|
`$PLATZ_CFG/strafen.cfg` (independent of `--indir`). There is no `platz.cfg` indirection any
|
||||||
more — it was removed; `zyklus.cfg`/`strafen.cfg` are loaded straight from `$PLATZ_CFG`, and
|
more — it was removed; `zyklus.cfg`/`strafen.cfg` are loaded straight from `$PLATZ_CFG`, the
|
||||||
the GA profile is chosen by the `--zyklus-art <name>` switch (default `Adaptiv`).
|
GA profile is chosen by the `--zyklus-art <name>` switch (default `Adaptiv`), and the penalty
|
||||||
|
profile by the `--strafen <name>` switch (default `default`).
|
||||||
|
|
||||||
- `cfg/zyklus.cfg` — defines the GA profiles. Two flavours: **static** profiles (`Easy`,
|
- `cfg/zyklus.cfg` — defines the GA profiles. Two flavours: **static** profiles (`Easy`,
|
||||||
`Simple`) give an explicit sequence of actions (`e`=erzeugen/create, `s`=selektieren/
|
`Simple`) give an explicit sequence of actions (`e`=erzeugen/create, `s`=selektieren/
|
||||||
@@ -84,10 +85,18 @@ the GA profile is chosen by the `--zyklus-art <name>` switch (default `Adaptiv`)
|
|||||||
with a shrinking population (`Start`, `Schrumpfung`, `MinPopulation`) and a convergence
|
with a shrinking population (`Start`, `Schrumpfung`, `MinPopulation`) and a convergence
|
||||||
stop (`Geduld` generations without an improvement of at least `Schwelle`, capped by
|
stop (`Geduld` generations without an improvement of at least `Schwelle`, capped by
|
||||||
`MaxGenerationen`). Static profiles run via `Farm._LaufStatisch()`.
|
`MaxGenerationen`). Static profiles run via `Farm._LaufStatisch()`.
|
||||||
- `cfg/strafen.cfg` — the penalty ("Strafpunkte") table: cost of splitting a group across
|
- `cfg/strafen.cfg` — the penalty ("Strafpunkte") profiles. Each section is one complete
|
||||||
tables (by group size and number of splits), cost of a lone person, per-table
|
profile ("Vorgehen") in a single namespace, chosen by `--strafen <section>` (default
|
||||||
under-utilization penalties, and (optional `[Abstand]` section) a penalty for the spatial
|
`default`); this lets several scoring strategies live in one file (the shipped file also
|
||||||
distance between split-off parts of a group.
|
has a `locker` example with cheaper splits). Keys per profile: `Anzahl_Trennungen_N`
|
||||||
|
(`{groupsize:points}` for a group split across `N+1` tables, i.e. `N` splits — recursing to
|
||||||
|
smaller sizes when no exact entry), `NichtNachbar` (no group-mate at a neighbouring table),
|
||||||
|
`Allein` (a lone person), optional `TischWertigkeiten` (`{freeseats:points}` under-utilization,
|
||||||
|
with a nearest-lower-key fallback; absent = free seats not penalised, as in the `locker`
|
||||||
|
profile), and optional `AbstandFaktor` (per spatial-distance unit
|
||||||
|
between split-off parts; absent = distance not penalised). `Strafliste.laden(FileName,
|
||||||
|
Sektion='default')` parses one section; the old `[Trennung]`/`[Globals]`/`[Abstand]`
|
||||||
|
three-section layout is gone.
|
||||||
|
|
||||||
`tische.ini`/`bestellung.json`/`TischePersonen.txt`/`PersonenTische.csv` are fixed filenames
|
`tische.ini`/`bestellung.json`/`TischePersonen.txt`/`PersonenTische.csv` are fixed filenames
|
||||||
resolved as `os.path.join(indir, ...)` in `platz.py`.
|
resolved as `os.path.join(indir, ...)` in `platz.py`.
|
||||||
|
|||||||
+51
-33
@@ -1,38 +1,56 @@
|
|||||||
# Strafpunkte fuer die Trennung einer Gruppe:
|
# Strafpunkte-Profile ("Vorgehen") fuer die Bewertung einer Sitzplatzverteilung.
|
||||||
# Aufbau: Anzahl der Trennungen = { Gruppengroesse:Strafpunkte, .. }
|
#
|
||||||
# Deutlich hoeher als frueher, damit eine Trennung teurer ist als der Gewinn,
|
# Jede Sektion ist EIN vollstaendiges Bewertungsprofil mit allen Angaben. So
|
||||||
# einen Tisch dafuer teilbelegt zu lassen (sonst werden Gruppen getrennt,
|
# lassen sich mehrere Vorgehensweisen nebeneinander pflegen und speichern. Per
|
||||||
# obwohl daneben leere Tische stehen).
|
# Schalter --strafen <Sektionsname> waehlt man ein Profil; ohne Schalter wird
|
||||||
[Trennung]
|
# [default] verwendet.
|
||||||
1={ 2:-25, 3:-22, 4:-18, 6:-14, 8:-12, 10:-10 }
|
#
|
||||||
2={ 3:-40, 4:-30, 6:-22, 8:-18, 10:-14 }
|
# Schluessel je Profil:
|
||||||
3={ 4:-60, 6:-40, 8:-30, 10:-20 }
|
# Anzahl_Trennungen_N = { Gruppengroesse:Strafpunkte, .. }
|
||||||
|
# Strafpunkte fuer das Aufteilen einer Gruppe (N = Anzahl der Trennungen,
|
||||||
|
# also auf N+1 Tische verteilt), gestaffelt nach Gruppengroesse. Fehlt ein
|
||||||
|
# exakter Gruppengroessen-Eintrag, wird auf die naechstkleinere Groesse
|
||||||
|
# zurueckgegriffen. Mindestens Anzahl_Trennungen_1 sollte gesetzt sein.
|
||||||
|
# NichtNachbar = Strafe, wenn zwei Gruppenteile nicht am Nachbartisch sitzen
|
||||||
|
# Allein = Strafe, wenn eine Person ohne Gruppenmitglied am Tisch sitzt
|
||||||
|
# TischWertigkeiten = { freiePlaetze:Strafpunkte, .. } fuer freie Sitze an einem
|
||||||
|
# (teil-)belegten Tisch. Fehlt ein exakter Eintrag, gilt der
|
||||||
|
# naechstkleinere. Teilbelegte Tische sind am schlechtesten
|
||||||
|
# (dort passt keine ganze Gruppe mehr hin), daher steigt die
|
||||||
|
# Strafe mit den freien Plaetzen; ein komplett leerer Tisch
|
||||||
|
# (hoechster Schluessel) ist etwas besser, aber nicht gratis,
|
||||||
|
# damit sich das Zusammenziehen von Gruppen lohnt.
|
||||||
|
# Optional; fehlt der ganze Schluessel, werden freie Plaetze
|
||||||
|
# gar nicht bestraft -> mehr Teilbelegungen moeglich.
|
||||||
|
# AbstandFaktor = Strafpunkte je normierter Abstandseinheit (0..100) zwischen
|
||||||
|
# getrennten Teilen einer Gruppe. Der Abstand ist auf die
|
||||||
|
# maximale Ausdehnung der Tischanordnung normiert (100 =
|
||||||
|
# ein Teil ganz links/oben, der andere ganz rechts/unten).
|
||||||
|
# AbstandFaktor=-0.2 kostet den Maximalabstand also -20.
|
||||||
|
# Optional; fehlt der Schluessel, wird der Abstand nicht
|
||||||
|
# bestraft (Faktor 0).
|
||||||
|
|
||||||
# Wieviele Strafpunkte bekommt man
|
# Standardprofil: Trennungen sind bewusst teuer, damit eine Gruppe lieber
|
||||||
# wenn zwei Gruppen nicht am Nachbartisch sitzen
|
# zusammen an einem teilbelegten Tisch sitzt, als neben einem leeren getrennt zu
|
||||||
# wenn einer aus einer Gruppe allein sitzen muss
|
# werden.
|
||||||
[Globals]
|
[default]
|
||||||
|
Anzahl_Trennungen_1={ 2:-25, 3:-22, 4:-18, 6:-14, 8:-12, 10:-10 }
|
||||||
|
Anzahl_Trennungen_2={ 3:-40, 4:-30, 6:-22, 8:-18, 10:-14 }
|
||||||
|
Anzahl_Trennungen_3={ 4:-60, 6:-40, 8:-30, 10:-20 }
|
||||||
NichtNachbar=-3
|
NichtNachbar=-3
|
||||||
Allein=-30
|
Allein=-30
|
||||||
TischWertigkeiten={ 1:-3, 2:-3, 4:-5, 6:-6, 8:-4 }
|
TischWertigkeiten={ 1:-3, 2:-3, 4:-5, 6:-6, 8:-4 }
|
||||||
|
AbstandFaktor=-0.2
|
||||||
|
|
||||||
# TischWertigkeiten sind Strafpunkte fuer freie Sitze an einem Tisch, je nach
|
# Beispiel fuer ein alternatives Vorgehen: Trennungen sind guenstiger, der
|
||||||
# Anzahl freier Plaetze (fehlt ein exakter Eintrag, gilt der naechstkleinere).
|
# Abstand wird nicht bestraft, und freie Plaetze werden gar nicht bestraft
|
||||||
# Ziel: teilbelegte Tische sind am schlechtesten (dort passt keine ganze
|
# (TischWertigkeiten fehlt bewusst) — so sind mehr Teilbelegungen moeglich.
|
||||||
# Gruppe mehr hin und die Sitzenden haben leere Nachbarplaetze), daher steigt
|
# Nuetzlich fuer grosse Saele, in denen sich Gruppen kaum ungeteilt platzieren
|
||||||
# die Strafe mit den freien Plaetzen an (1..7 frei: -3 -> -6). Ein *komplett*
|
# lassen. Aufruf: --strafen locker
|
||||||
# leerer Tisch ist etwas besser als ein halbleerer (-4 bei 8 frei), weil ein
|
[locker]
|
||||||
# leerer Tisch als Ganzes fuer eine Gruppe frei bleibt - er soll aber nicht
|
Anzahl_Trennungen_1={ 2:-12, 3:-10, 4:-8, 6:-6, 8:-5, 10:-4 }
|
||||||
# gratis sein, damit sich das Zusammenziehen von Gruppen lohnt.
|
Anzahl_Trennungen_2={ 3:-20, 4:-16, 6:-12, 8:-9, 10:-7 }
|
||||||
|
Anzahl_Trennungen_3={ 4:-30, 6:-22, 8:-16, 10:-12 }
|
||||||
# Strafpunkte fuer den Abstand zwischen getrennten Teilen einer Gruppe.
|
NichtNachbar=-2
|
||||||
# Wird eine Gruppe auf mehrere Tische aufgeteilt, ist ein grosser raeumlicher
|
Allein=-20
|
||||||
# Abstand zwischen den Teilen viel schlechter als ein kleiner.
|
AbstandFaktor=0.0
|
||||||
# Der Abstand wird als normierter Wert 0..100 berechnet: Bezugsgroesse ist die
|
|
||||||
# maximale Ausdehnung der Tischanordnung (Breite oder Laenge). Ein Abstand von
|
|
||||||
# 100 heisst also, dass ein Gruppenteil ganz links/oben und der andere ganz
|
|
||||||
# rechts/unten sitzt.
|
|
||||||
# Faktor = Strafpunkte je normierter Abstandseinheit. Bei Faktor=-0.2 kostet
|
|
||||||
# der maximal moegliche Abstand (100) demnach -20 Punkte.
|
|
||||||
[Abstand]
|
|
||||||
Faktor=-0.2
|
|
||||||
|
|||||||
+35
-11
@@ -437,21 +437,45 @@ class Strafliste:
|
|||||||
return self.gibPunkte('Trennung',Gruppengroesse-1, N_Teilungen)
|
return self.gibPunkte('Trennung',Gruppengroesse-1, N_Teilungen)
|
||||||
else:
|
else:
|
||||||
raise IndexError("Bezeichner gibts nicht")
|
raise IndexError("Bezeichner gibts nicht")
|
||||||
def laden( self, FileName ):
|
def laden( self, FileName, Sektion='default' ):
|
||||||
print("-- Strafliste laden")
|
"""laedt ein Strafpunkte-Profil aus einer Sektion der strafen.cfg.
|
||||||
|
|
||||||
|
Jede Sektion ist ein vollstaendiges Bewertungs-'Vorgehen' mit allen
|
||||||
|
Angaben in einem gemeinsamen Namensraum. Der Aufrufer waehlt das
|
||||||
|
Profil ueber 'Sektion' (Vorgabe 'default'); so lassen sich mehrere
|
||||||
|
Bewertungsstrategien in derselben Datei pflegen. Die Trennungs-
|
||||||
|
Straffen stehen unter den Schluesseln 'Anzahl_Trennungen_N'."""
|
||||||
|
print("-- Strafliste laden (Profil '%s')" % Sektion)
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read( os.path.expanduser(FileName) )
|
config.read( os.path.expanduser(FileName) )
|
||||||
|
if not config.has_section( Sektion ):
|
||||||
|
raise ValueError(
|
||||||
|
"Strafprofil '%s' nicht in %s gefunden (vorhanden: %s)"
|
||||||
|
% ( Sektion, FileName, ', '.join(config.sections()) or '-' ) )
|
||||||
# ast.literal_eval statt eval: die Werte sind reine Literale
|
# ast.literal_eval statt eval: die Werte sind reine Literale
|
||||||
# (Zahlen und Dicts wie {2:-10}), also kein Code noetig.
|
# (Zahlen und Dicts wie {2:-10}), also kein Code noetig.
|
||||||
self.NichtNachbar = ast.literal_eval(config.get( 'Globals', 'NichtNachbar' ))
|
self.NichtNachbar = ast.literal_eval(config.get( Sektion, 'NichtNachbar' ))
|
||||||
self.Allein = ast.literal_eval(config.get( 'Globals', 'Allein' ))
|
self.Allein = ast.literal_eval(config.get( Sektion, 'Allein' ))
|
||||||
self.TischWert = ast.literal_eval( config.get( 'Globals', 'TischWertigkeiten' ) )
|
# TischWertigkeiten ist optional; fehlt der Schluessel, werden freie
|
||||||
# Abschnitt [Abstand] ist optional; fehlt er, wird der Abstand
|
# Plaetze gar nicht bestraft (leeres Dict -> gibPunkte('Tischbesetzung')
|
||||||
# getrennter Gruppen nicht bestraft (Faktor 0).
|
# gibt 0), wodurch mehr Teilbelegungen moeglich sind.
|
||||||
if config.has_section( 'Abstand' ):
|
if config.has_option( Sektion, 'TischWertigkeiten' ):
|
||||||
self.AbstandFaktor = ast.literal_eval(config.get( 'Abstand', 'Faktor' ))
|
self.TischWert = ast.literal_eval( config.get( Sektion, 'TischWertigkeiten' ) )
|
||||||
for (k,v) in config.items( 'Trennung' ):
|
else:
|
||||||
self.GruppenTrennen[int(k)] = ast.literal_eval(v)
|
self.TischWert = {}
|
||||||
|
# AbstandFaktor ist optional; fehlt er, wird der Abstand getrennter
|
||||||
|
# Gruppen nicht bestraft (Faktor 0).
|
||||||
|
if config.has_option( Sektion, 'AbstandFaktor' ):
|
||||||
|
self.AbstandFaktor = ast.literal_eval(config.get( Sektion, 'AbstandFaktor' ))
|
||||||
|
# Trennungs-Straffen: Schluessel 'Anzahl_Trennungen_N' -> N Trennungen.
|
||||||
|
# frisches Dict, damit nicht der (mutable) Konstruktor-Default geteilt
|
||||||
|
# wird und Reste eines frueheren laden() bleiben.
|
||||||
|
self.GruppenTrennen = {}
|
||||||
|
for (k, v) in config.items( Sektion ):
|
||||||
|
# configparser liefert Schluessel klein geschrieben
|
||||||
|
if k.startswith( 'anzahl_trennungen_' ):
|
||||||
|
N = int( k.rsplit( '_', 1 )[-1] )
|
||||||
|
self.GruppenTrennen[N] = ast.literal_eval(v)
|
||||||
|
|
||||||
|
|
||||||
# Sitzplatzverteilung, bestehend aus den Einzelelementen
|
# Sitzplatzverteilung, bestehend aus den Einzelelementen
|
||||||
|
|||||||
+9
-1
@@ -51,6 +51,10 @@ if __name__ == '__main__':
|
|||||||
parser.add_option( "--zyklus-art", dest="zyklusart", default="Adaptiv",
|
parser.add_option( "--zyklus-art", dest="zyklusart", default="Adaptiv",
|
||||||
help="Name des GA-Profils in zyklus.cfg (Vorgabe: Adaptiv). "
|
help="Name des GA-Profils in zyklus.cfg (Vorgabe: Adaptiv). "
|
||||||
"Weitere mitgelieferte Profile: Easy, Simple" )
|
"Weitere mitgelieferte Profile: Easy, Simple" )
|
||||||
|
parser.add_option( "--strafen", dest="strafen", default="default",
|
||||||
|
help="Name des Strafpunkte-Profils (Sektion in strafen.cfg; "
|
||||||
|
"Vorgabe: default). Erlaubt mehrere Bewertungs-Vorgehen in einer "
|
||||||
|
"Datei, z.B. --strafen locker" )
|
||||||
parser.add_option( "--seed", dest="seed", type="int", default=None,
|
parser.add_option( "--seed", dest="seed", type="int", default=None,
|
||||||
help="Zufalls-Startwert (int) fuer random.seed(), macht den Lauf "
|
help="Zufalls-Startwert (int) fuer random.seed(), macht den Lauf "
|
||||||
"reproduzierbar. Ohne Angabe laeuft der GA nichtdeterministisch. "
|
"reproduzierbar. Ohne Angabe laeuft der GA nichtdeterministisch. "
|
||||||
@@ -83,6 +87,7 @@ if __name__ == '__main__':
|
|||||||
Zyklusconfig = os.path.join( CfgDir, 'zyklus.cfg' )
|
Zyklusconfig = os.path.join( CfgDir, 'zyklus.cfg' )
|
||||||
Zyklusart = options.zyklusart
|
Zyklusart = options.zyklusart
|
||||||
Strafenconfig = os.path.join( CfgDir, 'strafen.cfg' )
|
Strafenconfig = os.path.join( CfgDir, 'strafen.cfg' )
|
||||||
|
Strafenart = options.strafen
|
||||||
|
|
||||||
# lade Eingabedateien
|
# lade Eingabedateien
|
||||||
Tischconfig = os.path.join( InDir, 'tische.ini' )
|
Tischconfig = os.path.join( InDir, 'tische.ini' )
|
||||||
@@ -99,7 +104,10 @@ if __name__ == '__main__':
|
|||||||
Z.laden( Zyklusconfig, Zyklusart )
|
Z.laden( Zyklusconfig, Zyklusart )
|
||||||
print(Z)
|
print(Z)
|
||||||
SL = Strafliste()
|
SL = Strafliste()
|
||||||
SL.laden( Strafenconfig )
|
try:
|
||||||
|
SL.laden( Strafenconfig, Strafenart )
|
||||||
|
except ValueError as Fehler:
|
||||||
|
parser.error( str(Fehler) )
|
||||||
print(SL)
|
print(SL)
|
||||||
TE = Tische()
|
TE = Tische()
|
||||||
TE.laden( Tischconfig )
|
TE.laden( Tischconfig )
|
||||||
|
|||||||
+4
-2
@@ -56,15 +56,17 @@ app = FastAPI(
|
|||||||
# Zyklus und Strafliste einmal beim Start laden (wie in libs/platz.py):
|
# Zyklus und Strafliste einmal beim Start laden (wie in libs/platz.py):
|
||||||
# zyklus.cfg und strafen.cfg direkt aus dem cfg-Verzeichnis (PLATZ_CFG bzw.
|
# zyklus.cfg und strafen.cfg direkt aus dem cfg-Verzeichnis (PLATZ_CFG bzw.
|
||||||
# Projekt-cfg). GA-Profil per Umgebungsvariable PLATZ_ZYKLUSART (Vorgabe:
|
# Projekt-cfg). GA-Profil per Umgebungsvariable PLATZ_ZYKLUSART (Vorgabe:
|
||||||
# Adaptiv).
|
# Adaptiv), Strafpunkte-Profil per PLATZ_STRAFENART (Vorgabe: default) —
|
||||||
|
# analog zum Schalter --strafen in libs/platz.py.
|
||||||
ZYKLUSDATEI = os.path.join( CFGDIR, 'zyklus.cfg' )
|
ZYKLUSDATEI = os.path.join( CFGDIR, 'zyklus.cfg' )
|
||||||
STRAFENDATEI = os.path.join( CFGDIR, 'strafen.cfg' )
|
STRAFENDATEI = os.path.join( CFGDIR, 'strafen.cfg' )
|
||||||
ZYKLUSART = os.environ.get( 'PLATZ_ZYKLUSART', 'Adaptiv' )
|
ZYKLUSART = os.environ.get( 'PLATZ_ZYKLUSART', 'Adaptiv' )
|
||||||
|
STRAFENART = os.environ.get( 'PLATZ_STRAFENART', 'default' )
|
||||||
|
|
||||||
GAZyklus = Zyklus()
|
GAZyklus = Zyklus()
|
||||||
GAZyklus.laden( ZYKLUSDATEI, ZYKLUSART )
|
GAZyklus.laden( ZYKLUSDATEI, ZYKLUSART )
|
||||||
Strafen = Strafliste()
|
Strafen = Strafliste()
|
||||||
Strafen.laden( STRAFENDATEI )
|
Strafen.laden( STRAFENDATEI, STRAFENART )
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Sessions
|
# ---------------------------------------------------------------- Sessions
|
||||||
SESSION_TTL_SEKUNDEN = 24 * 3600
|
SESSION_TTL_SEKUNDEN = 24 * 3600
|
||||||
|
|||||||
Reference in New Issue
Block a user