Zum Inhalt

journal

Das Journal führt eine Liste von Fdl-Änderungen an den graphbasierten Betriebsdaten. Diese Änderungen können z.B. nach einem neuen Import vom Simulator auf die jeweiligen Graphen aufgespielt werden, um den Betriebszustand wiederherzustellen.

Classes:

Journal

Journal()

Journal von Änderungen an einem Graph

Journal wird zum Erfassen von Fdl-Korrekturen an den Graphdaten verwendet. Es erlaubt, Änderungen an verschiedenen Graphen unter einer Korrektur-ID zusammenzufassen.

Anhand der Korrektur-ID können Journals wiedergefunden und gelöscht werden. Es werden dann jeweils alle zu einem Ereignis gehörenden Korrekturen gelöscht.

Methods:

Source code in stskit/model/journal.py
def __init__(self):
    self.entries: dict[Hashable, JournalEntry | JournalEntryGroup] = {}

add_entry

Journaleintrag hinzufügen

Parameters:

Source code in stskit/model/journal.py
def add_entry(self, id_: Hashable, entry: JournalEntry | JournalEntryGroup):
    """
    Journaleintrag hinzufügen

    Args:
        id_: Identifikation des Eintrags. Anhand der ID kann er später wieder gelöscht werden.
        entry: Eintrag oder Gruppe
    """
    self.entries[id_] = entry

clear

clear()

Alle Journaleinträge löschen

Source code in stskit/model/journal.py
def clear(self):
    """
    Alle Journaleinträge löschen
    """
    self.entries.clear()

delete_entry

delete_entry(id_: Hashable)

Journaleintrag löschen

Parameters:

  • id_

    (Hashable) –

    ID, die in add_entry verwendet wurde.

Raises:

Source code in stskit/model/journal.py
def delete_entry(self, id_: Hashable):
    """
    Journaleintrag löschen

    Args:
        id_: ID, die in add_entry verwendet wurde.

    Raises:
        KeyError: Eintrag existiert nicht.
    """
    del self.entries[id_]

replay

replay(graph_map: Mapping[Hashable, Graph] | None = None)

Journal abspielen

Die Zielgraphen werden anhand des target_graph-Attributs der JournalEntry bestimmt. target_graph kann entweder einen Identifikationsschlüssel enthalten, der mittels der graph_map zugeordnet wird, oder direkt eine Graphinstanz.

Parameters:

  • graph_map

    (Mapping[Hashable, Graph] | None, default: None ) –

    Ordnet den target_graph-Attributen der Einträge die Graphen zu, die verändert werden. Wird keine Zuordnung angegeben, müssen die Graphen direkt in den Einträgen angegeben sein.

Source code in stskit/model/journal.py
def replay(self, graph_map: Mapping[Hashable, nx.Graph] | None = None):
    """
    Journal abspielen

    Die Zielgraphen werden anhand des target_graph-Attributs der JournalEntry bestimmt.
    target_graph kann entweder einen Identifikationsschlüssel enthalten, der mittels der graph_map zugeordnet wird,
    oder direkt eine Graphinstanz.

    Args:
        graph_map: Ordnet den target_graph-Attributen der Einträge die Graphen zu, die verändert werden.
            Wird keine Zuordnung angegeben, müssen die Graphen direkt in den Einträgen angegeben sein.
    """
    for entry in self.entries.values():
        entry.replay(graph_map=graph_map)

JournalEntry

JournalEntry(target_graph: G | None = None, target_node: N | None = None)

Journal von Änderungen an einem Graphen.

Das Journal enthält Einträge von gelöschten, eingefügten und geänderten Knoten und Kanten. Die Einträge werden in Sets bzw. Dictionaries gehalten und sind nach Knoten- bzw. Kantenlabel aufgeschlüsselt.

