diff --git a/docs/examples/DataSet/MeasurementLoop.ipynb b/docs/examples/DataSet/MeasurementLoop.ipynb new file mode 100644 index 00000000000..dede2b3b93d --- /dev/null +++ b/docs/examples/DataSet/MeasurementLoop.ipynb @@ -0,0 +1,1403 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# MeasurementLoop" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook introduces the `MeasurementLoop`, which is one of three methods used to perform measurements. The three measurement methods are in increasing levels of complexity:\n", + "\n", + "- `qcodes.dataset.do_nd.dond` and its variations `do1d` and `do2d`. \n", + " These are function calls that perform basic N-dimensional sweeps, measuring a list of parameters in the innermost loop. \n", + " It is a wrapper around the `Measurement` class\n", + "- `qcodes.dataset.measurement_loop.MeasurementLoop` can perform more complex measurements, including conditional measurements, nested measurements. It can perform arbitrary python code in a measurement. \n", + "The `MeasurementLoop` relies on a fixed order in which parameters are measured (examples below). This fixed order reduces the amount of explicit code needed.\n", + "It is a wrapper around the `Measurement` class\n", + "- `qcodes.dataset.measurements.Measurement` is the most explicit type of measurement. \n", + "All parameters that are swept / measured must be explicitly registered before the measurement starts, as well as preferably their array shapes. \n", + "The `Measurement` can perform arbitrary python code. It further allows parameters to be measured in arbitrary order.\n", + "\n", + "The `MeasurementLoop` therefore lies in complexity between the `dond` and `Measurement`, and should be able to meet the majority of measurement needs while minimizing the amount of explicit definitions. For example, in contrast to `Measurement`, the `MeasurementLoop` does not need any parameters to be registered.\n", + "\n", + "We will start with basic examples of the `MeasurementLoop` and then go over some more advanced features " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic measurement" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import time\n", + "\n", + "from qcodes.dataset import (\n", + " MeasurementLoop, \n", + " Sweep,\n", + " initialise_or_create_database_at, \n", + " load_or_create_experiment\n", + ")\n", + "from qcodes.instrument import Parameter, ManualParameter\n", + "\n", + "initialise_or_create_database_at('database.db')\n", + "load_or_create_experiment('measurement_loop_experiment');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We start by creating a set parameter and a get parameter that returns a random value" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "random_parameter()=0.42399278190478207\n" + ] + } + ], + "source": [ + "set_parameter = ManualParameter('set_parameter')\n", + "random_parameter = Parameter('random_parameter', get_cmd=np.random.rand)\n", + "print(f'{random_parameter()=}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now perform a basic measurement: sweeping one parameter (`set_parameter`) and measuring another (`random_parameter`):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 2. \n", + "Finished measurement\n" + ] + } + ], + "source": [ + "with MeasurementLoop('basic_measurement') as msmt:\n", + " for val in Sweep(set_parameter, start=0, stop=10, num=11):\n", + " msmt.measure(random_parameter)\n", + "\n", + "print('Finished measurement')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's break this code down line-by-line.\n", + "```Python\n", + "with MeasurementLoop('basic_measurement') as msmt:\n", + "``` \n", + "> Here the `with` statement instantiates the `MeasurementLoop` inside a context manager. Everything inside this block is part of the measurement. We use the variable `msmt` to refer to the instantiated `MeasurementLoop` as we will use it to measure parameters later on. \n", + "> We're also supposed to give the measurement a name, in this case `'basic_measurement'`\n", + "\n", + "```Python\n", + "for set_val in Sweep(set_parameter, start=0, stop=10, num=11):\n", + "```\n", + "> We use a `Sweep` object to register in the `MeasurementLoop` that we want to sweep `set_parameter` with 11 points spaced between 0 and 10. The `MeasurementLoop` now also knows that everything inside this loop has a dimension of (11, ). \n", + "> Notice that we are using a standard python loop, so we can access the iterated values `set_val`. \n", + "> Notice also that the value of `set_parameter` is being updated as we sweep over it.\n", + "\n", + "```Python\n", + "msmt.measure(random_parameter)\n", + "```\n", + "> Here we measure `random_parameter` inside the sweep. The `MeasurementLoop` automatically registers `random_parameter` once it's measured for the first time." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comparison to dond" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since this is a very basic measurement, it can also performed in less code using `dond` as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 3. Using 'qcodes.dataset.dond'\n" + ] + } + ], + "source": [ + "from qcodes.dataset.do_nd import dond, LinSweep\n", + "dataset = dond(LinSweep(set_parameter, 0, 10, 11), random_parameter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It can even be performed by the simpler do1d:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 4. Using 'qcodes.dataset.do1d'\n" + ] + } + ], + "source": [ + "from qcodes.dataset.do_nd import do1d\n", + "dataset = do1d(set_parameter, 0, 10, 11, 0, random_parameter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this point you might wonder what the use is of `MeasurementLoop`. The point is that the `MeasurementLoop` can also perform significantly more complex types of measurements, as we will go into later." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comparison to `Measurement`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The same basic measurement can also be performed by the `Measurement` class as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 5. \n" + ] + } + ], + "source": [ + "from qcodes.dataset.measurements import Measurement\n", + "context_meas = Measurement(name='basic_measurement_Measurement_class')\n", + "\n", + "# Register the independent parameter...\n", + "context_meas.register_parameter(set_parameter)\n", + "# ...then register the dependent parameter\n", + "context_meas.register_parameter(random_parameter, setpoints=(set_parameter,))\n", + "\n", + "with context_meas.run() as datasaver:\n", + " for set_v in np.linspace(0, 10, 11):\n", + " set_parameter(set_v)\n", + " get_v = random_parameter()\n", + " datasaver.add_result((set_parameter, set_v),\n", + " (random_parameter, get_v))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are some clear differences with the code of the `MeasurementLoop`. - All set/get parameters involved in the measurement need to be registered beforehand, as well as their relation.\n", + "- When a parameter is swept over, it needs to be explicitly set.\n", + "- Any parameter that is measured also needs to be added, along with the corresponding set value(s).\n", + "\n", + "These differences all make the `Measurement` more explicit than both the `MeasurementLoop` and `dond/do1d/do2d`. On the one hand, this makes it more cumbersome to write a measurement. But on the other hand, this allows greater flexibility. For example, we could have set `set_parameter` to another value instead of the iterated value `set_v`; this would not have been possible using the other methods.\n", + "\n", + "The `MeasurementLoop` is supposed to lie somewhere in between `dond` and `Measurement`, enabling a wide variety of measurements while requiring a minimal amount of explicit code to be written." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A more complex measurement example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we perform a slightly more complex measurement. We perform a 2D sweep and again measure `random_parameter` inside it. However, we now count the number of times that it is above 0.5. Each time it is above 0.5, we sleep for 100 ms. after each inner loop we register how many times it was above 0.5." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 6. \n" + ] + } + ], + "source": [ + "with MeasurementLoop('conditional_measurement_example') as msmt:\n", + " for set_val1 in Sweep(set_parameter, np.logspace(1, 3, 51)):\n", + " above_half = 0 # We initialize a counter here\n", + "\n", + " # Notice that we don't need to sweep a parameter\n", + " for set_val2 in Sweep(np.arange(10), 'inner_set_parameter'):\n", + " random_val = msmt.measure(random_parameter)\n", + "\n", + " # We increment the counter if the random_val is above 0.5\n", + " if random_val > 0.5:\n", + " above_half += 1\n", + " # Let's also sleep a bit\n", + " time.sleep(0.1)\n", + "\n", + " # Notice that we don't need a parameter to measure this\n", + " msmt.measure(above_half, 'above_half')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from qcodes.dataset.plotting import plot_by_id\n", + "plot_by_id(msmt.dataset.run_id);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen, the measurement code did not increase much in complexity even though we started performing more complex measurements. this measurement cannot be performed using `dond`, though it can still be performed by `Measurement`.\n", + "\n", + "One surprising fact from the above measurement is that we didn't need to define new parameters for the inner sweep and for the measurement recording how many times `random_parameter` is above half. This is a feature of the `MeasurementLoop`: parameters aren't needed for sweeps and measurements! This can significantly simplify creating complex measurements.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Nested measurements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One big feature of the `MeasurementLoop` is that one can nest measurements. This is largely because we don't have to define our parameters beforehand. Here we show an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 7. \n" + ] + } + ], + "source": [ + "with MeasurementLoop('outer_measurement') as outer_msmt:\n", + " for set_val1 in Sweep(range(10), 'outer_sweep'):\n", + "\n", + " with MeasurementLoop('inner_measurement') as inner_msmt:\n", + " for set_val2 in Sweep(range(10), 'inner_sweep'):\n", + " inner_msmt.measure(random_parameter)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from qcodes.dataset.plotting import plot_by_id\n", + "plot_by_id(outer_msmt.dataset.run_id);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we instantiate the inner measurement, it sees that another measurement is already running, and so it realizes that it is part of this larger measurement and so attaches to it. It will therefore use the dimensionality of the outer measurement.\n", + "\n", + "You can again ask yourself why this is useful. One big reason is that this allows us to functionalize measurements. For example, we can create a function `retune_device()` that performs a complex retuning sequence, in this case finding the minimum of a 2D quadratic:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'scipy'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mc:\\Users\\Serwan\\Documents\\Github\\Qcodes_Sydney\\docs\\examples\\DataSet\\MeasurementLoop.ipynb Cell 30\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mscipy\u001b[39;00m \u001b[39mimport\u001b[39;00m optimize\n\u001b[0;32m 4\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mretune_device\u001b[39m():\n\u001b[0;32m 5\u001b[0m \u001b[39mwith\u001b[39;00m MeasurementLoop(\u001b[39m'\u001b[39m\u001b[39mretune_device\u001b[39m\u001b[39m'\u001b[39m) \u001b[39mas\u001b[39;00m msmt:\n\u001b[0;32m 6\u001b[0m \u001b[39m# Create a random minimal point\u001b[39;00m\n", + "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'scipy'" + ] + } + ], + "source": [ + "from scipy import optimize\n", + "\n", + "\n", + "def retune_device():\n", + " with MeasurementLoop('retune_device') as msmt:\n", + " # Create a random minimal point\n", + " x0 = msmt.measure(2 * (np.random.rand() - 0.5), 'x0')\n", + " y0 = msmt.measure(2 * (np.random.rand() - 0.5), 'y0')\n", + " print(f'{x0=:.3f}, {y0=:.3f}', end=', \\t')\n", + "\n", + " minimization_function = lambda x: (x[0] - x0)**2 + (x[1] - y0)**2\n", + "\n", + " intermediary_results = []\n", + " max_iter = 100\n", + " optimize.minimize(\n", + " minimization_function, \n", + " x0=(100*np.random.rand(), 100*np.random.rand()),\n", + " options={'maxiter': max_iter},\n", + " callback=intermediary_results.append\n", + " )\n", + "\n", + " for k in Sweep(range(max_iter), 'iteration'):\n", + " if k >= len(intermediary_results):\n", + " msmt.step_out() # See section \"Fixed measurement order\"\n", + " break\n", + "\n", + " msmt.measure(intermediary_results[k][0], 'x')\n", + " msmt.measure(intermediary_results[k][1], 'y')\n", + " msmt.measure(x0 - intermediary_results[k][0], 'x_error')\n", + " msmt.measure(y0 - intermediary_results[k][1], 'y_error')\n", + " \n", + " print(\n", + " f'x_error = {x0 - intermediary_results[-1][0]:.4g}, '\n", + " f'y_error = {y0 - intermediary_results[-1][1]:.4g}')\n", + " return msmt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we perform the retuning sequence and plot the results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 31. \n", + "x0=-0.676, y0=0.351, \tx_error = -6.712e-08, y_error = -6.954e-08\n" + ] + } + ], + "source": [ + "msmt = retune_device()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "dataset = msmt.dataset.to_xarray_dataarray_dict()\n", + "from matplotlib import pyplot as plt\n", + "fig, axes = plt.subplots(1, 2, figsize=(10,4))\n", + "ax = axes[0]\n", + "ax.plot(dataset['x0'], dataset['y0'], '*', ms=20, label='Target', color='C8')\n", + "for k, (x, y) in enumerate(zip(dataset['x'], dataset['y']), start=1):\n", + " ax.plot(x, y, 'o', label=f'Guess {k}')\n", + "ax.legend()\n", + "ax.set_xlabel('X')\n", + "ax.set_ylabel('Y')\n", + "ax.grid('on')\n", + "\n", + "ax = axes[1]\n", + "for k, (x, y) in enumerate(zip(dataset['x_error'], dataset['y_error']), start=1):\n", + " ax.plot(x, y, 'o', label=f'Guess {k}', color=f'C{k}')\n", + "ax.legend()\n", + "ax.set_xlabel('X error')\n", + "ax.set_ylabel('Y error')\n", + "ax.grid('on')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, since we can nest measurements, we can simply incorporate it as a function in another measurement:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 32. \n", + "x0=0.606, y0=-0.378, \tx_error = 1.036e-07, y_error = 1.664e-06\n", + "x0=-0.908, y0=-0.610, \tx_error = -2.148e-08, y_error = -4.82e-09\n", + "x0=-0.821, y0=0.242, \tx_error = -1.613e-06, y_error = 2.737e-06\n", + "x0=-0.090, y0=0.307, \tx_error = 2.077e-06, y_error = -4.658e-06\n", + "x0=-0.927, y0=0.470, \tx_error = 1.126e-08, y_error = 7.831e-08\n", + "x0=0.364, y0=-0.038, \tx_error = -1.531e-07, y_error = -2.273e-07\n", + "x0=-0.878, y0=0.987, \tx_error = 1.254e-06, y_error = 5.796e-07\n", + "x0=0.754, y0=-0.254, \tx_error = 2.951e-07, y_error = 3.297e-07\n", + "x0=0.855, y0=0.030, \tx_error = 2.321e-07, y_error = 1.216e-06\n", + "x0=0.198, y0=-0.337, \tx_error = 8.037e-08, y_error = 7.971e-08\n", + "x0=0.445, y0=0.292, \tx_error = 4.155e-09, y_error = 4.147e-09\n" + ] + } + ], + "source": [ + "with MeasurementLoop('measurement_with_retuning') as msmt:\n", + " for k in range(11):\n", + " result = retune_device()\n", + "\n", + " msmt.measure(random_parameter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ability to turn a measurement into a function that can then be used within other measurements allows the experimentalist to modularize measurements. This can help once measurements become more complex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fixed measurement order of parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One of the main reasons why the `MeasurementLoop` is able to make so much of the code implicit is because it assumes a fixed order in which parameters are swept / measured. This needs to be adhered to, or things can break. This restriction can be illustrated with the following example.\n", + "\n", + "For this example we first use the `Measurement`. We sweep a parameter from 0 to 10 in integer steps. If the integer is odd, we measure `random_parameter` which returns a random value. However, if it's even, we measure `fixed_parameter` which always returns 42.\n", + "\n", + "Importantly, the first parameter that is being measured in every sweep iteration changes between `random_parameter` and `fixed_parameter`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 33. \n" + ] + } + ], + "source": [ + "from qcodes.dataset.measurements import Measurement\n", + "context_meas = Measurement(name='varied_parameter_order_measurement')\n", + "\n", + "# Register the independent parameter...\n", + "context_meas.register_parameter(set_parameter)\n", + "# ...then register the dependent parameter\n", + "context_meas.register_parameter(random_parameter, setpoints=(set_parameter,))\n", + "\n", + "# We also add a second parameter that always returns 42\n", + "fixed_parameter = Parameter('fixed_parameter', get_cmd = lambda: 42)\n", + "context_meas.register_parameter(fixed_parameter, setpoints=(set_parameter,))\n", + "\n", + "with context_meas.run() as datasaver:\n", + " for set_v in np.linspace(0, 10, 11):\n", + " set_parameter(set_v)\n", + "\n", + " if set_v % 2:\n", + " get_v = random_parameter()\n", + " datasaver.add_result((set_parameter, set_v),\n", + " (random_parameter, get_v))\n", + " else:\n", + " get_v = fixed_parameter()\n", + " datasaver.add_result((set_parameter, set_v),\n", + " (fixed_parameter, get_v))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that both parameters are measured perfectly fine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX8AAAEXCAYAAABF40RQAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAc1UlEQVR4nO3debgdVZ3u8e+bBCTMKBGBAGFQEUFAo+BF2zTggCAgDsAFBLoRh4ugtoKx+zJctR3aAcRWRFTQIKiIoAgoLQQHBE0YZVADhBkShkAIyJT3/lHrmJ3DyTmV5NTenFPv53n2c2pev9p7n99etWpVlWwTERHtMqbXAURERPcl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn+MWpI+KemUXsfxXCDpzZLO6XUcvSbpWEnTai77E0m7NB1TryT5d4Gk2ZIel/SopHslnSpp1S6Ue5ek8ZJ2lHR2v3mXSJor6RFJ10jao2PeP0u6TtI8SQ9I+qmk9WuWOUmSy752vvYe7v0biu3/tH1It8uVdJCk3w2xzHRJyx2bpCmS7qyx6GeAz3WsZ0mbDUP5tZPpCPR54NO9DqIpSf7d8zbbqwLbANsCU5ssTNIGwAO2HwdeBVzZb5EjgHVtrw4cCkyTtG6ZdwPwZttrAusBfwO+sZQhrGl71Y7XD5d1X5aFpHHdLO+5TNKrgTVsX97rWEYS238EVpc0udexNCHJv8ts3wv8kupHYMCaWzlS2LkMHyvpR5K+J2m+pOtrfhknAzM7hhdL/ravtf103yiwArBBmXef7bs7Fn8GGI5a4oqSrpb0oTI+VtLvJR1dxo+VdJakH5Z9vVLS1h3rr1cOxedKulXS4R3z+tadJukR4KDOWmnHEcnBku6Q9JCk90t6taRry1HO1/rF+y+SbizL/lLSRh3zXNb/W1n3v1V5GXAS8NpyxDNvgPfhM8Drga+VZb5Wpm8u6SJJD0r6i6R3d6zzVkk3lPflLkkfk7QKcAGwXscR1noDvPW7AJd2bOs3ZfCazqMySbuVz2eepMskvaJjnaNKufNLbDtJegvwSWDvsp1rBvn4+46IbinbuFXSfmX6ppIuLkeZ90s6XdKaHevNlvTx8jktkPRtSetIuqBs638krdXvcz5U0t2S7pH0sUFi2r7s6zxVR8BT+i0yHdh1sP0asWzn1fALmA3sXIYnAtcBJ5TxKcCdgyx/LPB34K3AWOCzwOWDlHUMMK+s81gZfgZ4uAyP7Vj2vLKcgQuBMR3zNizLLwSeAg6qua+TyvbGLWH+lsBDwMuAfwcu74up7OtTwDupfow+BtxahsdQ/ZgdDawIbALcQnWE0rnunmXZ8WXatH5xnQSsBLyp7Ps5wAuB9YE5wBvK8nsAs0qc44D/AC7r2A+X92/N8l7NBd5S5h0E/G6I92k6cEjH+CrAHcDBpbxtgfuBLcr8e4DXl+G1gFcu6fszQFk/Bj7eb5qBzTrGty37vx3V9+xAqu/h84CXltjW63gvN+1436fV+F6sAjwCvLSMrwu8vAxvBryxlDUB+A1wfL//h8uBdTo+pytLzCsBFwPH9PuczyhlblU+m537x1u29QDV/9aYEsMDwISOsj8KnN3rHNLEKzX/7jlH0nyqf6I5VEm6rt/ZPt/2M8D3ga2XtKDt44C1qZLmxlRf7Attr2F7zbKNvmV3A1Yry/zK9sKOebe7avZZmyrx3bQU8QLcX2pTfa+Xle3+maod9Ryq5H5AZ0zATNtn2X4K+DLVP/f2wKup/in/n+0nbd8CfAvYp2PdP9g+x/ZCV81dA/mU7b/b/hWwADjD9hzbdwG/pUooAO8HPmv7RldHSP8JbNNZ+wc+Z3ue7duBSyhHc8toN2C27e/aftr2VcBPgHeV+U8BW0ha3fZDtvs34w1mTWD+EMscCnzT9hW2n7F9GvAE1Xv/DFVi3kLSCrZn2755KcrvsxDYUtJ42/fYvh7A9izbF9l+wvZcqs/9Df3WPdHVEWnf53SF7ats/x34KYs+tz7H2V5g+zrgu8C+A8SzP3B++d9aaPsiYAbV/0Of+VTv36iT5N89e9pejaqmtjlVUq3r3o7hx4CVNECbtqRtSjPDQ1S1qb9QJaUpJQHv1X8d20/ZvgB4k6TdB5j/IHAacO5AZQ5i7fJj0/e6sWPeacBGVP94f+u33h0dZS8E7qQ677ARVfPGP35QqJoc1hlo3UHc1zH8+ADjfSfiNwJO6CjrQUBUtcU+/T+X5TmJvxGwXb/92w94UZn/DqqkdJukSyW9dim2/RDVj/xQ5f9bv/I3oKrtzwI+TFVrniPpzCU0Ly2R7QXA3lQ/qvdI+oWkzQFKE86ZpVnpEWAaz/7/qPu59en8LtxG9R3qbyPgXf32+XVURyV9VqM6Ah51kvy7zPalwKnAF8ukBcDKffMljaU69F2WbV9dauufAY4uwzcAW5cEfPYgq48DNh1k3guB1ZclrgF8narJ5M2SXtdv3gZ9A5LGUDWT3U31z3xrvx+U1Wx31tKG8xa1dwDv61feeNuX1Vi3Thz9l7kDuLRfeava/gCA7T/Z3oPqczgH+NFSlHUt8JIhlrkD+Ey/8le2fUYp/we2X0eVME3VE6Zu+ZRt/NL2G6mS601UR25QHVUZ2MpVB4T9qX5ol8cGHcMbUn2H+rsD+H6/fV7F9uc6lnkZMOi5jJEqyb83jgfeqOpk5l+pavK7SlqBqonlecu5/VcBV0pakUU1t38oJxZ3UdUNdAVJ+wP/RDkpKGkvSS+VNEbSBKrD8KvKUUDfydXpyxKYpANKfAcBhwOnafFur68q5Y+jqm0+QdXe+0dgfjnxOF7VyeItVfVkacJJwFRJLy9xryHpXUOs0+c+YGJ5/wdbZpOO8fOAl0g6oHwmK6g6Gf0yVSfK95O0RmkOe4SqCaVvOy+QtMYgZZ3Ps5tR+pf/LeD9krZTZZXynVytfBd2lPQ8qvMkj/crf1L5oV6iUrvfQ9VJ6ieARzu2sVoZf1hVl+KPD7atmv6vpJXL53cwMFBvs2nA21RdAzFW0kqqOmBM7FjmDVQn1UedJP8eKO2a36OqnT8MfBA4BbiL6kigTr/twfR17dwK+PMA80U5hKc6GXYEsHdHO/L6VCeA51OdnF4IvL1j/Q2A3w8Rwzwt3s//o5I2pPrhe4/tR23/gKqN9Ssd651L1TzwEHAAsFdpmnqGql18G6rzGfdTvWeDJb1lZvunVLXbM0tTxJ+pes3UcTFwPXCvpPuXsMwJwDtV9ST6qu35VCeh96Gqpd5byu+rCBwAzC6xvJ+qSQjbN1Gd3LylNF08q3mjfK4PS9quY/KxVD+88yS92/YM4L3A16je+1lUP9CUGD5H9Z7fS3X00ddV+cfl7wOSBjsPMYbq5OndVE1obwA+UOYdB7ySqlPCL4DBjlDrurTsw6+BL5ZzPIuxfQfVif1PUv0f3EH1wzMG/tFF9lFXXT5HHdl5mEssHUlXAzvZfmCYt3ssVQ+U/YdzuwGS3gR80PaevY6lSZImUXqIeVFX5mXd1k+Ab9s+fzhie67JhTCx1Gxv0+sYYumUmu+zar+xZLbf0esYmpTkHxHDRtKjS5i1i+3fdjWYGFSafSIiWignfCMiWijJPyKihUZMm//aa6/tSZMm9TqMiIgRZebMmffbftaFoyMm+U+aNIkZM2b0OoyIiBFF0m0DTU+zT0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLdSX5l0ekXSXpvH7TvzrILWAjIqIh3ar5HwHc2DlB0mRgrS6VHxERHRpP/uVhyLtSPW+1b9pY4L+AI5suPyIinq0bNf/jqZL8wo5phwE/s31PF8qPiIh+Gk3+knYD5tie2TFtPeBdwIk11j9U0gxJM+bOndtgpBER7dL0LZ13AHaX9FZgJWB14HrgCWCWJICVJc2yvVn/lW2fDJwMMHny5DxvMiJimDRa87c91fZE25OAfYCLba9l+0W2J5Xpjw2U+CMiojnp5x8R0UJde5KX7enA9AGmr9qtGCIiopKaf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UFeSv6Sxkq6SdF4Z/7akayRdK+ksSat2I46IiKh0q+Z/BHBjx/hHbG9t+xXA7cBhXYojIiLoQvKXNBHYFTilb5rtR8o8AeMBNx1HREQs0o2a//HAkcDCzomSvgvcC2wOnNiFOCIiomg0+UvaDZhje2b/ebYPBtajag7aewnrHypphqQZc+fObTLUiIhWabrmvwOwu6TZwJnAjpKm9c20/UyZ/o6BVrZ9su3JtidPmDCh4VAjItqj0eRve6rtibYnAfsAFwMHSNoM/tHmvztwU5NxRETE4sb1oEwBp0lavQxfA3ygB3FERLRW15K/7enA9DK6Q7fKjYiIZ8sVvhERLZTkHxHRQkMmf1U26EYwERHRHUMmf9sGzu9CLBER0SV1m32ulPTqRiOJiIiuqdvbZztgP0m3AQuoumi63JgtIiJGmLrJ/82NRhEREV1Vq9nH9m3ABsCOZfixuutGRMRzT60ELukY4Chgapm0AjBtyWtERMRzWd3a+9up7sGzAMD23cBqTQUVERHNqpv8nyxdPg0gaZXmQoqIiKbVTf4/kvRNYE1J7wX+h44nc0VExMhSq7eP7S9KeiPwCPBS4GjbFzUaWURENKZW8pf0edtHARcNMC0iIkaYus0+bxxg2i7DGUhERHTPoDV/SR8APghsIunajlmrAb9vMrCIiGjOUM0+PwAuAD4LfKJj+nzbDzYWVURENGrQZh/bD9uebXtfFr/Cd4ykjbsSYUREDLtlvcJ3RXKFb0TEiJUrfCMiWihX+EZEtNDyXOH7rebCioiIJuUK34iIFqr7MBdsXyTpir51JD0/3T0jIkamurd3eB9wHPB3YCHlMY7AJs2FFhERTalb8/8YsKXt+5sMJiIiuqPuCd+bqR7dGBERo0Ddmv9U4LLS5v9E30TbhzcSVURENKpu8v8mcDFwHVWbf0REjGB1k/8Ktj/aaCQREdE1ddv8L5B0qKR1JT2/79VoZBER0Zi6Nf99y9+pHdPS1TMiYoSqe4Vvbt8cETGK1L7CV9KWwBbASn3TbH+viaAiIqJZda/wPQaYQpX8z6d6fu/vgCT/iIgRqO4J33cCOwH32j4Y2BpYo7GoIiKiUXWT/+O2FwJPS1odmEP1WMeIiBiB6rb5z5C0JtU9/GcCjwJ/aCqoiIho1pDJX5KAz9qeB5wk6UJgddvX1i1E0lhgBnCX7d0knQ5MBp4C/gi8z/ZTy7IDERGx9IZs9imPbzy/Y3z20iT+4gjgxo7x04HNga2A8cAhS7m9iIhYDnXb/K+U9OplKUDSRGBX4JS+abbPd0FV85+4LNuOiIhlU7fNfztgP0m3AQsoD3Ox/Yoa6x4PHAms1n+GpBWAA6iODBpx3M+v54a7H2lq8xERjdpivdU55m0vH/bt1k3+b16WjUvaDZhje6akKQMs8nXgN7Z/u4T1DwUOBdhwww2XJYSIiBiAqpaXmgtLL2TxK3xvH2L5z1LV7J8u660OnG17/3Lh2LbAXqUb6aAmT57sGTNm1I41IiJA0kzbk/tPr9XmL2l3SX8DbgUuBWYDFwy1nu2ptifangTsA1xcEv8hVEcT+9ZJ/BERMbzqnvD9FLA98Ndyk7edgMuXo9yTgHWAP0i6WtLRy7GtiIhYSnXb/J+y/YCkMZLG2L5E0vFLU5Dt6cD0Mlz7hnIRETH86ibheZJWBX4LnC5pDlWvn4iIGIHqNvvsATwOfBi4ELgZeFtDMUVERMPqPsxlgaQXAa8BHgR+afuBRiOLiIjG1O3tcwjVlbh7Ud3e+XJJ/9JkYBER0Zy6bf4fB7btq+1LegFwGfCdpgKLiIjm1G3zfwCY3zE+v0yLiIgRqG7NfxZwhaRzAVOdAL5W0kcBbH+5ofgiIqIBdZP/zeXV59zy91k3a4uIiOe+ur19jhtsvqQTbX9oeEKKiIim1W3zH8oOw7SdiIjoguFK/hERMYIk+UdEtNBwJX8N03YiIqILhiv5nzBM24mIiC4YtLePpJ9T9esfkO3dy99ThzesiIho0lBdPb9Y/u4FvAiYVsb3Be5rKqiIiGjWoMnf9qUAkr7U7xmQP5eUB+pGRIxQddv8V5G0Sd+IpI2BVZoJKSIimlb39g4fAaZLuoWqZ89GwPsaiyoiIhpV9/YOF0p6MbB5mXST7SeaCysiIppU92EuK1Pd0/8w29cAG0rardHIIiKiMXXb/L8LPAm8tozfBXy6kYgiIqJxdZP/pra/ADwFYPsxclVvRMSIVTf5PylpPOWCL0mbAmnzj4gYoer29jkWuBDYQNLpVLdwPripoCIioll1e/v8StJMYHuq5p4jbN/faGQREdGYur19vg88bfsXts+juujr182GFhERTanb5v87qge4v1XSe4GLgOMbiyoiIhpVt9nnm5KuBy4B7ge2tX1vo5FFRERj6jb7HAB8B3gPcCpwvqStG4wrIiIaVLe3zzuA19meA5wh6afAacA2TQUWERHNqdvss2e/8T9Kek0jEUVEROOGepLXkba/IOlEBn6i1+HNhBUREU0aquZ/FPAF4GbgoebDiYiIbhgq+d8naT2qq3mnkPv5RESMCkMl/28AvwY2AWZ2TBdVM9AmA60UERHPbUM9w/dE4ERJ37D9gS7FFBERDavVzz+JPyJidKl7e4flImmspKsknVfGD5M0S5Ilrd2NGCIiYpGuJH/gCODGjvHfAzsDt3Wp/IiI6NB48pc0EdgVOKVvmu2rbM9uuuyIiBhYN2r+xwNHAgu7UFZERNTQaPKXtBswx/bMIRceeP1DJc2QNGPu3LnDHF1ERHs1XfPfAdhd0mzgTGBHSdPqrmz7ZNuTbU+eMGFCUzFGRLROo8nf9lTbE21PAvYBLra9f5NlRkTE0LrV22cxkg6XdCcwEbhW0ilDrRMREcOn7v38l5vt6cD0MvxV4KvdKjsiIhbXk5p/RET0VpJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLRQkn9ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLdSX5Sxor6SpJ55XxjSVdIWmWpB9KWrEbcURERKVbNf8jgBs7xj8PfMX2ZsBDwL92KY6IiKALyV/SRGBX4JQyLmBH4KyyyGnAnk3HERERi3Sj5n88cCSwsIy/AJhn++kyfiewfhfiiIiIotHkL2k3YI7tmcu4/qGSZkiaMXfu3GGOLiKivZqu+e8A7C5pNnAmVXPPCcCaksaVZSYCdw20su2TbU+2PXnChAkNhxoR0R6NJn/bU21PtD0J2Ae42PZ+wCXAO8tiBwLnNhlHREQsrlf9/I8CPippFtU5gG/3KI6IiFYaN/Qiw8P2dGB6Gb4FeE23yo6IiMXlCt+IiBZK8o+IaKEk/4iIFkryj4hooST/iIgWSvKPiGihJP+IiBZK8o+IaKEk/4iIFpLtXsdQi6S5wG3LuPrawP3DGM5IkH1uh7btc9v2F5Z/nzey/aw7Y46Y5L88JM2wPbnXcXRT9rkd2rbPbdtfaG6f0+wTEdFCSf4RES3UluR/cq8D6IHsczu0bZ/btr/Q0D63os0/IiIW15aaf0REdEjyj4hooVGd/CW9RdJfJM2S9Ilex9M0SRtIukTSDZKul3REr2PqFkljJV0l6bxex9INktaUdJakmyTdKOm1vY6paZI+Ur7Xf5Z0hqSVeh3TcJP0HUlzJP25Y9rzJV0k6W/l71rDUdaoTf6SxgL/DewCbAHsK2mL3kbVuKeBf7O9BbA98H9asM99jgBu7HUQXXQCcKHtzYGtGeX7Lml94HBgsu0tgbHAPr2NqhGnAm/pN+0TwK9tvxj4dRlfbqM2+VM9I3iW7VtsPwmcCezR45gaZfse21eW4flUCWH93kbVPEkTgV2BU3odSzdIWgP4J+DbALaftD2vp0F1xzhgvKRxwMrA3T2OZ9jZ/g3wYL/JewCnleHTgD2Ho6zRnPzXB+7oGL+TFiTCPpImAdsCV/Q4lG44HjgSWNjjOLplY2Au8N3S1HWKpFV6HVSTbN8FfBG4HbgHeNj2r3obVdesY/ueMnwvsM5wbHQ0J//WkrQq8BPgw7Yf6XU8TZK0GzDH9sxex9JF44BXAt+wvS2wgGFqCniuKu3ce1D98K0HrCJp/95G1X2u+uYPS//80Zz87wI26BifWKaNapJWoEr8p9s+u9fxdMEOwO6SZlM17e0oaVpvQ2rcncCdtvuO6s6i+jEYzXYGbrU91/ZTwNnA/+pxTN1yn6R1AcrfOcOx0dGc/P8EvFjSxpJWpDo59LMex9QoSaJqB77R9pd7HU832J5qe6LtSVSf8cW2R3WN0Pa9wB2SXlom7QTc0MOQuuF2YHtJK5fv+U6M8pPcHX4GHFiGDwTOHY6NjhuOjTwX2X5a0mHAL6l6BnzH9vU9DqtpOwAHANdJurpM+6Tt83sXUjTkQ8DppWJzC3Bwj+NplO0rJJ0FXEnVq+0qRuGtHiSdAUwB1pZ0J3AM8DngR5L+leq29u8elrJye4eIiPYZzc0+ERGxBEn+EREtlOQfEdFCSf4RES2U5B+jmqSDJK3X6ziWhaQpktrSlz26LMk/RruDqK4IbUS5z0xTprCUFzI1HE+MIunqGSNOuY/Nj6iu2h4LfAqYBXwZWBW4nyrp70B1l8S7gMeB19p+fIDtzS7b26Us979tz5L0NuA/gBWBB4D9bN8n6VhgU2ATqouPpgLfB/rur3OY7cskTQGOA+YBW5UyrqO6A+l4YE/bN0uaAJwEbFjW/3CJ+XLgGar7+HwIuKn/crZ/3z8e2/suxdsZbWU7r7xG1At4B/CtjvE1gMuACWV8b6qL+gCmU90GeLDtzQb+vQy/BzivDK/FogrSIcCXyvCxwExgfBlfGVipDL8YmFGGp1Al/nWB51El9OPKvCOA48vwD4DXleENqa7Q7ivnYx1xDrbcP+LJK686rxwixkh0HfAlSZ8HzgMeArYELqqu/Gcs1Z0fl8YZHX+/UoYnAj8s91NZEbi1Y/mfedFRxArA1yRtQ1VTf0nHcn9yuSOjpJuBvjtRXgf8cxneGdiixA6werk5X3+DLdcZT8SQkvxjxLH9V0mvBN4KfBq4GLje9vI8zcoDDJ8IfNn2z0oTzrEdyyzoGP4IcB/VQ1XGAH/vmPdEx/DCjvGFLPr/GwNsb7tzPTqSPDWWW9B/4YjB5IRvjDil985jtqcB/wVsB0zoe5ShpBUkvbwsPh9YrcZm9+74+4cyvAaL7gR74LPWWGQN4B7bC6nurTS27r4Uv6Jq0wegHEHAs2Nf0nIRSy3JP0airYA/lpvXHQMcDbwT+Lyka4CrWdRL5lTgJElXSxo/yDbXknQtVVv8R8q0Y4EfS5pJdRJ5Sb4OHFjK3pylr4UfDkyWdK2kG4D3l+k/B95eYn/9IMtFLLX09onWK719JtseLMFHjCqp+UdEtFBO+EZrSPop1WMAOx3l6kEwEa2SZp+IiBZKs09ERAsl+UdEtFCSf0RECyX5R0S0UJJ/REQLJflHRLTQ/wdMfGMJiOuMpwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_by_id(datasaver.dataset.run_id);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now try the same using the `MeasurementLoop`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Measurement error RuntimeError(Wrong measurement at action_indices (0, 0). Expected: fixed_parameter. Received: random_parameter) - varied_order_measurement_loop\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 34. \n" + ] + }, + { + "ename": "RuntimeError", + "evalue": "Wrong measurement at action_indices (0, 0). Expected: fixed_parameter. Received: random_parameter", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_29980/2330389870.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mset_v\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mSweep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m11\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'sweep_values'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mset_v\u001b[0m \u001b[1;33m%\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mmsmt\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmeasure\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrandom_parameter\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0mmsmt\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmeasure\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfixed_parameter\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\serwan\\documents\\github\\qcodes_new\\qcodes\\dataset\\measurement_loop.py\u001b[0m in \u001b[0;36mmeasure\u001b[1;34m(self, measurable, name, label, unit, timestamp, **kwargs)\u001b[0m\n\u001b[0;32m 866\u001b[0m \u001b[1;31m# TODO Incorporate kwargs name, label, and unit, into each of these\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 867\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmeasurable\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mParameter\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 868\u001b[1;33m result = self._measure_parameter(\n\u001b[0m\u001b[0;32m 869\u001b[0m \u001b[0mmeasurable\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlabel\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0munit\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0munit\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 870\u001b[0m )\n", + "\u001b[1;32mc:\\users\\serwan\\documents\\github\\qcodes_new\\qcodes\\dataset\\measurement_loop.py\u001b[0m in \u001b[0;36m_measure_parameter\u001b[1;34m(self, parameter, name, label, unit, **kwargs)\u001b[0m\n\u001b[0;32m 647\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 648\u001b[0m \u001b[1;31m# Ensure measuring parameter matches the current action_indices\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 649\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_verify_action\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0maction\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mparameter\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0madd_if_new\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 650\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 651\u001b[0m \u001b[1;31m# Get parameter result\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32mc:\\users\\serwan\\documents\\github\\qcodes_new\\qcodes\\dataset\\measurement_loop.py\u001b[0m in \u001b[0;36m_verify_action\u001b[1;34m(self, action, name, add_if_new)\u001b[0m\n\u001b[0;32m 616\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0maction_names\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0maction_indices\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 617\u001b[0m \u001b[1;32melif\u001b[0m \u001b[0mname\u001b[0m \u001b[1;33m!=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0maction_names\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0maction_indices\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 618\u001b[1;33m raise RuntimeError(\n\u001b[0m\u001b[0;32m 619\u001b[0m \u001b[1;34mf\"Wrong measurement at action_indices {self.action_indices}. \"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 620\u001b[0m \u001b[1;34mf\"Expected: {self.action_names[self.action_indices]}. Received: {name}\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mRuntimeError\u001b[0m: Wrong measurement at action_indices (0, 0). Expected: fixed_parameter. Received: random_parameter" + ] + } + ], + "source": [ + "with MeasurementLoop('varied_order_measurement_loop') as msmt:\n", + " for set_v in Sweep(range(11), 'sweep_values'):\n", + " if set_v % 2:\n", + " msmt.measure(random_parameter)\n", + " else:\n", + " msmt.measure(fixed_parameter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lo and behold, an error appeared. This is because it expects the first measurement to be `fixed_parameter`, which was measured during the first iteration, but instead the second iteration it measures `random_parameter`.\n", + "\n", + "This problem can be solved by explicitly telling the `MeasurementLoop` which is the first or second measurement by adding `msmt.skip`.\n", + "In this example, `random_parameter` has a `msmt.skip()` before it, indicating that another parameter is usually measured first (though not this time) and so it's actually the second parameter being measured." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 36. \n" + ] + } + ], + "source": [ + "with MeasurementLoop('varied_order_measurement_loop') as msmt:\n", + " for set_v in Sweep(range(11), 'sweep_values'):\n", + " if set_v % 2:\n", + " msmt.skip()\n", + " msmt.measure(random_parameter)\n", + " else:\n", + " msmt.measure(fixed_parameter)\n", + " msmt.skip()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_by_id(msmt.dataset.run_id);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A `break` statement is the second situation in which the `MeasurementLoop` needs an explicit signal to ensure the measurement order is adhered to. The reason is because unlike a context manager, a for-loop has no way of knowing when the loop has been prematurely exited, and so it won't be able to perform the necessary actions when exiting a `Sweep`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 39. \n", + "set_v=0, continuing measurement\n", + "set_v=1, continuing measurement\n", + "set_v=2, continuing measurement\n", + "set_v=3, continuing measurement\n", + "set_v=4, exiting prematurely using `msmt.step_out`\n" + ] + } + ], + "source": [ + "with MeasurementLoop('varied_order_measurement_loop') as msmt:\n", + " for set_v in Sweep(range(11), 'sweep_values'):\n", + " if (set_v+1) % 5:\n", + " print(f'{set_v=}, continuing measurement')\n", + " msmt.measure(random_parameter)\n", + " else:\n", + " print(f'{set_v=}, exiting prematurely using `msmt.step_out`')\n", + " msmt.step_out()\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case we see that the `break` statement is preceded by `msmt.step_out`. This indicates to the `MeasurementLoop` that it has to take the necessary actions because the Sweep is exited." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `Sweep` functionalities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sweeping over a sequence of values in a `MeasurementLoop` is done using the `Sweep` object. It can sweep over an explicit sequence of values, or it can be given arguments to generate a sequence from." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sweeping a parameter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A parameter can be swept over by passing the parameter as the first argument. Here we create a sweep of parameter \"set_parameter\" over values \"[1, 2, 3, 4]\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Sweep(parameter=set_parameter, length=4)" + ] + }, + "execution_count": 123, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "parameter_sweep = Sweep(set_parameter, [1,2,3,4])\n", + "parameter_sweep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, the parameter value is automatically changed during the measurement:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 41. \n", + "set_parameter()=1\n", + "set_parameter()=2\n", + "set_parameter()=3\n", + "set_parameter()=4\n" + ] + } + ], + "source": [ + "with MeasurementLoop('sweep_set_parameter_measurement') as msmt:\n", + " for val in parameter_sweep:\n", + " print(f'{set_parameter()=}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sweeping without a parameter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to create a `Sweep` without a parameter. In this case, no parameter value is updated. In this case it's necessary to pass along a \"name\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Sweep('sweep_without_parameter', length=3)" + ] + }, + "execution_count": 129, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Sweep([1,2,3], name='sweep_without_parameter')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generating a sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous example we saw how you can create a sweep out of a pre-existing sequence. The `Sweep` also has convenient methods to generate a sequence using (keyword) arguments. \n", + "The following keyword arguments are identical to `np.linspace`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])" + ] + }, + "execution_count": 134, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sweep = Sweep(set_parameter, start=0, stop=10, num=11)\n", + "sweep.sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also give a `step`, in which case it behaves like `np.arange`, with the exception that here the last value is included" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 2, 4, 6, 8, 10])" + ] + }, + "execution_count": 136, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sweep = Sweep(set_parameter, start=0, stop=10, step=2)\n", + "sweep.sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can also use the current value of a \"set_parameter\" to generate a sequence. Here we tell it to create 11 points in a range of 5 below to above it's current value" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-3., -2., -1., 0., 1., 2., 3., 4., 5., 6., 7.])" + ] + }, + "execution_count": 137, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set_parameter(2)\n", + "sweep = Sweep(set_parameter, around=5, num=11)\n", + "sweep.sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or here we choose 11 points from whatever it's current value is to 12" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.])" + ] + }, + "execution_count": 140, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set_parameter(2)\n", + "sweep = Sweep(set_parameter, stop=12, num=11)\n", + "sweep.sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sweep arguments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The most common types of sweeps can also be created without using keyword arguments. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Sweep(parameter=set_parameter, length=11)" + ] + }, + "execution_count": 144, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Sweep(set_parameter, 0, 10, 11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "is equivalent to" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Sweep(parameter=set_parameter, length=11)" + ] + }, + "execution_count": 145, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Sweep(set_parameter, start=0, stop=10, num=11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A full list of argument combinations can be found in the docstring of `Sweep.transform_args_to_kwargs`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Additional features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Value masking" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `MeasurementLoop` also provides the ability to mask the value of an object during the measurement. For example" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial value get_parameter()=2\n", + "Starting experimental run with id: 8. \n", + "Masked value get_parameter()=9\n", + "Value after measurement finished: get_parameter()=2\n" + ] + } + ], + "source": [ + "get_parameter = ManualParameter('get_parameter', initial_value=2)\n", + "print(f'Initial value {get_parameter()=}')\n", + "\n", + "with MeasurementLoop('masking_measurement') as msmt:\n", + " msmt.mask(get_parameter, 9)\n", + " \n", + " print(f'Masked value {get_parameter()=}')\n", + "\n", + " for val in Sweep(set_parameter, range(5)):\n", + " msmt.measure(get_parameter)\n", + "\n", + "print(f'Value after measurement finished: {get_parameter()=}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This can be especially useful when measurements are encapsulated in functions, as it allows to set parameters to specific values during the measurement, knowing that it will be reset after.\n", + "\n", + "The unmasking of a parameter happens after a measurement is complete, even if the measurement fails." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Masking dictionaries and object attributes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We just saw that it's possible to mask a parameter value. It is also possible to mask two other elements:\n", + "- keys in dictionaries\n", + "- attributes of objects\n", + "\n", + "Here we show the two examples" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# First create a dummy class\n", + "class MyObject:\n", + " object_attribute = 42\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial object value my_object.object_attribute=42\n", + "Initial dictionary d={'key1': 12, 'key2': 13, 'key3': 14}\n", + "Starting experimental run with id: 9. \n", + "Masked object value my_object.object_attribute=999\n", + "Masked dictionary d={'key1': 12, 'key2': 999, 'key3': 14}\n", + "Final object value my_object.object_attribute=42\n", + "Final dictionary d={'key1': 12, 'key2': 13, 'key3': 14}\n" + ] + } + ], + "source": [ + "\n", + "my_object= MyObject()\n", + "print(f'Initial object value {my_object.object_attribute=}')\n", + "\n", + "d = dict(key1=12, key2=13, key3=14)\n", + "print(f'Initial dictionary {d=}')\n", + "\n", + "with MeasurementLoop('masking_dictionary_and_object') as msmt:\n", + " msmt.mask(my_object, object_attribute=999)\n", + " msmt.mask(d, key2=999)\n", + "\n", + " print(f'Masked object value {my_object.object_attribute=}')\n", + " print(f'Masked dictionary {d=}')\n", + "\n", + "print(f'Final object value {my_object.object_attribute=}')\n", + "print(f'Final dictionary {d=}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Measuring without a parameter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most previous examples showed how we can measure a `Parameter` in a `MeasurementLoop`. However, this is not a requirement. Just as one can create a `Sweep` without a parameter, so can one also measure things that are not a parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 47. \n" + ] + } + ], + "source": [ + "with MeasurementLoop('measure_non_parameters') as msmt:\n", + " for k in Sweep(range(5), 'sweep'):\n", + " msmt.measure(42, 'measure_value')\n", + " msmt.measure({'val1': 1, 'val2': 2}, 'measure_dict')\n", + "\n", + " # One can also measure a function that returns a dict\n", + " def random_int(min_val=1, max_val=50):\n", + " return {\n", + " 'val1': np.random.randint(min_val, max_val),\n", + " 'val2': np.random.randint(min_val, max_val)\n", + " }\n", + " msmt.measure(random_int, 'measure_callable')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Measuring same parameter multiple times" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One feature of the `MeasurementLoop` that is not possible in the original `Measurement` is that the same parameter can be swept/measured at multiple different points:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting experimental run with id: 49. \n" + ] + }, + { + "data": { + "text/plain": [ + "measure_same_parameter #49@C:\\Users\\Serwan\\experiments.db\n", + "---------------------------------------------------------\n", + "sweep_parameter - numeric\n", + "random_parameter - numeric\n", + "random_parameter_1 - numeric" + ] + }, + "execution_count": 160, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with MeasurementLoop('measure_same_parameter') as msmt:\n", + " for k in Sweep(range(10), 'sweep_parameter'):\n", + " msmt.measure(random_parameter)\n", + " msmt.measure(random_parameter)\n", + "msmt.dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen, this creates two different measurement arrays. The second measurement automatically appends an index to distinguish its name from the original measurement array.\n", + "\n", + "This is a useful feature especially when encapsulating measurements in functions. In this case it could very well occur that the same parameter is measured at multiple different locations" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.4 ('qcodes-sydney')", + "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.10.4" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "5489eaf2c0162c90544bb6633d254e9aaec572698f0919090a3f0c8d3f72ceff" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/qcodes/configuration/config.py b/qcodes/configuration/config.py index 05f251ea51e..d6d72f13f2b 100644 --- a/qcodes/configuration/config.py +++ b/qcodes/configuration/config.py @@ -403,13 +403,18 @@ def describe(self, name: str) -> str: return doc - def __getitem__(self, name: str) -> Any: - val = self.current_config - for key in name.split('.'): - if val is None: - raise KeyError(f"{name} not found in current config") - val = val[key] - return val + def __getitem__(self, name: Union[int,str]) -> Any: + if isinstance(name, int): + # Integer requested, likely a consequence of "if 'string' in config" + # Return key corresponding to index + return list(self.current_config.keys())[name] + else: + val = self.current_config + for key in name.split('.'): + if val is None: + raise KeyError(f"{name} not found in current config") + val = val[key] + return val def __getattr__(self, name: str) -> Any: return getattr(self.current_config, name) @@ -422,22 +427,21 @@ def __repr__(self) -> str: return output -class DotDict(Dict[str, Any]): +class DotDict(dict): """ Wrapper dict that allows to get dotted attributes - - Requires keys to be strings. """ - - def __init__(self, value: Mapping[str, Any] | None = None): + exclude_from_dict = [] + def __init__(self, value=None): if value is None: pass else: for key in value: self.__setitem__(key, value[key]) - def __setitem__(self, key: str, value: Any) -> None: - if '.' in key: + def __setitem__(self, key, value): + # string type must be checked, as key could be other datatype + if type(key)==str and '.' in key: myKey, restOfKey = key.split('.', 1) target = self.setdefault(myKey, DotDict()) target[restOfKey] = value @@ -446,36 +450,77 @@ def __setitem__(self, key: str, value: Any) -> None: value = DotDict(value) dict.__setitem__(self, key, value) - def __getitem__(self, key: str) -> Any: - if '.' not in key: + def __getitem__(self, key): + if type(key) != str or '.' not in key: return dict.__getitem__(self, key) myKey, restOfKey = key.split('.', 1) target = dict.__getitem__(self, myKey) return target[restOfKey] - def __contains__(self, key: object) -> bool: - if not isinstance(key, str): - return False - if '.' not in key: - return super().__contains__(key) + def __contains__(self, key): + if not isinstance(key, str) or '.' not in key: + return dict.__contains__(self, key) myKey, restOfKey = key.split('.', 1) - target = dict.__getitem__(self, myKey) - return restOfKey in target - def __deepcopy__(self, memo: dict[Any, Any] | None) -> DotDict: - return DotDict(copy.deepcopy(dict(self))) + if myKey not in self: + return False + else: + target = dict.__getitem__(self, myKey) + return restOfKey in target - def __getattr__(self, name: str) -> Any: - """ - Overwrite ``__getattr__`` to provide dot access - """ - return self.__getitem__(name) + def __deepcopy__(self, memo): + return DotDict(copy.deepcopy(dict(self))) - def __setattr__(self, key: str, value: Any) -> None: - """ - Overwrite ``__setattr__`` to provide dot access + # dot acces baby + def __setattr__(self, key, val): + if key in self.exclude_from_dict: + self.__dict__[key] = val + else: + self.__setitem__(key, val) + + def __getattr__(self, key): + try: + return self.__getitem__(key) + except KeyError: + raise AttributeError(f'Attribute {key} not found') + + def __dir__(self): + # Add keys to dir, used for auto-completion + items = super().__dir__() + items.extend(self.keys()) + return items + + def setdefault(self, key, default=None): + """Set value of a key if it does not yet exist""" + d = self + if isinstance(key, str): + *parent_keys, key = key.split('.') + for subkey in parent_keys: + d = dict.setdefault(d, subkey, DotDict()) + + return dict.setdefault(d, key, default) + + def create_dicts(self, *keys): + """Create nested dict structure + Args: + *keys: Sequence of key strings. Empty DotDicts will be created if + each key does not yet exist + Returns: + Most inner dict, newly created if it does not yet exist + Examples: + d = DotDict() + d.create_dicts('a', 'b', 'c') + print(d.a.b.c) + >>> {} """ - self.__setitem__(key, value) + d = self + for key in keys: + if key in self: + assert isinstance(d[key], dict) + + d.setdefault(key, DotDict()) + d = d[key] + return d def update(d: dict[Any, Any], u: Mapping[Any, Any]) -> dict[Any, Any]: diff --git a/qcodes/dataset/__init__.py b/qcodes/dataset/__init__.py index db14cbc4ca9..c9ade67e464 100644 --- a/qcodes/dataset/__init__.py +++ b/qcodes/dataset/__init__.py @@ -29,6 +29,7 @@ ) from .experiment_settings import get_default_experiment_id, reset_default_experiment_id from .legacy_import import import_dat_file +from .measurement_loop import MeasurementLoop, Sweep, Iterate from .measurements import Measurement from .plotting import plot_by_id, plot_dataset from .sqlite.connection import ConnectionPlus @@ -82,9 +83,11 @@ "load_from_netcdf", "load_last_experiment", "load_or_create_experiment", + "MeasurementLoop", "new_data_set", "new_experiment", "plot_by_id", "plot_dataset", "reset_default_experiment_id", + "Sweep", ] diff --git a/qcodes/dataset/data_set.py b/qcodes/dataset/data_set.py index 135108c1f87..baf6d090d23 100644 --- a/qcodes/dataset/data_set.py +++ b/qcodes/dataset/data_set.py @@ -313,11 +313,12 @@ def prepare( shapes: Shapes | None = None, parent_datasets: Sequence[Mapping[Any, Any]] = (), write_in_background: bool = False, + allow_empty_dataset: bool = False, ) -> None: self.add_snapshot(json.dumps({"station": snapshot}, cls=NumpyJSONEncoder)) - if interdeps == InterDependencies_(): + if interdeps == InterDependencies_() and not allow_empty_dataset: raise RuntimeError("No parameters supplied") self.set_interdependencies(interdeps, shapes) diff --git a/qcodes/dataset/data_set_in_memory.py b/qcodes/dataset/data_set_in_memory.py index b63350810a9..4a2c925e581 100644 --- a/qcodes/dataset/data_set_in_memory.py +++ b/qcodes/dataset/data_set_in_memory.py @@ -401,13 +401,14 @@ def prepare( shapes: Shapes | None = None, parent_datasets: Sequence[Mapping[Any, Any]] = (), write_in_background: bool = False, + allow_empty_dataset: bool = False, ) -> None: if not self.pristine: raise RuntimeError("Cannot prepare a dataset that is not pristine.") self.add_snapshot(json.dumps({"station": snapshot}, cls=NumpyJSONEncoder)) - if interdeps == InterDependencies_(): + if interdeps == InterDependencies_() and not allow_empty_dataset: raise RuntimeError("No parameters supplied") self._set_interdependencies(interdeps, shapes) diff --git a/qcodes/dataset/data_set_protocol.py b/qcodes/dataset/data_set_protocol.py index bcb75d3d518..d97e3400287 100644 --- a/qcodes/dataset/data_set_protocol.py +++ b/qcodes/dataset/data_set_protocol.py @@ -98,6 +98,7 @@ def prepare( shapes: Shapes | None = None, parent_datasets: Sequence[Mapping[Any, Any]] = (), write_in_background: bool = False, + allow_empty_dataset: bool = False, ) -> None: pass diff --git a/qcodes/dataset/measurement_loop.py b/qcodes/dataset/measurement_loop.py new file mode 100644 index 00000000000..1817b79ea16 --- /dev/null +++ b/qcodes/dataset/measurement_loop.py @@ -0,0 +1,2348 @@ +import builtins +import json +import logging +import threading +import traceback +import concurrent +from datetime import datetime +from time import perf_counter, sleep +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union +from warnings import warn +from matplotlib import pyplot as plt + +import numpy as np +from tqdm import tqdm +from tqdm.notebook import tqdm as tqdm_notebook + +from qcodes.dataset.data_set_protocol import DataSetProtocol +from qcodes.dataset.descriptions.detect_shapes import detect_shape_of_measurement +from qcodes.dataset.descriptions.rundescriber import RunDescriber +from qcodes.dataset.descriptions.versioning import serialization as serial +from qcodes.dataset.descriptions.versioning.converters import new_to_old +from qcodes.dataset.dond.sweeps import AbstractSweep +from qcodes.dataset.measurements import DataSaver, Measurement, Runner +from qcodes.dataset.sqlite.queries import add_parameter, update_run_description +from qcodes.instrument import ( + DelegateParameter, + InstrumentBase, + MultiParameter, + Parameter, + SweepValues, +) +from qcodes.instrument.parameter import _BaseParameter +from qcodes.parameters import ParameterBase +from qcodes.station import Station +from qcodes.utils import NumpyJSONEncoder +from qcodes.utils.dataset.doNd import AbstractSweep, ActionsT +from qcodes.utils.helpers import PerformanceTimer + +RAW_VALUE_TYPES = ( + float, + int, + bool, + np.integer, + np.floating, + np.bool_, + type(None), +) + + +class _DatasetHandler: + """Handler for a single DataSet (with Measurement and Runner) + + Used by the `MeasurementLoop` as an interface to the `Measurement` and `DataSet` + """ + + def __init__(self, measurement_loop: "MeasurementLoop", name: str = "results"): + self.measurement_loop = measurement_loop + self.name = name + + self.initialized: bool = False + self.datasaver: Optional[DataSaver] = None + self.runner: Optional[Runner] = None + self.measurement: Optional[Measurement] = None + self.dataset: Optional[DataSetProtocol] = None + + # Key: action_index + # Values: + # - parameter + # - dataset_parameter (differs from "parameter" when multiple share same name) + # - latest_value + self.setpoint_list: Dict[Tuple[int], Any] = dict() + + # Dict with key being action_index and value is a dict containing + # - parameter + # - setpoints_action_indices + # - setpoint_parameters + # - shape + # - unstored_results - list where each element contains (*setpoints, measurement_value) + # - latest_value + self.measurement_list: Dict[Tuple[int], Any] = {} + + self.initialize() + + def initialize(self) -> None: + """Creates a `Measurement`, runs it and initializes a dataset""" + # Once initialized, no new parameters can be added + assert not self.initialized, "Cannot initialize twice" + + # Create Measurement + self.measurement = Measurement(name=self.name) + + # Create measurement Runner + self.runner = self.measurement.run(allow_empty_dataset=True) + + # Create measurement Dataset + self.datasaver = self.runner.__enter__() + self.dataset = self.datasaver.dataset + + self.initialized = True + + def finalize(self) -> None: + """Finishes a measurement by flushing all data to the database""" + self.datasaver.flush_data_to_database() + + def _ensure_unique_parameter( + self, parameter_info: dict, setpoint: bool, max_idx: int = 99 + ) -> None: + """Ensure setpoint / measurement parameters have unique names + + If a previously registered parameter already shares the same name, it adds a + suffix '{name}_{idx}' where idx starts at zero + + Args: + parameter_info: dict for a setpoint/measurement parameter + See `DatasetHandler.create_measurement_info` for more information + setpoints: Whether parameter is a setpoint + max_idx: maximum allowed incremental index when parameters share same name + + Raises: + OverflowError if more than ``max_idx`` parameters share the same name + """ + if setpoint: + parameter_list = self.setpoint_list + else: + parameter_list = self.measurement_list + + parameter_names = [ + param_info["dataset_parameter"].name + for param_info in parameter_list.values() + if "dataset_parameter" in param_info + ] + + parameter_name = parameter_info["parameter"].name + if parameter_name not in parameter_names: + parameter_info["dataset_parameter"] = parameter_info["parameter"] + else: + for idx in range(1, max_idx): + parameter_idx_name = f"{parameter_name}_{idx}" + if parameter_idx_name not in parameter_names: + parameter_name = parameter_idx_name + break + else: + raise OverflowError( + f"All parameter names {parameter_name}_{{idx}} up to idx {max_idx} are taken" + ) + # Create a delegate parameter with modified name + delegate_parameter = DelegateParameter( + name=parameter_name, source=parameter_info["parameter"] + ) + parameter_info["dataset_parameter"] = delegate_parameter + + def create_measurement_info( + self, + action_indices: Tuple[int], + parameter: Parameter, + name: Optional[str] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + ) -> Dict[str, Any]: + """Creates information dict for a parameter that is to be measured + + Args: + action_indices: Indices in measurement loop corresponding to the + parameter being measured. + parameter: Parameter to be measured. + name: Name used for the measured parameter. + Will use parameter.name if not provided. + label: Label used for the measured parameter. + Will use parameter.label if not provided. + unit: Unit used for the measured parameter. + Will use parameter.unit if not provided. + """ + if parameter is None: + assert name is not None + parameter = Parameter(name=name, label=label, unit=unit) + elif {name, label, unit} != { + None, + }: + overwrite_attrs = {"name": name, "label": label, "unit": unit} + overwrite_attrs = { + key: val for key, val in overwrite_attrs.items() if val is not None + } + parameter = DelegateParameter(source=parameter, **overwrite_attrs) + + setpoints_action_indices = [] + for k in range(len(action_indices) + 1): + if action_indices[:k] in self.setpoint_list: + setpoints_action_indices.append(action_indices[:k]) + + measurement_info = { + "parameter": parameter, + "setpoints_action_indices": setpoints_action_indices, + "shape": self.measurement_loop.loop_shape, + "unstored_results": [], + "registered": False, + } + + return measurement_info + + def register_new_measurement( + self, + action_indices: Tuple[int], + parameter: _BaseParameter, + name: Optional[str] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + ) -> None: + """Register a new measurement parameter""" + measurement_info = self.create_measurement_info( + action_indices=action_indices, + parameter=parameter, + name=name, + label=label, + unit=unit, + ) + self.measurement_list[action_indices] = measurement_info + + # Add new measurement parameter + self._update_interdependencies() + + def add_measurement_result( + self, + action_indices: Tuple[int], + result: Union[float, int, bool], + parameter: _BaseParameter = None, + name: Optional[str] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + ) -> None: + """Store single measurement result + + This method is called from type-specific methods, such as + ``_measure_value``, ``_measure_parameter``, etc. + """ + if parameter is None and name is None: + raise SyntaxError( + "When adding a measurement result, must provide either a " + "parameter or name" + ) + + # Get parameter data array, creating a new one if necessary + if action_indices not in self.measurement_list: + self.register_new_measurement( + action_indices=action_indices, + parameter=parameter, + name=name, + label=label, + unit=unit, + ) + + measurement_info = self.measurement_list[action_indices] + + if name is None and parameter is not None: + name = parameter.name + if name != measurement_info["parameter"].name: + raise SyntaxError( + f"Provided name {name} must match that of previous measurement " + f"{measurement_info['parameter'].name}" + ) + + # Get setpoints corresponding to measurement + setpoints = self.get_result_setpoints(result, action_indices=action_indices) + + # Store results + parameters = ( + *measurement_info["setpoint_parameters"], + measurement_info["dataset_parameter"], + ) + result_with_setpoints = tuple(zip(parameters, (*setpoints, result))) + self.datasaver.add_result(*result_with_setpoints) + + # Also store in measurement_info + measurement_info["latest_value"] = result + + def get_result_setpoints(self, result, action_indices): + measurement_info = self.measurement_list[action_indices] + # Check if result is an array + if np.ndim(result) > 0: + if len(measurement_info["setpoints_action_indices"]) < np.ndim(result): + raise ValueError( + f"Number of setpoints {len(measurement_info['setpoints_action_indices'])} " + f"is less than array dimensionality {np.ndim(result)}" + ) + + # Pick the last N sweeps, where N is the array dimensionality + setpoints_action_indices = measurement_info["setpoints_action_indices"] + repeat_setpoints_action_indices = setpoints_action_indices[:-np.ndim(result)] + mesh_setpoints_action_indices = setpoints_action_indices[-np.ndim(result):] + + # Create repetitions of outer setpoints + repeat_setpoint_arrs = [] + for k, setpoint_indices in enumerate(repeat_setpoints_action_indices): + latest_value = self.setpoint_list[setpoint_indices]["latest_value"] + setpoint_arr = np.tile(latest_value, np.shape(result)) + repeat_setpoint_arrs.append(setpoint_arr) + + # Create mesh from last N setpoints matching + mesh_setpoint_arrs = [] + for k, setpoint_indices in enumerate(mesh_setpoints_action_indices): + setpoint_info = self.setpoint_list[setpoint_indices] + sequence = setpoint_info["sweep"].sequence + mesh_setpoint_arrs.append(sequence) + if len(sequence) != np.shape(result)[k]: + raise ValueError( + f'Setpoint {k} {setpoint_info["sweep"].name} length differs ' + f'from dimension {k} of array: {len(sequence)=} != {np.shape(result)[k]=}' + ) + + # Convert all 1D setpoint arrays to an N-D meshgrid + setpoints = repeat_setpoint_arrs + list(np.meshgrid(*mesh_setpoint_arrs, indexing='ij')) + else: + setpoints = [ + self.setpoint_list[action_indices]["latest_value"] + for action_indices in measurement_info["setpoints_action_indices"] + ] + + return setpoints + + def _update_interdependencies(self) -> None: + """Updates dataset after instantiation to include new setpoint/measurement parameter + + The `DataSet` was not made to register parameters after instantiation, so this + method is non-intuitive. + """ + dataset = self.datasaver.dataset + + # Get previous paramspecs + previous_paramspecs = dataset._rundescriber.interdeps.paramspecs + previous_paramspec_names = [spec.name for spec in previous_paramspecs] + + # Register all new setpoints parameters in Measurement + for setpoint_info in self.setpoint_list.values(): + if setpoint_info["registered"]: + # Already registered + continue + + self._ensure_unique_parameter(setpoint_info, setpoint=True) + self.measurement.register_parameter(setpoint_info["dataset_parameter"]) + setpoint_info["registered"] = True + + # Register all measurement parameters in Measurement + for measurement_info in self.measurement_list.values(): + if measurement_info["registered"]: + # Already registered + continue + + # Determine setpoint_parameters for each measurement_parameter + for measurement_info in self.measurement_list.values(): + measurement_info["setpoint_parameters"] = tuple( + self.setpoint_list[action_indices]["dataset_parameter"] + for action_indices in measurement_info["setpoints_action_indices"] + ) + + self._ensure_unique_parameter(measurement_info, setpoint=False) + self.measurement.register_parameter( + measurement_info["dataset_parameter"], + setpoints=measurement_info["setpoint_parameters"], + ) + measurement_info["registered"] = True + self.measurement.set_shapes( + detect_shape_of_measurement( + (measurement_info["dataset_parameter"],), measurement_info["shape"] + ) + ) + + # Update DataSaver + self.datasaver._interdeps = self.measurement._interdeps + + # Update DataSet + # Generate new paramspecs with matching RunDescriber + dataset._rundescriber = RunDescriber( + self.measurement._interdeps, shapes=self.measurement._shapes + ) + paramspecs = new_to_old(dataset._rundescriber.interdeps).paramspecs + + # Add new paramspecs + for spec in paramspecs: + if spec.name not in previous_paramspec_names: + add_parameter( + spec, + conn=dataset.conn, + run_id=dataset.run_id, + insert_into_results_table=True, + ) + + desc_str = serial.to_json_for_storage(dataset.description) + + update_run_description(dataset.conn, dataset.run_id, desc_str) + + # Update dataset cache + cache_data = self.dataset._cache._data + interdeps_empty_dict = dataset._rundescriber.interdeps._empty_data_dict() + for key, val in interdeps_empty_dict.items(): + cache_data.setdefault(key, val) + + +class MeasurementLoop: + """Class to perform measurements in a fixed sequential order. + + This measurement method complements the other two ways of doing measurements + by being more versatile than `do1d`, `do2d`, `dond`, and more implicit that `Measurement`. + + See the tutorial ``MeasurementLoop`` for a tutorial. + + Args: + name: Measurement name, also used as the dataset name + notify: Notify when measurement is complete. + The function `Measurement.notify_function` must be set + show_progress: Whether to show progress bars. + If not specified, will use value of class attribute ``MeasurementLoop.show_progress`` + """ + + # Context manager + running_measurement = None + measurement_thread = None + + # Default names for measurement and dataset, used to set user namespace + # variables if measurement is executed in a separate thread. + _default_measurement_name = "msmt" + _default_dataset_name = "data" + final_actions = [] + except_actions = [] + max_arrays = 100 + + # Progress bar + show_progress: bool = False + _progress_bar_kwargs: Dict[str, Any] = {'mininterval': 0.2} + + _t_start = None + + # Notification function, called if notify=True. + # Function should receive the following arguments: + # Measurement object, exception_type, exception_message, traceback + # The last three are only not None if an error has occured + notify_function = None + + def __init__(self, name: Optional[str], notify: bool = False, show_progress: bool = None): + self.name: str = name + + # Data handler is created during `with Measurement("name")` + # Used to control dataset(s) + self.data_handler: _DatasetHandler = None + + # Total dimensionality of loop + self.loop_shape: Union[Tuple[int], None] = None + + # Current loop indices + self.loop_indices: Union[Tuple[int], None] = None + + # Index of current action + self.action_indices: Union[Tuple[int], None] = None + + # Progress bars, only used if show_progress is True + if show_progress is not None: + self.show_progress = show_progress + self.progress_bars: Dict[Tuple[int], tqdm] = {} + + + # contains data groups, such as ParameterNodes and nested measurements + self._data_groups: Dict[Tuple[int], "MeasurementLoop"] = {} + + # Registry of actions: sweeps, measurements, and data groups + self.actions: Dict[Tuple[int], Any] = {} + self.action_names: Dict[Tuple[int], str] = {} + + self.is_context_manager: bool = False # Whether used as context manager + self.is_paused: bool = False # Whether the Measurement is paused + self.is_stopped: bool = False # Whether the Measurement is stopped + + # Whether to notify upon measurement completion + self.notify: bool = notify + + # Each measurement can have its own final actions, to be executed + # regardless of whether the measurement finished successfully or not + # Note that there are also Measurement.final_actions, which are always + # executed when the outermost measurement finishes + self.final_actions: List[Callable] = [] + self.except_actions: List[Callable] = [] + self._masked_properties: List[Dict[str, Any]] = [] + + self.timings: PerformanceTimer = PerformanceTimer() + + @property + def dataset(self) -> DataSetProtocol: + if self.data_handler is None: + return None + else: + return self.data_handler.dataset + + def log(self, message: str, level: str = "info") -> None: + """Send a log message + + Args: + message: Text to log + level: Logging level (debug, info, warning, error) + """ + assert level in ["debug", "info", "warning", "error"] + logger = logging.getLogger("msmt") + log_function = getattr(logger, level) + + # Append measurement name + if self.name is not None: + message += f" - {self.name}" + + log_function(message) + + @property + def data_groups(self) -> Dict[Tuple[int], "MeasurementLoop"]: + if running_measurement() is not None: + return running_measurement()._data_groups + else: + return self._data_groups + + @property + def active_action(self) -> Optional[Tuple[int]]: + return self.actions.get(self.action_indices, None) + + @property + def active_action_name(self) -> Optional[str]: + return self.action_names.get(self.action_indices, None) + + @property + def setpoint_list(self) -> Optional[Dict[Tuple[int], Any]]: + if self.data_handler is not None: + return self.data_handler.setpoint_list + else: + return None + + @property + def measurement_list(self) -> Optional[Dict[Tuple[int], Any]]: + if self.data_handler is not None: + return self.data_handler.measurement_list + else: + return None + + def __enter__(self) -> "MeasurementLoop": + """Operation when entering a loop, including dataset instantiation""" + self.is_context_manager = True + + # Encapsulate everything in a try/except to ensure that the context + # manager is properly exited. + try: + if MeasurementLoop.running_measurement is None: + # Register current measurement as active primary measurement + MeasurementLoop.running_measurement = self + MeasurementLoop.measurement_thread = threading.current_thread() + + # Initialize dataset handler + self.data_handler = _DatasetHandler( + measurement_loop=self, name=self.name + ) + + # Add metadata + self._t_start = datetime.now() + self._initialize_metadata(self.dataset) + + # Initialize attributes + self.loop_shape = () + self.loop_indices = () + self.action_indices = (0,) + self.data_arrays = {} + self.set_arrays = {} + + else: + if threading.current_thread() is not MeasurementLoop.measurement_thread: + raise RuntimeError( + "Cannot run a measurement while another measurement " + "is already running in a different thread." + ) + + # Primary measurement is already running. Add this measurement as + # a data_group of the primary measurement + msmt = MeasurementLoop.running_measurement + msmt.data_groups[msmt.action_indices] = self + # data_groups = [ + # (key, getattr(val, "name", "None")) + # for key, val in msmt.data_groups.items() + # ] + # TODO add metadata + # msmt.dataset.add_metadata({"data_groups": data_groups}) + msmt.action_indices += (0,) + + # Nested measurement attributes should mimic the primary measurement + self.loop_shape = msmt.loop_shape + self.loop_indices = msmt.loop_indices + self.action_indices = msmt.action_indices + self.data_arrays = msmt.data_arrays + self.set_arrays = msmt.set_arrays + self.timings = msmt.timings + + return self + except: + # An error has occured, ensure running_measurement is cleared + if MeasurementLoop.running_measurement is self: + MeasurementLoop.running_measurement = None + raise + + def __exit__(self, exc_type: Exception, exc_val, exc_tb) -> None: + """Operation when exiting a loop + + Args: + exc_type: Type of exception, None if no exception + exc_val: Exception message, None if no exception + exc_tb: Exception traceback object, None if no exception + """ + msmt = MeasurementLoop.running_measurement + if msmt is self: + # Immediately unregister measurement as main measurement, in case + # an error occurs during final actions. + MeasurementLoop.running_measurement = None + + for progress_bar in self.progress_bars.values(): + progress_bar.close() + + if exc_type is not None: + self.log(f"Measurement error {exc_type.__name__}({exc_val})", level="error") + + self._apply_actions(self.except_actions, label="except", clear=True) + + if msmt is self: + self._apply_actions( + MeasurementLoop.except_actions, label="global except", clear=True + ) + + self._apply_actions(self.final_actions, label="final", clear=True) + + self.unmask_all() + + if msmt is self: + # Also perform global final actions + # These are always performed when outermost measurement finishes + self._apply_actions(MeasurementLoop.final_actions, label="global final") + + # Notify that measurement is complete + if self.notify and self.notify_function is not None: + try: + self.notify_function(exc_type, exc_val, exc_tb) + except Exception: + self.log("Could not notify", level="error") + + # include final metadata + t_stop = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.dataset.add_metadata("t_stop", t_stop) + self.data_handler.finalize() + + self.log("Measurement finished") + else: + msmt.step_out(reduce_dimension=False) + + self.is_context_manager = False + + def _initialize_metadata(self, dataset): + """Initialize dataset metadata""" + if dataset is None: + dataset = self.dataset + + # Save config to metadata + try: + from qcodes import config + + config_str = json.dumps(dict(config), cls=NumpyJSONEncoder) + self.dataset.add_metadata('config', config_str) + except Exception as e: + warn(f'Could not save config due to error {e}') + + dataset.add_metadata("measurement_type", "MeasurementLoop") + dataset.add_metadata("t_start", self._t_start.strftime("%Y-%m-%d %H:%M:%S")) + + # Save latest IPython cells + from IPython import get_ipython + shell = get_ipython() + if shell is not None and "In" in shell.ns_table["user_global"]: + num_cells = 20 # Number of cells to save + last_input_cells = shell.ns_table["user_global"]['In'][-num_cells:] + dataset.add_metadata("measurement_code", last_input_cells[-1]) + dataset.add_metadata("last_input_cells", str(last_input_cells)) + + def _verify_action( + self, action: Callable, name: str, add_if_new: bool = True + ) -> None: + """Verify an action corresponds to the current action indices. + + An action is usually (currently always) a measurement. + + Args: + action: Action that is supposed to be performed at these action_indices + add_if_new: Register action if the action_indices have not yet been registered + + Raises: + RuntimeError if a different action is performed than is usually + performed at the current action_indices. An example is when + a different parameter is measuremed. + """ + if self.action_indices not in self.actions: + if add_if_new: + # Add current action to action registry + self.actions[self.action_indices] = action + self.action_names[self.action_indices] = name + elif name != self.action_names[self.action_indices]: + raise RuntimeError( + f"Wrong measurement at action_indices {self.action_indices}. " + f"Expected: {self.action_names[self.action_indices]}. Received: {name}" + ) + + def _apply_actions(self, actions: list, label="", clear=False) -> None: + """Apply actions, either except_actions or final_actions""" + for action in actions: + try: + action() + except Exception: + self.log( + f"Could not execute {label} action {action} \n" + f"{traceback.format_exc()}", + level="error", + ) + + if clear: + actions.clear() + + def _get_maximum_action_index(self, action_indices, position): + msmt = running_measurement() + + # Get maximum action idx + max_idx = 0 + for idxs in msmt.actions: + if idxs[:position] != action_indices[:position]: + continue + if len(idxs) <= position: + continue + max_idx = max(max_idx, idxs[position]) + return max_idx + + def _update_progress_bar(self, action_indices, description=None, create_if_new=True): + # Register new progress bar + if action_indices not in self.progress_bars: + # Do not create progress bar if one already exists and it's not a widget + # Otherwise stdout gets spammed + if not isinstance(tqdm, tqdm_notebook) and self.progress_bars: + return + elif create_if_new: + self.progress_bars[action_indices] = tqdm( + total=np.prod(self.loop_shape), + desc=description, + **self._progress_bar_kwargs + ) + else: + raise RuntimeError('Cannot update progress bar if not created') + + # Update progress bar + progress_bar = self.progress_bars[action_indices] + value = 1 + for k, loop_idx in enumerate(self.loop_indices[::-1]): + if k: + factor = np.prod(self.loop_shape[-k:]) + else: + factor = 1 + value += factor * loop_idx + + progress_bar.update(value - progress_bar.n) + if value == progress_bar.total: + progress_bar.close() + + + def _fraction_complete_action_indices(self, action_indices, silent=True): + """Calculate fraction complete from finished action_indices""" + msmt = running_measurement() + fraction_complete = 0 + scale = 1 + + max_idxs = [] + for k, action_idx in enumerate(action_indices): + # Check if previous idx is a sweep + # If so, reduce scale by loop dimension + action = msmt.actions.get(action_indices[:k]) + if not silent: + print(f'{action=}, {isinstance(action, BaseSweep)=}') + if isinstance(action, BaseSweep): + if not silent: + print(f'Decreasing scale by {len(action)}') + scale /= len(action) + + max_idx = self._get_maximum_action_index(action_indices, position=k) + + fraction_complete += action_idx / (max_idx + 1) * scale + scale /= max_idx + 1 + max_idxs.append(max_idx) + if not silent: + print(f'{fraction_complete=}, {scale=}, {action_idx=}, {max_idxs=}') + + return fraction_complete + + def _fraction_complete_loop(self, action_indices, silent=True): + msmt = running_measurement() + fraction_complete = 0 + scale = 1 + loop_idx = 0 + + for k, action_idx in enumerate(action_indices): + # Check if current action is a sweep + # If so, reduce scale by action index fraction + action = msmt.actions.get(action_indices[:k+1]) + if isinstance(action, BaseSweep): + max_idx = self._get_maximum_action_index(action_indices, position=k) + + if not silent: + print(f'Reducing current Sweep {loop_idx=} {msmt.loop_indices[loop_idx]} / {len(action)} * {scale}') + print(f'{max_idx=}') + scale /= (max_idx + 1) + + # Check if previous idx is a sweep + # If so, reduce scale by loop dimension + action = msmt.actions.get(action_indices[:k]) + if not silent: + print(f'{action=}, {isinstance(action, BaseSweep)=}') + if isinstance(action, BaseSweep): + if not silent: + print(f'Reducing previous Sweep {loop_idx=} fraction {msmt.loop_indices[loop_idx]} / {len(action)} * {scale}') + fraction_complete += msmt.loop_indices[loop_idx] / len(action) * scale + loop_idx += 1 + scale /= len(action) + + return fraction_complete + + def fraction_complete(self, silent=True, precision=3): + msmt = running_measurement() + if msmt is None: + return 1 + + fraction_complete = 0 + + # Calculate fraction complete from action indices + fraction_complete_actions = self._fraction_complete_action_indices(msmt.action_indices, silent=silent+1) + fraction_complete += fraction_complete_actions + if not silent: + print(f'Fraction complete from action indices: {fraction_complete_actions:.3f}') + + # Calculate fraction complete from point in loop + fraction_complete_loop = self._fraction_complete_loop(msmt.action_indices, silent=silent+1) + fraction_complete += fraction_complete_loop + if not silent: + print(f'Fraction complete from loop: {fraction_complete_loop:.3f}') + + return np.round(fraction_complete, precision) + + # Measurement-related functions + def _measure_parameter( + self, + parameter: _BaseParameter, + name: Optional[str] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + **kwargs, + ) -> Any: + """Measure parameter and store results. + + Called from `measure`. + MultiParameter is called separately. + + Args: + parameter: Parameter to be measured + name: Name used to measure parameter, overriding ``parameter.name`` + label: Label used to measure parameter, overriding ``parameter.label`` + unit: Unit used to measure parameter, overriding ``parameter.unit`` + **kwargs: optional kwargs passed to parameter, i.e. ``parameter(**kwargs)`` + + Returns: + Current value of parameter + """ + name = name or parameter.name + + # Ensure measuring parameter matches the current action_indices + self._verify_action(action=parameter, name=name, add_if_new=True) + + # Get parameter result + result = parameter(**kwargs) + + # Result "None causes issues, so it's converted to NaN" + if result is None: + result = np.nan + + self.data_handler.add_measurement_result( + action_indices=self.action_indices, + result=result, + parameter=parameter, + name=name, + label=label, + unit=unit, + ) + + return result + + def _measure_multi_parameter( + self, multi_parameter: MultiParameter, name: str = None, **kwargs + ) -> Any: + """Measure MultiParameter and store results + + Called from `measure` + + Args: + parameter: Parameter to be measured + name: Name used to measure parameter, overriding ``parameter.name`` + **kwargs: optional kwargs passed to parameter, i.e. ``parameter(**kwargs)`` + + Returns: + Current value of parameter + + Notes: + - Does not store setpoints yet + """ + name = name or multi_parameter.name + + # Ensure measuring multi_parameter matches the current action_indices + self._verify_action(action=multi_parameter, name=name, add_if_new=True) + + with self.timings.record(["measurement", self.action_indices, "get"]): + results_list = multi_parameter(**kwargs) + + results = dict(zip(multi_parameter.names, results_list)) + + if name is None: + name = multi_parameter.name + + with MeasurementLoop(name) as msmt: + for k, (key, val) in enumerate(results.items()): + msmt.measure( + val, + name=key, + parameter=multi_parameter, + label=multi_parameter.labels[k], + unit=multi_parameter.units[k], + ) + + return results + + def _measure_callable( + self, measurable_function: Callable, name: str = None, **kwargs + ) -> Dict[str, Any]: + """Measure a callable (function) and store results + + The function should return a dict, from which each item is measured. + If the function already contains creates a Measurement, the return + values aren't stored. + + Args: + name: Dataset name used for function. Extracts name from function if not provided + **kwargs: optional kwargs passed to callable, i.e. ``callable(**kwargs)`` + """ + # Determine name + if name is None: + if hasattr(measurable_function, "__self__") and isinstance( + measurable_function.__self__, InstrumentBase + ): + name = measurable_function.__self__.name + elif hasattr(measurable_function, "__name__"): + name = measurable_function.__name__ + else: + action_indices_str = "_".join(str(idx) for idx in self.action_indices) + name = f"data_group_{action_indices_str}" + + # Record action_indices before the callable is called + action_indices = self.action_indices + + results = measurable_function(**kwargs) + + if self.action_indices != action_indices: + # Measurements have been performed in this function, don't measure anymore + return + + # Ensure measuring callable matches the current action_indices + self._verify_action(action=measurable_function, name=name, add_if_new=True) + + # Check if the callable already performed a nested measurement + # In this case, the nested measurement is stored as a data_group, and + # has loop indices corresponding to the current ones. + msmt = MeasurementLoop.running_measurement + data_group = msmt.data_groups.get(action_indices) + if getattr(data_group, "loop_indices", None) != self.loop_indices: + # No nested measurement has been performed in the callable. + # Add results, which should be dict, by creating a nested measurement + if not isinstance(results, dict): + raise SyntaxError(f"{name} results must be a dict, not {results}") + + with MeasurementLoop(name) as msmt: + for key, val in results.items(): + msmt.measure(val, name=key) + + return results + + def _measure_array( + self, + array: Union[list, np.ndarray], + name: str, + label: str = None, + unit: str = None, + setpoints: 'Sweep' = None + ): + # Determine + ndim = np.ndim(array) + + setpoints_list = [] + + # Ensure setpoints is a Sweep + if setpoints is None: + # Create setpoints for each dimension + for dim, num in enumerate(np.shape(array)): + sweep = Sweep( + range(num), + name='setpoint_idx' + (f'_{dim}' if np.ndim(array) > 1 else ''), + label='Setpoint index' + (f' dim_{dim}' if np.ndim(array) > 1 else '') + ) + setpoints_list.append(sweep) + elif isinstance(setpoints, BaseSweep): + # Setpoints is a single Sweep + assert ndim == 1 + assert len(setpoints) == len(array) + if isinstance(setpoints, Iterate): + setpoints = setpoints.convert_to_Sweep() + + setpoints_list = [setpoints] + elif isinstance(setpoints, (list, np.ndarray)): + if isinstance(setpoints[0], BaseSweep): + setpoints_list = [] + for setpoint in setpoints: + if isinstance(setpoint, Iterate): + setpoint = setpoint.convert_to_Sweep() + setpoints_list.append(setpoint) + else: + # Convert sequence to Sweep + setpoints_list = [Sweep(setpoints, name='setpoint_idx', label='Setpoint index')] + else: + raise SyntaxError('Cannot measure because array setpoints not understood') + + # Enter sweep + for setpoints in setpoints_list: + iter(setpoints) + + # Ensure measuring array matches the current action_indices + self._verify_action(action=None, name=name, add_if_new=True) + + self.data_handler.add_measurement_result( + action_indices=self.action_indices, + result=array, + parameter=None, + name=name, + label=label, + unit=unit, + ) + + for setpoints in reversed(setpoints_list): + setpoints.exit_sweep() + + def _measure_dict(self, value: dict, name: str) -> Dict[str, Any]: + """Store dictionary results + + Each key is an array name, and the value is the value to store + + Args: + value: dictionary with (str, value) entries. + Each element is a separate dataset array + name: Dataset name used for dictionary + """ + if not isinstance(value, dict): + raise SyntaxError(f"{name} must be a dict, not {value}") + + if not isinstance(name, str) or name == "": + raise SyntaxError(f"Dict result {name} must have a valid name: {value}") + + # Ensure measuring callable matches the current action_indices + self._verify_action(action=None, name=name, add_if_new=True) + + with MeasurementLoop(name) as msmt: + for key, val in value.items(): + msmt.measure(val, name=key) + + return value + + def _measure_value( + self, + value: Union[float, int, bool], + name: str, + parameter: Optional[_BaseParameter] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + ) -> Union[float, int, bool]: + """Store a single value (float/int/bool) + + If this value comes from another parameter acquisition, e.g. from a + MultiParameter, the parameter can be passed to use the right set arrays. + + Args: + value: Value to be stored + name: Name used for storage + parameter: optional parameter that is passed on to + `MeasurementLoop.measure` as a kwarg, in which case it's used + for name, label, etc. + label: Optional label for dat array + unit: Optional unit for data array + """ + if name is None: + if parameter is not None: + name = parameter.name + else: + raise RuntimeError("Must provide a name when measuring a value") + + # Ensure measuring callable matches the current action_indices + self._verify_action(action=None, name=name, add_if_new=True) + + if isinstance(value, np.integer): + value = int(value) + elif isinstance(value, np.floating): + value = float(value) + elif isinstance(value, (bool, np.bool_)): + value = int(value) + + self.data_handler.add_measurement_result( + action_indices=self.action_indices, + result=value, + parameter=parameter, + name=name, + label=label, + unit=unit, + ) + return value + + def measure( + self, + measurable: Union[ + Parameter, Callable, dict, float, int, bool, np.ndarray, None + ], + name: Optional[str] = None, + *, # Everything after here must be a kwarg + label: Optional[str] = None, + unit: Optional[str] = None, + setpoints: Optional[Union['Sweep', Sequence]] = None, + timestamp: bool = False, + **kwargs, + ) -> Any: + """Perform a single measurement of a Parameter, function, etc. + + + Args: + measurable: Item to measure. Can be one of the following: + Parameter + Callable function/method, which should either perform a nested + Measurement, or return a dict. + In the case of returning a dict, all the key/value pairs + are grouped together. + float, int, bool, array + name: Optional name for measured element or data group. + If the measurable is a float, int, bool, or array, the name is + mandatory. + Otherwise, the default name is used. + label: Optional label, is ignored if measurable is a Parameter or callable + unit: Optional unit, is ignored if measurable is a Parameter or callable + setpoints: Optional setpoints if measuring an array, can be sequence or Sweep + timestamp: If True, the timestamps immediately before and after this + measurement are recorded + + Returns: + Return value of measurable + """ + if not self.is_context_manager: + raise RuntimeError( + "Must use the Measurement as a context manager, " + "i.e. 'with Measurement(name) as msmt:'" + ) + if self.is_stopped: + raise SystemExit("Measurement.stop() has been called") + if threading.current_thread() is not MeasurementLoop.measurement_thread: + raise RuntimeError( + "Cannot measure while another measurement is already running " + "in a different thread." + ) + + if self != MeasurementLoop.running_measurement: + # Since this Measurement is not the running measurement, it is a + # DataGroup in the running measurement. Delegate measurement to the + # running measurement + return MeasurementLoop.running_measurement.measure( + measurable, name=name, label=label, unit=unit, setpoints=setpoints, **kwargs + ) + + # Code from hereon is only reached by the primary measurement, + # i.e. the running_measurement + + # Wait as long as the measurement is paused + while self.is_paused: + sleep(0.1) + + t0 = perf_counter() + initial_action_indices = self.action_indices + + # Optionally record timestamp before measurement has been recorded + if timestamp: + t_now = datetime.now() + + # Store time referenced to t_start + self.measure( + (t_now - self._t_start).total_seconds(), + "T_pre", + unit="s", + timestamp=False, + ) + self.skip() # Increment last action index by 1 + + # TODO Incorporate kwargs name, label, and unit, into each of these + if isinstance(measurable, Parameter): + result = self._measure_parameter( + measurable, name=name, label=label, unit=unit, **kwargs + ) + self.skip() # Increment last action index by 1 + elif isinstance(measurable, MultiParameter): + result = self._measure_multi_parameter(measurable, name=name, **kwargs) + elif callable(measurable): + result = self._measure_callable(measurable, name=name, **kwargs) + elif isinstance(measurable, dict): + result = self._measure_dict(measurable, name=name) + elif isinstance(measurable, (list, np.ndarray)): + result = self._measure_array(measurable, name=name, setpoints=setpoints) + elif isinstance(measurable, RAW_VALUE_TYPES): + result = self._measure_value( + measurable, name=name, label=label, unit=unit, **kwargs + ) + self.skip() # Increment last action index by 1 + else: + raise RuntimeError( + f"Cannot measure {measurable} as it cannot be called, and it " + f"is not a dict, int, float, bool, or numpy array." + ) + + # Optionally show progress bar + if self.show_progress: + try: + self._update_progress_bar( + action_indices=initial_action_indices, + description=f'Measuring {self.action_names.get(initial_action_indices)}', + create_if_new=True + ) + except Exception as e: + warn(f'Failed to update progress bar. Error: {e}') + + # Optionally record timestamp after measurement has been recorded + if timestamp: + t_now = datetime.now() + + # Store time referenced to t_start + self.measure( + (t_now - self._t_start).total_seconds(), + "T_post", + unit="s", + timestamp=False, + ) + self.skip() # Increment last action index by 1 + + self.timings.record( + ["measurement", initial_action_indices, "total"], perf_counter() - t0 + ) + + return result + + def measure_threaded(self, params): + if all(isinstance(param, Parameter) for param in params): + with concurrent.futures.ThreadPoolExecutor() as executor: + threads = [executor.submit(param) for param in params] + + results = [thread.result() for thread in threads] + + for param, result in zip(params, results): + self.measure(result, parameter=param) + else: + results = [self.measure(param) for param in params] + return results + + # Methods related to masking of parameters/attributes/keys + def _mask_attr(self, obj: object, attr: str, value) -> Any: + """Temporarily override an object attribute during the measurement. + + The value will be reset at the end of the measurement + This can also be a nested measurement. + + Args: + obj: Object whose value should be masked + attr: Attribute to be masked + val: Masked value + + Returns: + original value + """ + + original_value = getattr(obj, attr) + setattr(obj, attr, value) + + self._masked_properties.append( + { + "unmask_type": "attr", + "obj": obj, + "attr": attr, + "original_value": original_value, + "value": value, + } + ) + + return original_value + + def _mask_parameter(self, param: _BaseParameter, value: Any) -> Any: + """Temporarily override a parameter value during the measurement. + + The value will be reset at the end of the measurement. + This can also be a nested measurement. + + Args: + param: Parameter whose value should be masked + val: Masked value + + Returns: + original value + """ + original_value = param() + param(value) + + self._masked_properties.append( + { + "unmask_type": "parameter", + "obj": param, + "original_value": original_value, + "value": value, + } + ) + + return original_value + + def _mask_key(self, obj: dict, key: str, value: Any) -> Any: + """Temporarily override a dictionary key during the measurement. + + The value will be reset at the end of the measurement + This can also be a nested measurement. + + Args: + obj: dictionary whose value should be masked + key: key to be masked + val: Masked value + + Returns: + original value + """ + original_value = obj[key] + obj[key] = value + + self._masked_properties.append( + { + "unmask_type": "key", + "obj": obj, + "key": key, + "original_value": original_value, + "value": value, + } + ) + + return original_value + + def mask(self, obj: Union[object, dict], val: Any = None, **kwargs) -> Any: + """Mask a key/attribute/parameter for the duration of the Measurement + + Multiple properties can be masked by passing as kwargs. + Masked properties are reverted at the end of the measurement, even if + the measurement crashes + + Args: + obj: Object from which to mask property. + For a dict, an item is masked. + For a ParameterNode, a parameter is masked. + For a parameter, the value is masked. + For all other objects, an attribute is masked. + val: Masked value, only relevant if obj is a parameter + **kwargs: Masked properties + + Returns: + List of original values before masking, or single value if parameter is passed + + Examples: + ``` + node = ParameterNode() + node.p1 = Parameter(initial_value=1, set_cmd=None) + + with Measurement("test_masking") as msmt: + msmt.mask(node, p1=2) + print(f"node.p1 has value {node.p1}") + >>> node.p1 has value 2 + print(f"node.p1 has value {node.p1}") + >>> node.p1 has value 1 + ``` + """ + if isinstance(obj, InstrumentBase): + assert val is None + # kwargs can be either parameters or attrs + return [ + self._mask_parameter(obj.parameters[key], val) + if key in obj.parameters + else self._mask_attr(obj, key, val) + for key, val in kwargs.items() + ] + if isinstance(obj, Parameter) and not kwargs: + # if kwargs are passed, they are to be treated as attrs + return self._mask_parameter(obj, val) + elif isinstance(obj, dict): + if not kwargs: + raise SyntaxError("Must pass kwargs when masking a dict") + return [self._mask_key(obj, key, val) for key, val in kwargs.items()] + else: + if not kwargs: + raise SyntaxError("Must pass kwargs when masking") + return [self._mask_attr(obj, key, val) for key, val in kwargs.items()] + + def unmask( + self, + obj: Union[_BaseParameter, object, dict], + attr: Optional[str] = None, + key: Optional[str] = None, + unmask_type: Optional[str] = None, + value: Optional[Any] = None, + raise_exception: bool = True, + remove_from_list: bool = True, + **kwargs, # Add kwargs because original_value may be None + ) -> None: + """Unmasks a previously masked object, i.e. revert value back to original + + Args: + obj: Parameter/object/dictionary for which to revert attribute/key + attr: object attribute to revert + key: dictionary key to revert + type: can be 'key', 'attr', 'parameter' if not explicitly provided by kwarg + value: Optional masked value, only used for logging + raise_exception: Whether to raise exception if unmasking fails + remove_from_list: Whether to remove the masked property from the list + msmt._masked_properties. This ensures we don't unmask twice. + """ + if "original_value" not in kwargs: + # No masked property passed. We collect all the masked properties + # that satisfy these requirements and unmask each of them. + unmask_properties = [] + remaining_masked_properties = [] + for masked_property in self._masked_properties: + if masked_property["obj"] != obj: + remaining_masked_properties.append(masked_property) + elif attr is not None and masked_property.get("attr") != attr: + remaining_masked_properties.append(masked_property) + elif key is not None and masked_property.get("key") != key: + remaining_masked_properties.append(masked_property) + else: + unmask_properties.append(masked_property) + + for unmask_property in reversed(unmask_properties): + self.unmask(**unmask_property) + + if remove_from_list: + self._masked_properties = remaining_masked_properties + else: + # A masked property has been passed, which we unmask here + try: + original_value = kwargs["original_value"] + if unmask_type is None: + if isinstance(obj, Parameter): + unmask_type = "parameter" + elif isinstance(obj, dict): + unmask_type = "key" + elif hasattr(obj, attr): + unmask_type = "attr" + + if unmask_type == "key": + obj[key] = original_value + elif unmask_type == "attr": + setattr(obj, attr, original_value) + elif unmask_type == "parameter": + obj(original_value) + else: + raise SyntaxError(f"Unmask type {unmask_type} not understood") + + # Try to find masked property and remove from list + if remove_from_list: + for masked_property in reversed(self._masked_properties): + if masked_property["obj"] != obj: + continue + elif attr is not None and masked_property.get("attr") != attr: + continue + elif key is not None and masked_property.get("key") != key: + continue + else: + self._masked_properties.remove(masked_property) + break + + except Exception as e: + self.log( + f"Could not unmask {obj} {unmask_type} from masked value {value} " + f"to original value {original_value}\n" + f"{traceback.format_exc()}", + level="error", + ) + + if raise_exception: + raise e + + def unmask_all(self) -> None: + """Unmask all masked properties""" + masked_properties = reversed(self._masked_properties) + for masked_property in masked_properties: + self.unmask(**masked_property, raise_exception=False) + self._masked_properties.clear() + + # Functions relating to measurement flow + def pause(self) -> None: + """Pause measurement at start of next parameter sweep/measurement""" + running_measurement().is_paused = True + + def resume(self) -> None: + """Resume measurement after being paused""" + running_measurement().is_paused = False + + def stop(self) -> None: + """Stop measurement at start of next parameter sweep/measurement""" + running_measurement().is_stopped = True + # Unpause loop + running_measurement().resume() + + def skip(self, N: int = 1) -> Tuple[int]: + """Skip an action index. + + Useful if a measure is only sometimes run + + Args: + N: number of action indices to skip + + Returns: + Measurement action_indices after skipping + + Examples: + This measurement repeatedly creates a random value. + It then stores the value twice, but the first time the value is + only stored if it is above a threshold. Notice that if the random + value is not above this threshold, the second measurement would + become the first measurement if msmt.skip is not called + ``` + with Measurement("skip_measurement") as msmt: + for k in Sweep(range(10)): + random_value = np.random.rand() + if random_value > 0.7: + msmt.measure(random_value, "random_value_conditional") + else: + msmt.skip() + + msmt.measure(random_value, "random_value_unconditional) + ``` + """ + if running_measurement() is not self: + return running_measurement().skip(N=N) + else: + action_indices = list(self.action_indices) + action_indices[-1] += N + self.action_indices = tuple(action_indices) + return self.action_indices + + def step_out(self, reduce_dimension: bool = True) -> None: + """Step out of a Sweep + + This function usually doesn't need to be called. + """ + if MeasurementLoop.running_measurement is not self: + MeasurementLoop.running_measurement.step_out( + reduce_dimension=reduce_dimension + ) + else: + if reduce_dimension: + self.loop_shape = self.loop_shape[:-1] + self.loop_indices = self.loop_indices[:-1] + + # Remove last action index and increment one before that by one + action_indices = list(self.action_indices[:-1]) + action_indices[-1] += 1 + self.action_indices = tuple(action_indices) + + def traceback(self) -> None: + """Print traceback if an error occurred. + + Measurement must be ran from separate thread + """ + if self.measurement_thread is None: + raise RuntimeError("Measurement was not started in separate thread") + + self.measurement_thread.traceback() + + +def running_measurement() -> MeasurementLoop: + """Return the running measurement""" + return MeasurementLoop.running_measurement + + +class _IterateDondSweep: + """Class used to encapsulate `AbstractSweep` into `Sweep` as a `Sweep.sequence`""" + + def __init__(self, sweep: AbstractSweep): + self.sweep: AbstractSweep = sweep + self.iterator: Iterable = None + self.parameter: _BaseParameter = sweep._param + + def __len__(self) -> int: + return self.sweep.num_points + + def __iter__(self) -> Iterable: + self.iterator = iter(self.sweep.get_setpoints()) + return self + + def __next__(self) -> float: + value = next(self.iterator) + self.sweep._param(value) + + for action in self.sweep.post_actions: + action() + + if self.sweep.delay: + sleep(self.sweep.delay) + + return value + + +class BaseSweep(AbstractSweep): + """Sweep over an iterable inside a Measurement + + Args: + sequence: Sequence to iterate over. + Can be an iterable, or a parameter Sweep. + If the sequence + name: Name of sweep. Not needed if a Parameter is passed + label: Label of sweep. Not needed if a Parameter is passed + unit: unit of sweep. Not needed if a Parameter is passed + parameter: Optional parameter that is being swept over. + If provided, the parameter value will be updated every + time the sweep is looped over + revert: Stores the state of a parameter before sweeping it, + then reverts the original value upon exiting the loop. + delay: Wait time after setting value (default zero). + initial_delay: Delay directly after the first element. + + Examples: + ``` + with Measurement("sweep_msmt") as msmt: + for value in Sweep(np.linspace(5), "sweep_values"): + msmt.measure(value, "linearly_increasing_value") + + p = Parameter("my_parameter") + for param_val in Sweep(p. + ``` + """ + plot_function = None + DEFAULT_MEASURE_THREADED = False + + def __init__( + self, + sequence: Union[Iterable, SweepValues, AbstractSweep], + name: Optional[str] = None, + label: Optional[str] = None, + unit: Optional[str] = None, + parameter: Optional[_BaseParameter] = None, + revert: bool = False, + delay: Optional[float] = None, + initial_delay: Optional[float] = None, + ): + if isinstance(sequence, AbstractSweep): + sequence = _IterateDondSweep(sequence) + elif not isinstance(sequence, Iterable): + raise SyntaxError(f"Sweep sequence must be iterable, not {type(sequence)}") + + # Properties for the data array + self.name: Optional[str] = name + self.label: Optional[str] = label + self.unit: Optional[str] = unit + self.parameter: _BaseParameter = parameter + + self.sequence: Union[Iterable, SweepValues, AbstractSweep] = sequence + self.dimension: Optional[int] = None + self.loop_index: Optional[Tuple[int]] = None + self.iterator: Optional[Iterable] = None + self.revert: bool = revert + self._delay: Optional[float] = delay + self.initial_delay: Optional[float] = initial_delay + + # setpoint_info will be populated once the sweep starts + self.setpoint_info: Optional[Dict[str, Any]] = None + + # Validate values + if self.parameter is not None and hasattr(self.parameter, "validate"): + for value in self.sequence: + self.parameter.validate(value) + + def __repr__(self) -> str: + components = [] + + # Add parameter or name + if self.parameter is not None: + components.append(f"parameter={self.parameter}") + elif self.name is not None: + components.append(f"{self.name}") + + # Add number of elements + num_elems = str(len(self.sequence)) if self.sequence is not None else "unknown" + components.append(f"length={num_elems}") + + # Combine components + components_str = ", ".join(components) + return f"Sweep({components_str})" + + def __len__(self) -> int: + return len(self.sequence) + + def __iter__(self) -> Iterable: + if threading.current_thread() is not MeasurementLoop.measurement_thread: + raise RuntimeError( + "Cannot create a Sweep while another measurement " + "is already running in a different thread." + ) + + msmt = running_measurement() + if msmt is None: + raise RuntimeError("Cannot start a sweep outside a Measurement") + + if self.revert: + if isinstance(self.sequence, SweepValues): + msmt.mask(self.sequence.parameter, self.sequence.parameter.get()) + elif self.parameter is not None: + msmt.mask(self.parameter, self.parameter.get()) + else: + raise NotImplementedError("Unable to revert non-parameter values.") + + self.loop_index = 0 + self.dimension = len(msmt.loop_shape) + self.iterator = iter(self.sequence) + + # Create setpoint_list + self.setpoint_info = self.initialize() + + msmt.loop_shape += (len(self.sequence),) + msmt.loop_indices += (self.loop_index,) + msmt.action_indices += (0,) + + return self + + def __next__(self) -> Any: + msmt = running_measurement() + + if not msmt.is_context_manager: + raise RuntimeError( + "Must use the Measurement as a context manager, " + "i.e. 'with Measurement(name) as msmt:'" + ) + + if msmt.is_stopped: + raise SystemExit + + # Wait as long as the measurement is paused + while msmt.is_paused: + sleep(0.1) + + # Increment loop index of current dimension + loop_indices = list(msmt.loop_indices) + loop_indices[self.dimension] = self.loop_index + msmt.loop_indices = tuple(loop_indices) + + try: # Perform loop action + sweep_value = next(self.iterator) + # Remove last action index and increment one before that by one + action_indices = list(msmt.action_indices) + action_indices[-1] = 0 + msmt.action_indices = tuple(action_indices) + except StopIteration: # Reached end of iteration + self.exit_sweep() + raise StopIteration + + # Set parameter if passed along + if self.parameter is not None and self.parameter.settable: + self.parameter(sweep_value) + + # Optional wait after settings value + if self.initial_delay and self.loop_index == 0: + sleep(self.initial_delay) + if self.delay: + sleep(self.delay) + + self.setpoint_info["latest_value"] = sweep_value + + self.loop_index += 1 + + return sweep_value + + def __call__( + self, + *args: Optional[Iterable["BaseSweep"]], + name: str = None, + measure_params: Union[Iterable, _BaseParameter] = None, + repetitions: int = 1, + sweep: Union[Iterable, "BaseSweep"] = None, + plot: bool = False, + ): + """Perform sweep, identical to `Sweep.execute` + + + Args: + *args: Optional additional sweeps used for N-dimensional measurements + The first arg is the outermost sweep dimension, and the sweep on which + `Sweep.execute` was called is the innermost dimension. + name: Dataset name, defaults to a concatenation of sweep parameter names + measure_params: Parameters to measure. + If not provided, it will check the attribute ``Station.measure_params`` + for parameters. Raises an error if undefined. + repetitions: Number of times to repeat measurement, defaults to 1. + This will be the outermost loop if set to a value above 1. + sweep: Identical to passing *args. + Note that ``sweep`` can be either a single Sweep, or a Sweep list. + + Returns: + Dataset corresponding to measurement + """ + return self.execute( + *args, + name=name, + measure_params=measure_params, + repetitions=repetitions, + sweep=sweep, + plot=plot + ) + + def initialize(self) -> Dict[str, Any]: + """Initializes a `Sweep`, attaching it to the current `MeasurementLoop`""" + msmt = running_measurement() + if msmt.action_indices in msmt.setpoint_list: + return msmt.setpoint_list[msmt.action_indices] + + # Determine sweep parameter + if self.parameter is None: + if isinstance(self.sequence, _IterateDondSweep): + # sweep is a doNd sweep that already has a parameter + self.parameter = self.sequence.parameter + else: + # Need to create a parameter + self.parameter = Parameter( + name=self.name, label=self.label, unit=self.unit + ) + + setpoint_info = { + "sweep": self, + "parameter": self.parameter, + "latest_value": None, + "registered": False, + } + + # Add to setpoint list + msmt.setpoint_list[msmt.action_indices] = setpoint_info + + # Add to measurement actions + assert msmt.action_indices not in msmt.actions + msmt.actions[msmt.action_indices] = self + + return setpoint_info + + def exit_sweep(self) -> None: + """Exits sweep, stepping out of the current `Measurement.action_indices`""" + msmt = running_measurement() + if self.revert: + if isinstance(self.sequence, SweepValues): + msmt.unmask(self.sequence.parameter) + elif self.parameter is not None: + msmt.unmask(self.parameter) + msmt.step_out(reduce_dimension=True) + + def execute( + self, + *args: Optional[Iterable["BaseSweep"]], + name: str = None, + measure_params: Union[Iterable, _BaseParameter] = None, + repetitions: int = 1, + sweep: Union[Iterable, "BaseSweep"] = None, + thread=None, + plot: bool = False, + ) -> DataSetProtocol: + """Performs a measurement using this sweep + + Args: + *args: Optional additional sweeps used for N-dimensional measurements + The first arg is the outermost sweep dimension, and the sweep on which + `Sweep.execute` was called is the innermost dimension. + name: Dataset name, defaults to a concatenation of sweep parameter names + measure_params: Parameters to measure. + If not provided, it will check the attribute ``Station.measure_params`` + for parameters. Raises an error if undefined. + repetitions: Number of times to repeat measurement, defaults to 1. + This will be the outermost loop if set to a value above 1. + sweep: Identical to passing *args. + Note that ``sweep`` can be either a single Sweep, or a Sweep list. + + Returns: + Dataset corresponding to measurement + """ + # Get "measure_params" from station if not provided + if measure_params is None: + station = Station.default + if station is None or not getattr(station, "measure_params", None): + raise RuntimeError( + "Cannot determine parameters to measure. " + "Either provide measure_params, or set station.measure_params" + ) + measure_params = station.measure_params + + if thread is None: + thread = self.DEFAULT_MEASURE_THREADED + + # Convert measure_params to list if it is a single param + if isinstance(measure_params, _BaseParameter): + measure_params = [measure_params] + + # Create list of sweeps + sweeps = list(args) + if isinstance(sweep, BaseSweep): + sweeps.append(sweep) + elif isinstance(sweep, (list, tuple)): + sweeps.extend(sweep) + + if not all(isinstance(sweep, BaseSweep) for sweep in sweeps): + raise ValueError("Args passed to Sweep.execute must be Sweeps") + + # Add repetition as a sweep if > 1 + if repetitions > 1: + repetition_sweep = BaseSweep(range(repetitions), name="repetition") + sweeps = [repetition_sweep] + sweeps + + # Add self as innermost sweep + sweeps += [self] + + # Determine "name" if not provided from sweeps + if name is None: + dimensionality = 1 + len(sweeps) + sweep_names = [str(sweep.name) for sweep in sweeps] + [str(self.name)] + name = f"{dimensionality}D_sweep_" + "_".join(sweep_names) + + with MeasurementLoop(name) as msmt: + measure_sweeps(sweeps=sweeps, measure_params=measure_params, msmt=msmt, thread=thread) + + if plot and Sweep.plot_function is not None and MeasurementLoop.running_measurement is None: + Sweep.plot_function(msmt.dataset) + plt.show() + + return msmt.dataset + + # Methods needed to make BaseSweep subclass of AbstractSweep + def get_setpoints(self) -> np.ndarray: + return self.sequence + + @property + def param(self) -> ParameterBase: + # TODO create necessary parameter if self.parameter is None + return self.parameter + + @property + def num_points(self) -> float: + return len(self.sequence) + + @property + def delay(self) -> float: + """ + Delay between two consecutive sweep points. + """ + return self._delay or 0 + + @property + def post_actions(self) -> ActionsT: + # TODO maybe add option for post actions + # However this can cause issues if sweep is prematurely exited + return [] + + +class Sweep(BaseSweep): + """Default class to create a sweep in `do1d`, `do2d`, `dond` and `MeasurementLoop` + + A Sweep can be created through its kwargs (listed below). For the most frequent + use-cases, a Sweep can also be created by passing args in a variety of ways: + + 1 arg: + - Sweep([1,2,3], name="name") + : sweep over sequence [1,2,3] with sweep array name "name" + Note that kwarg "name" must be provided + - Sweep(parameter, stop=stop_val) + : sweep "parameter" from current value to "stop_val" + - Sweep(parameter, around=around_val) + : sweep "parameter" around current value with range "around_val" + : Note that this will set ``revert`` to True if not explicitly False + 2 args: + - Sweep(parameter, [1,2,3]) + : sweep "parameter" over sequence [1,2,3] + - Sweep([1,2,3], "name") + : sweep over sequence [1,2,3] with sweep array name "name" + 3 args: + - Sweep(parameter, start_val, stop_val) + : sweep "parameter" from "start_val" to "stop_val" + If "num" or "step" is not given as kwarg, it will check if "num" or "step" + is set in dict "parameter.sweep_defaults" and use that, or else raise an error. + 4 args: + - Sweep(parameter, start_val, stop_val, num) + : Sweep "parameter" from "start_val" to "stop_val" with "num" number of points + + Args: + start: start value of sweep sequence + Cannot be used together with ``around`` + stop: stop value of sweep sequence + Cannot be used together with ``around`` + around: sweep around the current parameter value. + ``start`` and ``stop`` are defined from ``around`` and the current value + i.e. start=X-dx, stop=X+dx when current_value=X and around=dx. + Passing the kwarg "around" also sets revert=True unless explicitly set False + num: Number of points between start and stop. + Cannot be used together with ``step`` + step: Increment from start to stop. + Cannot be used together with ``num`` + delay: Time delay after incrementing to the next value + initial_delay: Time delay after having incremented to its first value + name: Sweep name, overrides parameter.name + label: Sweep label, overrides parameter.label + unit: Sweep unit, overrides parameter.unit + revert: Revert parameter back to original value after the sweep ends. + This is False by default, unless the kwarg ``around`` is passed + """ + + sequence_keywords = [ + "start", + "stop", + "around", + "num", + "step", + "parameter", + "sequence", + ] + base_keywords = [ + "delay", + "initial_delay", + "name", + "label", + "unit", + "revert", + "parameter", + ] + + def __init__( + self, + *args, + start: float = None, + stop: float = None, + around: float = None, + num: int = None, + step: float = None, + delay: float = None, + initial_delay: float = None, + name: str = None, + label: str = None, + unit: str = None, + revert: bool = None, + ): + kwargs = dict( + start=start, + stop=stop, + around=around, + num=num, + step=step, + delay=delay, + initial_delay=initial_delay, + name=name, + label=label, + unit=unit, + revert=revert, + ) + + sequence_kwargs, base_kwargs = self._transform_args_to_kwargs(*args, **kwargs) + + self.sequence: Iterable = self._generate_sequence(**sequence_kwargs) + + super().__init__(sequence=self.sequence, **base_kwargs) + + def _transform_args_to_kwargs(self, *args, **kwargs) -> Tuple[dict]: + """Transforms sweep initialization args to kwargs. + Allowed args are: + + 1 arg: + - Sweep([1,2,3], name="name") + : sweep over sequence [1,2,3] with sweep array name "name" + Note that kwarg "name" must be provided + - Sweep(parameter, stop=stop_val) + : sweep "parameter" from current value to "stop_val" + - Sweep(parameter, around=around_val) + : sweep "parameter" around current value with range "around_val" + : Note that this will set ``revert`` to True if not explicitly False + 2 args: + - Sweep(parameter, [1,2,3]) + : sweep "parameter" over sequence [1,2,3] + - Sweep([1,2,3], "name") + : sweep over sequence [1,2,3] with sweep array name "name" + 3 args: + - Sweep(parameter, start_val, stop_val) + : sweep "parameter" from "start_val" to "stop_val" + If "num" or "step" is not given as kwarg, it will check if "num" or "step" + if set in dict "parameter.sweep_defaults" and use that, or raise an error otherwise. + 4 args: + - Sweep(parameter, start_val, stop_val, num) + : Sweep "parameter" from "start_val" to "stop_val" with "num" number of points + """ + if len(args) == 1: # Sweep([1,2,3], name="name") + if isinstance(args[0], Iterable): + if kwargs.get("name") is None: + kwargs["name"] = "iteration" + if kwargs.get("label") is None: + kwargs["label"] = "Iteration" + (kwargs["sequence"],) = args + elif isinstance(args[0], _BaseParameter): + assert ( + kwargs.get("stop") is not None or kwargs.get("around") is not None + ), "Must provide stop value for parameter" + (kwargs["parameter"],) = args + elif isinstance(args[0], AbstractSweep): + kwargs["sequence"] = _IterateDondSweep(args[0]) + parameter = kwargs["sequence"].parameter + kwargs["name"] = kwargs["name"] or parameter.name + kwargs["label"] = kwargs["label"] or parameter.label + kwargs["unit"] = kwargs["unit"] or parameter.unit + else: + raise SyntaxError( + "Sweep with 1 arg must have iterable or parameter as arg" + ) + elif len(args) == 2: + if isinstance(args[0], _BaseParameter): # Sweep(parameter, [1,2,3]) + if isinstance(args[1], Iterable): + kwargs["parameter"], kwargs["sequence"] = args + else: + raise SyntaxError( + "Sweep with Parameter arg and second arg should have second arg" + " be a sequence" + ) + elif isinstance(args[0], Iterable): # Sweep([1,2,3], "name") + assert isinstance(args[1], str) + assert kwargs.get("name") is None + kwargs["sequence"], kwargs["name"] = args + else: + raise SyntaxError( + "Unknown sweep syntax. Either use 'Sweep(parameter, sequence)' or " + "'Sweep(sequence, name)'" + ) + elif len(args) == 3: # Sweep(parameter, 0, 1) + assert isinstance(args[0], _BaseParameter) + assert isinstance(args[1], (float, int)) + assert isinstance(args[2], (float, int)) + assert kwargs.get("start") is None + assert kwargs.get("stop") is None + kwargs["parameter"], kwargs["start"], kwargs["stop"] = args + elif len(args) == 4: # Sweep(parameter, 0, 1, 151) + assert isinstance(args[0], _BaseParameter) + assert isinstance(args[1], (float, int)) + assert isinstance(args[2], (float, int)) + assert isinstance(args[3], (float, int)) + assert kwargs.get("start") is None + assert kwargs.get("stop") is None + assert kwargs.get("num") is None + kwargs["parameter"], kwargs["start"], kwargs["stop"], kwargs["num"] = args + + # Use parameter name, label, and unit if not explicitly provided + if kwargs.get("parameter") is not None: + kwargs.setdefault("name", kwargs["parameter"].name) + kwargs.setdefault("label", kwargs["parameter"].label) + kwargs.setdefault("unit", kwargs["parameter"].unit) + + # Update kwargs with sweep_defaults from parameter + if hasattr(kwargs["parameter"], "sweep_defaults"): + for key, val in kwargs["parameter"].sweep_defaults.items(): + if key == 'num' and kwargs.get('step') is not None: + continue + if kwargs.get(key) is None: + kwargs[key] = val + + # Revert parameter to original value if kwarg "around" is passed + # and "revert" is not explicitly False + if kwargs["around"] is not None and kwargs["revert"] is None: + kwargs["revert"] = True + + sequence_kwargs = {key: kwargs.get(key) for key in self.sequence_keywords} + base_kwargs = {key: kwargs.get(key) for key in self.base_keywords} + + return sequence_kwargs, base_kwargs + + def _generate_sequence( + self, + start: Optional[float] = None, + stop: Optional[float] = None, + around: Optional[float] = None, + num: Optional[int] = None, + step: Optional[float] = None, + parameter: Optional[_BaseParameter] = None, + sequence: Optional[Iterable] = None, + ) -> Sequence: + """Creates a sequence from passed values""" + # Return "sequence" if explicitly provided + if sequence is not None: + return sequence + + # Verify that "around" is used with "parameter" but not with "start" and "stop" + if around is not None: + if start is not None or stop is not None: + raise SyntaxError( + "Cannot pass kwarg 'around' and also 'start' or 'stop'" + ) + elif parameter is None: + raise SyntaxError("Cannot use kwarg 'around' without a parameter") + + # Convert "around" to "start" and "stop" using parameter current value + center_value = parameter() + if center_value is None: + raise ValueError( + "Parameter must have initial value if 'around' keyword is used" + ) + start = center_value - around + stop = center_value + around + elif stop is not None: + # Use "parameter" current value if "start" is not provided + if start is None: + if parameter is None: + raise SyntaxError( + "Cannot use 'stop' without 'start' or a 'parameter'" + ) + start = parameter() + if start is None: + raise ValueError( + "Parameter must have initial value if start is not explicitly provided" + ) + else: + raise SyntaxError("Must provide either 'around' or 'stop'") + + if num is not None: + sequence = np.linspace(start, stop, num) + elif step is not None: + # Ensure step is positive + step = abs(step) if stop > start else -abs(step) + + sequence = np.arange(start, stop, step) + + # Append final datapoint + if abs((stop - sequence[-1]) / step) > 1e-9: + sequence = np.append(sequence, [stop]) + else: + raise SyntaxError( + "Cannot determine measurement points. " + "Either provide 'sequence', 'step' or 'num'" + ) + + return sequence + + +def measure_sweeps( + sweeps: List[BaseSweep], + measure_params: List[_BaseParameter], + msmt: "MeasurementLoop" = None, + thread=False, +) -> None: + """Recursively iterate over Sweep objects, measuring measure_params in innermost loop + + This method is used to perform arbitrary-dimension by passing a list of sweeps, + it can be compared to `dond` + + Args: + sweeps: list of BaseSweep objects to sweep over + measure_params: list of parameters to measure in innermost loop + """ + + if sweeps: + outer_sweep, *inner_sweeps = sweeps + + for _ in outer_sweep: + measure_sweeps(inner_sweeps, measure_params, msmt=msmt, thread=thread) + + else: + if msmt is None: + msmt = running_measurement() + + if thread: + msmt.measure_threaded(measure_params) + else: + for measure_param in measure_params: + msmt.measure(measure_param) + + +class Iterate(Sweep): + """Variant of Sweep that is used to iterate outside a MeasurementLoop""" + def __iter__(self) -> Iterable: + # Determine sweep parameter + if self.parameter is None: + if isinstance(self.sequence, _IterateDondSweep): + # sweep is a doNd sweep that already has a parameter + self.parameter = self.sequence.parameter + else: + # Need to create a parameter + self.parameter = Parameter( + name=self.name, label=self.label, unit=self.unit + ) + + # We use this to revert back in the end + self.original_value = self.parameter.get() + + self.loop_index = 0 + self.dimension = 1 + self.iterator = iter(self.sequence) + + return self + + def __next__(self) -> Any: + try: # Perform loop action + sweep_value = next(self.iterator) + except StopIteration: # Reached end of iteration + if self.revert: + try: + self.parameter(self.original_value) + except Exception: + warn(f'Could not revert {self.parameter} to {self.original_value}') + raise StopIteration + + # Set parameter if passed along + if self.parameter is not None and self.parameter.settable: + self.parameter(sweep_value) + + # Optional wait after settings value + if self.initial_delay and self.loop_index == 0: + sleep(self.initial_delay) + if self.delay: + sleep(self.delay) + + self.loop_index += 1 + + return sweep_value + + def convert_to_Sweep(self): + return BaseSweep( + sequence=self.sequence, + name=self.name, + label=self.label, + unit=self.unit, + parameter=self.parameter, + revert=self.revert, + delay=self.delay, + initial_delay=self.initial_delay + ) \ No newline at end of file diff --git a/qcodes/dataset/measurements.py b/qcodes/dataset/measurements.py index b85f5e3c043..c4dd6b25ade 100644 --- a/qcodes/dataset/measurements.py +++ b/qcodes/dataset/measurements.py @@ -512,6 +512,7 @@ def __init__( shapes: Shapes | None = None, in_memory_cache: bool = True, dataset_class: DataSetType = DataSetType.DataSet, + allow_empty_dataset: bool = False, ) -> None: self._dataset_class = dataset_class @@ -534,6 +535,7 @@ def __init__( self._extra_log_info = extra_log_info self._write_in_background = write_in_background self._in_memory_cache = in_memory_cache + self.allow_empty_dataset = allow_empty_dataset self.ds: DataSetProtocol @staticmethod @@ -610,6 +612,7 @@ def __enter__(self) -> DataSaver: write_in_background=self._write_in_background, shapes=self._shapes, parent_datasets=self._parent_datasets, + allow_empty_dataset=self.allow_empty_dataset, ) # register all subscribers @@ -698,6 +701,8 @@ class Measurement: produced by the measurement. If not given, a default value of 'results' is used for the dataset. """ + enteractions = [] + exitactions = [] def __init__( self, @@ -1228,6 +1233,7 @@ def run( write_in_background: bool | None = None, in_memory_cache: bool = True, dataset_class: DataSetType = DataSetType.DataSet, + allow_empty_dataset: bool = False, ) -> Runner: """ Returns the context manager for the experimental run @@ -1247,8 +1253,8 @@ def run( if write_in_background is None: write_in_background = qc.config.dataset.write_in_background return Runner( - self.enteractions, - self.exitactions, + [*self.enteractions, *Measurement.enteractions], + [*self.exitactions, *Measurement.exitactions], self.experiment, station=self.station, write_period=self._write_period, @@ -1261,4 +1267,5 @@ def run( shapes=self._shapes, in_memory_cache=in_memory_cache, dataset_class=dataset_class, + allow_empty_dataset=allow_empty_dataset, ) diff --git a/qcodes/instrument/instrument_base.py b/qcodes/instrument/instrument_base.py index 67d04576083..ba5c17e1a72 100644 --- a/qcodes/instrument/instrument_base.py +++ b/qcodes/instrument/instrument_base.py @@ -285,7 +285,7 @@ def snapshot_base( update_par = update try: snap["parameters"][name] = param.snapshot(update=update_par) - except: + except Exception: # really log this twice. Once verbose for the UI and once # at lower level with more info for file based loggers self.log.warning("Snapshot: Could not update parameter: %s", name) diff --git a/qcodes/instrument_drivers/QDevil/QDevil_QDAC.py b/qcodes/instrument_drivers/QDevil/QDevil_QDAC.py index e001cc8a55e..27923933ae8 100644 --- a/qcodes/instrument_drivers/QDevil/QDevil_QDAC.py +++ b/qcodes/instrument_drivers/QDevil/QDevil_QDAC.py @@ -13,6 +13,7 @@ import pyvisa as visa from pyvisa.resources.serial import SerialInstrument +from qcodes.utils.delaykeyboardinterrupt import DelayedKeyboardInterrupt from qcodes import validators as vals from qcodes.instrument import ChannelList, InstrumentChannel, VisaInstrument from qcodes.parameters import MultiChannelInstrumentParameter, ParamRawDataType @@ -783,9 +784,12 @@ def write(self, cmd: str) -> None: """ LOG.debug(f"Writing to instrument {self.name}: {cmd}") - self.visa_handle.write(cmd) - for _ in range(cmd.count(';')+1): - self._write_response = self.visa_handle.read() + + with DelayedKeyboardInterrupt(): + self.visa_handle.write(cmd) + for _ in range(cmd.count(';')+1): + self._write_response = self.visa_handle.read() + def read(self) -> str: return self.visa_handle.read() diff --git a/qcodes/monitor/monitor.py b/qcodes/monitor/monitor.py index f3e76798262..7aa27362741 100644 --- a/qcodes/monitor/monitor.py +++ b/qcodes/monitor/monitor.py @@ -39,6 +39,7 @@ Optional, Sequence, Union, + List ) import websockets @@ -62,7 +63,9 @@ def _get_metadata( - *parameters: Parameter, use_root_instrument: bool = True + *parameters: Parameter, + use_root_instrument: bool = True, + parameters_metadata: Dict[Union[Parameter, str], dict] ) -> Dict[str, Any]: """ Return a dictionary that contains the parameter metadata grouped by the @@ -71,28 +74,56 @@ def _get_metadata( metadata_timestamp = time.time() # group metadata by instrument metas: Dict[Any, Any] = defaultdict(list) + + # Ensure each element of parameters_metadata is a dict and not something else like a DotDict + parameters_metadata = {key: dict(val) for key, val in parameters_metadata.items()} + for parameter in parameters: + # Get potential parameter metadata describing how to process parameter + if parameter in parameters_metadata: + parameter_metadata = parameters_metadata[parameter] + elif parameter.name in parameters_metadata: + parameter_metadata = parameters_metadata[parameter.name] + elif parameter.full_name in parameters_metadata: + parameter_metadata = parameters_metadata[parameter.full_name] + else: + parameter_metadata = {} + # Get the latest value from the parameter, # respecting the max_val_age parameter meta: Dict[str, Optional[Union[float, str]]] = {} - meta["value"] = str(parameter.get_latest()) + value = parameter.get_latest() + + # Apply a modifier if provided by metadata + if 'modifier' in parameter_metadata: + value = parameter_metadata['modifier'](value) + if 'scale' in parameter_metadata: + value = value * parameter_metadata['scale'] + + # Format value, usually to a string unless specified in parameter_metadata['formatter'] + formatter = parameter_metadata.get('formatter', '{}') + meta["value"] = formatter.format(value) + timestamp = parameter.get_latest.get_timestamp() if timestamp is not None: meta["ts"] = timestamp.timestamp() else: meta["ts"] = None - meta["name"] = parameter.label or parameter.name - meta["unit"] = parameter.unit + meta["name"] = parameter_metadata.get('name') or parameter.label or parameter.name + meta["unit"] = parameter_metadata.get('unit') or parameter.unit # find the base instrument that this parameter belongs to if use_root_instrument: baseinst = parameter.root_instrument else: baseinst = parameter.instrument - if baseinst is None: - metas["Unbound Parameter"].append(meta) + + if 'group' in parameter_metadata: + metas[parameter_metadata['group']].append(meta) + elif baseinst is not None: + metas[str(parameter.root_instrument)].append(meta) else: - metas[str(baseinst)].append(meta) + metas["Unbound Parameter"].append(meta) # Create list of parameters, grouped by instrument parameters_out = [] @@ -105,7 +136,10 @@ def _get_metadata( def _handler( - parameters: Sequence[Parameter], interval: float, use_root_instrument: bool = True + parameters: Sequence[Parameter], + interval: float, + use_root_instrument: bool = True, + parameters_metadata: Dict[Union[Parameter, str], dict] = {} ) -> Callable[["WebSocketServerProtocol", str], Awaitable[None]]: """ Return the websockets server handler. @@ -120,7 +154,9 @@ async def server_func(websocket: "WebSocketServerProtocol", _: str) -> None: # Update the parameter values try: meta = _get_metadata( - *parameters, use_root_instrument=use_root_instrument + *parameters, + use_root_instrument=use_root_instrument, + parameters_metadata=parameters_metadata ) except ValueError: log.exception("Error getting parameters") @@ -149,6 +185,8 @@ def __init__( *parameters: Parameter, interval: float = 1, use_root_instrument: bool = True, + parameters_metadata: Optional[Dict[Union[Parameter, str], dict]] = None, + daemon: bool = True ): """ Monitor qcodes parameters. @@ -159,7 +197,7 @@ def __init__( use_root_instrument: Defines if parameters are grouped according to parameter.root_instrument or parameter.instrument """ - super().__init__() + super().__init__(daemon=daemon) # Check that all values are valid parameters for parameter in parameters: @@ -170,10 +208,14 @@ def __init__( self.loop: Optional[asyncio.AbstractEventLoop] = None self.server: Optional["WebSocketServer"] = None self._parameters = parameters + self._parameters_metadata = parameters_metadata self.loop_is_closed = Event() self.server_is_started = Event() self.handler = _handler( - parameters, interval=interval, use_root_instrument=use_root_instrument + parameters, + interval=interval, + use_root_instrument=use_root_instrument, + parameters_metadata=parameters_metadata or {} ) log.debug("Start monitoring thread") diff --git a/qcodes/parameters/parameter.py b/qcodes/parameters/parameter.py index f011fdb1fcc..eabad42295d 100644 --- a/qcodes/parameters/parameter.py +++ b/qcodes/parameters/parameter.py @@ -400,6 +400,105 @@ def sweep( return SweepFixedValues(self, start=start, stop=stop, step=step, num=num) + def sweep( + self, + *args, + start: float = None, + stop: float = None, + around: float = None, + num: int = None, + step: float = None, + delay: float = None, + initial_delay: float = None, + revert: bool = None, + measurement_name: str = None, + measure_params: ParameterBase=None, + repetitions: int = 1, + sweep=None, + thread=None, + plot: bool = None, + ): + """Perform a measurement by sweeping this parameter + + This creates a `Sweep` object and executes a measurement with it. + + For the most frequent use-cases, a Sweep can also be created by passing args in a variety of ways: + + 1 arg: + - parameter.sweep([1,2,3]) + : sweep "parameter" over sequence [1,2,3] + - parameter.sweep(stop_val) + : sweep "parameter" from current value to "stop_val" + 2 args: + - parameter.sweep(start_val, stop_val) + : sweep "parameter" from "start_val" to "stop_val" + If "num" or "step" is not given as kwarg, it will check if "num" or "step" + is set in dict "parameter.sweep_defaults" and use that, or else raise an error. + 3 args: + - Sweep(start_val, stop_val, num) + : Sweep "parameter" from "start_val" to "stop_val" with "num" number of points + + Args: + start: start value of sweep sequence + Cannot be used together with ``around`` + stop: stop value of sweep sequence + Cannot be used together with ``around`` + around: sweep around the current parameter value. + ``start`` and ``stop`` are defined from ``around`` and the current value + i.e. start=X-dx, stop=X+dx when current_value=X and around=dx. + Passing the kwarg "around" also sets revert=True unless explicitly set False + num: Number of points between start and stop. + Cannot be used together with ``step`` + step: Increment from start to stop. + Cannot be used together with ``num`` + delay: Time delay after incrementing to the next value + initial_delay: Time delay after having incremented to its first value + name: Sweep name, overrides parameter.name + label: Sweep label, overrides parameter.label + unit: Sweep unit, overrides parameter.unit + revert: Revert parameter back to original value after the sweep ends. + This is False by default, unless the kwarg ``around`` is passed + measurement_name: Dataset name, defaults to a concatenation of sweep parameter names + measure_params: Parameters to measure. + If not provided, it will check the attribute ``Station.measure_params`` + for parameters. Raises an error if undefined. + repetitions: Number of times to repeat measurement, defaults to 1. + This will be the outermost loop if set to a value above 1. + sweep: Additional sweeps used for N-dimensional measurements + The first element is the outermost sweep dimension, and the sweep on which + `parameter.sweep` was called is the innermost dimension. + Note that ``sweep`` can be either a single Sweep, or a Sweep list. + """ + from qcodes.dataset import MeasurementLoop, Sweep + parameter_sweep = Sweep( + self, # Pass parameter as first arg + *args, + start=start, + stop=stop, + around=around, + num=num, + step=step, + delay=delay, + initial_delay=initial_delay, + revert=revert + ) + + # Only plot if not excplicitly set and not part of a larger measurement + if plot is None: + plot = (MeasurementLoop.running_measurement is None) + + + dataset = parameter_sweep.execute( + name=measurement_name, + measure_params=measure_params, + repetitions=repetitions, + sweep=sweep, + plot=plot, + thread=thread, + ) + return dataset + + class ManualParameter(Parameter): def __init__( self, diff --git a/qcodes/tests/dataset/measurement_loop/__init__.py b/qcodes/tests/dataset/measurement_loop/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/qcodes/tests/dataset/measurement_loop/test_measurement_loop_basics.py b/qcodes/tests/dataset/measurement_loop/test_measurement_loop_basics.py new file mode 100644 index 00000000000..e8e86360915 --- /dev/null +++ b/qcodes/tests/dataset/measurement_loop/test_measurement_loop_basics.py @@ -0,0 +1,350 @@ +import numpy as np +import pytest + +from qcodes.dataset import MeasurementLoop, Sweep +from qcodes.instrument import ManualParameter + + +@pytest.mark.usefixtures("empty_temp_db", "experiment") +def test_original_dond(): + from qcodes.utils.dataset.doNd import LinSweep, dond + + p1_get = ManualParameter("p1_get", initial_value=1) + p2_get = ManualParameter("p2_get", initial_value=1) + p1_set = ManualParameter("p1_set", initial_value=1) + dond(p1_set, 0, 1, 101, p1_get, p2_get) + + +def test_create_measurement(): + MeasurementLoop("test") + + +def test_basic_1d_measurement(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + + with MeasurementLoop("test") as msmt: + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + p1_get(val + 1) + msmt.measure(p1_get) + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p1_set,p1_get" + + arrays = data.get_parameter_data() + data_arrays = arrays["p1_get"] + + assert np.allclose(data_arrays["p1_get"], np.linspace(1, 2, 11)) + assert np.allclose(data_arrays["p1_set"], np.linspace(0, 1, 11)) + + +def test_basic_2d_measurement(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + p2_set = ManualParameter("p2_set") + + with MeasurementLoop("test") as msmt: + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + for val2 in Sweep(p2_set, 0, 1, 11): + assert p2_set() == val2 + p1_get(val + 1) + msmt.measure(p1_get) + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p1_set,p2_set,p1_get" + + arrays = data.get_parameter_data() + data_array = arrays["p1_get"]["p1_get"] + + assert np.allclose(data_array, np.tile(np.linspace(1, 2, 11), (11, 1)).transpose()) + + assert np.allclose( + arrays["p1_get"]["p1_set"], np.tile(np.linspace(0, 1, 11), (11, 1)).transpose() + ) + + assert np.allclose( + arrays["p1_get"]["p2_set"], np.tile(np.linspace(0, 1, 11), (11, 1)) + ) + + +def test_1d_measurement_duplicate_get(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + + with MeasurementLoop("test") as msmt: + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + p1_get(val + 1) + msmt.measure(p1_get) + p1_get(val + 0.5) + msmt.measure(p1_get) + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p1_set,p1_get,p1_get_1" + + arrays = data.get_parameter_data() + + offsets = {"p1_get": 1, "p1_get_1": 0.5} + for key in ["p1_get", "p1_get_1"]: + data_arrays = arrays[key] + + assert np.allclose(data_arrays[key], np.linspace(0, 1, 11) + offsets[key]) + assert np.allclose(data_arrays["p1_set"], np.linspace(0, 1, 11)) + + +def test_1d_measurement_duplicate_getset(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + + with MeasurementLoop("test") as msmt: + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + p1_get(val + 1) + msmt.measure(p1_get) + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + p1_get(val + 0.5) + msmt.measure(p1_get) + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p1_set,p1_get,p1_set_1,p1_get_1" + + arrays = data.get_parameter_data() + + offsets = {"p1_get": 1, "p1_get_1": 0.5} + for suffix in ["", "_1"]: + get_key = f"p1_get{suffix}" + set_key = f"p1_set{suffix}" + data_arrays = arrays[get_key] + + assert np.allclose( + data_arrays[get_key], np.linspace(0, 1, 11) + offsets[get_key] + ) + assert np.allclose(data_arrays[set_key], np.linspace(0, 1, 11)) + + +def test_2d_measurement_initialization(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + p2_set = ManualParameter("p2_set") + + with MeasurementLoop("test") as msmt: + outer_sweep = Sweep(p1_set, 0, 1, 11) + for k, val in enumerate(outer_sweep): + assert p1_set() == val + + for val2 in Sweep(p2_set, 0, 1, 11): + assert p2_set() == val2 + p1_get(val + 1) + msmt.measure(p1_get) + + +def test_initialize_empty_dataset(): + from qcodes import Measurement + + msmt = Measurement() + # msmt.register_parameter(p1_set) + # msmt.register_parameter(p1_get, setpoints=(p1_set,)) + with msmt.run(allow_empty_dataset=True) as datasaver: + pass + + +def test_nested_measurement(): + def nested_measurement(): + # Initialize parameters + p1_get = ManualParameter("p1_get") + p1_set = ManualParameter("p1_set") + + with MeasurementLoop("test") as msmt: + for val in Sweep(p1_set, 0, 1, 11): + assert p1_set() == val + p1_get(val + 1) + msmt.measure(p1_get) + + # Initialize parameters + p2_set = ManualParameter("p2_set") + + with MeasurementLoop("test") as msmt: + for val2 in Sweep(p2_set, 0, 1, 11): + assert p2_set() == val2 + nested_measurement() + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p2_set,p1_set,p1_get" + + arrays = data.get_parameter_data() + data_array = arrays["p1_get"]["p1_get"] + + assert np.allclose(data_array, np.tile(np.linspace(1, 2, 11), (11, 1))) + + assert np.allclose( + arrays["p1_get"]["p2_set"], np.tile(np.linspace(0, 1, 11), (11, 1)).transpose() + ) + + assert np.allclose( + arrays["p1_get"]["p1_set"], np.tile(np.linspace(0, 1, 11), (11, 1)) + ) + + +def test_measurement_no_parameter(): + with MeasurementLoop("test") as msmt: + for val in Sweep(np.linspace(0, 1, 11), "p1_set", label="p1 label", unit="V"): + msmt.measure(val + 1, name="p1_get") + + data = msmt.dataset + assert data.name == "test" + assert data.parameters == "p1_set,p1_get" + + arrays = data.get_parameter_data() + data_arrays = arrays["p1_get"] + + assert np.allclose(data_arrays["p1_get"], np.linspace(1, 2, 11)) + assert np.allclose(data_arrays["p1_set"], np.linspace(0, 1, 11)) + + +def test_measurement_fraction_complete(): + with MeasurementLoop("test") as msmt: + print(f'Before Sweep') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + for k, val in enumerate(Sweep(np.linspace(0, 1, 10), "p1_set")): + print(f'\n') + print(f'\nBefore first measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + print(f'{msmt.fraction_complete(silent=False)=}') + assert msmt.fraction_complete() == round(0.1*k, 3) + + msmt.measure(val+1, name="p1_get") + print(f'\nBetween first and second measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + print(f'{msmt.fraction_complete(silent=False)=}') + if not k: + assert msmt.fraction_complete() == 0.1 + else: + assert msmt.fraction_complete() == round(0.1*k+0.05, 3) + + msmt.measure(val+1, name="p1_get") + print(f'\nAfter second measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + # print(f'{msmt.fraction_complete(silent=False)=}') + print(f'{msmt.fraction_complete(silent=False)=}') + assert msmt.fraction_complete() == round(0.1 * (k+1), 3) + + for k, val in enumerate(Sweep(np.linspace(0, 1, 10), "p1_set")): + print(f'\n') + print(f'\nBefore first measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + print(f'{msmt.fraction_complete(silent=-1)=}') + assert msmt.fraction_complete() == round(0.5 + 0.05*k, 3) + + msmt.measure(val+1, name="p1_get") + print(f'\nBetween first and second measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + print(f'{msmt.fraction_complete(silent=-1)=}') + if not k: + assert msmt.fraction_complete() == 0.55 + else: + assert msmt.fraction_complete() == round(0.525 + 0.05*k, 3) + + msmt.measure(val+1, name="p1_get") + print(f'\nAfter second measurement') + print(f'{msmt.action_indices=}, {msmt.loop_indices=}, {msmt.loop_shape=}') + # print(f'{msmt.fraction_complete(silent=False)=}') + print(f'{msmt.fraction_complete(silent=-1)=}') + assert msmt.fraction_complete() == round(0.5 + 0.05 * (k+1), 3) + + +def test_save_array_0D(): + with MeasurementLoop('array_0D') as msmt: + msmt.measure([1,2,3], 'array') + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'setpoint_idx' in data + assert list(data['array']) == [1,2,3] + assert list(data['setpoint_idx']) == [0, 1, 2] + + +def test_save_array_0D_custom_setpoint_list(): + with MeasurementLoop('array_0D') as msmt: + msmt.measure([1,2,3], 'array', setpoints=[3,4,5]) + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'setpoint_idx' in data + assert list(data['array']) == [1,2,3] + assert list(data['setpoint_idx']) == [3, 4, 5] + + +def test_save_array_0D_custom_setpoint_sweep(): + with MeasurementLoop('array_0D') as msmt: + msmt.measure( + [1,2,3], 'array', + setpoints=Sweep([2,3,4], 'my_sweep')) + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'my_sweep' in data + assert list(data['array']) == [1,2,3] + assert list(data['my_sweep']) == [2, 3, 4] + + +def test_save_array_1D(): + with MeasurementLoop('array_0D') as msmt: + for k in Sweep([5, 6], 'outer_sweep'): + msmt.measure([1,2,3], 'array') + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'outer_sweep' in data + assert 'setpoint_idx' in data + np.testing.assert_array_equal(data['array'], [[1,2,3], [1,2,3]]) + np.testing.assert_array_equal(data['outer_sweep'], [[5,5,5], [6,6,6]]) + np.testing.assert_array_equal(data['setpoint_idx'], [[0,1,2], [0,1,2]]) + + +def test_save_array_1D_custom_setpoint_list(): + with MeasurementLoop('array_0D') as msmt: + for k in Sweep([5, 6], 'outer_sweep'): + msmt.measure([1,2,3], 'array', setpoints=[3,4,5]) + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'outer_sweep' in data + assert 'setpoint_idx' in data + np.testing.assert_array_equal(data['array'], [[1,2,3], [1,2,3]]) + np.testing.assert_array_equal(data['outer_sweep'], [[5,5,5], [6,6,6]]) + np.testing.assert_array_equal(data['setpoint_idx'], [[3,4,5], [3,4,5]]) + + +def test_save_array_1D_custom_setpoint_sweep(): + with MeasurementLoop('array_0D') as msmt: + for k in Sweep([5, 6], 'outer_sweep'): + msmt.measure( + [1,2,3], 'array', + setpoints=Sweep([2,3,4], 'my_sweep', unit='V')) + + # Verify results + data = msmt.dataset.get_parameter_data('array')['array'] + assert 'array' in data + assert 'outer_sweep' in data + assert 'my_sweep' in data + np.testing.assert_array_equal(data['array'], [[1,2,3], [1,2,3]]) + np.testing.assert_array_equal(data['outer_sweep'], [[5,5,5], [6,6,6]]) + np.testing.assert_array_equal(data['my_sweep'], [[2,3,4], [2,3,4]]) \ No newline at end of file diff --git a/qcodes/tests/dataset/measurement_loop/test_measurement_loop_sweep.py b/qcodes/tests/dataset/measurement_loop/test_measurement_loop_sweep.py new file mode 100644 index 00000000000..a867c08eb6a --- /dev/null +++ b/qcodes/tests/dataset/measurement_loop/test_measurement_loop_sweep.py @@ -0,0 +1,209 @@ +import numpy as np +import pytest + +from qcodes.dataset import LinSweep, MeasurementLoop, Sweep, dond, Iterate +from qcodes.instrument import ManualParameter, Parameter + + +def test_sweep_1_arg_sequence(): + sequence = [1, 2, 3] + sweep = Sweep(sequence, name="sweep_name") + assert sweep.sequence == sequence + + +def test_sweep_1_arg_parameter_stop(): + sweep_parameter = ManualParameter("sweep_parameter") + + # Should raise an error since it does not have an initial value + with pytest.raises(ValueError): + sweep = Sweep(sweep_parameter, stop=10, num=21) + + sweep_parameter(0) + sweep = Sweep(sweep_parameter, stop=10, num=21) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + sweep_parameter.sweep_defaults = {"num": 21} + sweep = Sweep(sweep_parameter, stop=10) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + +def test_sweep_1_arg_parameter_around(): + sweep_parameter = ManualParameter("sweep_parameter", initial_value=0) + + sweep = Sweep(sweep_parameter, around=5, num=21) + assert np.allclose(sweep.sequence, np.linspace(-5, 5, 21)) + + sweep_parameter.sweep_defaults = {"num": 21} + sweep = Sweep(sweep_parameter, around=5) + assert np.allclose(sweep.sequence, np.linspace(-5, 5, 21)) + + +def test_sweep_2_args_parameter_sequence(): + sweep_parameter = ManualParameter("sweep_parameter", initial_value=0) + + sequence = [1, 2, 3] + sweep = Sweep(sweep_parameter, sequence) + assert np.allclose(sweep.sequence, sequence) + assert sweep.parameter == sweep_parameter + + +def test_sweep_2_args_parameter_stop(): + sweep_parameter = ManualParameter("sweep_parameter") + + # No initial value + with pytest.raises(ValueError): + sweep = Sweep(sweep_parameter, stop=10) + with pytest.raises(ValueError): + sweep = Sweep(sweep_parameter, stop=10, num=21) + + sweep_parameter(0) + with pytest.raises(SyntaxError): + sweep = Sweep(sweep_parameter, 10) + + sweep = Sweep(sweep_parameter, stop=10, num=21) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + sweep_parameter.sweep_defaults = {"num": 21} + sweep = Sweep(sweep_parameter, stop=10) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + +def test_sweep_2_args_sequence_name(): + sweep_values = [1, 2, 3] + sweep = Sweep(sweep_values) + assert sweep.name == 'iteration' + assert sweep.label == 'Iteration' + + sweep = Sweep(sweep_values, "sweep_values") + assert np.allclose(sweep.sequence, sweep_values) + + +def test_sweep_3_args_parameter_start_stop(): + sweep_parameter = ManualParameter("sweep_parameter") + + with pytest.raises(SyntaxError): + sweep = Sweep(sweep_parameter, 0, 10) + + sweep = Sweep(sweep_parameter, 0, 10, num=21) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + sweep_values = [1, 2, 3] + sweep = Sweep(sweep_values) + assert sweep.name == 'iteration' + assert sweep.label == 'Iteration' + + sweep = Sweep(sweep_values, "sweep_values") + assert np.allclose(sweep.sequence, sweep_values) + + +def test_sweep_4_args_parameter_start_stop_num(): + sweep_parameter = ManualParameter("sweep_parameter") + + sweep = Sweep(sweep_parameter, 0, 10, 21) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + +def test_sweep_step(): + sweep = Sweep(start=0, stop=10, step=0.5) + assert np.allclose(sweep.sequence, np.linspace(0, 10, 21)) + + # Append final element since it isn't a multiple of 0.5 + sweep = Sweep(start=0, stop=9.9, step=0.5) + assert np.allclose(sweep.sequence, np.append(np.arange(0, 9.9, 0.5), [9.9])) + + +def test_sweep_len(): + sweep = Sweep(start=0, stop=10, step=0.5) + assert len(sweep) == 21 + + +def test_error_on_iterate_sweep(): + sweep = Sweep([1, 2, 3], "sweep") + + with pytest.raises(RuntimeError): + iter(sweep) + + +@pytest.mark.usefixtures("empty_temp_db", "experiment") +def test_sweep_in_dond(): + set_parameter = ManualParameter("set_param") + sweep = Sweep(set_parameter, [1, 2, 3]) + get_parameter = Parameter("get_param", get_cmd=set_parameter) + + dataset, _, _ = dond(sweep, get_parameter) + assert np.allclose( + dataset.get_parameter_data("get_param")["get_param"]["get_param"], [1, 2, 3] + ) + + +@pytest.mark.usefixtures("empty_temp_db", "experiment") +def test_sweep_and_linsweep_in_dond(): + set_parameter = ManualParameter("set_param") + + sweep = Sweep(set_parameter, [1, 2, 3]) + + set_parameter2 = ManualParameter("set_param2") + linsweep = LinSweep(set_parameter2, 0, 10, 11) + get_parameter = Parameter("get_param", get_cmd=set_parameter) + + dataset, _, _ = dond(sweep, linsweep, get_parameter) + arr = dataset.get_parameter_data("get_param")["get_param"]["get_param"] + + assert np.allclose(arr, np.repeat(np.array([1, 2, 3])[:, np.newaxis], 11, axis=1)) + + +@pytest.mark.usefixtures("empty_temp_db", "experiment") +def test_linsweep_in_MeasurementLoop(): + set_parameter = ManualParameter("set_param") + get_parameter = ManualParameter("get_param", initial_value=42) + + linsweep = LinSweep(set_parameter, 0, 10, 11) + + sweep = Sweep(linsweep) + assert sweep.name == "set_param" + + with MeasurementLoop("linsweep_in_MeasurementLoop") as msmt: + for k, val in enumerate(sweep): + assert val == k + msmt.measure(get_parameter) + + +def test_sweep_execute_sweep_args(): + set_parameter = ManualParameter("set_param") + sweep = Sweep(set_parameter, [1, 2, 3]) + set_parameter2 = ManualParameter("set_param2") + other_sweep = Sweep(set_parameter2, [1, 2, 3]) + + get_param = Parameter( + "get_param", get_cmd=lambda: set_parameter() + set_parameter2() + ) + + dataset = sweep.execute(other_sweep, measure_params=get_param) + + arr = dataset.get_parameter_data("get_param")["get_param"]["get_param"] + assert np.allclose(arr, [[2, 3, 4], [3, 4, 5], [4, 5, 6]]) + print(dataset) + + +def test_sweep_reverting(): + param = ManualParameter('param', initial_value=42) + with MeasurementLoop('test_revert') as msmt: + for val in Sweep(param, range(5), revert=True): + msmt.measure(val, 'value') + + print(msmt._masked_properties) + + assert param() == 42 + + param(41) + assert param() == 41 + + +def test_iterate(): + param = ManualParameter('param', initial_value=42) + + expected_vals = np.linspace(37, 47, 21) + for k, val in enumerate(Iterate(param, around=5, num=21)): + assert val == expected_vals[k] + + assert param() == 42 \ No newline at end of file diff --git a/qcodes/utils/helpers.py b/qcodes/utils/helpers.py index f67a9d0d816..57cb813eca8 100644 --- a/qcodes/utils/helpers.py +++ b/qcodes/utils/helpers.py @@ -3,6 +3,7 @@ Please do not import from this in any new code """ import logging +from contextlib import contextmanager from typing import Any, Dict, Hashable, Optional, Tuple # for backwards compatibility since this module used @@ -42,6 +43,114 @@ def warn_units(class_name: str, instance: object) -> None: '` class, use `unit` instead. ' + repr(instance)) +# TODO these functions need a place +import builtins +import pprint +import sys +import time + +import numpy as np + +from qcodes.configuration.config import DotDict + + +def get_exponent_prefactor(val: float) -> Tuple[int, str]: + """Get the exponent and unit prefactor of a number + + Currently lower bounded at atto + + Args: + val: value for which to get exponent and prefactor + + Returns: + Exponent corresponding to prefactor + Prefactor + + Examples: + ``` + get_exponent_prefactor(1.82e-8) + >>> -9, "n" # i.e. 18.2*10**-9 n{unit} + ``` + + + """ + prefactors = [ + (9, "G"), + (6, "M"), + (3, "k"), + (0, ""), + (-3, "m"), + (-6, "u"), + (-9, "n"), + (-12, "p"), + (-15, "f"), + (-18, "a"), + ] + for exponent, prefactor in prefactors: + if val >= np.power(10.0, exponent): + return exponent, prefactor + + return prefactors[-1] + + +class PerformanceTimer: + max_records = 100 + + def __init__(self): + self.timings = DotDict() + + def __getitem__(self, key: str) -> str: + val = self.timings.__getitem__(key) + return self._timing_to_str(val) + + def __repr__(self): + return pprint.pformat(self._timings_to_str(self.timings), indent=2) + + def clear(self) -> None: + self.timings.clear() + + def _timing_to_str(self, val: float) -> str: + mean_val = np.mean(val) + exponent, prefactor = get_exponent_prefactor(mean_val) + factor = np.power(10.0, exponent) + + return f"{mean_val / factor:.3g}+-{np.abs(np.std(val))/factor:.3g} {prefactor}s" + + def _timings_to_str(self, d: dict) -> str: + + timings_str = DotDict() + for key, val in d.items(): + if isinstance(val, dict): + timings_str[key] = self._timings_to_str(val) + else: + timings_str[key] = self._timing_to_str(val) + + return timings_str + + @contextmanager + def record(self, key: str, val: Any = None) -> None: + if isinstance(key, str): + timing_list = self.timings.setdefault(key, []) + elif isinstance(key, (list)): + *parent_keys, subkey = key + d = self.timings.create_dicts(*parent_keys) + timing_list = d.setdefault(subkey, []) + else: + raise ValueError("Key must be str or list/tuple") + + if val is not None: + timing_list.append(val) + else: + t0 = time.perf_counter() + yield + t1 = time.perf_counter() + timing_list.append(t1 - t0) + + # Optionally remove oldest elements + for _ in range(len(timing_list) - self.max_records): + timing_list.pop(0) + + @deprecate("Internal function no longer part of the public qcodes api") def compare_dictionaries( dict_1: Dict[Hashable, Any],