DOT-Referenz für Messgraphen

Eine DOT-Datei beschreibt aktive Geräte, passive Korrekturelemente und ihre Verbindungen. Sie ist kein Ablaufprogramm: Frequenzschleifen, Bedienerinteraktion und Auswertung bleiben Aufgabe der Messklasse. Die mpylab.tools.mgraph.MGraph-Instanz verbindet den Graphen mit Geräteerzeugung, Conditions, Schaltaktionen und Pfadkorrekturen.

Produktiver Parser und Eingaben

MGraph verwendet pydot als verbindlichen produktiven Parser. Es akzeptiert:

  • einen Dateinamen oder ein os.PathLike-Objekt;

  • ein lesbares dateiähnliches Objekt;

  • direkten DOT-Text;

  • strukturierte pydot-kompatible Graphdaten.

Relative Dateinamen werden in SearchPaths gesucht. Eine nicht gefundene Datei, ungültiger DOT-Text oder mehrere aneinandergehängte Graphen lösen mpylab.tools.mgraph.GraphInputError aus. Ein Messgraph enthält genau einen graph oder digraph.

from mpylab.tools.mgraph import MGraph

graph = MGraph(
    "measurement.dot",
    SearchPaths=["lab/config", "shared/config"],
)

Pickle und Rückverfolgbarkeit

Beim Pickeln wird der vollständige DOT-Text im MGraph gespeichert. Beim Wiederherstellen ist dieser eingebettete Text maßgeblich; die ursprüngliche Datei wird nicht erneut gelesen. Eine Messhistorie bleibt dadurch auch dann reproduzierbar, wenn die lokale DOT-Datei fehlt oder später geändert wurde. Der ursprüngliche Dateiname bleibt in fname_or_data als Herkunft erhalten.

Unterstützter DOT-Teilumfang

Der in mpylab verwendete Teilumfang umfasst insbesondere:

  • graph, digraph sowie die optionale strict-Kennzeichnung;

  • Knoten- und Kantenanweisungen;

  • eine oder mehrere Attributlisten in [...];

  • gerichtete -> und ungerichtete -- Verbindungen;

  • IDs, Zahlen und gequotete Strings;

  • #-, //- und /* ... */-Kommentare.

Für Messgraphen wird üblicherweise ein digraph verwendet.

Knoten und Kanten

digraph {
    Sg [ini="sg.ini"]
    Amp [ini="amplifier.ini"
         condition="FREQUENCY <= 1e9"]
    Cable [ini="cable.ini"]

    Sg -> Amp_Input
    Amp_Input -> Amp_Output [dev=Amp what="S21"]
    Amp_Output -> Pm [dev=Cable what="S21"]
}

Wichtige Attribute sind:

ini

Konfiguration einer Geräteinstanz. Der Dateiname wird über SearchPaths aufgelöst.

dev

Geräteknoten, dessen Kanal eine Kante beschreibt. Der Knoten muss im Graph ausdrücklich deklariert sein.

what

Name des Datenkanals, beispielsweise S21. Das Attribut darf fehlen, wenn eine Kante das Gerät bewusst ohne Datenkanal referenziert.

condition

Boolescher Ausdruck, der Knoten oder Kante aktiviert.

action

Sicher begrenzter Methodenaufruf, der beim Aktivieren eines Elements mit Condition ausgeführt werden kann.

context_controller

Kennzeichnet ein Gerät, das logische Context-Werte auf einen physischen Schaltzustand abbildet und diesen zurückliest. Der Wert benennt die für eine erneute Anwendung relevanten Context-Schlüssel, beispielsweise context_controller="mode".

Reine Verbindungs- oder Messpositionsknoten benötigen keine INI-Datei. Aufbau, Einheiten und DAT-Verweise der Gerätekanäle beschreibt die INI- und DAT-Referenz.

Gerätetypen und Treiber

DESCRIPTION/TYPE in der INI-Datei benennt die Einstiegsklasse, die das unter DESCRIPTION/DRIVER angegebene Modul exportiert. Kanonische Typen entsprechen den Gerätebasisklassen, beispielsweise SIGNALGENERATOR, POWERMETER, FIELDPROBE, RECEIVER, SPECTRUMANALYZER, NETWORKANALYZER, VLISN und TLISN.

