{ "cells": [ { "cell_type": "markdown", "id": "f303e324", "metadata": {}, "source": [ "# Tutorial 1. Controlling a basic experiment using MeasurementControl\n", "\n", "```{seealso}\n", "The complete source code of this tutorial can be found in\n", "\n", "{nb-download}`Tutorial 1. Controlling a basic experiment using MeasurementControl.ipynb`\n", "```\n", "\n", "## Introduction\n", "\n", "Following this Tutorial requires familiarity with the **core concepts** of Quantify, we **highly recommended** to consult the (short) {ref}`User guide` before proceeding (see Quantify documentation). If you have some difficulties following the tutorial it might be worth reviewing the {ref}`User guide` !\n", "\n", "This tutorial covers the basic usage of Quantify focusing on running basic experiments using {class}`.MeasurementControl`.\n", "The {class}`.MeasurementControl` is the main {class}`~qcodes.instrument.Instrument` in charge of running any experiment.\n", "\n", "It takes care of saving the data in a standardized format as well as live plotting of the data during the experiment.\n", "Quantify makes a distinction between {ref}`Iterative` measurements and {ref}`Batched` measurements.\n", "\n", "In an {ref}`Iterative` measurement, the {class}`.MeasurementControl` processes each setpoint fully before advancing to the next.\n", "\n", "In a {ref}`Batched` measurement, the {class}`.MeasurementControl` processes setpoints in batches, for example triggering 10 samples and then reading those 10 outputs.\n", "This is useful in resource constrained or overhead heavy situations.\n", "\n", "Both measurement policies can be 1D, 2D or higher dimensional. Quantify also supports adaptive measurements in which the datapoints are determined during the measurement loop, which are explored in subsequent tutorials.\n", "\n", "This tutorial is structured as follows.\n", "In the first section we use a 1D Iterative loop to explain the flow of a basic experiment.\n", "We start by setting up a noisy cosine model to serve as our mock setup and then use the meas_ctrl to measure this.\n", "We then execute an analysis on the data from this experiment.\n", "\n", "### Import modules and instantiate the MeasurementControl" ] }, { "cell_type": "code", "execution_count": null, "id": "c07d8c84", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "import quantify_core.visualization.pyqt_plotmon as pqm\n", "from quantify_core.analysis import base_analysis as ba\n", "from quantify_core.analysis import cosine_analysis as ca\n", "from quantify_core.data.handling import (\n", " default_datadir,\n", " set_datadir,\n", ")\n", "from quantify_core.measurement import MeasurementControl\n", "from quantify_core.utilities.examples_support import mk_cosine_instrument\n", "from quantify_core.utilities.experiment_helpers import create_plotmon_from_historical\n", "\n", "from quantify_core.utilities.inspect_utils import display_source_code\n", "from quantify_core.visualization.instrument_monitor import InstrumentMonitor\n" ] }, { "cell_type": "markdown", "id": "ef897f1e", "metadata": {}, "source": [ "Before instantiating any instruments or starting a measurement we change the\n", "directory in which the experiments are saved using the\n", "{meth}`~quantify_core.data.handling.set_datadir`\n", "\\[{meth}`~quantify_core.data.handling.get_datadir`\\] functions.\n", "\n", "----------------------------------------------------------------------------------------\n", "\n", "⚠️ **Warning!**\n", "\n", "We recommend always setting the directory at the start of the python kernel and stick\n", "to a single common data directory for all notebooks/experiments within your\n", "measurement setup/PC.\n", "\n", "The cell below sets a default data directory (`~/quantify-data` on Linux/macOS or\n", "`$env:USERPROFILE\\\\quantify-data` on Windows) for tutorial purposes. Change it to your\n", "desired data directory. The utilities to find/search/extract data only work if\n", "all the experiment containers are located within the same directory.\n", "\n", "----------------------------------------------------------------------------------------" ] }, { "cell_type": "code", "execution_count": null, "id": "eb719e1f", "metadata": {}, "outputs": [], "source": [ "set_datadir(default_datadir()) # change me!\n" ] }, { "cell_type": "code", "execution_count": null, "id": "00fc671f", "metadata": {}, "outputs": [], "source": [ "meas_ctrl = MeasurementControl(\"meas_ctrl\")\n", "\n", "# Create the live plotting instrument which handles the graphical interface\n", "# Two windows will be created, the main will feature 1D plots and any 2D plots will go to the secondary\n", "plotmon = pqm.PlotMonitor_pyqt(\"plotmon\")\n", "# Connect the live plotting monitor to the measurement control\n", "meas_ctrl.instr_plotmon(plotmon.name)\n", "\n", "# The instrument monitor will give an overview of all parameters of all instruments\n", "insmon = InstrumentMonitor(\"InstrumentMonitor\")\n" ] }, { "cell_type": "markdown", "id": "7b509735", "metadata": {}, "source": [ "### Define a simple model\n", "\n", "We start by defining a simple model to mock our experiment setup (i.e. emulate physical setup for demonstration purposes).\n", "We will be generating a cosine with some normally distributed noise added on top of it." ] }, { "cell_type": "code", "execution_count": null, "id": "5d563d01", "metadata": {}, "outputs": [], "source": [ "# We create an instrument to contain all the parameters of our model to ensure\n", "# we have proper data logging.\n", "display_source_code(mk_cosine_instrument)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6157bb2b", "metadata": {}, "outputs": [], "source": [ "pars = mk_cosine_instrument()\n" ] }, { "cell_type": "markdown", "id": "f42e02aa", "metadata": {}, "source": [ "Many experiments involving physical instruments are much slower than the time it takes to simulate our `cosine_model`, that is why we added a `sleep()` controlled by the `acq_delay`.\n", "\n", "This allows us to exemplify (later in the tutorial) some of the features of the meas_ctrl that would be imperceptible otherwise." ] }, { "cell_type": "code", "execution_count": null, "id": "df9936fb", "metadata": {}, "outputs": [], "source": [ "# by setting this to a non-zero value we can see the live plotting in action for a slower experiment\n", "pars.acq_delay(0.0)\n" ] }, { "cell_type": "markdown", "id": "85faac5a", "metadata": {}, "source": [ "## A 1D Iterative loop\n", "\n", "### Running the 1D experiment\n", "\n", "The complete experiment is defined in just 4 lines of code. We specify what parameter we want to set, time `t` in this case, what points to measure at, and what parameter to measure.\n", "We then tell the {ref}`MeasurementControl` `meas_ctrl` to run which will return an {class}`~xarray.Dataset` object.\n", "\n", "We use the {class}`.Settable` and {class}`.Gettable` helper classes to ensure our parameters contain the correct attributes." ] }, { "cell_type": "code", "execution_count": null, "id": "66f1d928", "metadata": {}, "outputs": [], "source": [ "meas_ctrl.settables(\n", " pars.t\n", ") # as a QCoDeS parameter, 't' obeys the JSON schema for a valid Settable and can be passed to the meas_ctrl directly.\n", "meas_ctrl.setpoints(np.linspace(0, 2, 50))\n", "meas_ctrl.gettables(\n", " pars.sig\n", ") # as a QCoDeS parameter, 'sig' obeys the JSON schema for a valid Gettable and can be passed to the meas_ctrl directly.\n", "dataset = meas_ctrl.run(\"Cosine test\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "c68fcdcd", "metadata": {}, "outputs": [], "source": [ "plotmon.main_QtPlot\n" ] }, { "cell_type": "code", "execution_count": null, "id": "ff12b8b2", "metadata": {}, "outputs": [], "source": [ "# The dataset has a time-based unique identifier automatically assigned to it\n", "# The name of the experiment is stored as well\n", "dataset.attrs[\"tuid\"], dataset.attrs[\"name\"]\n" ] }, { "cell_type": "markdown", "id": "4e4e8604", "metadata": {}, "source": [ "The {ref}`dataset` is stored as an {class}`xarray.Dataset` (you can read more about xarray project at ).\n", "\n", "As shown below, a **Data variable** is assigned to each dimension of the settables and the gettable(s), following a format in which the settable takes the form x0, x1, etc. and the gettable(s) the form y0, y1, y2, etc.. You can click on the icons on the right to see the attributes of each variable and the values.\n", "\n", "See {ref}`data-storage` in the {ref}`User guide` for details." ] }, { "cell_type": "code", "execution_count": null, "id": "f74fc466", "metadata": {}, "outputs": [], "source": [ "dataset\n" ] }, { "cell_type": "markdown", "id": "cf31058f", "metadata": {}, "source": [ "We can play with some live plotting options to see how the meas_ctrl behaves when changing the update interval." ] }, { "cell_type": "code", "execution_count": null, "id": "54678ecd", "metadata": {}, "outputs": [], "source": [ "# By default the meas_ctrl updates the datafile and live plot every 0.1 seconds (and not faster) to reduce overhead.\n", "meas_ctrl.update_interval(\n", " 0.1\n", ") # Setting it even to 0.01 creates a dramatic slowdown, try it out!\n", "\n" ] }, { "cell_type": "markdown", "id": "589fa31f", "metadata": {}, "source": [ "In order to avoid an experiment being bottlenecked by the `update_interval` we recommend setting it between ~0.1-1.0 s for a comfortable refresh rate and good performance." ] }, { "cell_type": "code", "execution_count": null, "id": "d361e9e1", "metadata": {}, "outputs": [], "source": [ "meas_ctrl.settables(pars.t)\n", "meas_ctrl.setpoints(np.linspace(0, 50, 1000))\n", "meas_ctrl.gettables(pars.sig)\n", "dataset = meas_ctrl.run(\"Many points live plot test\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b838768d", "metadata": {}, "outputs": [], "source": [ "plotmon.main_QtPlot\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b8fb0eb8", "metadata": {}, "outputs": [], "source": [ "pars.noise_level(0) # let's disable noise from here on to get prettier figures\n" ] }, { "cell_type": "markdown", "id": "c1bd86b6", "metadata": {}, "source": [ "## Analyzing the experiment\n", "\n", "Plotting the data and saving the plots for a simple 1D case can be achieved in a few lines using a standard analysis from the {mod}`quantify_core.analysis.base_analysis` module.\n", "In the same module you can find several common analyses that might fit your needs.\n", "It also provides a base data-analysis class ({class}`~quantify_core.analysis.base_analysis.BaseAnalysis`) -- a flexible framework for building custom analyses, which we explore in detail in {ref}`a dedicated tutorial `.\n", "\n", "The {class}`~xarray.Dataset` generated by the meas_ctrl contains all the information required to perform basic analysis of the experiment. Running an analysis can be as simple as:" ] }, { "cell_type": "code", "execution_count": null, "id": "06d5e22b", "metadata": {}, "outputs": [], "source": [ "a_obj = ca.CosineAnalysis(label=\"Cosine test\").run()\n", "a_obj.display_figs_mpl()\n" ] }, { "cell_type": "markdown", "id": "29f7aee3", "metadata": {}, "source": [ "Here the analysis loads the latest dataset on disk matching a search based on the {code}`label`. See {class}`~quantify_core.analysis.base_analysis.BaseAnalysis` for alternative dataset specification.\n", "\n", "After loading the data, it executes the different steps of the analysis and saves the results into a directory within the experiment container.\n", "\n", "The {ref}`data-storage` contains more details on the folder structure and\n", "files contained in the data directory. The {mod}`quantify_core.data.handling` module provides\n", "convenience data searching and handling utilities like {meth}`~quantify_core.data.handling.get_latest_tuid`.\n", "\n", "For guidance on creating custom analyses, e.g., fitting a model to the data, see\n", "{ref}`analysis-framework-tutorial` where we showcase the implementation of the analysis above.\n", "\n", "## A 2D Iterative loop\n", "\n", "It is often desired to measure heatmaps (2D grids) of some parameter.\n", "This can be done by specifying two settables.\n", "The setpoints of the grid can be specified in two ways.\n", "\n", "### Method 1 - a quick grid" ] }, { "cell_type": "code", "execution_count": null, "id": "2aef0cf3", "metadata": {}, "outputs": [], "source": [ "pars.acq_delay(0.0001)\n", "meas_ctrl.update_interval(2.0)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "76c68901", "metadata": {}, "outputs": [], "source": [ "times = np.linspace(0, 5, 129)\n", "amps = np.linspace(-1, 1, 31)\n", "\n", "meas_ctrl.settables([pars.t, pars.amp])\n", "# meas_ctrl takes care of creating a meshgrid\n", "meas_ctrl.setpoints_grid([times, amps])\n", "meas_ctrl.gettables(pars.sig)\n", "dataset = meas_ctrl.run(\"2D Cosine test\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b958e187", "metadata": {}, "outputs": [], "source": [ "plotmon.secondary_QtPlot\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b6f913b9", "metadata": {}, "outputs": [], "source": [ "a_obj = ba.Basic2DAnalysis(label=\"2D Cosine test\").run()\n", "a_obj.display_figs_mpl()\n" ] }, { "cell_type": "markdown", "id": "77380e4f", "metadata": {}, "source": [ "### Method 2 - custom tuples in 2D\n", "\n", "N.B. it is also possible to do this for higher dimensional loops" ] }, { "cell_type": "code", "execution_count": null, "id": "11247427", "metadata": {}, "outputs": [], "source": [ "r = np.linspace(0, 1.2, 200)\n", "dt = np.linspace(0, 0.5, 200)\n", "f = 3\n", "theta = np.cos(2 * np.pi * f * dt)\n", "\n", "\n", "def polar_coords(r_, theta_):\n", " x_ = r_ * np.cos(2 * np.pi * theta_)\n", " y_ = r_ * np.sin(2 * np.pi * theta_)\n", " return x_, y_\n", "\n", "\n", "x, y = polar_coords(r, theta)\n", "setpoints = np.column_stack([x, y])\n", "setpoints[:5] # show a few points\n" ] }, { "cell_type": "code", "execution_count": null, "id": "50cb828c", "metadata": { "mystnb": { "remove-output": true } }, "outputs": [], "source": [ "pars.acq_delay(0.0001)\n", "meas_ctrl.update_interval(2.0)\n", "\n", "meas_ctrl.settables([pars.t, pars.amp])\n", "meas_ctrl.setpoints(setpoints)\n", "meas_ctrl.gettables(pars.sig)\n", "dataset = meas_ctrl.run(\"2D radial setpoints\")" ] }, { "cell_type": "code", "execution_count": null, "id": "b05f65d2", "metadata": {}, "outputs": [], "source": [ "plotmon.secondary_QtPlot\n" ] }, { "cell_type": "markdown", "id": "26b4392c", "metadata": {}, "source": [ "In this case running a simple (non-interpolated) 2D analysis will not be meaningful. Nevertheless the dataset can be loaded back using the {func}`~quantify_core.utilities.experiment_helpers.create_plotmon_from_historical`" ] }, { "cell_type": "code", "execution_count": null, "id": "f593d14c", "metadata": {}, "outputs": [], "source": [ "plotmon_loaded = create_plotmon_from_historical(label=\"2D radial setpoints\")\n", "plotmon_loaded.secondary_QtPlot\n" ] }, { "cell_type": "code", "execution_count": null, "id": "15c8c1c0-4ddb-4d2d-b656-c0b8b2a5b3c2", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "file_format": "mystnb", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 5 }