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.

Direct amplifier measurement setup with signal generator, amplifier, cable, attenuator, and power meter

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:

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:

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:

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"]
}
Graphical rendering of the DOT graph for the direct amplifier test

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 DOT reference documents all attributes in use, condition context, safe actions, and non-mutating path validation.

In Python, MGraph evaluates conditions using an explicit context:

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 INI and DAT reference 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:

self.set_autosave_resume(
    measurement="amplifier-test",
    method="Measure",
    description=description,
)
self.do_autosave()

Use load_pickle_compat when restoring it:

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:

  1. Validate the graph and frequency limits without instrument actions.

  2. Test initialization with RF disabled.

  3. Exercise RF-off and protection limits deliberately.

  4. Run a short measurement with only a few frequencies.

  5. Create an autosave and test resuming it in practice.

  6. 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.