diff --git a/.gitignore b/.gitignore index 53a3c31..eeeafdb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .ipynb_checkpoints *.txt~ *.ipynb~ +*.md~ diff --git a/.travis.yml b/.travis.yml index e4ad8c2..f3a86cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,9 @@ +# NOTE: When editing this, be sure to update setup.py + language: python python: - "3.6" - - "3.5" +# - "3.7" (fails due to watchdog; skip for now) install: # Setup anaconda. See http://conda.pydata.org/docs/travis.html diff --git a/README.md b/README.md index 057ae0c..da82a37 100644 --- a/README.md +++ b/README.md @@ -8,34 +8,46 @@ programs directly specify gates at the physical layer, but with many of the niceties of a high-level programming language provided by the python host language. +Documentation on the QGL2 compiler and language, including current known limitations, is in [doc]. + ## Examples -QGL2 directly parses the Python syntax to give natural looking binding of qubit -measurement results to variables and control flow statements. +For usage examples, see the sample Jupyter notebooks in the [sample notebooks directory](/notebooks). -```python -# single qubit reset -from qgl2.qgl2 import qgl2decl, QRegister -from qgl2.qgl1 import Id, X, MEAS +For code samples, see the [Basic Sequences](/src/python/qgl2/basic_sequences). + +For an example of compiling a QGL2 program from the command-line, see [doc/README.txt]. + +QGL2 directly parses the Python syntax to give natural looking qubit sequences and control flow. +measurement results to variables and control flow statements. For example: +```python @qgl2decl -def reset(): - q = QRegister(1) - m = MEAS(q) - if m: - X(q) - else: - Id(q) -``` +def RabiAmp(qubit: qreg, amps, phase=0): + """Variable amplitude Rabi nutation experiment.""" + for amp in amps: + init(qubit) + Utheta(qubit, amp=amp, phase=phase) + MEAS(qubit) Once a function is decorated with `@qgl2decl` it can act as the `main` for -compiling a QGL2 program. If the `reset` function is placed in Python module -then it can be compiled with: +compiling a QGL2 program. If the `RabiAmp` function is placed in a Python module +then it can be compiled with something like: ```python from pyqgl2.main import compile_function -result = compile_function(filename, "reset") +from pyqgl2.qreg import QRegister +import numpy as np +q = QRegister(1) +qgl1Function = compile_function(filename, "RabiAmp", (q, np.linspace(0, 1, 1), 0)) +``` + +The result is a function, whose execution generates a QGL sequence. +```python +# Run the compiled function. Note that the generated function takes no arguments itself +seq = qgl1Function() ``` +That sequence can then be examined or compiled to hardware, as described in the [QGL documentation](https://github.com/BBN-Q/QGL). QGL2 uses type annotations in function calls to mark quantum and classical values. Encapsulating subroutines makes it possible to write tidy compact code @@ -70,8 +82,8 @@ def qft(qs: qreg): MEAS(qs) ``` -By embedding in Python powerful metaprogramming of sequences is possible. For -example process tomography on a two qubit sequence comes a function. +By embedding in Python, powerful metaprogramming of sequences is possible. For +example process tomography on a two qubit sequence becomes a function. ```python @qgl2decl @@ -91,24 +103,64 @@ def tomo(f, q1: qreg, q2: qreg): ## Installation +### Current instructions + + * Most any OS should be OK. Instructions tested on Ubuntu 18.04 + * Install `git` and `buildessentials` packages + * `git-lfs` is now required: See https://git-lfs.github.com/ + * Download it & unpack and run `install.sh` + * Install python 3.6; easiest done using [Anaconda](https://www.anaconda.com/distribution/#download-section) + * See below for sample installation given an Anaconda install + * You will need python 3 compatible atom (either atom 1.0.0-dev or ecpy channel atom 0.4) + * Install QGL: (https://github.com/BBN-Q/QGL) + * Install QGL dependencies: `cd QGL; pip install -e .` + * From within the QGL git clone, set up git lfs: `$ git lfs install` + * Add QGL to your `.bashrc`: `export PYTHONPATH=$QHOME/QGL:$QHOME/pyqgl2/src/python` + * Then: `pip install meta` and `pip install watchdog` + * Optional: `pip install pep8` and `pip install pylint` + * For typical usage, you also need Auspex (https://github.com/BBN-Q/Auspex) + * See install instructions at https://auspex.readthedocs.io/en/latest/ + * Download or clone, then `cd auspex; pip install -e .` + * Put `Auspex/src` on your `PYTHONPATH` as in above + * Install `bbndb` as well (if not installed by QGL): `git clone git@github.com:BBN-Q/bbndb.git` + * Put the bbndb directory on your PYTHONPATH + * `pip install -e .` + * ?Optional: Get the BBN Adapt module as well + * `git@github.com:BBN-Q/Adapt.git` + * Put `Adapt/src` on your `PYTHONPATH` as in above + * Create a measurement file, typically eg `QHOME/test_measure.yml`, containing: +``` +config: + AWGDir: /tmp/awg + KernelDir: /tmp/kern + LogDir: /tmp/alog +``` + * Set an environment variable to point to it in your `.bashrc`: `export BBN_MEAS_FILE=$QHOME/test_measure.yml` + * Optional: Install `coveralls` (i.e. for CI) + * Download `pyqgl2` source from git (https://github.com/BBN-Q/pyqgl2) + * Test: `cd pyqgl2; python -m unittest discover`. Should see 80+ tests run without errors (warnings are OK). ### Dependencies + + * Working [https://github.com/BBN-Q/QGL] installation (including `networkx`, `numpy`, `scipy`, `bqplot`, `sqlalchemy`) + * Python 3.6 + * watchdog and meta + * PYTHONPATH includes `/src/python` - * Working QGL installation (including networkx, bokeh, numpy, scipy) - * Python 3.5 or 3.6 - * PYTHONPATH includes /src/python - -Expanding on that: -Requires python3 anaconda, python 3 compatible atom (either atom 1.0.0-dev or -ecpy channel atom 0.4), and latest QGL repo. The quickest way to get started is: - +### Sample install using Anaconda ```bash + conda install future conda install -c ecpy atom watchdog pip install meta git clone --recurse-submodules git@github.com:BBN-Q/QGL cd QGL pip install -e . +git lfs install +cd .. +git clone https://github.com/BBN-Q/auspex.git +cd auspex +pip install -e . cd .. git clone git@qiplab.bbn.com:buq-lab/pyqgl2.git ``` diff --git a/doc/README.txt b/doc/README.txt index 9203245..2f44af8 100644 --- a/doc/README.txt +++ b/doc/README.txt @@ -3,41 +3,34 @@ restrictions / limitations / ways it is not full python, see restrictions.txt To run a QGL2 program: -See sample Jupyter notebooks in /notebooks. Start with -"QGL2 RabiAmp.ipynb" or "QGL2 RabiSingleShot.ipynb". +See sample Jupyter notebooks in /notebooks. -The QGL2 main is in `pyqgl2.main` - -Run that with -h to get commandline help. +The QGL2 main is in `pyqgl2.main`. Run that with -h to get commandline +help. See sample uses in [src/pythong/qgl2/basic_sequences]. Sample commandline: ``` -/src/python $ python pyqgl2/main.py qgl2/basic_sequences/RabiMin.py -m doRabiAmp -Using ChannelLibrary from config -Compiled 1 sequences. -['/QGL/awg/qgl2main/test/test-APS1.h5', '/QGL/awg/qgl2main/test/test-APS2.h5'] +$ python src/python/pyqgl2/main.py -C src/python/qgl2/basic_sequences/Rabi.py -m SingleShotNoArg +AWG_DIR environment variable not defined. Unless otherwise specified, using temporary directory for AWG sequence file outputs. +Will create and use APS2ish 3 qubit test channel library +Creating engine... + + +COMPILING [src/python/qgl2/basic_sequences/Rabi.py] main SingleShotNoArg +... +Generated sequences: + +[WAIT((Qubit('q1'),)), + Id(q1), + MEAS(M-q1, shape_fun=), + WAIT((Qubit('q1'),)), + X(q1), + MEAS(M-q1, shape_fun=)] ``` When using pyqgl2.main as your main: - - Any Channel library defined in the usual QGL JSON files will be - used. - - The qubit named 'q1' will be used as the Qubit in your QGL2 - program, which must use a single qubit variable named 'q'. - - If you have no Channel library, a default one will be created. - -The QGL2 programs in `qgl2/basic_sequences/*Min.py` are tuned for -working with the latest QGL2 compiler. Note they all use a single -qubit variable named 'q' that is given a default value at the -start. This value will be replaced by the compiler if a different -value is given as the sole argument to the function. - -Note that not all the QGL2 programs in the `basic_sequences/*Min.py` will -currently work. E.G.: - * RabiWidth has a problem importing some needed QGL functions - * Some pulses use calibration sequences, but the helper to produce - those does not yet work. - - -Note that the unit tests defined in `tests/test_QGL2_Sequences.py` do -not currently work. + - Supply `-C` to use a test channel library, or supply a Channel + Library name to load from a file (see QGL / Auspex documentation). + - QGL2 function should be rewritten to require no arguments. See the + sample in `Rabi.py` - `SingleShotNoArg` diff --git a/doc/compiler.md b/doc/compiler.md index be2f8a6..ede2360 100644 --- a/doc/compiler.md +++ b/doc/compiler.md @@ -5,10 +5,6 @@ 3. *Inliner* - Iteratively (up to 20 times) try to inline things until the program stops changing. (Note that we don’t have a mechanism to ask for a piece of code NOT to be inlined.) 4. *EvalTransformer* - Evaluate each expression. 5. Replace bindings with their values from evaluation. -6. Group code into sets of qubits acted upon - 1. MarkReferencedQbits - 2. AddSequential - 3. QbitGrouper2 - why two steps? -7. *Flattener* - Flatten out repeat, range, ifs... Qiter, Qfor, with concur -8. *SequenceCreator* - Inject barriers -9. *SequenceExtractor* - Produce QGL1 sequence function +6. *Flattener* - Flatten out repeat, range, ifs... Qiter, Qfor, etc +7. *SequenceExtractor* - Produce QGL1 sequence function + diff --git a/doc/restrictions.txt b/doc/restrictions.txt index f963df4..0bbdb38 100644 --- a/doc/restrictions.txt +++ b/doc/restrictions.txt @@ -1,11 +1,13 @@ Some current limitations of the QGL2 language - including ways it is not Python +Note that for the latest set of known issues or planned enhancements, +see [https://github.com/BBN-Q/pyqgl2/issues]. + * In general, *args and **kwargs are not well handled - for type checking arguments, etc * QGL2 looks at variables named `QBIT_*` as canonical qbits. Another - variable that happens to have that name might cause problems. See - issue #77. + variable that happens to have that name might cause problems. * qgl2decl method declarations cannot be nested * Make all functions top level functions * Methods with a return statement are not inlined @@ -15,35 +17,13 @@ Some current limitations of the QGL2 language from qgl2.basic_sequences.qgl2_plumbing import init * QGL2 uses read-only qubits, so you can read their parameters, but cannot edit them. Note also that the full QGL - ChannelLibrary is not generally available. See issue #76. -* QGL2 main function cannot take arguments, specifically Qubits. See - issue #62. + ChannelLibrary is not generally available. See issue #37. * There is no current good way to do control flow based on measurement results, particularly measurements from a different channel. This - will change. See issue #66. -* Do not use QGL1 control flow functions directly. Use python to set - up your control flow. -* Do not use QGL1 functions that return a list of pulses. Much better - (and may be soon required) to rewrite that as a QGL2 function. Issue #79. + will change. See issue #24. * Do not use a QGL2 function where a pure python function will do; using QGL2 is slower / less efficient. -Things to fix: - -* 'label' argument to Qubit() must be a literal string - no variable - whose value is a string, no expression, etc. Issue #75. - * EG see `test_Sequences` which loops over qbit names. That won't work. -* A Qubit() creation is only recognized if it is in an assignment. EG - x = Qubit("alabel") works, but - return Qubit("alabel") does not. Issue #75. -* Various ways to read & use file contents (like `with open`) - fail. See issue #67, 68, 69. -* QGL2 performance must be improved. -* QGL2 does not currently use repeat blocks, in part to avoid repeat - blocks that contain a barrier. Issue #74 -* Provide way to give arguments to `compile_function` as arguments to - the function being compiled. Issue #50. - Other to do items for QGL2: * Need to do more error checking on arguments other than qbit diff --git a/notebooks/QGL2 AllXY.ipynb b/notebooks/QGL2 AllXY.ipynb index 9614fc3..b109730 100644 --- a/notebooks/QGL2 AllXY.ipynb +++ b/notebooks/QGL2 AllXY.ipynb @@ -13,47 +13,91 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files\n", - "import QGL.config\n", - "import os" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Should sequences be compiled to hardware, or just to QGL?" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "toHW = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary; alternatively, load a library you already defined" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "# Ensure the AWG directory exists\n", - "if not os.path.exists(QGL.config.AWGDir):\n", - " os.makedirs(QGL.config.AWGDir)" + "create_default_channelLibrary(toHW, True)\n", + "\n", + "# Or create a new ChannelLibrary with channels\n", + "# cl = ChannelLibrary(db_resource_name=\":memory:\")\n", + "# q1 = cl.new_qubit('q1')\n", + "\n", + "# Most calls required label and address\n", + "# aps2_1 = cl.new_APS2(\"BBNAPS1\", address=\"192.168.5.101\") \n", + "# aps2_2 = cl.new_APS2(\"BBNAPS2\", address=\"192.168.5.102\")\n", + "# dig_1 = cl.new_X6(\"X6_1\", address=0)\n", + "\n", + "# Label, instrument type, address, and an additional config parameter\n", + "# h1 = cl.new_source(\"Holz1\", \"HolzworthHS9000\", \"HS9004A-009-1\", power=-30)\n", + "# h2 = cl.new_source(\"Holz2\", \"HolzworthHS9000\", \"HS9004A-009-2\", power=-30)\n", + "\n", + "# Qubit q1 is controlled by AWG aps2_1, and uses microwave source h1\n", + "# cl.set_control(q1, aps2_1, generator=h1)\n", + "\n", + "# Qubit q1 is measured by AWG aps2_2 and digitizer dig_1, and uses microwave source h2\n", + "# cl.set_measure(q1, aps2_2, dig_1.ch(1), generator=h2)\n", + "\n", + "# The AWG aps2_1 is the master AWG, and distributes a synchronization trigger on its second marker channel\n", + "# cl.set_master(aps2_1, aps2_1.ch(\"m2\"))\n", + "# cl.commit()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -77,9 +121,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#from pyqgl2.ast_util import NodeError\n", @@ -92,26 +134,22 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", - "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", - "# True argument means save the QGL1 compiled function to a file" + "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/AllXYMin.py\", \"doAllXY\", True)" + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/AllXY.py\", \"AllXY\", (q,))" ] }, { @@ -124,14 +162,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "seqs = qgl1MainFunc()" ] }, { @@ -140,47 +175,38 @@ "collapsed": true }, "source": [ - "## Compile to machine instructions" + "## Optionally compile to machine instructions" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "scrolled": true, + "slideshow": { + "slide_type": "-" + } }, "outputs": [], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"AllXY/AllXY\")\n", - "print(fileNames)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "## Plot the sequences" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "plot_pulse_files(fileNames)" + "if toHW:\n", + " from IPython.display import display\n", + " metaFileName = qgl2_compile_to_hardware(seqs, \"AllXY/AllXY\")\n", + " print(f\"Generated sequence details in '{metaFileName}'\")\n", + " # Plot the sequences\n", + " p = plot_pulse_files(metaFileName)\n", + " # Explicitly display the graph which fails to auto-draw in some cases\n", + " display(p)\n", + "else:\n", + " from QGL.Scheduler import schedule\n", + " from IPython.lib.pretty import pretty\n", + " print(pretty(schedule(seqs)))" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -201,9 +227,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 CPMG.ipynb b/notebooks/QGL2 CPMG.ipynb index fd0e25c..9c1d7d2 100644 --- a/notebooks/QGL2 CPMG.ipynb +++ b/notebooks/QGL2 CPMG.ipynb @@ -13,32 +13,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files\n", - "from QGL.ChannelLibrary import QubitFactory" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Should sequences be compiled to hardware, or just to QGL?" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "toHW = True" ] }, { @@ -47,106 +51,107 @@ "collapsed": true }, "source": [ - "## Compile to QGL1" + "### Create a test ChannelLibrary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "create_default_channelLibrary(toHW, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" ] }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ - "### To turn on debug output, uncomment the next 4 lines" + "## Create needed qubit(s)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "#from pyqgl2.ast_util import NodeError\n", - "#from pyqgl2.debugmsg import DebugMsg\n", - "\n", - "#DebugMsg.set_level(1)\n", - "#NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE" + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], - "source": [] + "source": [ + "## Compile to QGL1" + ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], "source": [ - "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", - "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", - "# True argument means save the QGL1 compiled function to a file" + "### To turn on debug output, uncomment the next 4 lines" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/DecouplingMin.py\", \"doCPMG\", True)" + "#from pyqgl2.ast_util import NodeError\n", + "#from pyqgl2.debugmsg import DebugMsg\n", + "\n", + "#DebugMsg.set_level(1)\n", + "#NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Generate pulse sequences" + "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", + "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "scrolled": false }, "outputs": [], "source": [ - "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "tCalR = 2 # calRepeats\n", + "cpmgNumPulses = [0, 2, 4, 6]\n", + "cpmgSpacing = 500e-9\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Decoupling.py\", \"CPMG\", \n", + " (q, cpmgNumPulses, cpmgSpacing, tCalR))" ] }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ - "## Compile to machine instructions" + "## Generate pulse sequences" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"CPMG/CPMG\")\n", - "print(fileNames)" + "# Now run the QGL1 function, producing a list of sequences\n", + "seqs = qgl1MainFunc()" ] }, { @@ -155,28 +160,34 @@ "collapsed": true }, "source": [ - "## Plot the sequences" + " ## Optionally compile to machine instructions" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "if toHW:\n", + " from qgl2.basic_sequences.helpers import delay_descriptor, cal_descriptor\n", + " axis_desc = [\n", + " # NOTE: numPulses is not a numpy array, so cannot multiply by a float\n", + " # But thankfully, np.array(np.array) = np.array so this is always a good move here.\n", + " delay_descriptor(cpmgSpacing * np.array(cpmgNumPulses)),\n", + " cal_descriptor(('qubit',), tCalR)\n", + " ]\n", + " metaFileName = qgl2_compile_to_hardware(seqs, filename=\"CPMG/CPMG\", axis_descriptor=axis_desc)\n", + " print(f\"Generated sequence details in '{metaFileName}'\")\n", + " # Plot the sequences\n", + " p = plot_pulse_files(metaFileName)\n", + " # Explicitly display the graph which fails to auto-draw in some cases\n", + " display(p)\n", + "else:\n", + " from QGL.Scheduler import schedule\n", + " from IPython.lib.pretty import pretty\n", + " print(pretty(schedule(seqs)))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -195,9 +206,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 HahnEcho.ipynb b/notebooks/QGL2 HahnEcho.ipynb index d2628b3..f09becc 100644 --- a/notebooks/QGL2 HahnEcho.ipynb +++ b/notebooks/QGL2 HahnEcho.ipynb @@ -13,32 +13,70 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Should sequences be compiled to hardware, or just to QGL?" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, + "outputs": [], + "source": [ + "toHW = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "create_default_channelLibrary(toHW, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -62,9 +100,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#from pyqgl2.ast_util import NodeError\n", @@ -77,26 +113,25 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", - "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", - "# True argument means save the QGL1 compiled function to a file" + "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/DecouplingMin.py\", \"doHahnEcho\", True)" + "tCalR = 2 # calRepeats\n", + "hahnSpacings = np.linspace(0, 5e-6, 11)\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Decoupling.py\", \"HahnEcho\", \n", + " (q, hahnSpacings, 0, tCalR))" ] }, { @@ -109,35 +144,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "## Compile to machine instructions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"Echo/Echo\")\n", - "print(fileNames)" + "seqs = qgl1MainFunc()" ] }, { @@ -146,26 +157,37 @@ "collapsed": true }, "source": [ - "## Plot the sequences" + "## Optionally compile to machine instructions" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "if toHW:\n", + " from qgl2.basic_sequences.helpers import delay_descriptor, cal_descriptor\n", + " axis_desc = [\n", + " delay_descriptor(2 * hahnSpacings),\n", + " cal_descriptor(('qubit',), tCalR)\n", + " ]\n", + " metaFileName = qgl2_compile_to_hardware(seqs, \"Echo/Echo\", axis_descriptor=axis_desc)\n", + " print(f\"Generated sequence details in '{metaFileName}'\")\n", + " # Plot the sequences\n", + " p = plot_pulse_files(metaFileName)\n", + " # Explicitly display the graph which fails to auto-draw in some cases\n", + " display(p)\n", + "else:\n", + " from QGL.Scheduler import schedule\n", + " from IPython.lib.pretty import pretty\n", + " print(pretty(schedule(seqs)))" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -186,9 +208,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 InversionRecovery.ipynb b/notebooks/QGL2 InversionRecovery.ipynb index 8f86529..a37b49f 100644 --- a/notebooks/QGL2 InversionRecovery.ipynb +++ b/notebooks/QGL2 InversionRecovery.ipynb @@ -9,123 +9,125 @@ "# Compiling a QGL2 InversionRecovery and plotting the output" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "# Using delays=np.linspace(0, 5e-6, 11)" + "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "### Should sequences be compiled to hardware, or just to QGL?" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files" + "toHW = True\n", + "# Append suffix to filenames?\n", + "suffix = True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "create_default_channelLibrary(toHW, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" ] }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ - "## Compile to QGL1" + "## Create needed qubit(s)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", - "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", - "# True argument means save the QGL1 compiled function to a file" + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { - "collapsed": false, - "scrolled": true + "collapsed": true }, - "outputs": [], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/T1T2Min.py\", \"doInversionRecovery\", True)" + "## Compile to QGL1" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Generate pulse sequences" + "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", + "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "scrolled": true }, "outputs": [], "source": [ - "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "irDelays = np.linspace(0, 5e-6, 11)\n", + "tCalR = 2\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/T1T2.py\", \"InversionRecovery\", (q, irDelays, tCalR))" ] }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ - "## Compile to machine instructions" + "## Generate pulse sequences" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "# True filename optionally is T1_q1/T1_q1 where q1 is the qubit label\n", - "fileNames = qgl2_compile_to_hardware(seqs, \"T1/T1\")\n", - "print(fileNames)" + "# Now run the QGL1 function, producing a list of sequences\n", + "seqs = qgl1MainFunc()" ] }, { @@ -134,29 +136,38 @@ "collapsed": true }, "source": [ - "## Plot the sequences" + "## Optionally compile to machine instructions" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "if toHW:\n", + " from qgl2.basic_sequences.helpers import delay_descriptor, cal_descriptor\n", + " axis_desc = [\n", + " delay_descriptor(irDelays),\n", + " cal_descriptor(('qubit',), tCalR)\n", + " ]\n", + " label = \"T1\"\n", + " # Generate proper filenames; for these, it isn't just the label T1\n", + " # T1T2 QGL functions take a suffix boolean default false. If true, then append to label \"_qubit.label\"; ie \"_q1\"\n", + " if suffix:\n", + " label = label + \"_q1\"\n", + " \n", + " metaFileName = qgl2_compile_to_hardware(seqs, filename=f\"{label}/{label}\", axis_descriptor=axis_desc)\n", + " print(f\"Generated sequence details in '{metaFileName}'\")\n", + " # Plot the sequences\n", + " p = plot_pulse_files(metaFileName)\n", + " # Explicitly display the graph which fails to auto-draw in some cases\n", + " display(p)\n", + "else:\n", + " from QGL.Scheduler import schedule\n", + " from IPython.lib.pretty import pretty\n", + " print(pretty(schedule(seqs)))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -175,9 +186,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 RabiAmp-indirect.ipynb b/notebooks/QGL2 RabiAmp-indirect.ipynb index 0899bee..1dad7fb 100644 --- a/notebooks/QGL2 RabiAmp-indirect.ipynb +++ b/notebooks/QGL2 RabiAmp-indirect.ipynb @@ -1,12 +1,10 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], "source": [ "# Sample notebook running a previously compiled QGL2 program" ] @@ -14,155 +12,82 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import QGL" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "from QGL.Compiler import compile_to_hardware" + "import QGL\n", + "from QGL.Compiler import compile_to_hardware\n", + "from QGL.Scheduler import schedule\n", + "from QGL.PulseSequencePlotter import plot_pulse_files\n", + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "\n", + "# This next should be a valid import of a compiled QGL2 program and its qgl2main\n", + "# EG do the compilation manually with -o and put it in the right spot\n", + "from RabiAmpqgl1 import RabiAmp" ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "from QGL.PulseSequencePlotter import plot_pulse_files" + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "# This next should be a valid import of a compiled QGL2 program and its qgl2main" + "create_default_channelLibrary(True, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, - "outputs": [], "source": [ - "# EG do the compilation manually with -o and put it in the right spot" + "## Generate pulse sequences" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "from Rabiqgl1 import doRabiAmp" + "# Now run the QGL1 function as before, producing a list of sequences\n", + "seqs = RabiAmp()" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "from QGL.Plotting import output_notebook" + "# Note we cannot provide the correct axis description without knowing the amplitudes used\n", + "# Note we schedule the sequences and ensure they are a list\n", + "metaFileName = compile_to_hardware([schedule(seqs)], \"Rabi/Rabi\")\n", + "print(f\"Generated sequence details in '{metaFileName}'\")" ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "output_notebook()" + "## Plot the sequences" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "channels = QGL.ChannelLibrary.channelLib" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Specify the name of the qubit to use" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "seqs = doRabiAmp(q=channels['q1'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "fileNames = compile_to_hardware(seqs, \"Rabi/Rabi\", qgl2=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "print(fileNames)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "display(plot_pulse_files(metaFileName))" ] } ], @@ -182,9 +107,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 RabiAmp.ipynb b/notebooks/QGL2 RabiAmp.ipynb index fc1b012..31649b5 100644 --- a/notebooks/QGL2 RabiAmp.ipynb +++ b/notebooks/QGL2 RabiAmp.ipynb @@ -9,10 +9,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 22, + "metadata": {}, "outputs": [], "source": [ "# Using phase=0, amp from 0 -> 1 stepping by 0.1" @@ -22,33 +20,54 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### imports" + "### Imports" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 23, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "import QGL\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files\n", - "from QGL import QubitFactory" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 24, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "create_default_channelLibrary(True, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -60,27 +79,51 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 26, + "metadata": {}, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", - "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new functio\n", - "# True argument means save the QGL1 compiled function to a file" + "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new functio" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "metadata": { - "collapsed": false, "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "COMPILING [../src/python/qgl2/basic_sequences/Rabi.py] main RabiAmp\n", + "2019-09-26 11:50:08.300694: CALLING IMPORTER\n", + "../src/python/qgl2/basic_sequences/Rabi.py:188:4: warning: conditional/runtime import [from pyqgl2.qreg import QRegister] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:189:4: warning: conditional/runtime import [import pyqgl2.test_cl] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:190:4: warning: conditional/runtime import [from pyqgl2.main import compile_function\n", + "from pyqgl2.main import qgl2_compile_to_hardware] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:191:4: warning: conditional/runtime import [import numpy as np] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:192:4: warning: conditional/runtime import [import QGL.PulseShapes] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:290:12: warning: conditional/runtime import [from QGL.Scheduler import schedule] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:293:12: warning: conditional/runtime import [from IPython.lib.pretty import pretty] ignored by pyqgl2\n", + "../src/python/qgl2/basic_sequences/Rabi.py:285:16: warning: conditional/runtime import [from QGL.PulseSequencePlotter import plot_pulse_files] ignored by pyqgl2\n", + "2019-09-26 11:50:08.365738: CALLING INLINER\n", + "2019-09-26 11:50:08.365983: ITERATION 0\n", + "2019-09-26 11:50:08.367424: ITERATION 1\n", + "2019-09-26 11:50:08.368372: CALLING EVALUATOR\n", + "2019-09-26 11:50:08.373181: CALLING FLATTENER\n", + "2019-09-26 11:50:08.373489: GENERATING QGL1 SEQUENCE FUNCTION\n", + "Saved compiled code to /home/ahelsing/machome/Projects/Quantum/pyqgl2/src/python/qgl2/basic_sequences/Rabiqgl1.py\n" + ] + } + ], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/RabiMin.py\", \"doRabiAmp\", True)" + "rAmpAmps = np.linspace(0, 1, 1)\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Rabi.py\", \"RabiAmp\", (q, rAmpAmps, 0), saveOutput=True)" ] }, { @@ -92,15 +135,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 28, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "seqs = qgl1MainFunc()" ] }, { @@ -114,14 +154,30 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "scrolled": false - }, - "outputs": [], + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiled 1 sequences.\n", + "Generated sequence details in '/tmp/AWGpj4dtdnh/Rabi/Rabi-meta.json'\n" + ] + } + ], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"Rabi/Rabi\")" + "from qgl2.basic_sequences.helpers import delay_descriptor, cal_descriptor\n", + "axis_desc = [{\n", + " 'name': 'amplitude',\n", + " 'unit': None,\n", + " 'points': list(rAmpAmps),\n", + " 'partition': 1\n", + " }]\n", + "label = \"Rabi\"\n", + "\n", + "metaFileName = qgl2_compile_to_hardware(seqs, filename=f\"{label}/{label}\", axis_descriptor=axis_desc)\n", + "print(f\"Generated sequence details in '{metaFileName}'\")" ] }, { @@ -133,14 +189,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": { - "collapsed": false, "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sampling rate from extract_waveforms 1200000000.0\n", + "Sampling rate from extract_waveforms 1200000000.0\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "21712ef20433491e82aaf7c8f7d855bc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(IntSlider(value=1, description='Segment', max=1, min=1), Figure(animation_duration=50, axes=[Ax…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "plot_pulse_files(fileNames)" + "p = plot_pulse_files(metaFileName)\n", + "# Explicitly display the graph which fails to auto-draw in some cases\n", + "display(p)" ] } ], @@ -160,9 +240,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 RabiPulsedSpec.ipynb b/notebooks/QGL2 RabiPulsedSpec.ipynb index 4df1cdd..5fae667 100644 --- a/notebooks/QGL2 RabiPulsedSpec.ipynb +++ b/notebooks/QGL2 RabiPulsedSpec.ipynb @@ -13,32 +13,53 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, + "outputs": [], + "source": [ + "create_default_channelLibrary(True, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -62,9 +83,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#from pyqgl2.ast_util import NodeError\n", @@ -78,16 +97,13 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", - "# True argument means save the QGL1 compiled function to a file\n", - "\n", - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/RabiMin.py\", \"doPulsedSpec\", True)" + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Rabi.py\", \"PulsedSpec\", (q, True))" ] }, { @@ -100,14 +116,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "seqs = qgl1MainFunc()" ] }, { @@ -122,13 +135,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"Spec/Spec\")\n", - "print(fileNames)" + "metaFileName = qgl2_compile_to_hardware(seqs, \"Spec/Spec\")\n", + "print(f\"Generated sequence details in '{metaFileName}'\")" ] }, { @@ -143,12 +154,10 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "display(plot_pulse_files(metaFileName))" ] } ], @@ -168,9 +177,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 RabiSingleShot.ipynb b/notebooks/QGL2 RabiSingleShot.ipynb index 489ed3e..bb95cd7 100644 --- a/notebooks/QGL2 RabiSingleShot.ipynb +++ b/notebooks/QGL2 RabiSingleShot.ipynb @@ -12,26 +12,25 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files" + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "# Compile to hardware or just to QGL pulse sequence?\n", + "toHW = False\n", + "# Create a test library (or load one)\n", + "create_default_channelLibrary(toHW, True)" ] }, { @@ -53,31 +52,45 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "#from pyqgl2.ast_util import NodeError\n", - "#from pyqgl2.debugmsg import DebugMsg\n", + "# from pyqgl2.ast_util import NodeError\n", + "# from pyqgl2.debugmsg import DebugMsg\n", "\n", - "#DebugMsg.set_level(1)\n", - "#NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE" + "# DebugMsg.set_level(1)\n", + "# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generate a list of the arguments to the QGL2 function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qr = QRegister('q1')\n", + "args=(qr,)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "scrolled": true }, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", "# True argument means save the QGL1 compiled function to a file\n", - "\n", - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/RabiMin.py\", \"doSingleShot\", True)" + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Rabi.py\", \"SingleShot\", args, saveOutput=True)" ] }, { @@ -90,14 +103,12 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "# QGL2 to QGL1 compiled functions take no arguments\n", + "seqs = qgl1MainFunc()" ] }, { @@ -110,9 +121,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# import logging\n", @@ -127,40 +136,26 @@ "collapsed": true }, "source": [ - "## Compile to machine instructions" + "## Optionally compile to machine instructions" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"SingleShot/SingleShot\")\n", - "print(fileNames)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "## Plot the sequences" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "plot_pulse_files(fileNames)" + "if toHW:\n", + " metaFileName = qgl2_compile_to_hardware(seqs, \"SingleShot/SingleShot\")\n", + " print(f\"Generated sequence details in '{metaFileName}'\")\n", + " # Plot the sequences\n", + " display(plot_pulse_files(metaFileName))\n", + "else:\n", + " from QGL.Scheduler import schedule\n", + " from IPython.lib.pretty import pretty\n", + " print(pretty(schedule(seqs)))" ] } ], @@ -180,9 +175,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 RabiWidth.ipynb b/notebooks/QGL2 RabiWidth.ipynb index 52708c4..1403a5f 100644 --- a/notebooks/QGL2 RabiWidth.ipynb +++ b/notebooks/QGL2 RabiWidth.ipynb @@ -12,9 +12,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Using phase=0, amp=0, shapeFun=tanh, length: 0 to 5e-6 in 11 steps" @@ -24,32 +22,55 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from pyqgl2.main import compile_function, qgl2_compile_to_hardware" + "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import QGL.PulseShapes\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "create_default_channelLibrary(True, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -62,15 +83,15 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", "# Here we compile the named function in the named file from QGL2 to QGL1 and return the new function\n", "# True argument means save compiled QGL1 to file\n", - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/RabiMin.py\", \"doRabiWidth\", True)" + "rWidthWidths = np.linspace(0, 5e-6, 11)\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/Rabi.py\", \"RabiWidth\", \n", + " (q, rWidthWidths, 1, 0, QGL.PulseShapes.tanh), saveOutput=True)" ] }, { @@ -83,14 +104,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "seqs = qgl1MainFunc()" ] }, { @@ -103,13 +121,15 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "fileNames = qgl2_compile_to_hardware(seqs, \"Rabi/Rabi\")\n", - "print(fileNames)" + "from qgl2.basic_sequences.helpers import delay_descriptor\n", + "axis_desc = [delay_descriptor(rWidthWidths)]\n", + "label = \"Rabi\"\n", + "\n", + "metaFileName = qgl2_compile_to_hardware(seqs, filename=f\"{label}/{label}\", axis_descriptor=axis_desc)\n", + "print(f\"Generated sequence details in '{metaFileName}'\")" ] }, { @@ -124,12 +144,10 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "display(plot_pulse_files(metaFileName))" ] } ], @@ -149,9 +167,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/QGL2 Ramsey.ipynb b/notebooks/QGL2 Ramsey.ipynb index 1592688..154545f 100644 --- a/notebooks/QGL2 Ramsey.ipynb +++ b/notebooks/QGL2 Ramsey.ipynb @@ -12,9 +12,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Using pulseSpacings=np.arange(100e-9, 10e-6, 100e-9); 100ns to 10us step by 100ns\n", @@ -25,32 +23,54 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## imports" + "## Imports" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "from QGL.Plotting import output_notebook\n", - "from QGL.PulseSequencePlotter import plot_pulse_files\n", - "from QGL.ChannelLibrary import QubitFactory\n", - "from pyqgl2.main import compile_function, qgl2_compile_to_hardware" + "from pyqgl2.main import compile_function, qgl2_compile_to_hardware\n", + "from pyqgl2.test_cl import create_default_channelLibrary\n", + "from pyqgl2.qreg import QRegister\n", + "from QGL import plot_pulse_files, ChannelLibrary\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a test ChannelLibrary" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "output_notebook()" + "create_default_channelLibrary(True, True)\n", + "# Alternatively could load an existing library, or create one here; see the 'AllXY' notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create needed qubit(s)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# For QGL2, use a QRegister, not a QGL Qubit\n", + "q = QRegister(1)" ] }, { @@ -65,9 +85,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Insert proper path to QGL2 source and name of qgl2main if not so marked\n", @@ -79,12 +97,14 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], "source": [ - "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/T1T2Min.py\", \"doRamsey\", True)" + "rSpacings = np.linspace(0, 5e-6, 11)\n", + "tCalR = 2\n", + "qgl1MainFunc = compile_function(\"../src/python/qgl2/basic_sequences/T1T2.py\", \"Ramsey\", \n", + " (q,rSpacings, 0, tCalR), saveOutput=True)" ] }, { @@ -97,14 +117,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Now run the QGL1 function, producing a list of sequences\n", - "# Supply name of qubit from channel library, or omit to use a default\n", - "seqs = qgl1MainFunc(q=QubitFactory('q1'))" + "seqs = qgl1MainFunc()" ] }, { @@ -119,14 +136,20 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "# True filename optionally is Ramsey_q1/Ramsey_q1 where q1 is the qubit label\n", - "fileNames = qgl2_compile_to_hardware(seqs, \"Ramsey/Ramsey\")\n", - "print(fileNames)" + "from qgl2.basic_sequences.helpers import delay_descriptor, cal_descriptor\n", + "axis_desc = [ \n", + " delay_descriptor(rSpacings),\n", + " cal_descriptor(('qubit',), tCalR)\n", + "]\n", + "label = \"Ramsey\"\n", + "# Include the qbit name in the filename\n", + "label += \"_q1\"\n", + "\n", + "metaFileName = qgl2_compile_to_hardware(seqs, filename=f\"{label}/{label}\", axis_descriptor=axis_desc)\n", + "print(f\"Generated sequence details in '{metaFileName}'\")" ] }, { @@ -142,12 +165,11 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], "source": [ - "plot_pulse_files(fileNames)" + "display(plot_pulse_files(metaFileName))" ] } ], @@ -167,9 +189,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.1" + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/notebooks/README.md b/notebooks/README.md new file mode 100644 index 0000000..95ffbd7 --- /dev/null +++ b/notebooks/README.md @@ -0,0 +1,10 @@ +# QGL2 Sample Notebooks + +The jupyter notebooks in this directory show a few uses of QGL2, relying on the QGL2 implementation of the QGL Basic Sequences. Note that these notebooks do not include QGL2 programs, but show using QGL2 programs. + +Start with [QGL2 AllXY.ipynb], which will show how to create a 'Channel Library' and turn on debugging. +[QGL2 RabiAmp.ipynb] shows creating and supplying arguments, including specifying the axis descriptors, and requesting that the compiled QGL2 code be saved to a file. +[QGL2 RabiAmp-indirect.ipynb] shows a python program that runs a previously compiled QGL2 program. +[QGL2 RabiSingleShot.ipynb] shows turning on debugging within the QGL compiler. + +See [src/python/qgl2/basic_sequences] for sample QGL2 code, including non-notebook uses of QGL2. \ No newline at end of file diff --git a/notebooks/RabiAmpqgl1.py b/notebooks/RabiAmpqgl1.py new file mode 100644 index 0000000..6fc1117 --- /dev/null +++ b/notebooks/RabiAmpqgl1.py @@ -0,0 +1,15 @@ +def RabiAmp(): + from QGL import QubitFactory + from QGL.PulsePrimitives import MEAS + from QGL.PulsePrimitives import Utheta + from qgl2.qgl1_util import init_real as init + + QBIT_1 = QubitFactory('q1') + from pyqgl2.eval import EvalTransformer + _v = EvalTransformer.PRECOMPUTED_VALUES + seq = [ + init(QBIT_1), + Utheta(QBIT_1, amp=0.0, phase=0), + MEAS(QBIT_1) + ] + return seq diff --git a/setup.py b/setup.py index 1e25743..d739c57 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +# NOTE: If you update this, be sure .travis.yml is updated + from setuptools import setup setup(name="pyqgl2", packages=["src\python\pyqgl2","src\python\qgl2"],) diff --git a/src/python/pyqgl2/inline.py b/src/python/pyqgl2/inline.py index ea65baa..ba9aedb 100644 --- a/src/python/pyqgl2/inline.py +++ b/src/python/pyqgl2/inline.py @@ -569,6 +569,7 @@ def create_inline_procedure(func_ptree, call_ptree): new_name = tmp_names.create_tmp_name(orig_name=orig_name) actual_param = seen_param_names[orig_name] rewriter.add_mapping(orig_name, new_name) + # print(f"Add rewriter mapping from {orig_name} to {new_name} for actual param {ast.dump(actual_param)}") # NodeError.diag_msg(call_ptree, # 'ASSIGN %s -> %s' % (new_name, ast.dump(actual_param))) @@ -663,6 +664,7 @@ def create_inline_procedure(func_ptree, call_ptree): new_name = tmp_names.create_tmp_name(orig_name=orig_name) rewriter.add_mapping(orig_name, new_name) + # print(f"Added rewriter mapping from {param_ind}th keyword param {orig_name} to {new_name}") # NodeError.diag_msg(call_ptree, # ('DASSIGN %s -> %s' % @@ -710,6 +712,7 @@ def create_inline_procedure(func_ptree, call_ptree): isinstance(actual, ast.NameConstant)) and is_static_ref(func_ptree, name)): rewriter.add_constant(name, actual) + # print(f"Added rewriter map from {name} to constant '{actual}'") else: new_name = rewriter.get_mapping(name) new_setup_locals.append(ast.Assign( @@ -769,14 +772,21 @@ def create_inline_procedure(func_ptree, call_ptree): # # TODO: what if the expression isn't a const? # - if not isinstance( - rewriter.name2const[formal_param.arg], ast.Name): + # Note that here we now allow a qbit to be a None NameConstant (ie from a default) + if isinstance(rewriter.name2const[formal_param.arg], ast.Name): + qbit_fparams.append(formal_param.arg) + qbit_aparams.append(rewriter.name2const[formal_param.arg].id) + elif isinstance(rewriter.name2const[formal_param.arg], ast.NameConstant) and rewriter.name2const[formal_param.arg].value == None: + # A qbit argument given a default of None in the method signature should be OK + qbit_fparams.append(formal_param.arg) + qbit_aparams.append(None) + else: + NodeError.error_msg(call_ptree, "Had param '%s' annotated as a qbit but it is not a Name (is %s). Return list() when trying to inline '%s': %s" % (formal_param.arg, type(rewriter.name2const[formal_param.arg]), ast2str(call_ptree).strip(), ast2str(formal_param).strip())) return list() - qbit_fparams.append(formal_param.arg) - qbit_aparams.append(rewriter.name2const[formal_param.arg].id) - - qbit_aparams_txt = ', '.join(sorted(qbit_aparams)) + # Unused.... + # Note that above we now include None in the list of aparams, which we can't then sort + # qbit_aparams_txt = ', '.join(sorted(qbit_aparams)) # Now check whether any formal qbit parameters map onto # the same actual, by examining the name rewriter. @@ -794,15 +804,19 @@ def create_inline_procedure(func_ptree, call_ptree): # way to tell the user what the *local* name is, which # is usually more useful # - qbit_aparam = rewriter.name2const[qbit_fparam].id - if qbit_aparam in qbit_aparams_reverse: - NodeError.error_msg(call_ptree, - 'dup qbit [%s] used for [%s] and [%s]' % ( - qbit_aparam, - qbit_aparams_reverse[qbit_aparam], qbit_fparam)) - return None - else: - qbit_aparams_reverse[qbit_aparam] = qbit_fparam + # Note that here we only worry about qbits that map to a variable + # In particular, a qbit that maps to None (a default value), is ignored here + qbit_mapped = rewriter.name2const[qbit_fparam] + if isinstance(qbit_mapped, ast.Name): + qbit_aparam = qbit_mapped.id + if qbit_aparam in qbit_aparams_reverse: + NodeError.error_msg(call_ptree, + 'dup qbit [%s] used for [%s] and [%s]' % ( + qbit_aparam, + qbit_aparams_reverse[qbit_aparam], qbit_fparam)) + return None + else: + qbit_aparams_reverse[qbit_aparam] = qbit_fparam # We want to preserve a reference to the original call, # and make sure it doesn't get clobbered. @@ -934,6 +948,8 @@ def visit_Name(self, node): # # TODO: add a comment, if possible, with the name of the variable + # Trying to turn python back into AST + value = self.values[name] numpy_scalar_types = ( @@ -972,15 +988,19 @@ def visit_Name(self, node): redirection.elts[ct] = ast.Name(id=element.use_name(), ctx=ast.Load()) redirection.elts[ct].qgl_is_qbit = True - except: + except Exception as e: NodeError.warning_msg(node, - "Could not represent the value [%s] of [%s] as an AST node" % (value, name)) + "Could not represent the value [%s] of [%s] (iterable) as an AST node: %s" % (value, name, e)) redirection = ast.Subscript( value=ast.Name(id=self.table_name, ctx=ast.Load()), slice=ast.Index(value=ast.Str(s=name))) else: + # value is not an int, float, str, QRegister, QReference or iterable + # EG, could be a function reference + # Want AST from python. Instaed of making an AST that points to the actual function referenced by 'tanh' + # or whatever, create a reference to 'tanh' and hope no-one redefines 'tanh'. NodeError.warning_msg(node, - "Could not represent the value [%s] of [%s] as an AST node" % (value, name)) + "Could not represent the value [%s] (an %s) of [%s] as an AST node." % (value, type(value), name)) redirection = ast.Subscript( value=ast.Name(id=self.table_name, ctx=ast.Load()), slice=ast.Index(value=ast.Str(s=name))) @@ -1478,8 +1498,8 @@ def add_runtime_checks(self, call_ptree): if not isinstance(call_ptree.func, ast.Name): NodeError.error_msg( call_ptree, - ('cannot inline function expression [%s]' - % ast2str(call_ptree).strip())) + ('cannot inline function expression [%s] (type %s)' + % (ast2str(call_ptree).strip(), type(call_ptree.func)))) return None func_name = collapse_name(call_ptree.func) @@ -1934,8 +1954,8 @@ def inline_call(base_call, importer): if not isinstance(base_call.func, ast.Name): NodeError.error_msg( base_call, - ('function not called by name [%s]' % - ast2str(base_call).strip())) + ('function not called by name [%s]: func is a %s' % + (ast2str(base_call).strip(), type(base_call.func)))) return base_call func_filename = base_call.qgl_fname diff --git a/src/python/pyqgl2/main.py b/src/python/pyqgl2/main.py index 762ea4d..2d6c11e 100755 --- a/src/python/pyqgl2/main.py +++ b/src/python/pyqgl2/main.py @@ -110,6 +110,9 @@ def parse_args(argv): parser.add_argument('-hw', dest='tohw', default=False, action='store_true', help='Compile sequences to hardware (default %(default)s)') + # FIXME: Support getting other arguments from the commandline for passing + # into the qgl2decl + options = parser.parse_args(argv) # for the sake of consistency and brevity, convert the path @@ -345,7 +348,7 @@ def compile_function(filename, NodeError.halt_on_error() return qgl1_main -def qgl2_compile_to_hardware(seqs, filename, suffix=''): +def qgl2_compile_to_hardware(seqs, filename, suffix='', axis_descriptor=None, extra_meta=None, tdm_seq = False): ''' Custom compile_to_hardware for QGL2 ''' @@ -355,7 +358,7 @@ def qgl2_compile_to_hardware(seqs, filename, suffix=''): scheduled_seq = schedule(seqs) - return compile_to_hardware([scheduled_seq], filename, suffix) + return compile_to_hardware([scheduled_seq], filename, suffix=suffix, axis_descriptor=axis_descriptor, extra_meta=extra_meta, tdm_seq=tdm_seq) ###### # Run the main with @@ -402,6 +405,8 @@ def qgl2_compile_to_hardware(seqs, filename, suffix=''): print('No valid ChannelLibrary found') sys.exit(1) + # FIXME: parse remaining commandling arguments as toplevel_bindings + resFunction = compile_function( opts.filename, opts.main_name, toplevel_bindings=None, saveOutput=opts.saveOutput, @@ -432,6 +437,7 @@ def qgl2_compile_to_hardware(seqs, filename, suffix=''): if opts.tohw: print("Compiling sequences to hardware\n") + # FIXME: Add option to supply axis_descriptors? fileNames = qgl2_compile_to_hardware(sequences, opts.prefix, opts.suffix) print(fileNames) diff --git a/src/python/pyqgl2/qreg.py b/src/python/pyqgl2/qreg.py index 68fb4ae..b9a7cfe 100644 --- a/src/python/pyqgl2/qreg.py +++ b/src/python/pyqgl2/qreg.py @@ -34,8 +34,11 @@ def __init__(self, *args): ''' Valid constructor calls: QRegister(N) where N is an integer + Or mix and match or use lists or tuples of: QRegister("q2", "q5", ...) where the strings are the names of Qubits QRegister(qr1, qr2, qr3, ...) where each object is a QRegister + QRegister(q1, q2, ...) where each object is a QGL Qubit + NOTE: Qubit names must currently be of the form "q" ''' self.qubits = [] @@ -50,28 +53,9 @@ def __init__(self, *args): if ct not in QRegister.KNOWN_QUBITS: self.qubits.append(ct) ct += 1 - elif len(args) == 1 and isinstance(args[0], QRegister): - # duplicates the QRegister; allows failsafe creating QRegiser whatever the arg - self.qubits = args[0].qubits - elif all(isinstance(x, str) for x in args): - # named qubits - for arg in args: - # assume names are of the form "qN" - # TODO throw an error if the provided string doesn't have that form - try: - idx = int(arg[1:]) - except ValueError as ve: - raise ValueError(f"QRegister names must be of the form q: '{arg}'") - self.qubits.append(idx) - elif all(isinstance(x, QRegister) for x in args): - # concatenated register - for arg in args: - # FIXME: What overlaps does this disallow? - if arg.qubits in self.qubits: - raise NameError(f"Non-disjoint qubit sets in concatenated registers. {arg} has duplicates with others in {args}") - self.qubits.extend(arg.qubits) else: - raise NameError(f"Invalid QRegister constructor 'QRegister({args})'.") + for x in args: + self.addArg(x, args) # add qubits to KNOWN_QUBITS for q in self.qubits: @@ -80,6 +64,52 @@ def __init__(self, *args): QRegister.NUM_REGISTERS += 1 self.reg_name = 'QREG_' + str(QRegister.NUM_REGISTERS) + def addArg(self, x, args): + """Parse one argument to the QRegister constructor. A recursive function.""" + # Careful; avoid global import of QGL1 stuff from QGL2 + from QGL.Channels import Qubit + if hasattr(x, "__iter__") and not isinstance(x, str) and not isinstance(x, QRegister): + for xx in x: + self.addArg(xx, args) + elif isinstance(x, QRegister): + # duplicates the QRegister; allows failsafe creating QRegister whatever the arg + for q in x.qubits: + if q not in self.qubits: + self.qubits.append(q) + else: + # Duplicate - skip + # FIXME: Print warning? + pass + elif isinstance(x, str): + # assume names are of the form "qN" + # TODO throw an error if the provided string doesn't have that form + try: + idx = int(x[1:]) + except ValueError as ve: + raise ValueError(f"QRegister names must be of the form q: '{x}' (from args {args})") + if idx not in self.qubits: + self.qubits.append(idx) + else: + # duplicate - skip + pass + elif isinstance(x, Qubit): + # assume names are of the form "qN" + # TODO throw an error if the provided string doesn't have that form + try: + idx = int(x.label[1:]) + except ValueError as ve: + raise ValueError(f"QRegister names must be of the form q: '{x}' (from args {args})") + if idx not in self.qubits: + self.qubits.append(idx) + else: + # duplicate - skip + # FIXME: Print warning? + pass + elif isinstance(x, QReference): + self.addArg(x.ref[x.idx], args) + else: + raise NameError(f"Invalid QRegister constructor 'QRegister({args})'; arg {x} unknown.") + def __repr__(self): return str(self) diff --git a/src/python/qgl2/Cliffords.py b/src/python/qgl2/Cliffords.py new file mode 100644 index 0000000..eade898 --- /dev/null +++ b/src/python/qgl2/Cliffords.py @@ -0,0 +1,286 @@ +""" +Manipulating Cliffords. Mainly for RB purposes. +NOTE: This duplicates content in QGL. We defer to the QGL implementation for clifford_seq (and helpers), +which allows RB to use clifford_mat, inverse_clifford directly. +clifford_seq is a stub in qgl1.py +""" + +# TODO: Re-implement clifford_seq as QGL2. See issues #51-53. +# If clifford_seq was split into a 1qbit and 2qbit variety, Cx2 could use the 1qbit version directly and avoid looping (#52) + +import numpy as np +from scipy.linalg import expm +from numpy import pi +from itertools import product +from random import choice +import operator +from functools import reduce + +from qgl2.qgl1 import Id, X90, X90m, Y90, Y90m, X, Xm, Y, Ym, ZX90_CR + +# Single qubit paulis +pX = np.array([[0, 1], [1, 0]], dtype=np.complex128) +pY = np.array([[0, -1j], [1j, 0]], dtype=np.complex128) +pZ = np.array([[1, 0], [0, -1]], dtype=np.complex128) +pI = np.eye(2, dtype=np.complex128) + + +# def pauli_mats(n): +# """ +# Return a list of n-qubit Paulis as numpy array. +# """ +# assert n > 0, "You need at least 1 qubit!" +# if n == 1: +# return [pI, pX, pY, pZ] +# else: +# paulis = pauli_mats(n - 1) +# return [np.kron(p1, p2) +# for p1, p2 in product([pI, pX, pY, pZ], paulis)] + +# Basis single-qubit Cliffords with an arbitrary enumeration +C1 = {} +C1[0] = pI +C1[1] = expm(-1j * (pi / 4) * pX) +C1[2] = expm(-2j * (pi / 4) * pX) +C1[3] = expm(-3j * (pi / 4) * pX) +C1[4] = expm(-1j * (pi / 4) * pY) +C1[5] = expm(-2j * (pi / 4) * pY) +C1[6] = expm(-3j * (pi / 4) * pY) +C1[7] = expm(-1j * (pi / 4) * pZ) +C1[8] = expm(-2j * (pi / 4) * pZ) +C1[9] = expm(-3j * (pi / 4) * pZ) +C1[10] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pX + pY)) +C1[11] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pX - pY)) +C1[12] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pX + pZ)) +C1[13] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pX - pZ)) +C1[14] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pY + pZ)) +C1[15] = expm(-1j * (pi / 2) * (1 / np.sqrt(2)) * (pY - pZ)) +C1[16] = expm(-1j * (pi / 3) * (1 / np.sqrt(3)) * (pX + pY + pZ)) +C1[17] = expm(-2j * (pi / 3) * (1 / np.sqrt(3)) * (pX + pY + pZ)) +C1[18] = expm(-1j * (pi / 3) * (1 / np.sqrt(3)) * (pX - pY + pZ)) +C1[19] = expm(-2j * (pi / 3) * (1 / np.sqrt(3)) * (pX - pY + pZ)) +C1[20] = expm(-1j * (pi / 3) * (1 / np.sqrt(3)) * (pX + pY - pZ)) +C1[21] = expm(-2j * (pi / 3) * (1 / np.sqrt(3)) * (pX + pY - pZ)) +C1[22] = expm(-1j * (pi / 3) * (1 / np.sqrt(3)) * (-pX + pY + pZ)) +C1[23] = expm(-2j * (pi / 3) * (1 / np.sqrt(3)) * (-pX + pY + pZ)) + + +# A little memoize decorator +def memoize(function): + cache = {} + + def decorated(*args): + if args not in cache: + cache[args] = function(*args) + return cache[args] + + return decorated + + +# @memoize +# def clifford_multiply(cliff1, cliff2): +# ''' +# Multiplication table for single qubit cliffords. Note this assumes cliff1 is applied first. +# i.e. clifford_multiply(cliff1, cliff2) calculates cliff2*cliff1 +# ''' +# tmpMult = np.dot(C1[cliff2], C1[cliff1]) +# checkArray = np.array( +# [np.abs(np.trace(np.dot(tmpMult.transpose().conj(), C1[x]))) +# for x in range(24)]) +# return checkArray.argmax() + +# # We can usually (without atomic Cliffords) only apply a subset of the single-qubit Cliffords +# # i.e. the pulses that we can apply: Id, X90, X90m, Y90, Y90m, X, Y +# generatorPulses = [0, 1, 3, 4, 6, 2, 5] + + +# def generator_pulse(G): +# """ +# A function that returns the pulse corresponding to a generator +# Randomly chooses between the -p and -m versions for the 180's +# """ +# generatorPulses = {0: (Id, ), +# 1: (X90, ), +# 3: (X90m, ), +# 4: (Y90, ), +# 6: (Y90m, ), +# 2: (X, Xm), +# 5: (Y, Ym)} +# return choice(generatorPulses[G]) + +# # Get all combinations of generator sequences up to length three +# generatorSeqs = [x for x in product(generatorPulses,repeat=1)] + \ +# [x for x in product(generatorPulses,repeat=2)] + \ +# [x for x in product(generatorPulses,repeat=3)] + +# # Find the effective unitary for each generator sequence +# reducedSeqs = np.array([reduce(clifford_multiply, x) for x in generatorSeqs]) + +# Pick first generator sequence (and thus shortest) that gives each Clifford and then +# also add all those that have the same length + +# # First for each of the 24 single-qubit Cliffords find which sequences create them +# allC1Seqs = [np.nonzero(reducedSeqs == x)[0] for x in range(24)] +# # And the length of the first one for all 24 +# minSeqLengths = [len(generatorSeqs[seqs[0]]) for seqs in allC1Seqs] +# # Now pull out all those that are the same length as the first one +# C1Seqs = [] +# for minLength, seqs in zip(minSeqLengths, allC1Seqs): +# C1Seqs.append([s for s in seqs if len(generatorSeqs[s]) == minLength]) + +C2Seqs = [] +""" +The IBM paper has the Sgroup (rotation n*(pi/3) rotations about the X+Y+Z axis) +Sgroup = [C[0], C[16], C[17]] + +The two qubit Cliffords can be written down as the product of +1. A choice of one of 24^2 C times C single-qubit Cliffords +2. Optionally an entangling gate from CNOT, iSWAP and SWAP +3. Optional one of 9 S times S gate + +Therefore, we'll enumerate the two-qubit Clifford as a three tuple ((c1,c2), Entangling, (s1,s2)) +""" + +# 1. All pairs of single-qubit Cliffords +for c1, c2 in product(range(24), repeat=2): + C2Seqs.append(((c1, c2), None, None)) + +# 2. The CNOT-like class, replacing the CNOT with a echoCR +# TODO: sort out whether we need to explicitly encorporate the single qubit rotations into the trailing S gates +# The leading single-qubit Cliffords are fully sampled so they should be fine + +for (c1, c2), (s1, s2) in product( + product( + range(24), repeat=2), + product([0, 16, 17], repeat=2)): + C2Seqs.append(((c1, c2), "CNOT", (s1, s2))) + +# 3. iSWAP like class - replacing iSWAP with (echoCR - (Y90m*Y90m) - echoCR) +for (c1, c2), (s1, s2) in product( + product( + range(24), repeat=2), + product([0, 16, 17], repeat=2)): + C2Seqs.append(((c1, c2), "iSWAP", (s1, s2))) + +# 4. SWAP like class +for c1, c2 in product(range(24), repeat=2): + C2Seqs.append(((c1, c2), "SWAP", None)) + + +# @qgl2decl +# def Cx2(cliff1, cliff2, q1: qreg, q2: qreg) -> sequence: +# """ +# Helper function to create pulse block for a pair of single-qubit Cliffords +# """ +# # Create list of pulse objects on the qubits +# qr = QRegister(q1, q2) +# Barrier(qr) +# clifford_seq(cliff1, q1) +# clifford_seq(cliff2, q2) + +# @qgl2decl +# def entangling_seq(gate, q1: qreg, q2: qreg) -> sequence: +# """ +# Helper function to create the entangling gate sequence +# """ +# qr = QRegister(q1, q2) +# if gate == "CNOT": +# ZX90_CR(q2, q1) +# elif gate == "iSWAP": +# ZX90_CR(q2, q1) +# Barrier(qr) +# Y90m(q1) +# Y90m(q2) +# ZX90_CR(q2, q1) +# elif gate == "SWAP": +# ZX90_CR(q2, q1) +# Barrier(qr) +# Y90m(q1) +# Y90m(q2) +# ZX90_CR(q2, q1) +# Barrier(qr) +# X90(q1) +# Y90m(q1) +# X90(q2) +# ZX90_CR(q2, q1) + +def entangling_mat(gate): + """ + Helper function to create the entangling gate matrix + """ + echoCR = expm(1j * pi / 4 * np.kron(pX, pZ)) + if gate == "CNOT": + return echoCR + elif gate == "iSWAP": + return reduce(lambda x, y: np.dot(y, x), + [echoCR, np.kron(C1[6], C1[6]), echoCR]) + elif gate == "SWAP": + return reduce(lambda x, y: np.dot(y, x), + [echoCR, np.kron(C1[6], C1[6]), echoCR, np.kron( + np.dot(C1[6], C1[1]), C1[1]), echoCR]) + else: + raise ValueError("Entangling gate must be one of: CNOT, iSWAP, SWAP.") + +# TODO: If neccessary for issue #52, make this clifford_Seq_one1 and clifford_seq_twoq +# with clifford_seq doing if q2 is None: ... elif isinstance(q2, QRegister): ... else: raise TypeError + +# @qgl2decl +# def clifford_seq(c, q1: qreg, q2: qreg=None) -> sequence: +# """ +# Return a sequence of pulses that implements a clifford C +# """ +# # If qubit2 not defined assume 1 qubit +# if not q2: +# genSeq = generatorSeqs[choice(C1Seqs[c])] +# for g in genSeq: +# generator_pulse(g)(q1) +# # Or to handle issue #53: +# # func = generator_pulse(g) +# # func(q1) +# else: +# # Look up the sequence for the integer +# cseq = C2Seqs[c] +# Cx2(cseq[0][0], cseq[0][1], q1, q2) +# if cseq[1]: +# entangling_seq(cseq[1], q1, q2) +# if cseq[2]: +# Cx2(c[2][0], cseq[2][1], q1, q2) + + +@memoize +def clifford_mat(c, numQubits): + """ + Return the matrix unitary the implements the qubit clifford C + """ + assert numQubits <= 2, "Oops! I only handle one or two qubits" + if numQubits == 1: + return C1[c] + else: + c = C2Seqs[c] + mat = np.kron(clifford_mat(c[0][0], 1), clifford_mat(c[0][1], 1)) + if c[1]: + mat = np.dot(entangling_mat(c[1]), mat) + if c[2]: + mat = np.dot( + np.kron( + clifford_mat(c[2][0], 1), clifford_mat(c[2][1], 1)), mat) + return mat + + +def inverse_clifford(cMat): + dim = cMat.shape[0] + if dim == 2: + for ct in range(24): + if np.isclose( + np.abs(np.dot(cMat, clifford_mat(ct, 1)).trace()), dim): + return ct + elif dim == 4: + for ct in range(len(C2Seqs)): + if np.isclose( + np.abs(np.dot(cMat, clifford_mat(ct, 2)).trace()), dim): + return ct + else: + raise Exception("Expected 2 or 4 qubit dimensional matrix.") + + # If we got here something is wrong + raise Exception("Couldn't find inverse clifford") diff --git a/src/python/qgl2/basic_sequences/AllXY.py b/src/python/qgl2/basic_sequences/AllXY.py index cb6173d..5e36973 100644 --- a/src/python/qgl2/basic_sequences/AllXY.py +++ b/src/python/qgl2/basic_sequences/AllXY.py @@ -41,17 +41,9 @@ def AllXY(q: qreg): f2(q) MEAS(q) - -@qgl2decl -def doAllXY(): - # All in one QGL2 function to avoid args - from qgl2.qgl2 import QRegister - q = QRegister('q1') - AllXY(q) - # QGL1 function to compile the above QGL2 -# Uses main.py -# FIXME: Use the same argument parsing +# Uses main.py functions +# FIXME: Use the same argument parsing as main.py def main(): from pyqgl2.qreg import QRegister import pyqgl2.test_cl @@ -87,7 +79,7 @@ def main(): if toHW: print("Compiling sequences to hardware\n") # file prefix AllXY/AllXY, no suffix - fileNames = qgl2_compile_to_hardware(sequences, 'AllXY/AllXY') + fileNames = qgl2_compile_to_hardware(sequences, filename='AllXY/AllXY') print(f"Compiled sequences; metafile = {fileNames}") if plotPulses: from QGL.PulseSequencePlotter import plot_pulse_files @@ -102,9 +94,4 @@ def main(): print(pretty(scheduled_seq)) if __name__ == "__main__": - # I'd like to call pyqgl2.main but with particular args - # if -toHW or -showplot, pass those on - # to run a basic sequence, run - # basic_sequences/filename.py -m doTestname [-toHW [-showplt]] - # pyqgl2.main.__main__(sys.argv[1:] main() diff --git a/src/python/qgl2/basic_sequences/BlankingSweeps.py b/src/python/qgl2/basic_sequences/BlankingSweeps.py index da2269f..77f2970 100644 --- a/src/python/qgl2/basic_sequences/BlankingSweeps.py +++ b/src/python/qgl2/basic_sequences/BlankingSweeps.py @@ -2,31 +2,41 @@ """ Sequences for optimizing gating timing. +OBE: This assumes you modify the gateDelay on a generator. That is no longer a thing. """ from qgl2.qgl2 import qgl2decl, qreg - from qgl2.qgl1 import Id, X90, MEAS - -from QGL.Compiler import compile_to_hardware +from qgl2.util import init @qgl2decl -def sweep_gateDelay(qubit: qreg, sweepPts): +def sweep_gateDelaySeqs(qubit: qreg): + # QGL2 function to generate the sequence used in sweep_gateDelay + # a simple Id, Id, X90, X90 sequence + init(qubit) + Id(qubit, length=120e-9) + Id(qubit) + MEAS(qubit) + Id(qubit, length=120e-9) + MEAS(qubit) + Id(qubit, length=120e-9) + X90(qubit) + MEAS(qubit) + Id(qubit, length=120e-9) + X90(qubit) + MEAS(qubit) + +def sweep_gateDelay(qubit, sweepPts, qgl2func, plotPulses=False): """ - Sweep the gate delay associated with a qubit channel using a simple Id, Id, X90, X90 - seqeuence. + OBE: Sweep the gate delay associated with a qubit channel using a simple Id, Id, X90, X90 + sequence. But the gateDelay on a generator is no longer a thing Parameters --------- qubit : logical qubit to create sequences for sweepPts : iterable to sweep the gate delay over. + qgl2func : compiled QGL2 function that generates sequences when run """ - raise NotImplementedError("Not implemented") - - # Not apparently used? - - # Problem in doing in QGL2: Need params of the real qubit, which we don't have - # Original: # generator = qubit.phys_chan.generator # oldDelay = generator.gateDelay @@ -42,3 +52,92 @@ def sweep_gateDelay(qubit: qreg, sweepPts): # compile_to_hardware(seqs, 'BlankingSweeps/GateDelay', suffix='_{}'.format(ct+1)) # generator.gateDelay = oldDelay + + # Problem in doing in QGL2: Need params of the real qubit, which we don't have. + # SO, use a combo: QGL1 (looping and doing compile) and QGL2 (generating sequences) + from pyqgl2.main import qgl2_compile_to_hardware + + # WONTFIX: Generators no longer have a gateDelay + # But this shows the kind of thing you could do when mixing QGL1 and QGL2 + # generator = qubit.phys_chan.generator + # oldDelay = generator.gateDelay + + for ct, delay in enumerate(sweepPts): + seqs = qgl2func() + # generator.gateDelay = delay + metafile=qgl2_compile_to_hardware(seqs, filename='BlankingSweeps/GateDelay', suffix='_{}'.format(ct+1)) + print(f"Compiled sequences; metafile = {metafile}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(metafile) + + # generator.gateDelay = oldDelay + + +# QGL1 function to compile the above QGL2 +# Uses main.py functions +# FIXME: Use the same argument parsing as main.py +# NOTE: This does not wark, as generator no longer has a gateDelay +def main(): + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function + + toHW = True + plotPulses = True + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + # Note: That doesn't put a generator on the phys_chan, which we would need to really run this + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in a QRegister NOT the real Qubit + q = QRegister(1) + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunction = compile_function(__file__, + "sweep_gateDelaySeqs", + (q,)) + # Run the QGL2. Note that the generated function takes no arguments itself + if toHW: + from QGL.ChannelLibraries import QubitFactory + import numpy as np + print("Compiling sequences to hardware\n") + qubit = QubitFactory('q1') + if qubit.phys_chan is None: + print(f"Qubit {qubit} missing phys_chan") + return +# elif qubit.phys_chan.generator is None: +# print(f"Qubit {qubit} on phys_chan {qubit.phys_chan} missing phys_chan.generator") +# return + + # FIXME: What's a reasonable value here? + sweepPts = np.linspace(0, 5e-6, 11) + + # Here we call a special QGL1 function that uses the compiled QGL2 function to generate sequences + # We redo the sequence generation to avoid the compiler modifying the sequence causing problems + # This function will handle compile_to_hardware and plot_pulse_files + sweep_gateDelay(qubit, sweepPts, resFunction, plotPulses) + else: + sequences = resFunction() + print("\nGenerated sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(sequences) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) + +if __name__ == "__main__": + main() diff --git a/src/python/qgl2/basic_sequences/CR.py b/src/python/qgl2/basic_sequences/CR.py index 617d1d0..688ea9c 100644 --- a/src/python/qgl2/basic_sequences/CR.py +++ b/src/python/qgl2/basic_sequences/CR.py @@ -4,7 +4,7 @@ from qgl2.qgl1 import Id, X, MEAS, X90, flat_top_gaussian_edge, echoCR, Y90m -from qgl2.basic_sequences.helpers import create_cal_seqs, measConcurrently +from qgl2.basic_sequences.helpers import create_cal_seqs, measConcurrently, cal_descriptor, delay_descriptor from qgl2.util import init from itertools import product @@ -301,19 +301,72 @@ def main(): q1 = QRegister("q1") q2 = QRegister("q2") + # Axis Descriptor generator functions here + # This is ugly; they're method dependent, but I can't do them in the QGL2 itself + # Additionally, each uses values from the args to the function + # So here we make those arguments be constants so we can use them twice + # without rewriting the values + pirlengths = np.linspace(0, 4e-6, 11) # Lengths arg to PiRabi + eclLengths = np.linspace(0, 2e-6, 11) # Lengths arg to EchoCRLen + trisefall = 40e-9 # riseFall arg for many + tamp = 1 # amp arg for many + t2amp = 0.8 # amp arg for CRTomo + tphase = 0 # phase arg for many + tcalr = 2 # calRepeats arg for many + ecpPhases = np.linspace(0, np.pi/2, 11) # phases arg for EchoCRPhase + ecaAmps = np.linspace(0, 5e-6, 11) # amps arg for echoCRAmp + crtLengths = np.linspace(0, 2e-6, 11) # lengths arg for CRtomo_seq + def getPRAxisDesc(lengths, calRepeats): + return [ + delay_descriptor(np.concatenate((lengths, lengths))), + # Hard code there are 2 qubits + cal_descriptor(('c', 't'), calRepeats) + ] + def getECLAxisDesc(lengths, calRepeats): + return [ + delay_descriptor(np.concatenate((lengths, lengths))), + # Hard code there are 2 qubits + cal_descriptor(('controlQ', 'targetQ'), calRepeats) + ] + def getECPAxisDesc(phases, calRepeats): + return [ + { + 'name': 'phase', + 'unit': 'radians', + 'points': list(phases)+list(phases), + 'partition': 1 + }, + cal_descriptor(('controlQ', 'targetQ'), calRepeats) + ] + def getECAAxisDesc(amps, calRepeats): + return [ + { + 'name': 'amplitude', + 'unit': None, + 'points': list(amps)+list(amps), + 'partition': 1 + }, + cal_descriptor(('controlQ', 'targetQ'), calRepeats) + ] + def getCRtAxisDesc(lengths): + return [ + delay_descriptor(np.concatenate((np.repeat(lengths,3), np.repeat(lengths,3)))), + cal_descriptor(('targetQ',), 2) + ] + # FIXME: See issue #44: Must supply all args to qgl2main for now -# for func, args, label in [("PiRabi", (q1, q2, np.linspace(0, 4e-6, 11)), "PiRabi"), -# ("EchoCRLen", (q1, q2, np.linspace(0, 2e-6, 11)), "EchoCR"), -# ("EchoCRPhase", (q1, q2, np.linspace(0, np.pi/2, 11)), "EchoCR"), -# ("EchoCRAmp", (q1, q2, np.linspace(0, 5e-6, 11)), "EchoCR"), # FIXME: Right values? -# ("CRtomo_seq", (q1, q2, np.linspace(0, 2e-6, 11), 0), "CR") # FIXME: Right values? +# for func, args, label, axisDesc in [("PiRabi", (q1, q2, pirlengths), "PiRabi", getPRAxisDesc(pirlengths, tcalr)), +# ("EchoCRLen", (q1, q2, np.linspace(0, 2e-6, 11)), "EchoCR", getECLAxisDesc(eclLengths, tcalr)), +# ("EchoCRPhase", (q1, q2, np.linspace(0, np.pi/2, 11)), "EchoCR", getECPAxisDesc(ecpPhases, tcalr)), +# ("EchoCRAmp", (q1, q2, np.linspace(0, 5e-6, 11)), "EchoCR", getECAAxisDesc(ecaAmps, tcalr)), # FIXME: Right values? +# ("CRtomo_seq", (q1, q2, np.linspace(0, 2e-6, 11), 0), "CR", getCRtAxisDesc(crtLengths)) # FIXME: Right values? # ]: - for func, args, label in [("PiRabi", (q1, q2, np.linspace(0, 4e-6, 11), 40e-9,1,0,2), "PiRabi"), - ("EchoCRLen", (q1, q2, np.linspace(0, 2e-6, 11),40e-9,1,0,2,0,np.pi/2), "EchoCR"), - ("EchoCRPhase", (q1, q2, np.linspace(0, np.pi/2, 11),40e-9,1,100e-9,2,0,np.pi/2), "EchoCR"), - ("EchoCRAmp", (q1, q2, np.linspace(0, 5e-6, 11),40e-9,50e-9,0,2), "EchoCR"), # FIXME: Right values? - ("CRtomo_seq", (q1, q2, np.linspace(0, 2e-6, 11), 0, 0.8,20e-9), "CR") # FIXME: Right values? + for func, args, label, axisDesc in [("PiRabi", (q1, q2, pirlengths, trisefall,tamp,tphase,tcalr), "PiRabi", getPRAxisDesc(pirlengths, tcalr)), + ("EchoCRLen", (q1, q2, eclLengths, trisefall, tamp, tphase, tcalr, 0, np.pi/2), "EchoCR", getECLAxisDesc(eclLengths, tcalr)), + ("EchoCRPhase", (q1, q2, ecpPhases, trisefall,tamp,100e-9,tcalr,0,np.pi/2), "EchoCR", getECPAxisDesc(ecpPhases, tcalr)), + ("EchoCRAmp", (q1, q2, ecaAmps, trisefall,50e-9,tphase,tcalr), "EchoCR", getECAAxisDesc(ecaAmps, tcalr)), # FIXME: Right values? + ("CRtomo_seq", (q1, q2, crtLengths, 0, t2amp,20e-9), "CR", getCRtAxisDesc(crtLengths)) # FIXME: Right values? ]: print(f"\nRun {func}...") @@ -325,7 +378,7 @@ def main(): seq = resFunc() if toHW: print(f"Compiling {func} sequences to hardware\n") - fileNames = qgl2_compile_to_hardware(seq, f'{label}/{label}') + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}', axis_descriptor=axisDesc) print(f"Compiled sequences; metafile = {fileNames}") if plotPulses: from QGL.PulseSequencePlotter import plot_pulse_files diff --git a/src/python/qgl2/basic_sequences/Decoupling.py b/src/python/qgl2/basic_sequences/Decoupling.py index ceb85ea..a8eca71 100644 --- a/src/python/qgl2/basic_sequences/Decoupling.py +++ b/src/python/qgl2/basic_sequences/Decoupling.py @@ -1,12 +1,9 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. -# See DecouplingMin for versions that work better in QGL2 +from qgl2.qgl2 import qgl2decl, qreg, QRegister -from qgl2.qgl2 import qgl2decl, qreg - -from qgl2.qgl1 import X90, Id, Y, U90, MEAS, idPulseCentered -from qgl2.basic_sequences.helpers import create_cal_seqs -#from qgl2.basic_sequences.new_helpers import addCalibration, compileAndPlot +from qgl2.qgl1 import X90, Id, Y, U90, MEAS, pulseCentered +from qgl2.basic_sequences.helpers import create_cal_seqs, cal_descriptor, delay_descriptor from qgl2.util import init from math import pi @@ -53,7 +50,6 @@ def HahnEcho(qubit: qreg, pulseSpacings, periods = 0, calRepeats=2): # compileAndPlot('Echo/Echo', showPlot) -# No nested QGL2 functions; can have 1 call another though @qgl2decl def CPMG(qubit: qreg, numPulses, pulseSpacing, calRepeats=2): """ @@ -90,9 +86,9 @@ def CPMG(qubit: qreg, numPulses, pulseSpacing, calRepeats=2): X90(qubit) # Repeat the t-180-t block rep times for _ in range(rep): - idPulseCentered(qubit, pulseSpacing) + pulseCentered(qubit, Id, pulseSpacing) Y(qubit) - idPulseCentered(qubit, pulseSpacing) + pulseCentered(qubit, Id, pulseSpacing) X90(qubit) MEAS(qubit) @@ -110,7 +106,7 @@ def main(): import numpy as np toHW = True - plotPulses = False + plotPulses = False # This tries to produce graphics to display pyqgl2.test_cl.create_default_channelLibrary(toHW, True) # # To turn on verbose logging in compile_function @@ -129,13 +125,35 @@ def main(): # Pass in QRegister(s) NOT real Qubits q1 = QRegister("q1") + # Axis Descriptor generator functions here + # This is ugly; they're method dependent, but I can't do them in the QGL2 itself + # Additionally, each uses values from the args to the function + # So here we make those arguments be constants so we can use them twice + # without rewriting the values + hahnSpacings = np.linspace(0, 5e-6, 11) + tCalR = 2 # calRepeats + cpmgNumPulses = [0, 2, 4, 6] + cpmgSpacing = 500e-9 + def getHahnAxisDesc(pulseSpacings, calRepeats): + return [ + delay_descriptor(2 * pulseSpacings), + cal_descriptor(('qubit',), calRepeats) + ] + def getCPMGAxisDesc(pulseSpacing, numPulses, calRepeats): + return [ + # NOTE: numPulses is not a numpy array, so cannot multiply by a float + # But thankfully, np.array(np.array) = np.array so this is always a good move here. + delay_descriptor(pulseSpacing * np.array(numPulses)), + cal_descriptor(('qubit',), calRepeats) + ] + # FIXME: See issue #44: Must supply all args to qgl2main for now -# for func, args, label in [("HahnEcho", (q1, np.linspace(0, 5e-6, 11)), "HahnEcho"), -# ("CPMG", (q1, [0, 2, 4, 5], 500e-9), "CPMG"), +# for func, args, label, axisDesc in [("HahnEcho", (q1, hahnSpacings), "Echo", getHahnAxisDesc(hahnSpacings, tCalR)), +# ("CPMG", (q1, cpmgNumPulses, cpmgSpacing), "CPMG", getCPMGAxisDesc(cpmgSpacing, cpmgNumPulses, tCalR)), # ]: - for func, args, label in [("HahnEcho", (q1, np.linspace(0, 5e-6, 11), 0, 2), "HahnEcho"), - ("CPMG", (q1, [0, 2, 4, 6], 500e-9, 2), "CPMG"), + for func, args, label, axisDesc in [("HahnEcho", (q1, hahnSpacings, 0, tCalR), "Echo", getHahnAxisDesc(hahnSpacings, tCalR)), + ("CPMG", (q1, cpmgNumPulses, cpmgSpacing, tCalR), "CPMG", getCPMGAxisDesc(cpmgSpacing, cpmgNumPulses, tCalR)), ]: print(f"\nRun {func}...") @@ -147,7 +165,7 @@ def main(): seq = resFunc() if toHW: print(f"Compiling {func} sequences to hardware\n") - fileNames = qgl2_compile_to_hardware(seq, f'{label}/{label}') + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}', axis_descriptor=axisDesc) print(f"Compiled sequences; metafile = {fileNames}") if plotPulses: from QGL.PulseSequencePlotter import plot_pulse_files diff --git a/src/python/qgl2/basic_sequences/Feedback.py b/src/python/qgl2/basic_sequences/Feedback.py index d4e0b2a..8d515e8 100644 --- a/src/python/qgl2/basic_sequences/Feedback.py +++ b/src/python/qgl2/basic_sequences/Feedback.py @@ -8,6 +8,10 @@ from itertools import product +# FIXME 8/2019: This isn't working yet. +# Reset and BitFlip3 are good examples of trying to use TDM functions +# or measurement values which QGL2 can't quite do yet. + # The following qreset definitions represent a progression in complexity # This first one is the simplest (the "goal") @@ -58,25 +62,54 @@ def qreset_full(q:qreg, delay, measSign): Id(q) def Reset(qubits: qreg, measDelay = 1e-6, signVec = None, - doubleRound = True, showPlot = False, docals = True, - calRepeats=2): + doubleRound = True, buf = 20e-9, measChans = None, docals = True, + calRepeats=2, reg_size=None, TDM_map=None): """ - Reset a qubit register to the ground state. + Preparation, simultanoeus reset, and measurement of an arbitrary number of qubits Parameters ---------- qubits : tuple of logical channels to implement sequence (LogicalChannel) - measDelay : delay between end of measuerement and reset pulse - signVec : Measurement results that indicate that we should flip (default == 1 for all qubits) + measDelay : delay between end of measuerement and reset pulse / LOADCMP + signVec : Measurement results that indicate that we should flip Tuple of 0 (flip if signal > threshold or 1 for each qubit. (default == 0 for all qubits) doubleRound : if true, double round of feedback - showPlot : whether to plot (boolean) docals : enable calibration sequences - calRepeats: number of times to repleat calibration + calRepeats: number of times to repeat calibration + reg_size: total number of qubits, including those that are not reset. Default set to len(qubits) + TDM_map: map each qubit to a TDM digital input. Default: np.array(qN, qN-1, ..., q1) from MSB to LSB. """ + if measChans is None: + measChans = qubits if signVec is None: - signVec = [1]*len(qubits) + signVec = (0,)*len(qubits) + + # FIXME: create_cal_seqs does pulses, doesn't return them + # So what is old code expecting is returned here, that I can use instead? + # given numRepeats=1, this is a single [] containing 2*#qubits of the reduce() thing + # The reduce thing is a bunch of pulses with * between them + # So here I create the combinations of pulses we want for each prep thing and loop over those combos + for prep in product([Id,X], repeat=len(qubits)): + init(qubits) + prep # FIXME: See below where it did another loop? Look at git history? + # FIXME: Could I start by making qreset a qgl1 stub? + # Seems like the old qgl2 pushed into this method some of what new qgl1 qreset does itself + # so a git diff is key + qreset(qubits, signVec, measDelay, buf, reg_size=reg_size, TDM_map=TDM_map) + measConcurrently(qubits) + if doubleRound: + qreset(qubits, signVec, measDelay, buf, reg_size=reg_size, TDM_map=TDM_map) + # Add final measurement + measConcurrently(qubits) + Id(qubits[0], length=measDelay) + qwait(kind='CMP') + # If we're doing calibration too, add that at the very end + # - another 2^numQubits * calRepeats sequences + if docals: + create_cal_seqs(qubits, calRepeats, measChans=measChans, waitcmp=True) +# metafile = compile_to_hardware(seqs, 'Reset/Reset') +# old version for prep in product([Id,X], repeat=len(qubits)): for p,q,measSign in zip(prep, qubits, signVec): init(q) @@ -92,6 +125,139 @@ def Reset(qubits: qreg, measDelay = 1e-6, signVec = None, if docals: create_cal_seqs(qubits, calRepeats) - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot('Reset/Reset', showPlot) +# do not make it a subroutine for now +def BitFlip3(data_qs: qreg, ancilla_qs: qreg, theta=None, phi=None, nrounds=1, meas_delay=1e-6, docals=False, calRepeats=2): + """ + + Encoding on 3-qubit bit-flip code, followed by n rounds of syndrome detection, and final correction using the n results. + + Parameters + ---------- + data_qs : tuple of logical channels for the 3 code qubits + ancilla_qs: tuple of logical channels for the 2 syndrome qubits + theta, phi: longitudinal and azimuthal rotation angles for encoded state (default = no encoding) + meas_delay : delay between syndrome check rounds + docals, calRepeats: enable calibration sequences, repeated calRepeats times + + Returns + ------- + metafile : metafile path + """ + if len(data_qs) != 3 or len(ancilla_qs) != 2: + raise Exception("Wrong number of qubits") + + # Call some TDM Instructions + DecodeSetRounds(1,0,nrounds) + Invalidate(10, 2*nrounds) + Invalidate(11, 0x1) + + # encode single-qubit state into 3 qubits + if theta and phi: + Utheta(data_qs[1], angle=theta, phase=phi) + CNOT(data_qs[1], data_qs[0]) + CNOT(data_qs[1], data_qs[2]) + + # multiple rounds of syndrome measurements + for n in range(nrounds): + Barrier(data_qs[0], ancilla_qs[0], data_qs[1], ancilla_qs[1]) + CNOT(data_qs[0],ancilla_qs[0]) + CNOT(data_qs[1],ancilla_qs[1]) + Barrier(data_qs[1], ancilla_qs[0], data_qs[2], ancilla_qs[1]) + CNOT(data_qs[1], ancilla_qs[0]) + CNOT(data_qs[2],ancilla_qs[1]) + Barrier(ancilla_qs[0], ancilla_qs[1]) + MEASA(ancilla_qs[0], maddr=(10, 2*n)) + MEASA(ancilla_qs[1], maddr=(10, 2*n+1)) + Id(ancilla_qs[0], meas_delay) + # virtual msmt's just to keep the number of segments uniform across digitizer channels + Barrier(data_qs) + MEAS(data_qs[0], amp=0) + MEAS(data_qs[1], amp=0) + MEAS(data_qs[2], amp=0) + Decode(10, 11, 2*nrounds) + qwait("RAM",11) + Barrier(data_qs, ancilla_qs) + # virtual msmt's + MEAS(data_qs[0]) + MEAS(data_qs[1]) + MEAS(data_qs[2]) + MEAS(ancilla_qs[0], amp=0) + MEAS(ancilla_qs[1], amp=0) + + # FIXME: What's right way to do this bit + # apply corrective pulses depending on the decoder result + FbGates = [] + for q in data_qs: + FbGates.append([gate(q) for gate in [Id, X]]) + FbSeq = [reduce(operator.mul, x) for x in product(*FbGates)] + for k in range(8): + qif(k, [FbSeq[k]]) + + if docals: + create_cal_seqs(qubits, calRepeats) +# metafile = compile_to_hardware(seqs, 'BitFlip/BitFlip', tdm_seq=True) + +# A main for running the sequences here with some typical argument values +# Here it runs all of them; could do a parse_args like main.py +def main(): + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + import numpy as np + + toHW = True + plotPulses = False + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in QRegister(s) NOT real Qubits + q1 = QRegister("q1") + + # FIXME: What are reasonable args for this?! + + # FIXME: See issue #44: Must supply all args to qgl2main for now + +# for func, args, label in [("Reset", (q1, np.linspace(0, 5e-6, 11)), "Reset"), +# ("BitFlip3", (q1, [0, 2, 4, 5], 500e-9), "BitFlip"), +# ]: + for func, args, label in [("Reset", (q1, np.linspace(0, 5e-6, 11), 0, 2), "Reset"), + ("BitFlip3", (q1, [0, 2, 4, 6], 500e-9, 2), "BitFlip"), + ]: + + print(f"\nRun {func}...") + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunc = compile_function(__file__, func, args) + # Run the QGL2. Note that the generated function takes no arguments itself + seq = resFunc() + if toHW: + print(f"Compiling {func} sequences to hardware\n") + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}') + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print(f"\nGenerated {func} sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(seq) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) + +if __name__ == "__main__": + main() diff --git a/src/python/qgl2/basic_sequences/FlipFlop.py b/src/python/qgl2/basic_sequences/FlipFlop.py index 15e871d..d2316b0 100644 --- a/src/python/qgl2/basic_sequences/FlipFlop.py +++ b/src/python/qgl2/basic_sequences/FlipFlop.py @@ -1,135 +1,29 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. -from qgl2.qgl2 import qgl2decl, qreg, qgl2main, pulse - -from QGL.PulsePrimitives import X90, X90m, Y90, Id, X, MEAS -from QGL.Compiler import compile_to_hardware -from QGL.PulseSequencePlotter import plot_pulse_files - -from itertools import chain - -#from qgl2.basic_sequences.new_helpers import addMeasPulse, compileAndPlot +from qgl2.qgl2 import qgl2decl, qreg, qgl2main, pulse, QRegister +from qgl2.qgl1 import X90, X90m, Y90, Id, X, MEAS from qgl2.util import init -def FlipFlopq1(qubit: qreg, dragParamSweep, maxNumFFs=10, showPlot=False): - """ - Flip-flop sequence (X90-X90m)**n to determine off-resonance or DRAG parameter optimization. - - Parameters - ---------- - qubit : logical channel to implement sequence (LogicalChannel) - dragParamSweep : drag parameter values to sweep over (iterable) - maxNumFFs : maximum number of flip-flop pairs to do - showPlot : whether to plot (boolean) - """ - - # Original: - # def flipflop_seqs(dragScaling): - # """ Helper function to create a list of sequences with a specified drag parameter. """ - # qubit.pulse_params['dragScaling'] = dragScaling - # return [[X90(qubit)] + [X90(qubit), X90m(qubit)]*rep + [Y90(qubit)] for rep in range(maxNumFFs)] - - # # Insert an identity at the start of every set to mark them off - # originalScaling = qubit.pulse_params['dragScaling'] - # seqs = list(chain.from_iterable([[[Id(qubit)]] + flipflop_seqs(dragParam) for dragParam in dragParamSweep])) - # qubit.pulse_params['dragScaling'] = originalScaling - - # # Add a final pi for reference - # seqs.append([X(qubit)]) - - # # Add the measurment block to every sequence - # measBlock = MEAS(qubit) - # for seq in seqs: - # seq.append(measBlock) - - # fileNames = compile_to_hardware(seqs, 'FlipFlop/FlipFlop') - # print(fileNames) - - # if showPlot: - # plot_pulse_files(fileNames) - - def flipflop_seqs(dragScaling): - """ Helper function to create a list of sequences with a specified drag parameter. """ - qubit.pulse_params['dragScaling'] = dragScaling - seqs = [] - for rep in range(maxNumFFs): - seq = [] - seq.append(X90(qubit)) - # FIXME: Origin used [X90] + [X90, X90m]... is this right? - for _ in range(rep): - seq.append(X90(qubit)) - seq.append(X90m(qubit)) - seq.append(Y90(qubit)) - seqs.append(seq) - return seqs - - # Insert an identity at the start of every set to mark them off - # Want a result something like: - # [['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9'], ['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9'], ['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9']] - - seqs = [] - originalScaling = qubit.pulse_params['dragScaling'] - for dragParam in dragParamSweep: - seqs.append([Id(qubit)]) - # FIXME: In original this was [[Id]] + flipflop - is this - # right? - ffs = flipflop_seqs(dragParam) - for elem in ffs: - seqs.append(elem) - qubit.pulse_params['dragScaling'] = originalScaling - - # Add a final pi for reference - seqs.append([X(qubit)]) - - # Add the measurment block to every sequence -# seqs = addMeasPulse(seqs, qubit) - - # Be sure to un-decorate this function to make it work without the - # QGL2 compiler -# compileAndPlot(seqs, 'FlipFlop/FlipFlop', showPlot) +from itertools import chain @qgl2decl def flipflop_seqs(dragScaling, maxNumFFs, qubit: qreg): """ Helper function to create a list of sequences with a specified drag parameter. """ - # FIXME: cause qubit is a placeholder, can't access pulse_params + # QGL2 qubits are read only. + # So instead, supply the dragScaling as an explicit kwarg to all pulses # qubit.pulse_params['dragScaling'] = dragScaling + for rep in range(maxNumFFs): init(qubit) X90(qubit, dragScaling=dragScaling) - # FIXME: Original used [X90] + [X90, X90m]... is this right? for _ in range(rep): X90(qubit, dragScaling=dragScaling) - X90m(qubi, dragScaling=dragScaling) + X90m(qubit, dragScaling=dragScaling) Y90(qubit, dragScaling=dragScaling) - MEAS(qubit) # FIXME: Need original dragScaling? - -@qgl2decl -def FlipFlopMin(): - # FIXME: No args - qubit = QubitFactory('q1') - dragParamSweep = np.linspace(0, 5e-6, 11) # FIXME - maxNumFFs = 10 - - # FIXME: cause qubit is a placeholder, can't access pulse_params - # originalScaling = qubit.pulse_params['dragScaling'] - for dragParam in dragParamSweep: - init(qubit) - Id(qubit) - MEAS(qubit) # FIXME: Need original dragScaling? - - # FIXME: In original this was [[Id]] + flipflop - is this - # right? - flipflop_seqs(dragParam, maxNumFFs, qubit) - # FIXME: cause qubit is a placeholder, can't access pulse_params - # qubit.pulse_params['dragScaling'] = originalScaling - - # Add a final pi for reference - init(qubit) - X(qubit) - MEAS(qubit) + MEAS(qubit) @qgl2decl -def FlipFlop(qubit: qreg, dragParamSweep, maxNumFFs=10, showPlot=False): +def FlipFlop(qubit: qreg, dragParamSweep, maxNumFFs=10): """ Flip-flop sequence (X90-X90m)**n to determine off-resonance or DRAG parameter optimization. @@ -138,7 +32,6 @@ def FlipFlop(qubit: qreg, dragParamSweep, maxNumFFs=10, showPlot=False): qubit : logical channel to implement sequence (LogicalChannel) dragParamSweep : drag parameter values to sweep over (iterable) maxNumFFs : maximum number of flip-flop pairs to do - showPlot : whether to plot (boolean) """ # Original: @@ -170,16 +63,17 @@ def FlipFlop(qubit: qreg, dragParamSweep, maxNumFFs=10, showPlot=False): # Want a result something like: # [['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9'], ['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9'], ['Id'], ['X9', 'Y9'], ['X9', 'X9', 'X9m', 'Y9'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9']] - originalScaling = qubit.pulse_params['dragScaling'] + # QGL2 qubits are read only, so can't modify qubit.pulse_params[dragScaling], + # Instead of modifying qubit, we'll just supply the drag param explicitly to each pulse + # So no need to save this off and reset afterwards + # originalScaling = qubit.pulse_params['dragScaling'] for dragParam in dragParamSweep: init(qubit) Id(qubit) - MEAS(qubit) # FIXME: Need original dragScaling? + MEAS(qubit) - # FIXME: In original this was [[Id]] + flipflop - is this - # right? flipflop_seqs(dragParam, maxNumFFs, qubit) - qubit.pulse_params['dragScaling'] = originalScaling + # qubit.pulse_params['dragScaling'] = originalScaling # Add a final pi for reference init(qubit) @@ -194,31 +88,61 @@ def FlipFlop(qubit: qreg, dragParamSweep, maxNumFFs=10, showPlot=False): # 'X9', 'X9m', 'Y9', 'M'], ['X9', 'X9', 'X9m', 'X9', 'X9m', 'Y9', # 'M'], ['X', 'M']] - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot # compileAndPlot('FlipFlop/FlipFlop', showPlot) -# Imports for testing only -from QGL.Channels import Qubit, LogicalMarkerChannel -from qgl2.qgl1 import Qubit, QubitFactory -import numpy as np -from math import pi - -@qgl2main +# QGL1 function to compile the above QGL2 +# Uses main.py +# FIXME: Use the same argument parsing as in main.py def main(): - # Set up 1 qbit, following model in QGL/test/test_Sequences - - # FIXME: Cannot use these in current QGL2 compiler, because - # a: QGL2 doesn't understand creating class instances, and - # b: QGL2 currently only understands the fake Qbits -# qg1 = LogicalMarkerChannel(label="q1-gate") -# q1 = Qubit(label='q1', gate_chan=qg1) -# q1.pulse_params['length'] = 30e-9 -# q1.pulse_params['phase'] = pi/2 - - # Use stub Qubits, but comment this out when running directly. - q1 = QubitFactory("q1") - FlipFlop(q1, np.linspace(0, 5e-6, 11)) + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + import numpy as np + + toHW = True + plotPulses = True + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in a QRegister NOT the real Qubit + q = QRegister(1) + # Here we do FlipFlop(q1, np.linspace(0, 5e-6, 11)) + # - test_basic_mins uses np.linspace(0,1,11) + + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunction = compile_function(__file__, + "FlipFlop", + (q, np.linspace(0, 5e-6, 11), 10)) + # Run the QGL2. Note that the generated function takes no arguments itself + sequences = resFunction() + if toHW: + print("Compiling sequences to hardware\n") + fileNames = qgl2_compile_to_hardware(sequences, filename='FlipFlop/FlipFlop') + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print("\nGenerated sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(sequences) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) if __name__ == "__main__": main() diff --git a/src/python/qgl2/basic_sequences/FlipFlopMin.py b/src/python/qgl2/basic_sequences/FlipFlopMin.py deleted file mode 100644 index c0faa14..0000000 --- a/src/python/qgl2/basic_sequences/FlipFlopMin.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. - -from qgl2.qgl2 import qgl2decl, qreg, QRegister -from qgl2.util import init -from qgl2.qgl1 import X90, X90m, Y90, MEAS, Id, X - -import numpy as np - -@qgl2decl -def flipflop_seqs(dragParam, maxNumFFs, qubit: qreg): - """ Helper function to create a list of sequences with a specified drag parameter. """ - # QGL2 qubits are read only. - # So instead, supply the dragScaling as an explicit kwarg to all pulses - # qubit.pulse_params['dragScaling'] = dragParam - for rep in range(maxNumFFs): - init(qubit) - X90(qubit, dragScaling=dragParam) - # FIXME: Original used [X90] + [X90, X90m]... is this right? - for _ in range(rep): - X90(qubit, dragScaling=dragParam) - X90m(qubit, dragScaling=dragParam) - Y90(qubit, dragScaling=dragParam) - MEAS(qubit) - -@qgl2decl -def doFlipFlop(qubit:qreg, dragParamSweep, maxNumFFs): - - # QGL2 qubits are read only, so can't modify qubit.pulse_params[dragScaling], - # So no need to save this off and reset afterwards - for dragParam in dragParamSweep: - # Id sequence for reference - init(qubit) - Id(qubit) - MEAS(qubit) - - # then a flip flop sequence for a particular DRAG parameter - flipflop_seqs(dragParam, maxNumFFs, qubit) - - # Final pi for reference - init(qubit) - X(qubit) - MEAS(qubit) diff --git a/src/python/qgl2/basic_sequences/RB.py b/src/python/qgl2/basic_sequences/RB.py index a757c8a..8a0549f 100644 --- a/src/python/qgl2/basic_sequences/RB.py +++ b/src/python/qgl2/basic_sequences/RB.py @@ -1,13 +1,18 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. -from qgl2.qgl2 import qgl2decl, qreg, pulse, concur +from qgl2.qgl2 import qgl2decl, qreg, pulse, sequence, QRegister -from QGL.PulsePrimitives import MEAS, Id, X, AC -from QGL.PulseSequencePlotter import plot_pulse_files -from QGL.Cliffords import clifford_seq, clifford_mat, inverse_clifford -from QGL.Compiler import compile_to_hardware +from qgl2.qgl1 import MEAS, Id, X, AC, clifford_seq, Y90m, X90 -from qgl2.basic_sequences.helpers import create_cal_seqs +# RB uses Cliffords. Importing all of QGL.Cliffords forces QGL2 +# to read more of QGL than we want. +# So instead we redo (some of) Cliffords in QGL2 with QGL1 stub for clifford_seq +# TODO: Redo more of Cliffords as QGL2. See issues 51-53 that make this hard. + +# from QGL.Cliffords import clifford_seq, clifford_mat, inverse_clifford +from qgl2.Cliffords import clifford_mat, inverse_clifford + +from qgl2.basic_sequences.helpers import create_cal_seqs, measConcurrently, cal_descriptor, delay_descriptor from qgl2.util import init @@ -18,42 +23,11 @@ import numpy as np -def create_RB_seqs(numQubits, lengths, repeats=32, interleaveGate=None): +# This is not pulses, just math; so this is just the original +def create_RB_seqs(numQubits, lengths, repeats=32, interleaveGate=None, recovery=True): """ Create a list of lists of Clifford gates to implement RB. """ - - # Original: - # if numQubits == 1: - # cliffGroupSize = 24 - # elif numQubits == 2: - # cliffGroupSize = 11520 - # else: - # raise Exception("Can only handle one or two qubits.") - - # # Create lists of of random integers - # # Subtract one from length for recovery gate - # seqs = [] - # for length in lengths: - # seqs += np.random.random_integers(0, cliffGroupSize-1, (repeats, length-1)).tolist() - - # # Possibly inject the interleaved gate - # if interleaveGate: - # newSeqs = [] - # for seq in seqs: - # newSeqs.append(np.vstack((np.array(seq, dtype=np.int), interleaveGate*np.ones(len(seq), dtype=np.int))).flatten(order='F').tolist()) - # seqs = newSeqs - # # Calculate the recovery gate - # for seq in seqs: - # if len(seq) == 1: - # mat = clifford_mat(seq[0], numQubits) - # else: - # mat = reduce(lambda x,y: np.dot(y,x), [clifford_mat(c, numQubits) for c in seq]) - # seq.append(inverse_clifford(mat)) - - # return seqs - - # This function seems to just give lists of numbers. So leave it intact # Sample output: @@ -74,26 +48,31 @@ def create_RB_seqs(numQubits, lengths, repeats=32, interleaveGate=None): # Subtract one from length for recovery gate seqs = [] for length in lengths: - seqs += np.random.random_integers(0, cliffGroupSize-1, (repeats, length-1)).tolist() + seqs += np.random.randint(0, cliffGroupSize, + size=(repeats, length-1)).tolist() # Possibly inject the interleaved gate if interleaveGate: newSeqs = [] for seq in seqs: - newSeqs.append(np.vstack((np.array(seq, dtype=np.int), interleaveGate*np.ones(len(seq), dtype=np.int))).flatten(order='F').tolist()) + newSeqs.append(np.vstack((np.array( + seq, dtype=np.int), interleaveGate*np.ones( + len(seq), dtype=np.int))).flatten(order='F').tolist()) seqs = newSeqs - # Calculate the recovery gate - for seq in seqs: - if len(seq) == 1: - mat = clifford_mat(seq[0], numQubits) - else: - mat = reduce(lambda x,y: np.dot(y,x), [clifford_mat(c, numQubits) for c in seq]) - seq.append(inverse_clifford(mat)) + + if recovery: + # Calculate the recovery gate + for seq in seqs: + if len(seq) == 1: + mat = clifford_mat(seq[0], numQubits) + else: + mat = reduce(lambda x,y: np.dot(y,x), [clifford_mat(c, numQubits) for c in seq]) + seq.append(inverse_clifford(mat)) return seqs @qgl2decl -def SingleQubitRB(qubit: qreg, seqs, showPlot=False): +def SingleQubitRB(qubit: qreg, seqs, purity=False, add_cals=True): """ Single qubit randomized benchmarking using 90 and 180 generators. @@ -101,25 +80,30 @@ def SingleQubitRB(qubit: qreg, seqs, showPlot=False): ---------- qubit : logical channel to implement sequence (LogicalChannel) seqs : list of lists of Clifford group integers - showPlot : whether to plot (boolean) """ # Original: # seqsBis = [] - # for seq in seqs: + # op = [Id(qubit, length=0), Y90m(qubit), X90(qubit)] + # for ct in range(3 if purity else 1): + # for seq in seqs: # seqsBis.append(reduce(operator.add, [clifford_seq(c, qubit) for c in seq])) - # # Add the measurement to all sequences - # for seq in seqsBis: - # seq.append(MEAS(qubit)) + # #append tomography pulse to measure purity + # seqsBis[-1].append(op[ct]) + # # Add the measurement to all sequences + # seqsBis[-1].append(MEAS(qubit)) # # Tack on the calibration sequences - # seqsBis += create_cal_seqs((qubit,), 2) - - # fileNames = compile_to_hardware(seqsBis, 'RB/RB') - # print(fileNames) + # if add_cals: + # seqsBis += create_cal_seqs((qubit,), 2) - # if showPlot: - # plot_pulse_files(fileNames) +# axis_descriptor = [{ +# 'name': 'length', +# 'unit': None, +# 'points': list(map(len, seqs)), +# 'partition': 1 +# }] +# metafile = compile_to_hardware(seqsBis, 'RB/RB', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs}) # seqs are result of create_RB_seqs: list of lists of integers @@ -130,34 +114,35 @@ def SingleQubitRB(qubit: qreg, seqs, showPlot=False): # gives a single sequence of all the elements in listOfSequences # So the first for loop creates a single list of sequences + ops = [Id] + if purity: + ops = [Id, Y90m, X90] + for op in ops: + for seq in seqs: + init(qubit) + for c in seq: + clifford_seq(c, qubit) + # append tomography pulse to measure purity + if op == Id: + op(qubit, length=0) + else: + op(qubit) + # append measurement + MEAS(qubit) - # I assume we're not redoing clifford_seq - - for seq in seqs: - init(qubit) - for c in seq: - clifford_seq(c, qubit) - MEAS(qubit) - - # FIXME: This doesn't work yet - # Tack on calibration sequences - create_cal_seqs((qubit,), 2) - - # FIXME: Do this in calling function instead - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot('RB/RB', showPlot) + if add_cals: + # Tack on calibration sequences + create_cal_seqs(qubit, 2) @qgl2decl -def TwoQubitRB(q1: qreg, q2: qreg, seqs, showPlot=False, suffix=""): +def TwoQubitRB(q1: qreg, q2: qreg, seqs, add_cals=True): """ Two qubit randomized benchmarking using 90 and 180 single qubit generators and ZX90 Parameters ---------- - qubit : logical channel to implement sequence (LogicalChannel) + q1,q2 : logical channels to implement sequence (LogicalChannel) seqs : list of lists of Clifford group integers - showPlot : whether to plot (boolean) """ # Original: @@ -170,33 +155,30 @@ def TwoQubitRB(q1: qreg, q2: qreg, seqs, showPlot=False, suffix=""): # seq.append(MEAS(q1, q2)) # # Tack on the calibration sequences - # seqsBis += create_cal_seqs((q1,q2), 2) - - # fileNames = compile_to_hardware(seqsBis, 'RB/RB', suffix=suffix) - # print(fileNames) - - # if showPlot: - # plot_pulse_files(fileNames) - + # if add_cals: + # seqsBis += create_cal_seqs((q1,q2), 2) + +# axis_descriptor = [{ +# 'name': 'length', +# 'unit': None, +# 'points': list(map(len, seqs)), +# 'partition': 1 +# }] +# metafile = compile_to_hardware(seqsBis, 'RB/RB', axis_descriptor = axis_descriptor, suffix = suffix, extra_meta = {'sequences':seqs}) + + bothQs = QRegister(q1, q2) for seq in seqs: - with concur: - init(q1) - init(q2) + init(bothQs) for c in seq: - clifford_seq(c, q1, q2) - with concur: - MEAS(q1) - MEAS(q2) + clifford_seq(c, q2, q1) + measConcurrently(bothQs) # Tack on the calibration sequences - create_cal_seqs((q1,q2), 2) - - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot('RB/RB', showPlot, suffix=suffix) + if add_cals: + create_cal_seqs((q1, q2), 2) @qgl2decl -def SingleQubitRB_AC(qubit: qreg, seqs, showPlot=False): +def SingleQubitRB_AC(qubit: qreg, seqs, purity=False, add_cals=True): """ Single qubit randomized benchmarking using atomic Clifford pulses. @@ -204,41 +186,101 @@ def SingleQubitRB_AC(qubit: qreg, seqs, showPlot=False): ---------- qubit : logical channel to implement sequence (LogicalChannel) seqFile : file containing sequence strings - showPlot : whether to plot (boolean) """ # Original: # seqsBis = [] - # for seq in seqs: + # op = [Id(qubit, length=0), Y90m(qubit), X90(qubit)] + # for ct in range(3 if purity else 1): + # for seq in seqs: # seqsBis.append([AC(qubit, c) for c in seq]) - - # # Add the measurement to all sequences - # for seq in seqsBis: - # seq.append(MEAS(qubit)) + # #append tomography pulse to measure purity + # seqsBis[-1].append(op[ct]) + # #append measurement + # seqsBis[-1].append(MEAS(qubit)) # # Tack on the calibration sequences - # seqsBis += create_cal_seqs((qubit,), 2) - - # fileNames = compile_to_hardware(seqsBis, 'RB/RB') - # print(fileNames) + # if add_cals: + # seqsBis += create_cal_seqs((qubit,), 2) - # if showPlot: - # plot_pulse_files(fileNames) +# axis_descriptor = [{ +# 'name': 'length', +# 'unit': None, +# 'points': list(map(len, seqs)), +# 'partition': 1 +# }] +# metafile = compile_to_hardware(seqsBis, 'RB/RB', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs}) # AC() gives a single pulse on qubit - for seq in seqs: - init(qubit) - for c in seq: - AC(qubit, c) - MEAS(qubit) + op = [Id, Y90m, X90] + for ct in range(3 if purity else 1): + for seq in seqs: + init(qubit) + for c in seq: + AC(qubit, c) + # append tomography pulse to measure purity + # See issue #: 53 + func = op[ct] + if ct == 0: + func(qubit, length=0) + else: + func(qubit) + # append measurement + MEAS(qubit) + + if add_cals: + # Tack on calibration sequences + create_cal_seqs(qubit, 2) + +@qgl2decl +def SingleQubitRB_DiAC(qubit, seqs, compiled=True, purity=False, add_cals=True): + """Single qubit randomized benchmarking using diatomic Clifford pulses. + + Parameters + ---------- + qubit : logical channel to implement sequence (LogicalChannel) + seqFile : file containing sequence strings + compiled : if True, compile Z90(m)-X90-Z90(m) to Y90(m) pulses + purity : measure ,, of final state, to measure purity. See J.J. + Wallman et al., New J. Phys. 17, 113020 (2015) + """ + op = [Id, Y90m, X90] + for ct in range(3 if purity else 1): + for seq in seqs: + init(qubit) + for c in seq: + DiAC(qubit, c, compiled) + # append tomography pulse to measure purity + if ct == 0: + op[ct](qubit, length=0) + else: + op[ct](qubit) + # append measurement + MEAS(qubit) + +# axis_descriptor = [{ +# 'name': 'length', +# 'unit': None, +# 'points': list(map(len, seqs)), +# 'partition': 1 +# }] + + # Tack on the calibration sequences + if add_cals: + for _ in range(2): + init(qubit) + Id(qubit) + MEAS(qubit) + for _ in range(2): + init(qubit) + X90(qubit) + X90(qubit) + MEAS(qubit) +# axis_descriptor.append(cal_descriptor((qubit,), 2)) - # Tack on calibration sequences - create_cal_seqs((qubit,), 2) +# metafile = compile_to_hardware(seqsBis, 'RB_DiAC/RB_DiAC', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs}) - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot('RB/RB', showPlot) @qgl2decl def doACPulse(qubit: qreg, cliffNum) -> sequence: @@ -255,8 +297,20 @@ def getPulseSeq(qubit: qreg, pulseSeqStr) -> sequence: doACPulse(qubit, int(pulseStr)) MEAS(qubit) +def readSeqFile(seqFile): + pulseSeqStrs = [] + if seqFile is None: + raise ValueError("Missing file of sequences") + # This next block as QGL2 gives warnings + with open(seqFile, 'r') as FID: + fileReader = reader(FID) + # each line in the file is a sequence, but I don't know how many that is + for pulseSeqStr in fileReader: + pulseSeqStrs.append(pulseSeqStr) + return pulseSeqStrs + @qgl2decl -def SingleQubitIRB_AC(qubit: qreg, seqFile, showPlot=False): +def SingleQubitIRB_AC(qubit: qreg, seqFile): """ Single qubit interleaved randomized benchmarking using atomic Clifford pulses. @@ -264,7 +318,6 @@ def SingleQubitIRB_AC(qubit: qreg, seqFile, showPlot=False): ---------- qubit : logical channel to implement sequence (LogicalChannel) seqFile : file containing sequence strings - showPlot : whether to plot (boolean) """ # Original: @@ -296,15 +349,11 @@ def SingleQubitIRB_AC(qubit: qreg, seqFile, showPlot=False): # chunk2 += [[Id(qubit), measBlock], [X(qubit), measBlock]] # fileNames = compile_to_hardware(chunk2, 'RB/RB', suffix='_{0}'.format(2*ct+2)) - # if showPlot: - # plot_pulse_files(fileNames) - - pulseSeqStrs = [] - with open(seqFile, 'r') as FID: - fileReader = reader(FID) - # each line in the file is a sequence, but I don't know how many that is - for pulseSeqStr in fileReader: - pulseSeqStrs.append(pulseSeqStr) + # Issue #54: + # FIXME: If the helper here raises an error, we get a QGL2 compiler error like: + # error: ast eval failure [readSeqFile(seqFile)]: type Missing file of sequences + # error: failed to evaluate assignment [pulseSeqStrs___ass_006 = readSeqFile(seqFile)] + pulseSeqStrs = readSeqFile(seqFile) numSeqs = len(pulseSeqStrs) # Hack for limited APS waveform memory and break it up into multiple files @@ -324,40 +373,39 @@ def SingleQubitIRB_AC(qubit: qreg, seqFile, showPlot=False): MEAS(qubit) init(qubit) X(qubit) - meas(qubit) + MEAS(qubit) else: init(qubit) Id(qubit) - meas(qubit) + MEAS(qubit) init(qubit) X(qubit) - meas(qubit) + MEAS(qubit) # Now write these sequences # FIXME: Then magically get the sequences here.... # This needs to get refactored.... # We need to split creating seqs from c_to_h - fileNames = compile_to_hardware([], 'RB/RB', - suffix='_{0}'.format(2*ct+1+1*(not - isOne)), - qgl2=True) +# fileNames = compile_to_hardware([], 'RB/RB', +# suffix='_{0}'.format(2*ct+1+1*(not +# isOne)), +# qgl2=True) doCt += numRandomizations isOne = not isOne - if showPlot: - plot_pulse_Files(fileNames) - +# NOTE: This one not expected to work @qgl2decl -def SingleQubitRBT(qubit: qreg, seqFileDir, analyzedPulse: pulse, showPlot=False): - """ - Single qubit randomized benchmarking using atomic Clifford pulses. +def SingleQubitRBT(qubit: qreg, seqFileDir, analyzedPulse: pulse, add_cals=True): + """ Single qubit randomized benchmarking tomography using atomic Clifford pulses. + + This relies on specific sequence files and is here for historical purposes only. Parameters ---------- qubit : logical channel to implement sequence (LogicalChannel) seqFile : file containing sequence strings - showPlot : whether to plot (boolean) + analyzedPulse : specific pulse to analyze """ # Original: @@ -386,13 +434,11 @@ def SingleQubitRBT(qubit: qreg, seqFileDir, analyzedPulse: pulse, showPlot=False # for ct in range(numFiles): # chunk = seqs[ct*seqsPerFile:(ct+1)*seqsPerFile] # # Tack on the calibration scalings - # numCals = 4 - # chunk += [[Id(qubit), measBlock]]*numCals + [[X(qubit), measBlock]]*numCals + # if add_cals: + # numCals = 4 + # chunk += [[Id(qubit), measBlock]]*numCals + [[X(qubit), measBlock]]*numCals # fileNames = compile_to_hardware(chunk, 'RBT/RBT', suffix='_{0}'.format(ct+1)) - # if showPlot: - # plot_pulse_files(fileNames) - pulseSeqStrs = [] for ct in range(10): fileName = 'RBT_Seqs_fast_{0}_F1.txt'.format(ct+1) @@ -413,36 +459,31 @@ def SingleQubitRBT(qubit: qreg, seqFileDir, analyzedPulse: pulse, showPlot=False init(qubit) seqStr = pulseSeqStrs[ct*seqsPerFile+s] getPulseSeq(qubit, seqStr) - # Add numCals calibration scalings - for _ in range(numCals): - init(qubit) - Id(qubit) - MEAS(qubit) - - init(qubit) - X(qubit) - MEAS(qubit) - # FIXME: Then magically get the sequences here.... - # This needs to get refactored.... - # We need to split creating seqs from c_to_h - fileNames = compile_to_hardware([], 'RBT/RBT', - suffix='_{0}'.format(ct+1), qgl2=True) + if add_cals: + # Add numCals calibration scalings + for _ in range(numCals): + init(qubit) + Id(qubit) + MEAS(qubit) - # FIXME: Do this from calling function - if showPlot: - plot_pulse_files(fileNames) + init(qubit) + X(qubit) + MEAS(qubit) +# # FIXME: Then magically get the sequences here.... +# # This needs to get refactored.... +# # We need to split creating seqs from c_to_h +# fileNames = compile_to_hardware([], 'RBT/RBT', +# suffix='_{0}'.format(ct+1), qgl2=True) -# FIXME: No args @qgl2decl -def SimultaneousRB_AC(qubits: qreg, seqs, showPlot=False): +def SimultaneousRB_AC(qubits: qreg, seqs, add_cals=True): """ Simultaneous randomized benchmarking on multiple qubits using atomic Clifford pulses. Parameters ---------- - qubits : iterable of logical channels to implement seqs on (list or tuple) + qubits : QRegister of logical channels to implement seqs on seqs : a tuple of sequences created for each qubit in qubits - showPlot : whether to plot (boolean) Example ------- @@ -450,7 +491,8 @@ def SimultaneousRB_AC(qubits: qreg, seqs, showPlot=False): >>> q2 = QubitFactory('q2') >>> seqs1 = create_RB_seqs(1, [2, 4, 8, 16]) >>> seqs2 = create_RB_seqs(1, [2, 4, 8, 16]) - >>> SimultaneousRB_AC((q1, q2), (seqs1, seqs2), showPlot=False) + >>> qr = QRegister(q1, q2) + >>> SimultaneousRB_AC(qr, (seqs1, seqs2)) """ # Original: # seqsBis = [] @@ -462,128 +504,200 @@ def SimultaneousRB_AC(qubits: qreg, seqs, showPlot=False): # for seq in seqsBis: # seq.append(reduce(operator.mul, [MEAS(q) for q in qubits])) - # # Tack on the calibration sequences - # seqsBis += create_cal_seqs((qubits), 2) +# axis_descriptor = [{ +# 'name': 'length', +# 'unit': None, +# 'points': list(map(len, seqs)), +# 'partition': 1 +# }] - # fileNames = compile_to_hardware(seqsBis, 'RB/RB') - # print(fileNames) + # # Tack on the calibration sequences + # if add_cals: + # seqsBis += create_cal_seqs((qubits), 2) + # axis_descriptor.append(cal_descriptor((qubits), 2)) - # if showPlot: - # plot_pulse_files(fileNames) + # metafile = compile_to_hardware(seqsBis, 'RB/RB', axis_descriptor = axis_descriptor, extra_meta = {'sequences':seqs}) for seq in zip(*seqs): # Start sequence - with concur: - for q in qubits: - init(q) + init(qubits) for pulseNums in zip(*seq): - with concur: - for q,c in zip(qubits, pulseNums): - AC(q,c) + Barrier(qubits) + for q, c in zip(qubits, pulseNums): + AC(q, c) # Measure at end of each sequence - with concur: - for q in qubits: - MEAS(q) - - # FIXME: Not working in QGL2 yet - # Tack on calibration - create_cal_seqs((qubits), 2) - - # FIXME: Do this from calling function, not here - # QGL2 compiler must supply missing list of sequences argument -# compileAndPlot('RB/RB', showPlot) - -# Imports for testing only -from qgl2.qgl2 import qgl2main -from QGL.Channels import Qubit, LogicalMarkerChannel, Measurement, Edge -from QGL import ChannelLibraries -from qgl2.qgl1 import Qubit, QubitFactory -import numpy as np -from math import pi -import random + measConcurrently(qubits) + + if add_cals: + # Tack on calibration + create_cal_seqs(qubits, 2) -@qgl2main +# A main for running the sequences here with some typical argument values +# Here it runs all of them; could do a parse_args like main.py def main(): - # Set up 2 qbits, following model in QGL/test/test_Sequences - - # FIXME: Cannot use these in current QGL2 compiler, because - # a: QGL2 doesn't understand creating class instances, and - # b: QGL2 currently only understands the fake Qbits -# qg1 = LogicalMarkerChannel(label="q1-gate") -# q1 = Qubit(label='q1', gate_chan=qg1) -# q1.pulse_params['length'] = 30e-9 -# q1.pulse_params['phase'] = pi/2 -# qg2 = LogicalMarkerChannel(label="q2-gate") -# q2 = Qubit(label='q2', gate_chan=qg2) -# q2.pulse_params['length'] = 30e-9 -# q2.pulse_params['phase'] = pi/2 - -# sTrig = LogicalMarkerChannel(label='slaveTrig') -# dTrig = LogicalMarkerChannel(label='digitizerTrig') -# Mq1gate = LogicalMarkerChannel(label='M-q1-gate') -# m1 = Measurement(label='M-q1', gate_chan = Mq1gate, trig_chan = dTrig) -# Mq2gate = LogicalMarkerChannel(label='M-q2-gate') -# m2 = Measurement(label='M-q2', gate_chan = Mq2gate, trig_chan = dTrig) - - # this block depends on the existence of q1 and q2 -# crgate = LogicalMarkerChannel(label='cr-gate') - -# cr = Edge(label="cr", source = q1, target = q2, gate_chan = crgate ) -# cr.pulse_params['length'] = 30e-9 -# cr.pulse_params['phase'] = pi/4 - -# mq1q2g = LogicalMarkerChannel(label='M-q1q2-gate') -# m12 = Measurement(label='M-q1q2', gate_chan = mq1q2g, trig_chan=dTrig) - -# ChannelLibraries.channelLib = ChannelLibraries.ChannelLibrary(blank=True) -# ChannelLibraries.channelLib.channelDict = { -# 'q1-gate': qg1, -# 'q1': q1, -# 'q2-gate': qg2, -# 'q2': q2, -# 'cr-gate': crgate, -# 'cr': cr, -# 'slaveTrig': sTrig, -# 'digitizerTrig': dTrig, -# 'M-q1': m1, -# 'M-q1-gate': Mq1gate, -# 'M-q2': m2, -# 'M-q2-gate': Mq2gate, -# 'M-q1q2-gate': mq1q2g, -# 'M-q1q2': m12 -# } -# ChannelLibraries.channelLib.build_connectivity_graph() - - # Use stub Qubits, but comment this out when running directly. - q1 = QubitFactory("q1") - q2 = QubitFactory("q2") - - np.random.seed(20152606) # set seed for create_RB_seqs() - random.seed(20152606) # set seed for random.choice() - SingleQubitRB(q1, create_RB_seqs(1, 2**np.arange(1,7))) - - # For some reason this only works if I reverse the 2 qubit args - # q2 then q1. Why? - # The original unit test had this commeent: + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + import numpy as np + import random + + toHW = True + plotPulses = False + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in QRegister(s) NOT real Qubits + q1 = QRegister("q1") + q2 = QRegister("q2") + qr = QRegister(q1, q2) + + # FIXME: See issue #44: Must supply all args to qgl2main for now + + # Functions here have some extra code to run before running the compiled QGL2, + # so define functions for those; random number seeding + + def beforeSingleRB(): + np.random.seed(20152606) # set seed for create_RB_seqs() + random.seed(20152606) # set seed for random.choice() + # SingleQubitRB(q1, create_RB_seqs(1, 2**np.arange(1,7))) + + # The original unit test had this comment: """ Fails on APS1, APS2, and Tek7000 due to: - File "QGL\PatternUtils.py", line 129, in add_gate_pulses + File "QGL/PatternUtils.py", line 129, in add_gate_pulses if has_gate(chan) and not pulse.isZero and not (chan.gate_chan AttributeError: 'CompositePulse' object has no attribute 'isZero' """ - np.random.seed(20152606) # set seed for create_RB_seqs() - TwoQubitRB(q2, q1, create_RB_seqs(2, [2, 4, 8, 16, 32], repeats=16)) - - np.random.seed(20151709) # set seed for create_RB_seqs - seqs1 = create_RB_seqs(1, 2**np.arange(1,7)) - seqs2 = create_RB_seqs(1, 2**np.arange(1,7)) - SimultaneousRB_AC((q1, q2), (seqs1, seqs2)) - - np.random.seed(20152606) # set seed for create_RB_seqs - SingleQubitRB_AC(q1,create_RB_seqs(1, 2**np.arange(1,7))) - -# SingleQubitIRB_AC(q1,'') + def beforeTwoRB(): + np.random.seed(20152606) # set seed for create_RB_seqs() + # TwoQubitRB(q2, q1, create_RB_seqs(2, [2, 4, 8, 16, 32], repeats=16)) + + def beforeSimRBAC(): + np.random.seed(20151709) # set seed for create_RB_seqs + #seqs1 = create_RB_seqs(1, 2**np.arange(1,7)) + #seqs2 = create_RB_seqs(1, 2**np.arange(1,7)) + # SimultaneousRB_AC((q1, q2), (seqs1, seqs2)) + + def beforeSingleRBAC(): + np.random.seed(20152606) # set seed for create_RB_seqs + # SingleQubitRB_AC(q1,create_RB_seqs(1, 2**np.arange(1,7))) + +# FIXME: Add test of SingleQubitRB_DiAC + + sqCSeqs = create_RB_seqs(1, 2**np.arange(1,7)) + tqCSeqs = create_RB_seqs(2, [2, 4, 8, 16, 32], repeats=16) + simCSeqs = (sqCSeqs, sqCSeqs) + tAddCals = True + def getSingleQubitRBAD(seqs, add_cals): + ad = [{ + 'name': 'length', + 'unit': None, + 'points': list(map(len, seqs)), + 'partition': 1 + }] + if add_cals: + ad.append(cal_descriptor(('qubit',), 2)) + return ad + + def getTwoQubitRBAD(seqs, add_cals): + axis_descriptor = [{ + 'name': 'length', + 'unit': None, + 'points': list(map(len, seqs)), + 'partition': 1 + }] + if add_cals: + axis_descriptor.append(cal_descriptor(('q1', 'q2'), 2)) + return axis_descriptor + + def getSingleQubitRBAC_AD(seqs, add_cals): + axis_descriptor = [{ + 'name': 'length', + 'unit': None, + 'points': list(map(len, seqs)), + 'partition': 1 + }] + if add_cals: + axis_descriptor.append(cal_descriptor(('qubit',), 2)) + return axis_descriptor + + def getSimRBACAD(seqs, add_cals, qubits): + axis_descriptor = [{ + 'name': 'length', + 'unit': None, + 'points': list(map(len, seqs)), + 'partition': 1 + }] + if add_cals: + axis_descriptor.append(cal_descriptor((qubits), 2)) + return axis_descriptor + +# FIXME: SingleQubitIRB_AC filenames are more complex +# FIXME: SingleQubitRBT has complex suffix it should pass to compile_to_hardware + +# for func, args, label, beforeFunc, axisDesc, cseqs in [("SingleQubitRB", (q1, sqCSeqs), "RB", beforeSingleRB, getSingleQubitRBAD(sqCSeqs, tAddCals), sqCSeqs), +# ("TwoQubitRB", (q1, q2, tqCSeqs), "RB", beforeTwoRB, getTwoQubitRBAD(tqCSeqs, tAddCals), tqCSeqs), +# ("SingleQubitRB_AC", (q1,sqCSeqs), "RB", beforeSingleRBAC, getSingleQubitRBAC_AD(sqCSeqs, tAddCals), sqCSeqs), +# ("SimultaneousRB_AC", (qr, simCSeqs), "RB", beforeSimRBAC, getSimRBACAD(simCSeqs, tAddCals, qr), simCSeqs), +# ("SingleQubitIRB_AC", (q1,''), "RB", None, None, None), +# Comment says this next one relies on a specific file, so don't bother running +# ("SingleQubitRBT", (q1,'', fixmePulse), "RBT", None, None, None), +# ]: + + for func, args, label, beforeFunc, axisDesc, cseqs in [("SingleQubitRB", (q1, sqCSeqs, False, tAddCals), "RB", beforeSingleRB, getSingleQubitRBAD(sqCSeqs, tAddCals), sqCSeqs), + ("TwoQubitRB", (q1, q2, tqCSeqs, tAddCals), "RB", beforeTwoRB, getTwoQubitRBAD(tqCSeqs, tAddCals), tqCSeqs), + ("SingleQubitRB_AC", (q1,sqCSeqs, False, tAddCals), "RB", beforeSingleRBAC, getSingleQubitRBAC_AD(sqCSeqs, tAddCals), sqCSeqs), +# Warning: This next one is slow.... + ("SimultaneousRB_AC", (qr, simCSeqs, tAddCals), "RB", beforeSimRBAC, getSimRBACAD(simCSeqs, tAddCals, qr), simCSeqs), +# This next one relies on a file of sequence strings, which I don't have +# ("SingleQubitIRB_AC", (q1,None), "RB", None, None, None), +# Comment says this next one relies on a specific file, so don't bother running +# # ("SingleQubitRBT", (q1,'', fixmePulse, True), "RBT", None, None, None), + ]: + + print(f"\nRun {func}...") + + # This is typically setting random seed + if beforeFunc is not None: + beforeFunc() + + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunc = compile_function(__file__, func, args) + # Run the QGL2. Note that the generated function takes no arguments itself + seq = resFunc() + if toHW: + import QGL + print(f"Compiling {func} sequences to hardware\n") + # QGL.Compiler.set_log_level() + em = None + if cseqs: + em = {'sequences':cseqs} + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}', axis_descriptor=axisDesc, extra_meta = em) + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print(f"\nGenerated {func} sequences:\n") + from QGL.Scheduler import schedule -# SingleQubitRBT(q1,'') + scheduled_seq = schedule(seq) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) if __name__ == "__main__": main() diff --git a/src/python/qgl2/basic_sequences/README.txt b/src/python/qgl2/basic_sequences/README.txt index cb8ed7f..e916ca3 100644 --- a/src/python/qgl2/basic_sequences/README.txt +++ b/src/python/qgl2/basic_sequences/README.txt @@ -1,24 +1,15 @@ QGL2 versions of QGL.BasicSequences +- Used to test what can be done in QGL2, and show usage / programming patterns. -The *Min files are closer to working QGL2 versions. -The base versions include rewritten QGL1 versions, and first cut QGL2 -versions. -But in QGL2, the function that produces the sequence does not compile -it; hence the move to the *Min files (no more use of compileAndWait, etc). -The *Min files also remove arguments from the main function. -Look for FIXME comments about things that do not currently work. +Each *.py has the QGL BasicSequence function(s), and a main() to show usage patterns. Typically, write a +python program that uses QGL2 main.py to compile a QGL2 program and then invokes that compiled program. -The base files generally have a main, intended to help unit test these -files. That main has not been run in a while. - -There are a number of things that keep theses functions from working: -* We have no good way to pass in arguments, so these (including -Qubits) must be hard coded in the `do*` methods in `*Min.py`. See -issue #62, etc. -* We don't yet have a good way to use the result of a `MEAS`, as in -`Reset` in `Feedback.py`. See issue #66. -* Qubits are not full objects so you can't use the `pulse_params`, as -in `doCPMG` or `doFlipFlop`. See issue #65. -* Various file reading methods fail, so Clifford sequence functions as -in `RB.py` fail. See issue #67, 68, 69. +There are a number of things that keep these functions from working as desired: +* Issue #44: We have to supply all the qgl2main arguments currently +* We don't yet have a good way to use the result of a `MEAS`, as in `Reset` in `Feedback.py`. See issue #24. +* Qubits are not full objects so you can't use the `pulse_params`, as in `doCPMG` or `doFlipFlop`. See issue #37. +* Cliffords should be redone as QGL2 where possible, to avoid importing all of QGL1. But see issues #51-53 +Todo includes: +* RB.py: Redo more of Cliffords as QGL2 +* Feedback.py: Support using measurements diff --git a/src/python/qgl2/basic_sequences/Rabi.py b/src/python/qgl2/basic_sequences/Rabi.py index c8f9d55..6ddf951 100644 --- a/src/python/qgl2/basic_sequences/Rabi.py +++ b/src/python/qgl2/basic_sequences/Rabi.py @@ -1,34 +1,15 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. -# See RabiMin for more function QGL2 versions of these functions. - -import QGL.PulseShapes -from QGL.PulsePrimitives import Utheta, MEAS, X, Id -from QGL.Compiler import compile_to_hardware -from QGL.PulseSequencePlotter import plot_pulse_files - -from qgl2.basic_sequences.helpers import create_cal_seqs - +from qgl2.basic_sequences.helpers import create_cal_seqs, measConcurrently, cal_descriptor, delay_descriptor +from qgl2.qgl2 import qgl2decl, qreg, QRegister +from qgl2.qgl1 import Utheta, MEAS, X, Id from qgl2.util import init -from pyqgl2.main import compile_function -from functools import reduce -import operator -import os.path - -from qgl2.qgl2 import qgl2decl, qreg, qgl2main, concur -from qgl2.qgl1 import Utheta, MEAS, X, Id, QubitFactory -import numpy as np +# For tanh shape function +import QGL.PulseShapes @qgl2decl -def doRabiAmp(q:qreg, amps, phase): - for amp in np.linspace(0,1,11): - init(q) - Utheta(q, amp=amp, phase=phase) - MEAS(q) - -# currently ignores all arguments except 'qubit' -def RabiAmp(qubit, amps, phase=0, showPlot=False): +def RabiAmp(qubit: qreg, amps, phase=0): """ Variable amplitude Rabi nutation experiment. @@ -37,30 +18,23 @@ def RabiAmp(qubit, amps, phase=0, showPlot=False): qubit : logical channel to implement sequence (LogicalChannel) amps : pulse amplitudes to sweep over (iterable) phase : phase of the pulse (radians) - showPlot : whether to plot (boolean) """ - resFunction = compile_function( - os.path.relpath(__file__), - "doRabiAmp", - (qubit, amps, phase) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "Rabi/Rabi") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) + for amp in amps: + init(qubit) + Utheta(qubit, amp=amp, phase=phase) + MEAS(qubit) +# axis_descriptor = [{ +# 'name': 'amplitude', +# 'unit': None, +# 'points': list(amps), +# 'partition': 1 +# }] -@qgl2decl -def doRabiWidth(q:qreg, widths, amp, phase, shape): - for l in widths: - init(q) - # FIXME: QGL2 loses the import needed for this QGL function - Utheta(q, length=l, amp=amp, phase=phase, shapeFun=shape) - MEAS(q) +# metafile = compile_to_hardware(seqs, 'Rabi/Rabi', axis_descriptor=axis_descriptor) -def RabiWidth(qubit, widths, amp=1, phase=0, shapeFun=QGL.PulseShapes.tanh, showPlot=False): +# Note that QGL2 gives a warning printing the tanh function; harmless +@qgl2decl +def RabiWidth(qubit: qreg, widths, amp=1, phase=0, shape_fun=QGL.PulseShapes.tanh): """ Variable pulse width Rabi nutation experiment. @@ -69,40 +43,20 @@ def RabiWidth(qubit, widths, amp=1, phase=0, shapeFun=QGL.PulseShapes.tanh, show qubit : logical channel to implement sequence (LogicalChannel) widths : pulse widths to sweep over (iterable) phase : phase of the pulse (radians, default = 0) - shapeFun : shape of pulse (function, default = PulseShapes.tanh) - showPlot : whether to plot (boolean) + shape_fun : shape of pulse (function, default = PulseShapes.tanh) """ - resFunction = compile_function( - os.path.relpath(__file__), - "doRabiWidth" - (qubit, widths, amp, phase, shapeFun) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "Rabi/Rabi") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) + # Original created 1 seq per width. This is same pulses, but in 1 sequence + for l in widths: + init(qubit) + Utheta(qubit, length=l, amp=amp, phase=phase, shape_fun=shape_fun) + MEAS(qubit) +# metafile = compile_to_hardware(seqs, 'Rabi/Rabi', +# axis_descriptor=[delay_descriptor(widths)]) @qgl2decl -def doRabiAmp_NQubits(qubits: qreg, amps, phase, - measChans: qreg, docals, calRepeats): - for amp in amps: - with concur: - for q in qubits: - init(q) - Utheta(q, amp=amp, phase=phase) - with concur: - for m in measChans: - MEAS(m) - - if docals: - create_cal_seqs(qubits, calRepeats, measChans) - -def RabiAmp_NQubitsq1(qubits, amps, phase=0, showPlot=False, - measChans=None, docals=False, calRepeats=2): +def RabiAmp_NQubits(qubits: qreg, amps, phase=0, + measChans: qreg=None, docals=False, calRepeats=2): """ Variable amplitude Rabi nutation experiment for an arbitrary number of qubits simultaneously @@ -111,64 +65,66 @@ def RabiAmp_NQubitsq1(qubits, amps, phase=0, showPlot=False, qubits : tuple of logical channels to implement sequence (LogicalChannel) amps : pulse amplitudes to sweep over for all qubits (iterable) phase : phase of the pulses (radians) - showPlot : whether to plot (boolean) - measChans : tuble of qubits to be measured (LogicalChannel) + measChans : tuple of qubits to be measured (use qubits if not specified) (LogicalChannel) docals, calRepeats: enable calibration sequences, repeated calRepeats times """ - if measChans is None: measChans = qubits + allChans = QRegister(qubits, measChans) + for amp in amps: + init(allChans) + Utheta(qubits, amp=amp, phase=phase) + measConcurrently(measChans) - resFunction = compile_function( - os.path.relpath(__file__), - "doRabiWidth", - (qubits, amps, phase, measChans, docals, calRepeats) - ) - seqs = resFunction() - return seqs + if docals: + create_cal_seqs(qubits, calRepeats, measChans) - fileNames = qgl2_compile_to_hardware(seqs, "Rabi/Rabi") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) +# axis_descriptor = [ +# { +# 'name': 'amplitude', +# 'unit': None, +# 'points': list(amps), +# 'partition': 1 +# }, +# cal_descriptor(qubits, calRepeats) +# ] -@qgl2decl -def doRabiAmpPi(qubit: qreg, mqubit: qreg, amps, phase): - for amp in amps: - with concur: - init(qubit) - init(mqubit) - X(mqubit) - Utheta(qubit, amp=amp, phase=phase) - X(mqubit) - MEAS(mqubit) +# metafile = compile_to_hardware(seqs, 'Rabi/Rabi', +# axis_descriptor=axis_descriptor) -def RabiAmpPi(qubit: qreg, mqubit: qreg, amps, phase=0, showPlot=False): +@qgl2decl +def RabiAmpPi(qubit: qreg, mqubit: qreg, amps, phase=0): """ Variable amplitude Rabi nutation experiment. Parameters ---------- qubit : logical channel to implement sequence (LogicalChannel) + mqubit : logical measurement channel to implement sequence (LogicalChannel) + If None then use qubit amps : pulse amplitudes to sweep over (iterable) phase : phase of the pulse (radians) - showPlot : whether to plot (boolean) """ - resFunction = compile_function( - os.path.relpath(__file__), - "doRabiAmpPi", - (qubit, mqubit, amps, phase) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "Rabi/Rabi") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) + if mqubit is None: + mqubit = qubit + qNm = QRegister(qubit, mqubit) + for amp in amps: + init(qNm) + X(mqubit) + Utheta(qubit, amp=amp, phase=phase) + X(mqubit) + MEAS(mqubit) +# axis_descriptor = [{ +# 'name': 'amplitude', +# 'unit': None, +# 'points': list(amps), +# 'partition': 1 +# }] + +# metafile = compile_to_hardware(seqs, 'Rabi/Rabi', axis_descriptor=axis_descriptor) @qgl2decl -def doSingleShot(qubit: qreg): +def SingleShot(qubit: qreg): """ 2-segment sequence with qubit prepared in |0> and |1>, useful for single-shot fidelity measurements and kernel calibration """ @@ -179,51 +135,45 @@ def doSingleShot(qubit: qreg): X(qubit) MEAS(qubit) -def SingleShot(qubit: qreg, showPlot=False): +# axis_descriptor = { +# 'name': 'state', +# 'unit': 'state', +# 'points': ["0", "1"], +# 'partition': 1 +# } + +# metafile = compile_to_hardware(seqs, 'SingleShot/SingleShot') + +@qgl2decl +def SingleShotNoArg(): """ - 2-segment sequence with qubit prepared in |0> and |1>, useful for single-shot fidelity measurements and kernel calibration + Sample 0-argument 2-segment sequence with qubit prepared in |0> and |1>, useful for single-shot fidelity measurements and kernel calibration """ - resFunction = compile_function( - os.path.relpath(__file__), - "doSingleShot", - (qubit,) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "SingleShot/SingleShot") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) + qubit = QRegister(1) + init(qubit) + Id(qubit) + MEAS(qubit) + init(qubit) + X(qubit) + MEAS(qubit) @qgl2decl -def doPulsedSpec(qubit: qreg, specOn): +def PulsedSpec(qubit: qreg, specOn=True): + """ + Measurement preceded by a X pulse if specOn + """ init(qubit) if specOn: X(qubit) else: Id(qubit) MEAS(qubit) - -def PulsedSpec(qubit, specOn=True, showPlot=False): - """ - Measurement preceded by a qubit pulse if specOn = True - """ - resFunction = compile_function( - os.path.relpath(__file__), - "doSingleShot", - (qubit,) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "Spec/Spec") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) +# metafile = compile_to_hardware(seqs, 'Spec/Spec') @qgl2decl -def doSwap(qubit: qreg, mqubit: qreg, delays): +def Swap(qubit: qreg, delays, mqubit: qreg =None): + # Note: Not a QGL1 basic sequence any more, but preserving this anyhow + # Original: # seqs = [[X(qubit), X(mqubit), Id(mqubit, d), MEAS(mqubit)*MEAS(qubit)] for d in delays] + create_cal_seqs((mqubit,qubit), 2, measChans=(mqubit,qubit)) @@ -233,39 +183,128 @@ def doSwap(qubit: qreg, mqubit: qreg, delays): # if showPlot: # plotWin = plot_pulse_files(fileNames) # return plotWin + if mqubit is None: + mqubit = qubit + allChans = QRegister(qubit, mqubit) for d in delays: - with concur: - init(qubit) - init(mqubit) + init(allChans) X(qubit) X(mqubit) - Id(mqubit, d) - with concur: - MEAS(mqubit) - MEAS(qubit) - - create_cal_seqs((mqubit, qubit), 2) - -def Swap(qubit, mqubit, delays, showPlot=False): - """ - Variable amplitude Rabi nutation experiment. - - Parameters - ---------- - qubit : logical channel to implement sequence (LogicalChannel) - amps : pulse amplitudes to sweep over (iterable) - phase : phase of the pulse (radians) - showPlot : whether to plot (boolean) - """ - resFunction = compile_function( - os.path.relpath(__file__), - "doSwap", - (qubit,) - ) - seqs = resFunction() - return seqs - - fileNames = qgl2_compile_to_hardware(seqs, "Swap/Swap") - print(fileNames) - if showPlot: - plot_pulse_files(fileNames) + Id(mqubit, length=d) + measConcurrently(allChans) + + create_cal_seqs(allChans, 2) + +# A main for running the sequences here with some typical argument values +# Here it runs all of them; could do a parse_args like main.py +def main(): + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + import numpy as np + import QGL.PulseShapes + + toHW = True + plotPulses = False + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in QRegister(s) NOT real Qubits + q1 = QRegister("q1") + q2 = QRegister("q2") + qr = QRegister(q1, q2) + + rAmpAmps = np.linspace(0, 1, 1) + rWidthWidths = np.linspace(0, 5e-6, 11) + ranqAmps = np.linspace(0, 5e-6, 11) + tCalR = 2 + rAmpPiAmps = np.linspace(0, 1, 11) + + def getRAmpAD(amps): + return [{ + 'name': 'amplitude', + 'unit': None, + 'points': list(amps), + 'partition': 1 + }] + def getRWidthAD(widths): + return [delay_descriptor(widths)] + + def getRAmpNQAD(qubits, amps, calRepeats): + return [ + { + 'name': 'amplitude', + 'unit': None, + 'points': list(amps), + 'partition': 1 + }, + cal_descriptor(qubits, calRepeats) + ] + + def getRAmpPiAD(amps): + return [{ + 'name': 'amplitude', + 'unit': None, + 'points': list(amps), + 'partition': 1 + }] + + # FIXME: See issue #44: Must supply all args to qgl2main for now + +# for func, args, label, axisDec in [("RabiAmp", (q1, rAmpAmps), "Rabi", getRAmpAD(rAmpAmps)), +# ("RabiWidth", (q1, rWidthWidths), "Rabi", getRWidthAD(rWidthWidths)), +# ("RabiAmpPi", (q1, q2, rAmpPiAmps), "Rabi", getRAmpPiAD(rAmpPiAmps)), +# ("SingleShow", (q1,), "SingleShot", None), +# ("PulsedSpec", (q1,), "Spec", None), +# ("RabiAmp_NQubits", (qr,ranqAmps), "Rabi", getRAmpNQAD(qr, ranqAmps, tCalR)), +# ("Swap", (q1, np.linspace(0, 5e-6, 11), "Swap", None), +# ]: + + for func, args, label, axisDesc in [("RabiAmp", (q1, rAmpAmps, 0), "Rabi", getRAmpAD(rAmpAmps)), + ("RabiWidth", (q1, rWidthWidths, 1, 0, QGL.PulseShapes.tanh), "Rabi", getRWidthAD(rWidthWidths)), + ("RabiAmpPi", (q1, q2, rAmpPiAmps, 0), "Rabi", getRAmpPiAD(rAmpPiAmps)), + ("SingleShot", (q1,), "SingleShot", None), + ("PulsedSpec", (q1,True), "Spec", None), + ("RabiAmp_NQubits", (qr,ranqAmps, 0, qr, False, tCalR), "Rabi", getRAmpNQAD(qr, ranqAmps, tCalR)), + ("Swap", (q1, np.linspace(0, 5e-6, 11), q2), "Swap", None) + ]: + + print(f"\nRun {func}...") + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunc = compile_function(__file__, func, args) + # Run the QGL2. Note that the generated function takes no arguments itself + seq = resFunc() + if toHW: + print(f"Compiling {func} sequences to hardware\n") + # To get verbose logging including showing the compiled sequences: + # QGL.Compiler.set_log_level() + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}', axis_descriptor=axisDesc) + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print(f"\nGenerated {func} sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(seq) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) + +if __name__ == "__main__": + main() diff --git a/src/python/qgl2/basic_sequences/RabiMin.py b/src/python/qgl2/basic_sequences/RabiMin.py deleted file mode 100644 index c31ec6b..0000000 --- a/src/python/qgl2/basic_sequences/RabiMin.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. - -# QGL2 versions of Rabi.py functions. -# These work around QGL2 constraints, such as only doing sequence generation and -# not compilation, or not taking arguments. - -from qgl2.qgl2 import qgl2decl, qreg, QRegister -from qgl2.qgl1 import Utheta, MEAS, X, Id -from qgl2.util import init - -import qgl2.basic_sequences.pulses - -from qgl2.basic_sequences.helpers import create_cal_seqs - -import numpy as np - -@qgl2decl -def doRabiWidth(q:qreg, widths): - # FIXME: Note the local re-definition of tanh - shapeFun = qgl2.basic_sequences.pulses.local_tanh - for l in widths: - init(q) - Utheta(q, length=l, amp=1, phase=0, shapeFun=shapeFun) - MEAS(q) - -@qgl2decl -def doRabiAmp(q:qreg, amps, phase): - for amp in amps: - init(q) - Utheta(q, amp=amp, phase=phase) - MEAS(q) - -@qgl2decl -def doRabiAmpPi(qr:qreg, amps): - for l in amps: - init(qr) - X(qr[1]) - Utheta(qr[0], amp=l, phase=0) - X(qr[1]) - MEAS(qr[1]) - -@qgl2decl -def doSingleShot(q:qreg): - init(q) - Id(q) - MEAS(q) - init(q) - X(q) - MEAS(q) - -@qgl2decl -def doPulsedSpec(q:qreg, specOn): - init(q) - if specOn: - X(q) - else: - Id(q) - MEAS(q) - -@qgl2decl -def doRabiAmp_NQubits(qr:qreg, amps, docals, calRepeats): - p = 0 - - for a in amps: - init(qr) - Utheta(qr, amp=a, phase=p) - MEAS(qr) - - if docals: - create_cal_seqs(qr, calRepeats) - -@qgl2decl -def doSwap(qr:qreg, delays): - for d in delays: - init(qr) - X(qr) - Id(qr[1], length=d) - Barrier(qr) - MEAS(qr) - - create_cal_seqs(qr, 2) diff --git a/src/python/qgl2/basic_sequences/SPAM.py b/src/python/qgl2/basic_sequences/SPAM.py index 8cd49a8..03d3ba1 100644 --- a/src/python/qgl2/basic_sequences/SPAM.py +++ b/src/python/qgl2/basic_sequences/SPAM.py @@ -1,14 +1,9 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. -# See SPAMMin for cleaner QGL2 versions +from qgl2.qgl2 import qgl2decl, qreg, qgl2main, pulse, QRegister -from qgl2.qgl2 import qgl2decl, qreg, qgl2main, pulse +from qgl2.qgl1 import X, U, Y90, X90, MEAS, Id -from QGL.PulsePrimitives import X, U, Y90, X90, MEAS, Id -from QGL.Compiler import compile_to_hardware -from QGL.PulseSequencePlotter import plot_pulse_files - -#from qgl2.basic_sequences.new_helpers import compileAndPlot, addMeasPulse from qgl2.util import init from itertools import chain @@ -31,7 +26,7 @@ def spam_seqs(angle, qubit: qreg, maxSpamBlocks=10): MEAS(qubit) @qgl2decl -def SPAM(qubit: qreg, angleSweep, maxSpamBlocks=10, showPlot=False): +def SPAM(qubit: qreg, angleSweep, maxSpamBlocks=10): """ X-Y sequence (X-Y-X-Y)**n to determine quadrature angles or mixer correction. @@ -40,7 +35,6 @@ def SPAM(qubit: qreg, angleSweep, maxSpamBlocks=10, showPlot=False): qubit : logical channel to implement sequence (LogicalChannel) angleSweep : angle shift to sweep over maxSpamBlocks : maximum number of XYXY block to do - showPlot : whether to plot (boolean) """ # Original: # def spam_seqs(angle): @@ -77,101 +71,63 @@ def SPAM(qubit: qreg, angleSweep, maxSpamBlocks=10, showPlot=False): X(qubit) MEAS(qubit) - # FIXME: Do this in caller - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot # compileAndPlot('SPAM/SPAM', showPlot) -def SPAMq1(qubit: qreg, angleSweep, maxSpamBlocks=10, showPlot=False): - """ - X-Y sequence (X-Y-X-Y)**n to determine quadrature angles or mixer correction. - - Parameters - ---------- - qubit : logical channel to implement sequence (LogicalChannel) - angleSweep : angle shift to sweep over - maxSpamBlocks : maximum number of XYXY block to do - showPlot : whether to plot (boolean) - """ - # Original: - # def spam_seqs(angle): - # """ Helper function to create a list of sequences increasing SPAM blocks with a given angle. """ - # SPAMBlock = [X(qubit), U(qubit, phase=pi/2+angle), X(qubit), U(qubit, phase=pi/2+angle)] - # return [[Y90(qubit)] + SPAMBlock*rep + [X90(qubit)] for rep in range(maxSpamBlocks)] - - # # Insert an identity at the start of every set to mark them off - # seqs = list(chain.from_iterable([[[Id(qubit)]] + spam_seqs(angle) for angle in angleSweep])) - - # # Add a final pi for reference - # seqs.append([X(qubit)]) - - # # Add the measurment block to every sequence - # measBlock = MEAS(qubit) - # for seq in seqs: - # seq.append(measBlock) - - # fileNames = compile_to_hardware(seqs, 'SPAM/SPAM') - # print(fileNames) - - # if showPlot: - # plot_pulse_files(fileNames) - - def spam_seqs(angle): - """ Helper function to create a list of sequences increasing SPAM blocks with a given angle. """ - #SPAMBlock = [X(qubit), U(qubit, phase=pi/2+angle), X(qubit), U(qubit, phase=pi/2+angle)] - #return [[Y90(qubit)] + SPAMBlock*rep + [X90(qubit)] for rep in range(maxSpamBlocks)] - seqs = [] - for rep in range(maxSpamBlocks): - seq = [] - seq.append(Y90(qubit)) - for _ in range(rep): - seq.append(X(qubit)) - seq.append(U(qubit, phase=pi/2+angle)) - seq.append(X(qubit)) - seq.append(U(qubit, phase=pi/2+angle)) - seq.append(X90(qubit)) - seqs.append(seq) - return seqs - - # Insert an identity at the start of every set to mark them off - seqs = [] - for angle in angleSweep: - seqs.append([Id(qubit)]) - spams = spam_seqs(angle) - for elem in spams: - seqs.append(elem) - - # Add a final pi for reference - seqs.append([X(qubit)]) - - # # Add the measurment block to every sequence -# seqs = addMeasPulse(seqs, qubit) - - # Be sure to un-decorate this function to make it work without the - # QGL2 compiler -# compileAndPlot(seqs, 'SPAM/SPAM', showPlot) - -# Imports for testing only -from QGL.Channels import Qubit, LogicalMarkerChannel -from qgl2.qgl1 import Qubit, QubitFactory -import numpy as np -from math import pi - -@qgl2main +# QGL1 function to compile the above QGL2 +# Uses main.py +# FIXME: Use the same argument parsing as main.py def main(): - # Set up 1 qbit, following model in QGL/test/test_Sequences - - # FIXME: Cannot use these in current QGL2 compiler, because - # a: QGL2 doesn't understand creating class instances, and - # b: QGL2 currently only understands the fake Qbits -# qg1 = LogicalMarkerChannel(label="q1-gate") -# q1 = Qubit(label='q1', gate_chan=qg1) -# q1.pulse_params['length'] = 30e-9 -# q1.pulse_params['phase'] = pi/2 - - # Use stub Qubits, but comment this out when running directly. - q1 = QubitFactory("q1") - SPAM(q1, np.linspace(0, pi/2, 11)) + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + import numpy as np + + toHW = True + plotPulses = True + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in a QRegister NOT the real Qubit + q = QRegister(1) + + # SPAM(q1, np.linspace(0, pi/2, 11)) + # - test_basic_mins uses np.linspace(0,1,11) + + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunction = compile_function(__file__, + "SPAM", + (q, np.linspace(0, pi/2, 11), 10)) + # Run the QGL2. Note that the generated function takes no arguments itself + sequences = resFunction() + if toHW: + print("Compiling sequences to hardware\n") + fileNames = qgl2_compile_to_hardware(sequences, filename='SPAM/SPAM') + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print("\nGenerated sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(sequences) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) if __name__ == "__main__": main() diff --git a/src/python/qgl2/basic_sequences/SPAMMin.py b/src/python/qgl2/basic_sequences/SPAMMin.py deleted file mode 100644 index 0559126..0000000 --- a/src/python/qgl2/basic_sequences/SPAMMin.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. - -# Cleaned up version of SPAM.py for QGL2 - -from qgl2.qgl2 import qgl2decl, qreg, QRegister -from qgl2.qgl1 import Y90, X, U, X90, MEAS, Id -from qgl2.util import init -from numpy import pi -import numpy as np - -@qgl2decl -def spam_seqs(angle, q: qreg, maxSpamBlocks=10): - for rep in range(maxSpamBlocks): - init(q) - Y90(q) - for _ in range(rep): - X(q) - U(q, phase=pi/2+angle) - X(q) - U(q, phase=pi/2+angle) - X90(q) - MEAS(q) - -#def doSPAM(angleSweep, maxSpamBlocks=10) -> sequence: -@qgl2decl -def doSPAM(q:qreg, angleSweep, maxSpamBlocks): - - # Insert an identity at the start of every set to mark them off - for angle in angleSweep: - init(q) - Id(q) - MEAS(q) - spam_seqs(angle, q, maxSpamBlocks) - - # Add a final pi for reference - init(q) - X(q) - MEAS(q) diff --git a/src/python/qgl2/basic_sequences/T1T2.py b/src/python/qgl2/basic_sequences/T1T2.py index ee00838..5c702ef 100644 --- a/src/python/qgl2/basic_sequences/T1T2.py +++ b/src/python/qgl2/basic_sequences/T1T2.py @@ -2,64 +2,16 @@ from qgl2.qgl2 import qgl2decl, qreg, qgl2main -from QGL.PulsePrimitives import X, Id, MEAS, X90, U90 -from QGL.Compiler import compile_to_hardware -from QGL.PulseSequencePlotter import plot_pulse_files +from qgl2.qgl1 import X, Id, MEAS, X90, U90 -from qgl2.basic_sequences.helpers import create_cal_seqs -#from qgl2.basic_sequences.new_helpers import addCalibration, compileAndPlot +from qgl2.basic_sequences.helpers import create_cal_seqs, cal_descriptor, delay_descriptor from qgl2.util import init -from qgl2.qgl1 import X90, Id, U90, MEAS, X, QubitFactory -from qgl2.qgl1 import Sync, Wait from scipy.constants import pi import numpy as np -def InversionRecoveryq1(qubit: qreg, delays, showPlot=False, calRepeats=2, suffix=False): - """ - Inversion recovery experiment to measure qubit T1 - - Parameters - ---------- - qubit : logical channel to implement sequence (LogicalChannel) - delays : delays after inversion before measurement (iterable; seconds) - showPlot : whether to plot (boolean) - calRepeats : how many repetitions of calibration pulses (int) - """ - - # Original: - # # Create the basic sequences - # seqs = [[X(qubit), Id(qubit, d), MEAS(qubit)] for d in delays] - - # # Tack on the calibration scalings - # seqs += create_cal_seqs((qubit,), calRepeats) - - # fileNames = compile_to_hardware(seqs, 'T1'+('_'+qubit.label)*suffix+'/T1'+('_'+qubit.label)*suffix) - # print(fileNames) - - # if showPlot: - # plot_pulse_files(fileNames) - seqs = [] - for d in delays: - seq = [] - seq.append(X(qubit)) - seq.append(Id(qubit, d)) - seq.append(MEAS(qubit)) - seqs.append(seq) - - # Tack on calibration -# seqs = addCalibration(seqs, (qubit,), calRepeats) - - # Calculate label - label = 'T1'+('_'+qubit.label)*suffix - fullLabel = label + '/' + label - - # Be sure to un-decorate this function to make it work without the - # QGL2 compiler -# compileAndPlot(seqs, fullLabel, showPlot) - @qgl2decl -def InversionRecovery(qubit: qreg, delays, showPlot=False, calRepeats=2, suffix=False): +def InversionRecovery(qubit: qreg, delays, calRepeats=2): """ Inversion recovery experiment to measure qubit T1 @@ -67,7 +19,6 @@ def InversionRecovery(qubit: qreg, delays, showPlot=False, calRepeats=2, suffix= ---------- qubit : logical channel to implement sequence (LogicalChannel) delays : delays after inversion before measurement (iterable; seconds) - showPlot : whether to plot (boolean) calRepeats : how many repetitions of calibration pulses (int) """ @@ -86,135 +37,24 @@ def InversionRecovery(qubit: qreg, delays, showPlot=False, calRepeats=2, suffix= for d in delays: init(qubit) X(qubit) - Id(qubit, d) + Id(qubit, length=d) MEAS(qubit) # Tack on calibration - create_cal_seqs((qubit,), calRepeats) + create_cal_seqs(qubit, calRepeats) - # Calculate label - label = 'T1'+('_'+qubit.label)*suffix - fullLabel = label + '/' + label +# metafile = compile_to_hardware(seqs, +# 'T1' + ('_' + qubit.label) * suffix + '/T1' + ('_' + qubit.label) * suffix, +# axis_descriptor=[ +# delay_descriptor(delays), +# cal_descriptor((qubit,), calRepeats) +# ]) - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot(fullLabel, showPlot) - -def Ramseyq1(qubit: qreg, pulseSpacings, TPPIFreq=0, showPlot=False, calRepeats=2, suffix=False): - """ - Variable pulse spacing Ramsey (pi/2 - tau - pi/2) with optional TPPI. - - Parameters - ---------- - qubit : logical channel to implement sequence (LogicalChannel) - pulseSpacings : pulse spacings (iterable; seconds) - TPPIFreq : frequency for TPPI phase updates of second Ramsey pulse (Hz) - showPlot : whether to plot (boolean) - calRepeats : how many repetitions of calibration pulses (int) - """ - # Original: - # # Create the phases for the TPPI - # phases = 2*pi*TPPIFreq*pulseSpacings - - # # Create the basic Ramsey sequence - # seqs = [[X90(qubit), Id(qubit, d), U90(qubit, phase=phase), MEAS(qubit)] - # for d,phase in zip(pulseSpacings, phases)] - - # # Tack on the calibration scalings - # seqs += create_cal_seqs((qubit,), calRepeats) - - # fileNames = compile_to_hardware(seqs, 'Ramsey'+('_'+qubit.label)*suffix+'/Ramsey'+('_'+qubit.label)*suffix) - # print(fileNames) - - # if showPlot: - # plot_pulse_files(fileNames) - - # Create the phases for the TPPI - phases = 2*pi*TPPIFreq*pulseSpacings - - # Creating sequences that look like this: - # [['X90', 'Id', 'U90', 'M'], ['X90', 'Id', 'U90', 'M']] - - # Create the basic Ramsey sequence - seqs = [] - for d,phase in zip(pulseSpacings, phases): - seq = [] - seq.append(X90(qubit)) - seq.append(Id(qubit, d)) - seq.append(U90(qubit, phase=phase)) - seq.append(MEAS(qubit)) - seqs.append(seq) - - # Tack on calibration -# seqs = addCalibration(seqs, (qubit,), calRepeats) - - # Calculate label - label = 'Ramsey'+('_'+qubit.label)*suffix - fullLabel = label + '/' + label - - # Be sure to un-decorate this function to make it work without the - # QGL2 compiler -# compileAndPlot(seqs, fullLabel, showPlot) - -# produce a Ramsey sequence on qreg named q # pulse spacings: 100ns to 10us step by 100ns # TPPIFreq: 1Mhz (arg is in hz) @qgl2decl -def doRamsey(): - q = QubitFactory('q1') - TPPIFreq=1e6 - # FIXME: QGL2 doesn't deal well with the call to np.arange - pulseS = [ 1.00000000e-07, 2.00000000e-07, 3.00000000e-07, - 4.00000000e-07, 5.00000000e-07, 6.00000000e-07, - 7.00000000e-07, 8.00000000e-07, 9.00000000e-07, - 1.00000000e-06, 1.10000000e-06, 1.20000000e-06, - 1.30000000e-06, 1.40000000e-06, 1.50000000e-06, - 1.60000000e-06, 1.70000000e-06, 1.80000000e-06, - 1.90000000e-06, 2.00000000e-06, 2.10000000e-06, - 2.20000000e-06, 2.30000000e-06, 2.40000000e-06, - 2.50000000e-06, 2.60000000e-06, 2.70000000e-06, - 2.80000000e-06, 2.90000000e-06, 3.00000000e-06, - 3.10000000e-06, 3.20000000e-06, 3.30000000e-06, - 3.40000000e-06, 3.50000000e-06, 3.60000000e-06, - 3.70000000e-06, 3.80000000e-06, 3.90000000e-06, - 4.00000000e-06, 4.10000000e-06, 4.20000000e-06, - 4.30000000e-06, 4.40000000e-06, 4.50000000e-06, - 4.60000000e-06, 4.70000000e-06, 4.80000000e-06, - 4.90000000e-06, 5.00000000e-06, 5.10000000e-06, - 5.20000000e-06, 5.30000000e-06, 5.40000000e-06, - 5.50000000e-06, 5.60000000e-06, 5.70000000e-06, - 5.80000000e-06, 5.90000000e-06, 6.00000000e-06, - 6.10000000e-06, 6.20000000e-06, 6.30000000e-06, - 6.40000000e-06, 6.50000000e-06, 6.60000000e-06, - 6.70000000e-06, 6.80000000e-06, 6.90000000e-06, - 7.00000000e-06, 7.10000000e-06, 7.20000000e-06, - 7.30000000e-06, 7.40000000e-06, 7.50000000e-06, - 7.60000000e-06, 7.70000000e-06, 7.80000000e-06, - 7.90000000e-06, 8.00000000e-06, 8.10000000e-06, - 8.20000000e-06, 8.30000000e-06, 8.40000000e-06, - 8.50000000e-06, 8.60000000e-06, 8.70000000e-06, - 8.80000000e-06, 8.90000000e-06, 9.00000000e-06, - 9.10000000e-06, 9.20000000e-06, 9.30000000e-06, - 9.40000000e-06, 9.50000000e-06, 9.60000000e-06, - 9.70000000e-06, 9.80000000e-06, 9.90000000e-06] - #pulseSpacings=np.arange(100e-9, 10e-6, 100e-9) - # Create the phases for the TPPI - phases = 2*pi*TPPIFreq*pulseS - # Create the basic Ramsey sequence - # FIXME: QGL2 doesn't deal well with this call to zip - for d,phase in zip(pulseS, phases): - init(q) - X90(q) - Id(q, d) - U90(q, phase=phase) - MEAS(q) - - # Tack on calibration - create_cal_seqs((q,), calRepeats) - -@qgl2decl -def Ramsey(qubit: qreg, pulseSpacings, TPPIFreq=0, showPlot=False, calRepeats=2, suffix=False): +def Ramsey(qubit: qreg, pulseSpacings, TPPIFreq=0, calRepeats=2): """ Variable pulse spacing Ramsey (pi/2 - tau - pi/2) with optional TPPI. @@ -223,7 +63,6 @@ def Ramsey(qubit: qreg, pulseSpacings, TPPIFreq=0, showPlot=False, calRepeats=2, qubit : logical channel to implement sequence (LogicalChannel) pulseSpacings : pulse spacings (iterable; seconds) TPPIFreq : frequency for TPPI phase updates of second Ramsey pulse (Hz) - showPlot : whether to plot (boolean) calRepeats : how many repetitions of calibration pulses (int) """ @@ -254,63 +93,106 @@ def Ramsey(qubit: qreg, pulseSpacings, TPPIFreq=0, showPlot=False, calRepeats=2, for d,phase in zip(pulseSpacings, phases): init(qubit) X90(qubit) - Id(qubit, d) + Id(qubit, length=d) U90(qubit, phase=phase) MEAS(qubit) # Tack on calibration - create_cal_seqs((qubit,), calRepeats) + create_cal_seqs(qubit, calRepeats) - # Calculate label - label = 'Ramsey'+('_'+qubit.label)*suffix - fullLabel = label + '/' + label +# metafile = compile_to_hardware(seqs, +# 'Ramsey' + ('_' + qubit.label) * suffix + '/Ramsey' + ('_' + qubit.label) * suffix, +# axis_descriptor=[ +# delay_descriptor(pulseSpacings), +# cal_descriptor((qubit,), calRepeats) +# ]) - # Here we rely on the QGL compiler to pass in the sequence it - # generates to compileAndPlot -# compileAndPlot(fullLabel, showPlot) - -# Imports for testing only -from QGL.Channels import Qubit, LogicalMarkerChannel, Measurement -import QGL.ChannelLibraries as ChannelLibraries -from qgl2.qgl1 import QubitFactory -import numpy as np -from math import pi -@qgl2main +# A main for running the sequences here with some typical argument values +# Here it runs all of them; could do a parse_args like main.py def main(): - # Set up 2 qregs, following model in QGL/test/test_Sequences - - # FIXME: Cannot use these in current QGL2 compiler, because - # a: QGL2 doesn't understand creating class instances, and - # b: QGL2 currently only understands the fake Qbits -# qg1 = LogicalMarkerChannel(label="q1-gate") -# q1 = Qubit(label='q1', gate_chan=qg1) -# q1.pulse_params['length'] = 30e-9 -# q1.pulse_params['phase'] = pi/2 -# sTrig = LogicalMarkerChannel(label='slaveTrig') -# dTrig = LogicalMarkerChannel(label='digitizerTrig') -# Mq1 = ''; -# Mq1gate = LogicalMarkerChannel(label='M-q1-gate') -# m = Measurement(label='M-q1', gate_chan = Mq1gate, trig_chan = dTrig) - -# ChannelLibraries.channelLib = ChannelLibraries.ChannelLibrary(blank=True) -# ChannelLibraries.channelLib.channelDict = { -# 'q1-gate': qg1, -# 'q1': q1, -# 'slaveTrig': sTrig, -# 'digitizerTrig': dTrig, -# 'M-q1': m, -# 'M-q1-gate': Mq1gate -# } -# ChannelLibrary.channelLib.build_connectivity_graph() - - # Use stub Qubits, but comment this out when running directly. - q1 = QubitFactory("q1") - - print("Run InversionRecovery") - InversionRecovery(q1, np.linspace(0, 5e-6, 11)) - print("Run Ramsey") - Ramsey(q1, np.linspace(0, 5e-6, 11)) + from pyqgl2.qreg import QRegister + import pyqgl2.test_cl + from pyqgl2.main import compile_function, qgl2_compile_to_hardware + + toHW = True + plotPulses = False # Don't try creating graphics objects + suffix = False # change generated filename to include qbit name + pyqgl2.test_cl.create_default_channelLibrary(toHW, True) + +# # To turn on verbose logging in compile_function +# from pyqgl2.ast_util import NodeError +# from pyqgl2.debugmsg import DebugMsg +# NodeError.MUTE_ERR_LEVEL = NodeError.NODE_ERROR_NONE +# DebugMsg.set_level(0) + + # Now compile the QGL2 to produce the function that would generate the expected sequence. + # Supply the path to the QGL2, the main function in that file, and a list of the args to that function. + # Can optionally supply saveOutput=True to save the qgl1.py + # file, + # and intermediate_output="path-to-output-file" to save + # intermediate products + + # Pass in QRegister(s) NOT real Qubits + qbitName = "q1" + q1 = QRegister(qbitName) + + # FIXME: See issue #44: Must supply all args to qgl2main for now + + # InversionRecovery(q1, np.linspace(0, 5e-6, 11)) + # Ramsey(q1, np.linspace(0, 5e-6, 11)) + + irDelays = np.linspace(0, 5e-6, 11) + rSpacings = np.linspace(0, 5e-6, 11) + tCalR = 2 + + def irAD(delays, calRepeats): + return [ + delay_descriptor(delays), + cal_descriptor(('qubit',), calRepeats) + ] + + def rAD(pulseSpacings, calRepeats): + return [ + delay_descriptor(pulseSpacings), + cal_descriptor(('qubit',), calRepeats) + ] + +# for func, args, label, axisDesc in [("InversionRecovery", (q1, irDelays), "T1", irAD(irDelays, tCalR)), +# ("Ramsey", (q1, rSpacings), "Ramsey", rAD(rSpacings, tCalR)) +# ]: + for func, args, label, axisDesc in [("InversionRecovery", (q1, irDelays, tCalR), "T1", irAD(irDelays, tCalR)), + ("Ramsey", (q1, rSpacings, 0, tCalR), "Ramsey", rAD(rSpacings, tCalR)) + ]: + + print(f"\nRun {func}...") + # Here we know the function is in the current file + # You could use os.path.dirname(os.path.realpath(__file)) to find files relative to this script, + # Or os.getcwd() to get files relative to where you ran from. Or always use absolute paths. + resFunc = compile_function(__file__, func, args) + # Run the QGL2. Note that the generated function takes no arguments itself + seq = resFunc() + if toHW: + print(f"Compiling {func} sequences to hardware\n") + + # Generate proper filenames; for these, it isn't just the label Ramsey + # T1T2 QGL functions take a suffix boolean default false. If true, then append to label "_qubit.label"; ie "_q1" + if suffix: + label = label + f"_{qbitName}" + + fileNames = qgl2_compile_to_hardware(seq, filename=f'{label}/{label}', axis_descriptor=axisDesc) + print(f"Compiled sequences; metafile = {fileNames}") + if plotPulses: + from QGL.PulseSequencePlotter import plot_pulse_files + # FIXME: As called, this returns a graphical object to display + plot_pulse_files(fileNames) + else: + print(f"\nGenerated {func} sequences:\n") + from QGL.Scheduler import schedule + + scheduled_seq = schedule(seq) + from IPython.lib.pretty import pretty + print(pretty(scheduled_seq)) if __name__ == "__main__": main() diff --git a/src/python/qgl2/basic_sequences/T1T2Min.py b/src/python/qgl2/basic_sequences/T1T2Min.py deleted file mode 100644 index 077f0bd..0000000 --- a/src/python/qgl2/basic_sequences/T1T2Min.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. - -# QGL2 clean versions for T1T2.py - -from qgl2.qgl2 import qgl2decl, qreg, QRegister -from qgl2.basic_sequences.helpers import create_cal_seqs -from qgl2.util import init -from qgl2.qgl1 import X, Id, MEAS, U90, X90 -import numpy as np -from numpy import pi - -@qgl2decl -def doInversionRecovery(q:qreg, delays, calRepeats): - for d in delays: - init(q) - X(q) - Id(q, length=d) - MEAS(q) - - # Tack on calibration - create_cal_seqs(q, calRepeats) - -@qgl2decl -def doRamsey(q:qreg, delays, TPPIFreq, calRepeats): - # Create the phases for the TPPI - phases = 2*pi*TPPIFreq*delays - - # Create the basic Ramsey sequence - for d,phase in zip(delays, phases): - init(q) - X90(q) - Id(q, length=d) - U90(q, phase=phase) - MEAS(q) - - # Tack on calibration - create_cal_seqs(q, calRepeats) diff --git a/src/python/qgl2/basic_sequences/helpers.py b/src/python/qgl2/basic_sequences/helpers.py index c297416..97ec54e 100644 --- a/src/python/qgl2/basic_sequences/helpers.py +++ b/src/python/qgl2/basic_sequences/helpers.py @@ -5,7 +5,11 @@ from qgl2.util import init from itertools import product +import operator +from functools import reduce +import numpy as np +# FIXME: measChans should be declared a qreg, but the inliner isn't handling that @qgl2decl def create_cal_seqs(qubits: qreg, numRepeats, measChans=None, waitcmp=False, delay=None): """ @@ -19,8 +23,13 @@ def create_cal_seqs(qubits: qreg, numRepeats, measChans=None, waitcmp=False, del waitcmp = True if the sequence contains branching; default False delay: optional time between state preparation and measurement (s) """ + # Allows supplying a tuple as is usually done in QGL1 + qubitreg = QRegister(qubits) + + # QGL2 will warn here: + # warning: parameter [measChans] overwritten by assignment if measChans is None: - measChans = qubits + measChans = qubitreg # Make all combinations for qubit calibration states for n qubits and repeat @@ -35,15 +44,15 @@ def create_cal_seqs(qubits: qreg, numRepeats, measChans=None, waitcmp=False, del # Calibrate using Id and X pulses calSet = [Id, X] - for pulseSet in product(calSet, repeat=len(qubits)): + for pulseSet in product(calSet, repeat=len(qubitreg)): # Repeat each calibration numRepeats times for _ in range(numRepeats): - init(qubits) - for pulse, qubit in zip(pulseSet, qubits): + init(qubitreg) + for pulse, qubit in zip(pulseSet, qubitreg): pulse(qubit) if delay: # Add optional delay before measurement - Id(qubits(0), length=delay) + Id(qubitreg(0), length=delay) Barrier(measChans) MEAS(measChans) # If branching do wait @@ -52,7 +61,42 @@ def create_cal_seqs(qubits: qreg, numRepeats, measChans=None, waitcmp=False, del @qgl2decl def measConcurrently(listNQubits: qreg) -> pulse: - '''Concurrently measure given QRegister of qubits.''' + '''Concurrently measure given QRegister of qubits. + Note: Includes a Barrier on the input qreg to force measurements + to be concurrent; QGL1 does Pulse*Pulse == PulseBlock(pulses), which is equivalent.''' qr = QRegister(listNQubits) Barrier(qr) MEAS(qr) + +# Copied from QGL/BasicSequences/helpers +def cal_descriptor(qubits, numRepeats, partition=2, states = ['0', '1']): + # generate state set in same order as we do above in create_cal_seqs() + state_set = [reduce(operator.add, s) for s in product(states, repeat=len(qubits))] + descriptor = { + 'name': 'calibration', + 'unit': 'state', + 'partition': partition, + 'points': [] + } + for state in state_set: + descriptor['points'] += [state] * numRepeats + return descriptor + +# Copied from QGL/BasicSequences/helpers +def delay_descriptor(delays, desired_units="us"): + if desired_units == "s": + scale = 1 + elif desired_units == "ms": + scale = 1e3 + elif desired_units == "us" or desired_units == u"μs": + scale = 1e6 + elif desired_units == "ns": + scale = 1e9 + axis_descriptor = { + 'name': 'delay', + 'unit': desired_units, + # Make sure delays is a numpy array so can multiply it by a float safely + 'points': list(scale * np.array(delays)), + 'partition': 1 + } + return axis_descriptor diff --git a/src/python/qgl2/basic_sequences/pulses.py b/src/python/qgl2/basic_sequences/pulses.py deleted file mode 100644 index cbac0c1..0000000 --- a/src/python/qgl2/basic_sequences/pulses.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. - -# from QGL.PulseShapes import tanh - -import numpy as np - -# a local copy of QGL.PulseShapes.tanh, because pulling in -# QGL/__init__.py causes QGL2 grief. -# See RabiMin -def local_tanh(amp=1, length=0, sigma=0, cutoff=2, sampling_rate=1e9, **params): - ''' - A rounded constant shape from the sum of two tanh shapes. - ''' - numPts = np.round(length * sampling_rate) - xPts = np.linspace(-length / 2, length / 2, numPts) - x1 = -length / 2 + cutoff * sigma - x2 = +length / 2 - cutoff * sigma - return amp * 0.5 * (np.tanh((xPts - x1) / sigma) + np.tanh( - (x2 - xPts) / sigma)).astype(np.complex) - diff --git a/src/python/qgl2/qgl1.py b/src/python/qgl2/qgl1.py index 71ba815..ffc9cc9 100644 --- a/src/python/qgl2/qgl1.py +++ b/src/python/qgl2/qgl1.py @@ -4,9 +4,10 @@ # how to handle these functions # The annotations are defined in here -from qgl2.qgl2 import qreg, pulse, qgl2stub, qgl2meas, control, classical +from qgl2.qgl2 import qreg, pulse, qgl2stub, qgl2meas, control, classical, sequence # Many uses of Id supply a delay. That's the length: an int or float +# Must use the label 'length' # FIXME: Do we need to include that explicitly? @qgl2stub('QGL.PulsePrimitives') def Id(channel: qreg, *args, **kwargs) -> pulse: @@ -35,6 +36,14 @@ def Ztheta(qubit: qreg, angle=0, label='Ztheta', **kwargs) -> pulse: def X(qubit: qreg, **kwargs) -> pulse: print('X') +@qgl2stub('QGL.PulsePrimitives') +def Xm(qubit: qreg, **kwargs) -> pulse: + print('Xm') + +@qgl2stub('QGL.PulsePrimitives') +def Ym(qubit: qreg, **kwargs) -> pulse: + print('Ym') + @qgl2stub('QGL.PulsePrimitives') def X90(qubit: qreg, **kwargs) -> pulse: print('X90') @@ -75,6 +84,18 @@ def U90(qubi: qreg, phase=0, **kwargs) -> pulse: def AC(qubit: qreg, cliffNum) -> pulse: print('AC') +@qgl2stub('QGL.PulsePrimitives') +def ZX90_CR(controlQ: qreg, targetQ: qreg, **kwargs) -> pulse: + """ + A calibrated CR ZX90 pulse. Uses 'amp' for the pulse amplitude, 'phase' for its phase (in deg). + """ + print('ZX90_CR') + +# Used by RB basic sequences +@qgl2stub('QGL.Cliffords') +def clifford_seq(c, q1: qreg, q2: qreg = None) -> sequence: + print('clifford_seq') + @qgl2stub('QGL.PulsePrimitives') def flat_top_gaussian(chan: qreg, riseFall, length, amp, phase=0, label="flat_top_gaussian") -> pulse: print('flat_top_gaussian') @@ -85,9 +106,9 @@ def flat_top_gaussian_edge(source: qreg, target: qreg, riseFall, print('flat_top_gaussian_edge') # Helper for CPMG, to get around not being able to access qubit params (issue #37) -@qgl2stub('qgl2.qgl1_util', 'idPulseCentered') -def idPulseCentered(qubit: qreg, pulseSpacing) -> pulse: - print("Id(qubit, length=(pulseSpacing - qubit.pulse_params['length']) / 2)") +@qgl2stub('qgl2.qgl1_util', 'pulseCentered') +def pulseCentered(qubit: qreg, pFunc, pulseSpacing) -> pulse: + print("pFunc(qubit, length=(pulseSpacing - qubit.pulse_params['length']) / 2)") @qgl2stub('QGL.PulsePrimitives') def echoCR(controlQ: qreg, targetQ: qreg, amp=1, phase=0, length=200e-9, riseFall=20e-9, lastPi=True) -> pulse: diff --git a/src/python/qgl2/qgl1_util.py b/src/python/qgl2/qgl1_util.py index bbe2e96..b686f53 100644 --- a/src/python/qgl2/qgl1_util.py +++ b/src/python/qgl2/qgl1_util.py @@ -4,7 +4,7 @@ from QGL.ChannelLibraries import EdgeFactory from QGL.ControlFlow import Sync, Wait -from QGL.PulsePrimitives import flat_top_gaussian, Id +from QGL.PulsePrimitives import * def init_real(*args): return Wait(args) @@ -18,5 +18,5 @@ def flat_top_gaussian_edge_impl( # Helper for CPMG # See issue #37 -def idPulseCentered(qubit, pulseSpacing): - return Id(qubit, length=(pulseSpacing - qubit.pulse_params["length"]) / 2) +def pulseCentered(qubit, pFunc, pulseSpacing): + return pFunc(qubit, length=(pulseSpacing - qubit.pulse_params["length"]) / 2) diff --git a/test/code/toplevel_binding.py b/test/code/toplevel_binding.py index d2a6f39..587b95b 100644 --- a/test/code/toplevel_binding.py +++ b/test/code/toplevel_binding.py @@ -22,7 +22,7 @@ def main3(q:qreg, amps): @qgl2decl def main4(q:qreg, amps, shape): for a in amps: - Xtheta(q, amp=a, shapeFun=shape) + Xtheta(q, amp=a, shape_fun=shape) @qgl2decl def main5(qs:qreg): diff --git a/test/helpers.py b/test/helpers.py index d2f5c7b..0ae8850 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -11,21 +11,22 @@ from QGL.PulsePrimitives import Id, X, MEAS from QGL.ControlFlow import qsync, qwait, ControlInstruction, Goto, Barrier from QGL.BlockLabel import BlockLabel +#from QGL.Compiler import normalize from pyqgl2.test_cl import create_default_channelLibrary import collections from math import pi -def channel_setup(new=True): +def channel_setup(doHW=False, new=True): # new indicates replace any existing library # Otherwise if there is an existing library, use it # FIXME: For now, supplying first arg false meaning do not create physical channel mappings if not new and ChannelLibraries.channelLib is not None and len(ChannelLibraries.channelLib.keys()) != 0: - create_default_channelLibrary(False, False) + create_default_channelLibrary(doHW, False) # create_channel_library(ChannelLibraries.channelLib.channelDict) else: - create_default_channelLibrary(False, True) + create_default_channelLibrary(doHW, True) # create_channel_library(new=True) # # OBE: Create a basic channel library @@ -182,10 +183,44 @@ def testable_sequence(seqs): by flattening pulse lists. ''' seqs = flattenSeqs(seqs) +# seqs = normalize(seqs, None) return seqs +def stripWaitBarrier(seqs): + '''' + QGL2 includes Waits and Barriers that are added after unit tests in QGL1, so strip them + for comparison. Note however that Barrier;Pulse is how QGL2 does QGL1 PulseBlock(pulses). + ''' + from QGL.ControlFlow import Barrier, Wait + newS = [] + for el in seqs: + if isinstance(el, Barrier) or isinstance(el, Wait): + continue + if isinstance(el, collections.Iterable) and not isinstance(el, (str, Pulse, CompositePulse)) : + newel = stripWaitBarrier(el) + newS.extend(newel) + else: + newS.append(el) + return newS + +# See RB SimultaneousRB_AC test +def flattenPulseBlocks(seqs): + '''Turn PulseBlocks into linear series of pulses. + Used on QGL1 sequences to look more like QGL2 (which precedes these with a Barrier, so is equivalent) + ''' + from QGL.PulseSequencer import PulseBlock + newS = [] + for el in seqs: + if isinstance(el, PulseBlock): + for p in el.pulses.values(): + newS.append(p) + else: + newS.append(el) + return newS + # Adapted from unittest.case.py: assertSequenceEqual # Except use difflib.unified_diff instead of ndiff - much faster (less detail) +# Note QGL2 uses Barriers to force concurrency where QGL1 might use PulseBlock; that will look different. def assertPulseSequenceEqual(test, seq1, seq2, msg=None): """An equality assertion for ordered sequences of pulses. diff --git a/test/test_basic_mins.py b/test/test_basic_mins.py index b1d709a..602523e 100644 --- a/test/test_basic_mins.py +++ b/test/test_basic_mins.py @@ -1,11 +1,12 @@ # Copyright 2016 by Raytheon BBN Technologies Corp. All Rights Reserved. ''' -Test the qgl1/basic_sequences +Test the qgl2/basic_sequences to ensure they replicate the QGL1 functionality. ''' import datetime import unittest import numpy as np from math import pi +import random from pyqgl2.main import compile_function from pyqgl2.qreg import QRegister @@ -13,7 +14,8 @@ from test.helpers import testable_sequence, \ channel_setup, assertPulseSequenceEqual, \ - get_cal_seqs_1qubit, get_cal_seqs_2qubits + get_cal_seqs_1qubit, get_cal_seqs_2qubits, \ + stripWaitBarrier, flattenPulseBlocks class TestAllXY(unittest.TestCase): def setUp(self): @@ -22,7 +24,7 @@ def setUp(self): def test_AllXY(self): # QGL1 uses QubitFactory, QGL2 uses QRegister q1 = QubitFactory('q1') - qr = QRegister(1) + qr = QRegister(q1) # Specify the QGL1 we expect QGL2 to generate # Note in this case we specify only a sample of the start @@ -121,6 +123,7 @@ def test_AllXY_alt2(self): self.assertEqual(len(seqs), 4*21*2) assertPulseSequenceEqual(self, seqs[:len(expectedseq)], expectedseq) +# BlankingSweeps are OBE, so not tested class TestCR(unittest.TestCase): def setUp(self): @@ -129,9 +132,8 @@ def setUp(self): def test_PiRabi(self): controlQ = QubitFactory('q1') targetQ = QubitFactory('q2') - controlQR = QRegister('q1') - targetQR = QRegister('q2') - qr = QRegister('q1', 'q2') + controlQR = QRegister(controlQ) + targetQR = QRegister(targetQ) edge = EdgeFactory(controlQ, targetQ) lengths = np.linspace(0, 4e-6, 11) riseFall=40e-9 @@ -178,7 +180,7 @@ def test_PiRabi(self): def test_EchoCRLen(self): controlQ = QubitFactory('q1') targetQ = QubitFactory('q2') - cR = QRegister('q1') + cR = QRegister('q1') # Equivalent to QRegister(controlQ) tR = QRegister('q2') # FIXME: Better values!? lengths = np.linspace(0, 2e-6, 11) @@ -370,6 +372,16 @@ def addt180t(q, pulseSpacingDiff, rep): seqs = testable_sequence(seqs) assertPulseSequenceEqual(self, seqs, expectedseq) +class TestFeedback(unittest.TestCase): + def setUp(self): + channel_setup() + + # FIXME: Add tests for these once implemented + #def test_Reset(self); + # ("Reset", (q1, np.linspace(0, 5e-6, 11), 0, 2), "Reset"), + #def test_BitFlip3(self); + # ("BitFlip3", (q1, [0, 2, 4, 6], 500e-9, 2), "BitFlip"), + class TestFlipFlop(unittest.TestCase): def setUp(self): channel_setup() @@ -411,22 +423,252 @@ def addFFSeqs(dragParam, maxNumFFs, qubit): X(qubit), MEAS(qubit) ] - resFunction = compile_function("src/python/qgl2/basic_sequences/FlipFlopMin.py", - "doFlipFlop", + resFunction = compile_function("src/python/qgl2/basic_sequences/FlipFlop.py", + "FlipFlop", (qr, dragParamSweep, maxNumFFs)) seqs = resFunction() seqs = testable_sequence(seqs) assertPulseSequenceEqual(self, seqs, expectedseq) -## RB isn't ready yet -class TestRabiMin(unittest.TestCase): +# FIXME: Tests for this class are incomplete +class TestRB(unittest.TestCase): + def setUp(self): + channel_setup(doHW=True) + + def test_SingleQubitRB(self): + q1 = QubitFactory('q1') + qr = QRegister(q1) + np.random.seed(20152606) # set seed for create_RB_seqs() + random.seed(20152606) # set seed for random.choice() + # Range below should be 1,7 but that takes too long; use 1,2 so it's quick + rbseqs = create_RB_seqs(1, 2**np.arange(1,2)) + purity = True + add_cals = True + + # Try copying in the basic QGL1 code + # Can't do it directly since that code doesn't return the + # sequence + # This isn't quite right; this is before adding the Waits for example + expectedseq = [] + def testSingleQubitRB(qubit, rbseqs, purit=False, add_cal=True): + from QGL.Cliffords import clifford_seq + from QGL.BasicSequences.helpers import create_cal_seqs + from functools import reduce + import operator + seqsBis = [] + op = [Id(qubit, length=0), Y90m(qubit), X90(qubit)] + for ct in range(3 if purit else 1): + for seq in rbseqs: + seqsBis.append(reduce(operator.add, [clifford_seq(c, qubit) + for c in seq])) + #append tomography pulse to measure purity + seqsBis[-1].append(op[ct]) + #append measurement + seqsBis[-1].append(MEAS(qubit)) + #Tack on the calibration sequences + if add_cal: + seqsBis += create_cal_seqs((qubit, ), 2) + return seqsBis + + expectedseq = testSingleQubitRB(q1, rbseqs, purity, add_cals) + # Must reset the seeds because QGL1 used the prior values, to ensure QGL2 gets same values + np.random.seed(20152606) # set seed for create_RB_seqs() + random.seed(20152606) # set seed for random.choice() + resFunction = compile_function("src/python/qgl2/basic_sequences/RB.py", + "SingleQubitRB", + (qr, rbseqs, purity, add_cals)) + seqs = resFunction() + seqs = testable_sequence(seqs) + # Run testable on the QGL1 to flatten the sequence of sequences + expectedseq = testable_sequence(expectedseq) + # Strip out the QGL2 Waits and Barriers that QGL1 doesn't have + seqs = stripWaitBarrier(seqs) + # self.maxDiff = None + assertPulseSequenceEqual(self, seqs, expectedseq) + + def test_TwoQubitRB(self): + q1 = QubitFactory('q1') + q2 = QubitFactory('q2') + qr1 = QRegister(q1) + qr2 = QRegister(q2) + np.random.seed(20152606) # set seed for create_RB_seqs() + # Without this next seed, results differ run to run and QGL1 to QGL2 + random.seed(20152606) # set seed for random.choice() + # Repeats below should be 16 but that takes too long; use 4 so it's quick + rbseqs = create_RB_seqs(2, [2, 4, 8, 16, 32], repeats=4) + add_cals = True + + # Try copying in the basic QGL1 code + # Can't do it directly since that code doesn't return the + # sequence + # This isn't quite right; this is before adding the Waits for example + expectedseq = [] + def testTwoQubitRB(q1, q2, rbseqs, add_cal=True): + from QGL.Cliffords import clifford_seq + from QGL.BasicSequences.helpers import create_cal_seqs + from functools import reduce + import operator + seqsBis = [] + for seq in rbseqs: + seqsBis.append(reduce(operator.add, [clifford_seq(c, q2, q1) + for c in seq])) + + #Add the measurement to all sequences + for seq in seqsBis: + # FIXME: Correct thing is doing these with * as below, + # But that differs from QGL2 version + # seq.append(MEAS(q1) * MEAS(q2)) + seq.append(MEAS(q1)) + seq.append(MEAS(q2)) + #Tack on the calibration sequences + if add_cal: + seqsBis += create_cal_seqs((q1, q2), 2) + return seqsBis + + expectedseq = testTwoQubitRB(q1, q2, rbseqs, add_cals) + # Must reset the seeds because QGL1 used the prior values, to ensure QGL2 gets same values + np.random.seed(20152606) # set seed for create_RB_seqs() + # Without this next seed, results differ run to run and QGL1 to QGL2 + random.seed(20152606) # set seed for random.choice() + resFunction = compile_function("src/python/qgl2/basic_sequences/RB.py", + "TwoQubitRB", + (qr1, qr2, rbseqs, add_cals)) + seqs = resFunction() + seqs = testable_sequence(seqs) + # Run testable on the QGL1 to flatten the sequence of sequences + expectedseq = testable_sequence(expectedseq) + # Strip out the QGL2 Waits and Barriers that QGL1 doesn't have + # Note that if you want to see the real sequence, don't do this + seqs = stripWaitBarrier(seqs) + # self.maxDiff = None + # Note: We expect the sequences to start differing around element 2110, due + # to PulseBlock vs list of pulses, given QGL2 uses Barrier;Pulse where QGL1 uses PulseBlock(pulse) + # (but that difference is harmless we think) + assertPulseSequenceEqual(self, seqs[:2110], expectedseq[:2110]) + # assertPulseSequenceEqual(self, seqs, expectedseq) + + def test_SingleQubitRB_AC(self): + q1 = QubitFactory('q1') + q2 = QubitFactory('q2') + qr1 = QRegister(q1) + qr2 = QRegister(q2) + np.random.seed(20152606) # set seed for create_RB_seqs() + rbseqs = create_RB_seqs(1, 2**np.arange(1,7)) + add_cals = True + purity = False + + # Try copying in the basic QGL1 code + # Can't do it directly since that code doesn't return the + # sequence + # This isn't quite right; this is before adding the Waits for example + expectedseq = [] + def testSingleQubitRB_AC(qubit, seqs, purit=False, add_cal=True): + from QGL.PulsePrimitives import AC, MEAS, Id, Y90m, X90 + from QGL.BasicSequences.helpers import create_cal_seqs + from functools import reduce + import operator + seqsBis = [] + op = [Id(qubit, length=0), Y90m(qubit), X90(qubit)] + for ct in range(3 if purit else 1): + for seq in seqs: + seqsBis.append([AC(qubit, c) for c in seq]) + # append tomography pulse to measure purity + seqsBis[-1].append(op[ct]) + # append measurement + seqsBis[-1].append(MEAS(qubit)) + # Tack on the calibration sequences + if add_cals: + seqsBis += create_cal_seqs((qubit, ), 2) + return seqsBis + + expectedseq = testSingleQubitRB_AC(q1, rbseqs, purity, add_cals) + # Must reset the seeds because QGL1 used the prior values, to ensure QGL2 gets same values + np.random.seed(20152606) # set seed for create_RB_seqs() + resFunction = compile_function("src/python/qgl2/basic_sequences/RB.py", + "SingleQubitRB_AC", + (qr1, rbseqs, purity, add_cals)) + seqs = resFunction() + seqs = testable_sequence(seqs) + # Run testable on the QGL1 to flatten the sequence of sequences + expectedseq = testable_sequence(expectedseq) + # Strip out the QGL2 Waits and Barriers that QGL1 doesn't have + # Note that if you want to see the real sequence, don't do this + seqs = stripWaitBarrier(seqs) + # self.maxDiff = None + assertPulseSequenceEqual(self, seqs, expectedseq) + + def test_SimultaneousRB_AC(self): + q1 = QubitFactory('q1') + q2 = QubitFactory('q2') + qr1 = QRegister(q1) + qr2 = QRegister(q2) + qr = QRegister(q1, q2) + np.random.seed(20151709) # set seed for create_RB_seqs() + rbseqs = create_RB_seqs(1, 2**np.arange(1,7)) + add_cals = True + + # Try copying in the basic QGL1 code + # Can't do it directly since that code doesn't return the + # sequence + # This isn't quite right; this is before adding the Waits for example + expectedseq = [] + def testSimultaneousRB_AC(qubits, seqs, add_cal=True): + from QGL.PulsePrimitives import AC, MEAS + from QGL.BasicSequences.helpers import create_cal_seqs + from functools import reduce + import operator + seqsBis = [] + for seq in zip(*seqs): + seqsBis.append([reduce(operator.__mul__, + [AC(q, c) for q, c in zip(qubits, pulseNums)]) + for pulseNums in zip(*seq)]) + + # Add the measurement to all sequences + for seq in seqsBis: + seq.append(reduce(operator.mul, [MEAS(q) for q in qubits])) + + # Tack on the calibration sequences + if add_cal: + seqsBis += create_cal_seqs((qubits), 2) + return seqsBis + + expectedseq = testSimultaneousRB_AC((q1, q2), (rbseqs, rbseqs), add_cals) + # Must reset the seeds because QGL1 used the prior values, to ensure QGL2 gets same values + np.random.seed(20151709) # set seed for create_RB_seqs() + resFunction = compile_function("src/python/qgl2/basic_sequences/RB.py", + "SimultaneousRB_AC", + (qr, (rbseqs, rbseqs), add_cals)) + seqs = resFunction() + seqs = testable_sequence(seqs) + # Run testable on the QGL1 to flatten the sequence of sequences + expectedseq = testable_sequence(expectedseq) + + # QGL2 generates Barrier, P(q1), P(q2), Barrier, .... + # where QGL1 does PulseBlock(P(q1) * P(q2)) + # - these are equivalent, but look different. + # I could go thru QGL2, when I find a Barrier, grab all next Pulses up to next Barrier & put them in a PulseBlock? + # Here though, I take any PulseBlock in QGL1 and just list the Pulses + expectedseq = flattenPulseBlocks(expectedseq) + + # Strip out the QGL2 Waits and Barriers that QGL1 doesn't have + # Note that if you want to see the real sequence, don't do this + seqs = stripWaitBarrier(seqs) + + # self.maxDiff = None + assertPulseSequenceEqual(self, seqs, expectedseq) + + # These RB functions are unlikely to be done: + # SingleQubitRB_DiAC (?) + # SingleQubitIRB_AC (needs a file of sequences that I don't have) + # Not this one that needs a specific file: SingleQubitRBT + +class TestRabi(unittest.TestCase): def setUp(self): channel_setup() - ## Rabi def test_RabiAmp(self): q1 = QubitFactory('q1') - qr = QRegister('q1') + qr = QRegister(q1) amps = np.linspace(0, 1, 11) phase = 0 @@ -438,23 +680,25 @@ def test_RabiAmp(self): MEAS(q1) ] - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doRabiAmp", + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "RabiAmp", (qr, amps, phase)) seqs = resFunction() seqs = testable_sequence(seqs) assertPulseSequenceEqual(self, seqs, expectedseq) - # Fails due to import of tanh, etc. See RabiMin.py + # Note that QGL2 gives a warning printing the tanh function; harmless def test_RabiWidth(self): - from qgl2.basic_sequences.pulses import local_tanh + from QGL.PulseShapes import tanh q1 = QubitFactory('q1') - qr = QRegister('q1') + qr = QRegister(q1) widths = np.linspace(0, 5e-6, 11) + amp=1 + phase=0 - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doRabiWidth", - (qr, widths)) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "RabiWidth", + (qr, widths, amp, phase, tanh)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -462,7 +706,7 @@ def test_RabiWidth(self): for l in widths: expectedseq += [ qwait(channels=(q1,)), - Utheta(q1, length=l, amp=1, phase=0, shapeFun=local_tanh), + Utheta(q1, length=l, amp=amp, phase=phase, shape_fun=tanh), MEAS(q1) ] @@ -471,12 +715,15 @@ def test_RabiWidth(self): def test_RabiAmpPi(self): q1 = QubitFactory('q1') q2 = QubitFactory('q2') - qr = QRegister('q1', 'q2') + qr1 = QRegister(q1) + qr2 = QRegister(q2) + amps = np.linspace(0, 1, 11) + phase=0 - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doRabiAmpPi", - (qr, amps)) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "RabiAmpPi", + (qr1, qr2, amps, phase)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -485,7 +732,7 @@ def test_RabiAmpPi(self): expectedseq += [ qwait(channels=(q1,q2)), X(q2), - Utheta(q1, amp=amp, phase=0), + Utheta(q1, amp=amp, phase=phase), X(q2), MEAS(q2) ] @@ -494,9 +741,9 @@ def test_RabiAmpPi(self): def test_SingleShot(self): q1 = QubitFactory('q1') - qr = QRegister('q1') - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doSingleShot", + qr = QRegister(q1) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "SingleShot", (qr,)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -514,9 +761,9 @@ def test_SingleShot(self): def test_PulsedSpec(self): q1 = QubitFactory('q1') - qr = QRegister('q1') - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doPulsedSpec", + qr = QRegister(q1) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "PulsedSpec", (qr, True)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -532,7 +779,7 @@ def test_PulsedSpec(self): def test_RabiAmp_NQubits(self): q1 = QubitFactory('q1') q2 = QubitFactory('q2') - qr = QRegister('q1', 'q2') + qr = QRegister(q1, q2) amps = np.linspace(0, 5e-6, 11) p = 0 docals = False @@ -544,6 +791,7 @@ def test_RabiAmp_NQubits(self): qwait(channels=(q1,q2)), Utheta(q1, amp=a, phase=p), Utheta(q2, amp=a, phase=p), + Barrier(q1, q2), MEAS(q1), MEAS(q2) ] @@ -555,18 +803,20 @@ def test_RabiAmp_NQubits(self): expectedseq = testable_sequence(expectedseq) - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doRabiAmp_NQubits", - (qr, amps, docals, calRepeats)) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "RabiAmp_NQubits", + (qr, amps, p, None, docals, calRepeats)) seqs = resFunction() seqs = testable_sequence(seqs) assertPulseSequenceEqual(self, seqs, expectedseq) + # Note this is not a QGL1 basic sequence any longer def test_Swap(self): q = QubitFactory('q1') mq = QubitFactory('q2') - qr = QRegister('q1', 'q2') + qr = QRegister(q) + mqr = QRegister(mq) delays = np.linspace(0, 5e-6, 11) expectedseq = [] for d in delays: @@ -586,9 +836,9 @@ def test_Swap(self): expectedseq = testable_sequence(expectedseq) - resFunction = compile_function("src/python/qgl2/basic_sequences/RabiMin.py", - "doSwap", - (qr, delays)) + resFunction = compile_function("src/python/qgl2/basic_sequences/Rabi.py", + "Swap", + (qr, delays, mqr)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -639,8 +889,8 @@ def spam_seqs(angle, q, maxSpamBlocks): X(q), MEAS(q) ] - resFunction = compile_function("src/python/qgl2/basic_sequences/SPAMMin.py", - "doSPAM", + resFunction = compile_function("src/python/qgl2/basic_sequences/SPAM.py", + "SPAM", (qr, angleSweep, maxSpamBlocks)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -670,8 +920,8 @@ def test_InversionRecovery(self): expectedseq = testable_sequence(expectedseq) - resFunction = compile_function("src/python/qgl2/basic_sequences/T1T2Min.py", - "doInversionRecovery", + resFunction = compile_function("src/python/qgl2/basic_sequences/T1T2.py", + "InversionRecovery", (qr, delays, calRepeats)) seqs = resFunction() seqs = testable_sequence(seqs) @@ -703,8 +953,8 @@ def test_Ramsey(self): expectedseq = testable_sequence(expectedseq) - resFunction = compile_function("src/python/qgl2/basic_sequences/T1T2Min.py", - "doRamsey", + resFunction = compile_function("src/python/qgl2/basic_sequences/T1T2.py", + "Ramsey", (qr, delays, TPPIFreq, calRepeats)) seqs = resFunction() seqs = testable_sequence(seqs) diff --git a/test/test_toplevel_binding.py b/test/test_toplevel_binding.py index 0966bbc..510c467 100644 --- a/test/test_toplevel_binding.py +++ b/test/test_toplevel_binding.py @@ -178,7 +178,7 @@ def test_main4(self): q1 = QubitFactory('q1') qr = QRegister('q1') amps = range(5) - expectedseq = [Xtheta(q1, amp=a, shapeFun=PulseShapes.tanh) for a in amps] + expectedseq = [Xtheta(q1, amp=a, shape_fun=PulseShapes.tanh) for a in amps] # tuple input for toplevel_bindings resFunction = compile_function(