├── .flake8 ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── black.yml │ ├── lint-and-test.yml │ └── pypi-upload.yml ├── .gitignore ├── .readthedocs.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── make.bat └── source │ ├── about │ ├── acknowledging.rst │ ├── authors.rst │ ├── changelog.rst │ ├── contributing.rst │ └── license.rst │ ├── api │ ├── classes.rst │ └── functions.rst │ ├── conf.py │ ├── images │ ├── sequencing-logo-bw.svg │ ├── sequencing-logo.pdf │ └── sequencing-logo.svg │ ├── index.rst │ ├── installation.rst │ ├── notebooks │ ├── 00-parameterized.ipynb │ ├── 01-modes.ipynb │ ├── 02-systems.ipynb │ ├── 03-pulse-sequences.ipynb │ ├── 04-sequences.ipynb │ ├── 05-qasm-sequence.ipynb │ ├── 06-transmon-cavity-control.ipynb │ ├── 07-snap-gate.ipynb │ └── introduction.ipynb │ └── tutorials │ └── tutorials.rst ├── pytest.ini ├── requirements.txt ├── sequencing ├── __init__.py ├── benchmarking.py ├── calibration.py ├── gates │ ├── __init__.py │ ├── onequbit.py │ └── twoqubit.py ├── modes.py ├── parameters.py ├── pulses.py ├── qasm.py ├── sequencing │ ├── __init__.py │ ├── basic.py │ ├── common.py │ └── main.py ├── system.py ├── test │ ├── __init__.py │ ├── test_benchmarking.py │ ├── test_calibration.py │ ├── test_gates.py │ ├── test_modes.py │ ├── test_parameters.py │ ├── test_pulses.py │ ├── test_qasm.py │ ├── test_rotations.py │ ├── test_sequencing.py │ └── test_system.py ├── testing.py └── version.py └── setup.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | select = C,E,F,W,B,B950 4 | extend-ignore = E203, E501, W503 5 | per-file-ignores = 6 | # Ignore unused imports in __init__ files. 7 | *sequencing/__init__.py:F401 8 | *sequencing/sequencing/__init__.py:F401 9 | *sequencing/gates/__init__.py:F401 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | * Sequencing version: 11 | * QuTiP version: 12 | * Python version: 13 | * Operating System: 14 | 15 | ### Description 16 | 17 | Describe what you were trying to get done. 18 | Tell us what happened, what went wrong, and what you expected to happen. 19 | 20 | ### What I Did 21 | 22 | ``` 23 | Paste the command(s) you ran and the output. 24 | If there was a crash, please include the full traceback here. 25 | ``` 26 | 27 | Please do not paste screenshots. A great way to report a bug is to create a Jupyter notebook, upload it to http://gist.github.com, and link it here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for `sequencing` 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered (if any). 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Black 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: psf/black@stable 11 | with: 12 | options: "--diff --color --check" 13 | src: "." 14 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: lint-and-test 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.7, 3.8, 3.9] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install pip 27 | run: | 28 | python -m pip install --upgrade pip 29 | - name: Get pip cache dir 30 | id: pip-cache 31 | run: | 32 | echo "::set-output name=dir::$(pip cache dir)" 33 | - name: pip cache 34 | uses: actions/cache@v2 35 | with: 36 | path: ${{ steps.pip-cache.outputs.dir }} 37 | key: ${{ runner.os }}${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }} 38 | restore-keys: | 39 | ${{ runner.os }}${{ matrix.python-version }}-pip- 40 | - name: Install dependencies 41 | run: | 42 | python -m pip install flake8 pytest 43 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 44 | python -m pip install -e . 45 | - name: Lint with flake8 46 | run: | 47 | # stop the build if there are Python syntax errors or undefined names 48 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 49 | # exit-zero treats all errors as warnings. 50 | flake8 . --count --exit-zero --select=C,E,F,W,B,B950 --extend-ignore=E203,E501,W503 --max-complexity=18 --max-line-length=88 --statistics 51 | - name: Test with pytest 52 | run: | 53 | pytest --cov=./ --cov-report=xml 54 | - name: Upload coverage to Codecov 55 | uses: codecov/codecov-action@v2 56 | with: 57 | files: coverage.xml 58 | verbose: true 59 | -------------------------------------------------------------------------------- /.github/workflows/pypi-upload.yml: -------------------------------------------------------------------------------- 1 | name: Upload to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | # Allows you to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | jobs: 10 | upload: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-python@v2 16 | with: 17 | python-version: 3.9 18 | - name: "Installs dependencies" 19 | run: | 20 | python -m pip install --upgrade pip 21 | python -m pip install setuptools wheel twine 22 | - name: "Builds and uploads to PyPI" 23 | run: | 24 | python setup.py sdist bdist_wheel 25 | python -m twine upload dist/* 26 | env: 27 | TWINE_USERNAME: __token__ 28 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/* 2 | *.ipynb-checkpoints* 3 | __pycache__ 4 | *.vscode 5 | *-checkpoint.ipynb 6 | build 7 | _build 8 | .coverage 9 | .coverage* 10 | dist -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/source/conf.py 12 | 13 | # Optionally build your docs in additional formats such as PDF 14 | # formats: 15 | # - pdf 16 | 17 | # Optionally set the version of Python and requirements required to build your docs 18 | python: 19 | version: 3.8 20 | install: 21 | - requirements: requirements.txt 22 | - method: pip 23 | path: . 24 | extra_requirements: 25 | - docs -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! 4 | 5 | ## Code of Conduct 6 | 7 | Everyone interacting in the `sequencing` project's code base, 8 | issue tracker, and any communication channels is expected to follow the 9 | [PyPA Code of Conduct](https://www.pypa.io/en/latest/code-of-conduct/). 10 | 11 | 12 | ## Reporting Bugs 13 | 14 | Report bugs [by creating an issue](https://docs.github.com/en/github/managing-your-work-on-github/creating-an-issue). 15 | 16 | When reporting a bug, please include: 17 | 18 | * Your operating system name and version. 19 | * Any details about your local setup that might be helpful in troubleshooting. 20 | * Detailed steps to reproduce the bug. 21 | 22 | 23 | ## Submitting Feedback 24 | 25 | The best way to send feedback is to [create an issue](https://docs.github.com/en/github/managing-your-work-on-github/creating-an-issue). 26 | 27 | If you are proposing a feature: 28 | 29 | * Explain in detail how it would work and why it is important. 30 | * Keep the scope as narrow as possible, to make it easier to implement. 31 | * Remember that this is a volunteer-driven project, and that contributions 32 | are welcome. 33 | 34 | 35 | ## Pull Request Guidelines 36 | 37 | Pull requests are the best way to propose changes to the `sequencing` codebase, and we actively welcome them. 38 | All pull requests should abide by the following guidelines: 39 | 40 | 1. All code must be compatible with [Black code style](https://black.readthedocs.io/en/stable/) 41 | (a subset of [PEP 8](https://www.python.org/dev/peps/pep-0008/)). 42 | We recommend using [Black](https://black.readthedocs.io/en/stable/) and [flake8](https://flake8.pycqa.org/en/latest/) locally to ensure compatibility. (See [.flake8](.flake8) for the recommended flake8 configuration.) 43 | 2. If you've added code that should be tested, add tests. 44 | 3. If you've changed APIs (e.g. function signatures), update the documentation accordingly. 45 | - If your pull request adds new features, please consider creating an example Jupyter notebook demonstrating the features. 46 | 4. Ensure that all tests pass locally before creating the PR. 47 | 48 | 49 | ## Getting Started 50 | 51 | Ready to contribute? Follow [GitHub best practices](https://www.asmeurer.com/git-workflow/) - in short: 52 | 53 | 1. Clone the repository. 54 | 2. Fork the repo on GitHub to your personal account. 55 | 3. Add your fork as a remote. 56 | 4. Pull the latest changes from the `main` branch. 57 | 5. Create a topic branch, e.g. `feature/short-description` for a feature or enhancement. 58 | 6. Make your changes and commit them (testing locally). 59 | 7. Push changes to the topic branch on *your* remote once local tests pass and you are happy with the changes you've made. 60 | 8. Create a pull request against the base `main` branch through the Github website. 61 | 62 | ## Making the Docs 63 | 64 | Make an HTML version of the documentation. 65 | 66 | > **Note**: This might require installing [Pandoc](https://pandoc.org/installing.html). 67 | 68 | ``` 69 | pip install sphinx sphinx_rtd_theme nbsphinx 70 | cd docs 71 | make html 72 | ``` 73 | 74 | ## Unit Tests 75 | 76 | Run all of the unit tests from a Python session using 77 | 78 | ``` python 79 | >>> import sequencing.testing as st 80 | >>> st.run() 81 | ``` 82 | 83 | Or run them from the command line, using 84 | 85 | ``` 86 | pytest --cov 87 | ``` 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, The Sequencing Authors. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seQuencing 2 | 3 | ![seQuencing logo](docs/source/images/sequencing-logo.svg) 4 | 5 | ``sequencing`` is a Python package for simulating realistic quantum control sequences using [QuTiP](http://qutip.org/docs/latest/index.html). Built for researchers and quantum engineers, ``sequencing`` provides an intuitive framework for constructing models of quantum systems 6 | composed of many modes and generating complex time-dependent control Hamiltonians 7 | for [master equation simulations](http://qutip.org/docs/latest/guide/dynamics/dynamics-master.html). 8 | 9 | ![PyPI](https://img.shields.io/pypi/v/sequencing) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/sequencing-dev/sequencing/lint-and-test/main) [![Documentation Status](https://readthedocs.org/projects/sequencing/badge/?version=latest)](https://sequencing.readthedocs.io/en/latest/?badge=latest) [![codecov](https://codecov.io/gh/sequencing-dev/sequencing/branch/main/graph/badge.svg?token=LLABAKBJ0C)](https://codecov.io/gh/sequencing-dev/sequencing) ![GitHub](https://img.shields.io/github/license/sequencing-dev/sequencing) [![DOI](https://zenodo.org/badge/334427937.svg)](https://zenodo.org/badge/latestdoi/334427937) 10 | 11 | ## Documentation 12 | 13 | The documentation for `sequencing` is available at: 14 | [sequencing.readthedocs.io](https://sequencing.readthedocs.io/) 15 | 16 | ## Installation 17 | 18 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sequencing) 19 | 20 | ``` 21 | conda create -n python=<3.7, 3.8, or 3.9> 22 | conda activate 23 | pip install sequencing 24 | ``` 25 | 26 | `sequencing` requires `python>=3.7` and is tested on `3.7`, `3.8`, and `3.9`. For more details, see the [documentation](https://sequencing.readthedocs.io/en/latest/installation.html). 27 | 28 | ## Tutorials 29 | 30 | The documentation includes a set of [tutorial notebooks](https://sequencing.readthedocs.io/en/latest/tutorials/tutorials.html). Click the badge below to run the notebooks interactively online using [binder](https://mybinder.org/): 31 | 32 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/sequencing-dev/sequencing/main?filepath=docs%2Fsource%2Fnotebooks) 33 | 34 | ## Authors 35 | 36 | Primary author and maintainer: [@loganbvh](https://github.com/loganbvh/). 37 | 38 | ## Contributing 39 | 40 | Want to contribute to `sequencing`? Check out our [contribution guidelines](CONTRIBUTING.md). 41 | 42 | ## Acknowledging 43 | 44 | If you used `sequencing` for work that was published in a journal article, preprint, blog post, etc., please cite/acknowledge the `sequencing` project using its DOI: 45 | 46 | [![DOI](https://zenodo.org/badge/334427937.svg)](https://zenodo.org/badge/latestdoi/334427937) 47 | 48 | **Uploading Examples** 49 | 50 | So that others may learn from and reproduce the results of your work, please consider uploading a demonstration of the simulations performed for your publication in the form of well-documented Jupyter notebooks or Python files to the [sequencing-examples](https://github.com/sequencing-dev/sequencing-examples) repository. 51 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/about/acknowledging.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: ../images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ************* 7 | Acknowledging 8 | ************* 9 | 10 | If you used ``sequencing`` for work that was published in a journal article, preprint, blog post, etc., please cite/acknowledge the ``sequencing`` project using its DOI: 11 | 12 | .. image:: https://zenodo.org/badge/334427937.svg 13 | :target: https://zenodo.org/badge/latestdoi/334427937 14 | 15 | Click the badge above to view ``sequencing`` on the open-access research repository, `Zenodo `_, where you can automatically generate a citation. 16 | 17 | Uploading Examples 18 | ------------------ 19 | 20 | So that others may learn from and reproduce the results of your work, please consider uploading a demonstration of the simulations performed for your publication in the form of well-documented Jupyter notebooks or Python files to the `sequencing-examples `_ repository. 21 | 22 | For an example of such a notebook reproducing published results, see the `SNAP gate tutorial <../notebooks/07-snap-gate.ipynb>`_. 23 | -------------------------------------------------------------------------------- /docs/source/about/authors.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: ../images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ******* 7 | Authors 8 | ******* 9 | 10 | Primary author and maintainer: `@loganbvh `_. 11 | 12 | Contributors 13 | ------------ 14 | 15 | - Your name here! :ref:`Contribute to SeQuencing `. 16 | -------------------------------------------------------------------------------- /docs/source/about/changelog.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: ../images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ********** 7 | Change Log 8 | ********** 9 | 10 | View release history on `PyPI `_ 11 | or `GitHub `_. 12 | 13 | Version 1.2.0 14 | ------------- 15 | 16 | Release date: 2022-08-30. 17 | 18 | **New Features** 19 | - Add support for time step ``dt != 1 ns`` (`#21 `_). 20 | 21 | 22 | Version 1.1.4 23 | ------------- 24 | 25 | Release date: 2021-06-22. 26 | 27 | **Bug fixes** 28 | - Fix a bug that prevented sequences with dynamic collapse operators (``CTerms``) from compiling. 29 | - Add a unit test for the above feature. 30 | 31 | Version 1.1.3 32 | ------------- 33 | 34 | Release date: 2021-02-05. 35 | 36 | **Bug fixes** 37 | - Suppress plots and DeprecationWarnings in ``sequencing.testing``. 38 | 39 | Version 1.1.2 40 | ------------- 41 | 42 | Release date: 2021-02-04. 43 | 44 | **Bug fixes** 45 | - Use ``tempfile`` for tests that require file IO, removing the possibility of tests failing due to file permission errors. 46 | 47 | Version 1.1.1 (initial release) 48 | ------------------------------- 49 | 50 | Release date: 2021-02-03. 51 | 52 | -------------------------------------------------------------------------------- /docs/source/about/contributing.rst: -------------------------------------------------------------------------------- 1 | .. figure:: ../images/sequencing-logo.* 2 | :alt: seQuencing logo 3 | 4 | .. _contributing: 5 | 6 | ************ 7 | Contributing 8 | ************ 9 | 10 | Contributions are welcome, and they are greatly appreciated! 11 | 12 | Code of Conduct 13 | =============== 14 | 15 | Everyone interacting in the ``sequencing`` project's code base, 16 | issue tracker, and any communication channels is expected to follow the 17 | `PyPA Code of Conduct `_. 18 | 19 | 20 | Reporting Bugs 21 | ============== 22 | 23 | Report bugs `by creating an issue `_. 24 | 25 | When reporting a bug, please include: 26 | 27 | * Your operating system name and version. 28 | * Any details about your local setup that might be helpful in troubleshooting. 29 | * Detailed steps to reproduce the bug. 30 | 31 | 32 | Submitting Feedback 33 | =================== 34 | 35 | The best way to send feedback is to `create an issue `_. 36 | 37 | If you are proposing a feature: 38 | 39 | * Explain in detail how it would work and why it is important. 40 | * Keep the scope as narrow as possible, to make it easier to implement. 41 | * Remember that this is a volunteer-driven project, and that contributions 42 | are welcome. 43 | 44 | 45 | Pull Request Guidelines 46 | ======================= 47 | 48 | Pull requests are the best way to propose changes to the ``sequencing`` codebase, and we actively welcome them. 49 | All pull requests should abide by the following guidelines: 50 | 51 | 1. All code must be compatible with `Black code style `_ (a subset of `PEP 8 `_). We recommend using `Black `_ and `flake8 `_ locally to ensure compatibility. 52 | 53 | 2. If you've added code that should be tested, add tests. 54 | 55 | 3. If you've changed APIs (e.g. function signatures), update the documentation accordingly. If your pull request adds new features, please consider creating an example Jupyter notebook demonstrating the features. 56 | 57 | 4. Ensure that all tests pass locally before creating the PR. 58 | 59 | 60 | Getting Started 61 | =============== 62 | 63 | Ready to contribute? Follow `GitHub best practices `_ - in short: 64 | 65 | 1. Clone the repository. 66 | 2. Fork the repo on GitHub to your personal account. 67 | 3. Add your fork as a remote. 68 | 4. Pull the latest changes from the `main` branch. 69 | 5. Create a topic branch, e.g. `feature/short-description` for a feature or enhancement. 70 | 6. Make your changes and commit them (testing locally). 71 | 7. Push changes to the topic branch on *your* remote once local tests pass and you are happy with the changes you've made. 72 | 8. Create a pull request against the base ``main`` branch through the Github website. 73 | -------------------------------------------------------------------------------- /docs/source/about/license.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: ../images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ******* 7 | License 8 | ******* 9 | 10 | .. code-block:: text 11 | 12 | BSD 3-Clause License 13 | 14 | Copyright (c) 2021, The Sequencing Authors. 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | 1. Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | 2. Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | 3. Neither the name of the copyright holder nor the names of its 28 | contributors may be used to endorse or promote products derived from 29 | this software without specific prior written permission. 30 | 31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 32 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 33 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 34 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 35 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 36 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 37 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 39 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41 | -------------------------------------------------------------------------------- /docs/source/api/classes.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. _api-classes: 4 | 5 | .. figure:: ../images/sequencing-logo.* 6 | :alt: seQuencing logo 7 | 8 | ******* 9 | Classes 10 | ******* 11 | 12 | .. _api-classes-parameters: 13 | 14 | Parameters 15 | ---------- 16 | 17 | Parameterized 18 | ************* 19 | .. autoclass:: sequencing.parameters.Parameterized 20 | :members: 21 | 22 | ------------------------------------------------- 23 | 24 | .. _api-classes-pulses: 25 | 26 | Pulses 27 | ------ 28 | 29 | Pulse 30 | ***** 31 | .. autoclass:: sequencing.pulses.Pulse 32 | :members: 33 | :show-inheritance: 34 | 35 | ConstantPulse 36 | ************* 37 | .. autoclass:: sequencing.pulses.ConstantPulse 38 | :members: 39 | :show-inheritance: 40 | 41 | SmoothedConstantPulse 42 | ********************* 43 | .. autoclass:: sequencing.pulses.SmoothedConstantPulse 44 | :members: 45 | :show-inheritance: 46 | 47 | GaussianPulse 48 | ************* 49 | .. autoclass:: sequencing.pulses.GaussianPulse 50 | :members: 51 | :show-inheritance: 52 | 53 | SechPulse 54 | ********* 55 | .. autoclass:: sequencing.pulses.SechPulse 56 | :members: 57 | :show-inheritance: 58 | 59 | SlepianPulse 60 | ************ 61 | .. autoclass:: sequencing.pulses.SlepianPulse 62 | :members: 63 | :show-inheritance: 64 | 65 | ------------------------------------------------- 66 | 67 | .. _api-classes-modes: 68 | 69 | Modes 70 | ----- 71 | 72 | Mode 73 | **** 74 | .. autoclass:: sequencing.modes.Mode 75 | :members: 76 | :show-inheritance: 77 | 78 | PulseMode 79 | ********* 80 | .. autoclass:: sequencing.modes.PulseMode 81 | :members: 82 | :show-inheritance: 83 | 84 | Qubit 85 | ***** 86 | .. autoclass:: sequencing.modes.Qubit 87 | :members: 88 | :show-inheritance: 89 | 90 | Transmon 91 | ******** 92 | .. autoclass:: sequencing.modes.Transmon 93 | :members: 94 | :show-inheritance: 95 | 96 | Cavity 97 | ****** 98 | .. autoclass:: sequencing.modes.Cavity 99 | :members: 100 | :show-inheritance: 101 | 102 | ------------------------------------------------- 103 | 104 | .. _api-classes-system: 105 | 106 | System 107 | ------ 108 | 109 | CouplingTerm 110 | ************ 111 | .. autoclass:: sequencing.system.CouplingTerm 112 | :members: 113 | 114 | System 115 | ****** 116 | .. autoclass:: sequencing.system.System 117 | :members: 118 | :show-inheritance: 119 | 120 | ------------------------------------------------- 121 | 122 | .. _api-classes-sequencing: 123 | 124 | Sequencing 125 | ---------- 126 | 127 | HamiltonianChannels 128 | ******************* 129 | .. autoclass:: sequencing.sequencing.HamiltonianChannels 130 | :members: 131 | 132 | CompiledPulseSequence 133 | ********************* 134 | .. autoclass:: sequencing.sequencing.CompiledPulseSequence 135 | :members: 136 | 137 | PulseSequence 138 | ********************* 139 | .. autoclass:: sequencing.sequencing.PulseSequence 140 | :members: 141 | :show-inheritance: 142 | :inherited-members: 143 | 144 | Sequence 145 | ******** 146 | .. autoclass:: sequencing.sequencing.main.Sequence 147 | :members: 148 | :show-inheritance: 149 | :inherited-members: 150 | 151 | SequenceResult 152 | ************** 153 | .. autoclass:: sequencing.sequencing.SequenceResult 154 | :members: 155 | 156 | HTerm 157 | ***** 158 | .. autoclass:: sequencing.sequencing.HTerm 159 | :members: 160 | 161 | CTerm 162 | ***** 163 | .. autoclass:: sequencing.sequencing.CTerm 164 | :members: 165 | 166 | Operation 167 | ********* 168 | .. autoclass:: sequencing.sequencing.Operation 169 | :members: 170 | 171 | SyncOperation 172 | ************* 173 | .. autoclass:: sequencing.sequencing.SyncOperation 174 | :members: 175 | 176 | .. seealso:: 177 | :func:`sequencing.sequencing.sync` 178 | 179 | DelayOperation 180 | ************** 181 | .. autoclass:: sequencing.sequencing.DelayOperation 182 | :members: 183 | 184 | .. seealso:: 185 | :func:`sequencing.sequencing.delay` 186 | 187 | DelayChannelsOperation 188 | ********************** 189 | .. autoclass:: sequencing.sequencing.DelayChannelsOperation 190 | :members: 191 | 192 | .. seealso:: 193 | :func:`sequencing.sequencing.delay_channels` 194 | 195 | ValidatedList 196 | ************* 197 | .. autoclass:: sequencing.sequencing.common.ValidatedList 198 | :members: 199 | 200 | ------------------------------------------------- 201 | 202 | .. _api-classes-gates: 203 | 204 | Gates 205 | ----- 206 | 207 | TwoQubitGate 208 | ************ 209 | .. autoclass:: sequencing.gates.twoqubit.TwoQubitGate 210 | :members: 211 | 212 | ControlledTwoQubitGate 213 | ********************** 214 | .. autoclass:: sequencing.gates.twoqubit.ControlledTwoQubitGate 215 | :members: 216 | :show-inheritance: 217 | 218 | CUGate 219 | ****** 220 | .. autoclass:: sequencing.gates.twoqubit.CUGate 221 | :show-inheritance: 222 | 223 | .. seealso:: 224 | :func:`sequencing.gates.cu` 225 | 226 | CXGate 227 | ****** 228 | .. autoclass:: sequencing.gates.CXGate 229 | :show-inheritance: 230 | 231 | .. seealso:: 232 | :func:`sequencing.gates.cx` 233 | 234 | CYGate 235 | ****** 236 | .. autoclass:: sequencing.gates.CYGate 237 | :members: 238 | :show-inheritance: 239 | 240 | .. seealso:: 241 | :func:`sequencing.gates.cy` 242 | 243 | CZGate 244 | ****** 245 | .. autoclass:: sequencing.gates.CZGate 246 | :members: 247 | :show-inheritance: 248 | 249 | .. seealso:: 250 | :func:`sequencing.gates.cz` 251 | 252 | CPhaseGate 253 | ********** 254 | .. autoclass:: sequencing.gates.CPhaseGate 255 | :members: 256 | :show-inheritance: 257 | 258 | .. seealso:: 259 | :func:`sequencing.gates.cphase` 260 | 261 | SWAPGate 262 | ******** 263 | .. autoclass:: sequencing.gates.SWAPGate 264 | :members: 265 | :show-inheritance: 266 | 267 | .. seealso:: 268 | :func:`sequencing.gates.swap` 269 | 270 | SWAPphiGate 271 | *********** 272 | .. autoclass:: sequencing.gates.SWAPphiGate 273 | :members: 274 | 275 | .. seealso:: 276 | :func:`sequencing.gates.swapphi` 277 | 278 | iSWAPGate 279 | ********* 280 | .. autoclass:: sequencing.gates.iSWAPGate 281 | :members: 282 | :show-inheritance: 283 | 284 | .. seealso:: 285 | :func:`sequencing.gates.iswap` 286 | 287 | eSWAPGate 288 | ********* 289 | .. autoclass:: sequencing.gates.eSWAPGate 290 | :members: 291 | :show-inheritance: 292 | 293 | .. seealso:: 294 | :func:`sequencing.gates.eswap` 295 | 296 | SqrtSWAPGate 297 | ************ 298 | .. autoclass:: sequencing.gates.SqrtSWAPGate 299 | :members: 300 | :show-inheritance: 301 | 302 | .. seealso:: 303 | :func:`sequencing.gates.sqrtswap` 304 | 305 | SqrtiSWAPGate 306 | ************* 307 | .. autoclass:: sequencing.gates.SqrtiSWAPGate 308 | :members: 309 | :show-inheritance: 310 | 311 | .. seealso:: 312 | :func:`sequencing.gates.sqrtiswap` 313 | 314 | ------------------------------------------------- 315 | 316 | .. _api-classes-qasm: 317 | 318 | QASM 319 | ---- 320 | 321 | QasmSequence 322 | ************ 323 | .. autoclass:: sequencing.qasm.QasmSequence 324 | :members: 325 | :show-inheritance: 326 | :inherited-members: 327 | 328 | ------------------------------------------------- 329 | 330 | .. _api-classes-benchmarking: 331 | 332 | Benchmarking 333 | ------------ 334 | 335 | Benchmark 336 | ********* 337 | .. autoclass:: sequencing.benchmarking.Benchmark 338 | :members: 339 | -------------------------------------------------------------------------------- /docs/source/api/functions.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. _api-functions: 4 | 5 | .. figure:: ../images/sequencing-logo.* 6 | :alt: seQuencing logo 7 | 8 | ********* 9 | Functions 10 | ********* 11 | 12 | .. _api-functions-parameters: 13 | 14 | Parameters 15 | ---------- 16 | 17 | StringParameter 18 | *************** 19 | .. autofunction:: sequencing.parameters.StringParameter 20 | 21 | BoolParameter 22 | ************* 23 | .. autofunction:: sequencing.parameters.BoolParameter 24 | 25 | IntParameter 26 | ************ 27 | .. autofunction:: sequencing.parameters.IntParameter 28 | 29 | FloatParameter 30 | ************** 31 | .. autofunction:: sequencing.parameters.FloatParameter 32 | 33 | NanosecondParameter 34 | ******************* 35 | .. autofunction:: sequencing.parameters.NanosecondParameter 36 | 37 | GigahertzParameter 38 | ****************** 39 | .. autofunction:: sequencing.parameters.GigahertzParameter 40 | 41 | RadianParameter 42 | *************** 43 | .. autofunction:: sequencing.parameters.RadianParameter 44 | 45 | DictParameter 46 | ************* 47 | .. autofunction:: sequencing.parameters.DictParameter 48 | 49 | ListParameter 50 | ************* 51 | .. autofunction:: sequencing.parameters.ListParameter 52 | 53 | ------------------------------------------------- 54 | 55 | .. _api-functions-modes: 56 | 57 | Modes 58 | ----- 59 | 60 | sort_modes 61 | ********** 62 | .. autofunction:: sequencing.modes.sort_modes 63 | 64 | ------------------------------------------------- 65 | 66 | .. _api-functions-pulses: 67 | 68 | Pulses 69 | ------ 70 | 71 | array_pulse 72 | *********** 73 | .. autofunction:: sequencing.pulses.array_pulse 74 | 75 | pulse_factory 76 | ************* 77 | .. autofunction:: sequencing.pulses.pulse_factory 78 | 79 | ------------------------------------------------- 80 | 81 | .. _api-functions-sequencing: 82 | 83 | Sequencing 84 | ---------- 85 | 86 | ket2dm 87 | ****** 88 | .. autofunction:: sequencing.sequencing.ket2dm 89 | 90 | ops2dms 91 | ******* 92 | .. autofunction:: sequencing.sequencing.ops2dms 93 | 94 | get_sequence 95 | ************ 96 | .. autofunction:: sequencing.sequencing.get_sequence 97 | 98 | sync 99 | **** 100 | .. autofunction:: sequencing.sequencing.sync 101 | 102 | .. seealso:: 103 | :class:`sequencing.sequencing.SyncOperation` 104 | 105 | delay 106 | ***** 107 | .. autofunction:: sequencing.sequencing.delay 108 | 109 | .. seealso:: 110 | :class:`sequencing.sequencing.DelayOperation` 111 | 112 | delay_channels 113 | ************** 114 | .. autofunction:: sequencing.sequencing.delay_channels 115 | 116 | .. seealso:: 117 | :class:`sequencing.sequencing.DelayChannelsOperation` 118 | 119 | @capture_operation 120 | ****************** 121 | .. autodecorator:: sequencing.sequencing.capture_operation 122 | 123 | ------------------------------------------------- 124 | 125 | .. _api-functions-gates: 126 | 127 | Gates 128 | ----- 129 | 130 | .. autodecorator:: sequencing.gates.single_qubit_gate 131 | 132 | .. autodecorator:: sequencing.gates.pulsed_gate_exists 133 | 134 | Single-qubit gates 135 | ****************** 136 | 137 | .. autofunction:: sequencing.gates.rx 138 | 139 | .. autofunction:: sequencing.gates.ry 140 | 141 | .. autofunction:: sequencing.gates.rz 142 | 143 | .. autofunction:: sequencing.gates.x 144 | 145 | .. autofunction:: sequencing.gates.y 146 | 147 | .. autofunction:: sequencing.gates.z 148 | 149 | .. autofunction:: sequencing.gates.h 150 | 151 | .. autofunction:: sequencing.gates.r 152 | 153 | Two-qubit gates 154 | *************** 155 | 156 | .. autofunction:: sequencing.gates.cu 157 | 158 | .. seealso:: 159 | :func:`sequencing.gates.twoqubit.CUGate` 160 | 161 | .. autofunction:: sequencing.gates.cx 162 | 163 | .. seealso:: 164 | :func:`sequencing.gates.CXGate` 165 | 166 | .. autofunction:: sequencing.gates.cy 167 | 168 | .. seealso:: 169 | :func:`sequencing.gates.CYGate` 170 | 171 | .. autofunction:: sequencing.gates.cz 172 | 173 | .. seealso:: 174 | :func:`sequencing.gates.CZGate` 175 | 176 | .. autofunction:: sequencing.gates.cphase 177 | 178 | .. seealso:: 179 | :func:`sequencing.gates.CPhaseGate` 180 | 181 | .. autofunction:: sequencing.gates.swap 182 | 183 | .. seealso:: 184 | :func:`sequencing.gates.SWAPGate` 185 | 186 | .. autofunction:: sequencing.gates.swapphi 187 | 188 | .. seealso:: 189 | :func:`sequencing.gates.SWAPphiGate` 190 | 191 | .. autofunction:: sequencing.gates.iswap 192 | 193 | .. seealso:: 194 | :func:`sequencing.gates.iSWAPGate` 195 | 196 | .. autofunction:: sequencing.gates.eswap 197 | 198 | .. seealso:: 199 | :func:`sequencing.gates.eSWAPGate` 200 | 201 | .. autofunction:: sequencing.gates.sqrtswap 202 | 203 | .. seealso:: 204 | :func:`sequencing.gates.SqrtSWAPGate` 205 | 206 | .. autofunction:: sequencing.gates.sqrtiswap 207 | 208 | .. seealso:: 209 | :func:`sequencing.gates.SqrtiSWAPGate` 210 | 211 | 212 | ------------------------------------------------- 213 | 214 | .. _api-functions-qasm: 215 | 216 | QASM 217 | ---- 218 | 219 | parse_qasm_gate 220 | *************** 221 | .. autofunction:: sequencing.qasm.parse_qasm_gate 222 | 223 | ------------------------------------------------- 224 | 225 | .. _api-functions-calibration: 226 | 227 | Calibration 228 | ----------- 229 | 230 | tune_rabi 231 | ********* 232 | .. autofunction:: sequencing.calibration.tune_rabi 233 | 234 | tune_drag 235 | ********* 236 | .. autofunction:: sequencing.calibration.tune_drag 237 | 238 | tune_displacement 239 | ***************** 240 | .. autofunction:: sequencing.calibration.tune_displacement 241 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = "seQuencing" 20 | copyright = "2021, The Sequencing Authors" 21 | author = "loganbvh" 22 | 23 | 24 | # -- General configuration --------------------------------------------------- 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 28 | # ones. 29 | extensions = [ 30 | "sphinx.ext.intersphinx", 31 | "sphinx.ext.autodoc", 32 | "sphinx.ext.napoleon", 33 | "sphinx.ext.mathjax", 34 | "sphinx.ext.todo", 35 | "sphinx.ext.viewcode", 36 | "sphinx_rtd_theme", 37 | "nbsphinx", 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ["_templates"] 42 | 43 | # List of patterns, relative to source directory, that match files and 44 | # directories to ignore when looking for source files. 45 | # This pattern also affects html_static_path and html_extra_path. 46 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 47 | 48 | 49 | # -- Options for HTML output ------------------------------------------------- 50 | 51 | # The theme to use for HTML and HTML Help pages. See the documentation for 52 | # a list of builtin themes. 53 | # 54 | html_theme = "sphinx_rtd_theme" 55 | 56 | # Add any paths that contain custom static files (such as style sheets) here, 57 | # relative to this directory. They are copied after the builtin static files, 58 | # so a file named "default.css" will overwrite the builtin "default.css". 59 | html_static_path = ["_static"] 60 | html_logo = "images/sequencing-logo-bw.svg" 61 | html_theme_options = { 62 | "logo_only": True, 63 | "display_version": False, 64 | } 65 | 66 | latex_engine = "xelatex" 67 | 68 | 69 | # -- Extension options ------------------------------------------------------- 70 | 71 | autodoc_member_order = "bysource" 72 | 73 | nbsphinx_execute = "always" 74 | 75 | nbsphinx_execute_arguments = [ 76 | "--InlineBackend.figure_formats={'svg', 'pdf'}", 77 | "--InlineBackend.rc figure.dpi=96", 78 | ] 79 | 80 | intersphinx_mapping = { 81 | "python": ("https://docs.python.org/3", None), 82 | } 83 | -------------------------------------------------------------------------------- /docs/source/images/sequencing-logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sequencing-dev/sequencing/423f72c58dd738ca4ce52a594aa6dfa6faa836a8/docs/source/images/sequencing-logo.pdf -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. sequencing documentation master file, created by 2 | sphinx-quickstart on Tue Dec 29 12:45:00 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. figure:: images/sequencing-logo.* 7 | :alt: seQuencing logo 8 | 9 | ********** 10 | seQuencing 11 | ********** 12 | 13 | `SeQuencing `_ is an open-source Python package 14 | for simulating realistic quantum control sequences using 15 | `QuTiP `_, the Quantum Toolbox in Python. 16 | 17 | Built for researchers and quantum engineers, ``sequencing`` provides an intuitive framework 18 | for constructing models of quantum systems composed of many modes and generating complex time-dependent control Hamiltonians 19 | for `simulations of quantum dynamics `_. 20 | 21 | 22 | .. image:: https://img.shields.io/pypi/v/sequencing 23 | :alt: PyPI 24 | 25 | .. image:: https://img.shields.io/github/workflow/status/sequencing-dev/sequencing/lint-and-test/main 26 | :alt: GitHub Workflow Status (branch) 27 | 28 | .. image:: https://readthedocs.org/projects/sequencing/badge/?version=latest 29 | :target: https://sequencing.readthedocs.io/en/latest/?badge=latest 30 | :alt: Documentation Status 31 | 32 | .. image:: https://codecov.io/gh/sequencing-dev/sequencing/branch/main/graph/badge.svg?token=LLABAKBJ0C 33 | :target: https://codecov.io/gh/sequencing-dev/sequencing 34 | 35 | .. image:: https://img.shields.io/github/license/sequencing-dev/sequencing 36 | :alt: GitHub 37 | 38 | .. image:: https://zenodo.org/badge/334427937.svg 39 | :target: https://zenodo.org/badge/latestdoi/334427937 40 | 41 | ----------------------------------------------------------- 42 | 43 | .. toctree:: 44 | :maxdepth: 2 45 | :caption: Getting Started 46 | 47 | installation.rst 48 | notebooks/introduction.ipynb 49 | 50 | .. toctree:: 51 | :maxdepth: 2 52 | :caption: Learn seQuencing 53 | 54 | tutorials/tutorials.rst 55 | 56 | .. toctree:: 57 | :maxdepth: 2 58 | :caption: About seQuencing 59 | 60 | about/license.rst 61 | about/authors.rst 62 | about/contributing.rst 63 | about/acknowledging.rst 64 | about/changelog.rst 65 | 66 | .. toctree:: 67 | :maxdepth: 2 68 | :caption: API Reference 69 | 70 | api/classes.rst 71 | api/functions.rst 72 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ************ 7 | Installation 8 | ************ 9 | 10 | We recommend creating a new 11 | `conda environment `_ 12 | for ``sequencing``. ``sequencing`` is compatible with, and tested on, Python 3.7, 3.8, and 3.9. 13 | 14 | .. code-block:: bash 15 | 16 | conda create -n python=<3.7, 3.8, or 3.9> 17 | conda activate 18 | 19 | .. important:: 20 | 21 | **Note for Windows users:** 22 | `QuTip: Installation on MS Windows `_: 23 | The only supported installation configuration is using the Conda environment with Python 3.5+ and Visual Studio 2015. 24 | 25 | Installing with pip 26 | =================== 27 | 28 | .. code-block:: bash 29 | 30 | pip install sequencing 31 | 32 | 33 | Installing from source 34 | ====================== 35 | 36 | Alternatively, you can install ``sequencing`` from 37 | `GitHub `_ using the following commands: 38 | 39 | .. code-block:: bash 40 | 41 | git clone https://github.com/sequencing-dev/sequencing.git 42 | pip install -e . 43 | 44 | Verifying the installation 45 | ========================== 46 | 47 | To verify your installation by running the ``sequencing`` test suite, 48 | execute the following commands in a Python session: 49 | 50 | .. code-block:: python 51 | 52 | >>> import sequencing.testing as st 53 | >>> st.run() 54 | 55 | If you prefer, you can also run the ``sequencing`` tests in a single line: 56 | 57 | .. code-block:: bash 58 | 59 | python -m sequencing.testing -------------------------------------------------------------------------------- /docs/source/notebooks/00-parameterized.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "![seQuencing logo](../images/sequencing-logo.svg)\n", 8 | "\n", 9 | "# Parameters" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Short introduction to `attrs`\n", 17 | "\n", 18 | "`sequencing` relies on the [attrs package](https://www.attrs.org/en/stable/index.html) for defining and instantiating classes with many parameters. `attrs` allows you to define classes with minimal boilerplate by automating the creation of important methods like `__init__`, `__repr__`, `__str__`, and comparison methods like `__eq__`. For full details on `attrs`, check out the [documentation](https://www.attrs.org/en/stable/overview.html).\n", 19 | "\n", 20 | "A class defined using `attrs` must be decorated with `@attr.s` (or `attr.attrs`). In place of an explicit `__init__` method, attributes of a class decorated with `@attr.s` can have instance attributes defined at the class level using `attr.ib()` (or `attr.attrib()`). Adding attributes to a class using `attr.ib()` has many advantages:\n", 21 | "\n", 22 | "- Attributes may be required or have a default value\n", 23 | "- Defaults can be defined either with a specific value or with a \"factory\"\n", 24 | " function that generates a default value when called\n", 25 | "- Attributes can have a `type` associated with them, or a `converter`\n", 26 | " function that converts the user-specified value into the desired format\n", 27 | "- Attributes can have `validators` which raise an error if an invalid value is provided\n", 28 | "\n", 29 | "For the full list of options see [attr.ib()](https://www.attrs.org/en/stable/api.html#attr.ib)\n", 30 | "\n", 31 | "## `Parameterized`\n", 32 | "\n", 33 | "`sequencing` builds on the functionality of `attrs` using a class called [Parameterized](../api/classes.rst#Parameterized). `Parameterized` objects must have a `name` and can have any number of `parameters`, which can be created using the functions defined in `sequencing.parameters`, or by using `attrs` directly via [attr.ib()](https://www.attrs.org/en/stable/api.html#attr.ib).\n", 34 | "\n", 35 | "**Parameterized offers the following convenient features:**\n", 36 | "\n", 37 | "- Recursive `get()` and `set()` methods for getting and setting attributes of nested `Parameterized` objects.\n", 38 | "- Methods for converting a `Parameterized` object into a Python `dict`, and creating a new `Parameterized` object from a `dict`.\n", 39 | "- Methods for serializing a `Parameterized` object to `json` and creating a new `Parameterized` object from `json`.\n", 40 | "\n", 41 | "**Notes:**\n", 42 | "\n", 43 | "- Subclasses of `Parameterized` must be decorated with `@attr.s`\n", 44 | "- Subclasses of `Parameterized` can define an `initialize()` method, which takes no arguments.\n", 45 | " It will be called on instantiation after the `attrs`-generated `__init__` method\n", 46 | " (see [__attrs_post_init__](https://www.attrs.org/en/stable/examples.html?highlight=__attrs#other-goodies)\n", 47 | " for more details). If defined, the subclass' `initialize()` method should always call `super().initialize()`\n", 48 | " to ensure that the superclass is correctly initialized." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 1, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "import json\n", 58 | "from pprint import pprint\n", 59 | "import attr\n", 60 | "from sequencing.parameters import (\n", 61 | " Parameterized, StringParameter, BoolParameter, ListParameter, DictParameter,\n", 62 | " IntParameter, FloatParameter, NanosecondParameter, GigahertzParameter, RadianParameter,\n", 63 | ")" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 2, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "@attr.s\n", 73 | "class Engine(Parameterized):\n", 74 | " cylinders = IntParameter(4)\n", 75 | " displacement = FloatParameter(2, unit='liter')\n", 76 | " current_rpm = FloatParameter(0, unit='rpm')\n", 77 | " turbo_charged = BoolParameter(False)\n", 78 | " \n", 79 | "@attr.s\n", 80 | "class Transmission(Parameterized):\n", 81 | " manual = BoolParameter(False)\n", 82 | " num_gears = IntParameter(5)\n", 83 | " current_gear = IntParameter(1)\n", 84 | " \n", 85 | " def initialize(self):\n", 86 | " super().initialize()\n", 87 | " # Add private attributes in initialize()\n", 88 | " self._is_broken = True\n", 89 | " \n", 90 | " @property\n", 91 | " def has_clutch(self):\n", 92 | " return self.manual\n", 93 | " \n", 94 | " def shift_to(self, gear):\n", 95 | " if gear not in range(self.num_gears+1):\n", 96 | " # 0 is reverse\n", 97 | " raise ValueError(f'Cannot shift into gear {gear}')\n", 98 | " if abs(gear - self.current_gear) > 1:\n", 99 | " raise ValueError('Cannot skip gears')\n", 100 | " self.current_gear = gear\n", 101 | " \n", 102 | "@attr.s\n", 103 | "class Car(Parameterized):\n", 104 | " VALID_CHASSIS = ['sedan', 'coupe', 'hatchback', 'suv']\n", 105 | " chassis = StringParameter('sedan', validator=attr.validators.in_(VALID_CHASSIS))\n", 106 | " num_doors = IntParameter(4, validator=attr.validators.in_([2,4]))\n", 107 | " miles_per_gallon = FloatParameter(30, unit='mpg')\n", 108 | " \n", 109 | " engine = attr.ib(factory=lambda: Engine('engine'))\n", 110 | " transmission = attr.ib(factory=lambda: Transmission('transmission'))" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 3, 116 | "metadata": {}, 117 | "outputs": [ 118 | { 119 | "name": "stdout", 120 | "output_type": "stream", 121 | "text": [ 122 | "Car(name='car', cls='__main__.Car', chassis='sedan', num_doors=4, miles_per_gallon=30.0, engine=Engine(name='engine', cls='__main__.Engine', cylinders=4, displacement=2.0, current_rpm=0.0, turbo_charged=False), transmission=Transmission(name='transmission', cls='__main__.Transmission', manual=False, num_gears=5, current_gear=1))\n" 123 | ] 124 | } 125 | ], 126 | "source": [ 127 | "car = Car('car') # All parameters other than name are optional because they have defaults\n", 128 | "print(car)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 4, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "{'chassis': 'sedan',\n", 141 | " 'cls': '__main__.Car',\n", 142 | " 'engine': {'cls': '__main__.Engine',\n", 143 | " 'current_rpm': 0.0,\n", 144 | " 'cylinders': 4,\n", 145 | " 'displacement': 2.0,\n", 146 | " 'name': 'engine',\n", 147 | " 'turbo_charged': False},\n", 148 | " 'miles_per_gallon': 30.0,\n", 149 | " 'name': 'car',\n", 150 | " 'num_doors': 4,\n", 151 | " 'transmission': {'cls': '__main__.Transmission',\n", 152 | " 'current_gear': 1,\n", 153 | " 'manual': False,\n", 154 | " 'name': 'transmission',\n", 155 | " 'num_gears': 5}}\n" 156 | ] 157 | } 158 | ], 159 | "source": [ 160 | "pprint(car.as_dict())" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 5, 166 | "metadata": {}, 167 | "outputs": [ 168 | { 169 | "name": "stdout", 170 | "output_type": "stream", 171 | "text": [ 172 | "True\n" 173 | ] 174 | } 175 | ], 176 | "source": [ 177 | "car2 = Car.from_dict(car.as_dict())\n", 178 | "print(car == car2)" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 6, 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "data": { 188 | "text/plain": [ 189 | "True" 190 | ] 191 | }, 192 | "execution_count": 6, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "car.get('engine.displacement') == {'engine.displacement': car.engine.displacement}" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 7, 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "data": { 208 | "text/plain": [ 209 | "True" 210 | ] 211 | }, 212 | "execution_count": 7, 213 | "metadata": {}, 214 | "output_type": "execute_result" 215 | } 216 | ], 217 | "source": [ 218 | "car.set_param('engine.displacement', 2.5)\n", 219 | "car.get_param('engine.displacement') == car.engine.displacement == 2.5" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 8, 225 | "metadata": {}, 226 | "outputs": [ 227 | { 228 | "name": "stdout", 229 | "output_type": "stream", 230 | "text": [ 231 | "{'engine.displacement': 3.0}\n" 232 | ] 233 | } 234 | ], 235 | "source": [ 236 | "car.set(engine__displacement=3.0)\n", 237 | "print(car.get('engine.displacement'))" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 9, 243 | "metadata": {}, 244 | "outputs": [ 245 | { 246 | "name": "stdout", 247 | "output_type": "stream", 248 | "text": [ 249 | "RPM: 0.0, gear: 1\n", 250 | "RPM: 4000, gear: 3\n", 251 | "RPM: 0.0, gear: 1\n" 252 | ] 253 | } 254 | ], 255 | "source": [ 256 | "print(f'RPM: {car.engine.current_rpm}, gear: {car.transmission.current_gear}')\n", 257 | "\n", 258 | "with car.temporarily_set(engine__current_rpm=4000, transmission__current_gear=3):\n", 259 | " print(f'RPM: {car.engine.current_rpm}, gear: {car.transmission.current_gear}')\n", 260 | " \n", 261 | "print(f'RPM: {car.engine.current_rpm}, gear: {car.transmission.current_gear}')" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 10, 267 | "metadata": {}, 268 | "outputs": [ 269 | { 270 | "name": "stdout", 271 | "output_type": "stream", 272 | "text": [ 273 | "ValueError: 'chassis' must be in ['sedan', 'coupe', 'hatchback', 'suv'] (got 'convertible')\n" 274 | ] 275 | } 276 | ], 277 | "source": [ 278 | "try:\n", 279 | " convertible = Car('convertible', chassis='convertible')\n", 280 | "except ValueError as e:\n", 281 | " print('ValueError:', e)" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": 11, 287 | "metadata": {}, 288 | "outputs": [ 289 | { 290 | "name": "stdout", 291 | "output_type": "stream", 292 | "text": [ 293 | "ValueError: 'num_doors' must be in [2, 4] (got 3)\n" 294 | ] 295 | } 296 | ], 297 | "source": [ 298 | "try:\n", 299 | " three_door = Car('three_door', num_doors=3)\n", 300 | "except ValueError as e:\n", 301 | " print('ValueError:', e)" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 12, 307 | "metadata": {}, 308 | "outputs": [ 309 | { 310 | "data": { 311 | "text/html": [ 312 | "
SoftwareVersion
QuTiP4.5.2
Numpy1.19.2
SciPy1.5.2
matplotlib3.2.2
Cython0.29.21
Number of CPUs4
BLAS InfoINTEL MKL
IPython7.19.0
Python3.7.0 (default, Jun 28 2018, 08:04:48) [MSC v.1912 64 bit (AMD64)]
OSnt [win32]
Sun Jan 24 16:06:57 2021 Eastern Standard Time
" 313 | ], 314 | "text/plain": [ 315 | "" 316 | ] 317 | }, 318 | "execution_count": 12, 319 | "metadata": {}, 320 | "output_type": "execute_result" 321 | } 322 | ], 323 | "source": [ 324 | "from qutip.ipynbtools import version_table\n", 325 | "version_table()" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": null, 331 | "metadata": {}, 332 | "outputs": [], 333 | "source": [] 334 | } 335 | ], 336 | "metadata": { 337 | "kernelspec": { 338 | "display_name": "Python 3", 339 | "language": "python", 340 | "name": "python3" 341 | }, 342 | "language_info": { 343 | "codemirror_mode": { 344 | "name": "ipython", 345 | "version": 3 346 | }, 347 | "file_extension": ".py", 348 | "mimetype": "text/x-python", 349 | "name": "python", 350 | "nbconvert_exporter": "python", 351 | "pygments_lexer": "ipython3", 352 | "version": "3.9.1" 353 | } 354 | }, 355 | "nbformat": 4, 356 | "nbformat_minor": 4 357 | } 358 | -------------------------------------------------------------------------------- /docs/source/notebooks/04-sequences.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "![seQuencing logo](../images/sequencing-logo.svg)\n", 8 | "\n", 9 | "# Sequences\n", 10 | "\n", 11 | "In some cases, one may want to intersperse ideal unitary gates within a sequence of time-dependent operations. This is possible using an object called a [Sequence](../api/classes.rst#Sequence). A `Sequence` is essentially a list containing [PulseSequences](../api/classes.rst#PulseSequence), [Operations](../api/classes.rst#Operation), and unitary operators. When `Sequence.run(init_state)` is called, the `Sequence` iterates over its constituent `PulseSequences`, `Operations`, and unitaries, applying each to the resulting state of the last.\n", 12 | "\n", 13 | "`Sequence` is designed to behave like a Python [list](https://docs.python.org/3/tutorial/datastructures.html), so it has the following methods defined:\n", 14 | "\n", 15 | "- `append()`\n", 16 | "- `extend()`\n", 17 | "- `insert()`\n", 18 | "- `pop()`\n", 19 | "- `clear()`\n", 20 | "- `__len__()`\n", 21 | "- `__getitem__()`\n", 22 | "- `__iter__()`\n", 23 | "\n", 24 | "**Notes:**\n", 25 | "\n", 26 | "- Just like a `PulseSequence` or `CompiledPulseSequence`, a `Sequence` must be associated with a `System`.\n", 27 | "- Whereas `PulseSequence.run()` and `CompiledPulseSequence.run()` return an instance of `qutip.solver.Result`, `Sequence.run()` returns a [SequenceResult](../api/classes.rst#SequenceResult) object, which behaves just like `qutip.solver.Result`. `SequenceResult.states` stores the quantum `states` after each stage of the simulation (`states[0]` is `init_state` and `states[-1]` is the final state of the system)." 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 1, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "%config InlineBackend.figure_formats = ['svg']\n", 37 | "%matplotlib inline\n", 38 | "import matplotlib.pyplot as plt\n", 39 | "import numpy as np\n", 40 | "import qutip\n", 41 | "from sequencing import Transmon, Cavity, System, Sequence" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "qubit = Transmon('qubit', levels=3, kerr=-200e-3)\n", 51 | "cavity = Cavity('cavity', levels=10, kerr=-10e-6)\n", 52 | "system = System('system', modes=[qubit, cavity])\n", 53 | "system.set_cross_kerr(cavity, qubit, chi=-2e-3)\n", 54 | "qubit.gaussian_pulse.drag = 5" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "## Interleave pulses and unitaries\n", 62 | "\n", 63 | "Here we perform a \"$\\pi$-pulse\" composed of $20$ interleaved $\\frac{\\pi}{40}$-pulses and unitary rotations." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stderr", 73 | "output_type": "stream", 74 | "text": [ 75 | "100%|██████████| 40/40 [00:00<00:00, 97.18it/s]\n" 76 | ] 77 | }, 78 | { 79 | "ename": "Exception", 80 | "evalue": "Operator and state do not have same tensor structure: [3, 10] and [30]", 81 | "output_type": "error", 82 | "traceback": [ 83 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 84 | "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", 85 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mseq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mqubit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRx\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtheta\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 21\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mseq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minit_state\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0me_ops\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0me_ops\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfull_evolution\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mprogress_bar\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 22\u001b[0m \u001b[0mstates\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstates\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 86 | "\u001b[0;32m~/GitHub/sequencing/sequencing/sequencing/main.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, init_state, e_ops, options, full_evolution, progress_bar)\u001b[0m\n\u001b[1;32m 324\u001b[0m \u001b[0mexpect\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mop\u001b[0m \u001b[0;32min\u001b[0m \u001b[0me_ops\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 326\u001b[0;31m \u001b[0mexpect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mqutip\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstates\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 327\u001b[0m \u001b[0mnum_collapse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mc_ops\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mclean\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 328\u001b[0m result = SequenceResult(\n", 87 | "\u001b[0;32m~/opt/anaconda3/envs/sequencing/lib/python3.9/site-packages/qutip/expect.py\u001b[0m in \u001b[0;36mexpect\u001b[0;34m(oper, state)\u001b[0m\n\u001b[1;32m 91\u001b[0m if oper.isherm and all([(op.isherm or op.type == 'ket')\n\u001b[1;32m 92\u001b[0m for op in state]):\n\u001b[0;32m---> 93\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0m_single_qobj_expect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moper\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 94\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 95\u001b[0m return np.array([_single_qobj_expect(oper, x) for x in state],\n", 88 | "\u001b[0;32m~/opt/anaconda3/envs/sequencing/lib/python3.9/site-packages/qutip/expect.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 91\u001b[0m if oper.isherm and all([(op.isherm or op.type == 'ket')\n\u001b[1;32m 92\u001b[0m for op in state]):\n\u001b[0;32m---> 93\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0m_single_qobj_expect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moper\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 94\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 95\u001b[0m return np.array([_single_qobj_expect(oper, x) for x in state],\n", 89 | "\u001b[0;32m~/opt/anaconda3/envs/sequencing/lib/python3.9/site-packages/qutip/expect.py\u001b[0m in \u001b[0;36m_single_qobj_expect\u001b[0;34m(oper, state)\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misoper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moper\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0moper\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdims\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdims\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 107\u001b[0;31m raise Exception('Operator and state do not have same tensor ' +\n\u001b[0m\u001b[1;32m 108\u001b[0m \u001b[0;34m'structure: %s and %s'\u001b[0m \u001b[0;34m%\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 109\u001b[0m (oper.dims[1], state.dims[0]))\n", 90 | "\u001b[0;31mException\u001b[0m: Operator and state do not have same tensor structure: [3, 10] and [30]" 91 | ] 92 | } 93 | ], 94 | "source": [ 95 | "init_state = system.ground_state()\n", 96 | "# calculate expectation value of |qubit=1, cavity=0>\n", 97 | "e_ops = [system.fock_dm(qubit=1)]\n", 98 | "n_rotations = 20\n", 99 | "theta = np.pi / n_rotations\n", 100 | "\n", 101 | "seq = Sequence(system)\n", 102 | "\n", 103 | "for _ in range(n_rotations):\n", 104 | "\n", 105 | " # Capture a PulseSequence\n", 106 | " qubit.rotate_x(theta/2)\n", 107 | " \n", 108 | " # # Alternatively, we can append an Operation\n", 109 | " # operation = qubit.rotate_x(theta/2, capture=False)\n", 110 | " # seq.append(operation)\n", 111 | " \n", 112 | " # Append a unitary\n", 113 | " seq.append(qubit.Rx(theta/2))\n", 114 | " \n", 115 | "result = seq.run(init_state, e_ops=e_ops, full_evolution=True, progress_bar=True)\n", 116 | "states = result.states" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "### Inspect the sequence\n", 124 | "\n", 125 | "`Sequence.plot_coefficients()` plots Hamiltonian coefficients vs. time. Instantaneous unitary operations are represented by dashed vertical lines. If multiple unitaries occur at the same time, only a single dashed line is drawn." 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "fig, ax = seq.plot_coefficients(subplots=False)\n", 135 | "ax.set_xlabel('Time [ns]')\n", 136 | "ax.set_ylabel('Hamiltonian coefficient [GHz]')\n", 137 | "fig.set_size_inches(8,4)\n", 138 | "fig.tight_layout()\n", 139 | "fig.subplots_adjust(top=0.9)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "print('len(states):', len(states))\n", 149 | "print(f'state fidelity: {qutip.fidelity(states[-1], qubit.Rx(np.pi) * init_state)**2:.4f}')" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "### Plot the results" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "metadata": {}, 163 | "outputs": [], 164 | "source": [ 165 | "e_pops = result.expect[0] # probability of measuring the state |qubit=1, cavity=0>\n", 166 | "\n", 167 | "fig, ax = plt.subplots(figsize=(8,4))\n", 168 | "ax.plot(result.times, e_pops, '.')\n", 169 | "ax.scatter(result.times[:1], e_pops[:1], marker='s', color='k', label='init_state')\n", 170 | "# draw vertical lines at the location of each unitary rotation\n", 171 | "for i in range(1, result.times.size // (2*n_rotations) + 1):\n", 172 | " t = 2 * n_rotations * i - 1\n", 173 | " label = 'unitaries' if i == 1 else None\n", 174 | " ax.axvline(t, color='k', alpha=0.25, ls='--', lw=1.5, label=label)\n", 175 | "ax.axhline(0, color='k', lw=1)\n", 176 | "ax.axhline(1, color='k', lw=1)\n", 177 | "ax.set_ylabel('$P(|e\\\\rangle)$')\n", 178 | "ax.set_xlabel('Times [ns]')\n", 179 | "ax.set_title('Interleaved pulses and unitaries')\n", 180 | "ax.legend(loc=0);" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "print(result)" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "from qutip.ipynbtools import version_table\n", 199 | "version_table()" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [] 208 | } 209 | ], 210 | "metadata": { 211 | "kernelspec": { 212 | "display_name": "Python 3", 213 | "language": "python", 214 | "name": "python3" 215 | }, 216 | "language_info": { 217 | "codemirror_mode": { 218 | "name": "ipython", 219 | "version": 3 220 | }, 221 | "file_extension": ".py", 222 | "mimetype": "text/x-python", 223 | "name": "python", 224 | "nbconvert_exporter": "python", 225 | "pygments_lexer": "ipython3", 226 | "version": "3.9.1" 227 | } 228 | }, 229 | "nbformat": 4, 230 | "nbformat_minor": 4 231 | } 232 | -------------------------------------------------------------------------------- /docs/source/tutorials/tutorials.rst: -------------------------------------------------------------------------------- 1 | .. sequencing 2 | 3 | .. figure:: ../images/sequencing-logo.* 4 | :alt: seQuencing logo 5 | 6 | ********* 7 | Tutorials 8 | ********* 9 | 10 | This section of the documentation contains a set of `Jupyter notebooks `_ 11 | intended to demonstrate important features and common idioms of the ``sequencing`` package. 12 | 13 | .. note:: 14 | These notebooks assume familiarity with scientific Python and `QuTIP `_. 15 | 16 | If you are not familiar with the `scientific Python stack `_ 17 | or with QuTiP, it may be useful to check out some of the following resources first: 18 | 19 | - `Lectures on scientific computing with Python `_ 20 | - Lectures 0-4 are most relevant to ``sequencing`` 21 | - `QuTiP documentation `_, especially the following sections 22 | - `Basic Operations on Quantum Objects `_ 23 | - `Manipulating States and Operators `_ 24 | - `Using Tensor Products and Partial Traces `_ 25 | - `Lindblad Master Equation Solver `_ 26 | 27 | Click the badge below to run the tutorial notebooks interactively online using `binder `_: 28 | 29 | .. image:: https://mybinder.org/badge_logo.svg 30 | :target: https://mybinder.org/v2/gh/sequencing-dev/sequencing/main?filepath=docs%2Fsource%2Fnotebooks 31 | 32 | .. toctree:: 33 | :maxdepth: 2 34 | :caption: Notebooks 35 | :glob: 36 | 37 | ../notebooks/00* 38 | ../notebooks/01* 39 | ../notebooks/02* 40 | ../notebooks/03* 41 | ../notebooks/04* 42 | ../notebooks/05* 43 | ../notebooks/06* 44 | ../notebooks/07* -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = 3 | ; Later versions of numpy (>1.19) introduce 4 | ; a huge number of DeprecationWarnings which are 5 | ; not really relevant to us. 6 | ; We see all of these warnings because we are using 7 | ; asteval with use_numpy=True. 8 | ignore::DeprecationWarning:numpy.* -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.16 2 | qutip>=4.5 3 | attrs>=20 4 | colorednoise>=1.1.1 5 | cython>=0.29.20 6 | jupyter 7 | scipy 8 | matplotlib 9 | tqdm 10 | lmfit 11 | 12 | -------------------------------------------------------------------------------- /sequencing/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | """ 10 | Here we adopt the convention that modes are ordered as 11 | |m_n, m_n-1, ..., m_1, m_0>. 12 | Furthermore, the ordering is decided primarily by mode Hilbert space size: 13 | modes with more levels go on the right. 14 | Among modes with the same number of levels, the ordering is decided 15 | alphanumerically from right to left. For example, 16 | assuming all cavities have the same number of levels 17 | and all qubits have the same, smaller, number of levels: 18 | |qubit1, qubit0, cavity2, cavity1, cavity0> 19 | 20 | For the motivation behind this convention, 21 | see https://arxiv.org/pdf/1711.02086.pdf. 22 | """ 23 | 24 | from .modes import Mode, Qubit, Transmon, Cavity, sort_modes 25 | from .system import System, CouplingTerm 26 | from .benchmarking import Benchmark 27 | from .sequencing import ( 28 | Sequence, 29 | PulseSequence, 30 | get_sequence, 31 | capture_operation, 32 | sync, 33 | delay, 34 | delay_channels, 35 | HTerm, 36 | CTerm, 37 | Operation, 38 | ket2dm, 39 | ops2dms, 40 | tqdm, 41 | SequenceResult, 42 | ) 43 | from .qasm import QasmSequence 44 | from .version import __version__, __version_info__ 45 | -------------------------------------------------------------------------------- /sequencing/benchmarking.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | import qutip 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from .sequencing import ket2dm, Sequence, PulseSequence, CompiledPulseSequence 13 | 14 | 15 | class Benchmark(object): 16 | """A class for comparing the performance of a given 17 | control sequence to a target unitary. 18 | 19 | Args: 20 | sequence (PulseSequence, CompiledPulseSequence, or Sequence): 21 | The sequence to benchmark. 22 | init_state (qutip.Qobj): The initial state of the system. 23 | target_unitary (qutip.Qobj): The unitary to which to compare the 24 | sequence. 25 | run_sequence (optional, bool): Whether to run the sequence immediately 26 | upon creating the Benchmark. Default: True. 27 | """ 28 | 29 | def __init__(self, sequence, init_state, target_unitary, run_sequence=True): 30 | allowed_sequence_types = (PulseSequence, CompiledPulseSequence, Sequence) 31 | if not isinstance(sequence, allowed_sequence_types): 32 | raise TypeError( 33 | f"Expected sequence to be one of the following types: " 34 | f'({", ".join(allowed_sequence_types)}), ' 35 | f"but got {type(sequence)}." 36 | ) 37 | self.seq = sequence 38 | self.init_state = init_state 39 | self.target_unitary = target_unitary 40 | self.target_state = (self.target_unitary * self.init_state).unit() 41 | self.mesolve_state = None 42 | if run_sequence: 43 | self.run_sequence() 44 | 45 | def run_sequence(self, **kwargs): 46 | """Simulate the sequence and save the final state. 47 | 48 | Keyword arguments are passed to ``self.seq.run()``. 49 | """ 50 | result = self.seq.run(self.init_state, **kwargs) 51 | self.mesolve_state = result.states[-1] 52 | 53 | def fidelity(self, target_state=None): 54 | """Returns the fidelty of the state resulting from the sequence 55 | to the target state. 56 | 57 | Args: 58 | target_state (optional, qutip.Qobj): State to which to compare the 59 | final state. Defaults to``target_unitary * init_state``. 60 | 61 | Returns: 62 | float or None 63 | """ 64 | if self.mesolve_state is None: 65 | return 66 | target_state = target_state or self.target_state 67 | return qutip.fidelity(self.mesolve_state, target_state) ** 2 68 | 69 | def tracedist(self, target_state=None, sparse=False, tol=0): 70 | """Returns the trace distance from the state resulting 71 | from the sequence to the target state. 72 | 73 | Args: 74 | target_state (optional, qutip.Qobj): State to which to compare the 75 | final state. Defaults to``target_unitary * init_state``. 76 | sparse, tol: See ``qutip.tracedist``. 77 | 78 | Returns: 79 | float or None 80 | """ 81 | if self.mesolve_state is None: 82 | return 83 | target_state = target_state or self.target_state 84 | state = self.mesolve_state 85 | return qutip.tracedist(state, target_state, sparse=sparse, tol=tol) 86 | 87 | def purity(self): 88 | """Returns the purity of the final state. 89 | 90 | Returns: 91 | float or None 92 | """ 93 | if self.mesolve_state is None: 94 | return 95 | return np.trace(ket2dm(self.mesolve_state) ** 2).real 96 | 97 | def plot_wigners( 98 | self, 99 | target_state=None, 100 | actual_state=None, 101 | sel=None, 102 | cmap="RdBu", 103 | disp_range=(-5, 5, 201), 104 | ): 105 | """Plots the Wigner function of the final state and the target state. 106 | 107 | Args: 108 | target_state (optional, qutip.Qobj): State to which to compare the 109 | final state. Defaults to``target_unitary * init_state``. 110 | actual_state (optional, qutip.Qobj): State to compare the 111 | target_state. Defaults to ``self.mesolve_state``. 112 | sel (optional, int or list[int]): Indices of modes to keep when 113 | taking the partial trace of the target and actual states. 114 | If None, no partial trace is taken. Default: None. 115 | cmap (optional, str): Name of the matplotlib colormap to use. 116 | Default: 'RdBu' 117 | disp_range (tuple[float, float, int]): Range of displacements to 118 | use when compouting the Wigner function, specified as 119 | (start, stop, num_steps). Default: (-5, 5, 201). 120 | 121 | Returns: 122 | tuple: matplotlib Figure and Axis. 123 | """ 124 | if self.mesolve_state is None: 125 | return 126 | target_state = target_state or self.target_state 127 | actual_state = actual_state or self.mesolve_state 128 | if sel is not None: 129 | target_state = target_state.ptrace(sel) 130 | actual_state = actual_state.ptrace(sel) 131 | xs = ys = np.linspace(*disp_range) 132 | w = qutip.wigner(actual_state, xs, ys) 133 | w0 = qutip.wigner(target_state, xs, ys) 134 | clim = max(abs(w.min()), abs(w.max()), abs(w0.min()), abs(w0.max())) 135 | norm = plt.Normalize(-clim, clim) 136 | fig, axes = plt.subplots(1, 2, sharex=True, sharey=True) 137 | labels = ["target state", "mesolve state"] 138 | for ax, wigner, title in zip(axes, [w0, w], labels): 139 | im = ax.pcolormesh(xs, ys, wigner, cmap=cmap, norm=norm, shading="auto") 140 | ax.set_aspect("equal") 141 | ax.set_title(title) 142 | ax.set_xlabel(r"Re[$\alpha$]") 143 | ax.set_ylabel(r"Im[$\alpha$]") 144 | fig.colorbar(im, ax=axes, orientation="horizontal") 145 | fig.suptitle(f"{self.seq.system.name} Wigner") 146 | return fig, axes 147 | 148 | def plot_fock_distribution( 149 | self, 150 | target_state=None, 151 | actual_state=None, 152 | sel=None, 153 | offset=0, 154 | ax=None, 155 | unit_y_range=True, 156 | ): 157 | """Plots the photon number distribution of the 158 | target and actual states. 159 | 160 | Args: 161 | target_state (optional, qutip.Qobj): State to which to compare the 162 | final state. Defaults to``target_unitary * init_state``. 163 | actual_state (optional, qutip.Qobj): State to compare the 164 | target_state. Defaults to ``self.mesolve_state``. 165 | sel (optional, int or list[int]): Indices of modes to keep when 166 | taking the partial trace of the target and actual states. 167 | If None, no partial trace is taken. Default: None. 168 | offset (optional, int): Minimum photon number to plot. Default: 0. 169 | ax (optional, Axis): matplotlib axis on which to plot. If None, 170 | one is automatically created. Default: None. 171 | unit_y_range (optional, bool): Whether to force the y axis limits 172 | to (0, 1). Default: True. 173 | 174 | Returns: 175 | tuple: matplotlib Figure and Axis. 176 | """ 177 | if self.mesolve_state is None: 178 | return 179 | if ax is not None: 180 | fig = plt.gcf() 181 | else: 182 | fig, ax = plt.subplots(1, 1) 183 | 184 | target_state = target_state or self.target_state 185 | actual_state = actual_state or self.mesolve_state 186 | if sel is not None: 187 | target_state = target_state.ptrace(sel) 188 | actual_state = actual_state.ptrace(sel) 189 | labels = ["target state", "mesolve state"] 190 | for rho, label in zip([target_state, actual_state], labels): 191 | rho = ket2dm(rho) 192 | N = rho.shape[0] 193 | xs = np.arange(offset, offset + N) 194 | ys = rho.diag().real 195 | ax.bar(xs, ys, alpha=0.5, width=0.8, label=label) 196 | if unit_y_range: 197 | ax.set_ylim(0, 1) 198 | ax.set_xlim(-0.5 + offset, N + offset) 199 | ax.set_xlabel("Fock number", fontsize=12) 200 | ax.set_ylabel("Occupation probability", fontsize=12) 201 | ax.legend(loc=0) 202 | ax.set_title(f"{self.seq.system.name} Fock distribution") 203 | return fig, ax 204 | -------------------------------------------------------------------------------- /sequencing/gates/__init__.py: -------------------------------------------------------------------------------- 1 | from .onequbit import ( 2 | rx, 3 | ry, 4 | rz, 5 | x, 6 | y, 7 | z, 8 | h, 9 | r, 10 | single_qubit_gate, 11 | pulsed_gate_exists, 12 | ) 13 | from .twoqubit import ( 14 | TwoQubitGate, 15 | ControlledTwoQubitGate, 16 | CUGate, 17 | CXGate, 18 | CYGate, 19 | CZGate, 20 | CPhaseGate, 21 | SWAPGate, 22 | SWAPphiGate, 23 | iSWAPGate, 24 | eSWAPGate, 25 | SqrtSWAPGate, 26 | SqrtiSWAPGate, 27 | cu, 28 | cx, 29 | cy, 30 | cz, 31 | cphase, 32 | swap, 33 | swapphi, 34 | iswap, 35 | eswap, 36 | sqrtswap, 37 | sqrtiswap, 38 | ) 39 | -------------------------------------------------------------------------------- /sequencing/gates/onequbit.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | import numpy as np 4 | 5 | from ..modes import Mode, Transmon 6 | 7 | 8 | def single_qubit_gate(func): 9 | """A decorator used to specify that the decorated function 10 | is a single-qubit gate acting on one or more Modes. 11 | """ 12 | 13 | @wraps(func) 14 | def wrapped(*args, **kwargs): 15 | space = None 16 | for arg in args: 17 | if isinstance(arg, Mode): 18 | if space is None: 19 | space = arg.space 20 | if arg.space != space: 21 | raise ValueError("All Modes must share the same Hilbert space.") 22 | result = func(*args, **kwargs) 23 | if isinstance(result, list): 24 | if all(r is None for r in result): 25 | return None 26 | if len(result) == 1: 27 | return result[0] 28 | return result 29 | 30 | return wrapped 31 | 32 | 33 | def pulsed_gate_exists(*types): 34 | """A decorator used to specify types of Modes 35 | for which a pulse-based version of the gate exists. 36 | 37 | If the first argument is ``None``, then the gate is required 38 | to be unitary-only for all types of Modes. 39 | """ 40 | unitary_only = False 41 | if types[0] is None: 42 | unitary_only = True 43 | 44 | def pulsed_gate_exists_decorator(func): 45 | @wraps(func) 46 | def wrapped(*args, **kwargs): 47 | kwargs = kwargs.copy() 48 | unitary = kwargs.get("unitary", True) 49 | if unitary_only: 50 | _ = kwargs.pop("unitary", None) 51 | if not unitary: 52 | if unitary_only: 53 | raise ValueError( 54 | f"No pulse-based version of " 55 | f"the {func.__name__} gate exists." 56 | ) 57 | for arg in args: 58 | if isinstance(arg, Mode) and not isinstance(arg, types): 59 | raise TypeError( 60 | f"No pulse-based version of the {func.__name__} " 61 | f"gate exists for Modes of type {type(arg)}." 62 | ) 63 | return func(*args, **kwargs) 64 | 65 | return wrapped 66 | 67 | return pulsed_gate_exists_decorator 68 | 69 | 70 | @pulsed_gate_exists(None) 71 | @single_qubit_gate 72 | def U(theta, phi, lamda, *qubits, **kwargs): 73 | r""" 74 | .. math:: 75 | U(\theta,\phi,\lambda) = R_z(\phi)R_y(\theta)R_z(\lambda) 76 | 77 | Args: 78 | theta, phi, lamda (float): Euler angles, in radians. 79 | *qubits (tuple[Mode]): Modes to which to apply the gate. 80 | 81 | Returns: 82 | list[Operation or qutip.Qobj] or None: 83 | List of length ``len(qubits)``, or None if the 84 | gates were captured. 85 | """ 86 | return [q.Rz(phi) * q.Ry(theta) * q.Rz(lamda) for q in qubits] 87 | 88 | 89 | @pulsed_gate_exists(Transmon) 90 | @single_qubit_gate 91 | def rx(theta, *qubits, **kwargs): 92 | r"""Rotates each mode about its x axis by a given angle. 93 | 94 | .. math:: 95 | R_x(\theta) = \exp(-i\theta/2 \cdot X) 96 | 97 | Args: 98 | theta (float): Rotation angle in radians. 99 | *qubits (tuple[Mode]): Modes to which to apply the gate. 100 | 101 | Returns: 102 | list[Operation or qutip.Qobj] or None: 103 | List of length ``len(qubits)``, or None if the 104 | gates were captured. 105 | """ 106 | return [q.rotate_x(theta, **kwargs) for q in qubits] 107 | 108 | 109 | @pulsed_gate_exists(Transmon) 110 | @single_qubit_gate 111 | def ry(theta, *qubits, **kwargs): 112 | r"""Rotates each mode about its y axis by a given angle. 113 | 114 | .. math:: 115 | R_y(\theta) = \exp(-i\theta/2 \cdot Y) 116 | 117 | Args: 118 | theta (float): Rotation angle in radians. 119 | *qubits (tuple[Mode]): Modes to which to apply the gate. 120 | 121 | Returns: 122 | list[Operation or qutip.Qobj] or None: 123 | List of length ``len(qubits)``, or None if the 124 | gates were captured. 125 | """ 126 | return [q.rotate_y(theta, **kwargs) for q in qubits] 127 | 128 | 129 | @pulsed_gate_exists(None) 130 | @single_qubit_gate 131 | def rz(phi, *qubits, **kwargs): 132 | r"""Rotates each mode about its z axis by a given angle. 133 | 134 | .. math:: 135 | R_z(\phi) = \exp(-i\phi/2 \cdot Z) 136 | 137 | Args: 138 | phi (float): Rotation angle in radians. 139 | *qubits (tuple[Mode]): Modes to which to apply the gate. 140 | 141 | Returns: 142 | list[Operation or qutip.Qobj] or None: 143 | List of length ``len(qubits)``, or None if the 144 | gates were captured. 145 | """ 146 | return [q.Rz(phi, **kwargs) for q in qubits] 147 | 148 | 149 | @pulsed_gate_exists(Transmon) 150 | @single_qubit_gate 151 | def x(*qubits, **kwargs): 152 | r"""X gate. 153 | 154 | .. math:: 155 | X = R_x(\pi) 156 | 157 | Args: 158 | *qubits (tuple[Mode]): Modes to which to apply the gate. 159 | 160 | Returns: 161 | list[Operation or qutip.Qobj] or None: 162 | List of length ``len(qubits)``, or None if the 163 | gates were captured. 164 | """ 165 | return rx(np.pi, *qubits, **kwargs) 166 | 167 | 168 | @pulsed_gate_exists(Transmon) 169 | @single_qubit_gate 170 | def y(*qubits, **kwargs): 171 | r"""Y gate. 172 | 173 | .. math:: 174 | Y = R_y(\pi) 175 | 176 | Args: 177 | *qubits (tuple[Mode]): Modes to which to apply the gate. 178 | 179 | Returns: 180 | list[Operation or qutip.Qobj] or None: 181 | List of length ``len(qubits)``, or None if the 182 | gates were captured. 183 | """ 184 | return ry(np.pi, *qubits, **kwargs) 185 | 186 | 187 | @pulsed_gate_exists(None) 188 | @single_qubit_gate 189 | def z(*qubits, **kwargs): 190 | r"""Z gate. 191 | 192 | .. math:: 193 | Z = R_z(\pi) 194 | 195 | Args: 196 | *qubits (tuple[Mode]): Modes to which to apply the gate. 197 | 198 | Returns: 199 | list[Operation or qutip.Qobj] or None: 200 | List of length ``len(qubits)``, or None if the 201 | gates were captured. 202 | """ 203 | return rz(np.pi, *qubits, **kwargs) 204 | 205 | 206 | @pulsed_gate_exists(None) 207 | @single_qubit_gate 208 | def h(*qubits, **kwargs): 209 | r"""Hadamard gate. 210 | 211 | .. math:: 212 | H = \frac{1}{\sqrt{2}}\left(X + Z\right) 213 | 214 | Args: 215 | *qubits (tuple[Mode]): Modes to which to apply the gate. 216 | 217 | Returns: 218 | list[Operation or qutip.Qobj] or None: 219 | List of length ``len(qubits)``, or None if the 220 | gates were captured. 221 | """ 222 | return [q.hadamard(**kwargs) for q in qubits] 223 | 224 | 225 | @pulsed_gate_exists(None) 226 | @single_qubit_gate 227 | def r(theta, phi, *qubits, **kwargs): 228 | r"""Rotate each mode by an angle ``theta`` about the axis 229 | given by ``phi``. 230 | 231 | .. math:: 232 | R_\text{axis}(\theta,\phi) 233 | = \exp\left(-i\theta/2 \cdot (\cos(\phi)X + \sin(\phi)Y)\right) 234 | 235 | Args: 236 | theta (float): Rotation angle in radians. 237 | phi (float): Angle between the axis of rotation 238 | and the x axis. 239 | *qubits (tuple[Mode]): Modes to which to apply the gate. 240 | 241 | Returns: 242 | list[Operation or qutip.Qobj] or None: 243 | List of length ``len(qubits)``, or None if the 244 | gates were captured. 245 | """ 246 | return [q.Raxis(theta, phi, **kwargs) for q in qubits] 247 | -------------------------------------------------------------------------------- /sequencing/parameters.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | import json 10 | import logging 11 | import importlib 12 | from functools import reduce 13 | from contextlib import contextmanager 14 | 15 | import numpy as np 16 | import attr 17 | 18 | 19 | class NumpyJSONEncoder(json.JSONEncoder): 20 | def default(self, obj): 21 | if isinstance(obj, np.integer): 22 | return int(obj) 23 | elif isinstance(obj, np.floating): 24 | return float(obj) 25 | elif isinstance(obj, np.ndarray): 26 | return obj.tolist() 27 | else: 28 | return super().default(obj) 29 | 30 | 31 | @attr.s 32 | class Parameterized(object): 33 | """A serializable object with parameters. 34 | 35 | Parameterized objects must have a name and can have any number of 36 | parameters, which can be created using the functions defined in 37 | ``sequencing.parameters``, or by using `attrs 38 | `__ directly 39 | via ``attr.ib()``. 40 | 41 | Parameterized offers the following convenient features: 42 | 43 | * Recursive ``get()`` and ``set()`` methods for getting 44 | and setting attributes of nested ``Parameterized`` objects. 45 | * Methods for converting a ``Parameterized`` object into a Python dict, 46 | and creating a new Parameterized object from a dict. 47 | * Methods for serializing a ``Parameterized`` object to json 48 | and creating a new ``Parameterized`` object from json. 49 | 50 | Supported parameter types include: 51 | 52 | * ``StringParameter`` 53 | * ``BoolParameter`` 54 | * ``IntParameter`` 55 | * ``FloatParameter`` 56 | * ``NanosecondParameter`` 57 | * ``GigahertzParameter`` 58 | * ``RadianParameter`` 59 | * ``DictParameter`` 60 | * ``ListParameter`` 61 | 62 | **Notes:** 63 | 64 | * Subclasses of ``Parameterized`` must be decorated with ``@attr.s`` 65 | * Subclasses of ``Parameterized`` can define an ``initialize()`` method, 66 | which takes no arguments. It will be called on instantiation after the 67 | attrs-generated ``__init__`` method (see `__attrs_post_init__ 68 | `_ 69 | for more details). If defined, the subclass' ``initialize()`` method 70 | should always call ``super().initialize()`` to ensure that 71 | the superclass is correctly initialized. 72 | 73 | """ 74 | 75 | name = attr.ib(type=str) 76 | cls = attr.ib(type=str, default="") 77 | logger = logging.getLogger("Parameterized") 78 | 79 | def initialize(self): 80 | """Called after the attrs-generated __init__ method. 81 | 82 | Can be specialized to set private attributes or perform other setup tasks. 83 | """ 84 | pass 85 | 86 | def __attrs_post_init__(self): 87 | # store the class name so instances can be serialized 88 | # and de-serialized 89 | self.cls = ".".join([self.__module__, self.__class__.__name__]) 90 | self.initialize() 91 | 92 | def get_param(self, address, *args, delimiter="."): 93 | """Recursively "get" a single attribute of nested 94 | ``Parameterized`` objects. 95 | 96 | Args: 97 | address (str): ``delimiter``-delimited string specifying the 98 | attribute to fetch, e.g. ``instance.param.sub_param``. 99 | delimiter (optional, str): String used to split ``address``. 100 | Default: ``'.'``. 101 | 102 | Returns: 103 | object: Attribute specified by ``address``. 104 | """ 105 | # https://stackoverflow.com/questions/31174295/ 106 | def _getattr(obj, attr): 107 | return getattr(obj, attr, *args) 108 | 109 | return reduce(_getattr, [self] + address.split(delimiter)) 110 | 111 | def set_param(self, address, value, delimiter="."): 112 | """Recursively "set" a single attribute of nested 113 | ``Parameterized`` objects. 114 | 115 | Args: 116 | address (str): ``delimiter``-delimited string specifying the 117 | attribute to fetch, e.g. ``instance.param.sub_param``. 118 | value (object): Value to assign to the attribute 119 | specified by ``address``. 120 | delimiter (optional, str): String used to split ``address``. 121 | Default: ".". 122 | """ 123 | # https://stackoverflow.com/questions/31174295/ 124 | pre, _, post = address.rpartition(delimiter) 125 | return setattr(self.get_param(pre) if pre else self, post, value) 126 | 127 | def set(self, **kwargs): 128 | """Recursively "set" attributes of nested ``Parameterized`` objects. 129 | 130 | Attributes must be specified as keyword arguments: 131 | ``attr_address=value``, where ``attr_address`` is a 132 | ``delimiter``-delimited string specifying the attribute to fetch, 133 | e.g. ``instance__param__sub_param=value``. The default ``delimiter`` 134 | is ``"__"``, i.e. two underscores. This can be overridden by passing in 135 | ``delimiter`` as a keyword argument. 136 | """ 137 | delimiter = kwargs.pop("delimiter", "__") 138 | for name, value in kwargs.items(): 139 | self.set_param(name, value, delimiter=delimiter) 140 | 141 | def get(self, *addresses, delimiter="."): 142 | """Recursively "get" attributes of nested ``Parameterized`` objects. 143 | 144 | Args: 145 | *names (tuple[str]): Names of the attributes whose values should be 146 | returned. 147 | delimiter (optional, str): Delimiter for the attribute addresses. 148 | Default: "." 149 | 150 | Returns: 151 | dict[str, object]: A dictionary of (attr_address, attr_value). 152 | """ 153 | params = {} 154 | for addr in addresses: 155 | params[addr] = self.get_param(addr, delimiter=delimiter) 156 | return params 157 | 158 | @contextmanager 159 | def temporarily_set(self, **kwargs): 160 | """A context mangaer that temporarily sets parameter values, 161 | then reverts them to the old values. 162 | 163 | Delimiter for ``get()`` and ``set()`` can be chosen using keyword 164 | argument ``delimiter="{whatever}"``. 165 | The default is two underscores, ``__``. 166 | """ 167 | delimiter = kwargs.pop("delimiter", "__") 168 | old_params = self.get(*list(kwargs), delimiter=delimiter) 169 | try: 170 | set_kwargs = kwargs.copy() 171 | set_kwargs["delimiter"] = delimiter 172 | self.set(**set_kwargs) 173 | yield 174 | finally: 175 | set_kwargs = old_params.copy() 176 | set_kwargs["delimiter"] = delimiter 177 | self.set(**set_kwargs) 178 | 179 | def as_dict(self, json_friendly=True): 180 | """Returns a dictionary representation of the object 181 | and all of its parameters. 182 | 183 | Args: 184 | json_friendly (optional, bool): Whether to return 185 | a JSON-friendly dictionary. Default:True. 186 | 187 | Returns: 188 | dict: Dictionary representation of the Parameterized object. 189 | """ 190 | return attr.asdict(self, retain_collection_types=True) 191 | 192 | def to_json(self, dumps=False, json_path=None): 193 | """Serialize object to json. 194 | 195 | Args: 196 | dumps (optional, bool): If True, returns the json string 197 | instead of writing to file. Default: False. 198 | json_path (optional, str): Path to write json file to. 199 | Default: ``{self.name}.json``. 200 | 201 | Returns: 202 | str or None: json string if ``dumps`` is True, 203 | else writes json to file and returns None. 204 | """ 205 | d = self.as_dict(json_friendly=True) 206 | if dumps: 207 | if json_path is not None: 208 | raise ValueError("If dumps is True, json_path must be None.") 209 | return json.dumps(d, indent=2, sort_keys=True) 210 | if json_path is None: 211 | json_path = f"{self.name}.json" 212 | if not json_path.endswith(".json"): 213 | json_path = json_path + ".json" 214 | with open(json_path, "w") as f: 215 | json.dump(d, f, indent=2, sort_keys=True, cls=NumpyJSONEncoder) 216 | 217 | @classmethod 218 | def from_dict(cls, d): 219 | """Creates a new instance from a dict 220 | like that returned by ``self.as_dict()``. 221 | 222 | Args: 223 | d (dict): Dict from which to create the Parameterized object. 224 | 225 | Returns: 226 | Parameterized: Instance of ``Parameterized`` 227 | whose parameters have been populated from ``d``. 228 | """ 229 | fields_dict = attr.fields_dict(cls) 230 | kwargs = {} 231 | for name, value in d.items(): 232 | if name not in fields_dict: 233 | # not a Parameter or Parameterized object 234 | continue 235 | if isinstance(value, (list, tuple)): 236 | # value is a list potentially containing both 237 | # Parameterized objects and 238 | # non-Parameterized objects 239 | vals = [] 240 | for val in value: 241 | if isinstance(val, dict) and "cls" in val: 242 | # val is a Parameterized object 243 | mod_name, cls_name = val["cls"].rsplit(".", 1) 244 | module = importlib.import_module(mod_name) 245 | other_cls = getattr(module, cls_name) 246 | vals.append(other_cls.from_dict(val)) 247 | else: 248 | # val is not Parameterized 249 | vals.append(val) 250 | kwargs[name] = vals 251 | elif isinstance(value, dict): 252 | # value must be either a Parameterized object itself, 253 | # or it comes from a DictParameter. 254 | if "cls" in value: 255 | # It's a Parameterized object itself, 256 | # so ook up the correct class and instantiate it 257 | mod_name, cls_name = value["cls"].rsplit(".", 1) 258 | module = importlib.import_module(mod_name) 259 | other_cls = getattr(module, cls_name) 260 | kwargs[name] = other_cls.from_dict(value) 261 | else: 262 | # It's a dict potentially containing some 263 | # Parameterized objects and some non-Parameterized objects 264 | val_dict = {} 265 | for key, val in value.items(): 266 | if isinstance(val, dict) and "cls" in val: 267 | # val is a Parameterized object 268 | mod_name, cls_name = val["cls"].rsplit(".", 1) 269 | module = importlib.import_module(mod_name) 270 | other_cls = getattr(module, cls_name) 271 | val_dict[key] = other_cls.from_dict(val) 272 | else: 273 | # val is not a Parameterized object 274 | val_dict[key] = val 275 | kwargs[name] = val_dict 276 | else: 277 | # value is not Parameterized, and it is not a list or dict, 278 | # so it must be a simple scalar parameter. 279 | kwargs[name] = value 280 | return cls(**kwargs) 281 | 282 | @classmethod 283 | def from_json(cls, json_path=None, json_str=None): 284 | """Creates a new instance from a JSON file or string 285 | like that returned by ``self.to_json()``. 286 | 287 | Args: 288 | json_path (optional, str): Path to JSON file from which 289 | to load parameters. Required if ``json_str`` is ``None``. 290 | Default: None. 291 | json_str (optional, str): JSON string like that returned by 292 | ``self.to_json(dumps=True)``. Required if ``json_path`` 293 | is ``None`` Default: None. 294 | 295 | Returns: 296 | Parameterized: Instance of ``Parameterized`` 297 | whose parameters have been populated from the JSON data. 298 | """ 299 | if json_str is not None: 300 | if json_path is not None: 301 | raise ValueError("Must provide either json_path or json_str, not both.") 302 | d = json.loads(json_str) 303 | else: 304 | if json_path is None: 305 | raise ValueError("Must provide either json_path or json_str.") 306 | if not json_path.endswith(".json"): 307 | json_path = json_path + ".json" 308 | with open(json_path, "r") as f: 309 | d = json.load(f) 310 | return cls.from_dict(d) 311 | 312 | 313 | def StringParameter(default, **kwargs): 314 | """Adds a string parameter. 315 | 316 | Args: 317 | default (str): Default value. 318 | """ 319 | return attr.ib(default=default, converter=str, **kwargs) 320 | 321 | 322 | def BoolParameter(default, **kwargs): 323 | """Adds a boolean parameter. 324 | 325 | Args: 326 | default (bool): Default value. 327 | """ 328 | return attr.ib(default=default, converter=bool, **kwargs) 329 | 330 | 331 | def IntParameter(default, unit=None, **kwargs): 332 | """Adds an integer parameter. 333 | 334 | Args: 335 | default (int): Default value. 336 | unit (optional, str): Unit to record in metadata. 337 | Default: None. 338 | """ 339 | if unit is not None: 340 | kwargs["metadata"] = dict(unit=str(unit)) 341 | return attr.ib(default=default, converter=int, **kwargs) 342 | 343 | 344 | def FloatParameter(default, unit=None, **kwargs): 345 | """Adds a float parameter. 346 | 347 | Args: 348 | default (float): Default value. 349 | unit (optional, str): Unit to record in metadata. 350 | Default: None. 351 | """ 352 | if unit is not None: 353 | kwargs["metadata"] = dict(unit=str(unit)) 354 | return attr.ib(default=default, converter=float, **kwargs) 355 | 356 | 357 | def NanosecondParameter(default, base=IntParameter, **kwargs): 358 | """Adds a nanosecond parameter. 359 | 360 | Args: 361 | default (int or float): Default value. 362 | base (optional, type): IntParameter or FloatParameter. 363 | Default: IntParameter 364 | """ 365 | return base(default, unit="ns", **kwargs) 366 | 367 | 368 | def GigahertzParameter(default, base=FloatParameter, **kwargs): 369 | """Adds a GHz parameter. 370 | 371 | Args: 372 | default (int or float): Default value. 373 | base (optional, type): IntParameter or FloatParameter. 374 | Default: FloatParameter 375 | """ 376 | return base(default, unit="GHz", **kwargs) 377 | 378 | 379 | def RadianParameter(default, base=FloatParameter, **kwargs): 380 | """Add a radian parameter. 381 | 382 | Args: 383 | default (int or float): Default value. 384 | base (optional, type): IntParameter or FloatParameter. 385 | Default: FloatParameter 386 | """ 387 | return base(default, unit="radian", **kwargs) 388 | 389 | 390 | def DictParameter(default=None, factory=dict, **kwargs): 391 | """Adds a dict parameter: 392 | 393 | Args: 394 | default (optional, dict): Default value. Default: None. 395 | base (optional, callabe): Factory function, e.g. dict 396 | or collections.OrderedDict. Default: dict. 397 | """ 398 | if default is not None: 399 | return attr.ib(default, **kwargs) 400 | return attr.ib(factory=factory, **kwargs) 401 | 402 | 403 | def ListParameter(default=None, factory=list, **kwargs): 404 | """Adds a list parameter. 405 | 406 | Args: 407 | default (optional, list): Default value. Default: None. 408 | base (optional, callabe): Factory function, e.g. list 409 | or tuple. Default: list. 410 | """ 411 | if default is not None: 412 | return attr.ib(default, **kwargs) 413 | return attr.ib(factory=factory, **kwargs) 414 | -------------------------------------------------------------------------------- /sequencing/pulses.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | import re 10 | import inspect 11 | from functools import lru_cache 12 | 13 | import attr 14 | import numpy as np 15 | from scipy.integrate import quad 16 | from colorednoise import powerlaw_psd_gaussian 17 | 18 | 19 | from .parameters import ( 20 | Parameterized, 21 | StringParameter, 22 | IntParameter, 23 | FloatParameter, 24 | NanosecondParameter, 25 | GigahertzParameter, 26 | RadianParameter, 27 | BoolParameter, 28 | ) 29 | 30 | 31 | def array_pulse( 32 | i_wave, 33 | q_wave=None, 34 | amp=1, 35 | phase=0, 36 | detune=0, 37 | dt=1.0, 38 | noise_sigma=0, 39 | noise_alpha=0, 40 | scale_noise=False, 41 | ): 42 | """Takes a real or complex waveform and applies an amplitude 43 | scaling, phase offset, time-dependent phase, and additive Gaussian noise. 44 | 45 | Args: 46 | i_wave (array-like): Real part of the waveform. 47 | q_wave (optional, array-like): Imaginary part of the waveform. 48 | If None, the imaginary part is set to 0. Default: None. 49 | amp (float): Factor by which to scale the waveform amplitude. 50 | Default: 1. 51 | phase (optional, float): Phase offset in radians. Default: 0. 52 | detune (optional, float): Software detuning/time-dependent phase to 53 | apply to the waveform, in GHz. Default: 0. 54 | dt (optional, float): Sample time in nanoseconds. 55 | noise_sigma (optional, float): Standard deviation of additive Gaussian 56 | noise applied to the pulse (see scale_noise). 57 | Default: 0. 58 | noise_alpha (optional, float): Exponent for the noise PSD S(f). 59 | S(f) is proportional to (1/f)**noise_alpha. 60 | noise_alpha = 0 for white noise, noise_alpha = 1 for 1/f noise, 61 | etc. Default: 0 (white noise). 62 | scale_noise (optional, bool): Whether to scale the noise by ``amp`` 63 | before adding it to the signal. If False, then noise_sigma has 64 | units of GHz. Default: False. 65 | 66 | Returns: 67 | ``np.ndarray``: Complex waveform. 68 | """ 69 | i_wave = np.asarray(i_wave) 70 | if q_wave is None: 71 | q_wave = np.zeros_like(i_wave) 72 | if detune: 73 | ts = np.linspace(0, len(i_wave) * dt, len(i_wave)) 74 | c_wave = (i_wave + 1j * q_wave) * np.exp(-2j * np.pi * ts * detune) 75 | i_wave, q_wave = c_wave.real, c_wave.imag 76 | if noise_sigma: 77 | i_noise, q_noise = noise_sigma * powerlaw_psd_gaussian( 78 | noise_alpha, [2, i_wave.size] 79 | ) 80 | else: 81 | i_noise, q_noise = 0, 0 82 | if scale_noise: 83 | i_wave = amp * (i_wave + i_noise) 84 | q_wave = amp * (q_wave + q_noise) 85 | else: 86 | i_wave = amp * i_wave + i_noise 87 | q_wave = amp * q_wave + q_noise 88 | c_wave = (i_wave + 1j * q_wave) * np.exp(1j * phase) 89 | return c_wave 90 | 91 | 92 | def gaussian_wave(sigma, chop=4, dt=1): 93 | length = chop * sigma 94 | ts = np.linspace(-length / 2, length / 2, int(length / dt)) 95 | P = np.exp(-(ts**2) / (2.0 * sigma**2)) 96 | ofs = P[0] 97 | return (P - ofs) / (1 - ofs) 98 | 99 | 100 | def gaussian_deriv_wave(sigma, chop=4, dt=1): 101 | length = chop * sigma 102 | ts = np.linspace(-length / 2, length / 2, int(length / dt)) 103 | ofs = np.exp(-ts[0] ** 2 / (2 * sigma**2)) 104 | return (0.25 / sigma**2) * ts * np.exp(-(ts**2) / (2 * sigma**2)) / (1 - ofs) 105 | 106 | 107 | def gaussian_chop(t, sigma, t0): 108 | P = np.exp(-(t**2) / (2.0 * sigma**2)) 109 | ofs = np.exp(-(t0**2) / (2.0 * sigma**2)) 110 | return (P - ofs) / (1 - ofs) 111 | 112 | 113 | @lru_cache() 114 | def gaussian_chop_norm(sigma, chop): 115 | t0 = sigma * chop / 2 116 | norm, _ = quad(gaussian_chop, -t0, t0, args=(sigma, -t0)) 117 | return 2 * norm 118 | 119 | 120 | def ring_up_wave(length, reverse=False, shape="tanh", **kwargs): 121 | if shape == "cos": 122 | wave = ring_up_cos(length) 123 | elif shape == "tanh": 124 | wave = ring_up_tanh(length) 125 | elif shape == "gaussian": 126 | sigma = kwargs.pop("gaussian_sigma", 6) 127 | wave = ring_up_gaussian_flattop(length, sigma, **kwargs) 128 | else: 129 | raise ValueError(f"Shape must be 'cos' or 'tanh', or 'gaussian', not {shape}.") 130 | if reverse: 131 | wave = wave[::-1] 132 | return wave 133 | 134 | 135 | def ring_up_gaussian_flattop(length, sigma, ramp_offset=None, dt=1): 136 | ramp_offset = 0 if ramp_offset is None else ramp_offset 137 | 138 | def _ring_up(t): 139 | if np.abs(t) < ramp_offset: 140 | return 1.0 141 | elif ts > ramp_offset: 142 | return np.exp(-((t - ramp_offset) ** 2) / (2.0 * sigma**2)) 143 | else: # t < ramp_offset 144 | return np.exp(-((t + ramp_offset) ** 2) / (2.0 * sigma**2)) 145 | 146 | ts = np.linspace(-length + 1, 0, int(length / dt)) 147 | P = np.array([_ring_up(t) for t in ts]) 148 | # normalize so tail amp = 0 and max amp = 0 149 | ofs = P[0] 150 | return (P - ofs) / (1 - ofs) 151 | 152 | 153 | def ring_up_cos(length, dt=1): 154 | return 0.5 * (1 - np.cos(np.linspace(0, np.pi, int(length / dt)))) 155 | 156 | 157 | def ring_up_tanh(length, dt=1): 158 | ts = np.linspace(-2, 2, int(length / dt)) 159 | return (1 + np.tanh(ts)) / 2 160 | 161 | 162 | def smoothed_constant_wave(length, sigma, shape="tanh", **kwargs): 163 | dt = kwargs.get("dt", 1) 164 | if sigma == 0: 165 | return np.ones(int(length / dt)) 166 | 167 | return np.concatenate( 168 | [ 169 | ring_up_wave(sigma, shape=shape, **kwargs), 170 | np.ones(int((length - 2 * sigma) / dt)), 171 | ring_up_wave(sigma, reverse=True, shape=shape, **kwargs), 172 | ] 173 | ) 174 | 175 | 176 | def constant_pulse(length=None, dt=1): 177 | length = int(length / dt) 178 | i_wave, q_wave = np.ones(length), np.zeros(length) 179 | return i_wave, q_wave 180 | 181 | 182 | def gaussian_pulse(sigma=None, chop=4, drag=0, dt=1): 183 | i_wave = gaussian_wave(sigma, chop=chop, dt=dt) 184 | q_wave = drag * gaussian_deriv_wave(sigma, chop=chop, dt=dt) 185 | return i_wave, q_wave 186 | 187 | 188 | def smoothed_constant_pulse(length=None, sigma=None, shape="tanh", dt=1): 189 | i_wave = smoothed_constant_wave(length, sigma, shape=shape, dt=dt) 190 | q_wave = np.zeros_like(i_wave) 191 | return i_wave, q_wave 192 | 193 | 194 | def sech_wave(sigma, chop=4, dt=1): 195 | # https://arxiv.org/pdf/1704.00803.pdf 196 | # https://doi.org/10.1103/PhysRevA.96.042339 197 | rho = np.pi / (2 * sigma) 198 | t0 = chop * sigma // 2 199 | ts = np.linspace(-t0, t0, int(chop * sigma / dt)) 200 | P = 1 / np.cosh(rho * ts) 201 | ofs = P[0] 202 | return (P - ofs) / (1 - ofs) 203 | 204 | 205 | def sech_deriv_wave(sigma, chop=4, dt=1): 206 | rho = np.pi / (2 * sigma) 207 | t0 = chop * sigma // 2 208 | ts = np.linspace(-t0, t0, int(chop * sigma / dt)) 209 | ofs = 1 / np.cosh(rho * ts[0]) 210 | P = -np.sinh(rho * ts) / np.cosh(rho * ts) ** 2 211 | return (P - ofs) / (1 - ofs) 212 | 213 | 214 | def sech_pulse(sigma=None, chop=4, drag=0, dt=1): 215 | i_wave = sech_wave(sigma, chop=chop, dt=dt) 216 | # q_wave = drag * sech_deriv_wave(sigma, chop=chop) 217 | q_wave = drag * np.gradient(i_wave) / dt 218 | return i_wave, q_wave 219 | 220 | 221 | def slepian_pulse(tau=None, width=10, drag=0, dt=1): 222 | # bandwidth is relative, i.e. scaled by 1/tau 223 | from scipy.signal.windows import slepian 224 | 225 | i_wave = slepian(tau, width / tau / dt) 226 | q_wave = drag * np.gradient(i_wave) / dt 227 | return i_wave, q_wave 228 | 229 | 230 | @attr.s 231 | class Pulse(Parameterized): 232 | """Generates a parameterized complex pulse waveform 233 | using callable ``pulse_func``. 234 | 235 | Args: 236 | amp (float): Maximum amplitude of the pulse. Default: 1. 237 | detune (float): "Software detuning" (time-dependent phase) 238 | to apply to the waveform, in GHz. Default: 0. 239 | phase (float): Phase offset to apply to the waveform, 240 | in radians. Default: 0. 241 | noise_sigma (float): Standard deviation of additive Gaussian noise 242 | applied to the pulse (in the same units as ``amp``). 243 | Default: 0. 244 | noise_alpha (float): Exponent for the noise PSD S(f). 245 | S(f) is proportional to (1/f)**noise_alpha. 246 | noise_alpha = 0 for white noise, noise_alpha = 1 for 1/f noise, 247 | etc. Default: 0 (white noise). 248 | scale_noise (optional, bool): Whether to scale the noise by ``amp`` 249 | before adding it to the signal. If False, then noise_sigma has 250 | units of GHz. Default: True. 251 | """ 252 | 253 | pulse_func = staticmethod(constant_pulse) 254 | amp = FloatParameter(1) 255 | detune = GigahertzParameter(0) 256 | phase = RadianParameter(0) 257 | dt = NanosecondParameter(1) 258 | noise_sigma = FloatParameter(0) 259 | noise_alpha = FloatParameter(0) 260 | scale_noise = BoolParameter(False) 261 | 262 | def __call__(self, **kwargs): 263 | """Returns the Pulse's complex waveform. 264 | 265 | Keyword arguments are passed to either ``pulse_func`` or 266 | ``array_pulse``, or used to override the pulse's parameters. 267 | 268 | Returns: 269 | ``np.ndarray``: complex waveform 270 | """ 271 | pulse_kwargs = {} 272 | pulse_arg_names = inspect.signature(self.pulse_func).parameters 273 | array_pulse_kwargs = {} 274 | array_pulse_arg_names = inspect.signature(array_pulse).parameters 275 | # first populate pulse kwargs with values from Parameters 276 | for name, value in self.as_dict().items(): 277 | if name in pulse_arg_names: 278 | pulse_kwargs[name] = value 279 | elif name in array_pulse_arg_names: 280 | array_pulse_kwargs[name] = value 281 | for name in list(kwargs): 282 | # populate array_pulse kwargs 283 | if name in array_pulse_arg_names: 284 | array_pulse_kwargs[name] = kwargs.pop(name) 285 | # override pulse kwargs from Parameters with those from kwargs 286 | elif name in pulse_arg_names: 287 | pulse_kwargs[name] = kwargs.pop(name) 288 | waves = self.pulse_func(**pulse_kwargs) 289 | if len(waves) == 2: 290 | i_wave, q_wave = waves 291 | else: 292 | i_wave, q_wave = waves, None 293 | return array_pulse(i_wave, q_wave=q_wave, **array_pulse_kwargs) 294 | 295 | def plot(self, ax=None, grid=True, legend=True, **kwargs): 296 | """Plots the waveform and returns the Axes. 297 | 298 | Keyword arguments are passed to ``__call__()``. 299 | """ 300 | import matplotlib.pyplot as plt 301 | 302 | if ax is None: 303 | _, ax = plt.subplots() 304 | c_wave = self(**kwargs) 305 | dt = kwargs.get("dt", self.dt) 306 | ts = np.linspace(0, len(c_wave) * dt, len(c_wave)) 307 | (line,) = ax.plot(ts, c_wave.real, ls="-", label=self.name) 308 | ax.plot(ts, c_wave.imag, color=line._color, ls="--") 309 | ax.grid(grid) 310 | if legend: 311 | ax.legend(loc="best") 312 | return ax 313 | 314 | 315 | @attr.s 316 | class ConstantPulse(Pulse): 317 | """A constant (rectangular) pulse. 318 | 319 | Args: 320 | amp (float): Maximum amplitude of the pulse. Default: 1. 321 | detune (float): "Software detuning" (time-dependent phase) 322 | to apply to the waveform, in GHz. Default: 0. 323 | phase (float): Phase offset to apply to the waveform, 324 | in radians. Default: 0. 325 | """ 326 | 327 | pass 328 | 329 | 330 | @attr.s 331 | class SmoothedConstantPulse(Pulse): 332 | """A constant pulse with smoothed ring-up and ring-down. 333 | 334 | Args: 335 | amp (float): Maximum amplitude of the pulse. Default: 1. 336 | detune (float): "Software detuning" (time-dependent phase) 337 | to apply to the waveform, in GHz. Default: 0. 338 | phase (float): Phase offset to apply to the waveform, 339 | in radians. Default: 0. 340 | length (int): Total length of the pulse in ns. Default: 100. 341 | sigma (int): Ring-up and ring-down time in ns. If sigma == 0, then 342 | this is equivalent to ControlPulse. The length of the constant 343 | portion of the pulse is ``length - 2 * sigma``. Default: 0. 344 | shape (str): String specifying the type of ring-up and ring-down. 345 | Valid options are 'tanh', 'cos', and 'gaussian' (see ``ringup_wave``). 346 | Default: 'tanh'. 347 | """ 348 | 349 | VALID_SHAPES = ["tanh", "cos", "gaussian"] 350 | pulse_func = staticmethod(smoothed_constant_pulse) 351 | length = NanosecondParameter(100) 352 | sigma = NanosecondParameter(0) 353 | shape = StringParameter("tanh", validator=attr.validators.in_(VALID_SHAPES)) 354 | 355 | 356 | @attr.s 357 | class GaussianPulse(Pulse): 358 | """A Gaussian that is "chopped" at 359 | +/- ``(chop / 2) * sigma``. The full 360 | pulse length is therefore ``sigma * chop``. 361 | 362 | Args: 363 | amp (float): Maximum amplitude of the pulse. Default: 1. 364 | detune (float): "Software detuning" (time-dependent phase) 365 | to apply to the waveform, in GHz. Default: 0. 366 | phase (float): Phase offset to apply to the waveform, 367 | in radians. Default: 0. 368 | sigma (float): Gaussian sigma in ns. Default: 10. 369 | chop (int): The Gaussian is truncated at 370 | +/- ``chop/2 * sigma``. Default: 4. 371 | drag (float): DRAG coefficient. Default: 0. 372 | """ 373 | 374 | pulse_func = staticmethod(gaussian_pulse) 375 | sigma = NanosecondParameter(10) 376 | chop = IntParameter(4, unit="sigma") 377 | drag = FloatParameter(0) 378 | 379 | 380 | @attr.s 381 | class SechPulse(Pulse): 382 | r"""Hyperbolic secant pulse that is "chopped" at 383 | +/- ``(chop / 2) * sigma``. 384 | 385 | .. math:: 386 | A(t) &= \text{sech}(\rho t)\\ 387 | \rho &= \pi / (2\sigma) 388 | 389 | See: https://doi.org/10.1103/PhysRevA.96.042339 390 | 391 | Args: 392 | sigma (int): Pulse "sigma" in ns (see equation above). 393 | Default: 10. 394 | chop (int): The waveform is truncated at 395 | +/- ``chop/2 * sigma``. Default: 4. 396 | drag (float): DRAG coefficient: 397 | imag(wave) = drag * d/dt real(wave). Default: 0. 398 | """ 399 | pulse_func = staticmethod(sech_pulse) 400 | sigma = NanosecondParameter(10) 401 | chop = IntParameter(4, unit="sigma") 402 | drag = FloatParameter(0) 403 | 404 | 405 | @attr.s 406 | class SlepianPulse(Pulse): 407 | """A Slepian Pulse. 408 | 409 | See ``scipy.signal.windows.slepian``. 410 | 411 | Args: 412 | tau (int): Pulse length in ns. Default: 40. 413 | width (int): Pulse width in ns 414 | (similar to a Gaussian sigma). Default: 10. 415 | drag (float): DRAG coefficient: 416 | imag(wave) = drag * d/dt real(wave). Default: 0. 417 | """ 418 | 419 | pulse_func = staticmethod(slepian_pulse) 420 | tau = NanosecondParameter(40) 421 | width = NanosecondParameter(10) 422 | drag = FloatParameter(0) 423 | 424 | 425 | def pulse_factory(cls, name=None, **kwargs): 426 | """Returns a function that creates an instance 427 | if the given pulse class. 428 | 429 | Keyword arguments are passed to ``cls.__init__()``. 430 | 431 | Args: 432 | cls (type): Subclass of Pulse of which to create an instance. 433 | name (optional, str): Name of the resulting pulse. If None, 434 | will use a snake-case version of the class name, 435 | e.g. 'GaussianPulse' -> 'gaussian_pulse'. Default: None. 436 | 437 | Returns: 438 | callable: A function that takes no arguments and returns 439 | an instance of ``cls``. 440 | """ 441 | if name is None: 442 | # turn 'GaussianPulse' into 'gaussian_pulse' 443 | name = "_".join(re.findall("[a-zA-Z][^A-Z]*", cls.__name__)).lower() 444 | return lambda: cls(name=name, **kwargs) 445 | -------------------------------------------------------------------------------- /sequencing/sequencing/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | from tqdm import tqdm 10 | 11 | from .basic import HamiltonianChannels, CompiledPulseSequence 12 | from .main import PulseSequence, Sequence, SequenceResult 13 | from .common import ( 14 | HTerm, 15 | CTerm, 16 | Operation, 17 | ket2dm, 18 | ops2dms, 19 | ValidatedList, 20 | SyncOperation, 21 | DelayOperation, 22 | DelayChannelsOperation, 23 | get_sequence, 24 | capture_operation, 25 | sync, 26 | delay, 27 | delay_channels, 28 | ) 29 | -------------------------------------------------------------------------------- /sequencing/sequencing/common.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | import inspect 10 | from functools import wraps 11 | from collections import namedtuple 12 | 13 | import qutip 14 | 15 | 16 | def ket2dm(obj): 17 | """Converts a ``qutip.Qobj`` from a ket 18 | to a density matrix. 19 | """ 20 | if obj.isket: 21 | return qutip.ket2dm(obj) 22 | return obj 23 | 24 | 25 | def ops2dms(ops): 26 | """Converts a ``qutip.Qobj`` or sequence of 27 | ``qutip.Qobj`` from kets to density matrices. 28 | """ 29 | if isinstance(ops, qutip.Qobj): 30 | ops = [ops] 31 | return list(map(ket2dm, ops)) 32 | 33 | 34 | class ValidatedList(object): 35 | """A list-like container which is enforced to 36 | only accept values of the types listed in the class attribute 37 | ``VALID_TYPES``. 38 | 39 | Args: 40 | iterable (optional, iterable): Iterable of initial values 41 | with which to populate the list. Default: None. 42 | """ 43 | 44 | VALID_TYPES = None 45 | 46 | def __init__(self, iterable=None): 47 | self._items = [] 48 | if iterable is not None: 49 | self.extend(iterable) 50 | 51 | def _validate(self, item): 52 | """Enforces that item is an instance of 53 | one of the types in VALID_TYPES. 54 | 55 | Returns: 56 | object: The item, if it is a valid type. 57 | """ 58 | valid_types = self.VALID_TYPES 59 | if valid_types is not None: 60 | if not isinstance(item, valid_types): 61 | raise TypeError( 62 | f"{type(self).__name__} expected instance of [" 63 | + ", ".join(t.__name__ for t in valid_types) 64 | + f"] , but got {type(item)}." 65 | ) 66 | return item 67 | 68 | def __repr__(self): 69 | return f"{type(self).__name__}([{','.join(map(repr, self))}])" 70 | 71 | def __len__(self): 72 | return len(self._items) 73 | 74 | def __iter__(self): 75 | for i in self._items: 76 | yield i 77 | 78 | def __getitem__(self, item): 79 | return self._items[item] 80 | 81 | def append(self, item): 82 | """Add an item to the end of the ValidatedList.""" 83 | item = self._validate(item) 84 | self._items.append(item) 85 | 86 | def extend(self, iterable): 87 | """Extend the ValidatedList by appending all the items 88 | from the iterable. 89 | """ 90 | iterable = [self._validate(item) for item in iterable] 91 | self._items.extend(iterable) 92 | 93 | def insert(self, i, item): 94 | """Insert an item at a given position in the ValidatedList.""" 95 | item = self._validate(item) 96 | self._items.insert(i, item) 97 | 98 | def pop(self, i=-1): 99 | """Remove the item at the given position 100 | in the ValidatedList, and return it. 101 | """ 102 | return self._items.pop(i) 103 | 104 | def clear(self): 105 | """Remove all items from the ValidatedList.""" 106 | self._items.clear() 107 | 108 | 109 | HTerm = namedtuple("HTerm", ["H", "coeffs", "args", "kwargs"], defaults=[1, None, None]) 110 | """A ``namedtuple`` specifying a time-depdendent 111 | Hamiltonian term. 112 | 113 | Args: 114 | H (qutip.Qobj): The Hamiltonian operator. 115 | coeffs (optional, number, array-like, or callable): 116 | Time-dependent coefficients for the given operator. 117 | You can specify single int/float/complex for constant 118 | coefficients, or provide a function that takes time 119 | as its first argument and returns coefficiencts. 120 | Default: None. 121 | args (optional, iterable): Positional arguments passed 122 | to coeffs if coeffs is callable. Default: None. 123 | kwargs (optional, dict): Keyword arguments passed to coeffs 124 | if coeffs is a function. Default: None. 125 | """ 126 | 127 | 128 | CTerm = namedtuple( 129 | "CTerm", ["op", "coeffs", "args", "kwargs"], defaults=[1, None, None] 130 | ) 131 | """A ``namedtuple`` specifying a time-depdendent 132 | collapse operator. 133 | 134 | Args: 135 | op (qutip.Qobj): The collapse operator. 136 | coeffs (optional, number, array-like, or callable): 137 | Time-dependenct oefficients for the given operator. 138 | You can specify single int/float/complex for constant 139 | coefficients, or provide a function that takes time 140 | as its first argument and returns coefficiencts. 141 | Default: None. 142 | args (optional, iterable): Positional arguments passed 143 | to coeffs if coeffs is callable. Default: None. 144 | kwargs (optional, dict): Keyword arguments passed to coeffs 145 | if coeffs is a function. Default: None. 146 | """ 147 | 148 | 149 | Operation = namedtuple("Operation", ["duration", "terms"]) 150 | """A ``namedtuple`` specifying a set of 151 | HTerms that are applied simultaneously. 152 | 153 | Args: 154 | duration (int): The number of time points in 155 | each of the terms, i.e. the duration of the 156 | operation. 157 | terms (dict): Dict of (hamiltonian_channel_name, HTerm) 158 | """ 159 | 160 | 161 | class SyncOperation(object): 162 | """When inserted into a PulseSequence, ensures that the 163 | Hamiltonian channels all align up to this point. 164 | 165 | This means that all operations which follow the sync will be 166 | executed after all those before the sync. Sequences are constructed 167 | in terms of blocks of operations separated by syncs. 168 | Within a block, channels are made to have equal duration by padding 169 | shorter channels to the maximum channel length. 170 | """ 171 | 172 | pass 173 | 174 | 175 | class DelayOperation(object): 176 | """When inserted into a PulseSequence, adds a global delay to the sequence, 177 | delaying all channels by the same amount. 178 | 179 | Args: 180 | length (int): Length of the delay. 181 | sync_before (optional, bool): Whether to insert a sync() before 182 | the delay. Default: True. 183 | sync_after (optional, bool): Whether to insert a sync() after 184 | the delay. Default: True. 185 | """ 186 | 187 | def __init__(self, length, sync_before=True, sync_after=True): 188 | self.length = length 189 | self.sync_before = sync_before 190 | self.sync_after = sync_after 191 | 192 | 193 | class DelayChannelsOperation(object): 194 | """When inserted into a PulseSequence, adds a delay of 195 | duration ``length`` to only the channels specified in ``channels``. 196 | 197 | Args: 198 | channels (str | list | dict): Either the name of a single channel, 199 | a list of channel names, or a dict of the form 200 | {channel_name: H_op}, or a dict of the form 201 | {channel_name: (H_op, C_op)}. One of the latter two is required 202 | if the channels are not yet defined in seq.channels 203 | (i.e. if no previous Operations have involved these channels). 204 | length (int): Duration of the delay. 205 | """ 206 | 207 | def __init__(self, channels, length): 208 | self.channels = channels 209 | self.length = length 210 | 211 | 212 | def get_sequence(system=None): 213 | """Returns the global PulseSequence. 214 | 215 | Args: 216 | system (optional, System): If system is not None, 217 | the global PulseSequence is reset. Default: None. 218 | 219 | Returns: 220 | PulseSequence: The global PulseSequence. 221 | """ 222 | from .main import _global_pulse_sequence 223 | 224 | if system is not None: 225 | _global_pulse_sequence.set_system(system, t0=0) 226 | return _global_pulse_sequence 227 | 228 | 229 | def capture_operation(func): 230 | """A decorator used to wrap functions that return an ``Operation``, 231 | which captures the ``Operation`` and adds it to the 232 | global ``PulseSequence``. 233 | 234 | If a function that is decorated with ``@capture_operation`` is called with 235 | the keyword argument ``capture=False``, the ``Operation`` returned by the 236 | wrapped function will not be captured and added to the global 237 | ``PulseSequence``, but rather returned as normal. 238 | """ 239 | 240 | @wraps(func) 241 | def wrapped_func(*args, **kwargs): 242 | sequence = get_sequence() 243 | if sequence.system is not None: 244 | # inject the current sequence time into kwargs 245 | params = inspect.signature(func).parameters 246 | if ( 247 | "t0" in params 248 | and params["t0"].default is None 249 | and kwargs.get("t0", None) is None 250 | ): 251 | kwargs["t0"] = sequence.t0 252 | capture = kwargs.pop("capture", True) 253 | result = func(*args, **kwargs) 254 | if capture and isinstance(result, Operation): 255 | if sequence.system is None: 256 | raise Exception( 257 | "The global PulseSequence is not" 258 | "currently associated with a System." 259 | ) 260 | sequence.append(result) 261 | return None 262 | return result 263 | 264 | return wrapped_func 265 | 266 | 267 | def sync(seq=None): 268 | """Ensure that the Hamiltonian channels all align up to this point. 269 | 270 | This means that all operations which follow the sync will be 271 | executed after all those before the sync. Sequences are constructed 272 | in terms of blocks of operations separated by sync()s. 273 | Within a block, channels are made to have equal duration by padding 274 | shorter channels to the maximum channel length. 275 | 276 | Args: 277 | seq (optional, CompiledPulseSequence): CompiledPulseSequence on 278 | which to apply the sync. If None, a SyncOperation is appended 279 | to the global PulseSequence. Default: None. 280 | """ 281 | if seq is None: 282 | get_sequence().append(SyncOperation()) 283 | elif seq.system is not None: 284 | seq.sync() 285 | 286 | 287 | def delay(length, sync_before=True, sync_after=True, seq=None): 288 | """Adds a global delay to the sequence, 289 | delaying all channels by the same amount. 290 | 291 | Args: 292 | length (int): Length of the delay. 293 | sync_before (optional, bool): Whether to insert a sync() before 294 | the delay. Default: True. 295 | sync_after (optional, bool): Whether to insert a sync() after 296 | the delay. Default: True. 297 | seq (optional, CompiledPulseSequence): CompiledPulseSequence on which 298 | to apply the delay. If None, a DelayOperation is appended to 299 | the global CompiledPulseSequence. Default: None. 300 | """ 301 | if seq is None: 302 | get_sequence().append(DelayOperation(length)) 303 | elif seq.system is not None: 304 | seq.delay(length, sync_before=sync_before, sync_after=sync_after) 305 | 306 | 307 | def delay_channels(channels, length, seq=None): 308 | """Adds a delay of duration `length` to only the channels 309 | specified in `channels`. 310 | 311 | Args: 312 | channels (str | list | dict): Either the name of a single channel, 313 | a list of channel names, or a dict of the form 314 | {channel_name: H_op}, or a dict of the form 315 | {channel_name: (H_op, C_op)}. One of the latter two is required 316 | if the channels are not yet defined in seq.channels 317 | (i.e. if no previous Operations have involved these channels). 318 | length (int): Duration of the delay. 319 | seq (optional, CompiledPulseSequence): CompiledPulseSequence on which 320 | to delay channels. If None, a DelayChannelsOperation is appended 321 | to the global CompiledPulseSequence. Default: None. 322 | """ 323 | if seq is None: 324 | get_sequence().append(DelayChannelsOperation(channels, length)) 325 | elif seq.system is not None: 326 | if isinstance(channels, str): 327 | channels = [channels] 328 | if isinstance(channels, dict): 329 | for name, val in channels.items(): 330 | if not isinstance(val, (list, tuple)): 331 | val = [val, None] 332 | H, C = val 333 | if name not in seq.channels: 334 | seq.add_channel(name, H=H, C_op=C, time_dependent=True) 335 | elif not isinstance(channels, (list, tuple)): 336 | raise TypeError( 337 | "Channels must be either a sequence of channel names " 338 | "or a dict like {name: operator}." 339 | ) 340 | seq.hc.delay_channels(list(channels), length) 341 | -------------------------------------------------------------------------------- /sequencing/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sequencing-dev/sequencing/423f72c58dd738ca4ce52a594aa6dfa6faa836a8/sequencing/test/__init__.py -------------------------------------------------------------------------------- /sequencing/test/test_benchmarking.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from sequencing import Transmon, Cavity, System, Benchmark, get_sequence, Sequence 6 | from sequencing.calibration import tune_rabi 7 | 8 | 9 | class TestBenchmark(unittest.TestCase): 10 | @classmethod 11 | def setUpClass(cls): 12 | qubit = Transmon("qubit", levels=2) 13 | cavity = Cavity("cavity", levels=6) 14 | system = System("system", modes=[qubit, cavity]) 15 | system.set_cross_kerr(qubit, cavity, -2e-3) 16 | with system.use_modes([qubit]): 17 | _ = tune_rabi(system, init_state=qubit.fock(0), verify=False) 18 | 19 | cls.system = system 20 | 21 | @classmethod 22 | def tearDownClass(cls): 23 | plt.close("all") 24 | 25 | def test_invalid_sequence(self): 26 | system = self.system 27 | 28 | init_state = system.fock() 29 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 30 | _ = get_sequence(system) 31 | system.qubit.rotate_x(np.pi) 32 | 33 | with self.assertRaises(TypeError): 34 | _ = Benchmark(system, init_state, target_unitary) 35 | 36 | def test_benchmark(self): 37 | system = self.system 38 | 39 | init_state = system.fock() 40 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 41 | seq = get_sequence(system) 42 | system.qubit.rotate_x(np.pi) 43 | 44 | bm = Benchmark(seq, init_state, target_unitary) 45 | 46 | self.assertAlmostEqual(bm.fidelity(), 1) 47 | self.assertLess(bm.tracedist(), 1e-5) 48 | self.assertAlmostEqual(bm.purity(), 1) 49 | 50 | def test_run_sequence_later(self): 51 | system = self.system 52 | 53 | init_state = system.fock() 54 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 55 | seq = get_sequence(system) 56 | system.qubit.rotate_x(np.pi) 57 | 58 | bm = Benchmark(seq, init_state, target_unitary, run_sequence=False) 59 | 60 | self.assertIsNone(bm.mesolve_state) 61 | self.assertIsNone(bm.fidelity()) 62 | self.assertIsNone(bm.tracedist()) 63 | self.assertIsNone(bm.purity()) 64 | 65 | bm.run_sequence() 66 | 67 | self.assertAlmostEqual(bm.fidelity(), 1) 68 | self.assertLess(bm.tracedist(), 1e-5) 69 | self.assertAlmostEqual(bm.purity(), 1) 70 | 71 | def test_benchmark_compiled_pulse_sequence(self): 72 | system = self.system 73 | 74 | init_state = system.fock() 75 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 76 | seq = get_sequence(system) 77 | system.qubit.rotate_x(np.pi) 78 | 79 | bm = Benchmark(seq.compile(), init_state, target_unitary) 80 | 81 | self.assertAlmostEqual(bm.fidelity(), 1) 82 | self.assertLess(bm.tracedist(), 1e-5) 83 | self.assertAlmostEqual(bm.purity(), 1) 84 | 85 | def test_benchmark_sequence(self): 86 | system = self.system 87 | 88 | init_state = system.fock() 89 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 90 | seq = Sequence(system) 91 | system.qubit.rotate_x(np.pi / 2) 92 | seq.capture() 93 | seq.append(system.qubit.rotate_x(np.pi / 2, unitary=True)) 94 | bm = Benchmark(seq, init_state, target_unitary) 95 | 96 | self.assertAlmostEqual(bm.fidelity(), 1) 97 | self.assertLess(bm.tracedist(), 2e-5) 98 | self.assertAlmostEqual(bm.purity(), 1) 99 | 100 | def test_plot_wigner(self): 101 | system = self.system 102 | 103 | init_state = system.fock() 104 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 105 | seq = get_sequence(system) 106 | system.qubit.rotate_x(np.pi) 107 | 108 | bm = Benchmark(seq, init_state, target_unitary) 109 | 110 | _fig, _ax = plt.subplots() 111 | 112 | fig, axes = bm.plot_wigners() 113 | self.assertIsInstance(fig, type(_fig)) 114 | for ax in axes: 115 | self.assertIsInstance(ax, type(_ax)) 116 | 117 | def test_plot_fock_distribution(self): 118 | system = self.system 119 | 120 | init_state = system.fock() 121 | target_unitary = system.qubit.rotate_x(np.pi, unitary=True) 122 | seq = get_sequence(system) 123 | system.qubit.rotate_x(np.pi) 124 | 125 | bm = Benchmark(seq, init_state, target_unitary) 126 | 127 | _fig, _ax = plt.subplots() 128 | 129 | fig, ax = bm.plot_fock_distribution() 130 | self.assertIsInstance(fig, type(_fig)) 131 | self.assertIsInstance(ax, type(_ax)) 132 | 133 | 134 | if __name__ == "__main__": 135 | unittest.main() 136 | -------------------------------------------------------------------------------- /sequencing/test/test_calibration.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import qutip 4 | 5 | from sequencing import Transmon, Cavity, System, get_sequence 6 | from sequencing.calibration import tune_rabi, tune_drag, tune_displacement 7 | 8 | 9 | class TestRabi(unittest.TestCase): 10 | @classmethod 11 | def tearDownClass(cls): 12 | import matplotlib.pyplot as plt 13 | 14 | plt.close("all") 15 | 16 | def test_rabi_two_levels(self): 17 | qubit = Transmon("qubit", levels=2) 18 | system = System("system", modes=[qubit]) 19 | system.dt = 0.75 20 | for _ in range(5): 21 | _, old_amp, new_amp = tune_rabi(system, qubit.fock(0)) 22 | self.assertLess(abs(old_amp - new_amp), 1e-5) 23 | 24 | init = qubit.fock(0) 25 | seq = get_sequence(system) 26 | qubit.rotate_x(np.pi) 27 | result = seq.run(init) 28 | 29 | target = qubit.Rx(np.pi) * init 30 | fidelity = qutip.fidelity(result.states[-1], target) ** 2 31 | self.assertLess(abs(1 - fidelity), 1e-10) 32 | 33 | 34 | class TestDrag(unittest.TestCase): 35 | @classmethod 36 | def tearDownClass(cls): 37 | import matplotlib.pyplot as plt 38 | 39 | plt.close("all") 40 | 41 | def test_drag(self): 42 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 43 | qubit.gaussian_pulse.sigma = 10 44 | system = System("system", modes=[qubit]) 45 | for _ in range(5): 46 | _, old_amp, new_amp = tune_rabi( 47 | system, qubit.fock(0), plot=False, verify=False 48 | ) 49 | self.assertLess(abs(old_amp - new_amp), 1e-7) 50 | 51 | _, old_drag, new_drag = tune_drag(system, qubit.fock(0), update=True) 52 | self.assertNotAlmostEqual(new_drag, 0) 53 | 54 | init = qubit.fock(0) 55 | seq = get_sequence(system) 56 | qubit.rotate_x(np.pi) 57 | result = seq.run(init) 58 | 59 | target = qubit.Rx(np.pi) * init 60 | fidelity = qutip.fidelity(result.states[-1], target) ** 2 61 | self.assertLess(abs(1 - fidelity), 1e-5) 62 | 63 | 64 | class TestDisplacement(unittest.TestCase): 65 | def test_displacement(self): 66 | cavity = Cavity("cavity", levels=12) 67 | system = System("system", modes=[cavity]) 68 | for _ in range(3): 69 | _, old_amp, new_amp = tune_displacement(system, cavity.fock(0)) 70 | self.assertLess(abs(old_amp - new_amp), 1e-7) 71 | 72 | init = cavity.fock(0) 73 | seq = get_sequence(system) 74 | cavity.displace(1 + 2j) 75 | result = seq.run(init) 76 | 77 | target = cavity.D(1 + 2j) * init 78 | fidelity = qutip.fidelity(result.states[-1], target) ** 2 79 | self.assertLess(abs(1 - fidelity), 1e-7) 80 | 81 | 82 | if __name__ == "__main__": 83 | unittest.main() 84 | -------------------------------------------------------------------------------- /sequencing/test/test_gates.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | import qutip 5 | 6 | from sequencing import Transmon, Cavity, System, get_sequence, Operation 7 | from sequencing import gates 8 | 9 | 10 | class TestSingleQubitGates(unittest.TestCase): 11 | @classmethod 12 | def setUpClass(cls): 13 | qubits = [Transmon(f"q{i}", levels=2) for i in range(5)][::-1] 14 | cls.system = System("system", modes=qubits) 15 | 16 | def test_rx(self): 17 | qubits = self.system.modes 18 | 19 | result = gates.rx(np.pi / 2, *qubits, unitary=True) 20 | self.assertEqual(len(result), len(qubits)) 21 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 22 | for r, q in zip(result, qubits): 23 | self.assertEqual(r, q.Rx(np.pi / 2)) 24 | 25 | result = gates.rx(np.pi / 2, *qubits, capture=False) 26 | self.assertEqual(len(result), len(qubits)) 27 | self.assertTrue(all(isinstance(r, Operation) for r in result)) 28 | for r, q in zip(result, qubits): 29 | expected = q.rotate_x(np.pi / 2, capture=False) 30 | for channel, info in r.terms.items(): 31 | self.assertTrue( 32 | np.array_equal(info.coeffs, expected.terms[channel].coeffs) 33 | ) 34 | 35 | _ = get_sequence(self.system) 36 | result = gates.rx(np.pi / 2, *qubits) 37 | self.assertIsNone(result) 38 | 39 | result = gates.rx(np.pi / 2, qubits[0], unitary=True) 40 | self.assertIsInstance(result, qutip.Qobj) 41 | 42 | def test_ry(self): 43 | qubits = self.system.modes 44 | 45 | result = gates.ry(-np.pi / 2, *qubits, unitary=True) 46 | self.assertEqual(len(result), len(qubits)) 47 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 48 | for r, q in zip(result, qubits): 49 | self.assertEqual(r, q.Ry(-np.pi / 2)) 50 | 51 | result = gates.ry(-np.pi / 2, *qubits, capture=False) 52 | self.assertEqual(len(result), len(qubits)) 53 | self.assertTrue(all(isinstance(r, Operation) for r in result)) 54 | for r, q in zip(result, qubits): 55 | expected = q.rotate_y(-np.pi / 2, capture=False) 56 | for channel, info in r.terms.items(): 57 | self.assertTrue( 58 | np.array_equal(info.coeffs, expected.terms[channel].coeffs) 59 | ) 60 | 61 | _ = get_sequence(self.system) 62 | result = gates.ry(-np.pi / 2, *qubits) 63 | self.assertIsNone(result) 64 | 65 | result = gates.ry(-np.pi / 2, qubits[0], unitary=True) 66 | self.assertIsInstance(result, qutip.Qobj) 67 | 68 | def test_rz(self): 69 | qubits = self.system.modes 70 | 71 | result = gates.rz(np.pi / 4, *qubits) 72 | self.assertEqual(len(result), len(qubits)) 73 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 74 | for r, q in zip(result, qubits): 75 | self.assertEqual(r, q.Rz(np.pi / 4)) 76 | 77 | result = gates.rz(np.pi / 4, qubits[0]) 78 | self.assertIsInstance(result, qutip.Qobj) 79 | 80 | def test_x(self): 81 | qubits = self.system.modes 82 | 83 | result = gates.x(*qubits, unitary=True) 84 | self.assertEqual(len(result), len(qubits)) 85 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 86 | for r, q in zip(result, qubits): 87 | self.assertEqual(r, q.Rx(np.pi)) 88 | 89 | result = gates.x(*qubits, capture=False) 90 | self.assertEqual(len(result), len(qubits)) 91 | self.assertTrue(all(isinstance(r, Operation) for r in result)) 92 | for r, q in zip(result, qubits): 93 | expected = q.rotate_x(np.pi, capture=False) 94 | for channel, info in r.terms.items(): 95 | self.assertTrue( 96 | np.array_equal(info.coeffs, expected.terms[channel].coeffs) 97 | ) 98 | 99 | _ = get_sequence(self.system) 100 | result = gates.x(*qubits) 101 | self.assertIsNone(result) 102 | 103 | result = gates.x(qubits[0], unitary=True) 104 | self.assertIsInstance(result, qutip.Qobj) 105 | 106 | def test_y(self): 107 | qubits = self.system.modes 108 | 109 | result = gates.y(*qubits, unitary=True) 110 | self.assertEqual(len(result), len(qubits)) 111 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 112 | for r, q in zip(result, qubits): 113 | self.assertEqual(r, q.Ry(np.pi)) 114 | 115 | result = gates.y(*qubits, capture=False) 116 | self.assertEqual(len(result), len(qubits)) 117 | self.assertTrue(all(isinstance(r, Operation) for r in result)) 118 | for r, q in zip(result, qubits): 119 | expected = q.rotate_y(np.pi, capture=False) 120 | for channel, info in r.terms.items(): 121 | self.assertTrue( 122 | np.array_equal(info.coeffs, expected.terms[channel].coeffs) 123 | ) 124 | 125 | _ = get_sequence(self.system) 126 | result = gates.y(*qubits) 127 | self.assertIsNone(result) 128 | 129 | result = gates.y(qubits[0], unitary=True) 130 | self.assertIsInstance(result, qutip.Qobj) 131 | 132 | def test_z(self): 133 | qubits = self.system.modes 134 | 135 | result = gates.z(*qubits, unitary=True) 136 | self.assertEqual(len(result), len(qubits)) 137 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 138 | for r, q in zip(result, qubits): 139 | self.assertEqual(r, q.Rz(np.pi)) 140 | 141 | result = gates.z(qubits[0], unitary=True) 142 | self.assertIsInstance(result, qutip.Qobj) 143 | 144 | def test_h(self): 145 | qubits = self.system.modes 146 | 147 | result = gates.h(*qubits, unitary=True) 148 | self.assertEqual(len(result), len(qubits)) 149 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 150 | for r, q in zip(result, qubits): 151 | self.assertEqual(r, q.hadamard()) 152 | 153 | result = gates.h(qubits[0], unitary=True) 154 | self.assertIsInstance(result, qutip.Qobj) 155 | 156 | def test_r(self): 157 | qubits = self.system.modes 158 | 159 | result = gates.r(np.pi / 8, np.pi / 2, *qubits, unitary=True) 160 | self.assertEqual(len(result), len(qubits)) 161 | self.assertTrue(all(isinstance(r, qutip.Qobj) for r in result)) 162 | for r, q in zip(result, qubits): 163 | self.assertEqual(r, q.Raxis(np.pi / 8, np.pi / 2)) 164 | 165 | result = gates.r(np.pi / 8, np.pi / 2, qubits[0], unitary=True) 166 | self.assertIsInstance(result, qutip.Qobj) 167 | 168 | def test_different_spaces(self): 169 | qubits = self.system.modes 170 | with self.assertRaises(ValueError): 171 | with qubits[0].use_space(qubits[0]): 172 | _ = gates.r(np.pi / 8, np.pi / 2, *qubits, unitary=True) 173 | 174 | def test_unitary_only(self): 175 | qubits = self.system.modes 176 | with self.assertRaises(ValueError): 177 | _ = gates.z(*qubits, unitary=False) 178 | 179 | def test_invalid_mode_type(self): 180 | cavities = [Cavity(f"c{i}", levels=3, kerr=-2e-3) for i in range(5)] 181 | _ = System("system", modes=cavities) 182 | with self.assertRaises(TypeError): 183 | _ = gates.x(*cavities, unitary=False) 184 | 185 | 186 | class TestTwoQubitGates(unittest.TestCase): 187 | @classmethod 188 | def setUpClass(cls): 189 | qubit1 = Transmon("q1", levels=2) 190 | qubit2 = Transmon("q0", levels=2) 191 | cls.system = System("system", modes=[qubit1, qubit2]) 192 | 193 | def test_cu(self): 194 | control, target = self.system.modes 195 | 196 | theta = np.pi / 5 197 | phi = 0.1 198 | lamda = -0.34 199 | 200 | cu = gates.CUGate(control=control, target=target)(theta, phi, lamda) 201 | 202 | c = np.cos(theta / 2) 203 | s = np.sin(theta / 2) 204 | 205 | ideal = np.array( 206 | [ 207 | [1, 0, 0, 0], 208 | [0, 1, 0, 0], 209 | [0, 0, c, -np.exp(1j * lamda) * s], 210 | [0, 0, np.exp(1j * phi) * s, np.exp(1j * (phi + lamda)) * c], 211 | ] 212 | ) 213 | self.assertTrue(np.array_equal(cu.full(), ideal)) 214 | 215 | cu = gates.cu(control, target, theta, phi, lamda) 216 | self.assertTrue(np.array_equal(cu.full(), ideal)) 217 | 218 | def test_cx(self): 219 | control, target = self.system.modes 220 | 221 | cx = gates.CXGate(control=control, target=target)() 222 | ideal = np.array( 223 | [ 224 | [1, 0, 0, 0], 225 | [0, 1, 0, 0], 226 | [0, 0, 0, 1], 227 | [0, 0, 1, 0], 228 | ] 229 | ) 230 | self.assertTrue(np.array_equal(cx.full(), ideal)) 231 | 232 | cx = gates.cx(control, target) 233 | self.assertTrue(np.array_equal(cx.full(), ideal)) 234 | 235 | def test_cy(self): 236 | control, target = self.system.modes 237 | 238 | cy = gates.CYGate(control=control, target=target)() 239 | ideal = np.array( 240 | [ 241 | [1, 0, 0, 0], 242 | [0, 1, 0, 0], 243 | [0, 0, 0, -1j], 244 | [0, 0, 1j, 0], 245 | ] 246 | ) 247 | self.assertTrue(np.array_equal(cy.full(), ideal)) 248 | 249 | cy = gates.cy(control, target) 250 | self.assertTrue(np.array_equal(cy.full(), ideal)) 251 | 252 | def test_cz(self): 253 | control, target = self.system.modes 254 | 255 | cz = gates.CZGate(control=control, target=target)() 256 | ideal = np.array( 257 | [ 258 | [1, 0, 0, 0], 259 | [0, 1, 0, 0], 260 | [0, 0, 1, 0], 261 | [0, 0, 0, -1], 262 | ] 263 | ) 264 | self.assertTrue(np.array_equal(cz.full(), ideal)) 265 | 266 | cz = gates.cz(control, target) 267 | self.assertTrue(np.array_equal(cz.full(), ideal)) 268 | 269 | def test_cphase(self): 270 | control, target = self.system.modes 271 | 272 | phi = np.pi / 4 273 | 274 | cphase = gates.CPhaseGate(control=control, target=target)(phi) 275 | ideal = np.array( 276 | [ 277 | [1, 0, 0, 0], 278 | [0, 1, 0, 0], 279 | [0, 0, 1, 0], 280 | [0, 0, 0, np.exp(1j * phi)], 281 | ] 282 | ) 283 | self.assertTrue(np.array_equal(cphase.full(), ideal)) 284 | 285 | cphase = gates.cphase(control, target, phi) 286 | self.assertTrue(np.array_equal(cphase.full(), ideal)) 287 | 288 | def test_swap(self): 289 | q1, q2 = self.system.modes 290 | 291 | swap = gates.SWAPGate(q1, q2)() 292 | ideal = np.array( 293 | [ 294 | [1, 0, 0, 0], 295 | [0, 0, 1, 0], 296 | [0, 1, 0, 0], 297 | [0, 0, 0, 1], 298 | ] 299 | ) 300 | self.assertTrue(np.array_equal(swap.full(), ideal)) 301 | 302 | swap = gates.swap(q1, q2) 303 | self.assertTrue(np.array_equal(swap.full(), ideal)) 304 | 305 | def test_swapphi(self): 306 | q1, q2 = self.system.modes 307 | phi = -np.pi / 4 308 | swapphi = gates.SWAPphiGate(q1, q2)(phi) 309 | p = -1j * np.exp(1j * phi) 310 | m = 1j * np.exp(-1j * phi) 311 | ideal = np.array( 312 | [ 313 | [1, 0, 0, 0], 314 | [0, 0, m, 0], 315 | [0, p, 0, 0], 316 | [0, 0, 0, 1], 317 | ] 318 | ) 319 | self.assertTrue(np.array_equal(swapphi.full(), ideal)) 320 | 321 | swapphi = gates.swapphi(q1, q2, phi) 322 | self.assertTrue(np.array_equal(swapphi.full(), ideal)) 323 | 324 | swapphi = gates.swapphi(q1, q2, np.pi / 2) 325 | swap = gates.swap(q1, q2) 326 | self.assertTrue(np.allclose(swapphi.full(), swap.full())) 327 | 328 | def test_iswap(self): 329 | q1, q2 = self.system.modes 330 | 331 | iswap = gates.iSWAPGate(q1, q2)() 332 | ideal = np.array( 333 | [ 334 | [1, 0, 0, 0], 335 | [0, 0, 1j, 0], 336 | [0, 1j, 0, 0], 337 | [0, 0, 0, 1], 338 | ] 339 | ) 340 | self.assertTrue(np.array_equal(iswap.full(), ideal)) 341 | 342 | iswap = gates.iswap(q1, q2) 343 | self.assertTrue(np.array_equal(iswap.full(), ideal)) 344 | 345 | def test_eswap(self): 346 | q1, q2 = self.system.modes 347 | 348 | theta_c = -np.pi / 4 349 | eswap = gates.eSWAPGate(q1, q2)(theta_c, phi=np.pi / 2) 350 | swap = np.array( 351 | [ 352 | [1, 0, 0, 0], 353 | [0, 0, 1, 0], 354 | [0, 1, 0, 0], 355 | [0, 0, 0, 1], 356 | ] 357 | ) 358 | ideal = np.cos(theta_c / 2) * np.eye(4) - 1j * np.sin(theta_c / 2) * swap 359 | self.assertTrue(np.allclose(eswap.full(), ideal)) 360 | 361 | eswap = gates.eswap(q1, q2, theta_c, phi=np.pi / 2) 362 | self.assertTrue(np.allclose(eswap.full(), ideal)) 363 | 364 | def test_sqrtswap(self): 365 | q1, q2 = self.system.modes 366 | 367 | sqrtswap = gates.SqrtSWAPGate(q1, q2)() 368 | p = (1 + 1j) / 2 369 | m = (1 - 1j) / 2 370 | ideal = np.array( 371 | [ 372 | [1, 0, 0, 0], 373 | [0, p, m, 0], 374 | [0, m, p, 0], 375 | [0, 0, 0, 1], 376 | ] 377 | ) 378 | self.assertTrue(np.allclose(sqrtswap.full(), ideal)) 379 | 380 | sqrtswap = gates.sqrtswap(q1, q2) 381 | self.assertTrue(np.allclose(sqrtswap.full(), ideal)) 382 | 383 | def test_invalid_mode_type(self): 384 | with self.assertRaises(TypeError): 385 | _ = gates.CXGate(0, 1) 386 | 387 | def test_eswap_qubit_order(self): 388 | q1, q2 = self.system.modes 389 | 390 | theta_c = np.pi / 4 391 | 392 | phi = np.pi / 4 393 | eswapq1q2 = gates.eswap(q1, q2, theta_c, phi=phi) 394 | eswapq2q1 = gates.eswap(q2, q1, theta_c, phi=np.pi - phi) 395 | self.assertEqual(eswapq1q2, eswapq2q1) 396 | 397 | def test_swapphi_qubit_order(self): 398 | q1, q2 = self.system.modes 399 | 400 | phi = -np.pi / 4 401 | q1q2 = gates.swapphi(q1, q2, phi) 402 | q2q1 = gates.swapphi(q2, q1, np.pi - phi) 403 | self.assertEqual(q1q2, q2q1) 404 | 405 | 406 | if __name__ == "__main__": 407 | unittest.main() 408 | -------------------------------------------------------------------------------- /sequencing/test/test_modes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import qutip 4 | 5 | from sequencing import Mode, Transmon, Cavity, System, get_sequence 6 | from sequencing.calibration import tune_rabi, tune_displacement 7 | 8 | 9 | class TestMode(unittest.TestCase): 10 | def test_basis(self): 11 | mode0 = Mode("mode0", levels=5) 12 | state = mode0.basis(0, full_space=False) 13 | self.assertEqual([[mode0.levels], [1]], state.dims) 14 | 15 | mode1 = Mode("mode1", levels=10) 16 | mode0.space = mode1.space = [mode1, mode0] 17 | state = mode0.basis(0, full_space=False) 18 | self.assertEqual([[mode0.levels], [1]], state.dims) 19 | 20 | state = mode0.basis(0, full_space=True) 21 | self.assertEqual([[mode1.levels, mode0.levels], [1, 1]], state.dims) 22 | 23 | def test_order_modes(self): 24 | mode0 = Mode("mode0", levels=5) 25 | mode1 = Mode("mode1", levels=10) 26 | 27 | Mode.order_modes = True 28 | mode0.space = [mode1, mode0] 29 | self.assertEqual(mode0.space, [mode1, mode0]) 30 | 31 | mode0.space = [mode0, mode1] 32 | self.assertEqual(mode0.space, [mode1, mode0]) 33 | 34 | Mode.order_modes = False 35 | mode0.space = [mode1, mode0] 36 | self.assertEqual(mode0.space, [mode1, mode0]) 37 | 38 | mode0.space = [mode0, mode1] 39 | self.assertEqual(mode0.space, [mode0, mode1]) 40 | 41 | Mode.order_modes = True 42 | 43 | def test_use_space(self): 44 | mode0 = Mode("mode0", levels=5) 45 | mode1 = Mode("mode1", levels=10) 46 | 47 | mode0.space = [mode1, mode0] 48 | with mode0.use_space([mode0]): 49 | self.assertEqual(mode0.space, [mode0]) 50 | self.assertEqual(mode0.space, [mode1, mode0]) 51 | 52 | def test_no_loss(self): 53 | mode = Mode("mode", levels=5) 54 | t1 = 100e3 55 | t2 = 200e3 56 | thermal_population = 0.05 57 | mode.t1 = t1 58 | mode.t2 = t2 59 | mode.thermal_population = thermal_population 60 | 61 | with mode.no_loss(): 62 | self.assertTrue(np.isinf(mode.t1)) 63 | self.assertTrue(np.isinf(mode.t2)) 64 | self.assertEqual(mode.thermal_population, 0) 65 | 66 | self.assertEqual(mode.t1, t1) 67 | self.assertEqual(mode.t2, t2) 68 | self.assertEqual(mode.thermal_population, thermal_population) 69 | 70 | def test_operator_expr(self): 71 | mode = Mode("mode", levels=2) 72 | self.assertEqual(mode.operator_expr("a.dag() * a"), mode.n) 73 | self.assertEqual(mode.operator_expr("Rx(pi/2)"), mode.Rx(np.pi / 2)) 74 | self.assertEqual(mode.operator_expr("Rx(pi/2)**2"), mode.Rx(np.pi)) 75 | 76 | 77 | class TestTransmon(unittest.TestCase): 78 | @classmethod 79 | def tearDownClass(cls): 80 | import matplotlib.pyplot as plt 81 | 82 | plt.close("all") 83 | 84 | def test_paulis(self): 85 | q0 = Transmon("q0", levels=2) 86 | q1 = Transmon("q1", levels=3, kerr=-200e-3) 87 | 88 | q0_0 = q0.basis(0, full_space=False) 89 | q0_1 = q0.basis(1, full_space=False) 90 | q1_0 = q1.basis(0, full_space=False) 91 | q1_1 = q1.basis(1, full_space=False) 92 | q0_I = q0.I 93 | q1_I = q1.I 94 | 95 | single_mode_paulis = { 96 | "X0": q0_0 * q0_1.dag() + q0_1 * q0_0.dag(), 97 | "X1": q1_0 * q1_1.dag() + q1_1 * q1_0.dag(), 98 | "Y0": -1j * (q0_0 * q0_1.dag() - q0_1 * q0_0.dag()), 99 | "Y1": -1j * (q1_0 * q1_1.dag() - q1_1 * q1_0.dag()), 100 | "Z0": q0_0 * q0_0.dag() - q0_1 * q0_1.dag(), 101 | "Z1": q1_0 * q1_0.dag() - q1_1 * q1_1.dag(), 102 | } 103 | 104 | q0.space = q1.space = [q1, q0] 105 | self.assertEqual(q0.sigmax(full_space=False), single_mode_paulis["X0"]) 106 | self.assertEqual(q0.sigmay(full_space=False), single_mode_paulis["Y0"]) 107 | self.assertEqual(q0.sigmaz(full_space=False), single_mode_paulis["Z0"]) 108 | 109 | self.assertEqual(q1.sigmax(full_space=False), single_mode_paulis["X1"]) 110 | self.assertEqual(q1.sigmay(full_space=False), single_mode_paulis["Y1"]) 111 | self.assertEqual(q1.sigmaz(full_space=False), single_mode_paulis["Z1"]) 112 | 113 | self.assertEqual( 114 | q0.sigmax(full_space=True), qutip.tensor(q1_I, single_mode_paulis["X0"]) 115 | ) 116 | self.assertEqual( 117 | q0.sigmay(full_space=True), qutip.tensor(q1_I, single_mode_paulis["Y0"]) 118 | ) 119 | self.assertEqual( 120 | q0.sigmaz(full_space=True), qutip.tensor(q1_I, single_mode_paulis["Z0"]) 121 | ) 122 | 123 | self.assertEqual( 124 | q1.sigmax(full_space=True), qutip.tensor(single_mode_paulis["X1"], q0_I) 125 | ) 126 | self.assertEqual( 127 | q1.sigmay(full_space=True), qutip.tensor(single_mode_paulis["Y1"], q0_I) 128 | ) 129 | self.assertEqual( 130 | q1.sigmaz(full_space=True), qutip.tensor(single_mode_paulis["Z1"], q0_I) 131 | ) 132 | 133 | def test_Rphi(self): 134 | q0 = Transmon("q0", levels=2) 135 | q1 = Transmon("q1", levels=3, kerr=-200e-3) 136 | q0_I = q0.I 137 | q1_I = q1.I 138 | q0_n = q0.n 139 | q1_n = q1.n 140 | 141 | q0.space = q1.space = [q1, q0] 142 | 143 | self.assertEqual(q0.Rphi(np.pi, full_space=False), (1j * np.pi * q0_n).expm()) 144 | self.assertEqual(q1.Rphi(np.pi, full_space=False), (1j * np.pi * q1_n).expm()) 145 | 146 | self.assertEqual( 147 | q0.Rphi(np.pi, full_space=True), 148 | qutip.tensor(q1_I, (1j * np.pi * q0_n).expm()), 149 | ) 150 | self.assertEqual(q0.Rphi(np.pi, full_space=True), (1j * np.pi * q0.n).expm()) 151 | self.assertEqual( 152 | q1.Rphi(np.pi, full_space=True), 153 | qutip.tensor((1j * np.pi * q1_n).expm(), q0_I), 154 | ) 155 | self.assertEqual(q1.Rphi(np.pi, full_space=True), (1j * np.pi * q1.n).expm()) 156 | 157 | def test_rotations_unitary(self): 158 | q0 = Transmon("q0", levels=2) 159 | q1 = Transmon("q1", levels=3) 160 | 161 | for full_space in [True, False]: 162 | self.assertEqual( 163 | q0.rotate_x(np.pi / 2, unitary=True, full_space=full_space), 164 | q0.Rx(np.pi / 2, full_space=full_space), 165 | ) 166 | self.assertEqual( 167 | q0.rotate_y(np.pi / 2, unitary=True, full_space=full_space), 168 | q0.Ry(np.pi / 2, full_space=full_space), 169 | ) 170 | self.assertEqual( 171 | q1.rotate_x(np.pi / 2, unitary=True, full_space=full_space), 172 | q1.Rx(np.pi / 2, full_space=full_space), 173 | ) 174 | self.assertEqual( 175 | q1.rotate_y(np.pi / 2, unitary=True, full_space=full_space), 176 | q1.Ry(np.pi / 2, full_space=full_space), 177 | ) 178 | 179 | q0.space = q1.space = [q1, q0] 180 | 181 | for full_space in [True, False]: 182 | self.assertEqual( 183 | q0.rotate_x(np.pi / 2, unitary=True, full_space=full_space), 184 | q0.Rx(np.pi / 2, full_space=full_space), 185 | ) 186 | self.assertEqual( 187 | q0.rotate_y(np.pi / 2, unitary=True, full_space=full_space), 188 | q0.Ry(np.pi / 2, full_space=full_space), 189 | ) 190 | self.assertEqual( 191 | q1.rotate_x(np.pi / 2, unitary=True, full_space=full_space), 192 | q1.Rx(np.pi / 2, full_space=full_space), 193 | ) 194 | self.assertEqual( 195 | q1.rotate_y(np.pi / 2, unitary=True, full_space=full_space), 196 | q1.Ry(np.pi / 2, full_space=full_space), 197 | ) 198 | 199 | def test_rotations_pulse(self): 200 | q0 = Transmon("q0", levels=2) 201 | q1 = Transmon("q1", levels=3, kerr=-200e-3) 202 | q0.gaussian_pulse.sigma = 40 203 | q1.gaussian_pulse.sigma = 40 204 | system = System("system", modes=[q0, q1]) 205 | init_state = system.fock() 206 | dt = 0.5 207 | system.dt = dt 208 | self.assertEqual(q0.dt, dt) 209 | self.assertEqual(q1.dt, dt) 210 | 211 | for qubit in [q0, q1]: 212 | for _ in range(1): 213 | _ = tune_rabi( 214 | system, init_state=init_state, mode_name=qubit.name, verify=False 215 | ) 216 | 217 | angles = np.linspace(-np.pi, np.pi, 5) 218 | for angle in angles: 219 | for qubit in [q0, q1]: 220 | seq = get_sequence(system) 221 | qubit.rotate_x(angle) 222 | unitary = qubit.rotate_x(angle, unitary=True) 223 | result = seq.run(init_state) 224 | fidelity = qutip.fidelity(result.states[-1], unitary * init_state) ** 2 225 | self.assertGreater(fidelity, 1 - 1e-2) 226 | 227 | seq = get_sequence(system) 228 | qubit.rotate_y(angle) 229 | unitary = qubit.rotate_y(angle, unitary=True) 230 | result = seq.run(init_state) 231 | fidelity = qutip.fidelity(result.states[-1], unitary * init_state) ** 2 232 | self.assertGreater(fidelity, 1 - 1e-2) 233 | 234 | 235 | class TestCavity(unittest.TestCase): 236 | @classmethod 237 | def tearDownClass(cls): 238 | import matplotlib.pyplot as plt 239 | 240 | plt.close("all") 241 | 242 | def test_paulis_fock(self): 243 | q0 = Cavity("q0", levels=6) 244 | q1 = Cavity("q1", levels=10) 245 | 246 | q0_0 = q0.basis(0, full_space=False) 247 | q0_1 = q0.basis(1, full_space=False) 248 | q1_0 = q1.basis(0, full_space=False) 249 | q1_1 = q1.basis(1, full_space=False) 250 | q0_I = q0.I 251 | q1_I = q1.I 252 | 253 | single_mode_paulis = { 254 | "X0": q0_0 * q0_1.dag() + q0_1 * q0_0.dag(), 255 | "X1": q1_0 * q1_1.dag() + q1_1 * q1_0.dag(), 256 | "Y0": -1j * (q0_0 * q0_1.dag() - q0_1 * q0_0.dag()), 257 | "Y1": -1j * (q1_0 * q1_1.dag() - q1_1 * q1_0.dag()), 258 | "Z0": q0_0 * q0_0.dag() - q0_1 * q0_1.dag(), 259 | "Z1": q1_0 * q1_0.dag() - q1_1 * q1_1.dag(), 260 | } 261 | 262 | q0.space = q1.space = [q1, q0] 263 | self.assertEqual(q0.sigmax(full_space=False), single_mode_paulis["X0"]) 264 | self.assertEqual(q0.sigmay(full_space=False), single_mode_paulis["Y0"]) 265 | self.assertEqual(q0.sigmaz(full_space=False), single_mode_paulis["Z0"]) 266 | 267 | self.assertEqual(q1.sigmax(full_space=False), single_mode_paulis["X1"]) 268 | self.assertEqual(q1.sigmay(full_space=False), single_mode_paulis["Y1"]) 269 | self.assertEqual(q1.sigmaz(full_space=False), single_mode_paulis["Z1"]) 270 | 271 | self.assertEqual( 272 | q0.sigmax(full_space=True), qutip.tensor(q1_I, single_mode_paulis["X0"]) 273 | ) 274 | self.assertEqual( 275 | q0.sigmay(full_space=True), qutip.tensor(q1_I, single_mode_paulis["Y0"]) 276 | ) 277 | self.assertEqual( 278 | q0.sigmaz(full_space=True), qutip.tensor(q1_I, single_mode_paulis["Z0"]) 279 | ) 280 | 281 | self.assertEqual( 282 | q1.sigmax(full_space=True), qutip.tensor(single_mode_paulis["X1"], q0_I) 283 | ) 284 | self.assertEqual( 285 | q1.sigmay(full_space=True), qutip.tensor(single_mode_paulis["Y1"], q0_I) 286 | ) 287 | self.assertEqual( 288 | q1.sigmaz(full_space=True), qutip.tensor(single_mode_paulis["Z1"], q0_I) 289 | ) 290 | 291 | def test_paulis_custom_logical_states(self): 292 | c0 = Cavity("c0", levels=6) 293 | c1 = Cavity("c1", levels=10) 294 | 295 | c0.set_logical_states(c0.basis(0), c0.basis(1)) 296 | c1.set_logical_states(c1.basis(2), c1.basis(3)) 297 | 298 | c0_0 = c0.logical_zero(full_space=False) 299 | c0_1 = c0.logical_one(full_space=False) 300 | c1_0 = c1.logical_zero(full_space=False) 301 | c1_1 = c1.logical_one(full_space=False) 302 | c0_I = c0.I 303 | c1_I = c1.I 304 | 305 | single_mode_paulis = { 306 | "X0": c0_0 * c0_1.dag() + c0_1 * c0_0.dag(), 307 | "X1": c1_0 * c1_1.dag() + c1_1 * c1_0.dag(), 308 | "Y0": -1j * (c0_0 * c0_1.dag() - c0_1 * c0_0.dag()), 309 | "Y1": -1j * (c1_0 * c1_1.dag() - c1_1 * c1_0.dag()), 310 | "Z0": c0_0 * c0_0.dag() - c0_1 * c0_1.dag(), 311 | "Z1": c1_0 * c1_0.dag() - c1_1 * c1_1.dag(), 312 | } 313 | 314 | c0.space = c1.space = [c1, c0] 315 | self.assertEqual(c0.sigmax(full_space=False), single_mode_paulis["X0"]) 316 | self.assertEqual(c0.sigmay(full_space=False), single_mode_paulis["Y0"]) 317 | self.assertEqual(c0.sigmaz(full_space=False), single_mode_paulis["Z0"]) 318 | 319 | self.assertEqual(c1.sigmax(full_space=False), single_mode_paulis["X1"]) 320 | self.assertEqual(c1.sigmay(full_space=False), single_mode_paulis["Y1"]) 321 | self.assertEqual(c1.sigmaz(full_space=False), single_mode_paulis["Z1"]) 322 | 323 | self.assertEqual( 324 | c0.sigmax(full_space=True), qutip.tensor(c1_I, single_mode_paulis["X0"]) 325 | ) 326 | self.assertEqual( 327 | c0.sigmay(full_space=True), qutip.tensor(c1_I, single_mode_paulis["Y0"]) 328 | ) 329 | self.assertEqual( 330 | c0.sigmaz(full_space=True), qutip.tensor(c1_I, single_mode_paulis["Z0"]) 331 | ) 332 | 333 | self.assertEqual( 334 | c1.sigmax(full_space=True), qutip.tensor(single_mode_paulis["X1"], c0_I) 335 | ) 336 | self.assertEqual( 337 | c1.sigmay(full_space=True), qutip.tensor(single_mode_paulis["Y1"], c0_I) 338 | ) 339 | self.assertEqual( 340 | c1.sigmaz(full_space=True), qutip.tensor(single_mode_paulis["Z1"], c0_I) 341 | ) 342 | 343 | def test_invalid_logical_state(self): 344 | c0 = Cavity("c0", levels=6) 345 | c1 = Cavity("c1", levels=10) 346 | 347 | c0.set_logical_states(c0.basis(0), c0.basis(1)) 348 | c1.set_logical_states(c1.basis(2), c1.basis(3)) 349 | 350 | c0.space = c1.space = [c1, c0] 351 | 352 | with self.assertRaises(ValueError): 353 | c0.set_logical_states(c0.basis(0), c0.basis(1)) 354 | 355 | with self.assertRaises(ValueError): 356 | c1.set_logical_states(c1.basis(2), c1.basis(3)) 357 | 358 | def test_set_logical_states_none(self): 359 | c0 = Cavity("c0", levels=6) 360 | c1 = Cavity("c1", levels=10) 361 | c0.set_logical_states(None, None) 362 | c1.set_logical_states(None, None) 363 | 364 | self.assertEqual(c0.logical_zero(), c0.basis(0)) 365 | self.assertEqual(c0.logical_one(), c0.basis(1)) 366 | 367 | self.assertEqual(c1.logical_zero(), c1.basis(0)) 368 | self.assertEqual(c1.logical_one(), c1.basis(1)) 369 | 370 | def test_displacement(self): 371 | c0 = Cavity("c0", levels=10, kerr=-10e-6) 372 | c1 = Cavity("c1", levels=12, kerr=-10e-6) 373 | system = System("system", modes=[c0, c1]) 374 | dt = 2 375 | system.dt = dt 376 | self.assertEqual(c0.dt, dt) 377 | self.assertEqual(c1.dt, dt) 378 | init_state = system.fock() 379 | for cavity in [c0, c1]: 380 | for _ in range(1): 381 | _ = tune_displacement( 382 | system, init_state, mode_name=cavity.name, verify=False 383 | ) 384 | for alpha in [0, 1, -1, 1j, -1j, 2, -2, 2j, -2j]: 385 | ideal_state = cavity.tensor_with_zero( 386 | qutip.coherent(cavity.levels, alpha) 387 | ) 388 | seq = get_sequence(system) 389 | unitary = cavity.displace(alpha, unitary=True) 390 | cavity.displace(alpha) 391 | result = seq.run(init_state) 392 | unitary_fidelity = ( 393 | qutip.fidelity(unitary * init_state, ideal_state) ** 2 394 | ) 395 | pulse_fidelity = qutip.fidelity(result.states[-1], ideal_state) ** 2 396 | self.assertGreater(unitary_fidelity, 1 - 1e-4) 397 | self.assertGreater(pulse_fidelity, 1 - 1e-4) 398 | 399 | 400 | if __name__ == "__main__": 401 | unittest.main() 402 | -------------------------------------------------------------------------------- /sequencing/test/test_parameters.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import tempfile 4 | 5 | import numpy as np 6 | import attr 7 | 8 | from sequencing.parameters import ( 9 | Parameterized, 10 | BoolParameter, 11 | StringParameter, 12 | IntParameter, 13 | FloatParameter, 14 | NanosecondParameter, 15 | GigahertzParameter, 16 | RadianParameter, 17 | ) 18 | 19 | 20 | @attr.s 21 | class Engine(Parameterized): 22 | cylinders = IntParameter(4) 23 | displacement = FloatParameter(2, unit="liter") 24 | current_rpm = FloatParameter(0, unit="rpm") 25 | turbo_charged = BoolParameter(False) 26 | 27 | 28 | @attr.s 29 | class Transmission(Parameterized): 30 | manual = BoolParameter(False) 31 | num_gears = IntParameter(5) 32 | current_gear = IntParameter(1) 33 | 34 | def initialize(self): 35 | super().initialize() 36 | # Add private attributes in initialize() 37 | self._is_broken = True 38 | 39 | @property 40 | def has_clutch(self): 41 | return self.manual 42 | 43 | def shift_to(self, gear): 44 | if gear not in range(self.num_gears + 1): 45 | # 0 is reverse 46 | raise ValueError(f"Cannot shift into gear {gear}") 47 | if abs(gear - self.current_gear) > 1: 48 | raise ValueError("Cannot skip gears") 49 | self.current_gear = gear 50 | 51 | 52 | @attr.s 53 | class Car(Parameterized): 54 | VALID_CHASSIS = ["sedan", "coupe", "hatchback", "suv"] 55 | chassis = StringParameter("sedan", validator=attr.validators.in_(VALID_CHASSIS)) 56 | num_doors = IntParameter(4, validator=attr.validators.in_([2, 4])) 57 | miles_per_gallon = FloatParameter(30, unit="mpg") 58 | engine = attr.ib(factory=lambda: Engine("engine")) 59 | transmission = attr.ib(factory=lambda: Transmission("transmission")) 60 | 61 | 62 | class TestSerialization(unittest.TestCase): 63 | def test_to_from_dict(self): 64 | car = Car("test") 65 | other_car = Car.from_dict(car.as_dict()) 66 | self.assertEqual(car, other_car) 67 | 68 | def test_to_from_json_str(self): 69 | car = Car("test") 70 | json_str = car.to_json(dumps=True) 71 | other_car = Car.from_json(json_str=json_str) 72 | self.assertEqual(car, other_car) 73 | 74 | def test_to_from_json_file(self): 75 | with tempfile.TemporaryDirectory() as dirname: 76 | json_path = os.path.join(dirname, "__test_to_from_json_file.json") 77 | car = Car("test") 78 | car.to_json(json_path=json_path) 79 | other_car = Car.from_json(json_path=json_path) 80 | self.assertEqual(car, other_car) 81 | 82 | 83 | class TestParameters(unittest.TestCase): 84 | def test_string_parameter(self): 85 | @attr.s 86 | class Test(object): 87 | param = StringParameter("") 88 | 89 | inst = Test(param=1.0) 90 | self.assertEqual(inst.param, str(1.0)) 91 | 92 | def test_bool_parameter(self): 93 | @attr.s 94 | class Test(object): 95 | param = BoolParameter(True) 96 | 97 | inst = Test(param="") 98 | self.assertFalse(inst.param) 99 | 100 | def test_int_parameter(self): 101 | @attr.s 102 | class Test(object): 103 | param = IntParameter(0) 104 | 105 | inst = Test(param=1.0) 106 | self.assertIsInstance(inst.param, int) 107 | 108 | def test_float_parameter(self): 109 | @attr.s 110 | class Test(object): 111 | param = FloatParameter(0) 112 | 113 | inst = Test(param="inf") 114 | self.assertIsInstance(inst.param, float) 115 | self.assertTrue(np.isinf(inst.param)) 116 | 117 | def test_nanosecond_parameter(self): 118 | @attr.s 119 | class Test(object): 120 | param = NanosecondParameter(20, base=FloatParameter) 121 | 122 | inst = Test(param="inf") 123 | self.assertIsInstance(inst.param, float) 124 | self.assertTrue(np.isinf(inst.param)) 125 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "ns") 126 | 127 | @attr.s 128 | class Test(object): 129 | param = NanosecondParameter(0.5, base=IntParameter) 130 | 131 | inst = Test() 132 | self.assertIsInstance(inst.param, int) 133 | self.assertEqual(inst.param, int(0.5)) 134 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "ns") 135 | 136 | def test_gigahertz_parameter(self): 137 | @attr.s 138 | class Test(object): 139 | param = GigahertzParameter(20, base=FloatParameter) 140 | 141 | inst = Test(param="inf") 142 | self.assertIsInstance(inst.param, float) 143 | self.assertTrue(np.isinf(inst.param)) 144 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "GHz") 145 | 146 | @attr.s 147 | class Test(object): 148 | param = GigahertzParameter(0.5, base=IntParameter) 149 | 150 | inst = Test() 151 | self.assertIsInstance(inst.param, int) 152 | self.assertEqual(inst.param, int(0.5)) 153 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "GHz") 154 | 155 | def test_radian_parameter(self): 156 | @attr.s 157 | class Test(object): 158 | param = RadianParameter(np.pi, base=FloatParameter) 159 | 160 | inst = Test(param="0") 161 | self.assertIsInstance(inst.param, float) 162 | self.assertEqual(inst.param, 0) 163 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "radian") 164 | 165 | @attr.s 166 | class Test(object): 167 | param = RadianParameter(0.5, base=IntParameter) 168 | 169 | inst = Test() 170 | self.assertIsInstance(inst.param, int) 171 | self.assertEqual(inst.param, int(0.5)) 172 | self.assertEqual(attr.fields(Test).param.metadata["unit"], "radian") 173 | 174 | 175 | if __name__ == "__main__": 176 | unittest.main() 177 | -------------------------------------------------------------------------------- /sequencing/test/test_pulses.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from sequencing.pulses import ( 6 | Pulse, 7 | ConstantPulse, 8 | SmoothedConstantPulse, 9 | GaussianPulse, 10 | pulse_factory, 11 | ) 12 | 13 | 14 | class TestPulseFactory(unittest.TestCase): 15 | def test_pulse_factory(self): 16 | 17 | factory = pulse_factory(cls=ConstantPulse) 18 | pulse = factory() 19 | self.assertIsInstance(pulse, ConstantPulse) 20 | self.assertEqual(pulse.name, "constant_pulse") 21 | 22 | factory = pulse_factory(cls=Pulse) 23 | pulse = factory() 24 | self.assertIsInstance(pulse, Pulse) 25 | self.assertEqual(pulse.name, "pulse") 26 | 27 | factory = pulse_factory(cls=GaussianPulse, name="customPulse") 28 | pulse = factory() 29 | self.assertIsInstance(pulse, GaussianPulse) 30 | self.assertEqual(pulse.name, "customPulse") 31 | 32 | 33 | class TestPulses(unittest.TestCase): 34 | @classmethod 35 | def tearDownClass(cls): 36 | plt.close("all") 37 | 38 | def test_call(self): 39 | pulse = Pulse("pulse") 40 | result = pulse(length=100) 41 | self.assertIsInstance(result, np.ndarray) 42 | self.assertTrue(np.iscomplexobj(result)) 43 | self.assertEqual(result.size, 100) 44 | 45 | def test_shape(self): 46 | pulse = SmoothedConstantPulse("smooth") 47 | 48 | pulse.sigma = 0 49 | result = pulse(length=100) 50 | self.assertIsInstance(result, np.ndarray) 51 | self.assertTrue(np.iscomplexobj(result)) 52 | self.assertEqual(result.size, 100) 53 | 54 | pulse.sigma = 10 55 | pulse.shape = "tanh" 56 | result = pulse(length=100) 57 | self.assertIsInstance(result, np.ndarray) 58 | self.assertTrue(np.iscomplexobj(result)) 59 | self.assertEqual(result.size, 100) 60 | 61 | pulse.sigma = 10 62 | pulse.shape = "cos" 63 | result = pulse(length=100) 64 | self.assertIsInstance(result, np.ndarray) 65 | self.assertTrue(np.iscomplexobj(result)) 66 | self.assertEqual(result.size, 100) 67 | 68 | pulse.sigma = 10 69 | pulse.shape = "other" 70 | with self.assertRaises(ValueError): 71 | result = pulse(length=100) 72 | 73 | def test_plot(self): 74 | pulse = GaussianPulse("gauss") 75 | 76 | fig, ax = plt.subplots() 77 | result = pulse.plot(ax=ax) 78 | self.assertIs(result, ax) 79 | 80 | result = pulse.plot() 81 | self.assertIsInstance(result, type(ax)) 82 | 83 | 84 | if __name__ == "__main__": 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /sequencing/test/test_rotations.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import qutip 4 | from sequencing import Transmon, System, Sequence, sync 5 | from sequencing.calibration import tune_rabi 6 | 7 | 8 | class TestSequenceVsUnitary(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(cls): 11 | qubit = Transmon("qubit", levels=2) 12 | system = System("system", modes=[qubit]) 13 | _ = tune_rabi(system, system.ground_state(), plot=False, verify=False) 14 | cls.system = system 15 | 16 | def test_sequence_Rx(self): 17 | 18 | system = self.system 19 | qubit = system.qubit 20 | 21 | init_state = system.ground_state() 22 | 23 | angles = np.linspace(-np.pi, np.pi, 11) 24 | for theta in angles: 25 | seq = Sequence(system) 26 | qubit.rotate_x(theta) 27 | sync() 28 | unitary = qubit.rotate_x(theta, unitary=True) 29 | 30 | result = seq.run(init_state) 31 | states = result.states 32 | fidelity = qutip.fidelity(states[-1], qubit.Rx(theta) * init_state) ** 2 33 | self.assertGreater(fidelity, 0.999) 34 | fidelity = qutip.fidelity(states[-1], unitary * init_state) ** 2 35 | self.assertGreater(fidelity, 0.999) 36 | 37 | def test_sequence_Ry(self): 38 | 39 | system = self.system 40 | qubit = system.qubit 41 | 42 | init_state = system.ground_state() 43 | 44 | angles = np.linspace(-np.pi, np.pi, 11) 45 | for theta in angles: 46 | seq = Sequence(system) 47 | qubit.rotate_y(theta) 48 | sync() 49 | unitary = qubit.rotate_y(theta, unitary=True) 50 | 51 | result = seq.run(init_state) 52 | states = result.states 53 | fidelity = qutip.fidelity(states[-1], qubit.Ry(theta) * init_state) ** 2 54 | self.assertGreater(fidelity, 0.999) 55 | fidelity = qutip.fidelity(states[-1], unitary * init_state) ** 2 56 | self.assertGreater(fidelity, 0.999) 57 | 58 | def test_sequence_Raxis(self): 59 | 60 | system = self.system 61 | qubit = system.qubit 62 | 63 | init_state = system.ground_state() 64 | 65 | angles = np.linspace(-np.pi, np.pi, 11) 66 | for theta in angles: 67 | for phi in angles: 68 | seq = Sequence(system) 69 | qubit.rotate(theta, phi) 70 | sync() 71 | unitary = qubit.rotate(theta, phi, unitary=True) 72 | 73 | result = seq.run(init_state) 74 | states = result.states 75 | fidelity = ( 76 | qutip.fidelity(states[-1], qubit.Raxis(theta, phi) * init_state) 77 | ** 2 78 | ) 79 | self.assertGreater(fidelity, 0.999) 80 | fidelity = qutip.fidelity(states[-1], unitary * init_state) ** 2 81 | self.assertGreater(fidelity, 0.999) 82 | 83 | def test_sequence_Raxis_vs_Rx_Ry(self): 84 | 85 | system = self.system 86 | qubit = system.qubit 87 | 88 | init_state = system.ground_state() 89 | 90 | angles = np.linspace(-np.pi, np.pi, 11) 91 | for theta in angles: 92 | # Test Raxis vs. Rx 93 | seq = Sequence(system) 94 | qubit.rotate(theta, 0) 95 | sync() 96 | unitary = qubit.rotate(theta, 0, unitary=True) 97 | 98 | result = seq.run(init_state) 99 | states = result.states 100 | fidelity = qutip.fidelity(states[-1], qubit.Rx(theta) * init_state) ** 2 101 | self.assertGreater(fidelity, 0.999) 102 | 103 | fidelity = ( 104 | qutip.fidelity(unitary * init_state, qubit.Rx(theta) * init_state) ** 2 105 | ) 106 | self.assertGreater(fidelity, 0.999) 107 | 108 | # Test Raxis vs. Ry 109 | seq = Sequence(system) 110 | qubit.rotate(theta, np.pi / 2) 111 | sync() 112 | unitary = qubit.rotate(theta, np.pi / 2, unitary=True) 113 | 114 | result = seq.run(init_state) 115 | states = result.states 116 | fidelity = qutip.fidelity(states[-1], qubit.Ry(theta) * init_state) ** 2 117 | self.assertGreater(fidelity, 0.999) 118 | 119 | fidelity = ( 120 | qutip.fidelity(unitary * init_state, qubit.Ry(theta) * init_state) ** 2 121 | ) 122 | self.assertGreater(fidelity, 0.999) 123 | 124 | 125 | if __name__ == "__main__": 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /sequencing/test/test_sequencing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import qutip 4 | from sequencing import ( 5 | Transmon, 6 | Cavity, 7 | System, 8 | Sequence, 9 | CTerm, 10 | Operation, 11 | capture_operation, 12 | get_sequence, 13 | sync, 14 | delay, 15 | delay_channels, 16 | ket2dm, 17 | ops2dms, 18 | ) 19 | from sequencing.sequencing import ( 20 | ValidatedList, 21 | CompiledPulseSequence, 22 | PulseSequence, 23 | SyncOperation, 24 | HamiltonianChannels, 25 | ) 26 | 27 | 28 | class TestFunctions(unittest.TestCase): 29 | def test_ket2dm(self): 30 | ket = qutip.fock(5, 0) 31 | dm = ket2dm(ket) 32 | self.assertEqual(dm, qutip.fock_dm(5, 0)) 33 | 34 | dm0 = qutip.fock_dm(3, 0) 35 | dm = ket2dm(dm0) 36 | self.assertEqual(dm, dm0) 37 | 38 | def test_ops2dms(self): 39 | ops = [qutip.fock(5, n) for n in range(5)] 40 | dms = ops2dms(ops) 41 | for n, dm in enumerate(dms): 42 | self.assertEqual(dm, qutip.fock_dm(5, n)) 43 | 44 | ops = [qutip.fock_dm(5, n) for n in range(5)] 45 | dms = ops2dms(ops) 46 | for n, dm in enumerate(dms): 47 | self.assertEqual(dm, qutip.fock_dm(5, n)) 48 | 49 | 50 | class TestValidatedList(unittest.TestCase): 51 | def test_valid_types(self): 52 | class Test(ValidatedList): 53 | VALID_TYPES = (int, float, complex) 54 | 55 | inst = Test(iterable=[1, 1j, 0.5]) 56 | 57 | with self.assertRaises(TypeError): 58 | inst.append("a string") 59 | 60 | with self.assertRaises(TypeError): 61 | inst._validate([1, 2, 3]) 62 | 63 | inst = Test() 64 | 65 | with self.assertRaises(TypeError): 66 | inst.append("a string") 67 | 68 | with self.assertRaises(TypeError): 69 | inst._validate([1, 2, 3]) 70 | 71 | inst.extend([0, float("inf"), -1j]) 72 | 73 | self.assertEqual(inst.pop(), -1j) 74 | 75 | 76 | class TestHamiltonianChannels(unittest.TestCase): 77 | def test_add_channel_operation(self): 78 | hc = HamiltonianChannels() 79 | with self.assertRaises(ValueError): 80 | hc.add_channel("H0") 81 | 82 | hc.add_channel("H0", H=qutip.qeye(3), time_dependent=False) 83 | with self.assertRaises(ValueError): 84 | hc.add_channel( 85 | "H0", H=qutip.qeye(3), time_dependent=False, error_if_exists=True 86 | ) 87 | 88 | with self.assertRaises(ValueError): 89 | hc.add_operation("H0", t0=0, duration=100, H=None, C_op=None) 90 | 91 | with self.assertRaises(ValueError): 92 | # ValueError because H0 was defined to be 93 | # time-independent 94 | hc.add_operation("H0", t0=0, duration=100, H=qutip.qeye(3)) 95 | 96 | with self.assertRaises(ValueError): 97 | hc.add_operation("H1", H=qutip.qeye(3)) 98 | 99 | with self.assertRaises(ValueError): 100 | hc.delay_channels("H3", 5) 101 | 102 | hc.add_operation("H2", H=qutip.qeye(3), t0=0, duration=10) 103 | 104 | hc.delay_channels("H1", 5) 105 | hc.add_operation("H1", t0=0, duration=10) 106 | 107 | self.assertEqual(hc.channels["H1"]["delay"], 5) 108 | self.assertEqual(hc.channels["H2"]["delay"], 0) 109 | 110 | H, C_ops, times = hc.build_hamiltonian() 111 | self.assertEqual(len(H), 3) 112 | self.assertEqual(C_ops, []) 113 | self.assertEqual(times.size, 10 + 5) 114 | 115 | fig, ax = hc.plot_coefficients(subplots=True) 116 | self.assertIsInstance(ax, np.ndarray) 117 | 118 | fig, ax = hc.plot_coefficients(subplots=False) 119 | self.assertNotIsInstance(ax, np.ndarray) 120 | 121 | 122 | class TestPulseSequence(unittest.TestCase): 123 | def test_pulse_sequence(self): 124 | qubit = Transmon("qubit", levels=2) 125 | pulse = qubit.gaussian_pulse 126 | pulse_len = pulse.sigma * pulse.chop 127 | system = System("system", modes=[qubit]) 128 | 129 | seq = PulseSequence(system=system) 130 | seq.append(qubit.rotate_x(np.pi, capture=False)) 131 | seq.append(SyncOperation()) 132 | seq.append(qubit.rotate_x(np.pi, capture=False)) 133 | self.assertIsInstance(seq.compile(), CompiledPulseSequence) 134 | self.assertEqual(seq.compile().hc.times.size, 2 * pulse_len) 135 | result = seq.run(qubit.fock(0)) 136 | self.assertIsInstance(result, qutip.solver.Result) 137 | 138 | def test_pulse_sequence_propagator(self): 139 | qubit = Transmon("qubit", levels=2) 140 | pulse = qubit.gaussian_pulse 141 | pulse_len = pulse.sigma * pulse.chop 142 | system = System("system", modes=[qubit]) 143 | 144 | seq = PulseSequence(system=system) 145 | seq.append(qubit.rotate_x(np.pi, capture=False)) 146 | seq.append(SyncOperation()) 147 | seq.append(qubit.rotate_x(np.pi, capture=False)) 148 | seq.append(SyncOperation()) 149 | seq.append(qubit.rotate_x(np.pi, capture=False)) 150 | self.assertIsInstance(seq.compile(), CompiledPulseSequence) 151 | self.assertEqual(seq.compile().hc.times.size, 3 * pulse_len) 152 | prop = seq.propagator() 153 | result = seq.run(qubit.fock(0)) 154 | self.assertIsInstance(prop, (list, np.ndarray)) 155 | for item in prop: 156 | self.assertIsInstance(item, qutip.Qobj) 157 | fid = qutip.fidelity(prop[-1] * qubit.fock(0), result.states[-1]) ** 2 158 | self.assertLess(abs(1 - fid), 5e-9) 159 | 160 | def test_compiled_pulse_sequence(self): 161 | qubit = Transmon("qubit", levels=2) 162 | pulse = qubit.gaussian_pulse 163 | pulse_len = pulse.sigma * pulse.chop 164 | system = System("system", modes=[qubit]) 165 | 166 | seq = CompiledPulseSequence(system=system) 167 | seq.add_operation(qubit.rotate_x(np.pi, capture=False)) 168 | seq.sync() 169 | seq.add_operation(qubit.rotate_x(np.pi, capture=False)) 170 | self.assertEqual(seq.hc.times.size, 2 * pulse_len) 171 | result = seq.run(qubit.fock(0)) 172 | self.assertIsInstance(result, qutip.solver.Result) 173 | 174 | def test_compiled_pulse_sequence_propagator(self): 175 | qubit = Transmon("qubit", levels=2) 176 | pulse = qubit.gaussian_pulse 177 | pulse_len = pulse.sigma * pulse.chop 178 | system = System("system", modes=[qubit]) 179 | 180 | seq = CompiledPulseSequence(system=system) 181 | seq.add_operation(qubit.rotate_x(np.pi, capture=False)) 182 | seq.sync() 183 | seq.add_operation(qubit.rotate_x(np.pi, capture=False)) 184 | seq.sync() 185 | seq.add_operation(qubit.rotate_x(np.pi, capture=False)) 186 | self.assertEqual(seq.hc.times.size, 3 * pulse_len) 187 | prop = seq.propagator() 188 | result = seq.run(qubit.fock(0)) 189 | self.assertIsInstance(prop, (list, np.ndarray)) 190 | for item in prop: 191 | self.assertIsInstance(item, qutip.Qobj) 192 | fid = qutip.fidelity(prop[-1] * qubit.fock(0), result.states[-1]) ** 2 193 | self.assertLess(abs(1 - fid), 5e-9) 194 | 195 | def test_dynamic_collapse_operators(self): 196 | qubit = Transmon("qubit", levels=2) 197 | qubit.t1 = 1000 198 | system = System("system", modes=[qubit]) 199 | 200 | @capture_operation 201 | def lossy_pi_pulse(qubit, pulsed_t1): 202 | total_gamma_down = 1 / pulsed_t1 203 | additional_gamma_down = total_gamma_down - qubit.Gamma_down 204 | coeff = np.sqrt(additional_gamma_down) 205 | op = qubit.rotate_x(np.pi, capture=False) 206 | terms = op.terms 207 | terms[f"{qubit.name}.Gamma_down"] = CTerm(qubit.a, coeffs=coeff) 208 | return Operation(op.duration, terms) 209 | 210 | @capture_operation 211 | def lossy_delay(qubit, length, pulsed_t1): 212 | total_gamma_down = 1 / pulsed_t1 213 | additional_gamma_down = total_gamma_down - qubit.Gamma_down 214 | coeff = np.sqrt(additional_gamma_down) 215 | terms = {f"{qubit.name}.Gamma_down": CTerm(qubit.a, coeffs=coeff)} 216 | return Operation(length, terms) 217 | 218 | def t1_sequence(system, qubit, max_time=10000, pulsed_t1=None): 219 | seq = get_sequence(system) 220 | if pulsed_t1 is not None: 221 | lossy_pi_pulse(qubit, pulsed_t1) 222 | sync() 223 | lossy_delay(qubit, max_time, pulsed_t1) 224 | else: 225 | qubit.rotate_x(np.pi) 226 | sync() 227 | delay(max_time) 228 | return seq 229 | 230 | def fit_exp_decay(xs, ys): 231 | slope, offset = np.polyfit(xs, np.log(ys), 1) 232 | amp = np.exp(offset) 233 | tau = -1 / slope 234 | return amp, tau 235 | 236 | normal_t1_result = t1_sequence(system, qubit).run( 237 | qubit.fock(0), 238 | e_ops=[qubit.fock(1)], 239 | only_final_state=False, 240 | ) 241 | t0 = qubit.gaussian_pulse.sigma * qubit.gaussian_pulse.chop 242 | ts = np.arange(len(normal_t1_result.states)) 243 | _, normal_fit_t1 = fit_exp_decay(ts[t0:], normal_t1_result.expect[0][t0:]) 244 | # Assert that t1 is what we expect to within 0.1 ns 245 | self.assertTrue(np.isclose(normal_fit_t1, qubit.t1, atol=0.1)) 246 | 247 | for factor in [2, 3, 4, 5]: 248 | pulsed_t1 = qubit.t1 / factor 249 | result = t1_sequence(system, qubit, pulsed_t1=pulsed_t1).run( 250 | qubit.fock(0), 251 | e_ops=[qubit.fock(1)], 252 | only_final_state=False, 253 | ) 254 | ts = np.arange(len(result.states)) 255 | _, fit_t1 = fit_exp_decay(ts[t0:], result.expect[0][t0:]) 256 | # Assert that t1 is what we expect to within 0.1 ns 257 | self.assertTrue(np.isclose(fit_t1, pulsed_t1, atol=0.1)) 258 | 259 | 260 | class TestTiming(unittest.TestCase): 261 | @classmethod 262 | def setUpClass(cls): 263 | sigma = 10 264 | chop = 4 265 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 266 | qubit.gaussian_pulse.sigma = sigma 267 | qubit.gaussian_pulse.chop = chop 268 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 269 | cavity.gaussian_pulse.sigma = sigma 270 | cavity.gaussian_pulse.chop = chop 271 | system = System("system", modes=[qubit, cavity]) 272 | system.set_cross_kerr(cavity, qubit, chi=-2e-3) 273 | cls.system = system 274 | 275 | def test_sync(self): 276 | system = self.system 277 | qubit = system.qubit 278 | sigma = qubit.gaussian_pulse.sigma 279 | chop = qubit.gaussian_pulse.chop 280 | 281 | seq = get_sequence(system) 282 | qubit.rotate_x(np.pi / 2) 283 | qubit.rotate_x(np.pi / 2) 284 | for channel in seq.channels.values(): 285 | self.assertEqual(channel["coeffs"].size, sigma * chop) 286 | 287 | seq = get_sequence(system) 288 | qubit.rotate_x(np.pi / 2) 289 | sync() 290 | qubit.rotate_x(np.pi / 2) 291 | for channel in seq.channels.values(): 292 | self.assertEqual(channel["coeffs"].size, 2 * sigma * chop) 293 | 294 | def test_delay(self): 295 | delay_time = 100 296 | system = self.system 297 | qubit = system.qubit 298 | sigma = qubit.gaussian_pulse.sigma 299 | chop = qubit.gaussian_pulse.chop 300 | 301 | seq = get_sequence(system) 302 | qubit.rotate_x(np.pi / 2) 303 | delay(delay_time) 304 | qubit.rotate_x(np.pi / 2) 305 | for channel in seq.channels.values(): 306 | self.assertEqual(channel["coeffs"].size, delay_time + 2 * sigma * chop) 307 | 308 | def test_delay_channels(self): 309 | system = self.system 310 | qubit = system.qubit 311 | cavity = system.cavity 312 | sigma = qubit.gaussian_pulse.sigma 313 | chop = qubit.gaussian_pulse.chop 314 | 315 | delay_time = 5 316 | 317 | seq = get_sequence(system) 318 | qubit.rotate_x(np.pi) 319 | cavity_channels = {"cavity.x": cavity.x, "cavity.y": cavity.y} 320 | delay_channels(cavity_channels, delay_time) 321 | cavity.displace(1) 322 | for channel in seq.channels.values(): 323 | self.assertEqual(channel["coeffs"].size, delay_time + sigma * chop) 324 | 325 | seq = get_sequence(system) 326 | cavity.displace(1) 327 | qubit_channels = {"qubit.x": qubit.x, "qubit.y": qubit.y} 328 | delay_channels(qubit_channels, delay_time) 329 | qubit.rotate_x(np.pi) 330 | for channel in seq.channels.values(): 331 | self.assertEqual(channel["coeffs"].size, delay_time + sigma * chop) 332 | 333 | 334 | class TestSequence(unittest.TestCase): 335 | @classmethod 336 | def setUpClass(cls): 337 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 338 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 339 | system = System("system", modes=[qubit, cavity]) 340 | system.set_cross_kerr(cavity, qubit, chi=-2e-3) 341 | cls.system = system 342 | 343 | def test_sequence(self): 344 | 345 | system = self.system 346 | qubit = system.qubit 347 | 348 | init_state = system.ground_state() 349 | n_rotations = 20 350 | theta = np.pi / n_rotations 351 | 352 | seq = Sequence(system) 353 | for _ in range(n_rotations): 354 | qubit.rotate_x(theta / 2) 355 | # append the current sequence 356 | seq.capture() 357 | # append a unitary 358 | seq.append(qubit.Rx(theta / 2)) 359 | 360 | _ = seq.plot_coefficients() 361 | result = seq.run(init_state) 362 | states = result.states 363 | fidelity = qutip.fidelity(states[-1], qubit.Rx(np.pi) * init_state) ** 2 364 | self.assertGreater(fidelity, 0.999) 365 | 366 | def test_hybrid_sequence_operation(self): 367 | system = self.system 368 | qubit = system.qubit 369 | 370 | init_state = system.ground_state() 371 | n_rotations = 20 372 | theta = np.pi / n_rotations 373 | 374 | seq = Sequence(system) 375 | for _ in range(n_rotations): 376 | # append an Operation 377 | qubit.rotate_x(theta / 4) 378 | sync() 379 | qubit.rotate_x(theta / 4) 380 | # append a unitary 381 | seq.append(qubit.Rx(theta / 4)) 382 | seq.append(qubit.Rx(theta / 4)) 383 | result = seq.run(init_state) 384 | states = result.states 385 | self.assertEqual(len(result.states), result.times.size) 386 | fidelity = qutip.fidelity(states[-1], qubit.Rx(np.pi) * init_state) ** 2 387 | self.assertGreater(fidelity, 0.9995) 388 | 389 | def test_sequence_propagator(self): 390 | system = self.system 391 | qubit = system.qubit 392 | init_state = system.ground_state() 393 | n_rotations = 20 394 | theta = np.pi / n_rotations 395 | 396 | seq = Sequence(system) 397 | for _ in range(n_rotations): 398 | # # append an Operation 399 | seq.append(qubit.rotate_x(theta / 4, capture=False)) 400 | sync() 401 | seq.append(qubit.rotate_x(theta / 4, capture=False)) 402 | # append a unitary 403 | seq.append(qubit.Rx(theta / 4)) 404 | seq.append(qubit.Rx(theta / 4)) 405 | props = seq.propagator() 406 | final_state = props[-1] * init_state 407 | fidelity = qutip.fidelity(final_state, qubit.Rx(np.pi) * init_state) ** 2 408 | self.assertGreater(fidelity, 0.9995) 409 | 410 | 411 | if __name__ == "__main__": 412 | unittest.main() 413 | -------------------------------------------------------------------------------- /sequencing/test/test_system.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import tempfile 4 | 5 | import numpy as np 6 | import qutip 7 | 8 | from sequencing import System, CouplingTerm, Transmon, Cavity 9 | 10 | 11 | class TestSerialization(unittest.TestCase): 12 | def test_to_from_dict(self): 13 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 14 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 15 | system = System("system", modes=[qubit, cavity]) 16 | system.set_cross_kerr(qubit, cavity, chi=-2e-3) 17 | other_system = System.from_dict(system.as_dict()) 18 | self.assertEqual(system, other_system) 19 | 20 | def test_to_from_json_str(self): 21 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 22 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 23 | system = System("system", modes=[qubit, cavity]) 24 | system.set_cross_kerr(qubit, cavity, chi=-2e-3) 25 | json_str = system.to_json(dumps=True) 26 | other_system = System.from_json(json_str=json_str) 27 | self.assertEqual(system, other_system) 28 | 29 | def test_to_from_json_file(self): 30 | 31 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 32 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 33 | system = System("system", modes=[qubit, cavity]) 34 | system.set_cross_kerr(qubit, cavity, chi=-2e-3) 35 | with tempfile.TemporaryDirectory() as dirname: 36 | json_path = os.path.join(dirname, "__test_to_from_json_file.json") 37 | system.to_json(json_path=json_path) 38 | other_system = System.from_json(json_path=json_path) 39 | self.assertEqual(system, other_system) 40 | 41 | 42 | class TestSystem(unittest.TestCase): 43 | def test_get_mode(self): 44 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 45 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 46 | system = System("system", modes=[qubit, cavity]) 47 | 48 | self.assertIs(system.get_mode(qubit), qubit) 49 | self.assertIs(system.get_mode("qubit"), qubit) 50 | 51 | with self.assertRaises(ValueError): 52 | system.get_mode("other_qubit") 53 | 54 | def test_set_cross_kerr(self): 55 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 56 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 57 | system = System("system", modes=[qubit, cavity]) 58 | 59 | with self.assertRaises(ValueError): 60 | system.set_cross_kerr(qubit, qubit, -1e-3) 61 | 62 | chi = -2e-3 63 | system.set_cross_kerr(qubit, cavity, chi) 64 | 65 | self.assertEqual(len(system.couplings()), 1) 66 | self.assertEqual(system.couplings()[0], 2 * np.pi * chi * qubit.n * cavity.n) 67 | 68 | system.set_cross_kerr(qubit, cavity, chi) 69 | 70 | self.assertEqual(len(system.couplings()), 1) 71 | self.assertEqual(system.couplings()[0], 2 * np.pi * chi * qubit.n * cavity.n) 72 | 73 | system.set_cross_kerr(cavity, qubit, chi) 74 | 75 | self.assertEqual(len(system.couplings()), 1) 76 | self.assertEqual(system.couplings()[0], 2 * np.pi * chi * qubit.n * cavity.n) 77 | 78 | def test_coupling_terms(self): 79 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 80 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 81 | system = System("system", modes=[qubit, cavity]) 82 | chi = -2e-3 83 | key = frozenset([qubit.name, cavity.name]) 84 | system.coupling_terms[key].append( 85 | CouplingTerm(qubit, "n", cavity, "n", strength=2 * np.pi * chi) 86 | ) 87 | self.assertEqual(len(system.couplings()), 1) 88 | self.assertEqual(system.couplings()[0], 2 * np.pi * chi * qubit.n * cavity.n) 89 | system.set_cross_kerr(qubit, cavity, chi) 90 | self.assertEqual(len(system.couplings()), 1) 91 | self.assertEqual(system.couplings()[0], 2 * np.pi * chi * qubit.n * cavity.n) 92 | 93 | def test_coupling_terms_multimode(self): 94 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 95 | cavity1 = Cavity("cavity1", levels=10, kerr=-10e-6) 96 | cavity2 = Cavity("cavity2", levels=6, kerr=-10e-6) 97 | system = System("system", modes=[qubit, cavity2, cavity1]) 98 | system.set_cross_kerr(qubit, cavity1, -2e-3) 99 | system.set_cross_kerr(qubit, cavity2, -1e-3) 100 | system.set_cross_kerr(cavity1, cavity2, -5e-6) 101 | self.assertEqual(len(system.couplings()), 3) 102 | 103 | for modes in [[qubit, cavity1], [qubit, cavity1, cavity2]]: 104 | with system.use_modes(modes): 105 | self.assertEqual( 106 | system.couplings()[0], 2 * np.pi * -2e-3 * qubit.n * cavity1.n 107 | ) 108 | 109 | for i, modes in enumerate([[qubit, cavity2], [qubit, cavity1, cavity2]]): 110 | with system.use_modes(modes): 111 | self.assertEqual( 112 | system.couplings()[i], 2 * np.pi * -1e-3 * qubit.n * cavity2.n 113 | ) 114 | 115 | for i, modes in enumerate([[cavity2, cavity1], [qubit, cavity1, cavity2]]): 116 | with system.use_modes(modes): 117 | self.assertEqual( 118 | system.couplings()[2 * i], 2 * np.pi * -5e-6 * cavity1.n * cavity2.n 119 | ) 120 | 121 | def test_order_modes(self): 122 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 123 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 124 | 125 | system = System("system", modes=[qubit, cavity]) 126 | system.order_modes = True 127 | system.active_modes = [qubit, cavity] 128 | self.assertEqual(system.modes, [qubit, cavity]) 129 | self.assertEqual(system.active_modes, [qubit, cavity]) 130 | system.active_modes = [cavity, qubit] 131 | self.assertEqual(system.modes, [qubit, cavity]) 132 | self.assertEqual(system.active_modes, [qubit, cavity]) 133 | 134 | system = System("system", modes=[qubit, cavity]) 135 | system.order_modes = False 136 | system.active_modes = [qubit, cavity] 137 | self.assertEqual(system.modes, [qubit, cavity]) 138 | self.assertEqual(system.active_modes, [qubit, cavity]) 139 | system.active_modes = [cavity, qubit] 140 | self.assertEqual(system.modes, [qubit, cavity]) 141 | self.assertEqual(system.active_modes, [cavity, qubit]) 142 | 143 | def test_use_modes(self): 144 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 145 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 146 | system = System("system", modes=[qubit, cavity]) 147 | 148 | with system.use_modes([qubit]): 149 | self.assertEqual(system.ground_state(), qubit.fock(0, full_space=False)) 150 | 151 | with system.use_modes([cavity]): 152 | self.assertEqual(system.ground_state(), cavity.fock(0, full_space=False)) 153 | 154 | with system.use_modes([qubit, cavity]): 155 | self.assertEqual( 156 | system.ground_state(), 157 | qutip.tensor( 158 | qubit.fock(0, full_space=False), cavity.fock(0, full_space=False) 159 | ), 160 | ) 161 | 162 | system.order_modes = False 163 | with system.use_modes([cavity, qubit]): 164 | self.assertEqual( 165 | system.ground_state(), 166 | qutip.tensor( 167 | cavity.fock(0, full_space=False), qubit.fock(0, full_space=False) 168 | ), 169 | ) 170 | 171 | def test_active_modes(self): 172 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 173 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 174 | system = System("system", modes=[qubit]) 175 | 176 | system.active_modes = [qubit] 177 | 178 | with self.assertRaises(ValueError): 179 | system.active_modes = [qubit, cavity] 180 | 181 | system.modes = [qubit, cavity] 182 | system.active_modes = [qubit, cavity] 183 | system.modes = [cavity] 184 | with self.assertRaises(ValueError): 185 | _ = system.active_modes 186 | 187 | def test_fock(self): 188 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 189 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 190 | system = System("system", modes=[qubit, cavity]) 191 | 192 | with self.assertRaises(ValueError): 193 | system.fock(0) 194 | 195 | with self.assertRaises(ValueError): 196 | system.fock(0, qubit=0) 197 | 198 | def test_H0(self): 199 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 200 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 201 | system = System("system", modes=[qubit, cavity]) 202 | system.set_cross_kerr(qubit, cavity, 0) 203 | 204 | # [qubit.self_kerr, cavity.self_kerr] 205 | self.assertEqual(len(system.H0(clean=True)), 2) 206 | # [ 207 | # qubit.self_kerr, cavity.self_kerr, 208 | # qubit.detuning, cavity.detuning, 209 | # qubit-cavity cross-Kerr 210 | # ] 211 | self.assertEqual(len(system.H0(clean=False)), 5) 212 | 213 | system.set_cross_kerr(qubit, cavity, -2e-3) 214 | # [qubit.self_kerr, cavity.self_kerr, qubit-cavity cross-Kerr] 215 | self.assertEqual(len(system.H0(clean=True)), 3) 216 | 217 | def test_c_ops(self): 218 | qubit = Transmon("qubit", levels=3, kerr=-200e-3) 219 | cavity = Cavity("cavity", levels=10, kerr=-10e-6) 220 | system = System("system", modes=[qubit, cavity]) 221 | 222 | # [ 223 | # qubit.decay, qubit.excitation, qubit.dephasing, 224 | # cavity.decay, cavity.excitation, cavity.dephasing, 225 | # ] 226 | self.assertEqual(len(system.c_ops(clean=False)), 6) 227 | self.assertEqual(len(system.c_ops(clean=True)), 0) 228 | 229 | qubit.t1 = 100e3 230 | cavity.t2 = 500e3 231 | # [qubit.decay, cavity.dephasing] 232 | self.assertEqual(len(system.c_ops(clean=True)), 2) 233 | 234 | 235 | if __name__ == "__main__": 236 | unittest.main() 237 | -------------------------------------------------------------------------------- /sequencing/testing.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | import os 10 | import pytest 11 | import warnings 12 | import matplotlib 13 | 14 | 15 | TESTDIR = os.path.join( 16 | os.path.pardir, os.path.dirname(os.path.abspath(__file__)), "test" 17 | ) 18 | 19 | 20 | def run(): 21 | # We want to temporarily use a non-GUI backend to avoid 22 | # spamming the user's screen with a bunch of plots. 23 | # Matplotlib helpfully raises a UserWarning when 24 | # using a non-GUI backend... 25 | with warnings.catch_warnings(): 26 | old_backend = matplotlib.get_backend() 27 | matplotlib.use("Agg") 28 | warnings.filterwarnings( 29 | "ignore", category=UserWarning, message="Matplotlib is currently using agg" 30 | ) 31 | # We also want to ignore DeprecationWarnings becuase 32 | # they are not directly relevant to the user. 33 | pytest.main(["-v", TESTDIR, "-W ignore::DeprecationWarning"]) 34 | matplotlib.pyplot.close("all") 35 | matplotlib.use(old_backend) 36 | 37 | 38 | if __name__ == "__main__": 39 | run() 40 | -------------------------------------------------------------------------------- /sequencing/version.py: -------------------------------------------------------------------------------- 1 | # This file is part of sequencing. 2 | # 3 | # Copyright (c) 2021, The Sequencing Authors. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. 8 | 9 | __version_info__ = (1, 2, 0) 10 | __version__ = ".".join(map(str, __version_info__)) 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """# seQuencing 2 | 3 | ``sequencing`` is a Python package for simulating realistic quantum control sequences using 4 | [QuTiP](http://qutip.org/docs/latest/index.html). Built for researchers and quantum engineers, 5 | ``sequencing`` provides an intuitive framework for constructing models of quantum systems 6 | composed of many modes and generating complex time-dependent control Hamiltonians 7 | for [master equation simulations](http://qutip.org/docs/latest/guide/dynamics/dynamics-master.html). 8 | """ 9 | 10 | from setuptools import setup, find_packages 11 | 12 | DESCRIPTION = "seQuencing: simulate realistic quantum control sequences using QuTiP" 13 | LONG_DESCRIPTION = __doc__ 14 | 15 | NAME = "sequencing" 16 | AUTHOR = "Logan Bishop-Van Horn" 17 | AUTHOR_EMAIL = "logan.bvh@gmail.com" 18 | URL = "https://github.com/sequencing-dev/sequencing" 19 | LICENSE = "BSD" 20 | PYTHON_VERSION = ">=3.7" 21 | 22 | INSTALL_REQUIRES = [ 23 | "qutip>=4.5", 24 | "numpy>=1.16", 25 | "colorednoise>=1.1.1", 26 | "attrs>=20", 27 | "cython>=0.29.20", 28 | "matplotlib", 29 | "scipy", 30 | "jupyter", 31 | "tqdm", 32 | "lmfit", 33 | "pytest", 34 | "pytest-cov", 35 | ] 36 | 37 | EXTRAS_REQUIRE = { 38 | "docs": [ 39 | "sphinx", 40 | "sphinx_rtd_theme", 41 | "nbsphinx", 42 | ], 43 | } 44 | 45 | CLASSIFIERS = """\ 46 | Development Status :: 5 - Production/Stable 47 | Intended Audience :: Science/Research 48 | License :: OSI Approved :: BSD License 49 | Operating System :: MacOS 50 | Operating System :: POSIX 51 | Operating System :: Unix 52 | Operating System :: Microsoft :: Windows 53 | Programming Language :: Python 54 | Programming Language :: Python :: 3.7 55 | Programming Language :: Python :: 3.8 56 | Programming Language :: Python :: 3.9 57 | Topic :: Scientific/Engineering 58 | Topic :: Scientific/Engineering :: Physics 59 | """ 60 | 61 | CLASSIFIERS = [line for line in CLASSIFIERS.splitlines() if line] 62 | PLATFORMS = ["Linux", "Mac OSX", "Unix", "Windows"] 63 | KEYWORDS = "quantum pulse sequence" 64 | 65 | exec(open("sequencing/version.py").read()) 66 | 67 | setup( 68 | name=NAME, 69 | version=__version__, # noqa: F821 70 | author=AUTHOR, 71 | author_email=AUTHOR_EMAIL, 72 | url=URL, 73 | license=LICENSE, 74 | packages=find_packages(), 75 | include_package_data=True, 76 | description=DESCRIPTION, 77 | long_description=LONG_DESCRIPTION, 78 | long_description_content_type="text/markdown", 79 | keywords=KEYWORDS, 80 | classifiers=CLASSIFIERS, 81 | platforms=PLATFORMS, 82 | python_requires=PYTHON_VERSION, 83 | install_requires=INSTALL_REQUIRES, 84 | extras_require=EXTRAS_REQUIRE, 85 | ) 86 | --------------------------------------------------------------------------------