Files
platz/CLAUDE.md
T
Michael Stangl 644f61ef6b Adaptiver GA-Zyklus, Pool-Groessen-Panel, Konfig direkt aus cfg-Dateien
Zyklus/Farm:
- neuer adaptiver Modus: grosse Startpopulation, die je Generation
  schrumpft (Start/Schrumpfung/MinPopulation) und bei Konvergenz stoppt
  (Geduld Generationen ohne Verbesserung >= Schwelle, max MaxGenerationen)
- Zyklus.laden erkennt adaptives Profil am Feld 'Start' (Modus adaptiv),
  sonst klassisch (Abfolge/Anzahl); Farm._LaufStatisch/_LaufAdaptiv
- neue Trunkierungs-Selektion Farm.PopulationBegrenzen
- [Adaptiv]-Profil in zyklus.cfg (dokumentiert)

Entwicklungsgrafik:
- zweites Panel unter dem Verlauf zeigt die Anzahl erzeugter
  Verteilungen im Pool je Schritt (gemeinsame Zeitachse)

Konfiguration:
- platz.cfg entfernt; zyklus.cfg/strafen.cfg werden direkt aus
  $PLATZ_CFG geladen
- neuer Schalter --zyklus-art (Default Adaptiv) waehlt das GA-Profil
- web/main.py analog: direktes Laden, Profil via PLATZ_ZYKLUSART

Doku (README.md, CLAUDE.md) und Tests entsprechend aktualisiert;
Beispiele mit dem Adaptiv-Default neu berechnet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:31:59 +02:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project overview

platz (German: "Sitzplatzverteilung" / seating arrangement) is a genetic-algorithm-based solver that assigns people to tables ("Tische") for an event, given group memberships, table sizes/neighbor relationships, and a penalty scheme. It reads an XML order/guest list and INI table layout, runs a small custom GA framework to search for a good seating, and writes the result to text/CSV output files.

