Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Stangl bd87b3df46 Add doc/dateiformate.md file-format reference
Documents tische.ini, bestellung.xml, and the output file formats
using real examples from work/test1 and work/test2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 11:09:32 +02:00
Michael Stangl fd223e8806 Add CLAUDE.md and README.md documentation
Documents the GA-based seating solver's architecture, config file
chain, and setup/usage for both AI assistants and human contributors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 11:07:32 +02:00
3 changed files with 408 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
# 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 2 code** (print statements, `ConfigParser`, `sets.Set`, `UserList`,
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`,
`dict.iteritems`). It will not run under Python 3 without a 2to3-style port. There is no
`requirements.txt`, build system, test suite, or linter configured in this repo — a Python 2
interpreter with only the standard library is sufficient to run it.
## Running the program
Entry point is `libs/platz.py`, invoked via the wrapper scripts in `bin/`, which set up
environment variables and `PYTHONPATH` before calling it — the scripts never take a script
argument, they just launch `platz.py`:
- `bin/run` (bash, for Linux/macOS) — hardcodes `PLATZ=/Users/sm/develop/python/platz`
- `bin/run.bat` (Windows) — hardcodes `PLATZ=h:\develop\python\platz` and a Python 2.4 interpreter path
Both scripts set `PLATZ_CFG`, `PLATZ_LIBS`, `PLATZ_WORK` and add `PLATZ_LIBS` to
`PYTHONPATH`, then run `python $PLATZ_LIBS/platz.py`. **The hardcoded paths at the top of
these scripts must be edited to match wherever this repo is actually checked out** before
they will work.
Configuration is driven by `cfg/platz.cfg`, which points (via `$PLATZ_CFG`/`$PLATZ_WORK`
env-var expansion) to:
- `cfg/zyklus.cfg` — defines the GA run schedule ("Zyklus"): a 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.
- `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, and per-table
under-utilization penalties.
- A per-run working directory under `work/<name>/` (e.g. `work/test1/`, `work/test2/`)
containing:
- `tische.ini` — table definitions: id, `Nummer`, `Hof` (venue/court), `Plaetze` (seats),
`Nachbarliste` (neighbor table ids), `Koordinaten`.
- `bestellung.xml` — the guest order: `<Gruppe>` blocks containing `<Person>` (with
`Vorname`/`Nachname`/optional `Titel`) and an `<Anzahl>` (reservation count per person
template — this expands into that many identical bookings), plus optional `<Tisch>` to
pin a VIP group to a specific table.
- Output: `TischePersonen.txt` (table → seated people) and `PersonenTische.csv`
(person → table, sorted).
## 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 `__cmp__` 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.
- `XMLConfig` — parses `bestellung.xml` into `Personen`/`Gruppen`/VIP-group/VIP-seat-map,
expanding each `<Person>` template by its sibling `<Anzahl>` into that many individual
bookings, and treating any `<Gruppe>` containing a `<Tisch>` tag as a VIP group pinned to
that table rather than going through normal GA placement.
### Data flow through `platz.py` (`__main__`)
1. Read `platz.cfg` to locate the other config/data files (env-var expansion via
`os.path.expandvars`).
2. Load `Zyklus` (GA schedule), `Strafliste` (penalties), `Tische` (table layout).
3. Load `bestellung.xml` via `XMLConfig` → people, groups, VIP group, VIP seat map.
4. Build a `Farm(Zyklus, Sitzplatzverteilung, Tische=..., Gruppen=..., Strafen=...,
VIPListe=..., VIPs=...)`, which runs the whole GA cycle in its constructor.
5. Take `Farm.Bester()` and call `.speichern(...)` to write the output files.
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.
+100
View File
@@ -0,0 +1,100 @@
# platz
A genetic-algorithm-based solver that assigns people to tables ("Tische") for an event,
given group memberships, table sizes/neighbor relationships, and a configurable penalty
scheme. It reads a guest list (XML) and a table layout (INI), searches for a good seating
using a small custom GA framework, and writes the result to text/CSV output files.
**This is Python 2 code** (print statements, `ConfigParser`, `sets.Set`, `UserList`,
`string.split`, old-style `raise Exception, "msg"`, `xrange`, `dict.has_key`,
`dict.iteritems`). It needs a Python 2 interpreter; there are no third-party dependencies
(standard library only).
## Project structure
```
platz/
├── bin/ # launcher scripts (bash + Windows batch)
├── cfg/ # GA cycle definition and penalty scheme
├── libs/ # the two Python modules (ga.py, Strukturdaten.py, platz.py)
└── work/ # per-run input (table layout, guest list) and output
├── test1/
└── test2/
```
- `libs/platz.py` — entry point; wires together config, input data, and the GA run.
- `libs/ga.py` — generic genetic-algorithm scaffolding (`Loesung`, `Zyklus`, `Farm`).
- `libs/Strukturdaten.py` — domain model for the seating problem (`Person`, `Gruppe`,
`Tisch`, `Strafliste`, `Sitzplatzverteilung`, `XMLConfig`).
## Setup
1. Install a Python 2 interpreter (2.4+; no `pip install` needed, standard library only).
2. Edit the hardcoded paths at the top of the launcher script for your platform:
- `bin/run` (bash): `PLATZ=/Users/sm/develop/python/platz`
- `bin/run.bat` (Windows): `PLATZ=h:\develop\python\platz` and `PYTHONSHELL=l:\tools\i486_nt\python24`
Point `PLATZ` at wherever you checked out this repository.
## Running
From a shell:
```bash
# Linux/macOS
source bin/run
```
```bat
:: Windows
bin\run.bat
```
The launcher sets `PLATZ_CFG`, `PLATZ_LIBS`, `PLATZ_WORK`, adds `PLATZ_LIBS` to
`PYTHONPATH`, and runs `libs/platz.py`.
### Configuring a run
`cfg/platz.cfg` selects which cycle/penalty config and which working directory
(`work/<name>/`) to use for a given run:
```ini
[Config]
Zyklusdatei=$PLATZ_CFG/zyklus.cfg
Zyklusart=Easy
Strafendatei=$PLATZ_CFG/strafen.cfg
Tischedatei=$PLATZ_WORK/test1/tische.ini
XMLdatei_Bestellung=$PLATZ_WORK/test1/bestellung.xml
AusgabeTischePersonen=$PLATZ_WORK/test1/TischePersonen.txt
AusgabePersonTisch=$PLATZ_WORK/test1/PersonenTische.csv
```
To run against a different dataset, point the `Tischedatei`/`XMLdatei_Bestellung`/output
entries at another `work/<name>/` folder, or add a new one following the same layout:
- `tische.ini` — one section per table: `Nummer`, `Hof` (venue/court), `Plaetze` (seat
count), `Nachbarliste` (neighboring table ids), `Koordinaten`.
- `bestellung.xml``<Gruppe>` blocks, each with a `<Person>` template
(`Vorname`/`Nachname`/optional `Titel`) and an `<Anzahl>` (how many bookings to expand
that template into). A `<Gruppe>` containing a `<Tisch>` tag is treated as a VIP group
pinned to that table instead of being placed by the GA.
`cfg/zyklus.cfg` defines the GA run schedule ("Zyklus") as an `Abfolge` (sequence of
actions) and matching `Anzahl` (counts): `e` create, `s` select best, `S` select randomly,
`m` mutate, `j` keep parents, `z` advance generation.
`cfg/strafen.cfg` defines the penalty scheme: cost of splitting a group across tables (by
group size and number of splits), cost of a person sitting alone or without a group-mate at
a neighboring table, and per-table seat-utilization penalties.
### Output
A run writes two files into the configured working directory:
- `TischePersonen.txt` — table → seated people, with venue/table/seat numbers.
- `PersonenTische.csv` — person → table, sorted alphabetically by name.
## Further details
See `CLAUDE.md` for a deeper architectural walkthrough of the GA framework and domain
model, intended for AI coding assistants but equally useful for human contributors.
+205
View File
@@ -0,0 +1,205 @@
# Dateiformate
Referenz fuer die Eingabe- und Ausgabedateien eines `platz`-Laufs, mit Beispielen aus
`work/test1/` und `work/test2/`.
## Tischaufstellung: `tische.ini`
Ein INI-File mit einer Sektion pro Tisch. Sektionsname ist die Tisch-Id.
| Schluessel | Bedeutung |
|-----------------|---------------------------------------------------------|
| `Nummer` | Anzeigenummer des Tisches |
| `Hof` | Hof/Bereich, in dem der Tisch steht |
| `Plaetze` | Anzahl der Sitzplaetze am Tisch |
| `Nachbarliste` | Python-Liste der Ids benachbarter Tische |
| `Koordinaten` | `(x,y)`-Tupel fuer die Position des Tisches |
Beispiel aus `work/test2/tische.ini` (eine Reihe von fuenf Tischen, jeder mit seinen
direkten Nachbarn verkettet):
```ini
[1]
Nummer=1
Hof=1
Plaetze=3
Nachbarliste=[2]
Koordinaten=(1,1)
[2]
Nummer=2
Hof=1
Plaetze=5
Nachbarliste=[1,3]
Koordinaten=(2,1)
[3]
Nummer=3
Hof=1
Plaetze=3
Nachbarliste=[2,4]
Koordinaten=(3,1)
[4]
Nummer=4
Hof=1
Plaetze=4
Nachbarliste=[3,5]
Koordinaten=(4,1)
[5]
Nummer=5
Hof=1
Plaetze=4
Nachbarliste=[4]
Koordinaten=(5,1)
```
Werte wie `Nachbarliste` und `Koordinaten` werden mit `eval()` geladen (siehe
`Tische.laden()` in `libs/Strukturdaten.py`) — es steht also echter Python-Ausdruckscode im
File, keine reine INI-Syntax.
## Gaesteliste: `bestellung.xml`
Ein `<Bestellung>` mit mehreren `<Gruppe>`-Bloecken. Jede Gruppe enthaelt:
- ein oder mehrere `<Person>`-Elemente mit `<Vorname>`, `<Nachname>` und optionalem
`<Titel>`
- optional `<Anzahl>N</Anzahl>`, um die zuletzt beschriebene Person zu einer Sammelbuchung
von `N` (anonymen/gleichnamigen) Personen zu vervielfaeltigen
- optional `<Tisch>Id</Tisch>`, um die Gruppe als VIP-Gruppe fest an den angegebenen Tisch
zu binden (sie wird dann **nicht** vom GA platziert)
XML-Kommentare (`<!-- ... -->`) sind erlaubt und werden beim Parsen ignoriert.
Minimalbeispiel mit Sammelbuchungen, aus `work/test1/bestellung.xml`:
```xml
<Bestellung>
<Gruppe>
<Person>
<Vorname>A</Vorname>
<Nachname>AA</Nachname>
<Titel>Dipl. Ing.</Titel>
</Person>
<Anzahl>10</Anzahl>
</Gruppe>
<Gruppe>
<Person>
<Vorname>B</Vorname>
<Nachname>BB</Nachname>
<Titel>Prof.</Titel>
</Person>
<Anzahl>4</Anzahl>
</Gruppe>
<Gruppe>
<Person>
<Vorname>C</Vorname>
<Nachname>CC</Nachname>
<Titel>Dr.</Titel>
</Person>
<Anzahl>5</Anzahl>
</Gruppe>
</Bestellung>
```
Umfangreicheres Beispiel mit benannten Personen und einer VIP-Gruppe, aus
`work/test2/bestellung.xml`:
```xml
<Bestellung>
<!-- einfache Gruppe aus drei Personen
mit festem Wunsch fuer einen Tisch-->
<Gruppe>
<Person>
<Vorname>Michael</Vorname>
<Nachname>Stangl</Nachname>
<Titel>Dipl. Ing.</Titel>
</Person>
<Person>
<Vorname>Magnus</Vorname>
<Nachname>Mueller</Nachname>
<Titel>Professor</Titel>
</Person>
<Person>
<Vorname>Bernd Robert</Vorname>
<Nachname>Hoehn</Nachname>
<Titel>Professor</Titel>
</Person>
<Tisch>1</Tisch>
</Gruppe>
<!-- einfache Gruppe aus zwei Personen -->
<Gruppe>
<Person>
<Vorname>Heinz</Vorname>
<Nachname>Bruegge</Nachname>
</Person>
<Person>
<Vorname>Hubert</Vorname>
<Nachname>K</Nachname>
</Person>
</Gruppe>
<!--Gruppe aus drei anonymen Personen-->
<Gruppe>
<Person>
<Vorname>Bestelli</Vorname>
<Nachname>Bloedkopf</Nachname>
</Person>
<Anzahl>3</Anzahl>
</Gruppe>
</Bestellung>
```
Die erste Gruppe hier ist per `<Tisch>1</Tisch>` fest an Tisch 1 gebunden (VIP-Gruppe); die
letzte Gruppe erzeugt ueber `<Anzahl>3</Anzahl>` drei gleichnamige "Bestelli Bloedkopf"
Buchungen.
## Ausgabe: `TischePersonen.txt`
Eine Sektion pro Tisch (`[Tischid]`), darunter eine Zeile pro Sitzplatz:
```
[1]
Dipl. Ing. A AA = Hof 1, Tisch 1, Nummer 1
Dipl. Ing. A AA = Hof 1, Tisch 1, Nummer 2
Dipl. Ing. A AA = Hof 1, Tisch 1, Nummer 3
Dipl. Ing. A AA = Hof 1, Tisch 1, Nummer 4
Dipl. Ing. A AA = Hof 1, Tisch 1, Nummer 5
[2]
Dipl. Ing. A AA = Hof 1, Tisch 2, Nummer 1
...
```
Format je Zeile: `Titel Vorname Nachname = Hof <Hof>, Tisch <Nummer>, Nummer <Platznummer>`.
## Ausgabe: `PersonenTische.csv`
Dieselben Sitzplatzdaten, aber eine Zeile pro Person statt pro Tisch, alphabetisch nach
Nachname sortiert:
```
AA, A, Dipl. Ing. = Hof 1, Tisch 1, Nummer 2
AA, A, Dipl. Ing. = Hof 1, Tisch 1, Nummer 3
...
BB, B, Prof. = Hof 1, Tisch 3, Nummer 2
...
CC, C, Dr. = Hof 1, Tisch 4, Nummer 2
...
```
Format je Zeile: `Nachname, Vorname, Titel = Hof <Hof>, Tisch <Nummer>, Nummer <Platznummer>`.
## Zyklus- und Strafenkonfiguration
Siehe `cfg/zyklus.cfg` und `cfg/strafen.cfg` sowie die Beschreibung in `CLAUDE.md` /
`README.md` fuer das Format der GA-Ablaufsteuerung (`Abfolge`/`Anzahl`) und der
Strafpunkte-Tabelle.