Von gelöschten Knoten und Kanten werden nur die Labels gespeichert. Bei hinzugefügten und geänderten Knoten und Kanten werden die Knotendaten (Attribute) in Dictionaries mitgespeichert. Bei hinzugefügten sollten alle nötigen Attribute deklariert sein, bei geänderten nur die zu ändernden Attribute. Insbesondere sollten keine Defaultwerte übergeben werden!

Änderungen werden über die bereitgestellten Methoden gemeldet. Das Journal kann darauf über die Replay-Methode auf einen Graphen angewendet werden. Der vorherige Zustand wird nicht gespeichert und kann nicht wiederhergestellt werden.

Das Journal enthält zu jedem Knoten/jeder Kante maximal einen Lösch-, Einfügungs- und Änderungseintrag. Die Einträge Einfügen und Löschen wirken auf den gesamten Knoten bzw. Kante inklusive aller Attribute. Änderungen wirken auf einzelne Attribute. Die Änderungen werden gesammelt.

Der generische Typ G ist die Graphklasse, N der Knotenlabeltyp und D der Knotendatentyp, die in dem Eintrag verwendet werden können.

Methods:

Source code in stskit/model/journal.py
def __init__(self,
             target_graph: G | None = None,
             target_node: N | None = None):
    self.target_graph = target_graph
    self.target_node = target_node
    self.removed_nodes: set[N] = set()
    self.added_nodes: dict[N, D] = {}
    self.changed_nodes: dict[N, D] = {}
    self.removed_edges: set[tuple[N, N]] = set()
    self.added_edges: dict[tuple[N, N], D] = {}
    self.changed_edges: dict[tuple[N, N], D] = {}

add_edge

add_edge(u: N, v: N, **data)

Kante hinzufügen

Pro Kante enthält das Journal nur einen Eintrag. Ein allfällig vorhandener Eintrag wird überschrieben.

Source code in stskit/model/journal.py
def add_edge(self, u: N, v: N, **data):
    """
    Kante hinzufügen

    Pro Kante enthält das Journal nur einen Eintrag.
    Ein allfällig vorhandener Eintrag wird überschrieben.
    """

    self.added_edges[(u, v)] = data

add_node

add_node(n: N, **data)

Knoten hinzufügen

Pro Knoten enthält das Journal nur einen Eintrag. Ein allfällig vorhandener Eintrag wird überschrieben.

Source code in stskit/model/journal.py
def add_node(self, n: N, **data):
    """
    Knoten hinzufügen

    Pro Knoten enthält das Journal nur einen Eintrag.
    Ein allfällig vorhandener Eintrag wird überschrieben.
    """

    self.added_nodes[n] = data

change_edge

change_edge(u: N, v: N, **data)

Kantenanttribute ändern

Pro Kante enthält das Journal nur einen Eintrag. Die zu ändernden Attribute werden in einen allfällig existierenden Eintrag übernommen.

Vorsicht: Die Kantendaten sollten keine Attribute mit Defaultwerten enthalten!

Source code in stskit/model/journal.py
def change_edge(self, u: N, v: N, **data):
    """
    Kantenanttribute ändern

    Pro Kante enthält das Journal nur einen Eintrag.
    Die zu ändernden Attribute werden in einen allfällig existierenden Eintrag übernommen.

    Vorsicht: Die Kantendaten sollten keine Attribute mit Defaultwerten enthalten!
    """

    if (u, v) in self.changed_edges:
        self.changed_edges[(u, v)].update(data)
    else:
        self.changed_edges[(u, v)] = data

change_node

change_node(n: N, **data)

Knoten ändern

Pro Knoten enthält das Journal nur einen Eintrag. Die zu ändernden Attribute werden in einen allfällig existierenden Eintrag übernommen.

Vorsicht: Die Knotendaten sollten keine Attribute mit Defaultwerten enthalten!

