diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e1628a3 --- /dev/null +++ b/CLAUDE.md @@ -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//` (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: `` blocks containing `` (with + `Vorname`/`Nachname`/optional `Titel`) and an `` (reservation count per person + template — this expands into that many identical bookings), plus optional `` 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 `` template by its sibling `` into that many individual + bookings, and treating any `` containing a `` 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8c8355 --- /dev/null +++ b/README.md @@ -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//`) 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//` 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` — `` blocks, each with a `` template + (`Vorname`/`Nachname`/optional `Titel`) and an `` (how many bookings to expand + that template into). A `` containing a `` 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.