Die Auflösung ignoriert Groß-/Kleinschreibung und historische umschließende Anführungszeichen, verwendet aber kein unscharfes String-Matching. Für TYPE = Custom müssen zusätzlich DRIVER und CLASS angegeben sein; das Modul wird aus SearchPaths geladen.

Fehlende INI- oder Treiberdateien, unbekannte Typen und fehlende Einstiegsklassen lösen mpylab.tools.mgraph.DeviceConfigurationError aus. CreateDevices erzeugt nur Python-Treiberinstanzen. Der Hardwarezugriff beginnt erst mit Init_Devices.

Aktive Geräte auswählen

Geräteoperationen wie Init_Devices, SetFreq_Devices, ConfReceivers, RFOff_Devices und RunReceiverScans verwenden einheitlich IgnoreInactive=True als Standard. Damit werden nur die durch die aktuellen Conditions aktiven Geräteknoten angesprochen. Mit IgnoreInactive=False werden bewusst auch inaktive, aber konfigurierte Geräte einbezogen. Dies ist beispielsweise für eine vollständige Diagnose oder ein ausdrücklich graphweites Aufräumen sinnvoll.

Namen-Mapping

Die Messklasse verwendet fachliche Rollen wie sg, pm_out oder fieldprobe. Das names-Dictionary aus conf.py wird als themap an MGraph übergeben:

names = {
    "sg": "Sg",
    "pm_out": "Pm",
    "fieldprobe": ["Probe1", "Probe2"],
}
graph = MGraph(dotfile, themap=names)

Die verbindliche Richtung lautet logischer Name -> DOT-Knoten. Leere Listen sind für optionale Rollen zulässig. Beim Laden historischer Pickles normalisiert MGraph eindeutig erkennbare umgekehrte oder bidirektionale Mappings.

Die semantische Prüfung lehnt unbekannte Mapping-Ziele und dev-Referenzen ab. Nach CreateDevices wird zusätzlich geprüft, ob für jedes referenzierte Gerät tatsächlich eine Treiberinstanz existiert.

Feldsondenorientierung

Feldsondentreiber liefern ihre drei Komponenten immer als [probe_x, probe_y, probe_z]. Die Abbildung in Zellkoordinaten gehört zur Messkonfiguration und wird bevorzugt am Feldsondenknoten im DOT-Graphen angegeben. Eine signierte Achsvertauschung lautet beispielsweise:

Probe [probe_axis_map="cell_x:-probe_y,cell_y:+probe_x,cell_z:+probe_z"]

Für allgemeine Verdrehungen kann eine eigentliche orthonormale Rotationsmatrix mit Determinante +1 angegeben werden. Sie bildet den Sondenvektor auf den Zellvektor ab:

Probe [probe_rotation_matrix="0,-1,0;1,0,0;0,0,1"]

Alternativ stehen die anschaulicheren festen Zellachsenwinkel probe_rotation_angles_deg zur Verfügung. Die Reihenfolge ist Drehung um Zell-z, danach Zell-x und zuletzt Zell-y. Orientierungseinträge können auch aus dem Konfigurations-Dictionary oder der Feldsonden-INI kommen. Werden widersprüchliche Angaben gefunden, muss die Auswahl ausdrücklich aufgelöst werden; eine stillschweigende Doppelkorrektur findet nicht statt.

Conditions und expliziter Kontext

Neue Graphen verwenden stabile Namen wie FREQUENCY. condition_map trennt diese Namen von Variablen im Anwendungsprogramm:

from mpylab.tools.mgraph import FREQUENCY_CONDITION_MAP, MGraph

graph = MGraph(
    dotfile,
    condition_map=FREQUENCY_CONDITION_MAP,
    allow_legacy_condition_context=False,
)
graph.EvaluateConditions(context={"frequency": current_frequency})
AmpLF [condition="80e6 <= FREQUENCY <= 1e9"]
AmpHF [condition="1e9 < FREQUENCY <= 6e9"]

