TRO-Fluss: Reihenfolge auf der Kreisel-Bahn auch fuer Weichen-TROs

Seit der Weichenerkennung (c788647/fa96a6e) bekommt ein Weichen-TRO als Host
die gefuetterte Strecke statt die Kreisel-Bahn (derive_tros Schritt 3b) - fachlich
richtig fuer den Materialfluss, aber die Positions-Verkettung in link_tros()
gruppierte bisher rein nach Host: sobald nur noch ein einzelner "echter"
Separator auf einer Bahn uebrig blieb (HundM05 Kreisel1 Bahn L: 0020, nachdem
0008/0010/0012/0014 eigene Weichen-TROs wurden), verschwand die Reihenfolge
0008->0010->0012->0014->0020 komplett - im TRO-Flussdiagramm sah es aus, als
haenge jede der 5 Abzweigungen direkt und gleichrangig an genau diesem einen
TRO (Review 2026-09-21: "es gibt viele unnoetige Linien").

_tro_bahn() bestimmt die Kreisel-Bahn eines TROs jetzt zusaetzlich ueber die
Zuordnung seines eigenen Separators, wenn der TRO-Host selbst keine Bahn ist -
link_tros() verkettet danach alle TROs derselben Bahn per Position, nicht nur
die mit identischem Host. Die bestehenden Materialfluss-Kanten (Weiche/Vario
-> Ruecklauf-Bahn) bleiben unveraendert bestehen; die Verkettung ergaenzt nur
die bislang fehlende Reihenfolge der Abzweige.

Die Colocated-Markierung ("gleiche Bahn", gepunktete Linie) wird jetzt pro
Paar statt pro TRO unterdrueckt (verkettete_paare statt verkettet als TRO-Id-
Menge) - sonst waere z. B. TRO01 (Weiche, auf der Bahn verkettet) faelschlich
auch von seiner Colocated-Markierung mit TRO02 (Vario, gleiche Strecke)
ausgenommen worden, obwohl das zwei unabhaengige Beziehungen sind.

