DOT reference for measurement graphs¶
A DOT file describes active instruments, passive correction elements, and
their connections. It is not a workflow program: frequency loops, operator
interaction, and evaluation remain responsibilities of the measurement
class. An mpylab.tools.mgraph.MGraph instance connects the graph to
device creation, conditions, switching actions, and path corrections.
Production parser and input forms¶
MGraph uses pydot as its authoritative production parser. It accepts:
a file name or
os.PathLikeobject;a readable file-like object;
direct DOT text;
structured pydot-compatible graph data.
Relative file names are resolved through SearchPaths. A missing file,
invalid DOT text, or multiple concatenated graphs raise
mpylab.tools.mgraph.GraphInputError. A measurement graph contains
exactly one graph or digraph.
from mpylab.tools.mgraph import MGraph
graph = MGraph(
"measurement.dot",
SearchPaths=["lab/config", "shared/config"],
)
Pickle persistence and traceability¶
Pickling an MGraph embeds its complete DOT text. Restoration treats this
embedded text as authoritative and does not read the original file again. A
measurement history therefore remains reproducible if the local DOT file is
missing or has subsequently changed. The original file name remains as
provenance in fname_or_data.
Supported DOT subset¶
The DOT subset used by mpylab includes in particular:
graph,digraph, and the optionalstrictqualifier;node and edge statements;
one or more attribute lists in
[...];directed
->and undirected--connections;identifiers, numbers, and quoted strings;
#,//, and/* ... */comments.
Measurement graphs normally use digraph.
Nodes and edges¶
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"]
}
Important attributes are:
iniConfiguration of an instrument instance. The file name is resolved through
SearchPaths.devInstrument node whose channel describes an edge. The node must be declared explicitly in the graph.
whatData-channel name such as
S21. It may be omitted when an edge deliberately references an instrument without a data channel.conditionBoolean expression that activates a node or edge.
actionSafely restricted method call that may run when an element with a condition becomes active.
context_controllerMarks an instrument that maps logical context values to physical switch state and reads that state back. Its value names the context keys that require reapplication, for example
context_controller="mode".
Pure connection or measurement-position nodes need no INI file. The INI and DAT reference documents channel layout, units, and DAT references.
Instrument types and drivers¶
DESCRIPTION/TYPE in the INI file names the entry class exported by the
module in DESCRIPTION/DRIVER. Canonical types correspond to instrument
base classes such as SIGNALGENERATOR, POWERMETER, FIELDPROBE,
RECEIVER, SPECTRUMANALYZER, NETWORKANALYZER, VLISN, and
TLISN.
Resolution ignores case and historical surrounding quotes, but it does not
use fuzzy string matching. TYPE = Custom additionally requires
DRIVER and CLASS; its module is loaded from SearchPaths.
Missing INI or driver files, unknown types, and missing entry classes raise
mpylab.tools.mgraph.DeviceConfigurationError. CreateDevices
only creates Python driver instances. Hardware access starts in
Init_Devices.
Selecting active instruments¶
Instrument operations such as Init_Devices, SetFreq_Devices,
ConfReceivers, RFOff_Devices, and RunReceiverScans consistently
default to IgnoreInactive=True. They therefore address only instrument
nodes activated by the current conditions. Passing IgnoreInactive=False
deliberately includes inactive but configured instruments, for example for a
complete diagnostic or an explicitly graph-wide cleanup operation.
Name mapping¶
A measurement class uses domain roles such as sg, pm_out, or
fieldprobe. The names dictionary from conf.py is passed to
MGraph as themap:
names = {
"sg": "Sg",
"pm_out": "Pm",
"fieldprobe": ["Probe1", "Probe2"],
}
graph = MGraph(dotfile, themap=names)
The authoritative direction is logical name -> DOT node. Empty lists are
valid for optional roles. When restoring historical pickles, MGraph
normalizes unambiguously reversed or bidirectional mappings.
Semantic validation rejects unknown mapping targets and dev references.
After CreateDevices, it additionally checks that every referenced device
has an actual driver instance.
Field-probe orientation¶
Field-probe drivers always return their components as
[probe_x, probe_y, probe_z]. Mapping into cell coordinates belongs to the
measurement configuration and should normally be stored on the field-probe
node in the DOT graph. A signed axis permutation can be written as:
Probe [probe_axis_map="cell_x:-probe_y,cell_y:+probe_x,cell_z:+probe_z"]
For arbitrary rotations, provide a proper orthonormal rotation matrix with
determinant +1. It maps the probe vector to the cell vector:
Probe [probe_rotation_matrix="0,-1,0;1,0,0;0,0,1"]
The more readable fixed-cell-axis alternative is
probe_rotation_angles_deg. Its order is cell z, then cell x, and finally
cell y. Orientation metadata may also come from the configuration dictionary
or a field-probe INI file. Conflicting definitions require explicit
resolution; mpylab does not silently apply an orientation twice.
Conditions and explicit context¶
New graphs use stable names such as FREQUENCY. condition_map keeps
those names independent of variables in application code:
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"]
During migration, FREQUENCY_CONDITION_MAP accepts both f and
FREQUENCY and maps them to frequency. Maintained graphs use
FREQUENCY. Missing names, invalid expressions, and non-Boolean results
raise mpylab.tools.mgraph.ConditionContextError.
For compatibility, EvaluateConditions can still inspect the caller frame
when no explicit context is supplied. This deprecated path emits
mpylab.tools.mgraph.LegacyConditionContextWarning once per graph
instance. New applications disable it with
allow_legacy_condition_context=False. The context mapping and this
setting are preserved in pickle files.
Physical context controllers¶
When a measurement graph must switch physical hardware in addition to selecting active paths, a context controller encapsulates that mapping. The application continues to supply logical values only:
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'"]
The instrument driver implements PrepareGraphContext(context),
ApplyGraphContext(context), and GetGraphState(). It may additionally
implement GraphStateMatches(expected, actual) to define domain-specific
equality. PrepareGraphContext must return the complete expected state,
not only the relay most recently changed. GetGraphState must query the
physical state; an incomplete or unparsable response is not a success.
ApplyContext switches RF off before a relevant context or frequency
change, applies required controller changes, evaluates conditions, sets the
frequency, and then verifies the read-back values. Keys named in the
attribute determine when ApplyGraphContext is called again. With
context_controller="mode", a
frequency-only change therefore does not reapply the mode; the complete
current switch state is still planned and verified.
A state mismatch raises
mpylab.tools.mgraph.GraphStateMismatchError and switches RF off.
CheckContextState verifies immediately, while PollContextState can
rate-limit queries. RFOn_Devices, Read, and NBRead perform this
check at their safe program boundaries. Only an explicit operator decision
should subsequently call ReapplyContext; general measurement
applications must not know the concrete switch type.
Safe actions¶
An active element with a condition may also contain an action:
SwitchLF [condition="FREQUENCY <= 1e9"
action="switch.switch_to(0)"]
graph.EvaluateConditions(context={
"frequency": frequency,
"switch": switch_instance,
})
Exactly one direct method call on an object from the context with literal
arguments is permitted. Arbitrary Python code, attribute chains as
arguments, and multiple statements are rejected. doAction=False only
evaluates graph state and does not switch hardware. This is the appropriate
mode for preflight and path validation.
Path validation¶
mpylab.tools.mgraph.MGraph.check_paths() evaluates conditions over a
parameter range without executing actions or changing graph state:
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())
The default exactly_one_path mode requires exactly one complete active
path. mode="warn_parallel" downgrades multiple complete paths to a
warning; a missing path remains an error. Active dangling branches are also
reported.
Simple comparisons are checked with boundary awareness: the boundary and
the immediately lower and higher floating-point values are tested. This
detects </<= gaps and overlaps. Actual measurement points may be
added using values=frequencies. Complex expressions cannot always be
analyzed completely; report.boundary_analysis_complete is then False.
For a single context, active_paths returns active paths without changing
state. See path corrections for
correction calculation and application.
Migrating local DOT files¶
mpylab-dot-migrate replaces the historical condition token f with
FREQUENCY. Its default is a dry run with syntax validation and a unified
diff:
mpylab-dot-migrate local-config.dot
mpylab-dot-migrate --write local-config.dot
mpylab-dot-migrate --check --recursive path/to/configurations
Only the exact name token in condition is changed. Comments, labels,
filenames, string literals, and action remain untouched. --write
writes atomically; directories are traversed only with --recursive.
Symbolic links are not followed.
For CI, --check returns status 1 when migration is required and status 2
for invalid input. Path checks may be added directly:
mpylab-dot-migrate \
--path Sg:TxAnt \
--frequency-range 80e6 6e9 \
--path-mode exactly_one_path \
--context "MODE='immunity'" \
local-config.dot
All options are listed in the CLI reference.
Validating configuration repositories¶
A collection of production graphs should be inventoried in a version-controlled manifest. Named TOML tables can record expected device counts, permitted fallbacks, and domain path checks:
[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
The manifest belongs to the configuration repository, not to the MGraph
file format. Generic tests can use it to verify complete inventory, resolved
INI references, device creation, and path checks. Store unfinished graphs in
an explicitly marked area. See Validate and migrate configurations for further checks.
Independent parser¶
mpylab.tools.dotparser implements the required DOT subset using only the
Python standard library. MGraph does not select it automatically and
continues to use pydot in production. The independent parser serves as a
comparison implementation and possible basis for future development.
Tests parse the version-controlled graphs with both parsers and compare
nodes, edges, and attributes. The earlier Yapps parser generated from
dot.g was never connected to the production MGraph path and was
removed together with its runtime dependency.