FREQUENCY_CONDITION_MAP akzeptiert während der Migration sowohl f als auch FREQUENCY und bildet beide auf frequency ab. Gepflegte Graphen verwenden FREQUENCY. Fehlende Namen, ungültige Ausdrücke oder Ergebnisse, die nicht boolesch sind, lösen mpylab.tools.mgraph.ConditionContextError aus.

Ohne expliziten Kontext kann EvaluateConditions aus Kompatibilitätsgründen noch im Aufrufer-Frame suchen. Dieser veraltete Weg erzeugt einmal pro Graphinstanz eine mpylab.tools.mgraph.LegacyConditionContextWarning. Neue Anwendungen schalten ihn mit allow_legacy_condition_context=False ab. Kontext-Mapping und diese Einstellung werden im Pickle gespeichert.

Physische Context-Controller

Wenn ein Messgraph außer der Auswahl aktiver Pfade auch einen physischen Aufbau umschalten muss, kapselt ein Context-Controller diese Abbildung. Die Anwendung übergibt weiterhin ausschließlich logische Werte:

from mpylab.tools.mgraph import MGraph, TEM_CONDITION_MAP

graph = MGraph(
    dotfile,
    condition_map=TEM_CONDITION_MAP,
    allow_legacy_condition_context=False,
)
graph.CreateDevices()
graph.Init_Devices()
result = graph.ApplyContext({
    "frequency": 150e6,
    "mode": "GTEM",
})
sw [ini="sw_gtem.ini" context_controller="mode"]
source -> gtem [condition="MODE == 'GTEM'"]
source -> term [condition="MODE == 'TERM'"]

Der Gerätetreiber implementiert dafür PrepareGraphContext(context), ApplyGraphContext(context) und GetGraphState(). Optional kann GraphStateMatches(expected, actual) die fachliche Gleichheit definieren. PrepareGraphContext muss den vollständigen erwarteten Zustand liefern, nicht nur das zuletzt geschaltete Relais. GetGraphState muss den realen Zustand abfragen; eine unvollständige oder nicht auswertbare Antwort ist kein Erfolg.

ApplyContext schaltet RF vor einer relevanten Context- oder Frequenzänderung aus, wendet nötige Controlleränderungen an, wertet Conditions aus, setzt die Frequenz und verifiziert anschließend die Rücklesewerte. Die im Attribut genannten Schlüssel steuern, wann ApplyGraphContext erneut aufgerufen wird. Bei context_controller="mode" löst daher ein Frequenzwechsel allein keine erneute Mode-Umschaltung aus; der aktuelle vollständige Schaltzustand wird trotzdem geplant und geprüft.

Eine Zustandsabweichung löst mpylab.tools.mgraph.GraphStateMismatchError aus und schaltet RF aus. CheckContextState prüft sofort, PollContextState kann die Abfragen zeitlich begrenzen. RFOn_Devices, Read und NBRead führen diese Prüfung an ihren sicheren Programmpunkten aus. Nur ein ausdrücklicher Bedienerentscheid sollte anschließend ReapplyContext aufrufen; allgemeine Messanwendungen dürfen den konkreten Schaltertyp nicht kennen.

Sichere Actions

Ein aktives Element mit condition kann zusätzlich eine action tragen:

SwitchLF [condition="FREQUENCY <= 1e9"
          action="switch.switch_to(0)"]
graph.EvaluateConditions(context={
    "frequency": frequency,
    "switch": switch_instance,
})

Zulässig ist genau ein direkter Methodenaufruf auf einem Objekt aus dem Kontext mit literalen Argumenten. Beliebiger Python-Code, Attributketten als Argumente und mehrere Anweisungen werden abgelehnt. doAction=False wertet nur den Graphzustand aus und schaltet keine Hardware. Das ist der richtige Modus für Preflight und Pfadprüfung.

Pfadprüfung

mpylab.tools.mgraph.MGraph.check_paths() prüft Conditions über einen Parameterbereich, ohne Actions auszuführen oder den Graphzustand zu ändern:

