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 :class:`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 :class:`mpylab.tools.mgraph.GraphInputError` aus. Ein Messgraph enthält genau einen ``graph`` oder ``digraph``. .. code-block:: python 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 ----------------- .. code-block:: dot 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 :doc:`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 :class:`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: .. code-block:: python 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. .. _de-dot-fieldprobe-orientation: 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: .. code-block:: dot 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: .. code-block:: dot 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: .. code-block:: python 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}) .. code-block:: dot 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 :class:`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 :class:`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: .. code-block:: python 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", }) .. code-block:: dot 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 :class:`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: .. code-block:: dot SwitchLF [condition="FREQUENCY <= 1e9" action="switch.switch_to(0)"] .. code-block:: python 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 ----------- :meth:`mpylab.tools.mgraph.MGraph.check_paths` prüft Conditions über einen Parameterbereich, ohne Actions auszuführen oder den Graphzustand zu ändern: .. code-block:: python 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 :doc:`Pfadkorrekturen <../framework/path-corrections>` 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: .. code-block:: console 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: .. code-block:: console 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 :doc:`CLI-Referenz <../../api/commands>`. 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: .. code-block:: toml [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 :doc:`validation`. 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 --------------------- * :doc:`instrument-data` für INI-Kanäle und DAT-Korrekturdaten; * :doc:`validation` für Konfigurations- und CI-Prüfungen; * :doc:`../framework/path-corrections` für Berechnung und Anwendung von Pfadkorrekturen; * :doc:`../../api/graph` für die kuratierte ``MGraph``-API.