diff --git a/doc/examples/Configuring sweeps and passing parameters.ipynb b/doc/examples/Sweeps/Configuring sweeps and passing parameters.ipynb similarity index 99% rename from doc/examples/Configuring sweeps and passing parameters.ipynb rename to doc/examples/Sweeps/Configuring sweeps and passing parameters.ipynb index 7f7519d..fe9393f 100644 --- a/doc/examples/Configuring sweeps and passing parameters.ipynb +++ b/doc/examples/Sweeps/Configuring sweeps and passing parameters.ipynb @@ -34,7 +34,7 @@ "metadata": {}, "outputs": [], "source": [ - "from labcore.measurement import *" + "from labcore.sweep import *" ] }, { diff --git a/doc/examples/Introduction to sweeping.ipynb b/doc/examples/Sweeps/Introduction to sweeping.ipynb similarity index 99% rename from doc/examples/Introduction to sweeping.ipynb rename to doc/examples/Sweeps/Introduction to sweeping.ipynb index 109307f..e598354 100644 --- a/doc/examples/Introduction to sweeping.ipynb +++ b/doc/examples/Sweeps/Introduction to sweeping.ipynb @@ -33,7 +33,7 @@ "import numpy as np\n", "import qcodes as qc\n", "\n", - "from labcore.measurement import *" + "from labcore.sweep import *" ] }, { diff --git a/doc/examples/opx_demo/Simple OPX setup demo without mixers.ipynb b/doc/examples/opx_demo/Simple OPX setup demo without mixers.ipynb new file mode 100644 index 0000000..48b78ae --- /dev/null +++ b/doc/examples/opx_demo/Simple OPX setup demo without mixers.ipynb @@ -0,0 +1,390 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "491887ef-8a6e-47fd-b99d-cbd6da3aef7f", + "metadata": {}, + "source": [ + "# README" + ] + }, + { + "cell_type": "markdown", + "id": "73a77995-e2e2-4631-b445-b29405fa9ad7", + "metadata": {}, + "source": [ + "This notebook is a good starting point for learning how we run our measurements.\n", + "We will setup a 'dry run', i.e., perform a dummy measurement in which we will simply directly measure the output of the OPX.\n", + "\n", + "We assume that one of the DACs of the OPX is directly connected to one of the ADCs.\n", + "\n", + "To use this notebook, you have to do the following things first:\n", + "- make sure you have a valid configuration for the OPX. You'll probably have to read the QUA code a bit and make sure it's all compatible. This notebook comes with one that was used to test this notebook. It makes use of the ParameterManager (see next point). Make sure the config corresponds correctly with your hardware setup.\n", + "- have instrumentserver started, and a ParameterManager instrument instantiated. This notebook comes with pre-saved parameters that were used during testing. They are used in the OPX config.\n", + "\n", + "Once this is done, you should be able to execute the below (some config needed)." + ] + }, + { + "cell_type": "markdown", + "id": "de86362f-ebb8-43a8-8f8c-b213176f507b", + "metadata": { + "tags": [] + }, + "source": [ + "# Initialization" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cb1cc0c8-cde3-43ec-821f-52a7d9d3f49d", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "65ac0359-bee0-4464-a4e5-573435d89255", + "metadata": { + "tags": [] + }, + "source": [ + "## setup for measurements\n", + "\n", + "This should be the only place where specific settings of your setup need to be declared." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f7070441-19e6-41eb-adb9-ba54c3f1f275", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2022-12-01 18:14:50.673] [root: INFO] Logging set up for .\n", + "[2022-12-01 18:14:50.674] [instrumentserver.client.core: INFO] Connecting to tcp://localhost:5555\n" + ] + } + ], + "source": [ + "### basic measurement setup\n", + "\n", + "from instrumentserver.client import Client\n", + "from labcore.setup import setup_opx_measurements\n", + "from labcore.setup.setup_opx_measurements import *\n", + "\n", + "# get the client to the instrumentserver, with default settings.\n", + "isrvr = Client()\n", + "\n", + "# fill in the name of your parameter manager instrument.\n", + "params = find_or_create_remote_instrument(isrvr, 'simple_demo_params') \n", + "\n", + "# make sure you specify the correct IP and port for your OPX system.\n", + "import qmcfg_simple_demo as qmcfg\n", + "qm_config = qmcfg.QMConfig(params, '128.174.248.249', '80')\n", + "\n", + "# these need to be specified so all measurement code is configured correctly\n", + "setup_opx_measurements.options.instrument_clients = {'instruments': isrvr}\n", + "setup_opx_measurements.options.parameters = params\n", + "setup_opx_measurements.options.qm_config = qm_config" + ] + }, + { + "cell_type": "markdown", + "id": "b2221e64-8eb1-4644-ae7d-6d7d56fb84f4", + "metadata": {}, + "source": [ + "## setup for analysis\n", + "\n", + "we import everything we need below to load data and analyze it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "66c4054e-f5ee-44cb-beba-11721b05959c", + "metadata": {}, + "outputs": [], + "source": [ + "### basic plotting and analysis setup\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from labcore.analysis.data import get_data, data_info, DatasetAnalysis\n", + "from labcore.plotting.basics import setup_plotting, format_ax, add_legend, ppcolormesh\n", + "\n", + "from labcore.setup.setup_notebook_analysis import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98d9c0af-3305-45bb-946d-2b7f4eeeed8a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "122fa691-a795-46f6-9c85-57c5cefa9e44", + "metadata": {}, + "source": [ + "# Measurements" + ] + }, + { + "cell_type": "markdown", + "id": "23e5f596-1366-4fc9-9e8e-1be09633fe2b", + "metadata": {}, + "source": [ + "## Raw data example: directly measure the DAC output with the ADC" + ] + }, + { + "cell_type": "markdown", + "id": "88658a24-3c54-4e7e-a665-02e1ac3a0564", + "metadata": {}, + "source": [ + "### Run the measurement" + ] + }, + { + "cell_type": "markdown", + "id": "6c0236d9-81a6-41f8-91ce-84e56d87d933", + "metadata": {}, + "source": [ + "Here, we define the entire measurment in the notebook.\n", + "For measurements that we use regularly in some standard from, the QUA code is often placed into some version controlled module.\n", + "\n", + "Just for illustration and as a basic tutorial, we leave it here in this case.\n", + "\n", + "This measurement is very basic -- we play a readout pulse and directly read it in again.\n", + "We include the raw data. See QM docs for reference.\n", + "\n", + "We make use of our own sweep framework that enables us to hide a lot of technical overhead in the background here.\n", + "It also automatically sets up correctly labeled data." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "d069b637-073c-43df-bfc2-f9e034fc5f59", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2022-12-02 17:16:35.522] [plottr.data.datadict_storage: INFO] Data location: /home/pfafflab/Documents/github/measurement-tools/examples/opx_demos/data/2022-12-02/2022-12-02T171635_5da67cea-test/data.ddh5\n", + "[2022-12-02 17:16:35.532] [qm: INFO] Performing health check\n", + "[2022-12-02 17:16:35.534] [qm: INFO] Health check passed\n", + "[2022-12-02 17:16:35.540] [root: INFO] Integration weights file not found, using flat weights.\n", + "[2022-12-02 17:16:35.547] [qm: INFO] Performing health check\n", + "[2022-12-02 17:16:35.550] [qm: INFO] Health check passed\n", + "chunksize: 5\n", + "n_chunks: 50\n", + "[2022-12-02 17:16:35.605] [qm: INFO] Flags: \n", + "[2022-12-02 17:16:35.606] [qm: INFO] Sending program to QOP\n", + "[2022-12-02 17:16:35.857] [qm: INFO] Executing program\n", + "[2022-12-02 17:16:36.216] [labcore.ddh5: INFO] The measurement has finished successfully and all of the data has been saved.\n" + ] + } + ], + "source": [ + "from qm.qua import *\n", + "from labcore.sweep import independent\n", + "from labcore.opx.sweep import \\\n", + " RecordOPXdata, ComplexOPXData, TimedOPXData\n", + "\n", + "@RecordOPXdata(\n", + " independent('repetition'),\n", + " TimedOPXData('raw_signal', depends_on=['repetition']),\n", + " TimedOPXData('I', depends_on=['repetition']),\n", + " TimedOPXData('Q', depends_on=['repetition']),\n", + ")\n", + "def measure_opx_output(n_reps=10, rep_delay_ns=0):\n", + " # this is in units of 4 ns.\n", + " # for the data to look 'good', make sure that you have a chunksize that has an even number of\n", + " # IF periods contained in it.\n", + " _chunksize = int(20 // 4)\n", + " _n_chunks = params.readout.short.len() // (4 * _chunksize)\n", + " \n", + " with program() as raw_measurement:\n", + " rep_stream = declare_stream()\n", + " raw_stream = declare_stream(adc_trace=True)\n", + " i_stream = declare_stream()\n", + " q_stream = declare_stream()\n", + "\n", + " rep = declare(int)\n", + " I = declare(fixed, size=_n_chunks)\n", + " Q = declare(fixed, size=_n_chunks)\n", + " j = declare(int)\n", + " \n", + " with for_(rep, 0, rep < n_reps, rep + 1):\n", + " measure('readout_short', 'readout', raw_stream, \n", + " demod.sliced(\"readout_short_sliced_cos\", I, _chunksize),\n", + " demod.sliced(\"readout_short_sliced_sin\", Q, _chunksize),)\n", + " \n", + " save(rep, rep_stream)\n", + " \n", + " with for_(j, 0, j < _n_chunks, j + 1):\n", + " save(I[j], i_stream)\n", + " save(Q[j], q_stream)\n", + " \n", + " if rep_delay_ns > 20:\n", + " wait(int(rep_delay_ns)//4)\n", + "\n", + " with stream_processing():\n", + " raw_stream.input2().save_all('raw_signal')\n", + " rep_stream.save_all('repetition')\n", + " i_stream.buffer(_n_chunks).save_all('I')\n", + " q_stream.buffer(_n_chunks).save_all('Q')\n", + " \n", + " return raw_measurement\n", + "\n", + "\n", + "measurement = measure_opx_output(n_reps=100,\n", + " rep_delay_ns=1e6, \n", + " collector_options=dict(batchsize=10)\n", + " )\n", + "data_loc, _ = run_measurement(sweep=measurement, name='test')" + ] + }, + { + "cell_type": "markdown", + "id": "c8808475-cfe4-4fe6-90a7-fe92c4213857", + "metadata": {}, + "source": [ + "### Analyse the data" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "77fed77a-e931-4879-8de9-67581696b33a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I: (100, 50)\n", + " ⌙ I_time_points: (100, 50)\n", + " ⌙ repetition: (100,)\n", + "Q: (100, 50)\n", + " ⌙ Q_time_points: (100, 50)\n", + " ⌙ repetition: (100,)\n", + "raw_signal: (100, 1000)\n", + " ⌙ raw_signal_time_points: (100, 1000)\n", + " ⌙ repetition: (100,)\n" + ] + } + ], + "source": [ + "### get some basic information about the data we just wrote\n", + "data_info(data_loc)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "eaa38d47-eefe-4254-aee3-299f686d757f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/pfafflab/Documents/github/measurement-tools/qcuiuc_measurement/analysis/plotting.py:264: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3. Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading']. This will become an error two minor releases later.\n", + " im = ax.pcolormesh(_x, _y, z, cmap=cmap, **kw)\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzcAAAGnCAYAAACKKKmwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAB7CAAAewgFu0HU+AACsnElEQVR4nOzdd3gU5doG8Ht302kJkNC7EpCOICBFBEQEEQQRDgooqHQL6BHbERsgTaSIoqCADQ9NKWLBXijnQxAkCCglgJBASEJ6NjvfH5ssO31ms5stuX/XxUUy5Z13ZieQ933mecYiCIIAIiIiIiKiIGf1dweIiIiIiIi8gYMbIiIiIiIKCRzcEBERERFRSODghoiIiIiIQgIHN0REREREFBI4uCEiIiIiopDAwQ0REREREYUEDm6IiIiIiCgkcHBDREREREQhgYMbIiIiIiIKCRzcEBERERFRSODghoiIiIiIQgIHN0REREREFBI4uCEiIiIiopDAwQ0REREREYUEDm6IiIiIiCgkcHBDREREREQhgYMbIiIiIiIKCRzcEBERERFRSODghoiIAtbu3buRmJiIzMxMf3eFvOTMmTNITExEUlKSv7tCRCGIgxsiUrVixQrcfffduP7669GlSxdMmjQJf//9t2ib/Px8vPDCC+jUqRPatWuHqVOn4uLFi671R44cwfTp03HzzTejTZs2uO2227B69WpRG1999RXGjRuHG2+8Ee3bt8fw4cPx448/6vbvyy+/xLhx49CpUyfFX5bOnDmDZ599Frfccgtat26NPn36YPHixSgoKNBte/fu3bjzzjvRsmVL3HLLLdi4caPha1PyC7nWn927dyMlJQXTp0/HrbfeimbNmuGVV16R9aNXr16K+7/wwguubdatW4dRo0ahffv2mgOBjRs3YuDAgWjVqhW6du2KF1980bXuf//7H0aMGIFOnTqhdevWuO2227BmzRrR/kuWLJH1o1+/frrXssSoUaNk+//nP/8xvL8ZFy5cwOOPP45OnTqhTZs2GDRoEA4dOgTg6i/XSn8+//xzn/THqBkzZmDSpElebXPJkiUYNGiQV9ssa6NGjVL8+SAikgrzdweIKHDt3r0bI0aMQKtWrVBUVISFCxdi3Lhx2LZtG2JiYgAAs2bNwvfff49FixahUqVKeOmllzBlyhR8/PHHAIBDhw4hLi4Or776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2rVrsX79eixdulS0nRn5+fmu47j78ssv8eijj6JGjRqYN28e1q1bh/Xr12Po0KHIz883fRxPlPSrrI5HREREgY+RGwpoGzZsgCAI+OKLL/DFF1/I1m/atAmPPvoobDabblt5eXn45ZdfUL9+fVfxgG7duuGnn37Ct99+64oMmRUTE4OHH34YDz/8MC5evIgffvgBCxYswIQJE1TzgkoGKhcvXkSNGjVcy+12u26ujppdu3YhJSUFa9euxQ033OBafuXKFY/aK+nnH3/8IVv+2WefoW7duli0aJEoqrN69WpD7UZERKCgoEC2/PLly4ZLO2dkZAAAS0ETERGRCyM3FLCKioqwadMm1K9fH2vWrJH9GTt2LFJTU/HDDz8YauvFF19Eeno6HnzwQdfyu+66C/Hx8Zg3b57q401ffvml4T5Xr14dQ4YMwYABA3DixAnVim4dO3YE4Kxu5u6LL76A3W43fDx3JYMMaaTl448/9qg9AGjcuDHS09NlAySLxYLw8HDRwCY1NRU7d+401G6dOnXw559/ipadOHECJ06cMNy35ORkAECTJk0M70NEREShjZEbClg//PADUlJS8Pjjj6NTp06y9ddeey3ef/99rF+/XlTt7OLFi9i/fz8EQUB2drbrJZ5HjhzBfffdh7vvvtu1baVKlfDGG29g/PjxGDx4MO655x60a9cO4eHhOHXqFD777DMcOXIEffv2Ve3nsGHD0LNnTyQmJqJKlSr466+/8Omnn6Jdu3aIjo5W3Ofaa6/F7bffjnfffRc2mw2dO3fGsWPH8O6776JSpUqKOS562rVrhypVquD555/HlClTEBYWhi1btsgGEWZ06tQJixcvxoEDB9CtWzfX8p49e+LLL7/EzJkzceutt+L8+fN44403kJCQIHscb8yYMdi7dy8OHz7sWjZo0CA88cQTrv3Pnj2Ld955RzEKc91116Fjx46yqNCBAwdgs9lcA0UiIiIiDm4oYK1fvx7h4eEYOnSo4vqqVavilltuwRdffIGLFy+68ldKHmGzWq2IiYlB7dq10a5dO7zwwguil3uWaN26NbZs2YL33nsPO3bswDvvvIOioiLUqlULnTt3xnPPPafZz86dO+Obb77B6tWrkZubixo1amDw4MG65Zdnz56N+Ph4rF+/Hu+99x6aN2+ORYsW4YEHHkDlypWNXSQ3cXFxeOut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/fQT4uPjy7Q/RETexMENERFRGUhPT8cnn3yCZs2aoU+fPvjvf//r1/7Mnj0bjRs3Fi2LjY31T2eIiLyEgxsiIqIyUKdOHezduxcWiwVpaWl+H9xce+21aNWqlV/7QETkbcy5ISIiKgMWiwUWi8XQttu3b8fw4cPRtm1btGvXDuPGjcPhw4d93EMiouDHwQ0REVEAefPNNzFt2jQ0adIEixYtwty5c5GdnY177rkHx48f99pxJkyYgObNm+OGG27AlClTcPToUa+1TUTkL3wsjYiIKED8888/WLJkCe699148++yzruU33ngjbr31VixduhSLFi0q1TGqV6+OCRMmoG3btqhYsSKOHj2KFStWYPjw4fjoo4/QrFmzUp4FEZH/cHBDREQUIH766SfY7XYMGjQIdrvdtTwyMhIdO3bE7t27Xcu0qp5J7d27F5UrVwYA9OjRAz169HCt69ixI2666SYMHDgQr7/+OpYvX+6lsyEiKnsc3BAREQWIixcvAgDuuusuxfVW69Wnydu3b4+XX37ZULtRUVGa6+vWrYvrr78eBw4cMNhTIqLAxMENERFRgIiLiwMALF68GLVr19bctmHDhmjYsKHXji0IgmjwREQUjDi4ISIiChDdunVDWFgYTp8+jVtvvbXMjpucnIx9+/bhxhtvLLNjEhH5Agc3REREZeT7779Hbm4usrOzAQDHjx/Hjh07AAA33XQT6tati4cffhiLFi1CcnIyevTogcqVK+PixYs4ePAgoqOj8fDDD5eqD/fddx86dOiAZs2aoUKFCjh69CjeeecdWCwWPPLII6U+RyIif7IIgiD4uxNERETlQa9evXD27FnFdTt37kTdunUBAF9//TXWrFmDP/74AwUFBYiPj0fLli3xr3/9C126dClVH2bNmoWff/4Z//zzD/Lz81G1alV07twZkyZNQqNGjUrVNhGRv3FwQ0REREREIYGZg0REREREFBI4uCEiIiIiopDAwQ0REREREYUEDm6IiIiIiCgkcHBDREREREQhgYMbIiIiIiIKCXyJpw673Y6MjAxERkbCauVYkIjIncPhQH5+PqpUqYKwMP6XooT/jxARKfPF/yH8n0hHRkYGTp486e9uEBEFtIYNG6JatWr+7kZA4v8jRETavPl/CAc3OiIjIwEACQkNERER7fX2w8KA6M83YvWVIViyBGjTBli5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H3yAs2fPIj4+HkOGDMGECRNc71KhwLRr1y68/fbb+PPPP3HlyhXUrFkTvXr1wkMPPYS4uDjXdrwXyJuys7Px2muvYceOHUhPT0fjxo0xfvx4DBgwwN9dIw+MGjUKe/bsUV3/559/ur42828JBQdv/A5B3sHBDRERERERhQTm3BARERERUUjg4IaIiIiIiEICBzdERERERBQSOLghIiIiIqKQwMENERERERGFBA5uiIiIiIgoJHBwQ0REREREIYGDGyIiIiIiCgkc3BDp2LdvH5YsWYLMzEzZulGjRmHUqFF+6JW2zZs3o3PnzsjKyvJJ+0888QQmTZrkk7aJiPypPP2b36tXL4wfP95HvdK2ceNGJCYm4uDBg6Vu65577sErr7zihV5RKODghkjHb7/9hqVLlyr+R/f888/j+eef90Ov1OXm5mLhwoV48MEHUbFiRZ8cY+rUqfj+++/x66+/+qR9IiJ/4b/5weeRRx7BRx99hL///tvfXaEAwMENUSlcc801uOaaa/zdDZFNmzYhPT0dw4YN89kx6tevj+7du+Ptt9/22TGIiAJNef03P9DdcMMNaNSoEd59911/d4UCAAc3RBqWLFmCuXPnAgB69+6NxMREJCYmYvfu3QDkjyicOXMGiYmJeOedd7BixQr06tULrVu3xqhRo3DixAkUFhZi/vz56NatG66//npMnjwZly5dkh13+/btGD58ONq2bYt27dph3LhxOHz4sKE+f/TRR7j55ptRuXJl0fLExES8+OKL2Lx5M2677Ta0adMGd9xxB7799lvRdmlpaXjuuedw0003oWXLlujcuTNGjBiBX375RbTdHXfcgV9++QWnT5821C8iokAXSv/mOxwOrF27FoMGDULr1q3RoUMH3H333di5c6esjR9++AF33nknWrdujX79+mH9+vWy65KYmCjbr+TRsjNnzriWlTzqptemkpSUFAwZMgR9+/bFyZMnAQDJycl47LHH0K1bN7Rs2RI33ngjxowZg6SkJNG+d9xxB7Zu3eqzx7EpeIT5uwNEgWzYsGHIyMjA2rVrsXTpUsTHxwOA7szdhx9+iKZNm+I///kPMjMz8eqrr2LChAlo06YNwsLCMGvWLJw7dw6vvvoqnnnmGbz55puufd98800sWrQIQ4YMwcSJE1FYWIiVK1finnvuwX//+1/NY58/fx5Hjx7Fv/71L8X13333HQ4ePIiHH34YMTExeOeddzBlyhTs2LED9erVA+DMpzl8+DAee+wxNGzYEJmZmTh8+DDS09NFbXXq1AmCIOD7778PyGfQiYjMCqV/82fMmIHPPvsMd911Fx5++GGEh4fj/9u7t5Am3zgO4N/91U1r6jxUntJ0MVoeukkW5onKLEhG4boaQpQzguxAYrWuCsTISNPcRZmHi6yszEK8sS4qQiJWIoh2kVA4tbR1kEqZ638Re2lNs2mr8fr9gBe+e97f82wXz8P33fs+6+3txeDgoFO7vr4+nD59GoWFhQgPD0dLSwuMRiPi4uKQmprqzsc3r5ovXryAwWBAREQErl69itDQUABAYWEh7HY7SkpKEBUVBavVimfPnrncNqjRaFBRUYEnT55gw4YNcxo3iQPDDdEvREREIDIyEgCgVqsRExPzW+cFBgaitrYW//33/ctRq9WKsrIyJCQkwGQyCe1evnyJxsZGjI+PQy6XY2hoCNXV1dDr9Thx4oTQLi0tDbm5uaipqUFlZeWM/ZrNZgBAYmLitK9PTEygvr5euC87MTERGRkZ6OjogMFgEGrodDrs3LlTOG/Tpk0utcLCwrBs2TKYzWaGGyISBbHM+U+fPkVbWxv27t2LQ4cOCcczMzNdalitVjQ3NyMqKgoAkJqaiq6uLty9e3fO4cbdmo8fP8b+/fuxfv16nDlzBjKZTKgzMDCA48ePQ6vVCu03b97sUkOtVkMikcBsNjPcLHAMN0QekJWVJSxyAKBUKgEA2dnZTu0cxy0WC1QqFR49egSbzQatVgubzSa0k8lkSE1NFW6NmMmbN28AQLji9TONRuP0wGl4eDjCwsKcruSlpKSgtbUVCoUCaWlpSExMhJ+f37T1wsLCMDIy8ssxERGJnbfN+Q8ePADwfRex2ajVaiGEOPpesWIFLBbLrOf+iZq3b9/GtWvXoNfrUVpaColEIrymUCgQGxuLuro62O12aDQarFq1yumzdvDz80NQUBDXJGK4IfKE4OBgp/8d4WCm4xMTEwCA0dFRAEB+fv60daeb0H/kqOO46vUzhULhckwqlQrnAcC5c+dgMplw48YNVFVVYdGiRcjJyUFJSYlwi4aDTCbD169ffzkmIiKx87Y5/927d/Dx8XGZs6fzO+uCu9yp2d7eDplMBp1O5xRsAEAikaChoQEXLlzApUuXUF5eDoVCgby8PBw8eNBld7j5jpvEgeGGyIuEhIQAAM6fP+901cvd8z98+IClS5fOaQyhoaEwGo0wGo2wWCy4f/8+zp49i7GxMdTV1Tm1ff/+PaKjo+fUDxHRQuepOT80NBRTU1N4+/btnNeCHznC0+TkJKRSqXDcarXOu3ZFRQWqqqqg1+tx+fJlqNVqp9ejo6NRVlYGABgYGEBHRwdqamowOTmJkydPOrX9+PHjtMGKFhbulkY0C8dE/jeuBqWnp8PX1xevXr1CcnLytH+/kpCQAAB/bAezqKgo6PV6pKWluezcY7PZMDw87HXbohIRzYcY5nzHszXNzc1/ZJyOi1h9fX1Ox3/ebXMugoODUV9fD6VSiYKCAjx//nzGtvHx8di3bx9UKpXLmjQyMoKJiQmuScRvbohmo1KpAACNjY3Yvn07fH19ER8f75EfS4uJiUFxcTEqKyvx+vVrZGZmIigoCKOjo+jp6UFAQACKi4tnPD8lJQX+/v7o7u7Gxo0b3e7/06dPKCgowLZt25CQkIDFixejp6cHDx8+RE5OjlPb/v5+fPnyBRqNxu1+iIi8lRjm/LVr10Kr1cJkMmFsbAzZ2dmQSqXo7e1FQECA25vAZGVlQaFQwGg04sCBA/Dx8UFrayuGhobm/N5/JJfLhd07d+3aBZPJhHXr1qGvrw+nTp3Cli1bEBcXBz8/P3R1daG/v1/YBMehu7sbALgmEcMN0Ww0Gg2KiorQ2tqKlpYW2O12NDU1eWwCLSoqglKpRFNTE9rb2zE5OYklS5YgKSlpxi2eHaRSKXJzc3Hv3j0cPnzY7b5lMhlSUlLQ1taGwcFB2Gw2REZGorCwEHv27HFq29nZiZCQEKSnp7vdDxGRtxLLnF9eXo7Vq1fj5s2buHXrFvz9/bFy5UoUFRW5PUa5XI6LFy+irKwMJSUlCAwMhE6nQ0ZGhtMub/Ph7++P2tpaHDlyBAaDAdXV1UhKSkJsbCyuXLmC4eFhAMDy5ctRWlrqEtA6OzuhUqmm/T0eWlgk3759+/avB0FEf05PTw/y8/Nx/fp1rFmzxiN9TE1NIScnB3l5eU7bjBIR0d/1N+Z8bzc+Po6MjAwcO3bM6WcMaGHiMzdEIpOcnIytW7eitrbWY33cuXMHnz9/xu7duz3WBxERze5vzPnerqGhAZGRkdixY8e/Hgp5AYYbIhE6evQokpOTMT4+7pH6drsdFRUVCAoK8kh9IiL6fZ6e872dXC5HeXk5fH35tAXxtjQiIiIiIhIJfnNDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESiwHBDRERERESi8D9E3UJ1S39WZQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "### using the DatasetAnalysis context helps us to load data easily, and automatically save plots in the right folder.\n", + "\n", + "with DatasetAnalysis(data_loc) as analysis:\n", + " i_data = analysis.get_data('I', avg_over=None)\n", + " q_data = analysis.get_data('Q', avg_over=None)\n", + " raw_data = analysis.get_data('raw_signal', avg_over=None)\n", + " \n", + " fig = analysis.make_figure('a plot', figsize=(4,2))\n", + " \n", + " # first subfig: plot the raw voltage as a colormap. We set the limits such that 0 is the middle.\n", + " ax = fig.add_subplot(121)\n", + " im = ppcolormesh(ax,\n", + " raw_data.data_vals('raw_signal_time_points'),\n", + " raw_data.data_vals('repetition'),\n", + " raw_data.data_vals('raw_signal'),\n", + " make_grid=False,\n", + " cmap='bwr', \n", + " vmin=-np.abs(raw_data.data_vals('raw_signal')).max(),\n", + " vmax=np.abs(raw_data.data_vals('raw_signal')).max()\n", + " )\n", + " format_ax(ax, xlabel='time (ns)', ylabel='repetition')\n", + " cb = fig.colorbar(im, ax=ax, location='top')\n", + " cb.set_label('ADC signal (a.u.)')\n", + " \n", + " # second subfig: plot one example trace of demodulated I and Q.\n", + " ax = fig.add_subplot(122)\n", + " ax.plot(i_data.data_vals('I_time_points')[:,0], i_data.data_vals('I')[:,0], '-',\n", + " label='I')\n", + " ax.plot(q_data.data_vals('Q_time_points')[:,0], q_data.data_vals('Q')[:,0], '-',\n", + " label='Q')\n", + " format_ax(ax, xlabel='time (chunks)', ylabel='demod. signal (a.u.)')\n", + " ax.legend(loc='best')\n", + " \n", + " # this command saves the figures associated with the analysis in the data folder.\n", + " analysis.save()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79e604d3-759b-4fe2-83bb-31990a667c2e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:qcodes]", + "language": "python", + "name": "conda-env-qcodes-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/examples/opx_demo/parameter_manager-simple_demo_params.json b/doc/examples/opx_demo/parameter_manager-simple_demo_params.json new file mode 100644 index 0000000..9f8e639 --- /dev/null +++ b/doc/examples/opx_demo/parameter_manager-simple_demo_params.json @@ -0,0 +1,18 @@ +{ + "simple_demo_params.readout.IF": { + "unit": "Hz", + "value": 50000000.0 + }, + "simple_demo_params.readout.short.amp": { + "unit": "", + "value": 0.25 + }, + "simple_demo_params.readout.short.buffer": { + "unit": "ns", + "value": 100 + }, + "simple_demo_params.readout.short.len": { + "unit": "ns", + "value": 1000 + } +} \ No newline at end of file diff --git a/doc/examples/opx_demo/qmcfg_simple_demo.py b/doc/examples/opx_demo/qmcfg_simple_demo.py new file mode 100755 index 0000000..6cad159 --- /dev/null +++ b/doc/examples/opx_demo/qmcfg_simple_demo.py @@ -0,0 +1,106 @@ +""" +A very basic OPX config +""" + +from labcore.opx.config import QMConfig as QMConfig_ + + +class QMConfig(QMConfig_): + + def config_(self): + + params = self.params + + cfg = { + 'version': 1, + + # The hardware + 'controllers': { + + # edit this part so the hardware connections match your setup + 'con2': { + 'type': 'opx1', + 'analog_outputs': { + 5: {'offset': 0.0}, + }, + 'digital_outputs': { + 1: {}, + }, + 'analog_inputs': { + 2: {'offset': 0.0}, + }, + }, + }, + + # The logical elements + 'elements': { + + 'readout': { + 'singleInput': { + 'port': ('con2', 5), + }, + 'digitalInputs': { + 'readout_trigger': { + 'port': ('con2', 1), + 'delay': 144, + 'buffer': 0, + }, + }, + + 'intermediate_frequency': params.readout.IF(), + + 'operations': { + 'readout_short': 'readout_short_pulse', + }, + + 'outputs': { + 'out1': ('con2', 2), + }, + + 'time_of_flight': 188+28, + 'smearing': 0, + }, + }, + + # The pulses + 'pulses': { + + 'readout_short_pulse': { + 'operation': 'measurement', + 'length': params.readout.short.len(), + 'waveforms': { + 'single': 'box_readout_wf' + }, + 'digital_marker': 'ON', + }, + + }, + + # the waveforms + 'waveforms': { + 'const_wf': { + 'type': 'constant', + 'sample': 0.1, + }, + 'zero_wf': { + 'type': 'constant', + 'sample': 0.0, + }, + 'box_readout_wf': { + 'type': 'arbitrary', + 'samples': [0.0] * params.readout.short.buffer() \ + + [params.readout.short.amp()] * \ + (params.readout.short.len()-2*params.readout.short.buffer()) \ + + [0.0] * params.readout.short.buffer(), + }, + }, + + 'digital_waveforms': { + + 'ON': { + 'samples': [(1, 0)] + }, + + }, + } + return cfg diff --git a/labcore/analysis/__init__.py b/labcore/analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/labcore/analysis/common_fits.py b/labcore/analysis/common_fits.py new file mode 100644 index 0000000..b55466b --- /dev/null +++ b/labcore/analysis/common_fits.py @@ -0,0 +1,114 @@ +import numpy as np + +from plottr.analyzer.fitters.fitter_base import Fit + + +class ExponentialDecay(Fit): + @staticmethod + def model(coordinates, A, of, tau) -> np.ndarray: + """$A * \exp(-x/\tau) + of$""" + return A * np.exp(-coordinates/tau) + of + + @staticmethod + def guess(coordinates, data): + + # offset guess: The mean of the last 10 percent of the data + of = np.mean(data[-data.size//10:]) + + # amplitude guess: difference between max and min. + A = np.abs(np.max(data) - np.min(data)) + if data[0] < data[-1]: + A *= -1 + + # tau guess: pick the point where we reach roughly 1/e + one_over_e_val = of + A/3. + one_over_e_idx = np.argmin(np.abs(data-one_over_e_val)) + tau = coordinates[one_over_e_idx] + + return dict(A=A, of=of, tau=tau) + + +class ExponentiallyDecayingSine(Fit): + @staticmethod + def model(coordinates, A, of, f, phi, tau) -> np.ndarray: + """$A \sin(2*\pi*(f*x + \phi/360)) \exp(-x/\tau) + of$""" + return A * np.sin(2 * np.pi * (f * coordinates + phi/360)) * np.exp(-coordinates/tau) + of + + @staticmethod + def guess(coordinates, data): + """This guess will ignore the first value because since it usually is not relaiable.""" + + # offset guess: The mean of the data + of = np.mean(data) + + # amplitude guess: difference between max and min. + A = np.abs(np.max(data) - np.min(data)) / 2. + if data[0] < data[-1]: + A *= -1 + + # f guess: Maximum of the absolute value of the fourier transform. + fft_data = np.fft.rfft(data)[1:] + fft_coordinates = np.fft.rfftfreq(data.size, coordinates[1] - coordinates[0])[1:] + + # note to confirm, could there be multiple peaks? I am always taking the first one here. + f_max_index = np.argmax(fft_data) + f = fft_coordinates[f_max_index] + + # phi guess + phi = -np.angle(fft_data[f_max_index], deg=True) + + # tau guess: pick the point where we reach roughly 1/e + one_over_e_val = of + A/3. + one_over_e_idx = np.argmin(np.abs(data-one_over_e_val)) + tau = coordinates[one_over_e_idx] + + return dict(A=A, of=of, phi=phi, f=f, tau=tau) + +class Cosine(Fit): + @staticmethod + def model(coordinates, A, of, f, phi) -> np.ndarray: + """$A \sin(2*\pi*(f*x + \phi/360)) + of$""" + return A * np.cos(2 * np.pi * (f * coordinates + phi/360.)) + of + + @staticmethod + def guess(coordinates, data): + """This guess will ignore the first value because since it usually is not relaiable.""" + + # offset guess: The mean of the data + of = np.mean(data) + + # amplitude guess: difference between max and min. + A = np.abs(np.max(data) - np.min(data)) / 2. + + # f guess: Maximum of the absolute value of the fourier transform. + fft_data = np.fft.rfft(data)[1:] + fft_coordinates = np.fft.rfftfreq(data.size, coordinates[1] - coordinates[0])[1:] + + # note to confirm, could there be multiple peaks? I am always taking the first one here. + f_max_index = np.argmax(np.abs(fft_data)) + f = fft_coordinates[f_max_index] + + # phi guess + phi = -np.angle(fft_data[f_max_index], deg=True) + + guess = dict(A=A, of=of, phi=phi, f=f) + + return guess + + +class Gaussian(Fit): + @staticmethod + def model(coordinates, x0, sigma, A, of): + """$A * np.exp(-(x-x_0)^2/(2\sigma^2)) + of""" + return A * np.exp(-(coordinates - x0) ** 2 / (2 * sigma ** 2)) + of + + @staticmethod + def guess(coordinates, data): + # TODO: very crude at the moment, not likely to work well with not-so-nice data. + of = np.mean(data) + dev = data - of + i_max = np.argmax(np.abs(dev)) + x0 = coordinates[i_max] + A = data[i_max] - of + sigma = np.abs((coordinates[-1] - coordinates[0])) / 20 + return dict(x0=x0, sigma=sigma, A=A, of=of) diff --git a/labcore/analysis/data.py b/labcore/analysis/data.py new file mode 100644 index 0000000..7363420 --- /dev/null +++ b/labcore/analysis/data.py @@ -0,0 +1,271 @@ +"""Tools for more convenient data handling.""" + +import os +import re +from typing import Union, List, Optional, Type, Any, Dict +from types import TracebackType +from pathlib import Path +from datetime import datetime +import json +import logging + +import numpy as np +from matplotlib.figure import Figure +from matplotlib import pyplot as plt +from sklearn.decomposition import PCA + +from plottr.analyzer.base import AnalysisResult +from plottr.analyzer.fitters.fitter_base import FitResult +from plottr.data.datadict import DataDictBase, datadict_to_meshgrid, MeshgridDataDict +from plottr.data.datadict_storage import datadict_from_hdf5 + + +logger = logging.getLogger(__name__) + + +def data_info(folder: str, fn: str = 'data.ddh5', do_print: bool = True): + fn = Path(folder, fn) + dataset = datadict_from_hdf5(fn) + if do_print: + print(dataset) + else: + return str(dataset) + + +def timestamp_from_path(p: Path) -> datetime: + """Return a `datetime` timestamp from a standard-formatted path. + Assumes that the path stem has a timestamp that begins in ISO-like format + ``YYYY-mm-ddTHHMMSS``. + """ + timestring = str(p.stem)[:13] + ":" + str(p.stem)[13:15] + ":" + str(p.stem)[15:17] + return datetime.fromisoformat(timestring) + + +def find_data(root, + newer_than: Optional[datetime]=None, + older_than: Optional[datetime]=None, + folder_filter: Optional[str]=None) -> List[Path]: + + folders = [] + for f, dirs, files in os.walk(root): + if 'data.ddh5' in files: + fp = Path(f) + ts = timestamp_from_path(fp) + if newer_than is not None and ts <= newer_than: + continue + if newer_than is not None and ts >= older_than: + continue + if folder_filter is not None: + pattern = re.compile(folder_filter) + if not pattern.match(str(fp.stem)): + continue + + folders.append(fp) + return sorted(folders) + + +def get_data( + folder: Union[str, Path], + data_name: Optional[Union[str, List[str]]] = None, + fn: str = 'data.ddh5', + mk_grid: bool = True, + avg_over: Optional[str] = 'repetition', + rotate_complex: bool = False, + ) -> DataDictBase: + + """Get data from disk. + + Parameters + ---------- + folder + the folder containing the data file (a ddh5 file) + data_name + which dependent(s) to extract from the data. + if ``None``, return all data. + fn + the file name + mk_grid + if True, try to automatically place data on grid. + avg_over + if not ``None``, average over this axis if it exists. + rotate_complex + if True: try to rotate data automatically in IQ to map onto a single + axis and return real data. We use sklearn's PCA tool for that. + this is done after averaging. + + Returns + ------- + the resulting dataset + + """ + fn = Path(folder, fn) + dataset = datadict_from_hdf5(fn) + dataset.add_meta('dataset.folder', str(Path(folder))) + dataset.add_meta('dataset.filepath', str(fn)) + + if data_name is None: + data_name = dataset.dependents() + elif isinstance(data_name, str): + data_name = [data_name] + dataset = dataset.extract(data_name, copy=False) + + if mk_grid: + dataset = datadict_to_meshgrid(dataset, copy=False) + + if avg_over is not None and avg_over in dataset.axes() and isinstance(dataset, MeshgridDataDict): + dataset = dataset.mean(avg_over) + + if rotate_complex: + for d in data_name: + dvals = dataset.data_vals(d) + shp = dvals.shape + if np.iscomplexobj(dvals): + pca = PCA(n_components=1) + newdata = pca.fit_transform( + np.vstack((dvals.real.flatten(), dvals.imag.flatten())).T + ) + dataset[d]['values'] = newdata.reshape(shp) + + dataset.validate() + return dataset + + +class DatasetAnalysis: + + def __init__(self, folder): + self.figure_save_format = ['png', 'pdf'] + self.folder = folder + if not isinstance(self.folder, Path): + self.folder = Path(self.folder) + self.timestamp = str(datetime.now().replace(microsecond=0).isoformat().replace(':', '')) + + self.additionals = {} + + def __enter__(self): + return self + + def __exit__(self, exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType]) -> None: + pass + + def _new_file_path(self, name: str, suffix: str = '') -> Path: + if suffix != '': + name = name + '.' + suffix + return Path(self.folder, f"{self.timestamp}_{name}") + + # --- loading measurement data --- # + def get_data(self, data_name, *arg, **kw): + return get_data(self.folder, data_name, *arg, **kw) + + def load_saved_parameter(self, parameter_name, + parameter_manager_name='parameter_manager', + file_name='parameters.json'): + fn = Path(self.folder) / file_name + with open(fn, 'r') as f: + data = json.load(f) + + parameter_path = f"{parameter_manager_name}.{parameter_name}" + if parameter_path not in data: + raise ValueError('this parameter was not found in the saved meta data.') + + return data[parameter_path]['value'] + + # --- Adding analysis results --- # + def add(self, **kwargs: Any) -> None: + for k, v in kwargs.items(): + if k in self.additionals: + raise ValueError('element with that name already exists in this analysis.') + self.additionals[k] = v + + def add_figure(self, name, *arg, fig: Optional[None], **kwargs) -> Figure: + if name in self.figures: + raise ValueError('element with that name already exists in this analysis.') + if fig is None: + fig = plt.figure(*arg, **kwargs) + self.additionals[name] = fig + return fig + + make_figure = add_figure + + # --- Saving analysis results --- # + def save(self): + for name, element in self.additionals.items(): + if isinstance(element, Figure): + fp = self.save_figure(element, name) + + elif isinstance(element, AnalysisResult): + fp = self.save_add_dict_data(element.params_to_dict(), name+"_params") + if isinstance(element, FitResult): + fp = self.save_add_str(element.lmfit_result.fit_report(), name+"_lmfit_report") + + elif isinstance(element, np.ndarray): + fp = self.save_add_np(element, name) + + elif isinstance(element, dict): + fp = self.save_add_dict_data(element, name) + + elif isinstance(element, str): + fp = self.save_add_str(element, name) + + else: + logger.error(f"additional data '{name}' is not supported for saving!") + + def save_figure(self, fig: Figure, name: str): + """save a figure in a standard way to the dataset directory. + + Parameters + ---------- + fig + the figure instance + name + name to give the figure + fmt + file format (defaults to png) + + Returns + ------- + ``None`` + + """ + fmts = self.figure_save_format + if not isinstance(fmts, list): + fmts = [fmts] + + fig.suptitle(f"{self.folder.name}: {name}", fontsize='small') + + for f in fmts: + fp = self._new_file_path(name, f) + fig.savefig(fp) + + return fp + + def save_add_dict_data(self, data: dict, name: str): + fp = self._new_file_path(name, 'json') + # d = dict_arrays_to_list(data) + with open(fp, 'x') as f: + json.dump(data, f, cls=NumpyEncoder) + return fp + + def save_add_str(self, data: str, name: str): + fp = self._new_file_path(name, 'txt') + with open(fp, 'x') as f: + f.write(data) + return fp + + def save_add_np(self, data: np.ndarray, name: str): + fp = self._new_file_path(name, 'json') + with open(fp, 'x') as f: + json.dump({name: data}, f, cls=NumpyEncoder) + + # --- loading (and managing) earlier analysis results --- # + # TBD... + + + +# enable saving numpy to json +class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + return json.JSONEncoder.default(self, obj) diff --git a/labcore/analysis/fitting.py b/labcore/analysis/fitting.py new file mode 100644 index 0000000..d7f2db9 --- /dev/null +++ b/labcore/analysis/fitting.py @@ -0,0 +1,126 @@ +from copy import deepcopy +from typing import Type, Optional, Callable, Tuple, Any, Dict +import numpy as np + +from plottr.data.datadict import DataDict, MeshgridDataDict +from plottr.analyzer.fitters.fitter_base import Fit + + +def batch_fitting(analysis_class, all_data: Dict[Any, Tuple[np.ndarray, np.ndarray]], **kwargs): + """ fit multiple datafiles of the same analysis class + + Parameters + ---------- + analysis_class + the name of the class of the measurement + all_data + a dictionary whose values are tuples of coordinates and measured data, each of which corresponds to a data + file, and keys are the labels for the data files + + Returns + ------- + dict + a dictionary whose values are FitResult class objects containing the fit results of the data files, + and keys are the labels for the data files + """ + fit_results = {} + for label in all_data.keys(): + coordinates, data = all_data[label] + fit = analysis_class(coordinates, data) + fit_result = fit.run(**kwargs) + fit_results[label] = fit_result + + return fit_results + + +def params_from_batch_fitting_results(fit_results: dict, params: list[str]) -> Tuple[list[Any], Dict[str, Any]]: + """ extract parameter values and errors from the fit results and resort them into a dictionary whose keys are + the parameter and values are corresponding parameter values and errors + + Parameters + ---------- + fit_results + a dictionary whose values are FitResult class objects containing the fit results of the data files, + and keys are the labels for the data files + params + list of strings corresponding to the names of parameters one wants to look at + + Returns + ------- + dict + a dictionary whose keys are the parameter and values are corresponding parameter values and errors + """ + resorted_dict = {} + for param in params: + resorted_dict[param] = [] + resorted_dict[param + '_error'] = [] + + labels = [] + for label, fit_result in fit_results.items(): + labels.append(label) + for key, values in fit_result.lmfit_result.params.items(): + resorted_dict[key].append(values.value) + resorted_dict[key + '_error'].append(values.stderr) + return labels, resorted_dict + + +# FIXME: Docstring incomplete +def iterate_over_slices_1d(data: MeshgridDataDict, slice_ax: str): + slice_idx = data.axes().index(slice_ax) + d2 = data.reorder_axes(**{slice_ax: len(data.axes()) - 1}) + iter_shape = d2.shape()[:-1] + for idx in np.ndindex(iter_shape): + ret = { + '_axis_names': d2.axes()[:-1], + '_axis_idxs': idx, + } + for d in d2.dependents(): + ret[d] = d2.data_vals(d)[idx] + for a in d2.axes(): + ret[a] = d2.data_vals(a)[idx] + yield ret + + +# FIXME: Docstring incomplete +def batch_fit_1d(data: MeshgridDataDict, fit_class: Type[Fit], dep: str, indep: str, cb: Optional[Callable] = None): + # TODO: include option to look at the individual fits? include a multipage pdf, for instance... + # TODO: get back the best fit curves? + + # first: set up a copy of the structure, but omit the axes we fit over. + indep_ax_idx = data.axes().index(indep) + axes = deepcopy(data.axes()) + axes.pop(indep_ax_idx) + result_shape = list(data.shape()) # we also want the shape, up to the axis that'll disappear + result_shape.pop(indep_ax_idx) + result = deepcopy(data.structure(include_meta=False)) + del result[indep] + result[dep]['axes'] = axes + for d in data.dependents(): + del result[d] + + # next: populate ax values. as first-order approximation, simply use the first values along the + # dimension we fit + copy_idx = tuple(slice(None) if i != indep else 0 for i in data.axes()) + for ax in data.axes(): + if ax != indep: + result[ax]['values'] = data[ax]['values'][copy_idx] + + result['_fit_success'] = {'axes': axes, 'values': np.zeros(result_shape).astype(bool)} + for data1d in iterate_over_slices_1d(data, indep): + i = data1d['_axis_idxs'] + x = data1d[indep] + y = data1d[dep] + fit = fit_class(x, y) + fit_result = fit.run() + result['_fit_success']['values'][i] = fit_result.lmfit_result.success + for param_name, param in fit_result.lmfit_result.params.items(): + if param_name not in result: + result[param_name] = {'axes': axes, 'values': np.zeros(result_shape) * np.nan} + result[param_name + '-stderr'] = {'axes': axes, 'values': np.zeros(result_shape) * np.nan} + + if fit_result.lmfit_result.success: + result[param_name]['values'][i] = param.value + result[param_name + '-stderr']['values'][i] = param.stderr + + result.validate() + return result diff --git a/labcore/opx/__init__.py b/labcore/opx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/labcore/opx/config.py b/labcore/opx/config.py new file mode 100644 index 0000000..85e4b4c --- /dev/null +++ b/labcore/opx/config.py @@ -0,0 +1,332 @@ +import os +import logging +import json +from typing import Dict, Any, Optional + +import numpy as np + +from instrumentserver.helpers import nestedAttributeFromString +from qcuiuc_measurement.opx_tools.machines import close_my_qm + +logger = logging.getLogger(__name__) + + +# FIXME: Docstring incomplete +class QMConfig: + """ + Base class for a QMConfig class. The purpose of this class is to implement the real time changes of the + parameter manager with the OPX. We do this to always have the most up-to-date parameters from the + parameter manager and integration weights (which depend on the parameters in the parameter manager). + + By default, when a new config is generated this class will close any open QuantumMachines that are using the same + controllers that this config uses. To not do this pass False to close_other_machines in config(). + + The user should still manually write the config dictionary used for the specific physical setup that the + measurement is going to be performed but a few helper methods are implemented in the base class: two helper + methods to write integration weights and a method that creates and adds the integration weights to the config dict. + + If the constructor is overriden the new constructor should call the super constructor to pass the parameter manager + instance used. + + If the constructor is overriden because the parameter manager is not being used, the method config(self) also needs + to be overriden. + + To have the integration weights added automatically into the config dict, you have to implement config_(self). + config_(self) should return the python dictionary without the integration weights in it. + If this is the case, the already implemented config(self) method will add the integration weights when called and + return the python dictionary with the integration weights added. + + The add_integration_weights will go through the items in the pulses dictionary and add weights to any + pulse that has the following characteristics: + * The key of the pulse starts with the str 'readout'. If the first 7 characters of the key are not 'readout' + that pulse will be ignored. + * It needs to have the string '_pulse', present in its key. anything that comes before '_pulse' is + as the name of the pulse, anything afterwards gets ignored. + e.g. If my pulse name is: 'readout_short_pulse', 'readout_short' is taken as a unique pulse that requires + integration weights. If another pulse exists called: 'readout_short_pulse_something_else' + add_integration_weights will add the same integration weights as it did to 'readout_short'. + This is useful if you have 2 different pules of the same length that need the same integration weights. + * For each unique pulse there needs to be a parameter in the parameter manager with the unique pulse name + followed by len. This is where the length of the pulse will be taken to get the + integration weights. An underscore ('_') in the pulse name will be interpreted as a dot ('.') in the + parameter manager. + e.g. If I have a pulse with name 'readout_short_pulse', the pulse name is 'readout_short' and there should + be a parameter in the parameter manager called: 'readout.short.len' with the pulse length in it. + Any pulse that does not fulfil any of the three requirements will be ignored and will not have + integration weights. + + 3 integration weights will be created for each unique pulse, a flat integration weights + for a full demodulation, flat integration weights for a sliced demodulation and weighted integration weights. + The weights for the weighted integration will be loaded from the calibration files folder. + If no weights are found in the calibration folder for a pulse, flat ones will be used instead. + If old integration weights are found in the file, they will be deleted. + + :param params: The instance of the parameter manager where the length of the pulses are stored. + :param opx_address: The address of the OPX where the config is going to get used. + :param opx_port: The port of the OPX where the config is going to get used. + """ + + def __init__(self, params, opx_address: Optional[str] = None, opx_port: Optional[int] = None, + octave=None) -> None: + self.params = params + self.opx_address = opx_address + self.opx_port = opx_port + self.octave = octave + + def __call__(self, *args, **kwargs) -> Dict[str, Any]: + return self.config() + + def config(self, close_other_machines: bool = True) -> Dict[str, Any]: + """ + Creates the config dictionary. + + :param close_other_machines: If True, closes any currently open qm in the opx that uses the same controllers + that this config is using. + :returns: The config dictionary. + """ + original = self.config_() + conf_with_weights = self.add_integration_weights(original) + with_random_wf = self.add_random_waveform(conf_with_weights) + if close_other_machines: + if self.opx_port is None or self.opx_address is None: + logger.warning(f'opx_port or opx_adress are empty, cannot close qm.') + else: + close_my_qm(with_random_wf, self.opx_address, self.opx_port) + return with_random_wf + + def config_(self) -> Dict[str, Any]: + raise NotImplementedError + + def add_random_waveform(self, conf): + """ + Adds a random waveform to the config dictionary. This is needed becaouse of a bug in the qm code that does not + close open QuantumMachines if the configuration is exactly the same. + """ + config_dict = conf.copy() + config_dict['waveforms']['random_wf'] = {'type': 'constant', + 'sample': np.random.rand() * 0.1} + return config_dict + + def add_integration_weights(self, conf): + """ + Automatically add integration weights to the config dictionary. See module docstring for further explanation on + the rules for this to work. + """ + integration_weights_file = 'calibration_files/integration_weights.json' + + # Changes to True if old integration weights are found + deleted_weights = False + + # Used to not repeat missing file warning + no_file_warning = False + config_dict = conf.copy() + pulses = {} + + if os.path.exists(integration_weights_file): + with open(integration_weights_file) as json_file: + loaded_weights = json.load(json_file) + else: + loaded_weights = None + + # Go throguh pulses and check if they should have integration weights + for key in config_dict['pulses'].keys(): + if 'readout_' in key and '_pulse' in key: + path_to_param = key.split('_') + readout_pulse_name = '' + + # find the parameter name as it should be in the parameter manager. + # note: it may be nested under something else! + for k in path_to_param: + if k[:5] == 'pulse': + break # found it! continue... + else: + # add all the stuff it's nested in... + if len(readout_pulse_name) > 0: + readout_pulse_name += f'_{k}' + else: + readout_pulse_name += f"{k}" + + pulse = readout_pulse_name # don't ask... too lazy to find all instances. + readout_param_name = readout_pulse_name.replace('_', '.') + len_param_name = readout_param_name + '.len' + + # str with the name of the length of the pulse in the param manager + if self.params.has_param(len_param_name): + if readout_pulse_name not in pulses.keys(): + pulse_len = nestedAttributeFromString(self.params, len_param_name)() + + # Using the old integration weights style for the sliced weights because the OPX currently + # raises an exception when using the new ones. + flat = [(0.2, pulse_len)] + flat_sliced = [0.2] * int(pulse_len // 4) + empty = [(0.0, pulse_len)] + empty_sliced = [0.0] * int(pulse_len // 4) + + pulses[pulse] = {} + pulses[pulse][pulse + '_cos'] = { + 'cosine': flat, + 'sine': empty + } + pulses[pulse][pulse + '_sin'] = { + 'cosine': empty, + 'sine': flat + } + pulses[pulse][pulse + '_sliced_cos'] = { + 'cosine': flat_sliced, + 'sine': empty_sliced + } + pulses[pulse][pulse + '_sliced_sin'] = { + 'cosine': empty_sliced, + 'sine': flat_sliced + } + + # Creating the variables for the weighted integration weights. + # If integrationg weights of the correct length are found on file, they get overwritten with + # the proper loaded weights. + pulse_weight_I = flat + pulse_weight_Q = flat + + pulse_weight_empty = empty + + if loaded_weights is not None: + # Check if the current pulse has loaded integration weights + if any(pulse in weights for weights in loaded_weights): + pulse_weight_I_temp = loaded_weights[pulse + '_I'] + pulse_weight_Q_temp = loaded_weights[pulse + '_Q'] + + I_length = sum(i[1] for i in pulse_weight_I_temp) + Q_length = sum(i[1] for i in pulse_weight_Q_temp) + + # Check that they are the correct length. + if I_length == pulse_len and Q_length == pulse_len: + pulse_weight_I = pulse_weight_I_temp + pulse_weight_Q = pulse_weight_Q_temp + + pulse_weight_empty = [(0.0, 40)] * len(pulse_weight_I) + logging.info(f'Loaded weighted integration weights for {pulse}.') + else: + logging.info(f'Found old integration weights for {pulse}, deleting them from file.') + loaded_weights.pop(pulse + '_I') + loaded_weights.pop(pulse + '_Q') + deleted_weights = True + + else: + logging.info(f'No integration weights found for {pulse}, using flat weights.') + else: + if not no_file_warning: + no_file_warning = True + logging.info('Integration weights file not found, using flat weights.') + + pulses[pulse][pulse + '_weighted_cos'] = { + 'cosine': pulse_weight_I, + 'sine': pulse_weight_empty + } + pulses[pulse][pulse + '_weighted_sin'] = { + 'cosine': pulse_weight_empty, + 'sine': pulse_weight_Q + } + + # Assembling the dictionary that the config dict needs in each pulse for integration weights. + possible_integration_weights_per_pulse = {} # Dictionary with integration weights for this pulse. + for weights in pulses[pulse].keys(): + possible_integration_weights_per_pulse[weights] = weights + config_dict["pulses"][key]['integration_weights'] = possible_integration_weights_per_pulse + + # Assembling the 'integration_weights' dictionary. + integration_weights = {} + for pul, val in pulses.items(): + for integration_name, int_weight in val.items(): + integration_weights[integration_name] = int_weight + + config_dict['integration_weights'] = integration_weights + + if loaded_weights is not None: + # Check that there are not old integration weights for pulses that don't exists anymore. + delete = [] + for weights in loaded_weights.keys(): + if weights[:-2] not in pulses.keys(): + delete.append(weights) + deleted_weights = True + + for old_weight in delete: + # Delete the weight from the file if these weights are not used. + loaded_weights.pop(old_weight) + + # Delete weights that should be deleted and save the weights without the old ones present. + if deleted_weights: + os.remove(integration_weights_file) + if len(loaded_weights) != 0: + with open(integration_weights_file, 'w') as file: + json.dump(loaded_weights, file) + + return config_dict + + # The following are helper methods written by Quantum Machines to create integration weights + def _round_to_fixed_point_accuracy(self, x, base=2 ** -15): + """ + Written by Quantum Machines. + """ + + return np.round(base * np.round(np.array(x) / base), 20) + + def convert_full_list_to_list_of_tuples(self, integration_weights, N=100, accuracy=2 ** -15): + """ + Written by Quantum Machines. + + Converts a list of integration weights, in which each sample corresponds to a clock cycle (4ns), to a list + of tuples with the format (weight, time_to_integrate_in_ns). + Can be used to convert between the old format (up to QOP 1.10) to the new format introduced in QOP 1.20. + + :param integration_weights: A list of integration weights. + :param N: Maximum number of tuples to return. The algorithm will first create a list of tuples, + and then if it is + too long, it will run :func:`compress_integration_weights` on them. + :param accuracy: The accuracy at which to calculate the integration weights. Default is 2^-15, which is + the accuracy at which the OPX operates for the integration weights. + :type integration_weights: list[float] + :type N: int + :type accuracy: float + :return: List of tuples representing the integration weights + """ + integration_weights = self._round_to_fixed_point_accuracy(integration_weights, accuracy) + changes_indices = np.where(np.abs(np.diff(integration_weights)) > 0)[0].tolist() + prev_index = -1 + new_integration_weights = [] + for curr_index in (changes_indices + [len(integration_weights) - 1]): + constant_part = (integration_weights[curr_index].tolist(), round(4 * (curr_index - prev_index))) + new_integration_weights.append(constant_part) + prev_index = curr_index + + new_integration_weights = self.compress_integration_weights(new_integration_weights, N=N) + return new_integration_weights + + def compress_integration_weights(self, integration_weights, N=100): + """ + Written by Quantum Machines. + + Compresses the list of tuples with the format (weight, time_to_integrate_in_ns) to one with length < N. + Works by iteratively finding the nearest integration weights and combining them with a weighted average. + + :param integration_weights: The integration_weights to be compressed. + :param N: The maximum list length required. + :return: The compressed list of tuples representing the integration weights. + """ + while len(integration_weights) > N: + diffs = np.abs(np.diff(integration_weights, axis=0)[:, 0]) + min_diff = np.min(diffs) + min_diff_indices = np.where(diffs == min_diff)[0] + integration_weights = np.array(integration_weights) + times1 = integration_weights[min_diff_indices, 1] + times2 = integration_weights[min_diff_indices + 1, 1] + weights1 = integration_weights[min_diff_indices, 0] + weights2 = integration_weights[min_diff_indices + 1, 0] + integration_weights[min_diff_indices, 0] = (weights1 * times1 + weights2 * times2) / (times1 + times2) + integration_weights[min_diff_indices, 1] = times1 + times2 + integration_weights = np.delete(integration_weights, min_diff_indices + 1, 0) + integration_weights = list(zip(integration_weights.T[0].tolist(), + integration_weights.T[1].astype(int).tolist())) + + return integration_weights + + def configure_octave(self, qmm, qm): + raise NotImplementedError diff --git a/labcore/opx/machines.py b/labcore/opx/machines.py new file mode 100644 index 0000000..24a4497 --- /dev/null +++ b/labcore/opx/machines.py @@ -0,0 +1,25 @@ +from qm.QuantumMachinesManager import QuantumMachinesManager + + +def close_my_qm(config, host, port): + """ + Helper function that closes any machines that is open in the OPT with host and port that uses any controller that + is present in the passed config. + + Parameters + ---------- + config + Config dictionary from which we are trying to open a QuantumMachine + host + The OPT host ip address + port + The OPT port + + """ + qmm = QuantumMachinesManager(host=host, port=port) + controllers = [con for con in config['controllers'].keys()] + open_qms = [qmm.get_qm(machine_id=machine_id) for machine_id in qmm.list_open_quantum_machines()] + for qm in open_qms: + for con in controllers: + if con in qm.list_controllers(): + qm.close() diff --git a/labcore/opx/sweep.py b/labcore/opx/sweep.py new file mode 100644 index 0000000..b701bf1 --- /dev/null +++ b/labcore/opx/sweep.py @@ -0,0 +1,241 @@ +from typing import Callable, Dict, Generator, List, Optional +from functools import wraps +from dataclasses import dataclass +import time +import logging + +import numpy as np + +from qm.qua import * +from qm.QuantumMachinesManager import QuantumMachinesManager + +from labcore.measurement import * +from labcore.measurement.record import make_data_spec +from labcore.measurement.sweep import AsyncRecord + +from .config import QMConfig + +# --- Options that need to be set by the user for the OPX to work --- +# config object that when called returns the config dictionary as expected by the OPX +config: Optional[QMConfig] = None # OPX config dictionary + +logger = logging.getLogger(__name__) + + +@dataclass +class TimedOPXData(DataSpec): + def __post_init__(self): + super().__post_init__() + if self.depends_on is None or len(self.depends_on) == 0: + deps = [] + else: + deps = list(self.depends_on) + self.depends_on = [self.name + '_time_points'] + deps + + +@dataclass +class ComplexOPXData(DataSpec): + i_data_stream: str = 'I' + q_data_stream: str = 'Q' + + +class RecordOPXdata(AsyncRecord): + """ + Implementation of AsyncRecord for use with the OPX machine. + """ + + def __init__(self, *specs): + self.communicator = {} + # self.communicator['raw_variables'] = [] + self.user_data = [] + self.specs = [] + for s in specs: + spec = make_data_spec(s) + self.specs.append(spec) + if isinstance(spec, TimedOPXData): + tspec = indep(spec.name + "_time_points") + self.specs.append(tspec) + self.user_data.append(tspec.name) + + def setup(self, fun, *args, **kwargs) -> None: + """ + Establishes connection with the OPX and starts the measurement. The config of the OPX is passed through + the module variable global_config. It saves the result handles and saves initial values to the communicator + dictionary. + """ + # Start the measurement in the OPX. + qmachine_mgr = QuantumMachinesManager(host=config.opx_address, port=config.opx_port, + octave=config.octave) + qmachine = qmachine_mgr.open_qm(config(), close_other_machines=False) + if config.octave is not None: + config.configure_octave(qmachine_mgr, qmachine) + qmachine = qmachine_mgr.open_qm(config(), close_other_machines=False) + job = qmachine.execute(fun(*args, **kwargs)) + result_handles = job.result_handles + + # Save the result handle and create initial parameters in the communicator used in the collector. + self.communicator['result_handles'] = result_handles + self.communicator['active'] = True + self.communicator['counter'] = 0 + self.communicator['manager'] = qmachine_mgr + self.communicator['qmachine'] = qmachine + self.communicator['qmachine_id'] = qmachine.id + + # FIXME change this such that we make sure that we have enough data on all handles + def _wait_for_data(self, batchsize: int) -> None: + """ + Waits for the opx to have measured more data points than the ones indicated in the batchsize. Also checks that + the OPX is still collecting data, when the OPX is no longer processing, turn communicator['active'] to False to + exhaust the collector. + + :param batchsize: Size of batch. How many data-points is the minimum for the sweep to get in an iteration. + e.g. if 5, _control_progress will keep running until at least 5 new data-points + are available for collection. + """ + + # When ready becomes True, the infinite loop stops. + ready = False + + # Collect necessary values from communicator. + res_handle = self.communicator['result_handles'] + counter = self.communicator['counter'] + + while not ready: + statuses = [] + processing = [] + for name, handle in res_handle: + current_datapoint = handle.count_so_far() + + # Check if the OPX is still processing. + if res_handle.is_processing(): + processing.append(True) + + # Check if enough data-points are available. + if current_datapoint - counter >= batchsize: + statuses.append(True) + else: + statuses.append(False) + + else: + # Once the OPX is done processing turn ready True and turn active False to exhaust the generator. + statuses.append(True) + processing.append(False) + # self.communicator['active'] = False + + if not False in statuses: + ready = True + if not True in processing: + self.communicator['active'] = False + + def cleanup(self): + """ + Functions in charge of cleaning up any software tools that needs cleanup. + + Currently, manually closes the qmachine in the OPT so that simultaneous measurements can occur. + """ + logger.info('Cleaning up') + + manager = self.communicator['manager'] + qm_id = self.communicator['qmachine_id'] + open_machines = manager.list_open_quantum_machines() + logger.info(f"currently open QMs: {open_machines}") + if qm_id in open_machines: + qmachine = manager.get_qm(qm_id) + qmachine.close() + logger.info(f"QM with ID {qm_id} closed.") + + manager.close() + logger.info(f"QMM closed.") + del self.communicator['qmachine'] + del self.communicator['manager'] + + def collect(self, batchsize: int = 100) -> Generator[Dict, None, None]: + """ + Implementation of collector for the OPX. Collects new data-points from the OPX and yields them in a dictionary + with the names of the recorded variables as keywords and numpy arrays with the values. Raises ValueError if a + stream name inside the QUA program has a different name than a recorded variable and if the amount of recorded + variables and streams are different. + + :param batchsize: Size of batch. How many data-points is the minimum for the sweep to get in an iteration. + e.g. if 5, _control_progress will keep running until at least 5 new data-points + are available for collection. + """ + + # Get the result_handles from the communicator. + result_handle = self.communicator['result_handles'] + try: + while self.communicator['active']: + # Restart values for each iteration. + return_data = {} + counter = self.communicator['counter'] # Previous iteration data-point number. + first = True + available_points = 0 + ds: Optional[DataSpec] = None + + # Make sure that the result_handle is active. + if result_handle is None: + yield None + + # Waits until new data-points are ready to be gathered. + self._wait_for_data(batchsize) + + def get_data_from_handle(name, up_to): + if up_to == counter: + return None + handle = result_handle.get(name) + handle.wait_for_values(up_to) + data = np.squeeze(handle.fetch(slice(counter, up_to))['value']) + return data + + for i, ds in enumerate(self.specs): + if isinstance(ds, ComplexOPXData): + iname = ds.i_data_stream + qname = ds.q_data_stream + if i == 0: + available_points = result_handle.get(iname).count_so_far() + idata = get_data_from_handle(iname, up_to=available_points) + qdata = get_data_from_handle(qname, up_to=available_points) + if (qdata is None or idata is None): + print(f'qdata is: {qdata}') + print(f'idata is: {idata}') + print(f'available points is:{available_points}') + print(f'i is: {i}') + print(f'ds is: {ds}') + print(f'iname is: {iname}') + print(f'qname is: {qdata}') + print(f'am I active: {self.communicator["active"]}') + print(f'counter is: {self.communicator["counter"]}') + + if qdata is not None and idata is not None: + return_data[ds.name] = idata + 1j * qdata + + elif ds.name in self.user_data: + continue + + elif ds.name not in result_handle: + raise RuntimeError(f'{ds.name} specified but cannot be found in result handle.') + + else: + name = ds.name + if i == 0: + available_points = result_handle.get(name).count_so_far() + return_data[name] = get_data_from_handle(name, up_to=available_points) + + if isinstance(ds, TimedOPXData): + data = return_data[ds.name] + if data is not None: + tvals = np.arange(1, data.shape[-1] + 1) + if len(data.shape) == 1: + return_data[name + '_time_points'] = tvals + elif len(data.shape) == 2: + return_data[name + '_time_points'] = np.tile(tvals, data.shape[0]).reshape( + data.shape[0], -1) + else: + raise NotImplementedError('someone needs to look at data saving ASAP...') + + self.communicator['counter'] = available_points + yield return_data + + finally: + self.cleanup() + diff --git a/labcore/plotting/__init__.py b/labcore/plotting/__init__.py index e69de29..b826f31 100644 --- a/labcore/plotting/__init__.py +++ b/labcore/plotting/__init__.py @@ -0,0 +1,3 @@ + + +# TODO: Add the important things from plotting in this file. \ No newline at end of file diff --git a/labcore/plotting/basics.py b/labcore/plotting/basics.py new file mode 100644 index 0000000..e2a343f --- /dev/null +++ b/labcore/plotting/basics.py @@ -0,0 +1,515 @@ +from typing import Tuple, List, Optional, Union +import logging + +import numpy as np +from numpy import ndarray +from numpy import complexfloating +import matplotlib as mpl +from matplotlib import pyplot as plt +from matplotlib.figure import Figure +from matplotlib.axes import Axes +from matplotlib import gridspec, cm, colors, ticker +from matplotlib.colors import rgb2hex +import seaborn as sns + +from plottr.analyzer.fitters.fitter_base import FitResult, Fit + +# default_cmap = cm.viridis + +logger = logging.getLogger(__name__) + + +def _fit_and_plot(x: Union[List, Tuple, ndarray], y: Union[List, Tuple, ndarray], + ax: Axes, residual_ax: Optional[Axes] = None, + data_color: str = 'tab:blue', data_label: str = 'data', fit_class: Optional[Fit] = None, + xlabel: str = '', ylabel: str = '', + initial_guess: bool = False, **guesses) -> FitResult: + """ + Helper function that plots and fits the data passed in the axis passed. + + Parameters + ---------- + x: + Array-like. Data for the x-axis. + y: + Array-like. Data for the y-axis. Can be complex. + ax: + The axis where to plot the data. + residual_ax: + axes for plotting the residuals; will only be plotted if axes are supplied. + data_color: + The color of the data line. + fit_class: + plottr Fit class of the intended fit. Defaults to None. + xlabel: + Label for the x-axis. Defaults to ''. + ylabel: + Label for the y-axis. Defaults to ''. + initial_guess: + If True, the initial guess of the fit will also be plotted. Defaults to False. + guesses: + Kwargs for custom initial parameters for fitting. + + Returns + ------- + FitResult + The FitResult of the fitting. + """ + + fit_result = None + line_handles = {} + data_line, = ax.plot(x, y, '.', color=data_color) + line_handles[data_label] = data_line + + if fit_class is not None: + fit = fit_class(x, y) + + if initial_guess: + guess_result = fit.run(dry=True, **guesses) + guess_y = guess_result.eval(coordinates=x) + initial_guess_line, = ax.plot(x, guess_y, '--', color='tab:green') + line_handles['i.g.'] = initial_guess_line + + fit_result = fit.run(**guesses) + fit_y = fit_result.eval() + + lbl = "best fit:" + for key, param in fit_result.params.items(): + lbl += "\n" + f"{key} = {param.value:.2E}" + fit_line, = ax.plot(x, fit_y, color='red') + line_handles[lbl] = fit_line + + add_legend(ax, legend_ref_point='upper left', **line_handles) + # ax.legend(loc='best', fontsize='x-small') + + if residual_ax is not None: + residuals = fit_y - y + residual_ax.plot(x, residuals, '.') + + return fit_result + + +def plot_data_and_fit_1d(x: Union[List, Tuple, ndarray], y: Union[List, Tuple, ndarray], + fit_class: Optional[Fit] = None, + xlabel: str = '', + ylabel: str = '', + initial_guess: bool = False, + fig: Optional[Figure] = None, + **guesses) -> Tuple[Figure, FitResult]: + """ + Fits and plots 1 dimensional data. + + If the data passed is complex, two subplots will be created, one with the real data and the other one with the + imaginary data (the y-axis of each plot will be labeled indicating which one is which, as well as the legend). + If a standard plottr fitting class is passed as an argument, the function will fit and return the fit result. + + Parameters + ---------- + x: + Array-like. Data for the x-axis. + y: + Array-like. Data for the y-axis. Can be complex. + fit_class: + plottr Fit class of the intended fit. Defaults to None. + xlabel: + Label for the x-axis. + ylabel: + Label for the y-axis. + initial_guess: + If True, the initial guess of the fit will also be plotted. Defaults to False. + figure: + If a figure is supplied, plots will be generated in that one; otherwise a new figure is created. + guesses: + Kwargs for custom initial parameters for fitting. + + Returns + ------- + Tuple[Figure, FitResult] + The first item of the tuple contains the matplotlib Figure. The second item contains the fit result from the + fitting. If the data is complex, the second item is a List with both fit results in it. + If no fit_class is passed, the second item of the return Tuple is None. + """ + + if fig is None: + fig = plt.figure() + + if np.iscomplex(y).any(): + y_ = y.real + logger.warning('Ignoring imaginary part of the data.') + else: + y_ = y + + ax = fig.add_subplot(211) + rax = fig.add_subplot(212, sharex=ax) + + fit_result = _fit_and_plot(x, y, ax, residual_ax=rax, + data_label='data', fit_class=fit_class, + initial_guess=initial_guess, **guesses) + + format_ax(ax, xlabel=xlabel, ylabel=ylabel) + format_ax(rax, xlabel=xlabel, ylabel=f'{ylabel} residuals') + + if fit_result is not None: + print(fit_result.lmfit_result.fit_report()) + + return fig, fit_result + + +def readout_hist(signal: ndarray, fig_ax: Optional[Tuple[Figure, Axes]] = None, + nbins: int = 41) -> Tuple[Figure, Axes]: + """ + Plots an IQ histogram. + + Parameters + ---------- + signal: + array-like, data in complex form. + + Returns + ------- + Figure + The matplotlib Figure with the plot. + """ + I = signal.real + Q = signal.imag + lim = max((I ** 2. + Q ** 2.) ** .5) + + if fig_ax is None: + fig, ax = plt.subplots(1, 1) + else: + fig, ax = fig_ax + + ax.set_xlabel('I') + ax.set_ylabel('Q') + ax.axvline(0, color='k', lw=0.5, dashes=[2, 2]) + ax.axhline(0, color='k', lw=0.5, dashes=[2, 2]) + _hist, _xe, _ye, im = ax.hist2d(I, Q, bins=nbins, + range=[[-lim, lim], [-lim, lim]]) + + ax.set_aspect('equal') + format_ax(ax, xlabel='I', ylabel='Q') + + cb = fig.colorbar(im, ax=ax, shrink=0.5) + format_right_cb(cb) + cb.ax.set_xlabel('cts', ha='left') + + return fig, ax + + +# tools for prettier plotting +def pplot(ax, x, y, yerr=None, linex=None, liney=None, color=None, fmt='o', + alpha=0.5, mew=0.5, **kw): + zorder = kw.pop('zorder', 2) + line_dashes = kw.pop('line_dashes', []) + line_lw = kw.pop('line_lw', 2) + line_alpha = kw.pop('line_alpha', 0.5) + line_color = kw.pop('line_color', color) + line_zorder = kw.pop('line_zorder', 1) + line_from_ypts = kw.pop('line_from_ypts', False) + elinewidth = kw.pop('elinewidth', 0.5) + label = kw.pop('label', None) + label_x = kw.pop('label_x', x[-1]) + label_y_ofs = kw.pop('label_y_ofs', 0) + label_kw = kw.pop('label_kw', {}) + fill_color = kw.pop('fill_color', None) + + syms = [] + + if linex is None: + linex = x + + if type(liney) == str: + if liney == 'data': + liney = y + + edge_plot_kws = dict(mfc='None', mew=mew, zorder=zorder) + edge_plot_kws.update(kw) + if colors is not None: + edge_plot_kws['mec'] = color + edge, = ax.plot(x, y, fmt, **edge_plot_kws) + color = edge.get_color() + + # TODO: the z-ordering with this method isn't too great (error bars may + # be hidden behind the line...) + if yerr is not None: + err = ax.errorbar(x, y, yerr=yerr, fmt='none', ecolor=color, capsize=0, + elinewidth=elinewidth, zorder=zorder - 1) + empty_symbol_kws = edge_plot_kws.copy() + empty_symbol_kws.update({'mfc': 'w', 'mew': 0, 'zorder': zorder - 1}, ) + _ = ax.plot(x, y, fmt, **empty_symbol_kws) + # syms.append(err) + + if liney is None and line_from_ypts: + liney = y.copy() + + if liney is not None: + if line_color is None: + line_color = color + line, = ax.plot(linex, liney, dashes=line_dashes, lw=line_lw, + color=line_color, zorder=line_zorder, alpha=line_alpha) + syms.append(line) + + if fill_color is None: + fill_color = color + + fill, = ax.plot(x, y, fmt, mec='none', mfc=fill_color, alpha=alpha, + zorder=zorder - 1, **kw) + + syms.append(fill) + syms.append(edge) + + if label is not None: + label_idx = np.argmin(np.abs(x - label_x)) + ax.annotate(label, (label_x, y[label_idx] + label_y_ofs), + color=color, **label_kw) + + return tuple(syms) + + +def ppcolormesh(ax, x, y, z, make_grid=True, **kw): + if make_grid: + _x, _y = pcolorgrid(x, y) + else: + _x, _y = x, y + + im = ax.pcolormesh(_x, _y, z, **kw) + ax.set_xlim(_x.min(), _x.max()) + ax.set_ylim(_y.min(), _y.max()) + + return im + + +def waterfall(ax, xs, ys, offset=None, style='pplot', **kw): + cmap = kw.pop('cmap', mpl.rcParams['image.cmap']) + linex = kw.pop('linex', xs) + liney = kw.pop('liney', None) + draw_baselines = kw.pop('draw_baselines', False) + baseline_kwargs = kw.pop('baseline_kwargs', {}) + + ntraces = ys.shape[0] + if offset is None: + offset = ys.max() - ys.min() + + if 'color' not in kw: + colorseq = get_color_cycle(ntraces, colormap=cmap) + else: + c = kw.pop('color', None) + colorseq = [c for n in range(ntraces)] + + for iy, yvals in enumerate(ys): + x = xs if len(xs.shape) == 1 else xs[iy] + y = yvals + iy * offset + lx = linex if len(linex.shape) == 1 else linex[iy] + ly = None if liney is None else liney[iy] + iy * offset + color = colorseq[iy] + + if draw_baselines: + baseline_opts = dict(color=color, lw=1, dashes=[1, 1]) + for k, v in baseline_kwargs: + baseline_opts[k] = v + ax.axhline(iy * offset, **baseline_opts) + + if style == 'pplot': + pplot(ax, x, y, linex=lx, liney=ly, color=color, **kw) + elif style == 'lines': + ax.plot(x, y, '-', color=color, **kw) + + +def plot_wigner(ax, xs, ys, zs, norm=None, clim=None, **kw): + cmap = kw.pop('cmap', cm.bwr) + xticks = kw.pop('xticks', None) + yticks = kw.pop('yticks', None) + xticklabels = kw.pop('xticklabels', None) + yticklabels = kw.pop('yticklabels', None) + + if norm is None and clim is None: + clim = max(abs(zs.min()), zs.max()) + elif norm is None: + norm = colors.Normalize(vmin=-abs(clim), vmax=abs(clim)) + + im = ppcolormesh(ax, xs, ys, zs, norm=norm, cmap=cmap) + + if xticks is None: + xtick = max(xs) // 1 + xticks = [-xtick, 0, xtick] + if yticks is None: + ytick = max(ys) // 1 + yticks = [-ytick, 0, ytick] + + ax.set_xticks(xticks) + ax.set_yticks(yticks) + + if xticklabels is not None: + ax.set_xticklabels(xticklabels) + if yticklabels is not None: + ax.set_yticklabels(yticklabels) + + return im + + +# some common tools +# ================= + +# color management tools +def get_color_cycle(n, colormap, start=0., stop=1., format='hex'): + if type(colormap) == str: + colormap = getattr(cm, colormap) + + pts = np.linspace(start, stop, n) + if format == 'hex': + colors = [rgb2hex(colormap(pt)) for pt in pts] + return colors + + +# tools for color plots +def centers2edges(arr): + e = (arr[1:] + arr[:-1]) / 2. + e = np.concatenate(([arr[0] - (e[0] - arr[0])], e)) + e = np.concatenate((e, [arr[-1] + (arr[-1] - e[-1])])) + return e + + +def pcolorgrid(xaxis, yaxis): + xedges = centers2edges(xaxis) + yedges = centers2edges(yaxis) + xx, yy = np.meshgrid(xedges, yedges) + return xx, yy + + +# creating and formatting figures +def correctly_sized_figure(widths, heights, margins=0.5, dw=0.2, dh=0.2, make_axes=True): + """ + Create a figure and grid where all dimensions are specified in inches. + Arguments: + widths: list of column widths + heights: list of row heights + margins: either a scalar or a list of four numbers (l, r, t, b) + dw: white space between subplots, horizontal + dh: white space between subplots, vertical + make_axes: bool; if True, create axes on the grid and return, + else return the gridspec. + """ + wsum = sum(widths) + hsum = sum(heights) + nrows = len(heights) + ncols = len(widths) + if type(margins) == list: + l, r, t, b = margins + else: + l = r = t = b = margins + + figw = wsum + (ncols - 1) * dw + l + r + figh = hsum + (nrows - 1) * dh + t + b + + # margins in fraction of the figure + top = 1. - t / figh + bottom = b / figh + left = l / figw + right = 1. - r / figw + + # subplot spacing in fraction of the subplot size + wspace = dw / np.average(widths) + hspace = dh / np.average(heights) + + fig = plt.figure(figsize=(figw, figh)) + gs = gridspec.GridSpec(nrows, ncols, + height_ratios=heights, width_ratios=widths) + gs.update(top=top, bottom=bottom, left=left, right=right, + wspace=wspace, hspace=hspace) + + if make_axes: + axes = [] + for i in range(nrows): + for j in range(ncols): + axes.append(fig.add_subplot(gs[i, j])) + + return fig, axes + + else: + return fig, gs + + +def format_ax(ax, top=False, right=False, xlog=False, ylog=False, + xlabel=None, ylabel=None, xlim=None, ylim=None, xticks=3, yticks=3): + ax.tick_params(axis='x', which='both', pad=2, + top=top, labeltop=top, bottom=not top, labelbottom=not top) + if top: + ax.xaxis.set_label_position('top') + + ax.tick_params(axis='y', which='both', pad=2, + right=right, labelright=right, left=not right, labelleft=not right) + if right: + ax.yaxis.set_label_position('right') + + if isinstance(xticks, list): + ax.xaxis.set_major_locator(ticker.FixedLocator(xticks)) + if xlim is not None: + ax.set_xlim(xlim) + elif xlim is not None: + ax.xaxis.set_major_locator(ticker.LinearLocator(xticks)) + ax.set_xlim(xlim) + else: + ax.xaxis.set_major_locator(ticker.MaxNLocator(xticks)) + + if isinstance(yticks, list): + ax.yaxis.set_major_locator(ticker.FixedLocator(yticks)) + if ylim is not None: + ax.set_ylim(ylim) + elif ylim is not None: + ax.yaxis.set_major_locator(ticker.LinearLocator(yticks)) + ax.set_ylim(ylim) + else: + ax.yaxis.set_major_locator(ticker.MaxNLocator(yticks)) + + if xlabel is not None: + ax.set_xlabel(xlabel) + if ylabel is not None: + ax.set_ylabel(ylabel) + + ax.xaxis.labelpad = 2 + ax.yaxis.labelpad = 2 + + +def format_right_cb(cb): + cb.outline.set_visible(False) + cb.ax.xaxis.set_visible(True) + cb.ax.xaxis.set_label_position('top') + + +def add_legend(ax, anchor_point=(1, 1), legend_ref_point='lower right', **labels_and_handles): + if len(labels_and_handles) > 0: + handles = [] + labels = [] + for l, h in labels_and_handles.items(): + handles.append(h) + labels.append(l) + ax.legend(handles, labels, bbox_to_anchor=anchor_point, borderpad=0, loc=legend_ref_point) + else: + ax.legend(bbox_to_anchor=anchor_point, borderpad=0, loc=legend_ref_point) + + +def setup_plotting(sns_style='whitegrid', rcparams={}): + # some sensible defaults for sizing, those are for a typical print-plot + sns.set_style(sns_style) + + mpl.rcParams['figure.constrained_layout.use'] = True + mpl.rcParams['figure.dpi'] = 300 + mpl.rcParams['figure.figsize'] = (3, 2) + # mpl.rcParams['font.family'] = 'Noto Sans Math', 'Arial', 'Helvetica', 'DejaVu Sans' + mpl.rcParams['font.size'] = 6 + # mpl.rcParams['lines.marker'] = 'o' + mpl.rcParams['lines.markersize'] = 3 + mpl.rcParams['lines.linewidth'] = 1.5 + mpl.rcParams['axes.linewidth'] = 0.5 + mpl.rcParams['axes.titlesize'] = 'medium' + mpl.rcParams['grid.linewidth'] = 0.5 + mpl.rcParams['image.cmap'] = 'RdPu' + mpl.rcParams['legend.fontsize'] = 5 + mpl.rcParams['legend.frameon'] = False + mpl.rcParams['xtick.major.width'] = 0.5 + mpl.rcParams['ytick.major.width'] = 0.5 + mpl.rcParams['xtick.major.size'] = 2 + mpl.rcParams['ytick.major.size'] = 2 + mpl.rcParams["mathtext.fontset"] = 'dejavusans' + + mpl.rcParams.update(rcparams) \ No newline at end of file diff --git a/labcore/setup/__init__.py b/labcore/setup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/labcore/setup/setup_measurements.py b/labcore/setup/setup_measurements.py new file mode 100644 index 0000000..ab8f40e --- /dev/null +++ b/labcore/setup/setup_measurements.py @@ -0,0 +1,132 @@ +import os +import sys +import logging +from typing import Optional, Any, Union, List, Dict, Tuple +from functools import partial +from dataclasses import dataclass +from pathlib import Path + +from instrumentserver.client import Client, ProxyInstrument + +from labcore.ddh5 import run_and_save_sweep +from labcore.measurement import Sweep + +from plottr.data.datadict import DataDict + +from .analysis.data import data_info + + +# constants +WD = os.getcwd() +DATADIR = os.path.join(WD, 'data') + + +@dataclass +class Options: + instrument_clients: Optional[Dict[str, Client]] = None + parameters: Optional[ProxyInstrument] = None + +options = Options() + + +# this function sets up our general logging +def setup_logging() -> logging.Logger: + """Setup logging in a reasonable way. Note: we use the root logger since + our measurements typically run in the console directly and we want + logging to work from scripts that are directly run in the console. + + Returns + ------- + The logger that has been setup. + """ + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + for h in logger.handlers: + logger.removeHandler(h) + del h + + fmt = logging.Formatter( + "%(asctime)s.%(msecs)03d\t| %(name)s\t| %(levelname)s\t| %(message)s", + datefmt='%Y-%m-%d %H:%M:%S', + ) + fh = logging.FileHandler('measurement.log') + fh.setFormatter(fmt) + fh.setLevel(logging.INFO) + logger.addHandler(fh) + + fmt = logging.Formatter( + "[%(asctime)s.%(msecs)03d] [%(name)s: %(levelname)s] %(message)s", + datefmt='%Y-%m-%d %H:%M:%S', + ) + streamHandler = logging.StreamHandler(sys.stdout) + streamHandler.setFormatter(fmt) + streamHandler.setLevel(logging.INFO) + logger.addHandler(streamHandler) + logger.info(f"Logging set up for {logger}.") + return logger + +# Create the logger +logger = setup_logging() + +def find_or_create_remote_instrument(cli: Client, ins_name: str, ins_class: Optional[str]=None, + *args: Any, **kwargs: Any) -> ProxyInstrument: + """Finds or creates an instrument in an instrument server. + + Parameters + ---------- + cli + instance of the client pointing to the instrument server + ins_name + name of the instrument to find or to create + ins_class + the class of the instrument (import path as string) if creating a new instrument + args + will be passed to the instrument creation call + kwargs + will be passed to the instrument creation call + + Returns + ------- + Proxy to the remote instrument + """ + if ins_name in cli.list_instruments(): + return cli.get_instrument(ins_name) + + if ins_class is None: + raise ValueError('Need a class to create a new instrument') + + ins = cli.create_instrument( + instrument_class=ins_class, + name=ins_name, *args, **kwargs) + + return ins + + +def run_measurement(sweep: Sweep, name: str, **kwargs) -> Tuple[Union[str, Path], Optional[DataDict]]: + if options.instrument_clients is None: + raise RuntimeError('it looks like options.instrument_clients is not configured.') + if options.parameters is None: + raise RuntimeError('it looks like options.parameters is not configured.') + + for n, c in options.instrument_clients.items(): + kwargs[n] = c.snapshot + kwargs['parameters'] = options.parameters.toParamDict + + data_location, data = run_and_save_sweep( + sweep=sweep, + data_dir=DATADIR, + name=name, + save_action_kwargs=True, + **kwargs) + + info = data + + logger.info(f""" +========== +Saved data at {data_location}: +{data_info(data_location, do_print=False)} +=========""") + return data_location, data + + diff --git a/labcore/setup/setup_notebook_analysis.py b/labcore/setup/setup_notebook_analysis.py new file mode 100644 index 0000000..f7bba18 --- /dev/null +++ b/labcore/setup/setup_notebook_analysis.py @@ -0,0 +1,7 @@ +import matplotlib as mpl +from matplotlib import pyplot as plt +import seaborn as sns + +from .analysis.plotting import setup_plotting, format_ax + +setup_plotting(rcparams={'figure.dpi': 200}) diff --git a/labcore/setup/setup_opx_measurements.py b/labcore/setup/setup_opx_measurements.py new file mode 100644 index 0000000..9f6a3ae --- /dev/null +++ b/labcore/setup/setup_opx_measurements.py @@ -0,0 +1,167 @@ +"""general setup file for OPX measurements. + +Use by importing and then configuring the options object. +""" + +# this is to prevent the OPX logger to also create log messages (results in duplicate messages) +import os +os.environ['QM_DISABLE_STREAMOUTPUT'] = "1" + +from typing import Optional, Callable +from dataclasses import dataclass +from functools import partial + +from IPython.display import display +import ipywidgets as widgets + +# FIXME: only until everyone uses the latest qm packages. +try: + from qm.QuantumMachinesManager import QuantumMachinesManager, QuantumMachine +except: + from qm.QuantumMachinesManager import QuantumMachinesManager + from qm import QuantumMachine + +from qm.qua import * + +from instrumentserver.helpers import nestedAttributeFromString + + + +from .opx_tools.config import QMConfig +from .opx_tools import sweep as qmsweep +from .opx_tools.mixer import calibrate_mixer, MixerConfig, mixer_of_step, mixer_imb_step + +from . import setup_measurements +from .setup_measurements import * + +@dataclass +class Options(setup_measurements.Options): + _qm_config: Optional[QMConfig] = None + + # this is implemented as a property so we automatically set the + # options correctly everywhere else... + @property + def qm_config(self): + return self._qm_config + + @qm_config.setter + def qm_config(self, cfg): + self._qm_config = cfg + qmsweep.config = cfg + +options = Options() +setup_measurements.options = options + +@dataclass +class Mixer: + config: MixerConfig + qm: Optional[QuantumMachine] = None + + def run_constant_waveform(self): + with program() as const_pulse: + with infinite_loop_(): + play('constant', self.config.element_name) + qmm = QuantumMachinesManager(host=self.config.qmconfig.opx_address, + port=self.config.qmconfig.opx_port) + qm = qmm.open_qm(self.config.qmconfig(), close_other_machines=False) + qm.execute(const_pulse) + self.qm = qm + + def step_of(self, di, dq): + if self.qm is None: + raise RuntimeError('No active QuantumMachine.') + mixer_of_step(self.config, self.qm, di, dq) + + def step_imb(self, dg, dp): + if self.qm is None: + raise RuntimeError('No active QuantumMachine.') + mixer_imb_step(self.config, self.qm, dg, dp) + +def add_mixer_config(element_name, analyzer, generator, element_to_param_map=None, **config_kwargs): + """ + FIXME: add docu (@wpfff) + TODO: make sure we document the meaning of `element_to_param_map`. + """ + if element_to_param_map is None: + element_to_param_map = element_name + + cfg = MixerConfig( + qmconfig=options.qm_config, + opx_address=options.qm_config.opx_address, + opx_port=options.qm_config.opx_port, + analyzer=analyzer, + generator=generator, + if_param=nestedAttributeFromString(options.parameters, f"{element_to_param_map}.IF"), + offsets_param=nestedAttributeFromString(options.parameters, f"mixers.{element_to_param_map}.offsets"), + imbalances_param=nestedAttributeFromString(options.parameters, f"mixers.{element_to_param_map}.imbalance"), + mixer_name=f'{element_name}_IQ_mixer', + element_name=element_name, + pulse_name='constant', + **config_kwargs + ) + return Mixer( + config=cfg, + ) + + +# A simple graphical mixer tuning tool +def mixer_tuning_tool(mixer): + # widgets for dc offset tuning + of_step = widgets.FloatText(description='dc of. step:', value=0.01, min=0, max=1, step=0.001) + iup_btn = widgets.Button(description='I ^') + idn_btn = widgets.Button(description='I v') + qup_btn = widgets.Button(description='Q ^') + qdn_btn = widgets.Button(description='Q v') + + def on_I_up(b): + mixer.step_of(of_step.value, 0) + + def on_I_dn(b): + mixer.step_of(-of_step.value, 0) + + def on_Q_up(b): + mixer.step_of(0, of_step.value) + + def on_Q_dn(b): + mixer.step_of(0, -of_step.value) + + iup_btn.on_click(on_I_up) + idn_btn.on_click(on_I_dn) + qup_btn.on_click(on_Q_up) + qdn_btn.on_click(on_Q_dn) + + # widgets for imbalance tuning + imb_step = widgets.FloatText(description='imb. step:', value=0.01, min=0, max=1, step=0.001) + gup_btn = widgets.Button(description='g ^') + gdn_btn = widgets.Button(description='g v') + pup_btn = widgets.Button(description='phi ^') + pdn_btn = widgets.Button(description='phi v') + + def on_g_up(b): + mixer.step_imb(imb_step.value, 0) + + def on_g_dn(b): + mixer.step_imb(-imb_step.value, 0) + + def on_p_up(b): + mixer.step_imb(0, imb_step.value) + + def on_p_dn(b): + mixer.step_imb(0, -imb_step.value) + + gup_btn.on_click(on_g_up) + gdn_btn.on_click(on_g_dn) + pup_btn.on_click(on_p_up) + pdn_btn.on_click(on_p_dn) + + # assemble reasonably for display + ofupbox = widgets.HBox([iup_btn, qup_btn]) + ofdnbox = widgets.HBox([idn_btn, qdn_btn]) + ofbox = widgets.VBox([of_step, ofupbox, ofdnbox]) + + imbupbox = widgets.HBox([gup_btn, pup_btn]) + imbdnbox = widgets.HBox([gdn_btn, pdn_btn]) + imbbox = widgets.VBox([imb_step, imbupbox, imbdnbox]) + + box = widgets.HBox([ofbox, imbbox]) + display(box) diff --git a/labcore/measurement/__init__.py b/labcore/sweep/__init__.py similarity index 100% rename from labcore/measurement/__init__.py rename to labcore/sweep/__init__.py diff --git a/labcore/ddh5.py b/labcore/sweep/ddh5.py similarity index 97% rename from labcore/ddh5.py rename to labcore/sweep/ddh5.py index 64b7fed..70bcba8 100644 --- a/labcore/ddh5.py +++ b/labcore/sweep/ddh5.py @@ -32,7 +32,7 @@ from plottr.data.datadict import DataDict, is_meta_key from plottr.data.datadict_storage import DDH5Writer -from .measurement.sweep import Sweep +from labcore.sweep.sweep import Sweep __author__ = 'Wolfgang Pfaff' __license__ = 'MIT' @@ -134,7 +134,7 @@ def run_and_save_sweep(sweep: Sweep, :param archive_files: List of files to copy into a folder called 'archived_files' in the same directory that the data is saved. It should be a list of paths (str), regular expressions are supported. If a folder is passed, it will copy the entire folder and all of its subdirectories and files into the - archived_files folder. If one of the arguments could not be found, a message will be printed and the measurement + archived_files folder. If one of the arguments could not be found, a message will be printed and the sweeping will be performed without the file being archived. An exception is raised if the type is invalid. e.g. archive_files=['*.txt', 'calibration_files', '../test_file.py']. '*.txt' will copy every txt file @@ -228,7 +228,7 @@ def run_and_save_sweep(sweep: Sweep, ret = (dir, data_dict) if return_data else (dir, None) return ret - logger.info('The measurement has finished successfully and all of the data has been saved.') + logger.info('The sweeping has finished successfully and all of the data has been saved.') ret = (dir, data_dict) if return_data else (dir, None) return ret diff --git a/labcore/measurement/record.py b/labcore/sweep/record.py similarity index 100% rename from labcore/measurement/record.py rename to labcore/sweep/record.py diff --git a/labcore/measurement/sweep.py b/labcore/sweep/sweep.py similarity index 99% rename from labcore/measurement/sweep.py rename to labcore/sweep/sweep.py index 7f157a0..7c93d7b 100644 --- a/labcore/measurement/sweep.py +++ b/labcore/sweep/sweep.py @@ -546,7 +546,7 @@ class AsyncRecord: """ Base class decorator used to record asynchronous data from instrument. Use the decorator with create_background_sweep function to create Sweeps that collect asynchronous data from - external devices running experiments independently of the measurement PC, + external devices running experiments independently of the sweeping PC, e.i. the measuring happening is not being controlled by a Sweep but instead an external device (e.g. the OPX). Each instrument should have its own custom setup_wrapper (see setup_wrapper docstring for more info), and a custom collector. @@ -592,7 +592,7 @@ def wrap_setup(self, fun: Callable, *args: Any, **kwargs: Any) -> Callable: Setup should accept the \*args and \**kwargs of fun. It should also place any returns from fun in the communicator. setup_wrapper needs to return the wrapped function (setup). - :param fun: The measurement function. In the case of the OPX this would be the function that returns the QUA + :param fun: The sweeping function. In the case of the OPX this would be the function that returns the QUA code with any arguments that it might use. """ self.wrapped_setup = partial(self.setup, fun, *args, **kwargs) diff --git a/prototyping/Developing_opx_labcore.ipynb b/prototyping/Developing_opx_labcore.ipynb deleted file mode 100644 index 7e78944..0000000 --- a/prototyping/Developing_opx_labcore.ipynb +++ /dev/null @@ -1,1992 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7e57fb25-d0f5-43fc-82b1-427506628ebc", - "metadata": {}, - "source": [ - "# Simulated OPX" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "seventh-madrid", - "metadata": {}, - "outputs": [], - "source": [ - "import time\n", - "from functools import wraps\n", - "\n", - "\n", - "from pprint import pprint\n", - "import numpy as np\n", - "import qcodes as qc\n", - "\n", - "from labcore.measurement import *\n", - "\n", - "from configuration import QMConfig\n", - "from qm.qua import *\n", - "from qm.QuantumMachinesManager import QuantumMachinesManager\n", - "\n", - "from plottr.data import datadict_storage as dds, datadict as dd\n", - "\n", - "# global module variable for the config file\n", - "global_config = None\n", - "\n", - "\n", - "# variable used for development\n", - "test_name_counter = 0\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "revolutionary-intersection", - "metadata": {}, - "outputs": [], - "source": [ - "class dummy_opx:\n", - " def __init__(self, shape, sleep, num_lines):\n", - " self.shape = shape \n", - " self.sleep = sleep\n", - " self.num_lines = num_lines\n", - " \n", - " self.processing = True\n", - " self.counter = 0\n", - " \n", - " def is_processing(self):\n", - " return self.processing\n", - " \n", - " \n", - " def get_data(self, batch):\n", - " if self.processing:\n", - " ret = []\n", - " for i in range(len(self.shape)):\n", - " if i == 0:\n", - " ret.append(np.arange(batch))\n", - " else:\n", - " ret.append(np.random.randint(0,10, (batch, self.shape[i])))\n", - " self.counter += batch\n", - " if self.counter >= self.num_lines:\n", - " self.processing = False\n", - " time.sleep(self.sleep)\n", - " return tuple(ret)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "small-botswana", - "metadata": {}, - "outputs": [], - "source": [ - "def start_experiment(): \n", - " print('running the experiment in the OPX (not implemented)')\n", - "\n", - "def conditional_generator(control):\n", - " \"\"\"\n", - " Generator that returns a list of increasing integers as long as the argument control has a boolean\n", - " variable called active that is True. The generator stops when the variable becomes False. \n", - " \"\"\"\n", - " counter_internal = -1\n", - "\n", - " # In the while goes whatever condition needs to be done to stop measuring\n", - " while control.is_processing():\n", - " counter_internal += 1\n", - " yield counter_internal\n", - " \n", - " \n", - "def create_sweep(opx_program, batchsize, num_lines):\n", - " # instantiating necessary objects\n", - " @recording(\n", - " independent('repetition', type='array'),\n", - " dependent('variable', type='array'),\n", - " dependent('independent', depends_on=['repetition','variable'], type='array'))\n", - " def gather_data():\n", - "\n", - " data = 0\n", - " data = result_handle.get_data(batchsize)\n", - " return tuple(data)\n", - " \n", - " # This would instantiate all of the QM stuff and return the reulst handle\n", - " \n", - " result_handle = opx_program((1,10,10), 1, num_lines)\n", - " sweep_param = sweep_parameter('ignore_param', conditional_generator(result_handle), record_as(gather_data))\n", - " sweep = Sweep(once(start_experiment) + sweep_param)\n", - " return(sweep)\n", - "\n", - "def create_structure(sweep):\n", - " data_specs = sweep.get_data_specs()\n", - " data_dict = dd.DataDict()\n", - " for spec in data_specs:\n", - " print(spec)\n", - " depends_on = spec.depends_on\n", - " unit = spec.unit\n", - " name = spec.name\n", - " if name != 'ignore_param':\n", - " if depends_on is None:\n", - " if unit is None:\n", - " data_dict[name] = dict()\n", - " else:\n", - " data_dict[name] = dict(unit = unit)\n", - " else:\n", - " if unit is None:\n", - " data_dict[name] = dict(axes = depends_on[1:])\n", - " else:\n", - " data_dict[name] = dict(axes = depends_on[1:], unit = unit)\n", - "\n", - " print(data_dict)\n", - " data_dict.validate()\n", - " \n", - " return data_dict\n", - "\n", - "def check_none(argument):\n", - " for arg in argument.keys():\n", - " if argument[arg] is not None:\n", - " return False\n", - " return True\n", - "\n", - "def run_sweep(sweep, data_dir, name, prt=True):\n", - " data_dict = create_structure(sweep)\n", - " with dds.DDH5Writer(data_dir, data_dict, name=name) as writer:\n", - " for line in sweep:\n", - " if not check_none(line):\n", - " line.pop('ignore_param')\n", - " if prt:\n", - " print(line)\n", - " writer.add_data(**line)\n", - " print('The measurement has finished and all of the data has been saved.')" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "advanced-globe", - "metadata": {}, - "outputs": [], - "source": [ - "#program = dummy_opx((1,1,1,5), 1)\n", - "DATADIR = './data/'\n", - "sweep = create_sweep(dummy_opx, 5, 50)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "dd024f83-da3a-4980-8d84-1e2ce9380c02", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSpec(name='ignore_param', depends_on=None, type=, unit='')\n", - "DataSpec(name='repetition', depends_on=None, type=, unit='')\n", - "DataSpec(name='variable', depends_on=['ignore_param'], type=, unit='')\n", - "DataSpec(name='independent', depends_on=['ignore_param', 'repetition', 'variable'], type=, unit='')\n", - "{'repetition': {'unit': ''}, 'variable': {'axes': [], 'unit': ''}, 'independent': {'axes': ['repetition', 'variable'], 'unit': ''}}\n", - "Data location: ./data/2021-07-26\\2021-07-26_0023_test_3\\2021-07-26_0023_test_3.ddh5\n", - "running the experiment in the OPX (not implemented)\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[1, 0, 3, 3, 4, 4, 2, 0, 2, 7],\n", - " [6, 7, 2, 7, 3, 3, 2, 6, 6, 8],\n", - " [3, 7, 1, 7, 3, 2, 1, 5, 3, 6],\n", - " [9, 8, 2, 8, 7, 4, 6, 3, 0, 7],\n", - " [4, 9, 7, 7, 5, 2, 8, 4, 4, 4]]), 'independent': array([[0, 7, 4, 8, 7, 0, 5, 8, 0, 2],\n", - " [1, 1, 6, 0, 9, 8, 6, 4, 5, 3],\n", - " [8, 6, 6, 7, 8, 6, 2, 7, 1, 2],\n", - " [8, 3, 8, 1, 3, 5, 5, 6, 1, 7],\n", - " [3, 0, 4, 3, 0, 1, 3, 4, 9, 5]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[0, 2, 9, 2, 2, 2, 6, 4, 2, 9],\n", - " [0, 9, 6, 5, 4, 4, 3, 5, 9, 6],\n", - " [5, 1, 5, 8, 8, 7, 6, 2, 9, 7],\n", - " [4, 7, 9, 9, 3, 7, 3, 3, 4, 3],\n", - " [0, 8, 4, 3, 1, 0, 4, 3, 5, 3]]), 'independent': array([[2, 5, 8, 3, 6, 2, 7, 3, 5, 9],\n", - " [5, 2, 0, 3, 9, 8, 2, 0, 5, 0],\n", - " [8, 7, 3, 1, 3, 2, 2, 6, 8, 1],\n", - " [4, 0, 6, 6, 4, 5, 6, 2, 3, 3],\n", - " [6, 5, 2, 1, 1, 2, 4, 2, 9, 3]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[0, 5, 5, 7, 6, 9, 2, 2, 6, 8],\n", - " [3, 9, 4, 0, 6, 4, 4, 5, 6, 7],\n", - " [7, 3, 5, 0, 4, 2, 8, 2, 8, 7],\n", - " [4, 1, 4, 4, 3, 8, 1, 4, 8, 6],\n", - " [3, 4, 6, 4, 5, 1, 9, 3, 4, 1]]), 'independent': array([[1, 0, 5, 2, 1, 4, 9, 2, 7, 4],\n", - " [4, 4, 2, 1, 7, 4, 7, 1, 2, 5],\n", - " [8, 3, 8, 0, 6, 9, 7, 8, 8, 5],\n", - " [3, 5, 0, 8, 3, 1, 0, 9, 8, 6],\n", - " [1, 1, 5, 6, 7, 1, 8, 4, 4, 8]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[8, 9, 8, 2, 3, 5, 0, 8, 7, 4],\n", - " [3, 5, 7, 7, 7, 7, 8, 7, 2, 7],\n", - " [8, 3, 7, 0, 1, 7, 1, 9, 5, 6],\n", - " [3, 3, 1, 3, 0, 7, 6, 8, 7, 3],\n", - " [1, 6, 0, 3, 7, 9, 1, 5, 3, 9]]), 'independent': array([[3, 4, 1, 9, 0, 2, 3, 1, 7, 2],\n", - " [1, 6, 1, 8, 8, 2, 0, 5, 4, 2],\n", - " [6, 1, 9, 7, 2, 6, 8, 1, 2, 1],\n", - " [8, 5, 1, 8, 1, 5, 2, 2, 5, 8],\n", - " [1, 5, 9, 1, 1, 1, 6, 8, 9, 3]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[7, 5, 5, 6, 6, 1, 2, 2, 7, 9],\n", - " [5, 6, 8, 1, 5, 7, 0, 2, 2, 9],\n", - " [2, 0, 2, 5, 7, 9, 9, 4, 0, 3],\n", - " [9, 1, 9, 1, 3, 0, 0, 3, 0, 4],\n", - " [9, 7, 1, 7, 9, 5, 0, 7, 6, 2]]), 'independent': array([[6, 8, 9, 9, 8, 9, 4, 8, 6, 2],\n", - " [4, 8, 9, 4, 8, 0, 3, 4, 4, 1],\n", - " [8, 2, 9, 0, 1, 6, 8, 5, 8, 9],\n", - " [0, 2, 9, 7, 4, 8, 4, 5, 7, 1],\n", - " [4, 3, 2, 1, 8, 4, 1, 7, 8, 5]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[1, 3, 7, 5, 7, 6, 8, 2, 7, 3],\n", - " [7, 9, 6, 4, 5, 7, 4, 3, 4, 4],\n", - " [8, 6, 9, 9, 5, 6, 9, 6, 4, 3],\n", - " [4, 9, 9, 1, 1, 5, 5, 9, 5, 1],\n", - " [7, 9, 6, 2, 1, 0, 8, 5, 4, 4]]), 'independent': array([[2, 0, 1, 1, 3, 2, 8, 2, 2, 5],\n", - " [9, 4, 8, 1, 5, 5, 6, 6, 3, 0],\n", - " [9, 4, 2, 8, 8, 5, 7, 1, 1, 0],\n", - " [2, 4, 2, 4, 4, 7, 9, 4, 7, 2],\n", - " [1, 2, 9, 6, 4, 0, 9, 8, 2, 2]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[5, 4, 3, 9, 1, 6, 7, 0, 8, 7],\n", - " [0, 4, 7, 3, 9, 1, 6, 0, 1, 3],\n", - " [4, 3, 7, 6, 4, 4, 0, 7, 9, 0],\n", - " [1, 2, 5, 7, 5, 9, 0, 9, 7, 9],\n", - " [0, 5, 1, 8, 8, 0, 4, 8, 2, 6]]), 'independent': array([[0, 4, 2, 9, 9, 0, 3, 5, 6, 7],\n", - " [2, 9, 5, 6, 4, 7, 8, 6, 2, 2],\n", - " [3, 0, 0, 6, 7, 0, 5, 5, 8, 5],\n", - " [8, 2, 5, 0, 4, 2, 2, 9, 5, 2],\n", - " [6, 7, 6, 6, 3, 0, 5, 3, 1, 1]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[1, 8, 6, 1, 7, 4, 5, 7, 9, 6],\n", - " [8, 6, 3, 7, 5, 6, 6, 1, 5, 2],\n", - " [7, 3, 8, 4, 3, 0, 7, 7, 1, 7],\n", - " [0, 3, 8, 5, 2, 5, 5, 1, 0, 2],\n", - " [8, 0, 0, 8, 2, 0, 9, 5, 4, 2]]), 'independent': array([[5, 5, 7, 7, 8, 4, 1, 2, 5, 9],\n", - " [9, 7, 2, 3, 5, 2, 7, 0, 6, 0],\n", - " [7, 1, 0, 6, 6, 9, 4, 4, 9, 8],\n", - " [3, 4, 9, 5, 1, 4, 6, 9, 6, 9],\n", - " [1, 7, 0, 6, 1, 6, 9, 4, 4, 8]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[5, 9, 1, 0, 4, 9, 9, 3, 2, 3],\n", - " [9, 5, 9, 5, 2, 4, 1, 6, 1, 9],\n", - " [9, 8, 4, 0, 4, 7, 1, 2, 8, 7],\n", - " [9, 0, 5, 3, 0, 7, 3, 8, 4, 5],\n", - " [2, 8, 6, 4, 0, 0, 6, 9, 3, 0]]), 'independent': array([[1, 7, 6, 8, 9, 5, 9, 7, 8, 4],\n", - " [4, 5, 1, 0, 4, 2, 2, 9, 9, 5],\n", - " [2, 0, 7, 7, 0, 4, 5, 3, 3, 1],\n", - " [3, 1, 7, 9, 9, 9, 2, 5, 0, 8],\n", - " [4, 3, 9, 6, 3, 9, 0, 5, 4, 4]])}\n", - "{'repetition': array([0, 1, 2, 3, 4]), 'variable': array([[4, 6, 4, 7, 0, 9, 7, 0, 0, 1],\n", - " [1, 1, 7, 5, 8, 2, 5, 4, 3, 9],\n", - " [1, 4, 2, 7, 6, 4, 0, 6, 9, 9],\n", - " [0, 4, 9, 9, 5, 1, 1, 8, 1, 3],\n", - " [1, 3, 8, 0, 3, 3, 7, 7, 8, 2]]), 'independent': array([[4, 9, 6, 2, 5, 2, 4, 6, 0, 7],\n", - " [6, 5, 4, 1, 0, 8, 3, 4, 8, 6],\n", - " [9, 5, 6, 7, 3, 2, 4, 7, 0, 2],\n", - " [6, 2, 6, 6, 3, 0, 1, 8, 7, 8],\n", - " [3, 0, 2, 2, 6, 0, 7, 8, 6, 5]])}\n", - "The measurement has finished and all of the data has been saved.\n" - ] - } - ], - "source": [ - "run_sweep(sweep, DATADIR, 'test_3')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "129221c9-ca21-4ff8-ab31-0f285d8107f7", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "6a66a5eb-07f2-4cf7-9fac-5490f2c49e47", - "metadata": {}, - "source": [ - "# Real OPX test" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "4e4d303b-a80f-4709-9d00-9208cc354592", - "metadata": {}, - "outputs": [], - "source": [ - "class ResHandleContainer:\n", - " def __init__(self, res_handle=None):\n", - " self.res_handle = res_handle\n", - " self.counter = 0\n", - " self.active = True\n", - " \n", - " def get_res_handle(self):\n", - " return self.res_handle\n", - " \n", - " def set_res_handle(self, res_handle):\n", - " self.res_handle = res_handle \n", - "\n", - "def record_qua_output(*data_specs):\n", - " container_inside = ResHandleContainer()\n", - " \n", - " def qua_function(func):\n", - " def nested_function(**kwarg):\n", - " qmachine_mgr = QuantumMachinesManager()\n", - " qmachine = qmachine_mgr.open_qm(global_config)\n", - " job = qmachine.execute(func(**kwarg))\n", - " result_handle = job.result_handles\n", - " container_inside.set_res_handle(result_handle)\n", - " print(f'the instance of container that I am getting is: {container_inside}')\n", - " return nested_function, container_inside, *data_specs\n", - " return qua_function" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "gothic-barbados", - "metadata": {}, - "outputs": [], - "source": [ - "def create_sweep_opx(opx_program, config, batchsize):\n", - " \n", - " def control_progress(res_handle_in, counter_in, batchsize_in):\n", - " ready = False\n", - " first = True\n", - " print('starting control_progress')\n", - " while not ready:\n", - " for name, handle in res_handle_in:\n", - " current_in = handle.count_so_far()\n", - " \n", - " if res_handle_in.is_processing():\n", - " if current_in - counter_in >= batchsize_in:\n", - " ready = True\n", - " else:\n", - " ready = False\n", - " else:\n", - " ready = True\n", - " container.active = False\n", - " print('I have turned container False')\n", - " \n", - " return ready\n", - " \n", - " @recording(*list(opx_program[2:]))\n", - " def gather_data():\n", - " print('entering gathering data')\n", - "\n", - " result_handle = container.get_res_handle()\n", - " \n", - " # checking if the parameters passed match the parameters in the opx\n", - " data_specs_names = [x.name for x in opx_program[2:]]\n", - " variable_counter = 0\n", - " for name, handle in result_handle:\n", - " print(name)\n", - " if name not in data_specs_names:\n", - " raise ValueError(f'{name} is not a recorded variable')\n", - " else:\n", - " variable_counter += 1\n", - " if variable_counter != len(data_specs_names):\n", - " raise ValueError(f'Number of recorded variables ({variable_counter}) does not match number of variables gathered from the OPX ({len(data_specs_names)})')\n", - " \n", - " \n", - " while container.active:\n", - "\n", - " first = True\n", - " data = {}\n", - " counter = container.counter\n", - " first = True\n", - " current = 0\n", - " \n", - " if result_handle == None:\n", - " yield None\n", - " \n", - " record = control_progress(result_handle, counter, batchsize) \n", - " for name, handle in result_handle:\n", - "\n", - " if first:\n", - " current = handle.count_so_far()\n", - " print(f'getting new current: {current}, my old counter is: {counter}')\n", - " first = False\n", - " if current == counter:\n", - " yield None\n", - " \n", - " \n", - " handle.wait_for_values(current)\n", - " data_temp = np.array(handle.fetch(slice(counter, current))) \n", - " if name[0:4] == 'raw_':\n", - " holding_converting = []\n", - " for i in data_temp:\n", - " i_holder = []\n", - " for j in i:\n", - " converted = j.astype(float)\n", - " i_holder.append(converted)\n", - " holding_converting.append(i_holder)\n", - " if len(holding_converting) == 1:\n", - " converted_data_temp = [np.squeeze(holding_converting)]\n", - " else:\n", - " converted_data_temp = np.squeeze(holding_converting)\n", - " else:\n", - " converted_data_temp = data_temp.astype(float)\n", - " data[name] = converted_data_temp\n", - " container.counter = current\n", - " yield data\n", - " \n", - " my_experiment = opx_program[0]\n", - " print(f'my experiment is: {my_experiment}')\n", - " container = opx_program[1]\n", - "\n", - " sweep = Sweep(once(my_experiment)) + Sweep(gather_data())\n", - "# sweep = Sweep(once(my_qua_experiment)) + Sweep(gather_data)\n", - " return(sweep)\n", - "\n", - "def create_structure(sweep):\n", - " data_specs = sweep.get_data_specs()\n", - " print(f'the data_specs are: {data_specs}')\n", - " data_dict = dd.DataDict()\n", - " for spec in data_specs:\n", - " print(spec)\n", - " depends_on = spec.depends_on\n", - " unit = spec.unit\n", - " name = spec.name\n", - " if depends_on is None:\n", - " if unit is None:\n", - " data_dict[name] = dict()\n", - " else:\n", - " data_dict[name] = dict(unit = unit)\n", - " else:\n", - " if unit is None:\n", - " data_dict[name] = dict(axes = depends_on)\n", - " else:\n", - " data_dict[name] = dict(axes = depends_on, unit = unit)\n", - " \n", - " data_dict.validate()\n", - " print(data_dict)\n", - " return data_dict\n", - "\n", - "def check_none(argument):\n", - " for arg in argument.keys():\n", - " if argument[arg] is not None:\n", - " return False\n", - " return True\n", - "\n", - "def run_sweep(sweep, data_dir, name, prt=False):\n", - " data_dict = create_structure(sweep)\n", - " with dds.DDH5Writer(data_dir, data_dict, name=name) as writer:\n", - " for line in sweep:\n", - " if not check_none(line):\n", - " if prt:\n", - " print(line)\n", - " try:\n", - " writer.add_data(**line)\n", - " except ValueError as e:\n", - " print(line)\n", - " raise e\n", - " \n", - " print('The measurement has finished and all of the data has been saved.')" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "official-award", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "# You should be able to pass arguments to my_qua_experiment(n_reps=1_000)\n", - "\n", - "@record_qua_output(\n", - " independent('repetition', type='array'),\n", - " dependent('V', depends_on=['repetition'], type='array'),\n", - " dependent('tracker', depends_on=['repetition'], type='array'))\n", - "# dependent('raw', depends_on=['repetition'], type='array'))\n", - " #qua_raw_dependent('raw', depends_on=['repetition'], type='array'))\n", - "def my_qua_experiment(n_reps=1000):\n", - " with program() as qua_measurement:\n", - " raw_stream = declare_stream(adc_trace=True)\n", - " v_stream = declare_stream()\n", - " tracker_stream = declare_stream()\n", - " i_stream = declare_stream()\n", - "\n", - " i = declare(int)\n", - " v = declare(fixed)\n", - " tracker = declare(int, value=0)\n", - "\n", - " with for_(i, 0, i.qua_function..nested_function at 0x000001F774D2BB80>\n" - ] - } - ], - "source": [ - "DATADIR = './data/'\n", - "config = QMConfig()\n", - "global_config = config.config()\n", - "sweep = create_sweep_opx(my_qua_experiment, config.config(), 5)\n", - "\n", - "sweep.set_action_opts(\n", - " nested_function=dict(n_reps=5),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16fbd8e2-3947-4c95-bd61-3a350d4c29d0", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "hydraulic-convergence", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "the data_specs are: (DataSpec(name='repetition', depends_on=None, type=, unit=''), DataSpec(name='V', depends_on=['repetition'], type=, unit=''), DataSpec(name='tracker', depends_on=['repetition'], type=, unit=''))\n", - "DataSpec(name='repetition', depends_on=None, type=, unit='')\n", - "DataSpec(name='V', depends_on=['repetition'], type=, unit='')\n", - "DataSpec(name='tracker', depends_on=['repetition'], type=, unit='')\n", - "{'repetition': {'unit': '', 'axes': [], 'label': '', 'values': array([], dtype=float64)}, 'V': {'axes': ['repetition'], 'unit': '', 'label': '', 'values': array([], dtype=float64)}, 'tracker': {'axes': ['repetition'], 'unit': '', 'label': '', 'values': array([], dtype=float64)}}\n", - "Data location: ./data/2021-07-28\\2021-07-28_0002_OPX test #2\\2021-07-28_0002_OPX test #2.ddh5\n", - "2021-07-28 17:21:38,142 - qm - INFO - Performing health check\n", - "2021-07-28 17:21:38,151 - qm - INFO - Health check passed\n", - "2021-07-28 17:21:38,440 - qm - INFO - Flags: \n", - "2021-07-28 17:21:38,440 - qm - INFO - Executing high level program\n", - "the instance of container that I am getting is: <__main__.ResHandleContainer object at 0x000001F774D21BE0>\n", - "entering gathering data\n", - "repetition\n", - "V\n", - "tracker\n", - "starting control_progress\n", - "getting new current: 5, my old counter is: 0\n", - "starting control_progress\n", - "getting new current: 18, my old counter is: 5\n", - "starting control_progress\n", - "getting new current: 33, my old counter is: 18\n", - "starting control_progress\n", - "getting new current: 46, my old counter is: 33\n", - "starting control_progress\n", - "getting new current: 61, my old counter is: 46\n", - "starting control_progress\n", - "getting new current: 73, my old counter is: 61\n", - "starting control_progress\n", - "getting new current: 89, my old counter is: 73\n", - "starting control_progress\n", - "getting new current: 104, my old counter is: 89\n", - "starting control_progress\n", - "getting new current: 119, my old counter is: 104\n", - "starting control_progress\n", - "getting new current: 134, my old counter is: 119\n", - "starting control_progress\n", - "getting new current: 146, my old counter is: 134\n", - "starting control_progress\n", - "getting new current: 162, my old counter is: 146\n", - "starting control_progress\n", - "getting new current: 177, my old counter is: 162\n", - "starting control_progress\n", - "getting new current: 192, my old counter is: 177\n", - "starting control_progress\n", - "getting new current: 207, my old counter is: 192\n", - "starting control_progress\n", - "getting new current: 219, my old counter is: 207\n", - "starting control_progress\n", - "getting new current: 235, my old counter is: 219\n", - "starting control_progress\n", - "getting new current: 292, my old counter is: 235\n", - "starting control_progress\n", - "getting new current: 307, my old counter is: 292\n", - "starting control_progress\n", - "getting new current: 323, my old counter is: 307\n", - "starting control_progress\n", - "getting new current: 338, my old counter is: 323\n", - "starting control_progress\n", - "getting new current: 353, my old counter is: 338\n", - "starting control_progress\n", - "getting new current: 368, my old counter is: 353\n", - "starting control_progress\n", - "getting new current: 383, my old counter is: 368\n", - "starting control_progress\n", - "getting new current: 398, my old counter is: 383\n", - "starting control_progress\n", - "getting new current: 413, my old counter is: 398\n", - "starting control_progress\n", - "getting new current: 428, my old counter is: 413\n", - "starting control_progress\n", - "getting new current: 441, my old counter is: 428\n", - "starting control_progress\n", - "getting new current: 456, my old counter is: 441\n", - "starting control_progress\n", - "getting new current: 468, my old counter is: 456\n", - "starting control_progress\n", - "getting new current: 483, my old counter is: 468\n", - "starting control_progress\n", - "getting new current: 498, my old counter is: 483\n", - "starting control_progress\n", - "getting new current: 511, my old counter is: 498\n", - "starting control_progress\n", - "getting new current: 526, my old counter is: 511\n", - "starting control_progress\n", - "getting new current: 541, my old counter is: 526\n", - "starting control_progress\n", - "getting new current: 556, my old counter is: 541\n", - "starting control_progress\n", - "getting new current: 571, my old counter is: 556\n", - "starting control_progress\n", - "getting new current: 584, my old counter is: 571\n", - "starting control_progress\n", - "getting new current: 599, my old counter is: 584\n", - "starting control_progress\n", - "getting new current: 614, my old counter is: 599\n", - "starting control_progress\n", - "getting new current: 627, my old counter is: 614\n", - "starting control_progress\n", - "getting new current: 642, my old counter is: 627\n", - "starting control_progress\n", - "getting new current: 657, my old counter is: 642\n", - "starting control_progress\n", - "getting new current: 669, my old counter is: 657\n", - "starting control_progress\n", - "getting new current: 684, my old counter is: 669\n", - "starting control_progress\n", - "getting new current: 699, my old counter is: 684\n", - "starting control_progress\n", - "getting new current: 712, my old counter is: 699\n", - "starting control_progress\n", - "getting new current: 727, my old counter is: 712\n", - "starting control_progress\n", - "getting new current: 742, my old counter is: 727\n", - "starting control_progress\n", - "getting new current: 757, my old counter is: 742\n", - "starting control_progress\n", - "getting new current: 772, my old counter is: 757\n", - "starting control_progress\n", - "getting new current: 787, my old counter is: 772\n", - "starting control_progress\n", - "getting new current: 802, my old counter is: 787\n", - "starting control_progress\n", - "getting new current: 817, my old counter is: 802\n", - "starting control_progress\n", - "getting new current: 832, my old counter is: 817\n", - "starting control_progress\n", - "getting new current: 845, my old counter is: 832\n", - "starting control_progress\n", - "getting new current: 860, my old counter is: 845\n", - "starting control_progress\n", - "getting new current: 875, my old counter is: 860\n", - "starting control_progress\n", - "getting new current: 890, my old counter is: 875\n", - "starting control_progress\n", - "getting new current: 905, my old counter is: 890\n", - "starting control_progress\n", - "getting new current: 920, my old counter is: 905\n", - "starting control_progress\n", - "getting new current: 936, my old counter is: 920\n", - "starting control_progress\n", - "getting new current: 951, my old counter is: 936\n", - "starting control_progress\n", - "getting new current: 966, my old counter is: 951\n", - "starting control_progress\n", - "getting new current: 981, my old counter is: 966\n", - "starting control_progress\n", - "getting new current: 996, my old counter is: 981\n", - "starting control_progress\n", - "I have turned container False\n", - "getting new current: 1000, my old counter is: 996\n", - "The measurement has finished and all of the data has been saved.\n" - ] - } - ], - "source": [ - "test_name_counter += 1\n", - "run_sweep(sweep, DATADIR, f'OPX test #{test_name_counter}', prt=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "moving-industry", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "None\n" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "rotary-serial", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "44c14b68-4b6d-4ec5-a55e-f0d482cfc7d5", - "metadata": {}, - "source": [ - "# Structured with base class decorator" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "forced-testament", - "metadata": {}, - "outputs": [], - "source": [ - "class BackgroundRecordingBase:\n", - " \"\"\"\n", - " Base class decorator used to record asynchronous data from instrument. \n", - " Each instrument should have its own custom setup_wrapper (see setup_wrapper docstring for more info), and a custom collector \n", - " \"\"\"\n", - " def __init__(self, *specs):\n", - " \n", - " self.specs = specs \n", - " self.communicator = {}\n", - "\n", - " \n", - " def __call__(self, fun):\n", - " def sweep(**collector_kwargs):\n", - " setup_sweep = once(self.setup_wrapper(fun))\n", - " gather_sweep = Sweep(record_as(self.collector(**collector_kwargs), *self.specs))\n", - " return setup_sweep + gather_sweep\n", - " return sweep\n", - " \n", - " \n", - " \n", - " def setup_wrapper(self, fun):\n", - " \"\"\"\n", - " Wraps the setup function. setup_wrapper should consist of a function wrapped by the decorator @wraps and takes fun as an arugment.\n", - " In this case the wrapped function is setup. \n", - " Setup should accpet the *args and **kwargs of fun. It should also place any returns from fun in the communicator.\n", - " setup_wrapper needs to return the wraped function (setup)\n", - " \"\"\"\n", - " @wraps(fun)\n", - " def setup(*args, **kwargs):\n", - " self.communicator['setup_return'] = fun(*args, **kwargs)\n", - " return None\n", - " \n", - " return setup\n", - " \n", - " def collector(self):\n", - " \"\"\"\n", - " Data gathering function. This function should be a generator that collects data from the instrument.\n", - " All the logic of asynchronous data collection should be here. It should yield data as long as it is \n", - " available and the generator should finish once its experiment is done.\n", - " \"\"\"\n", - " yield None\n", - " \n", - " \n", - "def create_background_sweep(decorated_setup_function, **collector_kwargs):\n", - " sweep = decorated_setup_function(**collector_kwargs)\n", - " return sweep\n" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "likely-childhood", - "metadata": {}, - "outputs": [], - "source": [ - "class RecordOPX(BackgroundRecordingBase):\n", - " def setup_wrapper(self, fun):\n", - " @wraps(fun)\n", - " def setup(*args, **kwargs):\n", - " qmachine_mgr = QuantumMachinesManager()\n", - " qmachine = qmachine_mgr.open_qm(global_config)\n", - " job = qmachine.execute(fun(*args, **kwargs))\n", - " result_handles = job.result_handles\n", - " self.communicator['result_handles'] = result_handles\n", - " self.communicator['active'] = True\n", - " self.communicator['counter'] = 0\n", - " \n", - " return setup\n", - " \n", - " \n", - " def _control_progress(self, batchsize):\n", - " ready = False\n", - " first = True\n", - " res_handle = self.communicator['result_handles']\n", - " counter = self.communicator['counter']\n", - " print('starting control_progress')\n", - " while not ready:\n", - " for name, handle in res_handle:\n", - " current = handle.count_so_far()\n", - " \n", - " if res_handle.is_processing():\n", - " if current - counter >= batchsize:\n", - " ready = True\n", - " else:\n", - " ready = False\n", - " else:\n", - " ready = True\n", - " self.communicator['active'] = False\n", - " print('I have turned container False')\n", - " \n", - " return ready\n", - " \n", - "\n", - " def collector(self, batchsize):\n", - " print('entering gathering data')\n", - "\n", - " result_handle = self.communicator['result_handles']\n", - " \n", - " # checking if the parameters passed match the parameters in the opx\n", - " data_specs_names = [x.name for x in self.specs]\n", - " variable_counter = 0\n", - " for name, handle in result_handle:\n", - " print(name)\n", - " if name not in data_specs_names:\n", - " raise ValueError(f'{name} is not a recorded variable')\n", - " else:\n", - " variable_counter += 1\n", - " if variable_counter != len(data_specs_names):\n", - " raise ValueError(f'Number of recorded variables ({variable_counter}) \\\n", - " does not match number of variables gathered from the OPX ({len(data_specs_names)})')\n", - " \n", - " \n", - " while self.communicator['active']:\n", - "\n", - " first = True\n", - " data = {}\n", - " counter = self.communicator['counter']\n", - " first = True\n", - " current = 0\n", - " \n", - " if result_handle == None:\n", - " yield None\n", - " \n", - " record = self._control_progress(batchsize) \n", - " for name, handle in result_handle:\n", - "\n", - " if first:\n", - " current = handle.count_so_far()\n", - " print(f'getting new current: {current}, my old counter is: {counter}')\n", - " first = False\n", - " if current == counter:\n", - " yield None\n", - " \n", - " \n", - " handle.wait_for_values(current)\n", - " data_temp = np.array(handle.fetch(slice(counter, current))) \n", - " if name[0:4] == 'raw_':\n", - " holding_converting = []\n", - " for i in data_temp:\n", - " i_holder = []\n", - " for j in i:\n", - " converted = j.astype(float)\n", - " i_holder.append(converted)\n", - " holding_converting.append(i_holder)\n", - " if len(holding_converting) == 1:\n", - " converted_data_temp = [np.squeeze(holding_converting)]\n", - " else:\n", - " converted_data_temp = np.squeeze(holding_converting)\n", - " else:\n", - " converted_data_temp = data_temp.astype(float)\n", - " data[name] = converted_data_temp\n", - " self.communicator['counter'] = current\n", - " yield data\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "d4ad51d7-df0b-42f1-961a-c525f990d77c", - "metadata": {}, - "outputs": [], - "source": [ - "@RecordOPX(\n", - " independent('repetition', type='array'),\n", - " dependent('V', depends_on=['repetition'], type='array'),\n", - " dependent('tracker', depends_on=['repetition'], type='array'))\n", - "# dependent('raw', depends_on=['repetition'], type='array'))\n", - " #qua_raw_dependent('raw', depends_on=['repetition'], type='array'))\n", - "def my_qua_experiment_standard(n_reps=1000):\n", - " with program() as qua_measurement:\n", - " raw_stream = declare_stream(adc_trace=True)\n", - " v_stream = declare_stream()\n", - " tracker_stream = declare_stream()\n", - " i_stream = declare_stream()\n", - "\n", - " i = declare(int)\n", - " v = declare(fixed)\n", - " tracker = declare(int, value=0)\n", - "\n", - " with for_(i, 0, i, unit=''), DataSpec(name='V', depends_on=['repetition'], type=, unit=''), DataSpec(name='tracker', depends_on=['repetition'], type=, unit=''))\n", - "DataSpec(name='repetition', depends_on=None, type=, unit='')\n", - "DataSpec(name='V', depends_on=['repetition'], type=, unit='')\n", - "DataSpec(name='tracker', depends_on=['repetition'], type=, unit='')\n", - "{'repetition': {'unit': '', 'axes': [], 'label': '', 'values': array([], dtype=float64)}, 'V': {'axes': ['repetition'], 'unit': '', 'label': '', 'values': array([], dtype=float64)}, 'tracker': {'axes': ['repetition'], 'unit': '', 'label': '', 'values': array([], dtype=float64)}}\n", - "Data location: ./data/2021-07-28\\2021-07-28_0007_OPX test standard #2\\2021-07-28_0007_OPX test standard #2.ddh5\n", - "2021-07-28 18:00:38,816 - qm - INFO - Performing health check\n", - "2021-07-28 18:00:38,818 - qm - INFO - Health check passed\n", - "2021-07-28 18:00:39,098 - qm - INFO - Flags: \n", - "2021-07-28 18:00:39,098 - qm - INFO - Executing high level program\n", - "entering gathering data\n", - "repetition\n", - "V\n", - "tracker\n", - "starting control_progress\n", - "getting new current: 10, my old counter is: 0\n", - "{'repetition': array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]), 'V': array([-0.19585172, -0.19580412, -0.19579314, -0.19579224, -0.19580339,\n", - " -0.19578793, -0.19578588, -0.19579728, -0.195802 , -0.19577818]), 'tracker': array([ 2., 4., 6., 8., 10., 12., 14., 16., 18., 20.])}\n", - "starting control_progress\n", - "getting new current: 26, my old counter is: 10\n", - "{'repetition': array([10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22.,\n", - " 23., 24., 25.]), 'V': array([-0.19575856, -0.19576599, -0.19574884, -0.19573573, -0.19575905,\n", - " -0.19577549, -0.1958014 , -0.19581931, -0.1957964 , -0.19577985,\n", - " -0.19578252, -0.19574846, -0.19577056, -0.19573121, -0.19577618,\n", - " -0.19577065]), 'tracker': array([22., 24., 26., 28., 30., 32., 34., 36., 38., 40., 42., 44., 46.,\n", - " 48., 50., 52.])}\n", - "starting control_progress\n", - "getting new current: 38, my old counter is: 26\n", - "{'repetition': array([26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37.]), 'V': array([-0.19576492, -0.19576509, -0.19578099, -0.19581264, -0.19577168,\n", - " -0.19577877, -0.19576147, -0.19576344, -0.19576478, -0.19575328,\n", - " -0.19577195, -0.19577415]), 'tracker': array([54., 56., 58., 60., 62., 64., 66., 68., 70., 72., 74., 76.])}\n", - "starting control_progress\n", - "getting new current: 53, my old counter is: 38\n", - "{'repetition': array([38., 39., 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., 50.,\n", - " 51., 52.]), 'V': array([-0.19577513, -0.19578515, -0.19581234, -0.19579154, -0.1957913 ,\n", - " -0.19578104, -0.19579247, -0.19577788, -0.19579973, -0.19577529,\n", - " -0.19581063, -0.19576654, -0.19580223, -0.19575444, -0.19576245]), 'tracker': array([ 78., 80., 82., 84., 86., 88., 90., 92., 94., 96., 98.,\n", - " 100., 102., 104., 106.])}\n", - "starting control_progress\n", - "getting new current: 66, my old counter is: 53\n", - "{'repetition': array([53., 54., 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65.]), 'V': array([-0.19577051, -0.19577536, -0.19575042, -0.19578873, -0.19577943,\n", - " -0.19576142, -0.19578826, -0.19578732, -0.19576568, -0.19580106,\n", - " -0.19576155, -0.19574936, -0.19580011]), 'tracker': array([108., 110., 112., 114., 116., 118., 120., 122., 124., 126., 128.,\n", - " 130., 132.])}\n", - "starting control_progress\n", - "getting new current: 81, my old counter is: 66\n", - "{'repetition': array([66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76., 77., 78.,\n", - " 79., 80.]), 'V': array([-0.19578186, -0.19581716, -0.19575651, -0.1957934 , -0.19578069,\n", - " -0.19577493, -0.19573466, -0.19576906, -0.19579173, -0.19577469,\n", - " -0.19577651, -0.1958001 , -0.19579489, -0.19579595, -0.19578649]), 'tracker': array([134., 136., 138., 140., 142., 144., 146., 148., 150., 152., 154.,\n", - " 156., 158., 160., 162.])}\n", - "starting control_progress\n", - "getting new current: 93, my old counter is: 81\n", - "{'repetition': array([81., 82., 83., 84., 85., 86., 87., 88., 89., 90., 91., 92.]), 'V': array([-0.19577065, -0.19580063, -0.19579859, -0.19578066, -0.19579165,\n", - " -0.19580898, -0.19579137, -0.19580229, -0.19576392, -0.19578167,\n", - " -0.19578138, -0.19576528]), 'tracker': array([164., 166., 168., 170., 172., 174., 176., 178., 180., 182., 184.,\n", - " 186.])}\n", - "starting control_progress\n", - "getting new current: 108, my old counter is: 93\n", - "{'repetition': array([ 93., 94., 95., 96., 97., 98., 99., 100., 101., 102., 103.,\n", - " 104., 105., 106., 107.]), 'V': array([-0.19574423, -0.19575272, -0.19578872, -0.19575793, -0.19574488,\n", - " -0.19572311, -0.19571695, -0.1957245 , -0.19574478, -0.19573105,\n", - " -0.19573945, -0.1957643 , -0.19574071, -0.19575833, -0.19572633]), 'tracker': array([188., 190., 192., 194., 196., 198., 200., 202., 204., 206., 208.,\n", - " 210., 212., 214., 216.])}\n", - "starting control_progress\n", - "getting new current: 124, my old counter is: 108\n", - "{'repetition': array([108., 109., 110., 111., 112., 113., 114., 115., 116., 117., 118.,\n", - " 119., 120., 121., 122., 123.]), 'V': array([-0.19576799, -0.19574634, -0.19577346, -0.19576201, -0.19574748,\n", - " -0.19575531, -0.19576957, -0.19576705, -0.19576599, -0.1957365 ,\n", - " -0.19572687, -0.19575964, -0.19576248, -0.19579114, -0.19578372,\n", - " -0.19580027]), 'tracker': array([218., 220., 222., 224., 226., 228., 230., 232., 234., 236., 238.,\n", - " 240., 242., 244., 246., 248.])}\n", - "starting control_progress\n", - "getting new current: 136, my old counter is: 124\n", - "{'repetition': array([124., 125., 126., 127., 128., 129., 130., 131., 132., 133., 134.,\n", - " 135.]), 'V': array([-0.19584877, -0.19580854, -0.19577908, -0.19578501, -0.19581062,\n", - " -0.1958263 , -0.19579604, -0.19575908, -0.19577266, -0.19574304,\n", - " -0.19578607, -0.19578546]), 'tracker': array([250., 252., 254., 256., 258., 260., 262., 264., 266., 268., 270.,\n", - " 272.])}\n", - "starting control_progress\n", - "getting new current: 151, my old counter is: 136\n", - "{'repetition': array([136., 137., 138., 139., 140., 141., 142., 143., 144., 145., 146.,\n", - " 147., 148., 149., 150.]), 'V': array([-0.19578759, -0.19578636, -0.19573835, -0.19578145, -0.19575462,\n", - " -0.19574554, -0.19574277, -0.1957546 , -0.19573143, -0.19571526,\n", - " -0.1957373 , -0.19574432, -0.19573912, -0.19575818, -0.19575404]), 'tracker': array([274., 276., 278., 280., 282., 284., 286., 288., 290., 292., 294.,\n", - " 296., 298., 300., 302.])}\n", - "starting control_progress\n", - "getting new current: 164, my old counter is: 151\n", - "{'repetition': array([151., 152., 153., 154., 155., 156., 157., 158., 159., 160., 161.,\n", - " 162., 163.]), 'V': array([-0.19573422, -0.1957174 , -0.19577619, -0.19578409, -0.1957681 ,\n", - " -0.19573138, -0.19570927, -0.19574173, -0.1957242 , -0.19572704,\n", - " -0.1957668 , -0.19576787, -0.19576233]), 'tracker': array([304., 306., 308., 310., 312., 314., 316., 318., 320., 322., 324.,\n", - " 326., 328.])}\n", - "starting control_progress\n", - "getting new current: 179, my old counter is: 164\n", - "{'repetition': array([164., 165., 166., 167., 168., 169., 170., 171., 172., 173., 174.,\n", - " 175., 176., 177., 178.]), 'V': array([-0.19580268, -0.19577736, -0.19581094, -0.19577114, -0.19575515,\n", - " -0.19577837, -0.19577119, -0.19574293, -0.19578837, -0.19578811,\n", - " -0.1958097 , -0.1957989 , -0.19578547, -0.19576683, -0.19577097]), 'tracker': array([330., 332., 334., 336., 338., 340., 342., 344., 346., 348., 350.,\n", - " 352., 354., 356., 358.])}\n", - "starting control_progress\n", - "getting new current: 194, my old counter is: 179\n", - "{'repetition': array([179., 180., 181., 182., 183., 184., 185., 186., 187., 188., 189.,\n", - " 190., 191., 192., 193.]), 'V': array([-0.19583013, -0.1957763 , -0.19576297, -0.19574791, -0.19577265,\n", - " -0.19573439, -0.19574504, -0.19572585, -0.19575854, -0.19579522,\n", - " -0.19577065, -0.19577944, -0.19571174, -0.19575343, -0.19576964]), 'tracker': array([360., 362., 364., 366., 368., 370., 372., 374., 376., 378., 380.,\n", - " 382., 384., 386., 388.])}\n", - "starting control_progress\n", - "getting new current: 206, my old counter is: 194\n", - "{'repetition': array([194., 195., 196., 197., 198., 199., 200., 201., 202., 203., 204.,\n", - " 205.]), 'V': array([-0.19579013, -0.19576529, -0.19574984, -0.19580515, -0.19581383,\n", - " -0.1958065 , -0.19574804, -0.1957694 , -0.19576592, -0.1958085 ,\n", - " -0.19577842, -0.19578798]), 'tracker': array([390., 392., 394., 396., 398., 400., 402., 404., 406., 408., 410.,\n", - " 412.])}\n", - "starting control_progress\n", - "getting new current: 222, my old counter is: 206\n", - "{'repetition': array([206., 207., 208., 209., 210., 211., 212., 213., 214., 215., 216.,\n", - " 217., 218., 219., 220., 221.]), 'V': array([-0.19578287, -0.19579412, -0.19577356, -0.19578513, -0.19580721,\n", - " -0.1958065 , -0.19579463, -0.19578249, -0.19579004, -0.19575552,\n", - " -0.19574103, -0.19576712, -0.19577198, -0.1957393 , -0.19578011,\n", - " -0.19577125]), 'tracker': array([414., 416., 418., 420., 422., 424., 426., 428., 430., 432., 434.,\n", - " 436., 438., 440., 442., 444.])}\n", - "starting control_progress\n", - "getting new current: 237, my old counter is: 222\n", - "{'repetition': array([222., 223., 224., 225., 226., 227., 228., 229., 230., 231., 232.,\n", - " 233., 234., 235., 236.]), 'V': array([-0.19581062, -0.19577893, -0.19577346, -0.19578392, -0.19577844,\n", - " -0.19576547, -0.19576558, -0.19577171, -0.19579251, -0.19575875,\n", - " -0.19574128, -0.19573756, -0.19577454, -0.19575565, -0.19576393]), 'tracker': array([446., 448., 450., 452., 454., 456., 458., 460., 462., 464., 466.,\n", - " 468., 470., 472., 474.])}\n", - "starting control_progress\n", - "getting new current: 252, my old counter is: 237\n", - "{'repetition': array([237., 238., 239., 240., 241., 242., 243., 244., 245., 246., 247.,\n", - " 248., 249., 250., 251.]), 'V': array([-0.19572202, -0.1957559 , -0.19574336, -0.19578778, -0.19576668,\n", - " -0.19574044, -0.1957992 , -0.19575293, -0.19574615, -0.19571473,\n", - " -0.19575188, -0.19576313, -0.19573705, -0.19573767, -0.19575009]), 'tracker': array([476., 478., 480., 482., 484., 486., 488., 490., 492., 494., 496.,\n", - " 498., 500., 502., 504.])}\n", - "starting control_progress\n", - "getting new current: 267, my old counter is: 252\n", - "{'repetition': array([252., 253., 254., 255., 256., 257., 258., 259., 260., 261., 262.,\n", - " 263., 264., 265., 266.]), 'V': array([-0.19573784, -0.19575049, -0.19576683, -0.19574679, -0.19577286,\n", - " -0.19576406, -0.1958008 , -0.19578627, -0.19582069, -0.19576072,\n", - " -0.19576221, -0.19578667, -0.195796 , -0.19577758, -0.19577562]), 'tracker': array([506., 508., 510., 512., 514., 516., 518., 520., 522., 524., 526.,\n", - " 528., 530., 532., 534.])}\n", - "starting control_progress\n", - "getting new current: 282, my old counter is: 267\n", - "{'repetition': array([267., 268., 269., 270., 271., 272., 273., 274., 275., 276., 277.,\n", - " 278., 279., 280., 281.]), 'V': array([-0.19573303, -0.19575805, -0.19577796, -0.19577616, -0.19572727,\n", - " -0.1957355 , -0.1957087 , -0.19572516, -0.19569132, -0.19574493,\n", - " -0.19571597, -0.19574904, -0.1957286 , -0.19574383, -0.19577252]), 'tracker': array([536., 538., 540., 542., 544., 546., 548., 550., 552., 554., 556.,\n", - " 558., 560., 562., 564.])}\n", - "starting control_progress\n", - "getting new current: 297, my old counter is: 282\n", - "{'repetition': array([282., 283., 284., 285., 286., 287., 288., 289., 290., 291., 292.,\n", - " 293., 294., 295., 296.]), 'V': array([-0.19578329, -0.19576024, -0.1957389 , -0.19574581, -0.19573799,\n", - " -0.19575953, -0.1957468 , -0.19576407, -0.19574384, -0.1957673 ,\n", - " -0.19572521, -0.19574425, -0.19575259, -0.19576729, -0.19578869]), 'tracker': array([566., 568., 570., 572., 574., 576., 578., 580., 582., 584., 586.,\n", - " 588., 590., 592., 594.])}\n", - "starting control_progress\n", - "getting new current: 310, my old counter is: 297\n", - "{'repetition': array([297., 298., 299., 300., 301., 302., 303., 304., 305., 306., 307.,\n", - " 308., 309.]), 'V': array([-0.19576481, -0.19578388, -0.19577233, -0.19578935, -0.19578798,\n", - " -0.19575791, -0.19576507, -0.19575607, -0.19578849, -0.19573919,\n", - " -0.19577397, -0.19575109, -0.19575265]), 'tracker': array([596., 598., 600., 602., 604., 606., 608., 610., 612., 614., 616.,\n", - " 618., 620.])}\n", - "starting control_progress\n", - "getting new current: 325, my old counter is: 310\n", - "{'repetition': array([310., 311., 312., 313., 314., 315., 316., 317., 318., 319., 320.,\n", - " 321., 322., 323., 324.]), 'V': array([-0.19577496, -0.19576808, -0.19577524, -0.19577883, -0.1957459 ,\n", - " -0.19574274, -0.19575836, -0.19574157, -0.19576629, -0.19579577,\n", - " -0.19577694, -0.19575644, -0.19575764, -0.19575735, -0.19573052]), 'tracker': array([622., 624., 626., 628., 630., 632., 634., 636., 638., 640., 642.,\n", - " 644., 646., 648., 650.])}\n", - "starting control_progress\n", - "getting new current: 340, my old counter is: 325\n", - "{'repetition': array([325., 326., 327., 328., 329., 330., 331., 332., 333., 334., 335.,\n", - " 336., 337., 338., 339.]), 'V': array([-0.19576511, -0.19579244, -0.19577689, -0.19576769, -0.19576903,\n", - " -0.1957386 , -0.19576965, -0.19575687, -0.19576989, -0.19577232,\n", - " -0.19575012, -0.19575499, -0.19575268, -0.19577586, -0.19574152]), 'tracker': array([652., 654., 656., 658., 660., 662., 664., 666., 668., 670., 672.,\n", - " 674., 676., 678., 680.])}\n", - "starting control_progress\n", - "getting new current: 352, my old counter is: 340\n", - "{'repetition': array([340., 341., 342., 343., 344., 345., 346., 347., 348., 349., 350.,\n", - " 351.]), 'V': array([-0.19578813, -0.19573673, -0.19576361, -0.19580159, -0.19576023,\n", - " -0.1957609 , -0.19577341, -0.19576542, -0.19577366, -0.19579495,\n", - " -0.19580591, -0.1958123 ]), 'tracker': array([682., 684., 686., 688., 690., 692., 694., 696., 698., 700., 702.,\n", - " 704.])}\n", - "starting control_progress\n", - "getting new current: 367, my old counter is: 352\n", - "{'repetition': array([352., 353., 354., 355., 356., 357., 358., 359., 360., 361., 362.,\n", - " 363., 364., 365., 366.]), 'V': array([-0.19579706, -0.19578061, -0.19580985, -0.19576491, -0.19575177,\n", - " -0.19576271, -0.1957955 , -0.19577308, -0.19575274, -0.19575532,\n", - " -0.19573416, -0.1957556 , -0.19577281, -0.1957748 , -0.19578194]), 'tracker': array([706., 708., 710., 712., 714., 716., 718., 720., 722., 724., 726.,\n", - " 728., 730., 732., 734.])}\n", - "starting control_progress\n", - "getting new current: 382, my old counter is: 367\n", - "{'repetition': array([367., 368., 369., 370., 371., 372., 373., 374., 375., 376., 377.,\n", - " 378., 379., 380., 381.]), 'V': array([-0.19579314, -0.19582359, -0.19577048, -0.19577219, -0.19577921,\n", - " -0.19577538, -0.19578233, -0.19577347, -0.1957843 , -0.19575157,\n", - " -0.19576677, -0.1957956 , -0.19577131, -0.19577608, -0.1957648 ]), 'tracker': array([736., 738., 740., 742., 744., 746., 748., 750., 752., 754., 756.,\n", - " 758., 760., 762., 764.])}\n", - "starting control_progress\n", - "getting new current: 398, my old counter is: 382\n", - "{'repetition': array([382., 383., 384., 385., 386., 387., 388., 389., 390., 391., 392.,\n", - " 393., 394., 395., 396., 397.]), 'V': array([-0.19575842, -0.19579078, -0.19577964, -0.19580021, -0.19575571,\n", - " -0.19576613, -0.19577941, -0.19575637, -0.19579144, -0.19570604,\n", - " -0.1957902 , -0.19581091, -0.19573324, -0.195778 , -0.19573765,\n", - " -0.19577699]), 'tracker': array([766., 768., 770., 772., 774., 776., 778., 780., 782., 784., 786.,\n", - " 788., 790., 792., 794., 796.])}\n", - "starting control_progress\n", - "getting new current: 413, my old counter is: 398\n", - "{'repetition': array([398., 399., 400., 401., 402., 403., 404., 405., 406., 407., 408.,\n", - " 409., 410., 411., 412.]), 'V': array([-0.19578019, -0.19578617, -0.1957447 , -0.19578684, -0.19574201,\n", - " -0.19576835, -0.19578174, -0.19576639, -0.19578563, -0.19576271,\n", - " -0.1957708 , -0.19576658, -0.19576205, -0.19576945, -0.19577354]), 'tracker': array([798., 800., 802., 804., 806., 808., 810., 812., 814., 816., 818.,\n", - " 820., 822., 824., 826.])}\n", - "starting control_progress\n", - "getting new current: 428, my old counter is: 413\n", - "{'repetition': array([413., 414., 415., 416., 417., 418., 419., 420., 421., 422., 423.,\n", - " 424., 425., 426., 427.]), 'V': array([-0.19574365, -0.19576992, -0.19578273, -0.19576402, -0.19575398,\n", - " -0.19577694, -0.19577922, -0.19575745, -0.19575531, -0.19575869,\n", - " -0.19575643, -0.19573796, -0.19573836, -0.19576814, -0.19575066]), 'tracker': array([828., 830., 832., 834., 836., 838., 840., 842., 844., 846., 848.,\n", - " 850., 852., 854., 856.])}\n", - "starting control_progress\n", - "getting new current: 443, my old counter is: 428\n", - "{'repetition': array([428., 429., 430., 431., 432., 433., 434., 435., 436., 437., 438.,\n", - " 439., 440., 441., 442.]), 'V': array([-0.19575517, -0.19577063, -0.19577231, -0.19579097, -0.19579511,\n", - " -0.19578572, -0.19578608, -0.19579819, -0.19577247, -0.19578824,\n", - " -0.19577778, -0.19577328, -0.19577415, -0.19576015, -0.19576841]), 'tracker': array([858., 860., 862., 864., 866., 868., 870., 872., 874., 876., 878.,\n", - " 880., 882., 884., 886.])}\n", - "starting control_progress\n", - "getting new current: 458, my old counter is: 443\n", - "{'repetition': array([443., 444., 445., 446., 447., 448., 449., 450., 451., 452., 453.,\n", - " 454., 455., 456., 457.]), 'V': array([-0.19576233, -0.19574028, -0.19576768, -0.19580057, -0.19577585,\n", - " -0.19575762, -0.1957702 , -0.19577461, -0.19577282, -0.19572397,\n", - " -0.19574225, -0.19571669, -0.19575674, -0.19571974, -0.19577206]), 'tracker': array([888., 890., 892., 894., 896., 898., 900., 902., 904., 906., 908.,\n", - " 910., 912., 914., 916.])}\n", - "starting control_progress\n", - "getting new current: 473, my old counter is: 458\n", - "{'repetition': array([458., 459., 460., 461., 462., 463., 464., 465., 466., 467., 468.,\n", - " 469., 470., 471., 472.]), 'V': array([-0.19575988, -0.19576492, -0.19575344, -0.19575002, -0.1957464 ,\n", - " -0.19574878, -0.19577142, -0.19577105, -0.19578417, -0.19573795,\n", - " -0.19573122, -0.19571762, -0.19576555, -0.19575001, -0.19574252]), 'tracker': array([918., 920., 922., 924., 926., 928., 930., 932., 934., 936., 938.,\n", - " 940., 942., 944., 946.])}\n", - "starting control_progress\n", - "getting new current: 488, my old counter is: 473\n", - "{'repetition': array([473., 474., 475., 476., 477., 478., 479., 480., 481., 482., 483.,\n", - " 484., 485., 486., 487.]), 'V': array([-0.19572167, -0.1957374 , -0.19574232, -0.19573629, -0.19574209,\n", - " -0.19572953, -0.19573419, -0.19576821, -0.19576487, -0.19575105,\n", - " -0.19574441, -0.1957302 , -0.1957319 , -0.19573577, -0.19572477]), 'tracker': array([948., 950., 952., 954., 956., 958., 960., 962., 964., 966., 968.,\n", - " 970., 972., 974., 976.])}\n", - "starting control_progress\n", - "getting new current: 503, my old counter is: 488\n", - "{'repetition': array([488., 489., 490., 491., 492., 493., 494., 495., 496., 497., 498.,\n", - " 499., 500., 501., 502.]), 'V': array([-0.19575965, -0.19574095, -0.19577964, -0.19576531, -0.1957581 ,\n", - " -0.19574733, -0.19576078, -0.19575166, -0.19576658, -0.19573462,\n", - " -0.19574659, -0.19572744, -0.1957468 , -0.19571928, -0.19576697]), 'tracker': array([ 978., 980., 982., 984., 986., 988., 990., 992., 994.,\n", - " 996., 998., 1000., 1002., 1004., 1006.])}\n", - "starting control_progress\n", - "getting new current: 516, my old counter is: 503\n", - "{'repetition': array([503., 504., 505., 506., 507., 508., 509., 510., 511., 512., 513.,\n", - " 514., 515.]), 'V': array([-0.19577319, -0.19574289, -0.19573363, -0.19576193, -0.19573025,\n", - " -0.19576148, -0.19573504, -0.19572638, -0.19574499, -0.19574682,\n", - " -0.19577214, -0.19575321, -0.19576225]), 'tracker': array([1008., 1010., 1012., 1014., 1016., 1018., 1020., 1022., 1024.,\n", - " 1026., 1028., 1030., 1032.])}\n", - "starting control_progress\n", - "getting new current: 531, my old counter is: 516\n", - "{'repetition': array([516., 517., 518., 519., 520., 521., 522., 523., 524., 525., 526.,\n", - " 527., 528., 529., 530.]), 'V': array([-0.1957626 , -0.19572385, -0.19574756, -0.19574137, -0.19573744,\n", - " -0.19575919, -0.19574772, -0.19573719, -0.1957392 , -0.19573919,\n", - " -0.19573899, -0.19574278, -0.19573775, -0.19573212, -0.19573404]), 'tracker': array([1034., 1036., 1038., 1040., 1042., 1044., 1046., 1048., 1050.,\n", - " 1052., 1054., 1056., 1058., 1060., 1062.])}\n", - "starting control_progress\n", - "getting new current: 546, my old counter is: 531\n", - "{'repetition': array([531., 532., 533., 534., 535., 536., 537., 538., 539., 540., 541.,\n", - " 542., 543., 544., 545.]), 'V': array([-0.19577264, -0.19574903, -0.19578788, -0.19576212, -0.19579994,\n", - " -0.19576741, -0.19578743, -0.19578701, -0.19578144, -0.19576551,\n", - " -0.19573426, -0.19576523, -0.19573572, -0.19576174, -0.1957588 ]), 'tracker': array([1064., 1066., 1068., 1070., 1072., 1074., 1076., 1078., 1080.,\n", - " 1082., 1084., 1086., 1088., 1090., 1092.])}\n", - "starting control_progress\n", - "getting new current: 561, my old counter is: 546\n", - "{'repetition': array([546., 547., 548., 549., 550., 551., 552., 553., 554., 555., 556.,\n", - " 557., 558., 559., 560.]), 'V': array([-0.19575676, -0.19572942, -0.19576163, -0.19574991, -0.19577293,\n", - " -0.1957659 , -0.19573754, -0.1957569 , -0.19575942, -0.19574101,\n", - " -0.1957352 , -0.19574931, -0.19575657, -0.19575075, -0.19578855]), 'tracker': array([1094., 1096., 1098., 1100., 1102., 1104., 1106., 1108., 1110.,\n", - " 1112., 1114., 1116., 1118., 1120., 1122.])}\n", - "starting control_progress\n", - "getting new current: 574, my old counter is: 561\n", - "{'repetition': array([561., 562., 563., 564., 565., 566., 567., 568., 569., 570., 571.,\n", - " 572., 573.]), 'V': array([-0.19574429, -0.19574675, -0.19574923, -0.19576131, -0.19577216,\n", - " -0.19576826, -0.19576892, -0.19570974, -0.1957396 , -0.19576179,\n", - " -0.19572863, -0.19571857, -0.19574117]), 'tracker': array([1124., 1126., 1128., 1130., 1132., 1134., 1136., 1138., 1140.,\n", - " 1142., 1144., 1146., 1148.])}\n", - "starting control_progress\n", - "getting new current: 589, my old counter is: 574\n", - "{'repetition': array([574., 575., 576., 577., 578., 579., 580., 581., 582., 583., 584.,\n", - " 585., 586., 587., 588.]), 'V': array([-0.19575764, -0.19575445, -0.19572538, -0.19575012, -0.19579348,\n", - " -0.19577249, -0.19577177, -0.19576791, -0.19576452, -0.19576158,\n", - " -0.19578413, -0.19580349, -0.19576159, -0.19575891, -0.19577966]), 'tracker': array([1150., 1152., 1154., 1156., 1158., 1160., 1162., 1164., 1166.,\n", - " 1168., 1170., 1172., 1174., 1176., 1178.])}\n", - "starting control_progress\n", - "getting new current: 604, my old counter is: 589\n", - "{'repetition': array([589., 590., 591., 592., 593., 594., 595., 596., 597., 598., 599.,\n", - " 600., 601., 602., 603.]), 'V': array([-0.19576747, -0.19578961, -0.19579923, -0.19578919, -0.19575879,\n", - " -0.19576924, -0.19575146, -0.19578908, -0.19578736, -0.19579463,\n", - " -0.19579294, -0.19581107, -0.19578658, -0.19578421, -0.19577275]), 'tracker': array([1180., 1182., 1184., 1186., 1188., 1190., 1192., 1194., 1196.,\n", - " 1198., 1200., 1202., 1204., 1206., 1208.])}\n", - "starting control_progress\n", - "getting new current: 619, my old counter is: 604\n", - "{'repetition': array([604., 605., 606., 607., 608., 609., 610., 611., 612., 613., 614.,\n", - " 615., 616., 617., 618.]), 'V': array([-0.1957866 , -0.19576372, -0.19574231, -0.19577928, -0.19577921,\n", - " -0.19579585, -0.19582024, -0.19577373, -0.19580342, -0.19577709,\n", - " -0.19576403, -0.19578641, -0.19573165, -0.19575763, -0.19572283]), 'tracker': array([1210., 1212., 1214., 1216., 1218., 1220., 1222., 1224., 1226.,\n", - " 1228., 1230., 1232., 1234., 1236., 1238.])}\n", - "starting control_progress\n", - "getting new current: 631, my old counter is: 619\n", - "{'repetition': array([619., 620., 621., 622., 623., 624., 625., 626., 627., 628., 629.,\n", - " 630.]), 'V': array([-0.19570861, -0.19576527, -0.1957134 , -0.19573737, -0.19572553,\n", - " -0.19577749, -0.19577896, -0.19573761, -0.19577084, -0.19576516,\n", - " -0.19578893, -0.1957602 ]), 'tracker': array([1240., 1242., 1244., 1246., 1248., 1250., 1252., 1254., 1256.,\n", - " 1258., 1260., 1262.])}\n", - "starting control_progress\n", - "getting new current: 647, my old counter is: 631\n", - "{'repetition': array([631., 632., 633., 634., 635., 636., 637., 638., 639., 640., 641.,\n", - " 642., 643., 644., 645., 646.]), 'V': array([-0.19571034, -0.19574427, -0.19577987, -0.19573187, -0.1957286 ,\n", - " -0.19572173, -0.19571079, -0.19574257, -0.19574926, -0.19572312,\n", - " -0.19570914, -0.19572528, -0.19569168, -0.19573534, -0.19572873,\n", - " -0.19575239]), 'tracker': array([1264., 1266., 1268., 1270., 1272., 1274., 1276., 1278., 1280.,\n", - " 1282., 1284., 1286., 1288., 1290., 1292., 1294.])}\n", - "starting control_progress\n", - "getting new current: 662, my old counter is: 647\n", - "{'repetition': array([647., 648., 649., 650., 651., 652., 653., 654., 655., 656., 657.,\n", - " 658., 659., 660., 661.]), 'V': array([-0.19575501, -0.19574064, -0.19573782, -0.19573177, -0.19573409,\n", - " -0.19572403, -0.19573971, -0.19575436, -0.19572347, -0.1957123 ,\n", - " -0.19572906, -0.19575923, -0.19571871, -0.19573157, -0.19573099]), 'tracker': array([1296., 1298., 1300., 1302., 1304., 1306., 1308., 1310., 1312.,\n", - " 1314., 1316., 1318., 1320., 1322., 1324.])}\n", - "starting control_progress\n", - "getting new current: 677, my old counter is: 662\n", - "{'repetition': array([662., 663., 664., 665., 666., 667., 668., 669., 670., 671., 672.,\n", - " 673., 674., 675., 676.]), 'V': array([-0.19571936, -0.19572929, -0.19571849, -0.19570949, -0.19573601,\n", - " -0.19574103, -0.1957112 , -0.19572997, -0.19573584, -0.19574551,\n", - " -0.1957437 , -0.19574552, -0.19575286, -0.19574619, -0.19573169]), 'tracker': array([1326., 1328., 1330., 1332., 1334., 1336., 1338., 1340., 1342.,\n", - " 1344., 1346., 1348., 1350., 1352., 1354.])}\n", - "starting control_progress\n", - "getting new current: 692, my old counter is: 677\n", - "{'repetition': array([677., 678., 679., 680., 681., 682., 683., 684., 685., 686., 687.,\n", - " 688., 689., 690., 691.]), 'V': array([-0.19575215, -0.19572866, -0.19575182, -0.19573189, -0.19577973,\n", - " -0.19578504, -0.1957748 , -0.19577009, -0.19576898, -0.19575509,\n", - " -0.19573693, -0.19574451, -0.19576193, -0.19576573, -0.1957339 ]), 'tracker': array([1356., 1358., 1360., 1362., 1364., 1366., 1368., 1370., 1372.,\n", - " 1374., 1376., 1378., 1380., 1382., 1384.])}\n", - "starting control_progress\n", - "getting new current: 704, my old counter is: 692\n", - "{'repetition': array([692., 693., 694., 695., 696., 697., 698., 699., 700., 701., 702.,\n", - " 703.]), 'V': array([-0.195753 , -0.19573673, -0.19573615, -0.19572631, -0.19577333,\n", - " -0.19575053, -0.19574484, -0.19577717, -0.19576654, -0.19575455,\n", - " -0.19575886, -0.19576237]), 'tracker': array([1386., 1388., 1390., 1392., 1394., 1396., 1398., 1400., 1402.,\n", - " 1404., 1406., 1408.])}\n", - "starting control_progress\n", - "getting new current: 720, my old counter is: 704\n", - "{'repetition': array([704., 705., 706., 707., 708., 709., 710., 711., 712., 713., 714.,\n", - " 715., 716., 717., 718., 719.]), 'V': array([-0.19576239, -0.19575494, -0.19578413, -0.19575422, -0.1957335 ,\n", - " -0.19576243, -0.19575835, -0.19571444, -0.19573792, -0.19576808,\n", - " -0.1957747 , -0.19571327, -0.19574999, -0.1957387 , -0.19573887,\n", - " -0.19574643]), 'tracker': array([1410., 1412., 1414., 1416., 1418., 1420., 1422., 1424., 1426.,\n", - " 1428., 1430., 1432., 1434., 1436., 1438., 1440.])}\n", - "starting control_progress\n", - "getting new current: 735, my old counter is: 720\n", - "{'repetition': array([720., 721., 722., 723., 724., 725., 726., 727., 728., 729., 730.,\n", - " 731., 732., 733., 734.]), 'V': array([-0.19579647, -0.19578544, -0.19581163, -0.19581464, -0.19580963,\n", - " -0.19578468, -0.19579515, -0.19578819, -0.19575239, -0.195752 ,\n", - " -0.19580071, -0.19576794, -0.19579114, -0.19578077, -0.19580105]), 'tracker': array([1442., 1444., 1446., 1448., 1450., 1452., 1454., 1456., 1458.,\n", - " 1460., 1462., 1464., 1466., 1468., 1470.])}\n", - "starting control_progress\n", - "getting new current: 747, my old counter is: 735\n", - "{'repetition': array([735., 736., 737., 738., 739., 740., 741., 742., 743., 744., 745.,\n", - " 746.]), 'V': array([-0.1957742 , -0.19581393, -0.19579934, -0.19583833, -0.19578037,\n", - " -0.19576357, -0.19579613, -0.19578404, -0.19576786, -0.19578788,\n", - " -0.19571121, -0.19573976]), 'tracker': array([1472., 1474., 1476., 1478., 1480., 1482., 1484., 1486., 1488.,\n", - " 1490., 1492., 1494.])}\n", - "starting control_progress\n", - "getting new current: 762, my old counter is: 747\n", - "{'repetition': array([747., 748., 749., 750., 751., 752., 753., 754., 755., 756., 757.,\n", - " 758., 759., 760., 761.]), 'V': array([-0.19575492, -0.1957792 , -0.19572459, -0.19572974, -0.19572679,\n", - " -0.19573491, -0.19575702, -0.19570345, -0.19573155, -0.19574498,\n", - " -0.19575002, -0.19576356, -0.19576192, -0.19573549, -0.19574732]), 'tracker': array([1496., 1498., 1500., 1502., 1504., 1506., 1508., 1510., 1512.,\n", - " 1514., 1516., 1518., 1520., 1522., 1524.])}\n", - "starting control_progress\n", - "getting new current: 775, my old counter is: 762\n", - "{'repetition': array([762., 763., 764., 765., 766., 767., 768., 769., 770., 771., 772.,\n", - " 773., 774.]), 'V': array([-0.19575006, -0.19577064, -0.19575576, -0.19575593, -0.19582352,\n", - " -0.19579913, -0.19578379, -0.19580873, -0.19572289, -0.19577239,\n", - " -0.19574414, -0.19575055, -0.19575908]), 'tracker': array([1526., 1528., 1530., 1532., 1534., 1536., 1538., 1540., 1542.,\n", - " 1544., 1546., 1548., 1550.])}\n", - "starting control_progress\n", - "getting new current: 790, my old counter is: 775\n", - "{'repetition': array([775., 776., 777., 778., 779., 780., 781., 782., 783., 784., 785.,\n", - " 786., 787., 788., 789.]), 'V': array([-0.19577031, -0.19579513, -0.19578459, -0.19579254, -0.19580233,\n", - " -0.1957849 , -0.19577014, -0.19572259, -0.19576883, -0.19577356,\n", - " -0.1957497 , -0.19577976, -0.19576114, -0.19574616, -0.19573834]), 'tracker': array([1552., 1554., 1556., 1558., 1560., 1562., 1564., 1566., 1568.,\n", - " 1570., 1572., 1574., 1576., 1578., 1580.])}\n", - "starting control_progress\n", - "getting new current: 802, my old counter is: 790\n", - "{'repetition': array([790., 791., 792., 793., 794., 795., 796., 797., 798., 799., 800.,\n", - " 801.]), 'V': array([-0.19569951, -0.19573152, -0.19578028, -0.19574172, -0.19573221,\n", - " -0.19575178, -0.19575303, -0.19573751, -0.19576462, -0.19577368,\n", - " -0.19573763, -0.19573139]), 'tracker': array([1582., 1584., 1586., 1588., 1590., 1592., 1594., 1596., 1598.,\n", - " 1600., 1602., 1604.])}\n", - "starting control_progress\n", - "getting new current: 817, my old counter is: 802\n", - "{'repetition': array([802., 803., 804., 805., 806., 807., 808., 809., 810., 811., 812.,\n", - " 813., 814., 815., 816.]), 'V': array([-0.19578054, -0.19574938, -0.19575801, -0.19579019, -0.19580123,\n", - " -0.19573683, -0.19574995, -0.19576469, -0.19575247, -0.19578248,\n", - " -0.19576091, -0.19574833, -0.19576409, -0.19577764, -0.19572341]), 'tracker': array([1606., 1608., 1610., 1612., 1614., 1616., 1618., 1620., 1622.,\n", - " 1624., 1626., 1628., 1630., 1632., 1634.])}\n", - "starting control_progress\n", - "getting new current: 833, my old counter is: 817\n", - "{'repetition': array([817., 818., 819., 820., 821., 822., 823., 824., 825., 826., 827.,\n", - " 828., 829., 830., 831., 832.]), 'V': array([-0.19572835, -0.19572877, -0.19577347, -0.19574624, -0.19576412,\n", - " -0.19578243, -0.19579901, -0.19576972, -0.19577969, -0.19573486,\n", - " -0.19574424, -0.19576554, -0.19573048, -0.19575347, -0.19575029,\n", - " -0.19574359]), 'tracker': array([1636., 1638., 1640., 1642., 1644., 1646., 1648., 1650., 1652.,\n", - " 1654., 1656., 1658., 1660., 1662., 1664., 1666.])}\n", - "starting control_progress\n", - "getting new current: 845, my old counter is: 833\n", - "{'repetition': array([833., 834., 835., 836., 837., 838., 839., 840., 841., 842., 843.,\n", - " 844.]), 'V': array([-0.19576955, -0.19577743, -0.19578689, -0.19576582, -0.19571972,\n", - " -0.19573686, -0.19574375, -0.19574789, -0.1957743 , -0.19576673,\n", - " -0.19576001, -0.19578537]), 'tracker': array([1668., 1670., 1672., 1674., 1676., 1678., 1680., 1682., 1684.,\n", - " 1686., 1688., 1690.])}\n", - "starting control_progress\n", - "getting new current: 860, my old counter is: 845\n", - "{'repetition': array([845., 846., 847., 848., 849., 850., 851., 852., 853., 854., 855.,\n", - " 856., 857., 858., 859.]), 'V': array([-0.19574061, -0.19576467, -0.19577138, -0.19571992, -0.19577087,\n", - " -0.19571232, -0.19572624, -0.19572648, -0.19572273, -0.19574994,\n", - " -0.19573916, -0.19576792, -0.1957518 , -0.19575615, -0.19575886]), 'tracker': array([1692., 1694., 1696., 1698., 1700., 1702., 1704., 1706., 1708.,\n", - " 1710., 1712., 1714., 1716., 1718., 1720.])}\n", - "starting control_progress\n", - "getting new current: 875, my old counter is: 860\n", - "{'repetition': array([860., 861., 862., 863., 864., 865., 866., 867., 868., 869., 870.,\n", - " 871., 872., 873., 874.]), 'V': array([-0.19577972, -0.19576756, -0.19573357, -0.19577948, -0.19576239,\n", - " -0.1957639 , -0.19579578, -0.19577057, -0.1957942 , -0.19573407,\n", - " -0.19576558, -0.19576694, -0.19576567, -0.19579304, -0.19575641]), 'tracker': array([1722., 1724., 1726., 1728., 1730., 1732., 1734., 1736., 1738.,\n", - " 1740., 1742., 1744., 1746., 1748., 1750.])}\n", - "starting control_progress\n", - "getting new current: 888, my old counter is: 875\n", - "{'repetition': array([875., 876., 877., 878., 879., 880., 881., 882., 883., 884., 885.,\n", - " 886., 887.]), 'V': array([-0.19576488, -0.19574624, -0.19576501, -0.19576481, -0.19576766,\n", - " -0.19579041, -0.1958085 , -0.19579134, -0.19576532, -0.19576516,\n", - " -0.19571576, -0.1957683 , -0.19575299]), 'tracker': array([1752., 1754., 1756., 1758., 1760., 1762., 1764., 1766., 1768.,\n", - " 1770., 1772., 1774., 1776.])}\n", - "starting control_progress\n", - "getting new current: 903, my old counter is: 888\n", - "{'repetition': array([888., 889., 890., 891., 892., 893., 894., 895., 896., 897., 898.,\n", - " 899., 900., 901., 902.]), 'V': array([-0.19575586, -0.19574425, -0.19572629, -0.19574856, -0.19569823,\n", - " -0.19572328, -0.19578272, -0.19570395, -0.1957453 , -0.19571691,\n", - " -0.19577704, -0.19576417, -0.19569682, -0.19572679, -0.19574861]), 'tracker': array([1778., 1780., 1782., 1784., 1786., 1788., 1790., 1792., 1794.,\n", - " 1796., 1798., 1800., 1802., 1804., 1806.])}\n", - "starting control_progress\n", - "getting new current: 918, my old counter is: 903\n", - "{'repetition': array([903., 904., 905., 906., 907., 908., 909., 910., 911., 912., 913.,\n", - " 914., 915., 916., 917.]), 'V': array([-0.19571785, -0.19571098, -0.19575038, -0.1957307 , -0.19571119,\n", - " -0.1957401 , -0.19573142, -0.19572868, -0.19572092, -0.19573402,\n", - " -0.19572985, -0.19574344, -0.1957375 , -0.19572707, -0.19573357]), 'tracker': array([1808., 1810., 1812., 1814., 1816., 1818., 1820., 1822., 1824.,\n", - " 1826., 1828., 1830., 1832., 1834., 1836.])}\n", - "starting control_progress\n", - "getting new current: 933, my old counter is: 918\n", - "{'repetition': array([918., 919., 920., 921., 922., 923., 924., 925., 926., 927., 928.,\n", - " 929., 930., 931., 932.]), 'V': array([-0.19574902, -0.19575946, -0.19575174, -0.1957424 , -0.19574685,\n", - " -0.19571595, -0.19571584, -0.19571168, -0.19571777, -0.19572635,\n", - " -0.19573719, -0.19574953, -0.19570252, -0.19575522, -0.19573407]), 'tracker': array([1838., 1840., 1842., 1844., 1846., 1848., 1850., 1852., 1854.,\n", - " 1856., 1858., 1860., 1862., 1864., 1866.])}\n", - "starting control_progress\n", - "getting new current: 948, my old counter is: 933\n", - "{'repetition': array([933., 934., 935., 936., 937., 938., 939., 940., 941., 942., 943.,\n", - " 944., 945., 946., 947.]), 'V': array([-0.19576511, -0.19574578, -0.19575017, -0.19573961, -0.19575302,\n", - " -0.19577726, -0.19572788, -0.19572851, -0.19574432, -0.19573516,\n", - " -0.19571884, -0.19573231, -0.19575582, -0.19572923, -0.19571621]), 'tracker': array([1868., 1870., 1872., 1874., 1876., 1878., 1880., 1882., 1884.,\n", - " 1886., 1888., 1890., 1892., 1894., 1896.])}\n", - "starting control_progress\n", - "getting new current: 963, my old counter is: 948\n", - "{'repetition': array([948., 949., 950., 951., 952., 953., 954., 955., 956., 957., 958.,\n", - " 959., 960., 961., 962.]), 'V': array([-0.19569742, -0.19570006, -0.19572387, -0.19570903, -0.19572389,\n", - " -0.19569775, -0.19574545, -0.19571875, -0.19576602, -0.1957106 ,\n", - " -0.19573279, -0.19574556, -0.19572769, -0.19573588, -0.19574581]), 'tracker': array([1898., 1900., 1902., 1904., 1906., 1908., 1910., 1912., 1914.,\n", - " 1916., 1918., 1920., 1922., 1924., 1926.])}\n", - "starting control_progress\n", - "getting new current: 976, my old counter is: 963\n", - "{'repetition': array([963., 964., 965., 966., 967., 968., 969., 970., 971., 972., 973.,\n", - " 974., 975.]), 'V': array([-0.19574424, -0.19568617, -0.19570247, -0.19573955, -0.19573638,\n", - " -0.19571823, -0.19574532, -0.1957263 , -0.19574895, -0.19572521,\n", - " -0.19571673, -0.19572715, -0.19574023]), 'tracker': array([1928., 1930., 1932., 1934., 1936., 1938., 1940., 1942., 1944.,\n", - " 1946., 1948., 1950., 1952.])}\n", - "starting control_progress\n", - "getting new current: 991, my old counter is: 976\n", - "{'repetition': array([976., 977., 978., 979., 980., 981., 982., 983., 984., 985., 986.,\n", - " 987., 988., 989., 990.]), 'V': array([-0.1957289 , -0.19574274, -0.19574245, -0.19570962, -0.19571475,\n", - " -0.19571752, -0.19573914, -0.19572211, -0.19572253, -0.19577275,\n", - " -0.19580164, -0.19575809, -0.19576358, -0.19575454, -0.19576885]), 'tracker': array([1954., 1956., 1958., 1960., 1962., 1964., 1966., 1968., 1970.,\n", - " 1972., 1974., 1976., 1978., 1980., 1982.])}\n", - "starting control_progress\n", - "I have turned container False\n", - "I have turned container False\n", - "I have turned container False\n", - "getting new current: 1000, my old counter is: 991\n", - "{'repetition': array([991., 992., 993., 994., 995., 996., 997., 998., 999.]), 'V': array([-0.19577323, -0.19576737, -0.19578125, -0.19574994, -0.19576499,\n", - " -0.19577337, -0.1957357 , -0.1957344 , -0.19568743]), 'tracker': array([1984., 1986., 1988., 1990., 1992., 1994., 1996., 1998., 2000.])}\n", - "The measurement has finished and all of the data has been saved.\n" - ] - } - ], - "source": [ - "run_sweep(sweep, DATADIR, f'OPX test standard #{test_name_counter}', prt=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ed177287-3342-4278-adcb-b0a9194fc301", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f55edb3c-61c3-45db-9da0-b153c9b241dc", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0d720608-fe03-45a9-aab9-2e0ca0fd657c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f7691482-1c64-46b2-be03-8338d6abbaab", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c25533c9-51d2-4e2e-b1de-2338ecc4801f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "sensitive-beijing", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2021-07-27 16:40:10,426 - qm - INFO - Performing health check\n", - "2021-07-27 16:40:10,441 - qm - INFO - Health check passed\n", - "2021-07-27 16:40:10,741 - qm - INFO - Flags: \n", - "2021-07-27 16:40:10,741 - qm - INFO - Executing high level program\n", - "\n", - "saving repetition\n", - "the data that comes back has a type of: \n", - "saving V\n", - "the data that comes back has a type of: \n", - "saving tracker\n", - "the data that comes back has a type of: \n", - "[array([ 0, 1, 2, ..., 9997, 9998, 9999], dtype=int64), array([-0.19584275, -0.19584035, -0.19582246, ..., -0.19582816,\n", - " -0.19582617, -0.19583536]), array([ 3, 5, 7, ..., 19997, 19999, 20001], dtype=int64)]\n" - ] - } - ], - "source": [ - "config = QMConfig()\n", - "qmachine_mgr = QuantumMachinesManager()\n", - "qmachine = qmachine_mgr.open_qm(config.config())\n", - "job = qmachine.execute(my_qua_experiment())\n", - "res_handle = job.result_handles\n", - "res_handle.wait_for_all_values()\n", - "data=[]\n", - "print(type(res_handle))\n", - "for name, handle in res_handle:\n", - " handle.wait_for_values(0)\n", - " \n", - " print(f'saving {name}')\n", - " print(f\"the data that comes back has a type of: {type(handle.fetch_all()['value'])}\")\n", - " data.append(np.array(handle.fetch_all()['value']))\n", - "\n", - "print(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "stainless-scanner", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aggressive-expert", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "resident-option", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "gothic-australia", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "instructional-infrastructure", - "metadata": {}, - "outputs": [], - "source": [ - "def gen():\n", - " x = 0\n", - " while x < 5:\n", - " x = np.random.randint(low=0, high=6)\n", - " yield x" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "equal-dairy", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'x': 3, 'a': 5}\n", - "{'x': 0, 'a': 5}\n", - "{'x': 3, 'a': 5}\n", - "{'x': 3, 'a': 5}\n", - "{'x': 3, 'a': 5}\n", - "{'x': 2, 'a': 5}\n", - "{'x': 5, 'a': 5}\n" - ] - } - ], - "source": [ - "sweep = sweep_parameter('x', gen(), record_as(lambda: 5, 'a'))\n", - "for rep in sweep:\n", - " print(rep)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "neutral-electron", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(DataSpec(name='x', depends_on=None, type=, unit=''),\n", - " DataSpec(name='a', depends_on=['x'], type=, unit=''))" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sweep.get_data_specs()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "106144a8-ba5a-42d5-94c7-5b685e1c3f31", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{}\n", - "{}\n", - "{}\n", - "{}\n", - "{}\n" - ] - } - ], - "source": [ - "for data in Sweep(range(5)):\n", - " print(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a323ce7d-c3f2-498f-a8b4-9d729f33dab3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "3affb9a0-2508-437f-9bb6-fcd86b099b11", - "metadata": {}, - "outputs": [], - "source": [ - "def repetitions(repeat):\n", - " def inner_function(func):\n", - " return func\n", - " return inner_function, repeat" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "a70dbd3a-2d68-4678-8a09-2f2dc64fe259", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "'tuple' object is not callable", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_808/3946660972.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mrepetitions\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrepeat\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mdef\u001b[0m \u001b[0mprint_something\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext\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[0m\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mTypeError\u001b[0m: 'tuple' object is not callable" - ] - } - ], - "source": [ - "@repetitions(repeat = 1)\n", - "def print_something(text):\n", - " print(text)" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "0971c1bb-d4a4-4340-9711-69713709bc84", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "'tuple' object is not callable", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_808/1475574347.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint_something\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'this text is being printed'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m: 'tuple' object is not callable" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e52b62bb-1869-4184-a5b4-8b9d32c452f8", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 120, - "id": "d0d5e118-82cf-4abc-89a0-03f52420a75e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x is: 1 and y is: 5\n", - "9\n", - "x is: 3 and y is: 1\n", - "7\n" - ] - } - ], - "source": [ - "def f(x):\n", - " def g(y):\n", - " print(f'x is: {x} and y is: {y}')\n", - " return y + x + 3 \n", - " return g\n", - "\n", - "nf1 = f(1)\n", - "nf2 = f(3)\n", - "\n", - "print(nf1(5))\n", - "print(nf2(1))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2de500ad-50d1-484c-a190-14ff8c1b1d42", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 142, - "id": "a83d5909-8c34-4a6b-861a-90aebfb0a1b2", - "metadata": {}, - "outputs": [], - "source": [ - "def my_qua_decorator_sim(*data_specs):\n", - " def nested_1(func):\n", - " return func, *data_specs\n", - " return nested_1" - ] - }, - { - "cell_type": "code", - "execution_count": 150, - "id": "7ff7a8d4-2577-4136-9c1b-1de90a6d0ce2", - "metadata": {}, - "outputs": [], - "source": [ - "@my_qua_decorator_sim(\n", - " independent('x', type='array'),\n", - " dependent('y', depends_on=['x'], type='array'))\n", - "def calculation(mult=2, num=5):\n", - " x = [i for i in range(num)]\n", - " y = [i*mult for i in x]\n", - " return np.array(x), np.array(y)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 154, - "id": "344af6aa-a5eb-4840-b4e9-6b0508f1b5da", - "metadata": {}, - "outputs": [], - "source": [ - "def sweep_creation(recorded_function):\n", - " for i in recorded_function:\n", - " print(i)\n", - " @recording(recorded_function[1:])\n", - " def collect():\n", - " counter = 5\n", - " while counter >= 0:\n", - " counter -=1\n", - " yield recorded_function[0]()\n", - " \n", - " \n", - " return Sweep(collect())\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 171, - "id": "0cf4ed08-fede-49fd-8027-073747e46391", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "DataSpec(name='x', depends_on=None, type=, unit='')\n", - "DataSpec(name='y', depends_on=['x'], type=, unit='')\n" - ] - } - ], - "source": [ - "sweep = sweep_creation(calculation)" - ] - }, - { - "cell_type": "code", - "execution_count": 174, - "id": "032a6f34-3d14-4366-95f5-4907cfe3517c", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "unhashable type: 'DataSpec'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_18236/3880226794.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mdata\u001b[0m \u001b[1;33m=\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----> 2\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0msweep\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 3\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32mc:\\users\\msmt\\documents\\github\\labcore\\labcore\\measurement\\sweep.py\u001b[0m in \u001b[0;36m__next__\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 270\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__next__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\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 271\u001b[0m \u001b[0mret\u001b[0m \u001b[1;33m=\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--> 272\u001b[1;33m \u001b[0mnext_point\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpointer\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 273\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mproduces_record\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msweep\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpointer\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 274\u001b[0m \u001b[0mret\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnext_point\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32mc:\\users\\msmt\\documents\\github\\labcore\\labcore\\measurement\\record.py\u001b[0m in \u001b[0;36m__iter__\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 204\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__iter__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\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 205\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mval\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0miterable\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 206\u001b[1;33m \u001b[1;32myield\u001b[0m \u001b[0m_to_record\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mval\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata_specs\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 207\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 208\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32mc:\\users\\msmt\\documents\\github\\labcore\\labcore\\measurement\\record.py\u001b[0m in \u001b[0;36m_to_record\u001b[1;34m(value, data_specs)\u001b[0m\n\u001b[0;32m 184\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0ms\u001b[0m \u001b[1;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata_specs\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 185\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 186\u001b[1;33m \u001b[0mret\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\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 187\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mIndexError\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 188\u001b[0m \u001b[0mret\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mTypeError\u001b[0m: unhashable type: 'DataSpec'" - ] - } - ], - "source": [ - "data = []\n", - "for i in sweep:\n", - " data.append(i)" - ] - }, - { - "cell_type": "code", - "execution_count": 161, - "id": "55764eb2-877e-4628-a38a-d2a6198ee358", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 162, - "id": "47bde0e9-1731-4ba8-bc88-316268b2f1d5", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 163, - "id": "70a1e552-c092-4cf5-a8f4-fa1cccf71ab9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(array([0, 1, 2, 3, 4]), array([0, 2, 4, 6, 8])),\n", - " (array([0, 1, 2, 3, 4]), array([0, 2, 4, 6, 8])),\n", - " (array([0, 1, 2, 3, 4]), array([0, 2, 4, 6, 8])),\n", - " (array([0, 1, 2, 3, 4]), array([0, 2, 4, 6, 8])),\n", - " (array([0, 1, 2, 3, 4]), array([0, 2, 4, 6, 8]))]" - ] - }, - "execution_count": 163, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e369dcfe-7b9f-4bf8-ad50-aef1909d6359", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:qcodes]", - "language": "python", - "name": "conda-env-qcodes-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/prototyping/configuration.py b/prototyping/configuration.py deleted file mode 100644 index bc853fc..0000000 --- a/prototyping/configuration.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Example config for testing the OPX. - -Author: Wolfgang Pfaff -""" -import numpy as np - - -class QMConfig: - - def __init__(self): - - # define constants here - # pulse parameters - self.readout_if = int(50e6) - self.box_length = 10000 - self.box_buffer = 100 - self.box_amp = 0.4 - - - - - - def config(self): - - """ - This config file is for an OPX with a cable connecting analog output 1 straight into analog input 1 - """ - - ret = { - 'version': 1, - - # The hardware - 'controllers': { - - 'con1': { - 'type': 'opx1', - 'analog_outputs': { - 1: {'offset': 0.0}, - }, - 'analog_inputs': { - 1: {'offset': 0.0}, - }, - }, - }, - - # The logical elements - 'elements': { - - 'readout': { - - 'singleInput': { - 'port': ('con1', 1), - }, - 'intermediate_frequency': self.readout_if, - 'operations': { - 'box': 'box_pulse', - }, - - 'outputs': { - 'out1': ('con1', 1), - }, - - 'time_of_flight': 180, - 'smearing': 0, - }, - }, - - # The pulses - 'pulses': { - - 'box_pulse': { - 'operation': 'measurement', - 'length': self.box_length, - 'waveforms': { - 'single': 'box_wf' - }, - 'integration_weights': { - 'box_sin': 'box_sin', - 'box_cos': 'box_cos', - }, - 'digital_marker': 'ON', - - }, - }, - - 'waveforms': { - - 'box_wf': { - 'type': 'arbitrary', - 'samples': [0.0] * int(self.box_buffer) + \ - [self.box_amp] * \ - int(self.box_length - 2 * self.box_buffer) + \ - [0.0] * int(self.box_buffer), - }, - }, - - 'digital_waveforms': { - - 'ON': { - 'samples': [(1, 0)] - }, - }, - - 'integration_weights': { - - 'box_sin': { - 'cosine': [0.0] * int(self.box_length), - 'sine': [1.0] * int(self.box_length), - }, - - 'box_cos': { - 'cosine': [1.0] * int(self.box_length), - 'sine': [0.0] * int(self.box_length), - }, - - }, - - 'mixers': { - # 'readout_IQ_mixer': [ - # { - # 'intermediate_frequency': self.readout_if, - # 'lo_frequency': self.readout_lo_frq, - # 'correction': MixerCalibration.IQ_imbalance_correction( - # *self.readout_mixer_imbalance) - # }, - # ], - # 'qubit_IQ_mixer': [ - # { - # 'intermediate_frequency': self.qubit_if, - # 'lo_frequency': self.qubit_lo_frq, - # 'correction': MixerCalibration.IQ_imbalance_correction( - # *self.qubit_mixer_imbalance) - # }, - # ], - }, - } - - return ret - diff --git a/prototyping/configuring sweeps and lazy pointers.ipynb b/prototyping/configuring sweeps and lazy pointers.ipynb deleted file mode 100644 index f857a0c..0000000 --- a/prototyping/configuring sweeps and lazy pointers.ipynb +++ /dev/null @@ -1,463 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "11efc49c-b674-4883-bdc4-2dd725e30a27", - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "20d83ffd-7219-4523-a5fe-874058fa627f", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "59134b13-524e-4c5f-b3b4-b781d5d768d9", - "metadata": {}, - "outputs": [], - "source": [ - "from labcore.measurement import Sweep, pointer, record_as, indep, sweep_parameter, recording, dep\n", - "from labcore.measurement.sweep import AsyncRecord" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b96ded0-70fd-4313-b2ea-c4b1396d4597", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "81b67e5f-d4d5-45e0-9b91-aefb32550aa5", - "metadata": {}, - "source": [ - "## configuring sweeps" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "7bed32d6-28d9-4645-954f-4369dcac880d", - "metadata": {}, - "outputs": [], - "source": [ - "@recording('x')\n", - "def measure_random_numbers(mean=0, scale=1., nvals=1):\n", - " return np.random.normal(loc=mean, scale=scale, size=nvals)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "7efba1e7-6de1-44bc-b6b3-623b831b0be4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'a': 0, 'x': array([-1.07520604])}\n", - "{'a': 1, 'x': array([-0.98872233])}\n", - "{'a': 2, 'x': array([1.78346645])}\n" - ] - } - ], - "source": [ - "sweep = sweep_parameter('a', range(3), measure_random_numbers)\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "b591b57c-77b5-41a8-8c0d-8cfcd4e5dcc7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'a': 0, 'x': array([9.50424447])}\n", - "{'a': 1, 'x': array([9.88050777])}\n", - "{'a': 2, 'x': array([10.46168569])}\n" - ] - } - ], - "source": [ - "sweep = sweep_parameter('a', range(3), measure_random_numbers.using(mean=10))\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "edf826d1-71dc-4d2a-807e-af0df65437f6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'a': 0, 'x': array([-3.81455827])}\n", - "{'a': 1, 'x': array([-4.34206255])}\n", - "{'a': 2, 'x': array([-5.90485879])}\n" - ] - } - ], - "source": [ - "sweep = sweep_parameter('a', range(3), measure_random_numbers)\n", - "sweep.set_options(\n", - " measure_random_numbers=dict(mean=-5),\n", - ")\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "768fb3e2-578f-4fbf-a100-f08996a08994", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'a': 0, 'x': array([11.06953062])}\n", - "{'a': 1, 'x': array([8.74032162])}\n", - "{'a': 2, 'x': array([8.66727255])}\n" - ] - } - ], - "source": [ - "sweep = sweep_parameter('a', range(3), measure_random_numbers.using(mean=10))\n", - "sweep.set_options(\n", - " measure_random_numbers=dict(mean=-5),\n", - ")\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2ec1782-bd8f-4d68-a3bf-e87299a684d8", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "b7122abc-327d-4cf0-b3cb-92f11fca8f0d", - "metadata": {}, - "source": [ - "## Lazy pointer" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12c69b5b-36e2-47a3-9a71-ee1a5a9b8f29", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "8a3ede5f-9e45-430d-bb15-c9285f1095e5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "doing the first sweep:\n", - "{'x': 0}\n", - "{'x': 1}\n", - "{'x': 2}\n", - "\n", - "trying again:\n" - ] - } - ], - "source": [ - "def a_generator_function(n_vals=3):\n", - " x = 0\n", - " while x < n_vals:\n", - " yield x\n", - " x += 1\n", - "\n", - "sweep = sweep_parameter('x', a_generator_function())\n", - "\n", - "print('doing the first sweep:')\n", - "for s in sweep:\n", - " print(s)\n", - "\n", - "print('\\ntrying again:')\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 76, - "id": "c8476262-ed63-4c35-8db9-61bd418c0e25", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "doing the first sweep:\n", - "{'x': 0}\n", - "{'x': 1}\n", - "{'x': 2}\n", - "\n", - "trying again:\n", - "{'x': 0}\n", - "{'x': 1}\n", - "{'x': 2}\n" - ] - } - ], - "source": [ - "@pointer('x')\n", - "def a_generator_function(n_vals=3):\n", - " x = 0\n", - " while x < n_vals:\n", - " yield x\n", - " x += 1\n", - "\n", - "sweep = Sweep(a_generator_function)\n", - "\n", - "print('doing the first sweep:')\n", - "for s in sweep:\n", - " print(s)\n", - "\n", - "print('\\ntrying again:')\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "id": "caaecd8e-ff09-4410-9ab0-c86ac84252d8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "doing the first sweep:\n", - "{'x': 0}\n", - "{'x': 1}\n", - "{'x': 2}\n", - "{'x': 3}\n", - "{'x': 4}\n" - ] - } - ], - "source": [ - "sweep = Sweep(a_generator_function.using(n_vals=5))\n", - "\n", - "print('doing the first sweep:')\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "markdown", - "id": "f0bf7c12-dd9e-44f8-b1ae-e26b1dc747ab", - "metadata": {}, - "source": [ - "## Complicated pointer " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "545a0e45-7a8e-4918-b4a0-59a1877cda3c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a12e9bf1-c1c6-4716-ae02-4c8d7df4ba07", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "c4162cef-dfaf-4a1f-9bc3-315f36d7b6c0", - "metadata": {}, - "source": [ - "## AsyncRecord" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "e0ba58c1-8c9f-4cfc-b69a-57a4ef0be184", - "metadata": {}, - "outputs": [], - "source": [ - "import time" - ] - }, - { - "cell_type": "code", - "execution_count": 132, - "id": "faff1021-6b0a-411e-82cd-3f5657cd0c22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'interrogation_times': None}\n", - "{'interrogation_times': 1630428893.361867}\n", - "{'interrogation_times': 1630428893.562287}\n", - "{'interrogation_times': 1630428893.7699819}\n", - "{'interrogation_times': 1630428893.9668071}\n", - "{'interrogation_times': 1630428894.16322}\n", - "{'interrogation_times': 1630428894.368414}\n", - "{'interrogation_times': 1630428894.572625}\n", - "{'interrogation_times': 1630428894.7647948}\n", - "{'interrogation_times': 1630428894.965135}\n", - "{'interrogation_times': 1630428895.1685271}\n" - ] - } - ], - "source": [ - "class DummyInstrument:\n", - " \n", - " def __init__(self, intervals):\n", - " self.intervals = intervals\n", - " \n", - " def run(self):\n", - " t0 = time.time()\n", - " for i in self.intervals:\n", - " while time.time()-t0 < i:\n", - " time.sleep(0.01)\n", - " yield time.time()\n", - "\n", - "\n", - "class DelayedGatheringOfTimes(AsyncRecord):\n", - " \n", - " def setup(self, fun, *args, **kwargs):\n", - " self.communicator['time'] = time.time()\n", - " intervals = fun(*args, **kwargs)\n", - " self.communicator['instrument'] = DummyInstrument(intervals)\n", - " \n", - " def collect(self, print_data=False, **kwargs):\n", - " for data in self.communicator['instrument'].run():\n", - " if print_data:\n", - " print('data:', data)\n", - " yield data\n", - " \n", - "\n", - "@DelayedGatheringOfTimes(\n", - " indep('interrogation_times'),\n", - ")\n", - "def equally_spaced_times(wait_time=0.5, points=2):\n", - " return np.arange(points) * wait_time\n", - "\n", - "\n", - "sweep = equally_spaced_times(wait_time=0.2, points=10, collector_options={'print_data': False})\n", - "\n", - "sweep.set_options(\n", - " equally_spaced_times=dict(points=5)\n", - ")\n", - "\n", - "for s in sweep:\n", - " print(s)" - ] - }, - { - "cell_type": "code", - "execution_count": 137, - "id": "16ff2035-2edb-45fd-b2cc-8e0cb969e241", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'points': 1, 'interrogation_times': None}\n", - "{'points': 1, 'interrogation_times': 1630429139.222152}\n", - "{'points': 2, 'interrogation_times': None}\n", - "{'points': 2, 'interrogation_times': 1630429139.2222779}\n", - "{'points': 2, 'interrogation_times': 1630429139.426282}\n", - "{'points': 3, 'interrogation_times': None}\n", - "{'points': 3, 'interrogation_times': 1630429139.427327}\n", - "{'points': 3, 'interrogation_times': 1630429139.631752}\n", - "{'points': 3, 'interrogation_times': 1630429139.8366048}\n", - "{'points': 4, 'interrogation_times': None}\n", - "{'points': 4, 'interrogation_times': 1630429139.837495}\n", - "{'points': 4, 'interrogation_times': 1630429140.047543}\n", - "{'points': 4, 'interrogation_times': 1630429140.24167}\n", - "{'points': 4, 'interrogation_times': 1630429140.441689}\n" - ] - } - ], - "source": [ - "outer_sweep = sweep_parameter('points', range(1,5))\n", - "inner_sweep = equally_spaced_times(wait_time=0.2)\n", - "combined_sweep = outer_sweep @ inner_sweep\n", - "\n", - "for data in combined_sweep:\n", - " print(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a992dba-63e8-4739-ab2b-6f8ad55ce8b0", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:msmt-pyqt5]", - "language": "python", - "name": "conda-env-msmt-pyqt5-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/prototyping/structured_OPX_implementation.ipynb b/prototyping/structured_OPX_implementation.ipynb deleted file mode 100644 index e40776a..0000000 --- a/prototyping/structured_OPX_implementation.ipynb +++ /dev/null @@ -1,772 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cb9a503a-e1fb-4a0d-a1e9-523e3de4bb03", - "metadata": {}, - "source": [ - "# Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "fb34ddaf-6790-428a-804d-24da83e0ea9b", - "metadata": {}, - "outputs": [], - "source": [ - "from functools import wraps\n", - "\n", - "import numpy as np\n", - "import qcodes as qc\n", - "\n", - "from itertools import chain\n", - "\n", - "from typing import Callable, Dict, Generator, List\n", - "\n", - "from labcore.measurement import *\n", - "\n", - "from configuration import QMConfig\n", - "from qm.qua import *\n", - "from qm.QuantumMachinesManager import QuantumMachinesManager\n", - "\n", - "from plottr.data import datadict_storage as dds, datadict as dd\n", - "\n", - "# global module variable for the config file\n", - "global_config = None" - ] - }, - { - "cell_type": "markdown", - "id": "f0351e21-a232-4472-8f33-b57452eaf8f8", - "metadata": {}, - "source": [ - "# Base Decorator" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "2d336f11-0893-4ce0-8cff-1be3bfd0847b", - "metadata": {}, - "outputs": [], - "source": [ - "class BackgroundRecordingBase:\n", - " \"\"\"\n", - " Base class decorator used to record asynchronous data from instrument.\n", - " Use the decorator with create_background_sweep function to create Sweeps that collect asynchronous data from\n", - " external devices running experiments independently of the measurement PC,\n", - " e.i. the measuring happening is not being controlled by a Sweep but instead an external device (e.g. the OPX).\n", - " Each instrument should have its own custom setup_wrapper (see setup_wrapper docstring for more info),\n", - " and a custom collector.\n", - " Auxiliary functions for the start_wrapper and collector should also be located in this class.\n", - "\n", - " :param *specs: A list of the DataSpecs to record the data produced.\n", - " \"\"\"\n", - "\n", - " def __init__(self, *specs):\n", - " self.specs = specs\n", - " self.communicator = {}\n", - "\n", - " def __call__(self, fun) -> Callable:\n", - " \"\"\"\n", - " When the decorator is called the experiment function gets wrapped so that it returns an Sweep object composed\n", - " of 2 different Sweeps, the setup sweep and the collector Sweep.\n", - " \"\"\"\n", - "\n", - " def sweep(**collector_kwargs) -> Sweep:\n", - " \"\"\"\n", - " Returns a Sweep comprised of 2 different Sweeps: start_sweep and collector_sweep.\n", - " start_sweep should perform any setup actions as well as starting the actual experiment. This sweep is only\n", - " executed once. collector_sweep is iterated multiple time to collect all the data generated from the\n", - " instrument.\n", - "\n", - " :param collector_kwargs: Any arguments that the collector needs.\n", - " \"\"\"\n", - "\n", - " start_sweep = once(self.start_wrapper(fun))\n", - " collector_sweep = Sweep(record_as(self.collector(**collector_kwargs), *self.specs))\n", - " return start_sweep + collector_sweep\n", - "\n", - " return sweep\n", - "\n", - " def start_wrapper(self, fun: Callable) -> Callable:\n", - " \"\"\"\n", - " Wraps the start function. setup_wrapper should consist of another function inside of it decorated with @wraps\n", - " with fun as its argument.\n", - " In this case the wrapped function is setup.\n", - " Setup should accept the *args and **kwargs of fun. It should also place any returns from fun in the communicator.\n", - " setup_wrapper needs to return the wrapped function (setup)\n", - "\n", - " :param fun: The measurement function. In the case of the OPX this would be the function that returns the QUA\n", - " code with any arguments that it might use.\n", - " \"\"\"\n", - "\n", - " @wraps(fun)\n", - " def start(*args, **kwargs) -> None:\n", - " \"\"\"\n", - " Starts the experiment and saves anything that the collector needs from the startup of the measurement in the\n", - " collector dictionary.\n", - "\n", - " :param args: Any args that fun needs.\n", - " :param kwargs: Any kwargs that fun needs.\n", - " \"\"\"\n", - " self.communicator['setup_return'] = fun(*args, **kwargs)\n", - " return None\n", - "\n", - " return start\n", - "\n", - " def collector(self, **kwargs) -> Generator[Dict, None, None]:\n", - " \"\"\"\n", - " Data collection generator. The generator should contain all the logic of waiting for the asynchronous data.\n", - " Its should yield a dictionary with the name of the of the DataSpecs as keywords and numpy arrays with the values\n", - " collected from the instrument. The generator should exhaust itself once all the data produced by the\n", - " measurement has been generated\n", - "\n", - " :param kwargs: Any kwargs necessary for the specific implementation of the collector.\n", - " \"\"\"\n", - " data = {}\n", - " yield data\n", - "\n", - "\n", - "def create_background_sweep(decorated_measurement_function: Callable, **collector_kwargs) -> Sweep:\n", - " \"\"\"\n", - " Creates the Sweep object from a measurement function decorated with any implementation of BackgroundRecordingBase.\n", - "\n", - " :param decorated_measurement_function: Measurement function decorated with\n", - " a BackgroundRecordingBase class decorator.\n", - " :param collector_kwargs: Any kwargs that the collector needs.\n", - " \"\"\"\n", - " sweep = decorated_measurement_function(**collector_kwargs)\n", - " return sweep\n" - ] - }, - { - "cell_type": "markdown", - "id": "95c79e0a-53f4-48f0-bcfd-d3b4941991ab", - "metadata": {}, - "source": [ - "# Raw Value DataSpec" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b3256633-08d4-4288-a851-c8fd9432ea60", - "metadata": {}, - "outputs": [], - "source": [ - "def raw_OPX_data(name: str, depends_on: List[str] = [], unit: str = '', type_var: str = 'array'):\n", - " indep_name = f'{name}_time'\n", - " indep = independent(indep_name, unit, type_var)\n", - " depends_on.append(indep_name)\n", - " dep = dependent(name,depends_on, unit, type_var)\n", - " ret = indep, dep, name\n", - "\n", - " return ret\n", - " " - ] - }, - { - "cell_type": "markdown", - "id": "c289479a-e039-413e-b444-d28a8404aea6", - "metadata": {}, - "source": [ - "# Specific OPX Implementation" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "4d12303c-bdc6-4265-a254-8267ff6e8a5d", - "metadata": {}, - "outputs": [], - "source": [ - "class RecordOPX(BackgroundRecordingBase):\n", - " \"\"\"\n", - " Implementation of BackgroundRecordingBase for use with the OPX machine.\n", - " \"\"\"\n", - "\n", - " def __init__(self, *specs):\n", - " self.communicator = {}\n", - " self.communicator['raw_variables'] = []\n", - " self.specs = tuple(self._flatten_and_sort_dataspecs(specs))\n", - " \n", - " def _flatten_and_sort_dataspecs(self, specs):\n", - " ret = []\n", - " if isinstance(specs,tuple):\n", - " for spec in specs:\n", - " if isinstance(spec, record.DataSpec):\n", - " ret.append(spec)\n", - " elif isinstance(spec, str):\n", - " self.communicator['raw_variables'].append(spec)\n", - " elif len(spec) > 1:\n", - " rec_list = self._flatten_and_sort_dataspecs(spec)\n", - " if len(rec_list) > 1:\n", - " for item in rec_list:\n", - " ret.append(item)\n", - " else:\n", - " ret.append(item)\n", - "\n", - " return ret\n", - "\n", - " def start_wrapper(self, fun: Callable) -> Callable:\n", - " \"\"\"\n", - " start_wrapper for the OPX machine. Wraps the startup function.\n", - " Returns the actual startup function to be executed when the sweep is iterated through.\n", - "\n", - " :param fun: Function that returns the QUA program.\n", - " \"\"\"\n", - "\n", - " @wraps(fun)\n", - " def startup(*args, **kwargs) -> None:\n", - " \"\"\"\n", - " Establishes connection with the OPX and starts the the measurement. The config of the OPX is passed through\n", - " the module variable global_config. It saves the result handles and saves initial values to the communicator\n", - " dictionary.\n", - " \"\"\"\n", - " # Start the measurement in the OPX.\n", - " qmachine_mgr = QuantumMachinesManager()\n", - " qmachine = qmachine_mgr.open_qm(global_config)\n", - " job = qmachine.execute(fun(*args, **kwargs))\n", - " result_handles = job.result_handles\n", - "\n", - " # Save the result handle and create initial parameters in the communicator used in the collector.\n", - " self.communicator['result_handles'] = result_handles\n", - " self.communicator['active'] = True\n", - " self.communicator['counter'] = 0\n", - "\n", - " return startup\n", - "\n", - " def _wait_for_data(self, batchsize: int) -> None:\n", - " \"\"\"\n", - " Waits for the opx to have measured more data points than the ones indicated in the batchsize. Also checks that\n", - " the OPX is still collecting data, when the OPX is no longer processing, turn communicator['active'] to False to\n", - " exhaust the collector.\n", - "\n", - " :param batchsize: Size of batch. How many data-points is the minimum for the sweep to get in an iteration.\n", - " e.g. if 5, _control_progress will keep running until at least 5 new data-points\n", - " are available for collection.\n", - " \"\"\"\n", - "\n", - " # When ready becomes True, the infinite loop stops.\n", - " ready = False\n", - "\n", - " # Collect necessary values from communicator.\n", - " res_handle = self.communicator['result_handles']\n", - " counter = self.communicator['counter']\n", - "\n", - " while not ready:\n", - " for name, handle in res_handle:\n", - " \n", - " current_datapoint = handle.count_so_far()\n", - " \n", - " # Check if the OPX is still processing.\n", - " if res_handle.is_processing():\n", - " # Check if enough data-points are available.\n", - " if current_datapoint - counter >= batchsize:\n", - " ready = True\n", - " else:\n", - " ready = False\n", - " else:\n", - " # Once the OPX is done processing turn ready True and turn active False to exhaust the generator.\n", - " ready = True\n", - " self.communicator['active'] = False\n", - "\n", - "\n", - " def collector(self, batchsize: int) -> Generator[Dict, None, None]:\n", - " \"\"\"\n", - " Implementation of collector for the OPX. Collects new data-points from the OPX and yields them in a dictionary\n", - " with the names of the recorded variables as keywords and numpy arrays with the values. Raises ValueError if a\n", - " stream name inside the QUA program has a different name than a recorded variable and if the amount of recorded\n", - " variables and streams are different.\n", - "\n", - " :param batchsize: Size of batch. How many data-points is the minimum for the sweep to get in an iteration.\n", - " e.g. if 5, _control_progress will keep running until at least 5 new data-points\n", - " are available for collection.\n", - " \"\"\"\n", - "\n", - " # Get the result_handles from the communicator.\n", - " result_handle = self.communicator['result_handles']\n", - "\n", - " # Get the names of all variables from the specs.\n", - " data_specs_names = []\n", - " raw_values_count = len(self.communicator['raw_variables'])\n", - "\n", - " data_specs_names = [x.name for x in self.specs]\n", - " variable_counter = 0\n", - " for name, handle in result_handle:\n", - "\n", - " # Check that the stream names are present in the DataSpecs.\n", - " if name not in data_specs_names:\n", - " raise ValueError(f'{name} is not a recorded variable')\n", - " else:\n", - " variable_counter += 1\n", - " \n", - "\n", - " # Check that the number of recorded variables and streams are the same.\n", - " if variable_counter != len(data_specs_names) - raw_values_count:\n", - " raise ValueError(f'Number of recorded variables ({variable_counter}) \\\n", - " does not match number of variables gathered from the OPX ({len(data_specs_names)})')\n", - " \n", - "\n", - " while self.communicator['active']:\n", - " # Restart values for each iteration.\n", - " data = {}\n", - " counter = self.communicator['counter'] # Previous iteration data-point number.\n", - " first = True\n", - " current = 0\n", - " \n", - " # Make sure that the result_handle is active.\n", - " if result_handle is None:\n", - " yield None\n", - "\n", - " # Waits until new data-points are ready to be gathered.\n", - " self._wait_for_data(batchsize)\n", - "\n", - " for name, handle in result_handle:\n", - "\n", - " # To ensure that we get the same number of data-points from every variable only get the current count\n", - " # for the first variable in the stream.\n", - " if first:\n", - " current = handle.count_so_far() # Current data-point number\n", - " first = False\n", - "\n", - " # if the current data-point number is the same as the previous data-point number, no new data\n", - " # has been gathered.\n", - " if current == counter:\n", - " yield None\n", - " break\n", - " \n", - " # Make sure that the OPX has actually measured the current value for all variables and fetch the\n", - " # new data lines.\n", - " handle.wait_for_values(current)\n", - " data_temp = np.array(handle.fetch(slice(counter, current)))\n", - "\n", - " # If the trace is a raw measurement, we need to go through its shape to properly convert it\n", - " if name in self.communicator['raw_variables']:\n", - " holding_converting = []\n", - " for i in data_temp:\n", - " i_holder = []\n", - " for j in i:\n", - " converted = j.astype(float)\n", - " i_holder.append(converted)\n", - " holding_converting.append(i_holder) \n", - " if len(holding_converting) == 1:\n", - " converted_data_temp = [np.squeeze(holding_converting)]\n", - " else:\n", - " converted_data_temp = np.squeeze(holding_converting)\n", - " \n", - " raw_count = []\n", - " for rep in range(len(data['repetition'])):\n", - " raw_count.append(np.arange(len(converted_data_temp[0])))\n", - " data[f'{name}_time'] = raw_count\n", - "\n", - " \n", - " else:\n", - " # data comes from the OPX as numpy.void. Converts array to contain floats instead.\n", - " converted_data_temp = data_temp.astype(float)\n", - " \n", - " \n", - " data[name] = converted_data_temp\n", - " self.communicator['counter'] = current\n", - " yield data" - ] - }, - { - "cell_type": "markdown", - "id": "318494f6-1514-496a-9bc6-3a3b561fdca7", - "metadata": {}, - "source": [ - "# Proposal for Base Running and Saving" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "0957ae77-3012-4d56-bc6a-5170b68fb764", - "metadata": {}, - "outputs": [], - "source": [ - "def _create_datadict_structure(sweep: Sweep) -> dd.DataDict:\n", - " \"\"\"\n", - " Returns a structured DataDict from the DataSpecs of a Sweep.\n", - "\n", - " :param sweep: Sweep object from which the DataDict is created.\n", - " \"\"\"\n", - "\n", - " data_specs = sweep.get_data_specs()\n", - " data_dict = dd.DataDict()\n", - " for spec in data_specs:\n", - "\n", - " depends_on = spec.depends_on\n", - " unit = spec.unit\n", - " name = spec.name\n", - "\n", - " # Checks which fields have information and which ones are None.\n", - " if depends_on is None:\n", - " if unit is None:\n", - " data_dict[name] = dict()\n", - " else:\n", - " data_dict[name] = dict(unit=unit)\n", - " else:\n", - " if unit is None:\n", - " data_dict[name] = dict(axes=depends_on)\n", - " else:\n", - " data_dict[name] = dict(axes=depends_on, unit=unit)\n", - "\n", - " data_dict.validate()\n", - "\n", - " return data_dict\n", - "\n", - "\n", - "def _check_none(line: Dict) -> bool:\n", - " \"\"\"\n", - " Checks if the values in a Dict are all None. Returns True if all values are None, False otherwise.\n", - " \"\"\"\n", - " for arg in line.keys():\n", - " if line[arg] is not None:\n", - " return False\n", - " return True\n", - "\n", - "\n", - "def run_and_save_sweep(sweep: Sweep, data_dir: str, name: str, prt: bool = False) -> None:\n", - " \"\"\"\n", - " Iterates through a sweep, saving the data coming through it into a file called at directory.\n", - "\n", - " :param sweep: Sweep object to iterate through.\n", - " :param data_dir: Directory of file location\n", - " :param name: name of the file\n", - " :param prt: Bool, if True, the function will print every result coming from the sweep. Default, False.\n", - " \"\"\"\n", - " data_dict = _create_datadict_structure(sweep)\n", - " \n", - " # Creates a file even when it fails.\n", - " with dds.DDH5Writer(data_dir, data_dict, name=name) as writer:\n", - " for line in sweep:\n", - " if not _check_none(line):\n", - " if prt:\n", - " print(line)\n", - "\n", - " writer.add_data(**line)\n", - "\n", - " print('The measurement has finished successfully and all of the data has been saved.')" - ] - }, - { - "cell_type": "markdown", - "id": "15453070-6d9b-422a-abe1-b7769abc4d29", - "metadata": {}, - "source": [ - "# QUA experiment with implemented decorator" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "1f24ded1-bd2a-4891-aa7e-85d76d25efc8", - "metadata": {}, - "outputs": [], - "source": [ - "@RecordOPX(\n", - " dependent('tracker', depends_on=['repetition'], type='array'),\n", - " independent('repetition', type='array'),\n", - " raw_OPX_data('data_raw', depends_on=['repetition'], type_var='array'),\n", - " dependent('V', depends_on=['repetition'], type='array'))\n", - "def my_qua_experiment(n_reps=1000):\n", - " with program() as qua_measurement:\n", - " raw_stream = declare_stream(adc_trace=True)\n", - " v_stream = declare_stream()\n", - " tracker_stream = declare_stream()\n", - " i_stream = declare_stream()\n", - "\n", - " i = declare(int)\n", - " v = declare(fixed)\n", - " tracker = declare(int, value=0)\n", - "\n", - " with for_(i, 0, i