report = graph.check_paths(
    "Sg",
    "TxAnt",
    "frequency",
    start_value=80e6,
    stop_value=6e9,
    context={"measurement_mode": "immunity"},
)
if not report.ok:
    print(report.format_text())

Der Standardmodus exactly_one_path verlangt genau einen vollständigen aktiven Pfad. mode="warn_parallel" stuft mehrere vollständige Pfade zur Warnung herab; ein fehlender Pfad bleibt ein Fehler. Aktive abzweigende Teilpfade werden ebenfalls gemeldet.

Einfache Vergleiche werden grenzwertbewusst geprüft: Neben dem Grenzwert werden die unmittelbar kleineren und größeren Gleitkommawerte getestet. So werden </<=-Lücken und Überlappungen erkannt. Tatsächliche Messpunkte können mit values=frequencies ergänzt werden. Bei komplexeren Ausdrücken ist keine vollständige statische Grenzanalyse möglich; report.boundary_analysis_complete ist dann False.

Für einen einzelnen Kontext liefert active_paths die aktiven Pfade ebenfalls zustandsfrei. Die Anwendung von Korrekturen wird unter Pfadkorrekturen beschrieben.

Lokale DOT-Dateien migrieren

mpylab-dot-migrate ersetzt das historische Condition-Token f durch FREQUENCY. Der Standard ist ein Dry-Run mit Syntaxprüfung und Unified Diff:

mpylab-dot-migrate local-config.dot
mpylab-dot-migrate --write local-config.dot
mpylab-dot-migrate --check --recursive path/to/configurations

Nur das exakte Namenstoken in condition wird geändert. Kommentare, Labels, Dateinamen, Stringliterale und action bleiben unverändert. --write schreibt atomar; Verzeichnisse werden nur mit --recursive durchlaufen. Symbolischen Links wird nicht gefolgt.

Für CI liefert --check Status 1 bei nötiger Migration und Status 2 bei ungültigen Eingaben. Pfadprüfungen können direkt ergänzt werden:

mpylab-dot-migrate \
    --path Sg:TxAnt \
    --frequency-range 80e6 6e9 \
    --path-mode exactly_one_path \
    --context "MODE='immunity'" \
    local-config.dot

Alle Optionen stehen in der CLI-Referenz.

Konfigurations-Repositories prüfen

Viele produktive Graphen sollten in einer versionskontrollierten Manifestdatei inventarisiert werden. Benannte TOML-Tabellen können erwartete Gerätezahl, erlaubte Rückfallebenen und fachliche Pfadprüfungen festhalten:

[configurations.large_gtem_immunity]
dot = "LargeGTEM/dot/gtem-immunity.dot"
expected_device_count = 15
allowed_fallback_modules = []

[[configurations.large_gtem_immunity.path_checks]]
start = "sg"
end = "gtem"
parameter = "frequency"
start_value = 1.0e6
stop_value = 4.2e9

Das Manifest gehört zum jeweiligen Konfigurations-Repository, nicht zum MGraph-Dateiformat. Generische Tests können damit vollständige Erfassung, auflösbare INI-Verweise, Geräteerzeugung und Pfadprüfungen sicherstellen. Unfertige Graphen gehören in einen ausdrücklich gekennzeichneten Bereich. Weitere Prüfungen beschreibt Konfigurationen prüfen und migrieren.

Unabhängiger Parser

mpylab.tools.dotparser implementiert den benötigten DOT-Teilumfang nur mit der Python-Standardbibliothek. MGraph wählt ihn nicht automatisch und verwendet produktiv weiterhin pydot. Der unabhängige Parser dient als Vergleichsimplementierung und mögliche Grundlage späterer Entwicklungen.

Tests lesen die eingecheckten Graphen mit beiden Parsern und vergleichen Knoten, Kanten und Attribute. Der frühere, aus dot.g erzeugte Yapps-Parser war nie an den produktiven MGraph-Pfad angebunden und wurde zusammen mit seiner Laufzeitabhängigkeit entfernt.

Weiterführende Seiten