Developing measurement classes ============================== A measurement class describes the domain workflow. It coordinates MGraph, instruments, measurement points, autosave, and evaluation, but contains neither instrument-specific SCPI commands nor Qt widgets. Derive from Measure ------------------- .. code-block:: python from mpylab.env.Measure import Measure class ExampleMeasurement(Measure): def __init__(self, SearchPaths=None): super().__init__(SearchPaths=SearchPaths) self.raw_data = {} self.processed_data = {} ``Measure`` provides logging, operator interaction, autosave, instrument initialization, RF-off, and pickle restoration. New classes should reuse this infrastructure rather than introduce independent variants. Measurement workflow -------------------- A robust workflow follows these steps: #. Prepare the dataset and resume information. #. Create ``MGraph`` with name and condition mappings. #. Create instruments and initialize them through ``_init_measurement_devices``. #. Explicitly apply the logical graph context before each frequency-dependent access. Explicit condition evaluation remains sufficient for graphs without a physical controller. #. Add only complete measurement points to the result structure. #. Call ``do_autosave`` regularly. #. Switch RF off and close instruments on a ``finally`` path. Use ``_finalize_measurement_devices`` for common finalization. Measurement errors must not bypass this safety path. .. _en-measurement-graph-context: Applying an explicit graph context ---------------------------------- Custom measurement applications should pass frequency, operating mode, and similar domain values together to ``MGraph.ApplyContext``. This keeps condition evaluation, frequency setup, physical switching, and state read-back in one coherent operation: .. code-block:: python from mpylab.tools.mgraph import ( GraphStateMismatchError, MGraph, TEM_CONDITION_MAP, ) graph = MGraph( dotfile, themap=names, SearchPaths=search_paths, condition_map=TEM_CONDITION_MAP, allow_legacy_condition_context=False, ) graph.CreateDevices() graph.Init_Devices() requested_context = {"frequency": frequency, "mode": "GTEM"} try: context_result = graph.ApplyContext(requested_context) except GraphStateMismatchError as error: # MGraph has already switched RF off. The application records # error.mismatches and asks the operator for a decision. if not operator_explicitly_selected_reapply(error.mismatches): raise graph.ReapplyContext() context_result = graph.ApplyContext(requested_context) ``GraphStateMismatchError.mismatches`` contains the expected and observed state for each controller, together with status or query errors where applicable. The application should record this information and present it neutrally in its CLI or user interface. It must not interpret instrument-specific relay state; that domain knowledge remains in the controller driver. On a mismatch, ``MGraph`` has already called graph-wide ``RFOff``. Aborting is therefore the safe default. Only an explicit operator choice may first restore the last planned state through ``ReapplyContext``. The application then applies the originally requested context again. Merely retrying ``ApplyContext`` is deliberately insufficient because that could silently override the external switching action. ``CheckContextState`` forces an immediate check; ``PollContextState`` rate-limits repeated queries. ``RFOn_Devices`` always checks immediately before enabling RF. ``Read`` and ``NBRead`` integrate a rate-limited check into measurement loops. This is not an asynchronous hardware watchdog, but it detects external intervention at the relevant program boundaries. The required DOT attributes and driver contract are documented in the :doc:`DOT reference <../configuration/dot>`. The curated :doc:`MGraph API <../../api/graph>` lists the result and exceptions. Autosave contract ----------------- Before the first autosave, store a structured resume instruction: .. code-block:: python self.set_autosave_resume( measurement="example", method="Measure_Example", description=description, ) On resume, the measurement routine must detect existing data and continue at the first incomplete point. Its test therefore verifies call counts and result data, rather than merely checking that the pickle can be read. Data structure -------------- Prefer ``dataset[frequency] = quantity`` for frequency-dependent data, or use a clearly documented equivalent structure. Keep raw data, evaluated data, and metadata separate. Store references to preparatory measurements with dataset key and source so reports can present traceability without recalculating results. Evaluation ---------- Measurement and evaluation are separate methods. This allows an evaluation algorithm to run again on an ``after-measure`` pickle without accessing hardware. Evaluation does not modify raw data and records the reference datasets and relevant parameters it used.