diff --git a/.gitignore b/.gitignore index 788f634..90e13ad 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,9 @@ dmypy.json # pycharm project file .idea -reports \ No newline at end of file +reports + +# DB files +*.fs* +*.db +.vscode/ diff --git a/docs/1_qpu_db.ipynb b/docs/1_qpu_db.ipynb new file mode 100644 index 0000000..05058fe --- /dev/null +++ b/docs/1_qpu_db.ipynb @@ -0,0 +1,799 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Tutorial: QPU Database\n", + "\n", + "**This tutorial requires version >=0.0.5 of the QPU DB**\n", + "\n", + "## Using the QPU DB\n", + "\n", + "The QPU database is a permanent store built for storing calibration data for Quantum Processing Units (QPU).\n", + "\n", + "It provides the following features and benefits:\n", + "\n", + "* Persistent storage of any python object related to QPU calibration info\n", + "* Metadata on parameter calibration state and last modified time\n", + "* Convenient addressing of quantum elements\n", + "* Easy revert to previously stored parameters\n", + "\n", + "In this short tutorial we will learn how to use the QPU DB by looking at a simplified example of a QPU with two superconducting\n", + "qubits, two readout resonators and a parametric coupling element.\n", + "\n", + "### Creating the database\n", + "\n", + "Below we can see a simple usage example. The DB is created by calling the `create_new_database` method.\n", + "This method is similar to initializing a git repo in the sense that we only do it once. Here we initialize it\n", + "with an initial dictionary which contains some basic attributes of our QPU. We'll be able to add more attributes,\n", + "and also elements, later on. Once we call `create_new_qpu_database`, a set of database files will be created for us at\n", + "the working directory of the python script.\n", + "\n", + "These files are the persistent storage of our DB. They can be saved to a different location by specifying\n", + "the `path` argument to the function." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# %load_ext autoreload\n", + "# %autoreload 2\n", + "from entropylab_qpudb import create_new_qpu_database, CalState, QpuDatabaseConnection" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "initial_dict = {\n", + " 'q1': {\n", + " 'f01': 5.65e9 # an initial guess for our transition frequency\n", + " },\n", + " 'q2': {\n", + " 'f01': 5.25e9\n", + " },\n", + " 'res1': {\n", + " 'f_r': 7.1e9\n", + " },\n", + " 'res2': {\n", + " 'f_r': 7.3e9\n", + " },\n", + " 'c1_2': {\n", + " 'f_r': 0.4e9\n", + " }\n", + "}\n", + "create_new_qpu_database('db1', initial_dict, force_create=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Notes:\n", + "\n", + "1. here we allow for the possibility of overwriting an existing database\n", + "by passing the `force_create=True` flag. This option is useful when experimenting with the database creation, however in\n", + "common usage it is recommended to remove this flag, since when it's false (by default), it will prevent overwriting an existing\n", + "database and deleting all the data stored in it.\n", + "\n", + "2. (For experts): if you need to create a DB server, rather than create a filesystem storage, please let us know.\n", + "The DB backend is currently\n", + "the [ZODB](https://zodb.org/en/latest/) database, with plans to be replaced by\n", + "[gitdb](https://github.com/gitpython-developers/gitdb).\n", + "\n", + "The keys of `initial_dict` are called the *elements* (and are similar in nature to QUA's quantum elements), and the\n", + "values of these elements are subdictionaries of *attributes*. The values of the attributes can be anything you like,\n", + "or more accurately, any python object that can be pickled. The different elements need not have the same attributes.\n", + "\n", + "### Connecting to the database and basic usage\n", + "\n", + "Now create a connection to our DB. The connection to the DB is our main \"workhorse\" - we create the DB once, and\n", + "whenever we want to connect to it in order to retrieve or store data, we open a connection object. Note that currently\n", + "only a single connection object per DB is allowed." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "opening qpu database db1 from commit at index 0\n" + ] + } + ], + "source": [ + "db1 = QpuDatabaseConnection('db1')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "and let's view the contents of our DB by calling `print`:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "q1\n", + "----\n", + "f01:\tQpuParameter(value=5400000000.0, last updated: 05/30/2021 09:24:45, calibration state: COARSE)\n", + "\n", + "q2\n", + "----\n", + "f01:\tQpuParameter(value=5250000000.0, last updated: 05/30/2021 09:24:19, calibration state: UNCAL)\n", + "\n", + "res1\n", + "----\n", + "f_r:\tQpuParameter(value=7100000000.0, last updated: 05/30/2021 09:24:19, calibration state: UNCAL)\n", + "\n", + "res2\n", + "----\n", + "f_r:\tQpuParameter(value=7300000000.0, last updated: 05/30/2021 09:24:19, calibration state: UNCAL)\n", + "\n", + "c1_2\n", + "----\n", + "f_r:\tQpuParameter(value=400000000.0, last updated: 05/30/2021 09:24:19, calibration state: UNCAL)\n" + ] + } + ], + "source": [ + "db1.print()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Congratulations! You've just created your first QPU DB. As you can see when calling `print` the values we entered\n", + "in `initial_dict` are now objects of type `QpuParameter`. These objects have 3 attributes:\n", + "\n", + "* `value`: the value you created initially and can be any python object\n", + "* `last_updated`: the time when this parameter was last updated (see *committing* section to understand how to\n", + "update). This parameter is handled by the DB itself.\n", + "* `cal_state`: an enumerated metadata that can take the values `UNCAL`, `COARSE`, `MED` and `FINE`. This\n", + "can be used by the user to communicate what is the calibration level of these parameters. They can be set and queried\n", + "during the script execution, but are not used by the DB itself.\n", + "\n", + "### Modifying and using QPU parameters\n", + "\n", + "We can use and modify values and calibration states of QPU parameters in two different ways:\n", + "\n", + "#### Using `get` and `set`\n", + "\n", + "let's modify the value of `f01` and then get the actual value:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5330000000.0" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db1.set('q1', 'f01', 5.33e9)\n", + "db1.get('q1', 'f01').value" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "We can also modify the calibration state when setting:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "db1.set('q1', 'f01', 5.36e9, CalState.COARSE)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "To get the full `QpuParameter` object we can omit `.value`. We can see that the cal state and modification date were updated." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "QpuParameter(value=5360000000.0, last updated: 05/30/2021 09:24:35, calibration state: COARSE)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db1.get('q1', 'f01')\n", + "#db1.get('q1', 'f01').cal_state" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Note that we can't modify the value by assigning to value directly - this will raise an exception." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Using resolved names\n", + "\n", + "The names we chose for the elements, namely `'q1'`, `'res1'` and `'c1_2'` have a special significance. If we follow this\n", + "convention of naming qubit elements with the format 'q'+number, resonators with the format 'res'+number\n", + "and couplers with the format 'c'+number1+'_'+number2, as shown above, this allows us to get and set values in a more\n", + "convenient way:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5360000000.0\n", + "7100000000.0\n", + "400000000.0\n", + "400000000.0\n" + ] + } + ], + "source": [ + "print(db1.q(1).f01.value)\n", + "print(db1.res(1).f_r.value)\n", + "print(db1.coupler(1, 2).f_r.value)\n", + "print(db1.coupler(2, 1).f_r.value)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "while this method basically syntactic sugar, it allows us to conveniently address elements by indices, which is useful when\n", + "working with multiple qubit systems, and especially with couplers. We can also set values using this resolved addressing method:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "QpuParameter(value=5400000000.0, last updated: 05/30/2021 09:24:45, calibration state: COARSE)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db1.update_q(1, 'f01', 5.4e9)\n", + "db1.q(1).f01" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Note: This default mapping between integer indices and strings can be modified by subclassing the\n", + "`Resolver` class found under `entropylab_qpudb._resolver.py`.\n", + "\n", + "### Committing (saving to persistent storage) and viewing history\n", + "\n", + "Everything we've done so far did not modify the persistent storage. In order to do this, we need to *commit* the changes we made.\n", + "This allows us to control at which stages we want to make aggregated changes to the database.\n", + "\n", + "Let's see how this is done. We need to call `commit`, and specify an optional commit message:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "commiting qpu database db1 with commit at index 1\n" + ] + } + ], + "source": [ + "db1.update_q(1, 'f01', 6.e9)\n", + "db1.commit('a test commit')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now the actual file was changed. To see this, we need to close the db. We can then delete db1,\n", + "and when re-opening the DB we'll see f01 of q1 has the modified value." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closing qpu database db1\n", + "closing qpu database db1\n", + "opening qpu database db1 from commit at index 1\n" + ] + }, + { + "data": { + "text/plain": [ + "QpuParameter(value=6000000000.0, last updated: 05/27/2021 09:44:34, calibration state: COARSE)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closing qpu database db1\n", + "closing qpu database db1\n", + "opening qpu database db1 from commit at index 1\n" + ] + }, + { + "data": { + "text/plain": [ + "QpuParameter(value=6000000000.0, last updated: 05/27/2021 09:44:34, calibration state: COARSE)" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db1.close()\n", + "del db1\n", + "db1 = QpuDatabaseConnection('db1')\n", + "db1.q(1).f01" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Note that the commit was saved with an index. This index can be later used to revert to a [previous state](#reverting-to-a-previous-state).\n", + "\n", + "To view a history of all the commits, we call `get_history`.\n", + "\n", + "Note that the timestamps of the commits are in UTC time." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
timestampmessage
02021-05-30 06:24:19.796403initial commit
12021-05-30 06:26:20.205781a test commit
\n", + "
" + ], + "text/plain": [ + " timestamp message\n", + "0 2021-05-30 06:24:19.796403 initial commit\n", + "1 2021-05-30 06:26:20.205781 a test commit" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db1.get_history()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adding attributes and elements\n", + "\n", + "In many cases you realize while calibrating your system that you want to add attributes that did not exist in the initial\n", + "dictionary, or even new elements. This is easy using the `add_element` and `add_attribute` methods.\n", + "Let's see an example for `add_attribute`:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "QpuParameter(None)\n", + "QpuParameter(value=-300000000.0, last updated: 05/30/2021 09:26:25, calibration state: COARSE)\n" + ] + } + ], + "source": [ + "db1.add_attribute('q1', 'anharmonicity')\n", + "\n", + "print(db1.q(1).anharmonicity)\n", + "\n", + "db1.update_q(1, 'anharmonicity', -300e6, new_cal_state=CalState.COARSE)\n", + "\n", + "print(db1.q(1).anharmonicity)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### Reverting to a previous state\n", + "\n", + "Many times when we work on bringing up a QPU, we reach a point where everything is calibrated properly and our measurements\n", + "and calibrations give good results. We want to be able to make additional changes, but to possibly revert to the good state\n", + "if things go wrong. We can do this using `restore_from_history`. We simply need to provide it with the history\n", + "index to which we want to return:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "opening qpu database db1 from commit at index 0\n", + "QpuParameter(value=5650000000.0, last updated: 05/30/2021 09:24:19, calibration state: UNCAL)\n" + ] + } + ], + "source": [ + "db1.restore_from_history(0)\n", + "print(db1.q(1).f01)\n", + "assert db1.q(1).f01.value == initial_dict['q1']['f01']\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Calling this method will replace the current working DB with the DB that was stored in the commit with the index\n", + "supplied to `restore_from_history`. The new values will not be committed. It is possible to modify the values and\n", + "commit them as usual.\n", + "\n", + "## Next steps\n", + "\n", + "While the QPU DB is a standalone tool, it is designed with QUA calibration node framework in mind.\n", + "In the notebook called `2_qubit_graph_calibration.ipynb` we explore how the QUA calibration nodes framework can be used\n", + "to generate calibration graphs.\n", + "\n", + "## Remove DB files\n", + "\n", + "To remove the DB files created in your workspace for the purpose of this demonstration, first close the db connection:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closing qpu database db1\n" + ] + } + ], + "source": [ + "db1.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "then run this cell:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from glob import glob\n", + "import os\n", + "for fl in glob(\"db1*\"):\n", + " os.remove(fl)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Entropy", + "language": "python", + "name": "entropy" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/2_qubit_graph_calibration.ipynb b/docs/2_qubit_graph_calibration.ipynb new file mode 100644 index 0000000..6ad8931 --- /dev/null +++ b/docs/2_qubit_graph_calibration.ipynb @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Tutorial: QUA calibration nodes on a graph\n", + "\n", + "## Motivation and background\n", + "\n", + "This tutorial explains, with a simple toy example, how to run calibrations on a graph using the *QUA Calibration node*\n", + "framework. It is based on the code in [hello QUA](https://github.com/qua-platform/qua-libs/tree/main/examples/basics/hello-qua)\n", + "in the [qua-libs](https://github.com/qua-platform/qua-libs) project.\n", + "\n", + "The basic idea of the QUA calibration nodes is to allow the quantum engineer or experimentalist to build up the\n", + "quantum machine config required to run an accurate and updated QUA program in an iterative way and to break it down\n", + "to a manageable series of small and self-contained steps, each within a `QuaCalNode`.\n", + "\n", + "The problem that `QuaCalNodes`solves is the following: in multiple qubit systems the QUA config becomes a large object\n", + "that is full of many parameters. These include IF frequencies, waveform samples and mixer correction entries that need to\n", + "be kept up do date and well-calibrated. Furthermore, when tuning up the system, we're often in experimentation mode\n", + "so we need a framework that is modular and flexible. `QuaCalNodes`, together with the QPU DB, provide a way to build\n", + "the QUA config and modify them both automatically and as well as manually when needed.\n", + "\n", + "## Prerequisites and assumptions\n", + "\n", + "This tutorial assumes a working knowledge of QUA (see [qua-libs](https://github.com/qua-platform/qua-libs))\n", + "and familiarity with the QPU DB\n", + "(see [section 1](https://github.com/entropy-lab/entropy-qpu/blob/main/docs/qpu_db.ipynb) of the tutorial).\n", + "\n", + "## QUA Calibration nodes\n", + "\n", + "### Basic idea\n", + "\n", + "![linear](linear.png)\n", + "\n", + "QUA cal nodes are `PyNodes` which pass the QUA config as input and output. Each QUA cal node performs a calibration\n", + "measurement and modifies the QUA config with only the values that were calibrated in this node. If needed, the node\n", + "can also add operations and pulses that are required to perform the calibration. This can be seen in the figure above:\n", + "We start with a root node, which is a `PyNode` that only passes a bare-bones QUA config (QUA config v0).\n", + "The next node, time of flight calibration, is a `QuaCalNode` that measures the time of flight to a readout resonator.\n", + "It updates QPU DB by that value and also modifies the QUA config (becoming QUA config v1), which is passed on to the next node.\n", + "The next node does the same, and in this manner the config is built up with fresh calibration values, which are concurrently\n", + "stored in the QPU DB.\n", + "\n", + "### Anatomy of a single QUA node\n", + "\n", + "![anatomy](singlenode.png)\n", + "\n", + "Let's look more closely at a single `QuaCalNode`, here time of flight calibration. The node contains 3 methods which\n", + "are performed sequentially when running the graph:\n", + "\n", + "* `prepare_config()`\n", + "* `run()`\n", + "* `update_config()`\n", + "\n", + "The first method, `prepare_config()`, is always executed and can be used to add to the config things required for the\n", + "calibration, such as readout pulses, integration weights etc.\n", + "\n", + "The second method, `run()`, performs the actual QUA program and the analysis, and modifies the QPU DB accordingly.\n", + "\n", + "The third method, `update_config()`, takes the values from the QPU DB and updates the QUA config accordingly. For\n", + "example, here we'd modify the `time_of_flight` field of the resonator.\n", + "\n", + "You may be wondering why `run()` is optional and why we don't just immediately modify TOF in the QUA config. The reason\n", + "is that we may want to build up the config without actually running the calibration - for example if decide that TOF\n", + "is sufficiently well calibrated and doesn't need to be re-measured. The decision whether to run a node in a graph or\n", + "not is determined by the `run_strategy` argument of the entropy `Graph` object.\n", + "\n", + "### Merging configs\n", + "\n", + "![merge](merge.png)\n", + "\n", + "in many cases, a node can depend on more than one ancestor. In this case, the configs will be automatically merged,\n", + "as seen above.\n", + "\n", + "NOTE: If ancestor nodes modify the same config fields, the stored value will be **undetermined**!\n", + "Please avoid this situation.\n", + "\n", + "### A note on flexibility\n", + "\n", + "The framework of QPU DB and `QuaCalNodes` is very flexibly by design. If you want, you can add arguments\n", + "to the class `__init__` method to define run parameters, or, for example, create the full config in the root node\n", + "and only modify its values, thus skipping `prepare_config()` entirely.\n", + "\n", + "The recommended way of using QUA cal nodes, and the config, is as shown here in the tutorial but we really value any\n", + "feedback and modification suggestions!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## initializing a QPU DB and an entropy DB\n", + "\n", + "Now that we understand the basic principle, let's look at an example.\n", + "The following code, similar to [section 1](https://github.com/entropy-lab/entropy-qpu/blob/main/docs/qpu_db.ipynb)\n", + "of the tutorial, creates a QPU DB. It also creates an entropy DB and registers the QPU DB as a resource." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# %load_ext autoreload\n", + "# %autoreload 2\n", + "from entropylab_qpudb import create_new_qpu_database, CalState, QpuDatabaseConnection\n", + "from entropylab.instruments.lab_topology import LabResources, ExperimentResources\n", + "from entropylab.results_backend.sqlalchemy.db import SqlAlchemyDB\n", + "\n", + "initial_dict = {\n", + " 'res1': {\n", + " 'TOF': 240, # an initial guess for TOF\n", + " 'f_res': 6e9 # an initial guess for f_res\n", + " },\n", + "}\n", + "create_new_qpu_database('db1', initial_dict, force_create=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "in the following lines we register the resource:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "opening qpu database db1 from commit at index 0\n" + ] + } + ], + "source": [ + "entropydb = SqlAlchemyDB('entropy_db.db')\n", + "lab_resources = LabResources(entropydb)\n", + "lab_resources.register_resource_if_not_exist(\n", + " 'qpu_db', QpuDatabaseConnection, ['db1']\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now we have the databases ready to go and to be used.\n", + "\n", + "## creating the root node\n", + "\n", + "Below we create the root node. We do not (in this case) take values from QPU DB, but we could if we wanted to.\n", + "\n", + "Note the usage of the `@pynode` decorator. This is a shorthand to building pynodes. Some more details on this technique are available on the main Entropy tutorial.\n", + "For now, it's sufficient to understand that the decorator takes the `root` function and turns it into a `pynode` object with that same name. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from entropylab import EntropyContext, pynode\n", + "from qm.qua import *\n", + "\n", + "from entropylab_qpudb import QuaConfig\n", + "\n", + "\n", + "@pynode(\"root\", output_vars={\"config\"})\n", + "def root(context: EntropyContext):\n", + " return {\"config\": QuaConfig({\n", + " \"version\": 1,\n", + " \"controllers\": {\n", + " \"con1\": {\n", + " \"type\": \"opx1\",\n", + " \"analog_outputs\": {\n", + " 1: {\"offset\": +0.0},\n", + " },\n", + " }\n", + " },\n", + " \"elements\": {\n", + " \"res1\": {\n", + " \"singleInput\": {\"port\": (\"con1\", 1)},\n", + " \"intermediate_frequency\": 6e9, # this is just a guess, will later be modified by the cal node\n", + " \"operations\": {\n", + " \"playOp\": \"constPulse\",\n", + " },\n", + " },\n", + " }\n", + " })}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Measuring TOF and updating the QPU DB and config\n", + "\n", + "Below we see a sketch for an implementation of the two nodes we discussed above: time of flight node and res spectroscopy\n", + "node." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from entropylab_qpudb import QuaCalNode\n", + "\n", + "class TOFNode(QuaCalNode):\n", + " def prepare_config(self, config: QuaConfig, context: EntropyContext):\n", + " # here we add a measurement pulse\n", + " print('adding a measurement pulse...')\n", + "\n", + " def run_program(self, config, context: EntropyContext):\n", + " # here we write a QUA program that measures the TOF, and modify QPU DB accordingly\n", + " print('running QUA program and analyzing...')\n", + " with program() as prog:\n", + " # measure TOF\n", + " pass\n", + "\n", + " # execute and get results... let's say we found that TOF is 252\n", + " print('updating QPU DB....')\n", + " context.get_resource('qpu_db').set('res1', 'TOF', 252) # here we update the QPU DB\n", + " context.get_resource('qpu_db').commit('after measuring TOF') # optionally, commit here to persistent storage\n", + " pass\n", + "\n", + " def update_config(self, config: QuaConfig, context: EntropyContext):\n", + " # below we update the config from the values stored in the QPU DB\n", + " print('updating QUA config from QPU DB values...')\n", + " config['elements']['res1']['time_of_flight'] = context.get_resource('qpu_db').get('res1', 'TOF').value\n", + " pass\n", + "\n", + "class ResSpecNode(QuaCalNode):\n", + "\n", + " def prepare_config(self, config: QuaConfig, context: EntropyContext):\n", + " print('asserting that the config has the calibrated TOF value...')\n", + " assert config['elements']['res1']['time_of_flight'] == 252 # this is what we expect\n", + " pass\n", + "\n", + " def run_program(self, config, context: EntropyContext):\n", + " pass\n", + "\n", + " def update_config(self, config: QuaConfig, context: EntropyContext):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Prepare graph experiment\n", + "\n", + "below we can see the syntax for preparing our short calibration experiment" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "opening qpu database db1 from commit at index 0\n" + ] + } + ], + "source": [ + "tofnode_res1 = TOFNode(dependency=root, name='time of flight node')\n", + "res_spec_node_res1 = ResSpecNode(dependency=tofnode_res1, name='res spec node')\n", + "\n", + "from entropylab import Graph\n", + "experiment_resources = ExperimentResources(entropydb)\n", + "experiment_resources.import_lab_resource('qpu_db')\n", + "calibration_experiment = Graph(experiment_resources, res_spec_node_res1.ancestors())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## run the graph experiment\n", + "\n", + "when we run the experiment, we see in the printouts the expected results. Note that the `prepare_config()` and `update_config()` methods of `TOFNode` are called again when running `ResSpecNode`. While this is not the most efficient way to run the code, it is done for robustness reasons and will be corrected later on." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2021-05-28 11:18:57,709 - entropy - INFO - Running node root\n", + "2021-05-28 11:18:57,716 - entropy - INFO - Running node time of flight node\n", + "adding a measurement pulse...\n", + "running QUA program and analyzing...\n", + "updating QPU DB....\n", + "commiting qpu database db1 with commit at index 1\n", + "2021-05-28 11:18:57,724 - entropy - INFO - Running node res spec node\n", + "adding a measurement pulse...\n", + "updating QUA config from QPU DB values...\n", + "asserting that the config has the calibrated TOF value...\n", + "2021-05-28 11:18:57,738 - entropy - INFO - Finished entropy experiment execution successfully\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calibration_experiment.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## closing the QPU DB\n", + "\n", + "if you get a \"can't lock\" error on the DB, run this cell to close it and try again." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closing qpu database db1\n" + ] + } + ], + "source": [ + "experiment_resources.get_resource('qpu_db').close()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## Remove DB files\n", + "\n", + "To remove the DB files created in your workspace for the purpose of this demonstration, run this cell:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from glob import glob\n", + "import os\n", + "for fl in glob(\"db1*\"):\n", + " os.remove(fl)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Entropy", + "language": "python", + "name": "entropy" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + }, + "toc-autonumbering": false, + "toc-showcode": false, + "toc-showmarkdowntxt": false + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/linear.png b/docs/linear.png new file mode 100644 index 0000000..69eb303 Binary files /dev/null and b/docs/linear.png differ diff --git a/docs/merge.png b/docs/merge.png new file mode 100644 index 0000000..3e17a20 Binary files /dev/null and b/docs/merge.png differ diff --git a/docs/quacalnode.drawio b/docs/quacalnode.drawio new file mode 100644 index 0000000..da2f1e7 --- /dev/null +++ b/docs/quacalnode.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/singlenode.png b/docs/singlenode.png new file mode 100644 index 0000000..2753e5d Binary files /dev/null and b/docs/singlenode.png differ diff --git a/entropylab_qpudb/_qpudatabase.py b/entropylab_qpudb/_qpudatabase.py index 5467f7b..188f3b6 100644 --- a/entropylab_qpudb/_qpudatabase.py +++ b/entropylab_qpudb/_qpudatabase.py @@ -35,6 +35,7 @@ class QpuParameter(Persistent): """ A QPU parameter which stores values and modification status for QPU DB entries """ + value: Any last_updated: datetime = None cal_state: CalState = CalState.UNCAL @@ -71,10 +72,12 @@ def _hist_file_from_path(path, dbname): return os.path.join(path, dbname + "_history.fs") -def create_new_qpu_database(dbname: str, - initial_data_dict: Dict = None, - force_create: bool = False, - path: str = None) -> None: +def create_new_qpu_database( + dbname: str, + initial_data_dict: Dict = None, + force_create: bool = False, + path: str = None, +) -> None: """ Create a new QPU database permanent storage file. This operation is performed once in the lifetime of a database, and is quite similar to initializing a git repo. @@ -189,12 +192,14 @@ def _open_data_db(self, history_index): try: self._db = ZODB.DB(dbfilename) if self._db is None else self._db except LockError: - raise ConnectionError(f"attempting to open a connection to {self._dbname} but a connection already exists." - f"Try closing existing python sessions.") + raise ConnectionError( + f"attempting to open a connection to {self._dbname} but a connection already exists." + f"Try closing existing python sessions." + ) con = self._db.open(transaction_manager=transaction.TransactionManager(), at=at) assert ( - con.isReadOnly() == readonly + con.isReadOnly() == readonly ), "internal error: Inconsistent readonly state" con.transaction_manager.begin() print( @@ -208,8 +213,10 @@ def _open_hist_db(self): try: db_hist = ZODB.DB(histfilename) except LockError: - raise ConnectionError(f"attempting to open a connection to {self._dbname} but a connection already exists." - f"Try closing existing python sessions.") + raise ConnectionError( + f"attempting to open a connection to {self._dbname} but a connection already exists." + f"Try closing existing python sessions." + ) con_hist = db_hist.open(transaction_manager=transaction.TransactionManager()) con_hist.transaction_manager.begin() return con_hist @@ -232,7 +239,13 @@ def close(self) -> None: def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def set(self, element: str, attribute: str, value: Any, new_cal_state: Optional[CalState] = None) -> None: + def set( + self, + element: str, + attribute: str, + value: Any, + new_cal_state: Optional[CalState] = None, + ) -> None: """ A generic function for modifying values of element attributes. @@ -257,11 +270,11 @@ def set(self, element: str, attribute: str, value: Any, new_cal_state: Optional[ root["elements"][element][attribute].cal_state = new_cal_state def add_attribute( - self, - element: str, - attribute: str, - value: Any = None, - new_cal_state: Optional[CalState] = None, + self, + element: str, + attribute: str, + value: Any = None, + new_cal_state: Optional[CalState] = None, ) -> None: """ Adds an attribute to an existing element. diff --git a/entropylab_qpudb/_resolver.py b/entropylab_qpudb/_resolver.py index cd9eca9..b07daa4 100644 --- a/entropylab_qpudb/_resolver.py +++ b/entropylab_qpudb/_resolver.py @@ -29,13 +29,13 @@ def aliases(self): class DefaultResolver(Resolver): def q(self, qubit, channel=None): if channel is None: - return f'q{qubit}' + return f"q{qubit}" else: - return f'q{qubit}_{channel}' + return f"q{qubit}_{channel}" def res(self, resonator): - return f'res{resonator}' + return f"res{resonator}" def coupler(self, qubit1, qubit2): qubit1, qubit2 = sorted((qubit1, qubit2)) - return f'c{qubit1}_{qubit2}' + return f"c{qubit1}_{qubit2}" diff --git a/entropylab_qpudb/tests/test_qpudatabase.py b/entropylab_qpudb/tests/test_qpudatabase.py index 15fe155..3209d09 100644 --- a/entropylab_qpudb/tests/test_qpudatabase.py +++ b/entropylab_qpudb/tests/test_qpudatabase.py @@ -57,8 +57,10 @@ def test_open_with_default_resolver(testdb): with QpuDatabaseConnection(testdb) as db: assert db.q(1).p1.value == 3.32 print(db.q(1).p1) - db.add_element(db._resolver.coupler(1, 2)) # todo: add methods for adding specific element types - db.add_attribute(db._resolver.coupler(1, 2), 'xx', 20) + db.add_element( + db._resolver.coupler(1, 2) + ) # todo: add methods for adding specific element types + db.add_attribute(db._resolver.coupler(1, 2), "xx", 20) print(db.coupler(1, 2).xx) assert db.coupler(1, 2).xx.value == 20 assert db.coupler(2, 1).xx.value == 20