{ "cells": [ { "cell_type": "markdown", "id": "ee6bbf81", "metadata": {}, "source": [ "# Overview\n", "\n", "A `quantify-core` experiment typically consists of a data-acquisition loop in\n", "which one or more parameters are set and one or more parameters are measured.\n", "\n", "The core of Quantify can be understood by understanding the following concepts:\n", "\n", "- {ref}`Instruments and Parameters`\n", "- {ref}`Measurement Control`\n", "- {ref}`Settables and Gettables`\n", "- {ref}`Data storage`\n", "- {ref}`Analysis`\n", "\n", "## Code snippets\n", "\n", "```{seealso}\n", "The complete source code of the examples on this page can be found in\n", "\n", "{nb-download}`concepts.ipynb`\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "ba3aa975", "metadata": { "mystnb": { "code_prompt_show": "Import common utilities used in the examples" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "import tempfile\n", "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import xarray as xr\n", "from qcodes import Instrument, ManualParameter, Parameter, validators\n", "from scipy.optimize import minimize_scalar\n", "\n", "import quantify_core.data.handling as dh\n", "from quantify_core.analysis import base_analysis as ba\n", "from quantify_core.analysis import cosine_analysis as ca\n", "from quantify_core.measurement import Gettable, MeasurementControl\n", "from quantify_core.utilities.dataset_examples import mk_2d_dataset_v1\n", "from quantify_core.utilities.examples_support import mk_cosine_instrument\n", "from quantify_core.utilities.inspect_utils import display_source_code\n", "\n", "dh.set_datadir(dh.default_datadir())\n", "meas_ctrl = MeasurementControl(\"meas_ctrl\")" ] }, { "cell_type": "markdown", "id": "2fd1ff2c", "metadata": {}, "source": [ "# Instruments and Parameters\n", "\n", "## Parameter\n", "\n", "A parameter represents a state variable of the system. Parameters:\n", "\n", "- can be gettable and/or settable;\n", "- contain metadata such as units and labels;\n", "- are commonly implemented using the QCoDeS {class}`~qcodes.parameters.Parameter` class.\n", "\n", "A parameter implemented using the QCoDeS {class}`~qcodes.parameters.Parameter` class\n", "is a valid {class}`.Settable` and {class}`.Gettable` and as such can be used directly in\n", "an experiment loop in the {class}`.MeasurementControl` (see subsequent sections).\n", "\n", "## Instrument\n", "\n", "An Instrument is a container for parameters that typically (but not necessarily)\n", "corresponds to a physical piece of hardware.\n", "\n", "Instruments provide the following functionality:\n", "\n", "- Container for parameters.\n", "- A standardized interface.\n", "- Logging of parameters through the {meth}`~qcodes.instrument.Instrument.snapshot` method.\n", "\n", "All instruments inherit from the QCoDeS {class}`~qcodes.instrument.Instrument` class.\n", "They are displayed by default in the {class}`.InstrumentMonitor`\n", "\n", "# Measurement Control\n", "\n", "The {class}`.MeasurementControl` (meas_ctrl) is in charge of the data-acquisition loop\n", "and is based on the notion that, in general, an experiment consists of the following\n", "three steps:\n", "\n", "1. Initialize (set) some parameter(s),\n", "2. Measure (get) some parameter(s),\n", "3. Store the data.\n", "\n", "`quantify-core` provides two helper classes, {class}`.Settable` and {class}`.Gettable` to aid\n", "in these steps, which are explored further in later sections of this article.\n", "\n", "{class}`.MeasurementControl` provides the following functionality:\n", "\n", "- standardization of experiments;\n", "- standardization data storage;\n", "- {ref}`live plotting of the experiment `;\n", "- {math}`n`-dimensional sweeps;\n", "- data acquisition controlled iteratively or in batches;\n", "- adaptive sweeps (measurement points are not predetermined at the beginning of an experiment).\n", "\n", "## Basic example, a 1D iterative measurement loop\n", "\n", "Running an experiment is simple!\n", "Simply define what parameters to set, and get, and what points to loop over.\n", "\n", "In the example below we want to set frequencies on a microwave source and acquire the\n", "signal from the Qblox Pulsar readout module:" ] }, { "cell_type": "code", "execution_count": null, "id": "32426643", "metadata": { "mystnb": { "code_prompt_show": "Initialize (mock) instruments" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "mw_source1 = Instrument(\"mw_source1\")\n", "\n", "# NB: for brevity only, this not the proper way of adding parameters to QCoDeS instruments\n", "mw_source1.freq = ManualParameter(\n", " name=\"freq\",\n", " label=\"Frequency\",\n", " unit=\"Hz\",\n", " vals=validators.Numbers(),\n", " initial_value=1.0,\n", ")\n", "\n", "pulsar_QRM = Instrument(\"pulsar_QRM\")\n", "# NB: for brevity only, this not the proper way of adding parameters to QCoDeS instruments\n", "pulsar_QRM.signal = Parameter(\n", " name=\"sig_a\", label=\"Signal\", unit=\"V\", get_cmd=lambda: mw_source1.freq() * 1e-8\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "5b54a5de", "metadata": {}, "outputs": [], "source": [ "meas_ctrl.settables(\n", " mw_source1.freq\n", ") # We want to set the frequency of a microwave source\n", "meas_ctrl.setpoints(np.arange(5e9, 5.2e9, 100e3)) # Scan around 5.1 GHz\n", "meas_ctrl.gettables(pulsar_QRM.signal) # acquire the signal from the pulsar QRM\n", "dset = meas_ctrl.run(name=\"Frequency sweep\") # run the experiment" ] }, { "cell_type": "markdown", "id": "8299856b", "metadata": {}, "source": [ "The {class}`.MeasurementControl` can also be used to perform more advanced experiments\n", "such as 2D scans, pulse-sequences where the hardware is in control of the acquisition\n", "loop, or adaptive experiments in which it is not known what data points to acquire in\n", "advance, they are determined dynamically during the experiment.\n", "Take a look at some of the tutorial notebooks for more in-depth examples on\n", "usage and application.\n", "\n", "## Control Mode\n", "\n", "Batched mode can be used to deal with constraints imposed by (hardware) resources or to reduce overhead.\n", "\n", "In **iterative mode** , the measurement control steps through each setpoint one at a time,\n", "processing them one by one.\n", "\n", "In **batched mode** , the measurement control vectorizes the setpoints such that they are processed in batches.\n", "The size of these batches is automatically calculated but usually dependent on resource\n", "constraints; you may have a device that can hold 100 samples but you wish to sweep over 2000 points.\n", "\n", "```{note}\n", "The maximum batch size of the settable(s)/gettable(s) should be specified using the\n", "`.batch_size` attribute. If not specified infinite size is assumed and all setpoint\n", "are passed to the settable(s).\n", "```\n", "\n", "```{tip}\n", "In *Batched* mode it is still possible to perform outer iterative sweeps with an inner\n", "batched sweep.\n", "This is performed automatically when batched settables (`.batched=True`) are mixed\n", "with iterative settables (`.batched=False`). To correctly grid the points in this mode\n", "use {meth}`.MeasurementControl.setpoints_grid`.\n", "```\n", "\n", "Control mode is detected automatically based on the `.batched` attribute of the\n", "settable(s) and gettable(s); this is expanded upon in subsequent sections.\n", "\n", "```{note}\n", "All gettables must have the same value for the `.batched` attribute.\n", "Only when all gettables have `.batched=True`, settables are allowed to have mixed\n", "`.batched` attribute (e.g. `settable_A.batched=True`, `settable_B.batched=False`).\n", "```\n", "\n", "Depending on which control mode the {class}`.MeasurementControl` is running in,\n", "the interfaces for Settables (their input interface) and Gettables\n", "(their output interface) are slightly different.\n", "\n", "It is also possible for batched gettables to return an array with a length less\n", "than the length of the setpoints, and similarly for the input of the Settables.\n", "This is often the case when working with resource-constrained devices, for\n", "example, if you have *n* setpoints but your device can load only less than *n*\n", "datapoints into memory. In this scenario, measurement control tracks how many\n", "datapoints were actually processed, automatically adjusting the size of the next\n", "batch." ] }, { "cell_type": "code", "execution_count": null, "id": "1dbb3573", "metadata": { "mystnb": { "code_prompt_show": "Example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time = ManualParameter(\n", " name=\"time\",\n", " label=\"Time\",\n", " unit=\"s\",\n", " vals=validators.Arrays(), # accepts an array of values\n", ")\n", "signal = Parameter(\n", " name=\"sig_a\", label=\"Signal\", unit=\"V\", get_cmd=lambda: np.cos(time())\n", ")\n", "\n", "time.batched = True\n", "time.batch_size = 5\n", "signal.batched = True\n", "signal.batch_size = 10\n", "\n", "meas_ctrl.settables(time)\n", "meas_ctrl.gettables(signal)\n", "meas_ctrl.setpoints(np.linspace(0, 7, 23))\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "dset_grid.y0.plot()" ] }, { "cell_type": "markdown", "id": "602f70da", "metadata": {}, "source": [ "# Settables and Gettables\n", "\n", "Experiments typically involve varying some parameters and reading others.\n", "In `quantify-core` we encapsulate these concepts as the {class}`.Settable`\n", "and {class}`.Gettable` respectively.\n", "As their name implies, a Settable is a parameter you set values to,\n", "and a Gettable is a parameter you get values from.\n", "\n", "The interfaces for Settable and Gettable parameters are encapsulated in the\n", "{class}`.Settable` and {class}`.Gettable` helper classes respectively.\n", "We set values to Settables; these values populate an `X`-axis.\n", "Similarly, we get values from Gettables which populate a `Y`-axis.\n", "These classes define a set of mandatory and optional attributes the\n", "{class}`.MeasurementControl` recognizes and will use as part of the experiment,\n", "which are expanded up in the API reference.\n", "For ease of use, we do not require users to inherit from a Gettable/Settable class,\n", "and instead provide contracts in the form of JSON schemas to which these classes\n", "must fit (see {class}`.Settable` and {class}`.Gettable` docs for these schemas).\n", "In addition to using a library that fits these contracts\n", "(such as the {class}`~qcodes.parameters.Parameter` family of classes).\n", "we can define our own Settables and Gettables." ] }, { "cell_type": "code", "execution_count": null, "id": "e41b8ccb", "metadata": { "tags": [ "hide-output" ] }, "outputs": [], "source": [ "t = ManualParameter(\"time\", label=\"Time\", unit=\"s\")\n", "\n", "\n", "class WaveGettable:\n", " \"\"\"An examples of a gettable.\"\"\"\n", "\n", " def __init__(self):\n", " self.unit = \"V\"\n", " self.label = \"Amplitude\"\n", " self.name = \"sine\"\n", "\n", " def get(self):\n", " \"\"\"Return the gettable value.\"\"\"\n", " return np.sin(t() / np.pi)\n", "\n", " def prepare(self) -> None:\n", " \"\"\"Optional methods to prepare can be left undefined.\"\"\"\n", " print(\"Preparing the WaveGettable for acquisition.\")\n", "\n", " def finish(self) -> None:\n", " \"\"\"Optional methods to finish can be left undefined.\"\"\"\n", " print(\"Finishing WaveGettable to wrap up the experiment.\")\n", "\n", "\n", "# verify compliance with the Gettable format\n", "wave_gettable = WaveGettable()\n", "Gettable(wave_gettable)" ] }, { "cell_type": "markdown", "id": "a55e8f9e", "metadata": {}, "source": [ "\"Grouped\" gettable(s) are also allowed.\n", "Below we create a Gettable which returns two distinct quantities at once:" ] }, { "cell_type": "code", "execution_count": null, "id": "2fba6d14", "metadata": { "tags": [ "hide-output" ] }, "outputs": [], "source": [ "t = ManualParameter(\n", " \"time\",\n", " label=\"Time\",\n", " unit=\"s\",\n", " vals=validators.Numbers(), # accepts a single number, e.g. a float or integer\n", ")\n", "\n", "\n", "class DualWave1D:\n", " \"\"\"Example of a \"dual\" gettable.\"\"\"\n", "\n", " def __init__(self):\n", " self.unit = [\"V\", \"V\"]\n", " self.label = [\"Sine Amplitude\", \"Cosine Amplitude\"]\n", " self.name = [\"sin\", \"cos\"]\n", "\n", " def get(self):\n", " \"\"\"Return the value of the gettable.\"\"\"\n", " return np.array([np.sin(t() / np.pi), np.cos(t() / np.pi)])\n", "\n", " # N.B. the optional prepare and finish methods are omitted in this Gettable.\n", "\n", "\n", "# verify compliance with the Gettable format\n", "wave_gettable = DualWave1D()\n", "Gettable(wave_gettable)" ] }, { "cell_type": "markdown", "id": "f2c664b8", "metadata": {}, "source": [ "(sec-batched-and-batch-size)=\n", "## .batched and .batch_size\n", "\n", "The {py:class}`.Gettable` and {py:class}`.Settable` objects can have a `bool` property\n", "`.batched` (defaults to `False` if not present); and an `int` property `.batch_size`.\n", "\n", "Setting the `.batched` property to `True` enables the *batched control code**\n", "in the {class}`.MeasurementControl`. In this mode, if present,\n", "the `.batch_size` attribute is used to determine the maximum size of a batch of\n", "setpoints, that can be set.\n", "\n", "```{admonition} Heterogeneous batch size and effective batch size\n", ":class: dropdown, note\n", "\n", "The minimum `.batch_size` among all settables and gettables will determine the\n", "(maximum) size of a batch.\n", "During execution of a measurement the size of a batch will be reduced if necessary\n", "to comply to the setpoints grid and/or total number of setpoints.\n", "```\n", "\n", "## .prepare() and .finish()\n", "\n", "Optionally the {meth}`!.prepare` and {meth}`!.finish` can be added.\n", "These methods can be used to set up and teardown work.\n", "For example, arming a piece of hardware with data and then closing a connection upon\n", "completion.\n", "\n", "The {meth}`!.finish` runs once at the end of an experiment.\n", "\n", "For `settables`, {meth}`!.prepare` runs once **before the start of a measurement**.\n", "\n", "For batched `gettables`, {meth}`!.prepare` runs **before the measurement of each batch**.\n", "For iterative `gettables`, the {meth}`!.prepare` runs before each loop counting towards\n", "soft-averages \\[controlled by {meth}`!meas_ctrl.soft_avg()` which resets to `1`\n", "at the end of each experiment\\].\n", "\n", "(data-storage)=\n", "# Data storage\n", "\n", "Along with the produced dataset, every {class}`~qcodes.parameters.Parameter`\n", "attached to QCoDeS {class}`~qcodes.instrument.Instrument` in an experiment run through\n", "the {class}`.MeasurementControl` of Quantify is stored in the [snapshot].\n", "\n", "This is intended to aid with reproducibility, as settings from a past experiment can\n", "easily be reloaded \\[see {func}`~quantify_core.utilities.experiment_helpers.load_settings_onto_instrument`\\].\n", "\n", "## Data Directory\n", "The top-level directory in the file system where output is saved to.\n", "This directory can be controlled using the {meth}`~quantify_core.data.handling.get_datadir`\n", "and {meth}`~quantify_core.data.handling.set_datadir` functions.\n", "We recommend changing the default directory when starting the python kernel\n", "(after importing {mod}`quantify_core`) and settling for a single common data directory\n", "for all notebooks/experiments within your measurement setup/PC\n", "(e.g., {code}`D:\\\\quantify-data`).\n", "\n", "`quantify-core` provides utilities to find/search and extract data,\n", "which expects all your experiment containers to be located within the same directory\n", "(under the corresponding date subdirectory).\n", "\n", "Within the data directory experiments are first grouped by date -\n", "all experiments which take place on a certain date will be saved together in a\n", "subdirectory in the form `YYYYmmDD`.\n", "\n", "## Experiment Container\n", "\n", "Individual experiments are saved to their own subdirectories (of the Data Directory)\n", "named based on the {class}`~quantify_core.data.types.TUID` and the\n", "{code}``.\n", "\n", "```{note}\n", "TUID: A Time-based Unique ID is of the form\n", "{code}`YYYYmmDD-HHMMSS-sss-` and these subdirectories'\n", "names take the form\n", "{code}`YYYYmmDD-HHMMSS-sss-<-experiment name (if any)>`.\n", "```\n", "\n", "These subdirectories are termed 'Experiment Containers', with a typical output being the\n", "Dataset in hdf5 format and a JSON format file describing Parameters, Instruments and such.\n", "\n", "Furthermore, additional analyses such as fits can also be written to this directory,\n", "storing all data in one location." ] }, { "cell_type": "code", "execution_count": null, "id": "7b2ae1a2", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "with tempfile.TemporaryDirectory() as tmpdir:\n", " old_dir = dh.get_datadir()\n", " dh.set_datadir(Path(tmpdir) / \"quantify-data\")\n", " # we generate a dummy dataset and a few empty dirs for pretty printing\n", " (Path(dh.get_datadir()) / \"20210301\").mkdir()\n", " (Path(dh.get_datadir()) / \"20210428\").mkdir()\n", "\n", " quantify_dataset = mk_2d_dataset_v1()\n", " ba.BasicAnalysis(dataset=quantify_dataset).run()\n", " dh.set_datadir(old_dir)" ] }, { "cell_type": "markdown", "id": "f47598b4", "metadata": {}, "source": [ "An experiment container within a data directory with the name `\"quantify-data\"`\n", "thus will look similar to:\n", "\n", "```{code-block}\n", "quantify-data/\n", "├── 20210301/\n", "├── 20210428/\n", "└── 20230125/\n", " └── 20230125-172802-085-874812-my experiment/\n", " ├── analysis_BasicAnalysis/\n", " │ ├── dataset_processed.hdf5\n", " │ ├── figs_mpl/\n", " │ │ ├── Line plot x0-y0.png\n", " │ │ ├── Line plot x0-y0.svg\n", " │ │ ├── Line plot x1-y0.png\n", " │ │ └── Line plot x1-y0.svg\n", " │ └── quantities_of_interest.json\n", " └── dataset.hdf5\n", "```\n", "\n", "## Dataset\n", "\n", "The Dataset is implemented with a **specific** convention using the\n", "{class}`xarray.Dataset` class.\n", "\n", "`quantify-core` arranges data along two types of axes: `X` and `Y`.\n", "In each dataset there will be *n* `X`-type axes and *m* `Y`-type axes.\n", "For example, the dataset produced in an experiment where we sweep 2 parameters (settables)\n", "and measure 3 other parameters (all 3 returned by a Gettable),\n", "we will have *n* = 2 and *m* = 3.\n", "Each `X` axis represents a dimension of the setpoints provided.\n", "The `Y` axes represent the output of the Gettable.\n", "Each axis type are numbered ascending from 0\n", "(e.g. {code}`x0`, {code}`x1`, {code}`y0`, {code}`y1`, {code}`y2`),\n", "and each stores information described by the {class}`.Settable` and {class}`.Gettable`\n", "classes, such as titles and units.\n", "The Dataset object also stores some further metadata,\n", "such as the {class}`~quantify_core.data.types.TUID` of the experiment which it was\n", "generated from.\n", "\n", "For example, consider an experiment varying time and amplitude against a Cosine function.\n", "The resulting dataset will look similar to the following:" ] }, { "cell_type": "code", "execution_count": null, "id": "28b3dda9", "metadata": {}, "outputs": [], "source": [ "# plot the columns of the dataset\n", "_, axs = plt.subplots(3, 1, sharex=True)\n", "xr.plot.line(quantify_dataset.x0[:54], label=\"x0\", ax=axs[0], marker=\".\")\n", "xr.plot.line(quantify_dataset.x1[:54], label=\"x1\", ax=axs[1], color=\"C1\", marker=\".\")\n", "xr.plot.line(quantify_dataset.y0[:54], label=\"y0\", ax=axs[2], color=\"C2\", marker=\".\")\n", "tuple(ax.legend() for ax in axs)\n", "# return the dataset\n", "quantify_dataset" ] }, { "cell_type": "markdown", "id": "9458bbc1", "metadata": {}, "source": [ "### Associating dimensions to coordinates\n", "\n", "To support both gridded and non-gridded data, we use {doc}`Xarray `\n", "using only `Data Variables` and `Coordinates` **with a single** `Dimension`\n", "(corresponding to the order of the setpoints).\n", "\n", "This is necessary as in the non-gridded case the dataset will be a perfect sparse array, the usability of which is cumbersome.\n", "A prominent example of non-gridded use-cases can be found {ref}`adaptive-tutorial`.\n", "\n", "To allow for some of Xarray's more advanced functionality,\n", "such as the in-built graphing or query system we provide a dataset conversion utility\n", "{func}`~quantify_core.data.handling.to_gridded_dataset`.\n", "This function reshapes the data and associates dimensions to the dataset\n", "\\[which can also be used for 1D datasets\\]." ] }, { "cell_type": "code", "execution_count": null, "id": "a811cde0", "metadata": {}, "outputs": [], "source": [ "gridded_dset = dh.to_gridded_dataset(quantify_dataset)\n", "gridded_dset.y0.plot()\n", "gridded_dset" ] }, { "cell_type": "markdown", "id": "dc580ef9", "metadata": {}, "source": [ "## Snapshot\n", "\n", "The configuration for each QCoDeS {class}`~qcodes.instrument.Instrument`\n", "used in this experiment.\n", "This information is automatically collected for all Instruments in use.\n", "It is useful for quickly reconstructing a complex set-up or verifying that\n", "{class}`~qcodes.parameters.Parameter` objects are as expected.\n", "\n", "(analysis-usage)=\n", "\n", "# Analysis\n", "\n", "To aid with data analysis, quantify comes with an {mod}`~quantify_core.analysis` module\n", "containing a base data-analysis class\n", "({class}`~quantify_core.analysis.base_analysis.BaseAnalysis`)\n", "that is intended to serve as a template for analysis scripts\n", "and several standard analyses such as\n", "the {class}`~quantify_core.analysis.base_analysis.BasicAnalysis`,\n", "the {class}`~quantify_core.analysis.base_analysis.Basic2DAnalysis`\n", "and the {class}`~quantify_core.analysis.spectroscopy_analysis.ResonatorSpectroscopyAnalysis`.\n", "\n", "The idea behind the analysis class is that most analyses follow a common structure\n", "consisting of steps such as data extraction, data processing, fitting to some model,\n", "creating figures, and saving the analysis results.\n", "\n", "To showcase the analysis usage we generate a dataset that we would like to analyze." ] }, { "cell_type": "code", "execution_count": null, "id": "b2dd231c", "metadata": { "mystnb": { "code_prompt_show": "Example cosine instrument source code" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "display_source_code(mk_cosine_instrument)" ] }, { "cell_type": "code", "execution_count": null, "id": "50c33548", "metadata": { "mystnb": { "code_prompt_show": "Generating a dataset labeled \"Cosine experiment\"" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "pars = mk_cosine_instrument()\n", "meas_ctrl.settables(pars.t)\n", "meas_ctrl.setpoints(np.linspace(0, 2, 50))\n", "meas_ctrl.gettables(pars.sig)\n", "dataset = meas_ctrl.run(\"Cosine experiment\")\n", "dataset" ] }, { "cell_type": "markdown", "id": "af91bdd1", "metadata": {}, "source": [ "## Using an analysis class\n", "\n", "Running an analysis is very simple:" ] }, { "cell_type": "code", "execution_count": null, "id": "48293fd1", "metadata": {}, "outputs": [], "source": [ "a_obj = ca.CosineAnalysis(label=\"Cosine experiment\")\n", "a_obj.run() # execute the analysis.\n", "a_obj.display_figs_mpl() # displays the figures created in previous step." ] }, { "cell_type": "markdown", "id": "e44aff92", "metadata": {}, "source": [ "The analysis was executed against the last dataset that has the label\n", "`\"Cosine experiment\"` in the filename.\n", "\n", "After the analysis the experiment container will look similar to the following:\n", "\n", "```{code-block}\n", "20230125-172804-537-f4f73e-Cosine experiment/\n", "├── analysis_CosineAnalysis/\n", "│ ├── dataset_processed.hdf5\n", "│ ├── figs_mpl/\n", "│ │ ├── cos_fit.png\n", "│ │ └── cos_fit.svg\n", "│ ├── fit_results/\n", "│ │ └── cosine.txt\n", "│ └── quantities_of_interest.json\n", "├── dataset.hdf5\n", "└── snapshot.json\n", "```\n", "\n", "The analysis object contains several useful methods and attributes such as the\n", "{code}`quantities_of_interest`, intended to store relevant quantities extracted\n", "during analysis, and the processed dataset.\n", "For example, the fitted frequency and amplitude are saved as:" ] }, { "cell_type": "code", "execution_count": null, "id": "c5912376", "metadata": {}, "outputs": [], "source": [ "freq = a_obj.quantities_of_interest[\"frequency\"]\n", "amp = a_obj.quantities_of_interest[\"amplitude\"]\n", "print(f\"frequency {freq}\")\n", "print(f\"amplitude {amp}\")" ] }, { "cell_type": "markdown", "id": "e4c1d08f", "metadata": {}, "source": [ "The use of these methods and attributes is described in more detail in\n", "{ref}`analysis-framework-tutorial`.\n", "\n", "## Creating a custom analysis class\n", "\n", "The analysis steps and their order of execution are determined by the\n", "{attr}`~quantify_core.analysis.base_analysis.BaseAnalysis.analysis_steps` attribute\n", "as an {class}`~enum.Enum` ({class}`~quantify_core.analysis.base_analysis.AnalysisSteps`).\n", "The corresponding steps are implemented as methods of the analysis class.\n", "An analysis class inheriting from the abstract-base-class\n", "({class}`~quantify_core.analysis.base_analysis.BaseAnalysis`)\n", "will only have to implement those methods that are unique to the custom analysis.\n", "Additionally, if required, a customized analysis flow can be specified by assigning it\n", "to the {attr}`~quantify_core.analysis.base_analysis.BaseAnalysis.analysis_steps` attribute.\n", "\n", "The simplest example of an analysis class is the\n", "{class}`~quantify_core.analysis.base_analysis.BasicAnalysis`\n", "that only implements the\n", "{meth}`~quantify_core.analysis.base_analysis.BasicAnalysis.create_figures` method\n", "and relies on the base class for data extraction and saving of the figures.\n", "\n", "Take a look at the source code (also available in the API reference):" ] }, { "cell_type": "code", "execution_count": null, "id": "b50ee2fe", "metadata": { "mystnb": { "code_prompt_show": "BasicAnalysis source code" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "display_source_code(ba.BasicAnalysis)" ] }, { "cell_type": "markdown", "id": "2050289e", "metadata": {}, "source": [ "A slightly more complex use case is the\n", "{class}`~quantify_core.analysis.spectroscopy_analysis.ResonatorSpectroscopyAnalysis`\n", "that implements\n", "{meth}`~quantify_core.analysis.spectroscopy_analysis.ResonatorSpectroscopyAnalysis.process_data`\n", "to cast the data to a complex-valued array,\n", "{meth}`~quantify_core.analysis.spectroscopy_analysis.ResonatorSpectroscopyAnalysis.run_fitting`\n", "where a fit is performed using a model\n", "(from the {mod}`quantify_core.analysis.fitting_models` library), and\n", "{meth}`~quantify_core.analysis.spectroscopy_analysis.ResonatorSpectroscopyAnalysis.create_figures`\n", "where the data and the fitted curve are plotted together.\n", "\n", "Creating a custom analysis for a particular type of dataset is showcased in the\n", "{ref}`analysis-framework-tutorial`.\n", "There you will also learn some other capabilities of the analysis and practical\n", "productivity tips.\n", "\n", "```{seealso}\n", "{ref}`Analysis API documentation ` and\n", "{ref}`tutorial on building custom analyses `.\n", "```\n", "\n", "# Examples: Settables and Gettables\n", "\n", "Below we give several examples of experiments that use Settables and Gettables in different control modes.\n", "\n", "## Iterative control mode\n", "\n", "### Single-float-valued settable(s) and gettable(s)\n", "\n", "- Each settable accepts a single float value.\n", "- Gettables return a single float value." ] }, { "cell_type": "code", "execution_count": null, "id": "3560a62c", "metadata": { "mystnb": { "code_prompt_show": "1D example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time = ManualParameter(\n", " name=\"time\", label=\"Time\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "signal = Parameter(\n", " name=\"sig_a\", label=\"Signal\", unit=\"V\", get_cmd=lambda: np.cos(time())\n", ")\n", "\n", "meas_ctrl.settables(time)\n", "meas_ctrl.gettables(signal)\n", "meas_ctrl.setpoints(np.linspace(0, 7, 20))\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "dset_grid.y0.plot(marker=\"o\")\n", "dset_grid" ] }, { "cell_type": "code", "execution_count": null, "id": "db03cbc3", "metadata": { "mystnb": { "code_prompt_show": "2D example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time_a = ManualParameter(\n", "name=\"time_a\", label=\"Time A\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "time_b = ManualParameter(\n", "name=\"time_b\", label=\"Time B\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "signal = Parameter(\n", "name=\"sig_a\",\n", "label=\"Signal A\",\n", "unit=\"V\",\n", "get_cmd=lambda: np.exp(time_a()) + 0.5 * np.exp(time_b()),\n", ")\n", "\n", "meas_ctrl.settables([time_a, time_b])\n", "meas_ctrl.gettables(signal)\n", "meas_ctrl.setpoints_grid([np.linspace(0, 5, 10), np.linspace(5, 0, 12)])\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "dset_grid.y0.plot(cmap=\"viridis\")\n", "dset_grid" ] }, { "cell_type": "markdown", "id": "875b7694", "metadata": {}, "source": [ "For more dimensions, you only need to pass more settables and the corresponding setpoints." ] }, { "cell_type": "code", "execution_count": null, "id": "a1b8cbe1", "metadata": { "mystnb": { "code_prompt_show": "1D adaptive example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time = ManualParameter(\n", " name=\"time\", label=\"Time\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "signal = Parameter(\n", " name=\"sig_a\", label=\"Signal\", unit=\"V\", get_cmd=lambda: np.cos(time())\n", ")\n", "meas_ctrl.settables(time)\n", "meas_ctrl.gettables(signal)\n", "dset = meas_ctrl.run_adaptive(\"1D minimizer\", {\"adaptive_function\": minimize_scalar})\n", "\n", "dset_ad = dh.to_gridded_dataset(dset)\n", "# add a grey cosine for reference\n", "x = np.linspace(np.min(dset_ad[\"x0\"]), np.max(dset_ad[\"x0\"]), 101)\n", "y = np.cos(x)\n", "plt.plot(x, y, c=\"grey\", ls=\"--\")\n", "_ = dset_ad.y0.plot(marker=\"o\")" ] }, { "cell_type": "markdown", "id": "cbd2af86", "metadata": {}, "source": [ "### Single-float-valued settable(s) with multiple float-valued gettable(s)\n", "\n", "- Each settable accepts a single float value.\n", "- Gettables return a 1D array of floats, with each element corresponding to a *different Y dimension*.\n", "\n", "We exemplify a 2D case, however, there is no limitation on the number of settables." ] }, { "cell_type": "code", "execution_count": null, "id": "cdd4275d", "metadata": { "mystnb": { "code_prompt_show": "2D example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time_a = ManualParameter(\n", " name=\"time_a\", label=\"Time A\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "time_b = ManualParameter(\n", " name=\"time_b\", label=\"Time B\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "\n", "signal = Parameter(\n", " name=\"sig_a\",\n", " label=\"Signal A\",\n", " unit=\"V\",\n", " get_cmd=lambda: np.exp(time_a()) + 0.5 * np.exp(time_b()),\n", ")\n", "\n", "\n", "class DualWave2D:\n", " \"\"\"A \"dual\" gettable example that depends on two settables.\"\"\"\n", "\n", " def __init__(self):\n", " self.unit = [\"V\", \"V\"]\n", " self.label = [\"Sine Amplitude\", \"Cosine Amplitude\"]\n", " self.name = [\"sin\", \"cos\"]\n", "\n", " def get(self):\n", " \"\"\"Returns the value of the gettable.\"\"\"\n", " return np.array([np.sin(time_a() * np.pi), np.cos(time_b() * np.pi)])\n", "\n", "\n", "dual_wave = DualWave2D()\n", "meas_ctrl.settables([time_a, time_b])\n", "meas_ctrl.gettables([signal, dual_wave])\n", "meas_ctrl.setpoints_grid([np.linspace(0, 3, 21), np.linspace(4, 0, 20)])\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "for yi, cmap in zip((\"y0\", \"y1\", \"y2\"), (\"viridis\", \"inferno\", \"plasma\")):\n", " dset_grid[yi].plot(cmap=cmap)\n", " plt.show()\n", "dset_grid" ] }, { "cell_type": "markdown", "id": "350a94b2", "metadata": {}, "source": [ "## Batched control mode\n", "\n", "### Float-valued array settable(s) and gettable(s)\n", "\n", "- Each settable accepts a 1D array of float values corresponding to all setpoints for a single *X dimension*.\n", "- Gettables return a 1D array of float values with each element corresponding to a datapoint *in a single Y dimension*." ] }, { "cell_type": "code", "execution_count": null, "id": "0f387ca7", "metadata": { "mystnb": { "code_prompt_show": "2D example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time = ManualParameter(\n", " name=\"time\",\n", " label=\"Time\",\n", " unit=\"s\",\n", " vals=validators.Arrays(),\n", " initial_value=np.array([1, 2, 3]),\n", ")\n", "signal = Parameter(\n", " name=\"sig_a\", label=\"Signal\", unit=\"V\", get_cmd=lambda: np.cos(time())\n", ")\n", "\n", "time.batched = True\n", "signal.batched = True\n", "\n", "meas_ctrl.settables(time)\n", "meas_ctrl.gettables(signal)\n", "meas_ctrl.setpoints(np.linspace(0, 7, 20))\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "dset_grid.y0.plot(marker=\"o\")\n", "print(f\"\\nNOTE: The gettable returns an array:\\n\\n{signal.get()}\")\n", "dset_grid" ] }, { "cell_type": "markdown", "id": "d1cb7ae4", "metadata": {}, "source": [ "### Mixing iterative and batched settables\n", "\n", "In this case:\n", "\n", "- One or more settables accept a 1D array of float values corresponding to all setpoints for the corresponding *X dimension*.\n", "- One or more settables accept a float value corresponding to its *X dimension*.\n", "\n", "Measurement control will set the value of each of these iterative settables before each batch." ] }, { "cell_type": "code", "execution_count": null, "id": "8a94ebbc", "metadata": { "mystnb": { "code_prompt_show": "2D (1D batch with iterative outer loop) example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time_a = ManualParameter(\n", " name=\"time_a\", label=\"Time A\", unit=\"s\", vals=validators.Numbers(), initial_value=1\n", ")\n", "time_b = ManualParameter(\n", " name=\"time_b\",\n", " label=\"Time B\",\n", " unit=\"s\",\n", " vals=validators.Arrays(),\n", " initial_value=np.array([1, 2, 3]),\n", ")\n", "signal = Parameter(\n", " name=\"sig_a\",\n", " label=\"Signal A\",\n", " unit=\"V\",\n", " get_cmd=lambda: np.exp(time_a()) + 0.5 * np.exp(time_b()),\n", ")\n", "\n", "time_b.batched = True\n", "time_b.batch_size = 12\n", "signal.batched = True\n", "\n", "meas_ctrl.settables([time_a, time_b])\n", "meas_ctrl.gettables(signal)\n", "# `setpoints_grid` will take into account the `.batched` attribute\n", "meas_ctrl.setpoints_grid([np.linspace(0, 5, 10), np.linspace(4, 0, time_b.batch_size)])\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "dset_grid.y0.plot(cmap=\"viridis\")\n", "dset_grid" ] }, { "cell_type": "markdown", "id": "f8a85881", "metadata": {}, "source": [ "### Float-valued array settable(s) with multi-return float-valued array gettable(s)\n", "\n", "- Each settable accepts a 1D array of float values corresponding to all setpoints\n", " for a single *X dimension*.\n", "- Gettables return a 2D array of float values with each row representing a\n", " *different Y dimension*, i.e. each column is a datapoint corresponding\n", " to each setpoint." ] }, { "cell_type": "code", "execution_count": null, "id": "e1f7a1c4", "metadata": { "mystnb": { "code_prompt_show": "1D example" }, "tags": [ "hide-cell" ] }, "outputs": [], "source": [ "time = ManualParameter(\n", " name=\"time\",\n", " label=\"Time\",\n", " unit=\"s\",\n", " vals=validators.Arrays(),\n", " initial_value=np.array([1, 2, 3]),\n", ")\n", "\n", "\n", "class DualWaveBatched:\n", " \"\"\"A \"dual\" batched gettable example.\"\"\"\n", "\n", " def __init__(self):\n", " self.unit = [\"V\", \"V\"]\n", " self.label = [\"Amplitude W1\", \"Amplitude W2\"]\n", " self.name = [\"sine\", \"cosine\"]\n", " self.batched = True\n", " self.batch_size = 100\n", "\n", " def get(self):\n", " \"\"\"Returns the value of the gettable.\"\"\"\n", " return np.array([np.sin(time() * np.pi), np.cos(time() * np.pi)])\n", "\n", "\n", "time.batched = True\n", "dual_wave = DualWaveBatched()\n", "\n", "meas_ctrl.settables(time)\n", "meas_ctrl.gettables(dual_wave)\n", "meas_ctrl.setpoints(np.linspace(0, 7, 100))\n", "dset = meas_ctrl.run(\"my experiment\")\n", "dset_grid = dh.to_gridded_dataset(dset)\n", "\n", "_, ax = plt.subplots()\n", "dset_grid.y0.plot(marker=\"o\", label=\"y0\", ax=ax)\n", "dset_grid.y1.plot(marker=\"s\", label=\"y1\", ax=ax)\n", "_ = ax.legend()" ] } ], "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.8.16" } }, "nbformat": 4, "nbformat_minor": 5 }