Source code in stskit/model/journal.py
def change_node(self, n: N, **data):
    """
    Knoten ändern

    Pro Knoten enthält das Journal nur einen Eintrag.
    Die zu ändernden Attribute werden in einen allfällig existierenden Eintrag übernommen.

    Vorsicht: Die Knotendaten sollten keine Attribute mit Defaultwerten enthalten!
    """

    if n in self.changed_nodes:
        self.changed_nodes[n].update(data)
    else:
        self.changed_nodes[n] = data

clear

clear()

Journal löschen

Source code in stskit/model/journal.py
def clear(self):
    """
    Journal löschen
    """

    self.removed_nodes = set()
    self.added_nodes = {}
    self.changed_nodes = {}
    self.removed_edges = set()
    self.added_edges = {}
    self.changed_edges = {}

merge

merge(other: JournalEntry)

Mit anderem Journal zusammenführen

Hat den gleichen Effekt, wie wenn die add-, change-, remove-Methoden für jedes Element aufgerufen würden.

Source code in stskit/model/journal.py
def merge(self, other: JournalEntry):
    """
    Mit anderem Journal zusammenführen

    Hat den gleichen Effekt, wie wenn die add-, change-, remove-Methoden für jedes Element aufgerufen würden.
    """

    if self.target_graph != other.target_graph or self.target_node != other.target_node:
        raise ValueError(f"JournalEntry.merge: Inkompatibler Journaleintrag {other.target_graph}/{other.target_node} "
                         f"zu {self.target_graph}/{self.target_node}")

    self.removed_edges.update(other.removed_edges)
    self.removed_nodes.update(other.removed_nodes)
    self.added_edges.update(other.added_edges)
    self.removed_nodes.update(other.removed_nodes)
    for edge, data in other.changed_edges.items():
        self.change_edge(*edge, **data)
    for node, data in other.changed_nodes.items():
        self.change_node(node, **data)

nodes

nodes() -> set[tuple[G, N]]

Betroffene Knoten auflisten

Source code in stskit/model/journal.py
def nodes(self) -> set[tuple[G, N]]:
    """
    Betroffene Knoten auflisten
    """

    _nodes = {self.target_node}
    _nodes.update(self.removed_nodes)
    _nodes.update(self.added_nodes)
    _nodes.update(self.changed_nodes)
    _nodes.update((e[0] for e in self.removed_edges))
    _nodes.update((e[1] for e in self.removed_edges))
    _nodes.update((e[0] for e in self.added_edges))
    _nodes.update((e[1] for e in self.added_edges))
    _nodes.update((e[0] for e in self.changed_edges))
    _nodes.update((e[1] for e in self.changed_edges))
    _nodes.discard(None)
    _nodes = set(((self.target_graph, node) for node in _nodes))

    return _nodes

remove_edge

remove_edge(u: N, v: N)

Kante löschen

Source code in stskit/model/journal.py
def remove_edge(self, u: N, v: N):
    """
    Kante löschen
    """

    self.removed_edges.add((u, v))

remove_node

remove_node(n: N)

Knoten löschen

Source code in stskit/model/journal.py
def remove_node(self, n: N):
    """
    Knoten löschen
    """

    self.removed_nodes.add(n)

replay

replay(graph: Graph | None = None, graph_map: Mapping[G, Graph] | None = None) -> dict[str, set[N]]

Journal abspielen

Die Abspielreihenfolge ist:

  1. Kanten löschen. Der Knoten muss existieren, ansonsten bleibt die Änderung wirkungslos.
  2. Knoten löschen. Die Kante muss existieren, ansonsten bleibt die Änderung wirkungslos.
  3. Knoten hinzufügen. Wenn der Knoten existiert, werden die Attribute gelöscht und überschrieben.
  4. Kanten hinzufügen. Wenn die Kante existiert, werden die Attribute gelöscht und überschrieben.
  5. Knoten ändern. Der Knoten muss existieren, ansonsten bleibt die Änderung wirkungslos. Im Änderungseintrag erfasste Attribute werden überschrieben, die übrigen bleiben bestehen.
  6. Knoten ändern. Die Kante muss existieren, ansonsten bleibt die Änderung wirkungslos. Im Änderungseintrag erfasste Attribute werden überschrieben, die übrigen bleiben bestehen.