This is Python 3 code, migrated from an original Python 2 implementation (the git history / old comments may still say "Python 2" — that's now stale). Notable migration artifacts to be aware of when touching this code:

  • Gruppe, Loesung, and Sitzplatzverteilung use @functools.total_ordering with __eq__/__lt__ instead of the old Python 2 __cmp__ (Python 3 dropped __cmp__ and cmp() entirely; max()/.sort() need rich comparisons).
  • sets.Set → builtin set; ConfigParserconfigparser; string.splitstr.split; xrangerange; dict.has_key(k)k in dict; dict.iteritems()dict.items(); old-style raise Exception, "msg"raise Exception("msg").
  • Strukturdaten.LeuteAllein() was fixed to skip people with no entry in self.Gruppen.GvonPid (e.g. VIP-group members who were never added to the regular Gruppen container) instead of letting GruppeVonPid raise KeyError — this was a latent bug that only surfaced once VIP data (work/test2) was actually exercised end-to-end.
  • HandleBestellung() had a G.Anzahl comparison (G.Anzahl > 0) that used to silently compare a bound method to an int under Python 2's permissive cross-type ordering; Python 3 raises TypeError for that, which is what surfaced it. Fixed to call G.Anzahl().

There is no build system or linter configured in this repo. A tests/ unittest suite exists (see below); requirements.txt is still empty/commented — everything used is standard library — it's there only so bin/install_py.* has something to install into the .venv.

Running the program

Entry point is libs/platz.py, invoked via the scripts in bin/ (each provided as a .bat/.sh pair, following the "Standard Programm Template" convention — see ~/.claude/CLAUDE.md for the template rules):

  • bin/setenv.bat / bin/setenv.sh — derives PLATZ from the script's own location (no path editing needed), sets PLATZ_BIN, PLATZ_CFG, PLATZ_LIBS, PLATZ_WORK, PLATZ_IN, PLATZ_OUT, adds PLATZ_LIBS to PYTHONPATH (idempotently), and creates missing folders. Sourced by every other script; not normally invoked directly. The .sh variant must be sourced (source bin/setenv.sh), not executed.
  • bin/install_py.bat / bin/install_py.sh — calls setenv, then creates .venv (via py -m venv / python3 -m venv) and pip install -r requirements.txt if .venv doesn't already exist.
  • bin/activate_venv.bat / bin/activate_venv.sh — calls setenv, then activates the existing .venv (errors out if install_py hasn't been run yet).
  • bin/get_cmd.bat / bin/get_cmd.sh — calls setenv, then opens a new shell with the PLATZ_* environment already set.
  • bin/platz.bat / bin/platz.sh — calls setenv, then runs python "$PLATZ_LIBS/platz.py". This is the actual program entry point (replaces the old bin/run / bin/run.bat, which hardcoded absolute paths and have been removed).

libs/platz.py requires a command-line switch --indir <dir> (parsed with optparse) pointing at a directory that holds the per-run input files and receives the output files — e.g. work/test1/, work/test2/, or any new folder following the same layout:

  • tische.ini — table definitions: id, Nummer, Hof (venue/court), Plaetze (seats), Nachbarliste (neighbor table ids), Koordinaten.
  • bestellung.json — the guest order: { "gruppen": [ ... ] }, each group holding a "personen" list (each with vorname/nachname/optional titel), an optional "anzahl" (reservation count — expands every person in the group into that many identical bookings), an optional "name" (display label), and an optional "tisch" to pin a VIP group to a specific table. Replaces the old bestellung.xml/XMLConfig.
  • Output (written back into the same --indir directory): TischePersonen.txt (table → seated people) and PersonenTische.csv (person → table, sorted).

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 more — it was removed; zyklus.cfg/strafen.cfg are loaded straight from $PLATZ_CFG, and the GA profile is chosen by the --zyklus-art <name> switch (default Adaptiv).

  • cfg/zyklus.cfg — defines the GA profiles. Two flavours: static profiles (Easy, Simple) give an explicit sequence of actions (e=erzeugen/create, s=selektieren/ select-best, S=random-select, m=mutieren/mutate, j=behalten/keep parents, z=neue Generation/advance generation) each with a count (Abfolge/Anzahl). The adaptive profile (Adaptiv, the default) is recognised by a Start field and has no fixed action list: Zyklus.Modus becomes 'adaptiv' and Farm._LaufAdaptiv() runs a generation loop with a shrinking population (Start, Schrumpfung, MinPopulation) and a convergence stop (Geduld generations without an improvement of at least Schwelle, capped by MaxGenerationen). Static profiles run via Farm._LaufStatisch().
  • cfg/strafen.cfg — the penalty ("Strafpunkte") table: cost of splitting a group across tables (by group size and number of splits), cost of a lone person, per-table under-utilization penalties, and (optional [Abstand] section) a penalty for the spatial distance between split-off parts of a group.

tische.ini/bestellung.json/TischePersonen.txt/PersonenTische.csv are fixed filenames resolved as os.path.join(indir, ...) in platz.py.

Pass --tosvg to additionally write <indir>/Sitzplatzverteilung.svg via Sitzplatzverteilung.alsSVG() (see below).

Run the test suite with bin/run_tests.bat / bin/run_tests.sh (or directly: python -m unittest discover -s tests -p "test_*.py" -v). A stray global PYTHONPATH entry on this machine points at an unrelated project that also defines a tests package — this shadows import tests but does not affect unittest discover, which is what the run_tests scripts use.

Architecture

Two library modules under libs/, imported via PYTHONPATH=$PLATZ_LIBS:

ga.py — generic genetic-algorithm scaffolding

Domain-agnostic and reusable in principle:

  • Loesung ("Solution") — base class for anything bred/mutated/selected by the GA. Meant to be subclassed; Sitzplatzverteilung in Strukturdaten.py is the concrete solution type used here.
  • Zyklus ("Cycle") — parses Abfolge/Anzahl strings (from zyklus.cfg) into the ordered list of GA actions and their counts for one run.
  • Farm — owns the population (Pool/PoolNeu), and executes a Zyklus action-by-action against instances of a given solution class (passed as Klasse plus its constructor args/kwargs). Bester() returns the fittest (max()) solution found; solutions are ordered via __eq__/__lt__ (@total_ordering) on self.value.

Strukturdaten.py — domain model for this specific seating problem

  • Person, Gruppe (group — a Set of people, splittable via teilen()), Tisch (table — a Gruppe subclass with a seat capacity and neighbor-table ids).
  • Container/index classes: Personen, Gruppen, Tische, and Plaetze ("Seats") which indexes tables by how many free seats they currently have, to answer "give me a table with at least N free seats" or "give me two neighboring tables that together fit two groups of given sizes" efficiently during placement/mutation.
  • Strafliste ("Penalty list") — loads strafen.cfg and computes penalty points for: splitting a group (gibPunkte('Trennung', ...), recursing to smaller group sizes if no exact rule is configured), a person sitting with no group-mate at their table ('allein'), a person having no group-mate at a neighboring table ('nichtNachbar'), and poor seat utilization at a table ('Tischbesetzung').
  • Sitzplatzverteilung ("Seating arrangement") — the actual Loesung subclass bred by the GA. Constructor seats VIPs first at pinned tables, then randomly seats groups either largest-first or smallest-first (coin flip), splitting a group across two neighboring tables when no single table has enough free seats, recursively splitting further if needed, then calls bewerten() to score the arrangement per Strafliste. mutieren() picks the worst-scoring groups (SchlechteGruppenTopX), evicts them, and reseats them, hoping for a better score. speichern() writes the two output files. alsSVG() renders the layout to SVG: tables as white circles sized to fit all their seats' person-circles without touching, people as colored circles (color = group, via SVGGruppenFarben) placed evenly around the inside of their table's circle. The table-to-table grid spacing is derived from the largest table radius so tables never overlap regardless of their Koordinaten.
  • JSONConfig — parses bestellung.json into Personen/Gruppen/VIP-group/VIP-seat-map/group-names, expanding each person by the group's "anzahl" into that many individual bookings, and treating any group with a "tisch" key as a VIP group pinned to that table rather than going through normal GA placement. Returns a 5th element (GruppenNamen, {Gid: display name}) that the older XMLConfig did not; CSVConfig (the web CSV adapter) returns the same 5-tuple.

Data flow through platz.py (__main__)

  1. Parse --indir (required) and --zyklus-art (default Adaptiv) from the command line via optparse.
  2. Resolve zyklus.cfg/strafen.cfg directly as os.path.join($PLATZ_CFG, ...).
  3. Load Zyklus (GA profile named by --zyklus-art), Strafliste (penalties), Tische (table layout, from <indir>/tische.ini).
  4. Load <indir>/bestellung.json via JSONConfig → people, groups, VIP group, VIP seat map, group names.
  5. Build a Farm(Zyklus, Sitzplatzverteilung, Tische=..., Gruppen=..., Strafen=..., VIPListe=..., VIPs=...), which runs the whole GA cycle in its constructor.
  6. Take Farm.Bester() and call .speichern(...) to write <indir>/TischePersonen.txt and <indir>/PersonenTische.csv.

Both libs/platz.py and libs/Strukturdaten.py also have self-test code under if __name__ == '__main__': that exercises the classes directly with hardcoded sample data — useful as a reference for how the classes are meant to be constructed and used.