HundM05.csv: 24 -> 28 Kanten (4 neue Bahn-Kanten 0008->0010->0012->0014->0020).
Mubea unveraendert (13 TROs, 55 Kanten), keine Fehler in beiden Projekten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-21 13:01:59 +02:00
co-authored by Claude Sonnet 5
parent fa96a6e666
commit b197bf8150
+57 -16
View File
@@ -714,8 +714,32 @@ def _bahn_richtung(node) -> tuple[float, float] | None:
return achse if mit_achse else (-achse[0], -achse[1])
def _tro_bahn(tro: Tro, graph: Graph, sep_assignment: dict[str, str]) -> str | None:
"""Kreisel-Bahn, auf der ein TRO physisch sitzt - fuer die Positions-
Verkettung in link_tros(). Meist einfach der TRO-Host (wenn er direkt auf
der Bahn liegt, z. B. ein gewoehnlicher Kreisel-Separator). Bei einem
Weichen-TRO zeigt der Host dagegen auf die gefuetterte Strecke, nicht auf
die Bahn (siehe derive_tros Schritt 3b) - dort zaehlt stattdessen die
Zuordnung des eigenen Separators, falls die auf eine Kreisel-Bahn zeigt.
Zeigt sie stattdessen auf eine Strecke (z. B. HundM05 Separator 0016/0018,
die auf ihrer eigenen Strecke montiert sind, nicht auf der Kreisel-Bahn),
gibt es keine Bahn-Zugehoerigkeit - der TRO gehoert dann nicht in die
Verkettung.
"""
for host in tro.hosts:
node = graph.nodes.get(host)
if node and node.kind == "Kreisel":
return host
for sep_id in tro.separators:
bahn = sep_assignment.get(sep_id)
node = graph.nodes.get(bahn or "")
if node and node.kind == "Kreisel":
return bahn
return None
def link_tros(
graph: Graph, tros: list[Tro]
graph: Graph, tros: list[Tro], elements: list[Element]
) -> tuple[list[tuple[str, str]], dict[tuple[str, str], str]]:
"""
TROs verknuepfen.
@@ -762,17 +786,32 @@ def link_tros(
# nebeneinander: das Material laeuft die Bahn in Foerderrichtung ab. Sie
# werden daher der Reihe nach verkettet statt nur als "gleiche Bahn"
# markiert - sonst fehlt z. B. die Kante von der einen Sperre zur naechsten.
verkettet: set[str] = set()
for host, ids in owners.items():
if len(ids) < 2:
# Gruppiert wird ueber _tro_bahn() (Separator-Zuordnung), NICHT ueber
# owners/host: ein Weichen-TRO liegt zwar physisch auf der Bahn, sein Host
# wurde in derive_tros Schritt 3b aber auf die gefuetterte Strecke gelegt
# (review 2026-09-21 - sonst fehlt bei HundM05 die Reihenfolge 0008->0010->
# 0012->0014->0020 auf Kreisel1 Bahn L komplett, sobald deren Weichen einen
# eigenen Host haben und nur noch ein einzelner Separator auf der Bahn
# selbst uebrig bleibt).
sep_assignment = {e.teile_id: e.assignment for e in elements
if e.kind == "Separator" and e.assignment}
bahn_gruppen: dict[str, list[Tro]] = {}
for tro in tros:
bahn = _tro_bahn(tro, graph, sep_assignment)
if bahn:
bahn_gruppen.setdefault(bahn, []).append(tro)
verkettete_paare: set[frozenset[str]] = set()
for bahn, gruppe in bahn_gruppen.items():
if len(gruppe) < 2:
continue
node = graph.nodes.get(host)
if node is None or node.kind != "Kreisel" or not node.lane:
node = graph.nodes[bahn]
if not node.lane:
continue
richtung = _bahn_richtung(node)
if richtung is None:
continue
auf_bahn = [t for t in tros if t.tro_id in ids and t.position]
auf_bahn = [t for t in gruppe if t.position]
if len(auf_bahn) < 2:
continue
# Projektion auf die Foerderrichtung: kleinster Wert kommt zuerst
@@ -782,18 +821,20 @@ def link_tros(
pair = (vorher.tro_id, nachher.tro_id)
edges.add(pair)
edge_kinds.setdefault(pair, "bahn")
verkettet.update(pair)
verkettete_paare.add(frozenset(pair))
# gleicher Host = physisch dieselbe Strecke/Bahn. Verkettete Bahnen sind
# oben schon gerichtet verbunden und brauchen die ungerichtete Notiz nicht.
# gleicher Host = physisch dieselbe Strecke/Bahn. Ein bereits verkettetes
# PAAR (nicht: irgendein verketteter TRO - ein Weichen-TRO kann zugleich
# auf der Bahn verkettet UND mit seinem Vario-TRO auf derselben Strecke
# colocated sein, das sind zwei verschiedene Beziehungen) braucht die
# ungerichtete Notiz nicht zusaetzlich.
for host, ids in owners.items():
for tro_id in ids:
tro = next(t for t in tros if t.tro_id == tro_id)
if tro_id in verkettet:
continue
tro.colocated = sorted(
set(tro.colocated) | {o for o in ids if o != tro_id and o not in verkettet}
)
others = {o for o in ids if o != tro_id
and frozenset((tro_id, o)) not in verkettete_paare}
if others:
tro.colocated = sorted(set(tro.colocated) | others)
for tro in tros:
tro.successors = sorted(dst for src, dst in edges if src == tro.tro_id)
@@ -878,7 +919,7 @@ def analyse_tros(
# Verorten vor dem Verknuepfen: die Reihenfolge mehrerer TROs auf einer
# Kreisel-Bahn wird ueber ihre Koordinate bestimmt.
locate_tros(linked, elements, graph)
analysis.edges, analysis.edge_kinds = link_tros(graph, linked)
analysis.edges, analysis.edge_kinds = link_tros(graph, linked, elements)
# Vor Ort aufgenommene Topologie schlaegt die abgeleitete. Die Differenz
# wird protokolliert, damit sichtbar bleibt, was die Ableitung falsch hatte.