Returns:

  • dict[str, set[N]]

    Dictionary mit Fehlermeldungen.

  • dict[str, set[N]]

    Mögliche Keys: 'remove_edge', 'add_edge', 'change_edge', 'remove_node', 'add_node', 'change_node'.

  • Values ( dict[str, set[N]] ) –

    set von edge oder node Labels.

Source code in stskit/model/journal.py
def replay(self, graph: nx.Graph | None = None, graph_map: Mapping[G, nx.Graph] | None = None) -> dict[str, set[N]]:
    """
    Journal abspielen

    Die Abspielreihenfolge ist:

    1. Kanten löschen.
       Der Knoten muss existieren, ansonsten bleibt die Änderung wirkungslos.
    2. Knoten löschen.
       Die Kante muss existieren, ansonsten bleibt die Änderung wirkungslos.
    3. Knoten hinzufügen.
       Wenn der Knoten existiert, werden die Attribute gelöscht und überschrieben.
    4. Kanten hinzufügen.
       Wenn die Kante existiert, werden die Attribute gelöscht und überschrieben.
    5. Knoten ändern.
       Der Knoten muss existieren, ansonsten bleibt die Änderung wirkungslos.
       Im Änderungseintrag erfasste Attribute werden überschrieben, die übrigen bleiben bestehen.
    6. Knoten ändern.
       Die Kante muss existieren, ansonsten bleibt die Änderung wirkungslos.
       Im Änderungseintrag erfasste Attribute werden überschrieben, die übrigen bleiben bestehen.

    Returns:
        Dictionary mit Fehlermeldungen.
        Mögliche Keys: 'remove_edge', 'add_edge', 'change_edge', 'remove_node', 'add_node', 'change_node'.
        Values: set von edge oder node Labels.
    """

    fails = {}
    def _failed(operation, item):
        if operation in fails:
            fails[operation].add(item)
        else:
            fails[operation] = {item}

    if graph is None:
        graph = self.target_graph
    if not isinstance(graph, nx.Graph):
        graph = graph_map.get(graph)
    if graph is None:
        _failed('unresolved_target', self.target_graph)

    for edge in self.removed_edges:
        try:
            graph.remove_edge(*edge)
        except (AttributeError, nx.NetworkXError):
            _failed('remove_edge', edge)

    for label in self.removed_nodes:
        try:
            graph.remove_node(label)
        except (AttributeError, nx.NetworkXError):
            _failed('remove_node', label)

    for label, data in self.added_nodes.items():
        try:
            if graph.has_node(label):
                graph.nodes[label].clear()
            graph.add_node(label, **data)
        except (AttributeError, nx.NetworkXError):
            _failed('add_node', label)

    for edge, data in self.added_edges.items():
        try:
            if graph.has_edge(*edge):
                graph.edges[edge].clear()
            graph.add_edge(*edge, **data)
        except (AttributeError, nx.NetworkXError):
            _failed('add_edge', edge)

    for label, data in self.changed_nodes.items():
        try:
            if graph.has_node(label):
                graph.add_node(label, **data)
            else:
                _failed('change_node', label)
        except (AttributeError, nx.NetworkXError):
            _failed('change_node', label)

    for edge, data in self.changed_edges.items():
        try:
            if graph.has_edge(*edge):
                graph.add_edge(*edge, **data)
            else:
                _failed('change_edge', edge)
        except (AttributeError, nx.NetworkXError):
            _failed('change_edge', edge)

    return fails

summary

summary() -> dict[tuple[G, N], set[str]]

Zusammenfassung von Änderungen

