End-to-end developer tutorial ============================= This tutorial presents the recommended path towards a custom mpylab application. It deliberately starts with an existing measurement class and virtual instruments. Add a new measurement class or driver only when the existing extension points are insufficient. The example uses the direct amplifier test. Its complete reference configuration is located in ``script/conf/amplifier-test-direct-virtual``. 1. Define the measurement task ------------------------------ Answer four questions before writing code: * Which measurement class best represents the domain workflow? * Which instruments and corrected signal paths does it require? * Which values are stored as SCUQ quantities? * Where must RF-off, autosave, and operator intervention take effect? For this example, ``AmplifierTest`` already exists. The direct setup consists of a signal generator, amplifier, output path, and power meter. .. figure:: ../../_static/tutorial/amplifier-direct-setup.svg :alt: Direct amplifier measurement setup with signal generator, amplifier, cable, attenuator, and power meter :width: 100% Physical view of the direct amplifier measurement setup. The cable and attenuator protect the power meter and are included in the path correction. 2. Virtual workflow configuration --------------------------------- The ``conf.py`` file is the application's top-level configuration. It defines files, search paths, and measurement and evaluation parameters. Frequencies are expressed in Hz; levels are passed as quantities: .. code-block:: python from scuq.quantities import Quantity from scuq.si import WATT measure_parameters = [{ "dotfile": "amplifier-test-direct-virtual.dot", "SearchPaths": [str(CONF_DIR), str(COMMON_CONF_DIR)], "freqs": [150e6, 240e6], "levels": [ Quantity(WATT, dBm2W(level)) for level in (-40, -35, -30) ], "measurement_setup": "direct", "names": { "sg": "Sg", "amp_in": "Amp_Input", "amp_out": "Amp_Output", "pm_out": "Pm", }, "virtual": True, }] Place output, log, and autosave files in a dedicated, ignored ``output`` directory. This keeps the source tree and test runs clean. Run the existing reference directly: .. code-block:: console python script/amplifier-test.py \ script/conf/amplifier-test-direct-virtual/conf.py 3. Instrument topology in the DOT graph --------------------------------------- The DOT graph describes instruments and RF paths, not the frequency-loop workflow. The ``names`` mapping connects logical names from the measurement class to graph nodes. Frequency-dependent branches use an explicit name such as ``FREQUENCY``: .. code-block:: dot digraph { Sg [ini="sg-virtual.ini"] Amp [ini="amp-virtual.ini"] Pm [ini="pm-virtual.ini"] PathSgAmp [ini="path-loss-virtual.ini"] CableAmpPm [ini="path-loss-virtual.ini"] Attenuator [ini="attenuator-40db-virtual.ini"] Sg -> Amp_Input [dev=PathSgAmp what="S21"] Amp_Input -> Amp_Output [dev=Amp what="S21"] Amp_Output -> AmpPmAfterCable [dev=CableAmpPm what="S21"] AmpPmAfterCable -> Pm [dev=Attenuator what="S21"] } .. figure:: ../../_static/tutorial/amplifier-direct-graph.svg :alt: Graphical rendering of the DOT graph for the direct amplifier test :width: 100% Graphical view of the DOT graph. Measurement positions appear as nodes; the ``dev`` and ``what`` attributes assign a correction element and its transfer quantity to each edge. The actual DOT graph adds frequency conditions and further instrument attributes. These refine the mapping without changing the separation between measurement positions and path corrections. The :doc:`DOT reference <../configuration/dot>` documents all attributes in use, condition context, safe actions, and non-mutating path validation. In Python, ``MGraph`` evaluates conditions using an explicit context: .. code-block:: python from mpylab.tools.mgraph import FREQUENCY_CONDITION_MAP, MGraph graph = MGraph( dotfile, names, SearchPaths=search_paths, condition_map=FREQUENCY_CONDITION_MAP, allow_legacy_condition_context=False, ) graph.EvaluateConditions(context={"frequency": frequency}) Looking up an accidentally matching variable in an outer Python frame is only a legacy fallback and should be disabled in new applications. 4. Instrument instances in INI files ------------------------------------ Each instrumented DOT node refers to an INI file. It selects the driver, channel, instrument limits, and optional DAT files. Virtual drivers expose the same public instrument API as real drivers. Moving to hardware should therefore primarily replace INI and DOT files, not introduce a second measurement routine. The :doc:`INI and DAT reference <../configuration/instrument-data>` explains channel layout, error declarations, and complex correction values. Safety limits such as ``MAXIN`` belong in the instrument or amplifier configuration. They must not be enforced only by the user interface. 5. Measurement values and evaluation ------------------------------------ Represent measurement values with units and uncertainty as SCUQ quantities. Restrict conversion to ``float`` to instrument protocols, numerical interfaces, and final presentation. Shared conversions use ``mpylab.tools.uconv``. Raw and evaluated data use stable, domain-specific structures. A measurement description identifies a dataset, while frequency is the preferred index inside that dataset. This allows later inspection of pickle files with pexplorer and the report modules. 6. Autosave and resume ---------------------- Long measurements must resume after the last completely measured point. A measurement routine therefore stores both its data and a structured resume instruction: .. code-block:: python self.set_autosave_resume( measurement="amplifier-test", method="Measure", description=description, ) self.do_autosave() Use ``load_pickle_compat`` when restoring it: .. code-block:: python from mpylab.env.Measure import load_pickle_compat measurement = load_pickle_compat(autosave_filename) The corresponding test must prove that existing frequency and level points are not measured again. Merely testing that a pickle can be loaded is insufficient. 7. Hardware-independent tests ----------------------------- A new workflow requires at least: * a configuration test that initializes the graph and virtual instruments; * a short end-to-end test with a few frequencies and levels; * an autosave/resume test; * tests for instrument limits and RF-off; * a test for missing or ambiguous active graph paths. The virtual test should call the same public methods as the later hardware run. Mocks are useful for targeted error cases, rather than as a replacement for the virtual end-to-end workflow. 8. Add a UI ----------- Keep the measurement routine independent of a text or Qt interface. A GUI runs blocking instrument and measurement calls in a worker. Progress, measurement values, and state updates reach the UI thread through signals or callbacks. RF-off and manual EUT intervention must remain available at all times. They are part of the safety and operator concept and must not depend on a blocked event loop. 9. Move to hardware ------------------- Before the first hardware run, copy the virtual configuration and change only the configuration layer. Then: #. Validate the graph and frequency limits without instrument actions. #. Test initialization with RF disabled. #. Exercise RF-off and protection limits deliberately. #. Run a short measurement with only a few frequencies. #. Create an autosave and test resuming it in practice. #. Only then enable the complete measurement range. This approach keeps the domain workflow identical for virtual and real instruments and exposes differences in the hardware configuration.