Listet zu jedem betroffenen Knoten die gemachten Änderungen. Die Änderungen werden als String in einem Set wiedergegeben. '.' steht für den Targetknoten, '+' für einen neuen Knoten, '-' für einen gelöschten Knoten und '*' für einen geänderten Knoten.

Source code in stskit/model/journal.py
def summary(self) -> dict[tuple[G, N], set[str]]:
    """
    Zusammenfassung von Änderungen

    Listet zu jedem betroffenen Knoten die gemachten Änderungen.
    Die Änderungen werden als String in einem Set wiedergegeben.
    '.' steht für den Targetknoten, '+' für einen neuen Knoten, '-' für einen gelöschten Knoten
    und '*' für einen geänderten Knoten.
    """

    target = defaultdict(set)
    if self.target_node is not None:
        target[(self.target_graph, self.target_node)] = {'.'}

    removed = defaultdict(set)
    removed.update({(self.target_graph, n): {'-'} for n in self.removed_nodes})
    removed.update({(self.target_graph, e[0]): {'-'} for e in self.removed_edges})
    removed.update({(self.target_graph, e[1]): {'-'} for e in self.removed_edges})

    added = defaultdict(set)
    added.update({(self.target_graph, n): {'+'} for n in self.added_nodes})
    added.update({(self.target_graph, e[0]): {'+'} for e in self.added_edges})
    added.update({(self.target_graph, e[1]): {'+'} for e in self.added_edges})

    changed = defaultdict(set)
    changed.update({(self.target_graph, n): {'*'} for n in self.changed_nodes})
    changed.update({(self.target_graph, e[0]): {'*'} for e in self.changed_edges})
    changed.update({(self.target_graph, e[1]): {'*'} for e in self.changed_edges})

    nodes = target | removed | added | changed
    result = {n: target[n] | removed[n] | added[n] | changed[n] for n in nodes}

    return result

JournalEntryGroup

JournalEntryGroup(*entries)

Methods:

  • nodes

    Betroffene Knoten auflisten

  • summary

    Zusammenfassung von Änderungen

Source code in stskit/model/journal.py
def __init__(self, *entries):
    self.entries: list[JournalEntry] = list(entries)
    self.title: str = ""
    self.timestamp: int = 0
    self.valid: bool = False

nodes

nodes() -> set[tuple[Hashable, Hashable]]

Betroffene Knoten auflisten

Source code in stskit/model/journal.py
def nodes(self) -> set[tuple[Hashable, Hashable]]:
    """
    Betroffene Knoten auflisten
    """

    nodes = [entry.nodes() for entry in self.entries]
    return set().union(*nodes)

summary

summary() -> dict[tuple[Hashable, Hashable], set[str]]

Zusammenfassung von Änderungen

Listet zu jedem betroffenen Knoten die gemachten Änderungen. Die Änderungen werden als String in einem Set wiedergegeben. '.' steht für den Targetknoten, '+' für einen neuen Knoten, '-' für einen geloeschten Knoten und '*' für einen geänderten Knoten.

Source code in stskit/model/journal.py
def summary(self) -> dict[tuple[Hashable, Hashable], set[str]]:
    """
    Zusammenfassung von Änderungen

    Listet zu jedem betroffenen Knoten die gemachten Änderungen.
    Die Änderungen werden als String in einem Set wiedergegeben.
    '.' steht für den Targetknoten, '+' für einen neuen Knoten, '-' für einen geloeschten Knoten
    und '*' für einen geänderten Knoten.
    """

    summary: dict[tuple[Hashable, Hashable], set[str]] = defaultdict(set)
    for entry in self.entries:
        entry_summary = entry.summary()
        for n in entry_summary:
            summary[n].update(entry_summary[n])

    return summary

JournalIDType


              flowchart TD
              stskit.model.journal.JournalIDType[JournalIDType]

              

              click stskit.model.journal.JournalIDType href "" "stskit.model.journal.JournalIDType"
            

Identifikation des Journals

Ein Journal wird durch Typ, Zug, Bst identifiziert.