├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yaml │ └── FEATURE_REQUEST.yaml ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── install-main-dependencies │ │ └── action.yml │ ├── install-qopt │ │ └── action.yml │ └── run-tests │ │ └── action.yml └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── constraints.txt ├── demos └── qiskit_patterns │ ├── 0_setup_instructions.md │ ├── 1_test_installation.ipynb │ ├── 2_qiskit_patterns.ipynb │ ├── data │ ├── 125node_eagle_depth_one.qpy │ ├── 125node_eagle_depth_zero.qpy │ ├── 125node_example.lp │ ├── 125node_example.png │ ├── 125node_example_ising.txt │ ├── 125node_heron_depth_one.qpy │ └── 125node_heron_depth_zero.qpy │ ├── demo_src │ ├── graph.py │ ├── map.py │ ├── post.py │ ├── run.py │ └── transpile.py │ ├── imgs │ ├── circuit-0.png │ ├── circuit.png │ ├── eagle-heron-0.png │ ├── eagle-heron.png │ ├── exec-modes.png │ ├── max-cut.png │ └── patterns.png │ ├── requirements.txt │ └── sampler_data │ ├── eagle_87ea531a.json │ └── heron_215ebe55.json ├── how_tos ├── __init__.py ├── data │ ├── graph_2layers_0seed.json │ └── naive_transpilation.json ├── how_to_apply_optimal_qaoa_transpilation.ipynb ├── how_to_build_qaoa_swap_circuit.ipynb ├── how_to_optimize_qaoa_for_hw.ipynb ├── how_to_run_custom_cost_functions.ipynb └── how_to_select_qubits.ipynb ├── pyproject.toml ├── qopt_best_practices ├── VERSION.txt ├── __init__.py ├── cost_function │ ├── __init__.py │ └── cost_utils.py ├── error_mitigation │ └── __init__.py ├── qubit_selection │ ├── __init__.py │ ├── backend_evaluator.py │ ├── metric_evaluators.py │ └── qubit_subset_finders.py ├── sat_mapping │ ├── __init__.py │ └── sat_mapper.py ├── swap_strategies │ ├── __init__.py │ └── build_circuit.py ├── transpilation │ ├── __init__.py │ ├── preset_qaoa_passmanager.py │ ├── qaoa_construction_pass.py │ └── swap_cancellation_pass.py └── utils │ ├── __init__.py │ └── graph_utils.py ├── requirements-dev.txt ├── requirements.txt ├── run ├── __init__.py ├── data │ └── hardware_native_127.json └── hw_native_test_1_circ.py ├── setup.py ├── stestr.conf ├── test ├── __init__.py ├── data │ └── graph_2layers_0seed.json ├── test_cost_function.py ├── test_graph_utils.py ├── test_qaoa_construction.py ├── test_qubit_selection.py ├── test_sat_mapping.py ├── test_swap_cancellation.py └── test_swap_strategies.py └── tox.ini /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file defines the persons who will be assigned as reviewers for PRs that 2 | # modify particular files in the repo. The PR can be merged when approved by at 3 | # least one codeowner. However, all Qiskit team members can (and should!) review the PRs. 4 | 5 | # Global rule, unless specialized by a later one 6 | * @eggerdj @ElePT @Cryoris 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.yaml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Create a report to help us improve 🤔. 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thank you for reporting! Please also use the search to see if there are any other relevant issues or pull requests. 9 | 10 | - type: textarea 11 | attributes: 12 | label: Environment 13 | description: For the version of Qiskit, please give the actual version number (_e.g._ 0.18.3) if you are using a release version, or the first 7-8 characters of the commit hash if you have installed from `git`. If anything else is relevant, you can add it to the list. 14 | # The trailing spaces on the following lines are to make filling the form 15 | # in easier. The type is 'textarea' rather than three separate 'input's 16 | # to make the resulting issue body less noisy with headings. 17 | value: | 18 | - **Qiskit version**: 19 | - **Python version**: 20 | - **Operating system**: 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: What is happening? 27 | description: A short description of what is going wrong, in words. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | attributes: 33 | label: How can we reproduce the issue? 34 | description: Give some steps that show the bug. A [minimal working example](https://stackoverflow.com/help/minimal-reproducible-example) of code with output is best. If you are copying in code, please remember to enclose it in triple backticks (` ``` [multiline code goes here] ``` `) so that it [displays correctly](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). 35 | validations: 36 | required: true 37 | 38 | - type: textarea 39 | attributes: 40 | label: What should happen? 41 | description: A short description, including about the expected output of any code in the previous section. 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | attributes: 47 | label: Any suggestions? 48 | description: Not required, but if you have suggestions for how a contributor should fix this, or any problems we should be aware of, let us know. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Suggest an idea for this project 💡! 3 | labels: ["type: feature request"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Please make sure to browse the opened and closed issues to make sure that this idea has not previously been discussed. 9 | 10 | - type: textarea 11 | attributes: 12 | label: What should we add? 13 | validations: 14 | required: true 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ### Summary 11 | 12 | 13 | 14 | ### Details and comments 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/actions/install-main-dependencies/action.yml: -------------------------------------------------------------------------------- 1 | # This code is part of a Qiskit project. 2 | # 3 | # (C) Copyright IBM 2021, 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | name: 'Install Main Dependencies' 14 | description: 'Installs Python dependencies from Main' 15 | inputs: 16 | os: 17 | description: 'OS' 18 | required: true 19 | python-version: 20 | description: 'Python version' 21 | required: true 22 | 23 | runs: 24 | using: "composite" 25 | steps: 26 | - name: Get main last commit ids 27 | run: | 28 | echo "QISKIT_HASH=$(git ls-remote --heads https://github.com/Qiskit/qiskit.git refs/heads/main | awk '{print $1}')" >> $GITHUB_ENV 29 | shell: bash 30 | - name: Qiskit Cache 31 | env: 32 | CACHE_VERSION: v1 33 | id: qiskit-cache 34 | uses: actions/cache@v3 35 | with: 36 | path: qiskit-cache 37 | key: qiskit-cache-${{ inputs.os }}-${{ inputs.python-version }}-${{ env.QISKIT_HASH }}-${{ env.CACHE_VERSION }} 38 | - name: Install Qiskit from Main 39 | env: 40 | MACOSX_DEPLOYMENT_TARGET: 10.15 41 | run: | 42 | echo 'Install Qiskit from Stable' 43 | pip install -U qiskit 44 | shell: bash 45 | -------------------------------------------------------------------------------- /.github/actions/install-qopt/action.yml: -------------------------------------------------------------------------------- 1 | # This code is part of a Qiskit project. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | name: 'Install qopt_best_practices' 14 | description: 'Installs qopt_best_practices' 15 | 16 | runs: 17 | using: "composite" 18 | steps: 19 | - run : | 20 | pip install -e . 21 | pip install -U -c constraints.txt -r requirements-dev.txt 22 | shell: bash 23 | -------------------------------------------------------------------------------- /.github/actions/run-tests/action.yml: -------------------------------------------------------------------------------- 1 | # This code is part of a Qiskit project. 2 | # 3 | # (C) Copyright IBM 2021, 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | name: 'Run Unit Tests' 14 | description: 'Run Unit Tests' 15 | inputs: 16 | os: 17 | description: 'OS' 18 | required: true 19 | event-name: 20 | description: 'Actions event' 21 | required: true 22 | run-slow: 23 | description: 'Run slow tests or not' 24 | required: true 25 | python-version: 26 | description: 'Python version' 27 | required: true 28 | runs: 29 | using: "composite" 30 | steps: 31 | - name: Run Unit Tests 32 | env: 33 | PYTHONWARNINGS: default 34 | run: | 35 | # run slow tests only on scheduled event or if input flag is set 36 | if [ "${{ inputs.os }}" == "ubuntu-latest" ] && [ "${{ inputs.python-version }}" == "3.8" ]; then 37 | export PYTHON="coverage3 run --source qopt_best_practices --parallel-mode" 38 | fi 39 | stestr --test-path test run 2> >(tee /dev/stderr out.txt > /dev/null) 40 | shell: bash 41 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This code is part of a Qiskit project. 2 | # 3 | # (C) Copyright IBM 2021, 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | name: qopt-best-practices Unit Tests 14 | 15 | on: 16 | push: 17 | branches: 18 | - main 19 | - 'stable/**' 20 | pull_request: 21 | branches: 22 | - main 23 | - 'stable/**' 24 | 25 | concurrency: 26 | group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | Checks: 31 | if: github.repository_owner == 'qiskit-community' 32 | runs-on: ${{ matrix.os }} 33 | strategy: 34 | matrix: 35 | os: [ubuntu-latest] 36 | python-version: [3.8] 37 | steps: 38 | - name: Print Concurrency Group 39 | env: 40 | CONCURRENCY_GROUP: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }} 41 | run: | 42 | echo -e "\033[31;1;4mConcurrency Group\033[0m" 43 | echo -e "$CONCURRENCY_GROUP\n" 44 | shell: bash 45 | - uses: actions/checkout@v3 46 | with: 47 | fetch-depth: 0 48 | - uses: actions/setup-python@v4 49 | with: 50 | python-version: ${{ matrix.python-version }} 51 | cache: 'pip' 52 | cache-dependency-path: | 53 | setup.py 54 | requirements.txt 55 | requirements-dev.txt 56 | - uses: ./.github/actions/install-main-dependencies 57 | with: 58 | os: ${{ matrix.os }} 59 | python-version: ${{ matrix.python-version }} 60 | qiskit-main: "false" 61 | if: ${{ !startsWith(github.ref, 'refs/heads/stable') && !startsWith(github.base_ref, 'stable/') }} 62 | - uses: ./.github/actions/install-qopt 63 | - run: pip check 64 | if: ${{ !cancelled() }} 65 | shell: bash 66 | - name: Style Check 67 | run: | 68 | make style 69 | if: ${{ !cancelled() }} 70 | shell: bash 71 | qopt: 72 | if: github.repository_owner == 'qiskit-community' 73 | runs-on: ${{ matrix.os }} 74 | strategy: 75 | fail-fast: false 76 | matrix: 77 | os: [ubuntu-latest] 78 | python-version: [3.8, 3.11] 79 | include: 80 | - os: macos-latest 81 | python-version: 3.8 82 | - os: macos-latest 83 | python-version: 3.11 84 | - os: windows-latest 85 | python-version: 3.8 86 | - os: windows-latest 87 | python-version: 3.11 88 | steps: 89 | - uses: actions/checkout@v3 90 | - uses: actions/setup-python@v4 91 | with: 92 | python-version: ${{ matrix.python-version }} 93 | cache: 'pip' 94 | cache-dependency-path: | 95 | setup.py 96 | requirements.txt 97 | requirements-dev.txt 98 | - uses: ./.github/actions/install-main-dependencies 99 | with: 100 | os: ${{ matrix.os }} 101 | python-version: ${{ matrix.python-version }} 102 | qiskit-main: "false" 103 | if: ${{ !startsWith(github.ref, 'refs/heads/stable') && !startsWith(github.base_ref, 'stable/') }} 104 | - uses: ./.github/actions/install-qopt 105 | - run: make lint 106 | shell: bash 107 | - name: qopt Unit Tests under Python ${{ matrix.python-version }} 108 | uses: ./.github/actions/run-tests 109 | with: 110 | os: ${{ matrix.os }} 111 | event-name: ${{ github.event_name }} 112 | run-slow: ${{ contains(github.event.pull_request.labels.*.name, 'run_slow') }} 113 | python-version: ${{ matrix.python-version }} 114 | if: ${{ !cancelled() }} 115 | - name: Coverage combine 116 | run: | 117 | mkdir ./ci-artifact-data 118 | coverage3 combine 119 | mv .coverage ./ci-artifact-data/qopt.dat 120 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == 3.8 }} 121 | shell: bash 122 | - uses: actions/upload-artifact@v3 123 | with: 124 | name: ${{ matrix.os }}-${{ matrix.python-version }} 125 | path: ./ci-artifact-data/* 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | */.* 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OS := $(shell uname -s) 2 | 3 | ifeq ($(OS), Linux) 4 | NPROCS := $(shell grep -c ^processor /proc/cpuinfo) 5 | else ifeq ($(OS), Darwin) 6 | NPROCS := 2 7 | else 8 | NPROCS := 0 9 | endif # $(OS) 10 | 11 | ifeq ($(NPROCS), 2) 12 | CONCURRENCY := 2 13 | else ifeq ($(NPROCS), 1) 14 | CONCURRENCY := 1 15 | else ifeq ($(NPROCS), 3) 16 | CONCURRENCY := 3 17 | else ifeq ($(NPROCS), 0) 18 | CONCURRENCY := 0 19 | else 20 | CONCURRENCY := $(shell echo "$(NPROCS) 2" | awk '{printf "%.0f", $$1 / $$2}') 21 | endif 22 | 23 | .PHONY: lint style black test test_ci coverage clean 24 | 25 | all_check: style lint 26 | 27 | lint: 28 | pylint -rn qopt_best_practices test 29 | 30 | black: 31 | python -m black qopt_best_practices test 32 | 33 | style: 34 | python -m black --check qopt_best_practices test 35 | 36 | test: 37 | python -m unittest discover -v test 38 | 39 | test_ci: 40 | echo "Detected $(NPROCS) CPUs running with $(CONCURRENCY) workers" 41 | python -m stestr run --concurrency $(CONCURRENCY) 42 | 43 | coverage: 44 | python -m coverage3 run --source qopt_best_practices -m unittest discover -s test -q 45 | python -m coverage3 report 46 | 47 | coverage_erase: 48 | python -m coverage erase 49 | 50 | clean: coverage_erase; 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # q-optimization-best-practices 2 | 3 | A collection of guidelines to run quantum optimization algorithms on superconducting qubits with Qiskit, 4 | using as reference the Quantum Approximate Optimization Algorithm (QAOA) workflow. 5 | 6 | This repository shows how to combine methods employed in the QAOA literature to get good results on hardware [[1]](https://arxiv.org/abs/2307.14427), 7 | such as SWAP strategies [[2]](https://arxiv.org/abs/2202.03459), 8 | SAT mapping [[3]](https://arxiv.org/abs/2212.05666), 9 | pulse-efficient transpilation and dynamical-decoupling [coming soon]. In the future, it will be expanded 10 | to include a broader range of quantum algorithms for combinatorial optimization. 11 | 12 | The `qopt_best_practices` directory contains a series of reference implementations for the key 13 | strategies mentioned in the description. These are not intended to be feature-complete, they are 14 | prototypes that should be easy to test and try out in different settings 15 | (that's why the library is pip-installable!) and can also serve as a guide for your 16 | own advanced implementations of these techniques. 17 | 18 | The `how-tos` directory contains a series of notebooks you can run to see these techniques in action. 19 | In many cases, the helper methods provided for a specific task (for example, swap mapping), are just 20 | light wrappers over already-available Qiskit utilities (transpiler passes). How-tos that focus 21 | on a specific tasks will probably show direct use of the Qiskit code, while how-tos that lay out a full 22 | workflow will likely rely on the provided wrappers for a more general overview of the steps to follow. 23 | 24 | 25 | This is only a relatively polished repository. If you see any bug or feature you'd like to add, this is a community 26 | effort, contributions are welcome. Don't hesitate to open an issue or a PR. 27 | 28 | ## Quick Start 29 | 30 | 1. run `git clone https://github.com/qiskit-community/q-optimization-best-practices.git` in your local environment 31 | 2. do `pip install -r requirements.txt` 32 | 3. do `pip install .` 33 | 4. navigate to the `how-tos` section and run the notebook of your choice! 34 | 35 | 36 | ## Table of Contents 37 | 38 | The contents of the `qopt_best_practices` package are structured around the key strategies that 39 | can be applied to best run quantum optimization algorithms on real hardware: 40 | 41 | 1. Qubit Selection -> `qubit_selection` 42 | 2. SAT Mapping -> `sat_mapping` 43 | 3. Application of SWAP strategies -> `swap_strategies` 44 | 4. QAOA cost function -> `cost_function` 45 | 46 | ## QAOA Formulation Convention 47 | 48 | The convention in this repository is that the (depth-one) QAOA should create an Ansatz of the form: 49 | 50 | $$ 51 | \begin{align} 52 | \exp\left(-i\beta H_m\right)\exp\left(-i\gamma H_c\right)\vert+\rangle^{\otimes n} 53 | \end{align} 54 | $$ 55 | 56 | to **minimize** the energy of $H_c$. Since we are minimizing $\\langle H_c\\rangle$, 57 | the initial state of the ansatz should be the ground state of the mixer $H_m$. This enforces: 58 | 59 | $$ 60 | \\begin{align} 61 | H_m=-\\sum_iX_i 62 | \\end{align} 63 | $$ 64 | 65 | As cost operator we apply: 66 | 67 | $$ 68 | \\begin{align} 69 | H_c=\\sum_{i,j=0}^{n-1}w_{i,j}Z_iZ_j 70 | \\end{align} 71 | $$ 72 | 73 | where the sum runs over $i < j$. 74 | 75 | At the circuit level, these definitions imply that the exponential of the mixer is built 76 | with $R_x(-2\\beta)$ rotations and the exponential of the cost operator is built from $R_{zz}(2\\gamma w_{i,j})$. 77 | 78 | 79 | ## References 80 | 1. Sack, S. H., & Egger, D. J. (2023). Large-scale quantum approximate optimization on non-planar graphs with machine learning noise mitigation. arXiv preprint arXiv:2307.14427. [Link](https://arxiv.org/pdf/2307.14427.pdf). 81 | 2. Weidenfeller, J., Valor, L. C., Gacon, J., Tornow, C., Bello, L., Woerner, S., & Egger, D. J. (2022). Scaling of the quantum approximate optimization algorithm on superconducting qubit based hardware. Quantum, 6, 870. [Link](https://arxiv.org/abs/2202.03459). 82 | 3. Matsuo, A., Yamashita, S., & Egger, D. J. (2023). A SAT approach to the initial mapping problem in SWAP gate insertion for commuting gates. IEICE Transactions on Fundamentals of Electronics, Communications and Computer Sciences, 2022EAP1159. [Link](https://arxiv.org/abs/2212.05666). 83 | -------------------------------------------------------------------------------- /constraints.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/constraints.txt -------------------------------------------------------------------------------- /demos/qiskit_patterns/0_setup_instructions.md: -------------------------------------------------------------------------------- 1 | # Qiskit Patterns Lab Prep Work 2 | 3 | ## Requirements 4 | 5 | - Laptop (Windows, MacOS or Linux), access to a terminal 6 | 7 | ## Instructions 8 | 9 | 1. Follow the instructions on https://docs.quantum.ibm.com/start/install#install-and-set-up-qiskit-with-the-qiskit-runtime-client 10 | to install and set up a Python environment with Qiskit. Activate your environment. 11 | 2. Follow the instructions on https://docs.quantum.ibm.com/start/setup-channel#iqp to set up your access credentials to use 12 | IBM Quantum systems. Only focus on the IBM Quantum Platform instructions, no need to worry about the IBM Cloud setup. 13 | 3. Clone this repository running `git clone https://github.com/qiskit-community/qopt-best-practices.git` or downloading the zip file: 14 | - go to: https://github.com/qiskit-community/qopt-best-practices 15 | - click on code > download zip 16 | Screenshot 2024-03-04 at 06 09 12 17 | 18 | 4. Navigate to the `demos/qiskit_patterns` directory using the terminal 19 | 5. Run `pip install -r requirements.txt` 20 | 6. Activate your jupyter environment running `jupyter notebook` in your environment, this should open a browser window 21 | that shows the contents of the `qiskit_patterns` directory 22 | 7. Open `1_test_installation.ipynb` and follow the instructions to test that the installation was successful 23 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/1_test_installation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "pycharm": { 7 | "name": "#%% md\n" 8 | } 9 | }, 10 | "source": [ 11 | "# Installation Test\n", 12 | "\n", 13 | "Run the cell below (press `Shift` + `Enter`) to check that all the imports work correctly. If you see the 🎉 emoji as the cell output, your installation was successful." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": { 20 | "pycharm": { 21 | "name": "#%%\n" 22 | } 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "import matplotlib.pyplot\n", 27 | "import networkx as nx\n", 28 | "from qiskit_ibm_runtime.fake_provider import FakeVigoV2\n", 29 | "from qiskit import qpy\n", 30 | "from qiskit_aer import Aer\n", 31 | "from qiskit_optimization.applications import Maxcut\n", 32 | "print(\"Your installation works 🎉!\")" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "If you have followed the installation instructions (i.e. you have run `pip install -r requirements.txt`) and you get a `module not found` error, your jupyter environment might not be using the right Python kernel. You can try following the following instructions to register your kernel: https://saturncloud.io/blog/how-to-add-a-python-3-kernel-to-jupyter-ipython/." 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": { 45 | "pycharm": { 46 | "name": "#%%\n" 47 | } 48 | }, 49 | "source": [ 50 | "If you have not done so yet, you can use the cell below to check that your IBM Quantum Platform account registration has been successful. The backend retrieval step may take a couple of seconds." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "pycharm": { 58 | "name": "#%%\n" 59 | } 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "from qiskit_ibm_runtime import QiskitRuntimeService\n", 64 | "\n", 65 | "service = QiskitRuntimeService()\n", 66 | "print(\"Your available backends are: \", service.backends())" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": { 72 | "pycharm": { 73 | "name": "#%% md\n" 74 | } 75 | }, 76 | "source": [ 77 | "If your service authentication fails (you get an `IBMNotAuthorizedError`), you can try saving your account again. Make sure to replace `\"\"` with your token in string format (example: `service = QiskitRuntimeService(channel=\"ibm_quantum\", token=\"1234abcd\")`)." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": { 84 | "pycharm": { 85 | "name": "#%%\n" 86 | } 87 | }, 88 | "outputs": [], 89 | "source": [ 90 | "from qiskit_ibm_runtime import QiskitRuntimeService\n", 91 | "\n", 92 | "# Save an IBM Quantum account and set it as your default account.\n", 93 | "QiskitRuntimeService.save_account(channel=\"ibm_quantum\", token=\"\", overwrite=True, set_as_default=True)\n", 94 | "\n", 95 | "# Load saved credentials\n", 96 | "service = QiskitRuntimeService()" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "kernelspec": { 102 | "display_name": "ibm_tech_clean", 103 | "language": "python", 104 | "name": "ibm_tech_clean" 105 | }, 106 | "language_info": { 107 | "codemirror_mode": { 108 | "name": "ipython", 109 | "version": 3 110 | }, 111 | "file_extension": ".py", 112 | "mimetype": "text/x-python", 113 | "name": "python", 114 | "nbconvert_exporter": "python", 115 | "pygments_lexer": "ipython3", 116 | "version": "3.10.0" 117 | } 118 | }, 119 | "nbformat": 4, 120 | "nbformat_minor": 1 121 | } -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_eagle_depth_one.qpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/data/125node_eagle_depth_one.qpy -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_eagle_depth_zero.qpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/data/125node_eagle_depth_zero.qpy -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_example.lp: -------------------------------------------------------------------------------- 1 | \ This file has been generated by DOcplex 2 | \ ENCODING=ISO-8859-1 3 | \Problem name: CPLEX 4 | 5 | Minimize 6 | obj: [ - 8 x0^2 + 8 x0*x1 + 8 x0*x13 - 8 x1^2 + 8 x1*x2 - 8 x2*x3 + 8 x3*x4 7 | - 4 x4^2 - 8 x4*x5 + 8 x4*x14 + 8 x5*x6 - 8 x6^2 + 8 x6*x7 - 8 x7^2 8 | + 8 x7*x8 - 12 x8^2 + 8 x8*x9 + 8 x8*x15 - 8 x9*x10 + 8 x10^2 - 8 x10*x11 9 | + 8 x11*x12 - 8 x12*x16 - 8 x13*x17 - 8 x14*x21 - 8 x15*x25 + 8 x16^2 10 | - 8 x16*x29 + 8 x17*x18 - 8 x18^2 + 8 x18*x19 + 4 x19^2 - 8 x19*x20 11 | - 8 x19*x32 + 8 x20*x21 - 4 x21^2 + 8 x21*x22 - 8 x22^2 + 8 x22*x23 12 | - 12 x23^2 + 8 x23*x24 + 8 x23*x33 - 8 x24^2 + 8 x24*x25 + 4 x25^2 13 | - 8 x25*x26 + 8 x26*x27 - 4 x27^2 + 8 x27*x28 - 8 x27*x34 - 8 x28*x29 14 | + 12 x29^2 - 8 x29*x30 + 8 x30^2 - 8 x30*x31 + 8 x31^2 - 8 x31*x35 15 | + 8 x32*x38 - 8 x33*x42 + 8 x34^2 - 8 x34*x46 + 8 x35^2 - 8 x35*x50 16 | - 8 x36^2 + 8 x36*x37 + 8 x36*x51 - 8 x37*x38 - 4 x38^2 + 8 x38*x39 17 | - 8 x39*x40 + 12 x40^2 - 8 x40*x41 - 8 x40*x52 + 8 x41^2 - 8 x41*x42 18 | + 12 x42^2 - 8 x42*x43 + 8 x43*x44 - 4 x44^2 + 8 x44*x45 - 8 x44*x53 19 | - 8 x45*x46 + 12 x46^2 - 8 x46*x47 + 8 x47*x48 + 4 x48^2 - 8 x48*x49 20 | - 8 x48*x54 + 8 x49^2 - 8 x49*x50 + 8 x50^2 - 8 x51^2 + 8 x51*x55 21 | + 8 x52*x59 + 8 x53^2 - 8 x53*x63 + 8 x54*x67 - 8 x55^2 + 8 x55*x56 22 | - 8 x56^2 + 8 x56*x57 - 4 x57^2 - 8 x57*x58 + 8 x57*x70 + 8 x58^2 23 | - 8 x58*x59 - 4 x59^2 + 8 x59*x60 - 8 x60^2 + 8 x60*x61 - 4 x61^2 24 | - 8 x61*x62 + 8 x61*x71 + 8 x62^2 - 8 x62*x63 + 4 x63^2 + 8 x63*x64 25 | - 8 x64^2 + 8 x64*x65 + 4 x65^2 - 8 x65*x66 - 8 x65*x72 + 8 x66*x67 26 | - 12 x67^2 + 8 x67*x68 - 8 x68^2 + 8 x68*x69 - 8 x69^2 + 8 x69*x73 27 | - 8 x70^2 + 8 x70*x76 - 8 x71^2 + 8 x71*x80 + 8 x72*x84 - 8 x73*x88 28 | - 8 x74*x75 + 8 x74*x89 + 8 x75*x76 - 12 x76^2 + 8 x76*x77 - 8 x77*x78 29 | + 4 x78^2 + 8 x78*x79 - 8 x78*x90 - 8 x79^2 + 8 x79*x80 - 4 x80^2 30 | - 8 x80*x81 + 8 x81^2 - 8 x81*x82 - 4 x82^2 + 8 x82*x83 + 8 x82*x91 31 | - 8 x83^2 + 8 x83*x84 - 12 x84^2 + 8 x84*x85 - 8 x85^2 + 8 x85*x86 32 | - 4 x86^2 + 8 x86*x87 - 8 x86*x92 - 8 x87^2 + 8 x87*x88 - 8 x89*x93 33 | + 8 x90^2 - 8 x90*x97 - 8 x91*x101 + 8 x92^2 - 8 x92*x105 + 8 x93^2 34 | - 8 x93*x94 + 8 x94*x95 - 12 x95^2 + 8 x95*x96 + 8 x95*x108 - 8 x96*x97 35 | + 4 x97^2 + 8 x97*x98 - 8 x98*x99 + 12 x99^2 - 8 x99*x100 - 8 x99*x109 36 | + 8 x100*x101 - 4 x101^2 + 8 x101*x102 - 8 x102^2 + 8 x102*x103 + 4 x103^2 37 | - 8 x103*x104 - 8 x103*x110 + 8 x104*x105 + 4 x105^2 - 8 x105*x106 38 | + 8 x106^2 - 8 x106*x107 + 8 x107^2 - 8 x107*x111 - 8 x108*x112 39 | + 8 x109*x116 + 8 x110^2 - 8 x110*x120 + 8 x111*x124 + 8 x112^2 40 | - 8 x112*x113 + 8 x113*x114 - 8 x114*x115 + 8 x115^2 - 8 x115*x116 41 | - 4 x116^2 + 8 x116*x117 - 8 x117^2 + 8 x117*x118 - 8 x118*x119 42 | + 8 x119*x120 + 4 x120^2 - 8 x120*x121 + 8 x121^2 - 8 x121*x122 + 8 x122^2 43 | - 8 x122*x123 + 8 x123*x124 - 8 x124^2 ]/2 + 4 44 | Subject To 45 | 46 | Bounds 47 | 0 <= x0 <= 1 48 | 0 <= x1 <= 1 49 | 0 <= x2 <= 1 50 | 0 <= x3 <= 1 51 | 0 <= x4 <= 1 52 | 0 <= x5 <= 1 53 | 0 <= x6 <= 1 54 | 0 <= x7 <= 1 55 | 0 <= x8 <= 1 56 | 0 <= x9 <= 1 57 | 0 <= x10 <= 1 58 | 0 <= x11 <= 1 59 | 0 <= x12 <= 1 60 | 0 <= x13 <= 1 61 | 0 <= x14 <= 1 62 | 0 <= x15 <= 1 63 | 0 <= x16 <= 1 64 | 0 <= x17 <= 1 65 | 0 <= x18 <= 1 66 | 0 <= x19 <= 1 67 | 0 <= x20 <= 1 68 | 0 <= x21 <= 1 69 | 0 <= x22 <= 1 70 | 0 <= x23 <= 1 71 | 0 <= x24 <= 1 72 | 0 <= x25 <= 1 73 | 0 <= x26 <= 1 74 | 0 <= x27 <= 1 75 | 0 <= x28 <= 1 76 | 0 <= x29 <= 1 77 | 0 <= x30 <= 1 78 | 0 <= x31 <= 1 79 | 0 <= x32 <= 1 80 | 0 <= x33 <= 1 81 | 0 <= x34 <= 1 82 | 0 <= x35 <= 1 83 | 0 <= x36 <= 1 84 | 0 <= x37 <= 1 85 | 0 <= x38 <= 1 86 | 0 <= x39 <= 1 87 | 0 <= x40 <= 1 88 | 0 <= x41 <= 1 89 | 0 <= x42 <= 1 90 | 0 <= x43 <= 1 91 | 0 <= x44 <= 1 92 | 0 <= x45 <= 1 93 | 0 <= x46 <= 1 94 | 0 <= x47 <= 1 95 | 0 <= x48 <= 1 96 | 0 <= x49 <= 1 97 | 0 <= x50 <= 1 98 | 0 <= x51 <= 1 99 | 0 <= x52 <= 1 100 | 0 <= x53 <= 1 101 | 0 <= x54 <= 1 102 | 0 <= x55 <= 1 103 | 0 <= x56 <= 1 104 | 0 <= x57 <= 1 105 | 0 <= x58 <= 1 106 | 0 <= x59 <= 1 107 | 0 <= x60 <= 1 108 | 0 <= x61 <= 1 109 | 0 <= x62 <= 1 110 | 0 <= x63 <= 1 111 | 0 <= x64 <= 1 112 | 0 <= x65 <= 1 113 | 0 <= x66 <= 1 114 | 0 <= x67 <= 1 115 | 0 <= x68 <= 1 116 | 0 <= x69 <= 1 117 | 0 <= x70 <= 1 118 | 0 <= x71 <= 1 119 | 0 <= x72 <= 1 120 | 0 <= x73 <= 1 121 | 0 <= x74 <= 1 122 | 0 <= x75 <= 1 123 | 0 <= x76 <= 1 124 | 0 <= x77 <= 1 125 | 0 <= x78 <= 1 126 | 0 <= x79 <= 1 127 | 0 <= x80 <= 1 128 | 0 <= x81 <= 1 129 | 0 <= x82 <= 1 130 | 0 <= x83 <= 1 131 | 0 <= x84 <= 1 132 | 0 <= x85 <= 1 133 | 0 <= x86 <= 1 134 | 0 <= x87 <= 1 135 | 0 <= x88 <= 1 136 | 0 <= x89 <= 1 137 | 0 <= x90 <= 1 138 | 0 <= x91 <= 1 139 | 0 <= x92 <= 1 140 | 0 <= x93 <= 1 141 | 0 <= x94 <= 1 142 | 0 <= x95 <= 1 143 | 0 <= x96 <= 1 144 | 0 <= x97 <= 1 145 | 0 <= x98 <= 1 146 | 0 <= x99 <= 1 147 | 0 <= x100 <= 1 148 | 0 <= x101 <= 1 149 | 0 <= x102 <= 1 150 | 0 <= x103 <= 1 151 | 0 <= x104 <= 1 152 | 0 <= x105 <= 1 153 | 0 <= x106 <= 1 154 | 0 <= x107 <= 1 155 | 0 <= x108 <= 1 156 | 0 <= x109 <= 1 157 | 0 <= x110 <= 1 158 | 0 <= x111 <= 1 159 | 0 <= x112 <= 1 160 | 0 <= x113 <= 1 161 | 0 <= x114 <= 1 162 | 0 <= x115 <= 1 163 | 0 <= x116 <= 1 164 | 0 <= x117 <= 1 165 | 0 <= x118 <= 1 166 | 0 <= x119 <= 1 167 | 0 <= x120 <= 1 168 | 0 <= x121 <= 1 169 | 0 <= x122 <= 1 170 | 0 <= x123 <= 1 171 | 0 <= x124 <= 1 172 | 173 | Binaries 174 | x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 175 | x22 x23 x24 x25 x26 x27 x28 x29 x30 x31 x32 x33 x34 x35 x36 x37 x38 x39 x40 x41 176 | x42 x43 x44 x45 x46 x47 x48 x49 x50 x51 x52 x53 x54 x55 x56 x57 x58 x59 x60 x61 177 | x62 x63 x64 x65 x66 x67 x68 x69 x70 x71 x72 x73 x74 x75 x76 x77 x78 x79 x80 x81 178 | x82 x83 x84 x85 x86 x87 x88 x89 x90 x91 x92 x93 x94 x95 x96 x97 x98 x99 x100 179 | x101 x102 x103 x104 x105 x106 x107 x108 x109 x110 x111 x112 x113 x114 x115 x116 180 | x117 x118 x119 x120 x121 x122 x123 x124 181 | End 182 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/data/125node_example.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_example_ising.txt: -------------------------------------------------------------------------------- 1 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ, 1.0 2 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZI, 1.0 3 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII, -1.0 4 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII, 1.0 5 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIII, 1.0 6 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII, -1.0 7 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIII, 1.0 8 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIII, 1.0 9 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIII, 1.0 10 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIII, 1.0 11 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIII, -1.0 12 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIII, -1.0 13 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIII, 1.0 14 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZ, 1.0 15 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIII, -1.0 16 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIII, 1.0 17 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIII, -1.0 18 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIII, -1.0 19 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIII, 1.0 20 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIII, 1.0 21 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIII, -1.0 22 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIII, -1.0 23 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIII, 1.0 24 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIII, -1.0 25 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIII, 1.0 26 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIII, 1.0 27 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIII, 1.0 28 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIII, -1.0 29 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIII, 1.0 30 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 31 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 32 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 33 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 34 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 35 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 36 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 37 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 38 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIII, 1.0 39 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 40 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 41 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 42 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 43 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 44 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 45 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 46 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 47 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 48 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 49 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 50 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 51 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 52 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 53 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 54 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 55 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 56 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 57 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 58 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 59 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 60 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 61 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 62 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 63 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 64 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 65 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 66 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 67 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 68 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 69 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 70 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 71 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 72 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 73 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 74 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 75 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 76 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 77 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 78 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 79 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 80 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 81 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 82 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 83 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 84 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 85 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 86 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 87 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 88 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 89 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 90 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 91 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 92 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 93 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 94 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 95 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 96 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 97 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 98 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 99 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 100 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 101 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 102 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 103 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 104 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 105 | IIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 106 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 107 | IIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 108 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 109 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 110 | IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 111 | IIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 112 | IIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 113 | IIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 114 | IIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 115 | IIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 116 | IIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 117 | IIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 118 | IIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 119 | IIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 120 | IIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 121 | IIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 122 | IIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 123 | IIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 124 | IIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 125 | IIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 126 | IIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 127 | IIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 128 | ZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 129 | IIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 130 | IIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 131 | IIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 132 | IIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 133 | IIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 134 | IIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 135 | IIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 136 | IIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 137 | IIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 138 | IIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 139 | IIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 140 | IIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 141 | IZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, -1.0 142 | ZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII, 1.0 143 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_heron_depth_one.qpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/data/125node_heron_depth_one.qpy -------------------------------------------------------------------------------- /demos/qiskit_patterns/data/125node_heron_depth_zero.qpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/data/125node_heron_depth_zero.qpy -------------------------------------------------------------------------------- /demos/qiskit_patterns/demo_src/graph.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | def generate_demo_graph(): 7 | # Generating a graph of 5 nodes 8 | n = 5 # Number of nodes in graph 9 | G = nx.Graph() 10 | G.add_nodes_from(np.arange(0, n, 1)) 11 | elist = [(0, 1, 1.0), (0, 2, 1.0), (0, 4, 1.0), (1, 2, 1.0), (2, 3, 1.0), (3, 4, 1.0)] 12 | # tuple is (i,j,weight) where (i,j) is the edge 13 | G.add_weighted_edges_from(elist) 14 | return G 15 | 16 | 17 | def draw_graph(G): 18 | colors = ["tab:grey" for node in G.nodes()] 19 | pos = nx.spring_layout(G) 20 | default_axes = plt.axes(frameon=True) 21 | nx.draw_networkx(G, node_color=colors, node_size=600, alpha=0.8, ax=default_axes, pos=pos) 22 | edge_labels = nx.get_edge_attributes(G, "weight") 23 | nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edge_labels) 24 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/demo_src/map.py: -------------------------------------------------------------------------------- 1 | from qiskit_optimization.applications import Maxcut 2 | from qiskit.circuit.library import QAOAAnsatz 3 | 4 | 5 | def map_graph_to_qubo(graph): 6 | max_cut = Maxcut(graph) 7 | qp = max_cut.to_quadratic_program() 8 | return qp 9 | 10 | 11 | def map_qubo_to_ising(qubo): 12 | qubitOp, offset = qubo.to_ising() 13 | return qubitOp, offset 14 | 15 | 16 | def map_ising_to_circuit(hamiltonian, num_layers): 17 | ansatz = QAOAAnsatz(cost_operator=hamiltonian, reps=num_layers) 18 | ansatz.measure_all() 19 | return ansatz 20 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/demo_src/post.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import json 3 | import matplotlib 4 | import matplotlib.pyplot as plt 5 | import networkx as nx 6 | import numpy as np 7 | import os 8 | 9 | from qiskit_optimization.applications import Maxcut 10 | 11 | 12 | # auxiliary functions to sample most likely bitstring 13 | def to_bitstring(integer, num_bits): 14 | result = np.binary_repr(integer, width=num_bits) 15 | return [int(digit) for digit in result] 16 | 17 | 18 | def sample_most_likely(state_vector, num_bits): 19 | keys = list(state_vector.keys()) 20 | values = list(state_vector.values()) 21 | most_likely = keys[np.argmax(np.abs(values))] 22 | most_likely_bitstring = to_bitstring(most_likely, num_bits) 23 | most_likely_bitstring.reverse() 24 | return np.asarray(most_likely_bitstring) 25 | 26 | 27 | # auxiliary function to plot graphs 28 | def plot_result(G, x): 29 | colors = ["tab:grey" if i == 0 else "tab:purple" for i in x] 30 | pos, default_axes = nx.spring_layout(G), plt.axes(frameon=True) 31 | nx.draw_networkx(G, node_color=colors, node_size=600, alpha=0.8, pos=pos) 32 | 33 | 34 | def plot_distribution(final_distribution): 35 | matplotlib.rcParams.update({"font.size": 10}) 36 | final_bits = final_distribution.binary_probabilities() 37 | values = np.abs(list(final_bits.values())) 38 | top_4_values = sorted(values, reverse=True)[:4] 39 | positions = [] 40 | for value in top_4_values: 41 | positions.append(np.where(values == value)[0]) 42 | fig = plt.figure(figsize=(11, 6)) 43 | ax = fig.add_subplot(1, 1, 1) 44 | plt.xticks(rotation=45) 45 | plt.title("Result Distribution") 46 | plt.xlabel("Bitstrings (reversed)") 47 | plt.ylabel("Probability") 48 | ax.bar(list(final_bits.keys()), list(final_bits.values()), color="tab:grey") 49 | for p in positions: 50 | ax.get_children()[int(p)].set_color("tab:purple") 51 | plt.show() 52 | 53 | 54 | # auxiliary function to convert bit-strings to objective values 55 | def samples_to_objective_values(samples, qp): 56 | """Convert the samples to values of the objective function.""" 57 | objective_values = defaultdict(float) 58 | for bit_str, prob in samples.items(): 59 | # Qiskit use little endian hence the [::-1] 60 | candidate_sol = [int(bit) for bit in bit_str[::-1]] 61 | fval = qp.objective.evaluate(candidate_sol) 62 | objective_values[fval] += prob 63 | 64 | return objective_values 65 | 66 | 67 | # auxiliary function to load saved samples 68 | def load_data(qp): 69 | depth_one_heron, depth_zero_heron, depth_one_eagle, depth_zero_eagle = {}, {}, {}, {} 70 | for file in os.listdir("sampler_data/"): 71 | with open(f"sampler_data/{file}", "r") as fin: 72 | data = json.load(fin) 73 | 74 | if file.startswith("heron"): 75 | depth_one_heron.update(data["depth-one"]) 76 | depth_zero_heron.update(data["depth-zero"]) 77 | else: 78 | depth_one_eagle.update(data["depth-one"]) 79 | depth_zero_eagle.update(data["depth-zero"]) 80 | 81 | depth_zero_heron = samples_to_objective_values(depth_zero_heron, qp) 82 | depth_one_heron = samples_to_objective_values(depth_one_heron, qp) 83 | depth_zero_eagle = samples_to_objective_values(depth_zero_eagle, qp) 84 | depth_one_eagle = samples_to_objective_values(depth_one_eagle, qp) 85 | 86 | return depth_one_heron, depth_zero_heron, depth_one_eagle, depth_zero_eagle 87 | 88 | 89 | # auxiliary function to load the QP from the saved Paulis 90 | def load_qp(): 91 | # First, load the Paulis that encode the MaxCut problem. 92 | with open("data/125node_example_ising.txt", "r") as fin: 93 | paulis, lines = [], fin.read() 94 | for edge in lines.split("\n"): 95 | try: 96 | pauli, coefficient = edge.split(", ") 97 | paulis.append((pauli, float(coefficient))) 98 | except ValueError: 99 | pass 100 | 101 | # Next, convert the Paulis to a weighted graph. 102 | wedges = [] 103 | for pauli_str, coefficient in paulis: 104 | wedges.append( 105 | [idx for idx, char in enumerate(pauli_str[::-1]) if char == "Z"] 106 | + [{"weight": coefficient}] 107 | ) 108 | 109 | weighted_graph = nx.DiGraph(wedges) 110 | 111 | # Create the Quadratic program to return form the weighted graph. 112 | mc = Maxcut(weighted_graph) 113 | qp = mc.to_quadratic_program() 114 | 115 | # Finding the min and max requires CPLEX. If this is not installed we use 116 | # hard coded values for the sake of the demo. 117 | try: 118 | from qiskit_optimization.algorithms import CplexOptimizer 119 | from qiskit_optimization.problems.quadratic_objective import ObjSense 120 | 121 | # Mximization gives the max cut 122 | sol = CplexOptimizer().solve(qp) 123 | 124 | # Minimization gives the min cut 125 | qp2 = mc.to_quadratic_program() 126 | qp2.objective._sense = ObjSense.MINIMIZE 127 | sol2 = CplexOptimizer().solve(qp2) 128 | 129 | max_cut, min_cut = sol.fval, sol2.fval 130 | except: 131 | max_cut, min_cut = 67, -63 132 | 133 | return qp, max_cut, min_cut 134 | 135 | 136 | # auxiliary function to help plot cumulative distribution functions 137 | def _plot_cdf(objective_values: dict, ax, label): 138 | x_vals = sorted(objective_values.keys()) 139 | y_vals = np.cumsum([objective_values[x] for x in x_vals]) 140 | ax.plot(x_vals, y_vals, label=label) 141 | 142 | 143 | def plot_cdf(depth_zero, depth_one, max_cut, min_cut, ax, title): 144 | _plot_cdf(depth_zero, ax, "Depth-zero") 145 | _plot_cdf(depth_one, ax, "Depth-one") 146 | ax.vlines(max_cut, 0, 1, "k", linestyle="--") 147 | ax.vlines(max(list(depth_one.keys())), 0, 1, "C1", linestyle="--") 148 | approx = 100 * (max(list(depth_one.keys())) - min_cut) / (max_cut - min_cut) 149 | ax.set_title(title + f" {approx}%") 150 | ax.set_xlabel("Objective function value") 151 | ax.set_ylabel("Cumulative distribution function") 152 | ax.legend(loc=2) 153 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/demo_src/run.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | import json 3 | 4 | 5 | # auxiliary function to save result data 6 | def save_result(result, backend_type, path="sampler_data"): 7 | results_to_save = { 8 | "depth-one": result.quasi_dists[0].binary_probabilities(), 9 | "depth-zero": result.quasi_dists[1].binary_probabilities(), 10 | } 11 | 12 | with open(f"{path}/{backend_type}_{str(uuid4())[0:8]}.json", "w") as fout: 13 | json.dump(results_to_save, fout) 14 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/demo_src/transpile.py: -------------------------------------------------------------------------------- 1 | from qiskit import transpile 2 | 3 | 4 | def optimize_circuit(circuit, backend): 5 | return transpile(circuit, backend=backend) 6 | -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/circuit-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/circuit-0.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/circuit.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/eagle-heron-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/eagle-heron-0.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/eagle-heron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/eagle-heron.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/exec-modes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/exec-modes.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/max-cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/max-cut.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/imgs/patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/demos/qiskit_patterns/imgs/patterns.png -------------------------------------------------------------------------------- /demos/qiskit_patterns/requirements.txt: -------------------------------------------------------------------------------- 1 | qiskit>=1.0.0 2 | qiskit_ibm_runtime>=0.18.0 3 | qiskit_optimization>=0.6.0 4 | qiskit_aer 5 | matplotlib 6 | notebook 7 | ipykernel 8 | pylatexenc -------------------------------------------------------------------------------- /how_tos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/how_tos/__init__.py -------------------------------------------------------------------------------- /how_tos/data/graph_2layers_0seed.json: -------------------------------------------------------------------------------- 1 | {"Original graph": [[0, 7], [0, 3], [0, 5], [7, 9], [7, 8], [1, 2], [1, 4], [1, 6], [2, 3], [2, 8], [5, 8], [5, 3], [4, 9], [4, 6], [9, 6]], "SAT mapping": {"0": 6, "1": 2, "2": 7, "3": 8, "4": 1, "5": 9, "6": 3, "7": 5, "8": 4, "9": 0}, "paulis": [["IIIIIZZIII", 1.0], ["IIIIIIZIZI", 1.0], ["IIIIIIZIIZ", 1.0], ["ZIIIIZIIII", 1.0], ["IIIIZZIIII", 1.0], ["IIZIIIIZII", 1.0], ["IZZIIIIIII", 1.0], ["IIZZIIIIII", 1.0], ["IIIIIIIZZI", 1.0], ["IIIIZIIZII", 1.0], ["IIIIZIIIIZ", 1.0], ["IIIIIIIIZZ", 1.0], ["ZZIIIIIIII", 1.0], ["IZIZIIIIII", 1.0], ["ZIIZIIIIII", 1.0]], "description": "paulis are remapped with `SAT mapping`.", "min swap layers": 2, "nx seed": 0} -------------------------------------------------------------------------------- /how_tos/data/naive_transpilation.json: -------------------------------------------------------------------------------- 1 | {"method": "Naive", "time": 2178.7279443740845, "ops": {"sx": 24249, "rz": 18917, "cz": 13298, "measure": 133, "x": 43, "barrier": 1}, "depth": 6814} -------------------------------------------------------------------------------- /how_tos/how_to_select_qubits.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true, 7 | "jupyter": { 8 | "outputs_hidden": true 9 | }, 10 | "pycharm": { 11 | "name": "#%% md\n" 12 | } 13 | }, 14 | "source": [ 15 | "# How to select the qubit subset to run your circuit on HW\n", 16 | "\n", 17 | "The `BackendEvaluator` util allows you to find the best subset of qubits of a given **geometry** according to a given **metric**. This is useful to define the initial layout for the circuit transpilation to run on hardware." 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": { 23 | "collapsed": false, 24 | "jupyter": { 25 | "outputs_hidden": false 26 | } 27 | }, 28 | "source": [ 29 | "\n", 30 | "## 1. Running with default settings\n", 31 | "\n", 32 | "By default, the `BackendEvaluator` class will try to find the line (geometry) witht the best 2-qubit gate fidelity (metric) for a given backend. Let's see this in action for 10 qubits in a fake 16-qubit backend." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 1, 38 | "metadata": { 39 | "collapsed": false, 40 | "jupyter": { 41 | "outputs_hidden": false 42 | }, 43 | "pycharm": { 44 | "name": "#%%\n" 45 | } 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "# SIMULATED FAKE BACKEND\n", 50 | "from qiskit_ibm_runtime.fake_provider import FakeGuadalupe\n", 51 | "\n", 52 | "backend = FakeGuadalupe()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": { 59 | "collapsed": false, 60 | "jupyter": { 61 | "outputs_hidden": false 62 | }, 63 | "pycharm": { 64 | "name": "#%%\n" 65 | } 66 | }, 67 | "outputs": [ 68 | { 69 | "data": { 70 | "image/png": "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", 71 | "text/plain": [ 72 | "
" 73 | ] 74 | }, 75 | "execution_count": 2, 76 | "metadata": {}, 77 | "output_type": "execute_result" 78 | } 79 | ], 80 | "source": [ 81 | "from qiskit.visualization import plot_gate_map\n", 82 | "\n", 83 | "plot_gate_map(backend)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 3, 89 | "metadata": { 90 | "collapsed": false, 91 | "jupyter": { 92 | "outputs_hidden": false 93 | }, 94 | "pycharm": { 95 | "name": "#%%\n" 96 | } 97 | }, 98 | "outputs": [ 99 | { 100 | "name": "stdout", 101 | "output_type": "stream", 102 | "text": [ 103 | "Best path: [1, 4, 7, 10, 12, 13, 14, 11, 8, 5]\n", 104 | "Best path fidelity 0.9099375156097996\n", 105 | "Num. evaluated paths 20\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "from qopt_best_practices.qubit_selection import BackendEvaluator\n", 111 | "\n", 112 | "num_qubits = 10\n", 113 | "path_finder = BackendEvaluator(backend)\n", 114 | "path, fidelity, num_subsets = path_finder.evaluate(num_qubits)\n", 115 | "\n", 116 | "print(\"Best path: \", path)\n", 117 | "print(\"Best path fidelity\", fidelity)\n", 118 | "print(\"Num. evaluated paths\", num_subsets)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": { 124 | "collapsed": false, 125 | "jupyter": { 126 | "outputs_hidden": false 127 | }, 128 | "pycharm": { 129 | "name": "#%% md\n" 130 | } 131 | }, 132 | "source": [ 133 | "## 2. Running with custom evaluation criteria\n", 134 | "\n", 135 | "The evaluation criteria can also be defined by the user and given to the `BackendEvaluator` class as a callable:\n" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 4, 141 | "metadata": { 142 | "collapsed": false, 143 | "jupyter": { 144 | "outputs_hidden": false 145 | }, 146 | "pycharm": { 147 | "name": "#%%\n" 148 | } 149 | }, 150 | "outputs": [], 151 | "source": [ 152 | "from __future__ import annotations\n", 153 | "import numpy as np\n", 154 | "import rustworkx as rx\n", 155 | "\n", 156 | "from qiskit.transpiler import CouplingMap\n", 157 | "\n", 158 | "\n", 159 | "def find_lines(length: int, backend, coupling_map: CouplingMap | None = None) -> list[int]:\n", 160 | " \"\"\"Finds all possible lines of length `length` for a specific backend topology.\n", 161 | "\n", 162 | " This method can take quite some time to run on large devices since there\n", 163 | " are many paths.\n", 164 | "\n", 165 | " Returns:\n", 166 | " The found paths.\n", 167 | " \"\"\"\n", 168 | "\n", 169 | " # might make sense to make backend the only input for simplicity\n", 170 | " if coupling_map is None:\n", 171 | " coupling_map = CouplingMap(backend.configuration().coupling_map)\n", 172 | "\n", 173 | " all_paths = rx.all_pairs_all_simple_paths(\n", 174 | " coupling_map.graph,\n", 175 | " min_depth=length,\n", 176 | " cutoff=length,\n", 177 | " ).values()\n", 178 | "\n", 179 | " paths = np.asarray(\n", 180 | " [\n", 181 | " (list(c), list(sorted(list(c))))\n", 182 | " for a in iter(all_paths)\n", 183 | " for b in iter(a)\n", 184 | " for c in iter(a[b])\n", 185 | " ]\n", 186 | " )\n", 187 | "\n", 188 | " # filter out duplicated paths\n", 189 | " _, unique_indices = np.unique(paths[:, 1], return_index=True, axis=0)\n", 190 | " paths = paths[:, 0][unique_indices].tolist()\n", 191 | "\n", 192 | " return paths" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 5, 198 | "metadata": { 199 | "collapsed": false, 200 | "jupyter": { 201 | "outputs_hidden": false 202 | }, 203 | "pycharm": { 204 | "name": "#%%\n" 205 | } 206 | }, 207 | "outputs": [], 208 | "source": [ 209 | "def evaluate_fidelity(path: list[int], backend, edges) -> float:\n", 210 | " \"\"\"Evaluates fidelity on a given list of qubits based on the two-qubit gate error\n", 211 | " for a specific backend.\n", 212 | "\n", 213 | " Returns:\n", 214 | " Path fidelity.\n", 215 | " \"\"\"\n", 216 | "\n", 217 | " two_qubit_fidelity = {}\n", 218 | " props = backend.properties()\n", 219 | "\n", 220 | " if \"cx\" in backend.configuration().basis_gates:\n", 221 | " gate_name = \"cx\"\n", 222 | " elif \"ecr\" in backend.configuration().basis_gates:\n", 223 | " gate_name = \"ecr\"\n", 224 | " else:\n", 225 | " raise ValueError(\"Could not identify two-qubit gate\")\n", 226 | "\n", 227 | " for edge in edges:\n", 228 | " try:\n", 229 | " cx_error = props.gate_error(gate_name, edge)\n", 230 | "\n", 231 | " except:\n", 232 | " cx_error = props.gate_error(gate_name, edge[::-1])\n", 233 | "\n", 234 | " two_qubit_fidelity[tuple(edge)] = 1 - cx_error\n", 235 | "\n", 236 | " if not path or len(path) == 1:\n", 237 | " return 0.0\n", 238 | "\n", 239 | " fidelity = 1.0\n", 240 | " for idx in range(len(path) - 1):\n", 241 | " fidelity *= two_qubit_fidelity[(path[idx], path[idx + 1])]\n", 242 | "\n", 243 | " return fidelity" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 6, 249 | "metadata": { 250 | "collapsed": false, 251 | "jupyter": { 252 | "outputs_hidden": false 253 | }, 254 | "pycharm": { 255 | "name": "#%%\n" 256 | } 257 | }, 258 | "outputs": [ 259 | { 260 | "name": "stdout", 261 | "output_type": "stream", 262 | "text": [ 263 | "Best path: [1, 4, 7, 10, 12, 13, 14, 11, 8, 5]\n", 264 | "Best path fidelity 0.9099375156097996\n", 265 | "Num. evaluated paths 20\n" 266 | ] 267 | } 268 | ], 269 | "source": [ 270 | "num_qubits = 10\n", 271 | "path_finder = BackendEvaluator(backend)\n", 272 | "path, fidelity, num_subsets = path_finder.evaluate(\n", 273 | " num_qubits, subset_finder=find_lines, metric_eval=evaluate_fidelity\n", 274 | ")\n", 275 | "\n", 276 | "print(\"Best path: \", path)\n", 277 | "print(\"Best path fidelity\", fidelity)\n", 278 | "print(\"Num. evaluated paths\", num_subsets)\n" 279 | ] 280 | } 281 | ], 282 | "metadata": { 283 | "kernelspec": { 284 | "display_name": "ibm_tech", 285 | "language": "python", 286 | "name": "ibm_tech" 287 | }, 288 | "language_info": { 289 | "codemirror_mode": { 290 | "name": "ipython", 291 | "version": 3 292 | }, 293 | "file_extension": ".py", 294 | "mimetype": "text/x-python", 295 | "name": "python", 296 | "nbconvert_exporter": "python", 297 | "pygments_lexer": "ipython3", 298 | "version": "3.10.0" 299 | } 300 | }, 301 | "nbformat": 4, 302 | "nbformat_minor": 4 303 | } 304 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.black] 6 | line-length = 100 7 | target-version = ['py38', 'py39', 'py310', 'py311'] 8 | 9 | [tool.pylint.main] 10 | py-version = "3.8" # update it when bumping minimum supported python version 11 | 12 | [tool.pylint.basic] 13 | good-names = ["a", "b", "i", "j", "k", "d", "n", "m", "ex", "v", "w", "x", "y", "z", "Run", "_", "logger", "q", "c", "r", "qr", "cr", "qc", "nd", "pi", "op", "b", "ar", "br", "p", "cp", "ax", "dt", "__unittest", "iSwapGate", "mu"] 14 | method-rgx = "(([a-z_][a-z0-9_]{2,49})|(assert[A-Z][a-zA-Z0-9]{2,43})|(test_[_a-zA-Z0-9]{2,}))$" 15 | variable-rgx = "[a-z_][a-z0-9_]{1,30}$" 16 | 17 | [tool.pylint.format] 18 | max-line-length = 105 # default 100 19 | 20 | [tool.pylint."messages control"] 21 | disable = [ 22 | # intentionally disabled: 23 | "spelling", # too noisy 24 | "fixme", # disabled as TODOs would show up as warnings 25 | "protected-access", # disabled as we don't follow the public vs private convention strictly 26 | "duplicate-code", # disabled as it is too verbose 27 | "redundant-returns-doc", # for @abstractmethod, it cannot interpret "pass" 28 | "too-many-lines", "too-many-branches", "too-many-locals", "too-many-nested-blocks", "too-many-statements", 29 | "too-many-instance-attributes", "too-many-arguments", "too-many-public-methods", "too-few-public-methods", "too-many-ancestors", 30 | "unnecessary-pass", # allow for methods with just "pass", for clarity 31 | "no-else-return", # relax "elif" after a clause with a return 32 | "docstring-first-line-empty", # relax docstring style 33 | "import-outside-toplevel", "import-error", # overzealous with our optionals/dynamic packages 34 | # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so, 35 | # remove from here and fix the issues. Else, move it above this section and add a comment 36 | # with the rationale 37 | "arguments-renamed", 38 | "broad-exception-raised", 39 | "consider-iterating-dictionary", 40 | "consider-using-dict-items", 41 | "consider-using-enumerate", 42 | "consider-using-f-string", 43 | "modified-iterating-list", 44 | "nested-min-max", 45 | "no-member", 46 | "no-value-for-parameter", 47 | "non-ascii-name", 48 | "not-context-manager", 49 | "superfluous-parens", 50 | "unknown-option-value", 51 | "unexpected-keyword-arg", 52 | "unnecessary-dict-index-lookup", 53 | "unnecessary-direct-lambda-call", 54 | "unnecessary-dunder-call", 55 | "unnecessary-ellipsis", 56 | "unnecessary-lambda-assignment", 57 | "unnecessary-list-index-lookup", 58 | "unspecified-encoding", 59 | "unsupported-assignment-operation", 60 | "use-dict-literal", 61 | "use-list-literal", 62 | "use-implicit-booleaness-not-comparison", 63 | "use-maxsplit-arg", 64 | ] 65 | 66 | enable = [ 67 | "use-symbolic-message-instead" 68 | ] 69 | 70 | [tool.pylint.spelling] 71 | spelling-private-dict-file = ".pylintdict" 72 | spelling-store-unknown-words = "n" 73 | -------------------------------------------------------------------------------- /qopt_best_practices/VERSION.txt: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /qopt_best_practices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/qopt_best_practices/__init__.py -------------------------------------------------------------------------------- /qopt_best_practices/cost_function/__init__.py: -------------------------------------------------------------------------------- 1 | """Cost function""" 2 | 3 | from .cost_utils import evaluate_sparse_pauli, qaoa_sampler_cost_fun 4 | 5 | __all__ = ["evaluate_sparse_pauli", "qaoa_sampler_cost_fun"] 6 | -------------------------------------------------------------------------------- /qopt_best_practices/cost_function/cost_utils.py: -------------------------------------------------------------------------------- 1 | """QAOA Cost function utils""" 2 | 3 | import numpy as np 4 | from qiskit.quantum_info import SparsePauliOp 5 | 6 | _PARITY = np.array([-1 if bin(i).count("1") % 2 else 1 for i in range(256)], dtype=np.complex128) 7 | 8 | 9 | def evaluate_sparse_pauli(state: int, observable: SparsePauliOp) -> complex: 10 | """Utility for the evaluation of the expectation value of a measured state.""" 11 | packed_uint8 = np.packbits(observable.paulis.z, axis=1, bitorder="little") 12 | state_bytes = np.frombuffer(state.to_bytes(packed_uint8.shape[1], "little"), dtype=np.uint8) 13 | reduced = np.bitwise_xor.reduce(packed_uint8 & state_bytes, axis=1) 14 | return np.sum(observable.coeffs * _PARITY[reduced]) 15 | 16 | 17 | def qaoa_sampler_cost_fun(params, ansatz, hamiltonian, sampler): 18 | """Standard sampler-based QAOA cost function to be plugged into optimizer routines.""" 19 | job = sampler.run(ansatz, params) 20 | sampler_result = job.result() 21 | sampled = sampler_result.quasi_dists[0] 22 | 23 | # a dictionary containing: {state: (measurement probability, value)} 24 | evaluated = { 25 | state: (probability, evaluate_sparse_pauli(state, hamiltonian)) 26 | for state, probability in sampled.items() 27 | } 28 | 29 | result = sum(probability * value for probability, value in evaluated.values()) 30 | 31 | return result 32 | -------------------------------------------------------------------------------- /qopt_best_practices/error_mitigation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/qopt_best_practices/error_mitigation/__init__.py -------------------------------------------------------------------------------- /qopt_best_practices/qubit_selection/__init__.py: -------------------------------------------------------------------------------- 1 | """Qubit selection""" 2 | 3 | from .backend_evaluator import BackendEvaluator 4 | from .metric_evaluators import evaluate_fidelity 5 | from .qubit_subset_finders import find_lines 6 | 7 | __all__ = ["BackendEvaluator", "evaluate_fidelity", "find_lines"] 8 | -------------------------------------------------------------------------------- /qopt_best_practices/qubit_selection/backend_evaluator.py: -------------------------------------------------------------------------------- 1 | """Backend Evaluator""" 2 | 3 | from __future__ import annotations 4 | from collections.abc import Callable 5 | 6 | from qiskit.transpiler import CouplingMap 7 | from qiskit.providers import Backend 8 | 9 | from .metric_evaluators import evaluate_fidelity 10 | from .qubit_subset_finders import find_lines 11 | 12 | 13 | class BackendEvaluator: 14 | """ 15 | Finds best subset of qubits for a given device that maximizes a given 16 | metric for a given geometry. 17 | This subset can be provided as an initial_layout for the SwapStrategy 18 | transpiler pass. 19 | """ 20 | 21 | def __init__(self, backend: Backend): 22 | self.backend = backend 23 | if backend.version == 2: 24 | coupling_map = CouplingMap(backend.coupling_map) 25 | else: 26 | coupling_map = CouplingMap(backend.configuration().coupling_map) 27 | self.coupling_map = coupling_map 28 | if not self.coupling_map.is_symmetric: 29 | self.coupling_map.make_symmetric() 30 | 31 | def evaluate( 32 | self, 33 | num_qubits: int, 34 | subset_finder: Callable | None = None, 35 | metric_eval: Callable | None = None, 36 | ): 37 | """ 38 | Args: 39 | num_qubits: the number of qubits 40 | subset_finder: callable, will default to "find_line" 41 | metric_eval: callable, will default to "evaluate_fidelity" 42 | """ 43 | 44 | if metric_eval is None: 45 | metric_eval = evaluate_fidelity 46 | 47 | if subset_finder is None: 48 | subset_finder = find_lines 49 | 50 | # TODO: add callbacks 51 | qubit_subsets = subset_finder(num_qubits, self.backend) 52 | 53 | # evaluating the subsets 54 | scores = [ 55 | metric_eval(subset, self.backend, self.coupling_map.get_edges()) 56 | for subset in qubit_subsets 57 | ] 58 | 59 | # Return the best subset sorted by score 60 | best_subset, best_score = min(zip(qubit_subsets, scores), key=lambda x: -x[1]) 61 | num_subsets = len(qubit_subsets) 62 | 63 | return best_subset, best_score, num_subsets 64 | -------------------------------------------------------------------------------- /qopt_best_practices/qubit_selection/metric_evaluators.py: -------------------------------------------------------------------------------- 1 | """Subset finders. Currently contains reference implementation 2 | to evaluate 2-qubit gate fidelity.""" 3 | 4 | from __future__ import annotations 5 | from qiskit.providers import Backend 6 | import rustworkx as rx 7 | 8 | TWO_Q_GATES = ["cx", "ecr", "cz"] 9 | 10 | 11 | def evaluate_fidelity(path: list[int], backend: Backend, edges: rx.EdgeList) -> float: 12 | """Evaluates fidelity on a given list of qubits based on the two-qubit gate error 13 | for a specific backend. 14 | 15 | Returns: 16 | Path fidelity. 17 | """ 18 | 19 | two_qubit_fidelity = {} 20 | 21 | if backend.version == 2: 22 | target = backend.target 23 | try: 24 | gate_name = list(set(TWO_Q_GATES).intersection(backend.operation_names))[0] 25 | except IndexError as exc: 26 | raise ValueError("Could not identify two-qubit gate") from exc 27 | 28 | for edge in edges: 29 | try: 30 | cx_error = target[gate_name][edge].error 31 | except: # pylint: disable=bare-except 32 | cx_error = target[gate_name][edge[::-1]].error 33 | 34 | two_qubit_fidelity[tuple(edge)] = 1 - cx_error 35 | else: 36 | props = backend.properties() 37 | try: 38 | gate_name = list(set(TWO_Q_GATES).intersection(backend.configuration().basis_gates))[0] 39 | except IndexError as exc: 40 | raise ValueError("Could not identify two-qubit gate") from exc 41 | 42 | for edge in edges: 43 | try: 44 | cx_error = props.gate_error(gate_name, edge) 45 | except: # pylint: disable=bare-except 46 | cx_error = props.gate_error(gate_name, edge[::-1]) 47 | 48 | two_qubit_fidelity[tuple(edge)] = 1 - cx_error 49 | 50 | if not path or len(path) == 1: 51 | return 0.0 52 | 53 | fidelity = 1.0 54 | for idx in range(len(path) - 1): 55 | fidelity *= two_qubit_fidelity[(path[idx], path[idx + 1])] 56 | return fidelity 57 | -------------------------------------------------------------------------------- /qopt_best_practices/qubit_selection/qubit_subset_finders.py: -------------------------------------------------------------------------------- 1 | """Subset finders. Currently contains reference implementation 2 | to find lines.""" 3 | 4 | from __future__ import annotations 5 | import numpy as np 6 | import rustworkx as rx 7 | 8 | from qiskit.transpiler import CouplingMap 9 | from qiskit.providers import Backend 10 | 11 | 12 | def find_lines(length: int, backend: Backend) -> list[int]: 13 | """Finds all possible lines of length `length` for a specific backend topology. 14 | 15 | This method can take quite some time to run on large devices since there 16 | are many paths. 17 | 18 | Returns: 19 | The found paths. 20 | """ 21 | 22 | if backend.version == 2: 23 | coupling_map = CouplingMap(backend.coupling_map) 24 | else: 25 | coupling_map = CouplingMap(backend.configuration().coupling_map) 26 | 27 | if not coupling_map.is_symmetric: 28 | coupling_map.make_symmetric() 29 | 30 | all_paths = rx.all_pairs_all_simple_paths( 31 | coupling_map.graph, 32 | min_depth=length, 33 | cutoff=length, 34 | ).values() 35 | 36 | paths = np.asarray( 37 | [ 38 | (list(c), list(sorted(list(c)))) 39 | for a in iter(all_paths) 40 | for b in iter(a) 41 | for c in iter(a[b]) 42 | ] 43 | ) 44 | 45 | # filter out duplicated paths 46 | _, unique_indices = np.unique(paths[:, 1], return_index=True, axis=0) 47 | paths = paths[:, 0][unique_indices].tolist() 48 | 49 | return paths 50 | -------------------------------------------------------------------------------- /qopt_best_practices/sat_mapping/__init__.py: -------------------------------------------------------------------------------- 1 | """SAT Mapping""" 2 | 3 | from .sat_mapper import SATMapper, SATResult 4 | 5 | __all__ = ["SATMapper", "SATResult"] 6 | -------------------------------------------------------------------------------- /qopt_best_practices/sat_mapping/sat_mapper.py: -------------------------------------------------------------------------------- 1 | """A class to solve the SWAP gate insertion initial mapping problem 2 | using the SAT approach from https://arxiv.org/abs/2212.05666. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from dataclasses import dataclass 8 | from itertools import combinations 9 | from threading import Timer 10 | 11 | import networkx as nx 12 | import numpy as np 13 | 14 | from pysat.formula import CNF, IDPool 15 | from pysat.solvers import Solver 16 | 17 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import SwapStrategy 18 | 19 | 20 | @dataclass 21 | class SATResult: 22 | """A data class to hold the result of a SAT solver.""" 23 | 24 | satisfiable: bool # Satisfiable is True if the SAT model could be solved in a given time. 25 | solution: dict # The solution to the SAT problem if it is satisfiable. 26 | mapping: list # The mapping of nodes in the pattern graph to nodes in the target graph. 27 | elapsed_time: float # The time it took to solve the SAT model. 28 | 29 | 30 | class SATMapper: 31 | r"""A class to introduce a SAT-approach to solve 32 | the initial mapping problem in SWAP gate insertion for commuting gates. 33 | 34 | When this pass is run on a DAG it will look for the first instance of 35 | :class:`.Commuting2qBlock` and use the program graph :math:`P` of this block of gates to 36 | find a layout for a given swap strategy. This layout is found with a 37 | binary search over the layers :math:`l` of the swap strategy. At each considered layer 38 | a subgraph isomorphism problem formulated as a SAT is solved by a SAT solver. Each instance 39 | is whether it is possible to embed the program graph :math:`P` into the effective 40 | connectivity graph :math:`C_l` that is achieved by applying :math:`l` layers of the 41 | swap strategy to the coupling map :math:`C_0` of the backend. Since solving SAT problems 42 | can be hard, a ``time_out`` fixes the maximum time allotted to the SAT solver for each 43 | instance. If this time is exceeded the considered problem is deemed unsatisfiable and 44 | the binary search proceeds to the next number of swap layers :math:``l``. 45 | """ 46 | 47 | def __init__(self, timeout: int = 60): 48 | """Initialize the SATMapping. 49 | 50 | Args: 51 | timeout: The allowed time in seconds for each iteration of the SAT solver. This 52 | variable defaults to 60 seconds. 53 | """ 54 | self.timeout = timeout 55 | 56 | def find_initial_mappings( 57 | self, 58 | program_graph: nx.Graph, 59 | swap_strategy: SwapStrategy, 60 | min_layers: int | None = None, 61 | max_layers: int | None = None, 62 | ) -> dict[int, SATResult]: 63 | r"""Find an initial mapping for a given swap strategy. Perform a binary search 64 | over the number of swap layers, and for each number of swap layers solve a 65 | subgraph isomorphism problem formulated as a SAT problem. 66 | 67 | Args: 68 | program_graph (nx.Graph): The program graph with commuting gates, where 69 | each edge represents a two-qubit gate. 70 | swap_strategy (SwapStrategy): The swap strategy to use to find the initial mapping. 71 | min_layers (int): The minimum number of swap layers to consider. Defaults to 72 | the maximum degree of the program graph - 2. 73 | max_layers (int): The maximum number of swap layers to consider. Defaults to 74 | the number of qubits in the swap strategy - 2. 75 | 76 | Returns: 77 | dict[int, SATResult]: A dictionary containing the results of the SAT solver for 78 | each number of swap layers. 79 | """ 80 | # pylint: disable=too-many-locals 81 | num_nodes_g1 = len(program_graph.nodes) 82 | num_nodes_g2 = swap_strategy.distance_matrix.shape[0] 83 | if num_nodes_g1 > num_nodes_g2: 84 | return SATResult(False, [], [], 0) 85 | if min_layers is None: 86 | # use the maximum degree of the program graph - 2 as the lower bound. 87 | min_layers = max((d for _, d in program_graph.degree)) - 2 88 | if max_layers is None: 89 | max_layers = num_nodes_g2 - 1 90 | 91 | variable_pool = IDPool(start_from=1) 92 | variables = np.array( 93 | [ 94 | [variable_pool.id(f"v_{i}_{j}") for j in range(num_nodes_g2)] 95 | for i in range(num_nodes_g1) 96 | ], 97 | dtype=int, 98 | ) 99 | vid2mapping = {v: idx for idx, v in np.ndenumerate(variables)} 100 | binary_search_results = {} 101 | 102 | def interrupt(solver): 103 | # This function is called to interrupt the solver when the timeout is reached. 104 | solver.interrupt() 105 | 106 | # Make a cnf for the one-to-one mapping constraint 107 | cnf1 = [] 108 | for i in range(num_nodes_g1): 109 | clause = variables[i, :].tolist() 110 | cnf1.append(clause) 111 | for k, m in combinations(clause, 2): 112 | cnf1.append([-1 * k, -1 * m]) 113 | for j in range(num_nodes_g2): 114 | clause = variables[:, j].tolist() 115 | for k, m in combinations(clause, 2): 116 | cnf1.append([-1 * k, -1 * m]) 117 | 118 | # Perform a binary search over the number of swap layers to find the minimum 119 | # number of swap layers that satisfies the subgraph isomorphism problem. 120 | while min_layers < max_layers: 121 | num_layers = (min_layers + max_layers) // 2 122 | 123 | # Create the connectivity matrix. Note that if the swap strategy cannot reach 124 | # full connectivity then its distance matrix will have entries with -1. These 125 | # entries must be treated as False. 126 | d_matrix = swap_strategy.distance_matrix 127 | connectivity_matrix = ((-1 < d_matrix) & (d_matrix <= num_layers)).astype(int) 128 | # Make a cnf for the adjacency constraint 129 | cnf2 = [] 130 | for e_0, e_1 in program_graph.edges: 131 | clause_matrix = np.multiply(connectivity_matrix, variables[e_1, :]) 132 | clause = np.concatenate( 133 | ( 134 | [[-variables[e_0, i]] for i in range(num_nodes_g2)], 135 | clause_matrix, 136 | ), 137 | axis=1, 138 | ) 139 | # Remove 0s from each clause 140 | cnf2.extend([c[c != 0].tolist() for c in clause]) 141 | 142 | cnf = CNF(from_clauses=cnf1 + cnf2) 143 | 144 | with Solver(bootstrap_with=cnf, use_timer=True) as solver: 145 | # Solve the SAT problem with a timeout. 146 | # Timer is used to interrupt the solver when the timeout is reached. 147 | timer = Timer(self.timeout, interrupt, [solver]) 148 | timer.start() 149 | status = solver.solve_limited(expect_interrupt=True) 150 | timer.cancel() 151 | # Get the solution and the elapsed time. 152 | sol = solver.get_model() 153 | e_time = solver.time() 154 | 155 | if status: 156 | # If the SAT problem is satisfiable, convert the solution to a mapping. 157 | mapping = [vid2mapping[idx] for idx in sol if idx > 0] 158 | binary_search_results[num_layers] = SATResult(status, sol, mapping, e_time) 159 | max_layers = num_layers 160 | else: 161 | # If the SAT problem is unsatisfiable, return the last satisfiable solution. 162 | binary_search_results[num_layers] = SATResult(status, sol, [], e_time) 163 | min_layers = num_layers + 1 164 | 165 | return binary_search_results 166 | 167 | def remap_graph_with_sat( 168 | self, graph: nx.Graph, swap_strategy 169 | ) -> tuple[int, dict, list] | tuple[None, None, None]: 170 | """Applies the SAT mapping. 171 | 172 | Args: 173 | graph (nx.Graph): The graph to remap. 174 | swap_strategy (SwapStrategy): The swap strategy to use to find the initial mapping. 175 | 176 | Returns: 177 | tuple: A tuple containing the remapped graph, the edge map, and the number of layers of 178 | the swap strategy that was used to find the initial mapping. If no solution is found 179 | then the tuple contains None for each element. 180 | Note the returned edge map `{k: v}` means that node `k` in the original 181 | graph gets mapped to node `v` in the Pauli strings. 182 | """ 183 | num_nodes = len(graph.nodes) 184 | results = self.find_initial_mappings(graph, swap_strategy, 0, num_nodes - 1) 185 | solutions = [k for k, v in results.items() if v.satisfiable] 186 | if len(solutions): 187 | min_k = min(solutions) 188 | edge_map = dict(results[min_k].mapping) 189 | remapped_graph = nx.relabel_nodes(graph, edge_map) 190 | return remapped_graph, edge_map, min_k 191 | else: 192 | return None, None, None 193 | -------------------------------------------------------------------------------- /qopt_best_practices/swap_strategies/__init__.py: -------------------------------------------------------------------------------- 1 | """SWAP strategies""" 2 | 3 | from .build_circuit import ( 4 | create_qaoa_swap_circuit, 5 | make_meas_map, 6 | apply_swap_strategy, 7 | apply_qaoa_layers, 8 | ) 9 | 10 | __all__ = [ 11 | "create_qaoa_swap_circuit", 12 | "make_meas_map", 13 | "apply_swap_strategy", 14 | "apply_qaoa_layers", 15 | ] 16 | -------------------------------------------------------------------------------- /qopt_best_practices/swap_strategies/build_circuit.py: -------------------------------------------------------------------------------- 1 | """Circuit utils""" 2 | 3 | from __future__ import annotations 4 | 5 | from qiskit.circuit import QuantumCircuit 6 | from qiskit.quantum_info import SparsePauliOp 7 | 8 | from qiskit.transpiler import PassManager 9 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import ( 10 | SwapStrategy, 11 | FindCommutingPauliEvolutions, 12 | Commuting2qGateRouter, 13 | ) 14 | 15 | from qiskit.circuit.library import QAOAAnsatz 16 | from qiskit.circuit import ParameterVector 17 | 18 | 19 | def make_meas_map(circuit: QuantumCircuit) -> dict: 20 | """Return a mapping from qubit index (the key) to classical bit (the value). 21 | 22 | This allows us to account for the swapping order introduced by the SwapStrategy. 23 | """ 24 | creg = circuit.cregs[0] 25 | qreg = circuit.qregs[0] 26 | 27 | meas_map = {} 28 | for inst in circuit.data: 29 | if inst.operation.name == "measure": 30 | meas_map[qreg.index(inst.qubits[0])] = creg.index(inst.clbits[0]) 31 | 32 | return meas_map 33 | 34 | 35 | def apply_swap_strategy( 36 | circuit: QuantumCircuit, 37 | swap_strategy: SwapStrategy, 38 | edge_coloring: dict[tuple[int, int], int] | None = None, 39 | ) -> QuantumCircuit: 40 | """Transpile with a SWAP strategy. 41 | 42 | Returns: 43 | A quantum circuit transpiled with the given swap strategy. 44 | """ 45 | 46 | pm_pre = PassManager( 47 | [ 48 | FindCommutingPauliEvolutions(), 49 | Commuting2qGateRouter( 50 | swap_strategy, 51 | edge_coloring, 52 | ), 53 | ] 54 | ) 55 | return pm_pre.run(circuit) 56 | 57 | 58 | def apply_qaoa_layers( # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments 59 | cost_layer: QuantumCircuit, 60 | meas_map: dict, 61 | num_layers: int, 62 | gamma: list[float] | ParameterVector = None, 63 | beta: list[float] | ParameterVector = None, 64 | initial_state: QuantumCircuit = None, 65 | mixer: QuantumCircuit = None, 66 | ): 67 | """Applies QAOA layers to construct circuit. 68 | 69 | First, the initial state is applied. If `initial_state` is None we begin in the 70 | initial superposition state. Next, we alternate between layers of the cot operator 71 | and the mixer. The cost operator is alternatively applied in order and in reverse 72 | instruction order. This allows us to apply the swap-strategy on odd `p` layers 73 | and undo the swap strategy on even `p` layers. 74 | """ 75 | 76 | num_qubits = cost_layer.num_qubits 77 | new_circuit = QuantumCircuit(num_qubits, num_qubits) 78 | 79 | if initial_state is not None: 80 | new_circuit.append(initial_state, range(num_qubits)) 81 | else: 82 | # all h state by default 83 | new_circuit.h(range(num_qubits)) 84 | 85 | if gamma is None or beta is None: 86 | gamma = ParameterVector("γ", num_layers) 87 | if mixer is None or mixer.num_parameters == 0: 88 | beta = ParameterVector("β", num_layers) 89 | else: 90 | beta = ParameterVector("β", num_layers * mixer.num_parameters) 91 | 92 | if mixer is not None: 93 | mixer_layer = mixer 94 | else: 95 | mixer_layer = QuantumCircuit(num_qubits) 96 | mixer_layer.rx(-2 * beta[0], range(num_qubits)) 97 | 98 | for layer in range(num_layers): 99 | bind_dict = {cost_layer.parameters[0]: gamma[layer]} 100 | cost_layer_ = cost_layer.assign_parameters(bind_dict) 101 | bind_dict = { 102 | mixer_layer.parameters[i]: beta[layer + i] for i in range(mixer_layer.num_parameters) 103 | } 104 | layer_mixer = mixer_layer.assign_parameters(bind_dict) 105 | 106 | if layer % 2 == 0: 107 | new_circuit.append(cost_layer_, range(num_qubits)) 108 | else: 109 | new_circuit.append(cost_layer_.reverse_ops(), range(num_qubits)) 110 | 111 | new_circuit.append(layer_mixer, range(num_qubits)) 112 | 113 | for qidx, cidx in meas_map.items(): 114 | new_circuit.measure(qidx, cidx) 115 | 116 | return new_circuit 117 | 118 | 119 | def create_qaoa_swap_circuit( # pylint: disable=too-many-arguments,too-many-positional-arguments 120 | cost_operator: SparsePauliOp, 121 | swap_strategy: SwapStrategy, 122 | edge_coloring: dict = None, 123 | theta: list[float] = None, 124 | qaoa_layers: int = 1, 125 | initial_state: QuantumCircuit = None, 126 | mixer: QuantumCircuit = None, 127 | ): 128 | """Create the circuit for QAOA. 129 | 130 | Notes: This circuit construction for QAOA works for quadratic terms in `Z` and will be 131 | extended to first-order terms in `Z`. Higher-orders are not supported. 132 | 133 | Args: 134 | cost_operator: the cost operator. 135 | swap_strategy: selected swap strategy 136 | edge_coloring: A coloring of edges that should correspond to the coupling 137 | map of the hardware. It defines the order in which we apply the Rzz 138 | gates. This allows us to choose an ordering such that `Rzz` gates will 139 | immediately precede SWAP gates to leverage CNOT cancellation. 140 | theta: The QAOA angles. 141 | qaoa_layers: The number of layers of the cost-operator and the mixer operator. 142 | initial_state: The initial state on which we apply layers of cost-operator 143 | and mixer. 144 | mixer: The QAOA mixer. It will be applied as is onto the QAOA circuit. Therefore, 145 | its output must have the same ordering of qubits as its input. 146 | """ 147 | 148 | num_qubits = cost_operator.num_qubits 149 | 150 | if theta is not None: 151 | gamma = theta[: len(theta) // 2] 152 | beta = theta[len(theta) // 2 :] 153 | qaoa_layers = len(theta) // 2 154 | else: 155 | gamma = beta = None 156 | 157 | # First, create the ansatz of 1 layer of QAOA without mixer 158 | cost_layer = QAOAAnsatz( 159 | cost_operator, 160 | reps=1, 161 | initial_state=QuantumCircuit(num_qubits), 162 | mixer_operator=QuantumCircuit(num_qubits), 163 | ).decompose() 164 | 165 | # This will allow us to recover the permutation of the measurements that the swap introduce. 166 | cost_layer.measure_all() 167 | 168 | # Now, apply the swap strategy for commuting pauli evolution gates 169 | cost_layer = apply_swap_strategy(cost_layer, swap_strategy, edge_coloring) 170 | 171 | # Compute the measurement map (qubit to classical bit). 172 | # we will apply this for qaoa_layers % 2 == 1. 173 | if qaoa_layers % 2 == 1: 174 | meas_map = make_meas_map(cost_layer) 175 | else: 176 | meas_map = {idx: idx for idx in range(num_qubits)} 177 | 178 | cost_layer.remove_final_measurements() 179 | 180 | # Finally, introduce the mixer circuit and add measurements following measurement map 181 | circuit = apply_qaoa_layers( 182 | cost_layer, meas_map, qaoa_layers, gamma, beta, initial_state, mixer 183 | ) 184 | 185 | return circuit 186 | -------------------------------------------------------------------------------- /qopt_best_practices/transpilation/__init__.py: -------------------------------------------------------------------------------- 1 | """Module with transpiler methods for QAOA like circuits.""" 2 | 3 | from .preset_qaoa_passmanager import qaoa_swap_strategy_pm 4 | -------------------------------------------------------------------------------- /qopt_best_practices/transpilation/preset_qaoa_passmanager.py: -------------------------------------------------------------------------------- 1 | """Make a pass manager to transpile QAOA.""" 2 | 3 | from typing import Any, Dict 4 | 5 | from qiskit.transpiler import PassManager 6 | from qiskit.transpiler.passes import HighLevelSynthesis, InverseCancellation 7 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import ( 8 | FindCommutingPauliEvolutions, 9 | Commuting2qGateRouter, 10 | ) 11 | from qiskit.circuit.library import CXGate 12 | 13 | from qopt_best_practices.transpilation.qaoa_construction_pass import QAOAConstructionPass 14 | from qopt_best_practices.transpilation.swap_cancellation_pass import SwapToFinalMapping 15 | 16 | 17 | def qaoa_swap_strategy_pm(config: Dict[str, Any]): 18 | """Provide a pass manager to build the QAOA cirucit. 19 | 20 | This function will be extended in the future. 21 | """ 22 | 23 | num_layers = config.get("num_layers", 1) 24 | swap_strategy = config.get("swap_strategy", None) 25 | edge_coloring = config.get("edge_coloring", None) 26 | basis_gates = config.get("basis_gates", ["sx", "x", "rz", "cx", "id"]) 27 | construct_qaoa = config.get("construct_qaoa", True) 28 | 29 | if swap_strategy is None: 30 | raise ValueError("No swap_strategy provided in config.") 31 | 32 | if edge_coloring is None: 33 | raise ValueError("No edge_coloring provided in config.") 34 | 35 | # 2. define pass manager for cost layer 36 | qaoa_passes = [ 37 | HighLevelSynthesis(basis_gates=["PauliEvolution"]), 38 | FindCommutingPauliEvolutions(), 39 | Commuting2qGateRouter( 40 | swap_strategy, 41 | edge_coloring, 42 | ), 43 | SwapToFinalMapping(), 44 | HighLevelSynthesis(basis_gates=basis_gates), 45 | InverseCancellation(gates_to_cancel=[CXGate()]), 46 | ] 47 | 48 | if construct_qaoa: 49 | qaoa_passes.append(QAOAConstructionPass(num_layers)) 50 | 51 | return PassManager(qaoa_passes) 52 | -------------------------------------------------------------------------------- /qopt_best_practices/transpilation/qaoa_construction_pass.py: -------------------------------------------------------------------------------- 1 | """A pass to build a full QAOA ansatz circuit.""" 2 | 3 | from typing import Optional 4 | 5 | from qiskit.converters import circuit_to_dag, dag_to_circuit 6 | from qiskit.circuit import QuantumCircuit, ParameterVector, Parameter 7 | from qiskit.dagcircuit import DAGCircuit 8 | from qiskit.transpiler import TranspilerError 9 | from qiskit.transpiler.basepasses import TransformationPass 10 | 11 | 12 | class QAOAConstructionPass(TransformationPass): 13 | """Build the QAOAAnsatz from a transpiled cost operator. 14 | 15 | This pass takes as input a single layer of a transpiled QAOA operator. 16 | It then repeats this layer the appropriate number of time and adds (i) 17 | the initial state, (ii) the mixer operator, and (iii) the measurements. 18 | The measurements are added in such a fashion so as to undo the local 19 | permuttion induced by the SWAP gates in the cost layer. 20 | """ 21 | 22 | def __init__( 23 | self, 24 | num_layers: int, 25 | init_state: Optional[QuantumCircuit] = None, 26 | mixer_layer: Optional[QuantumCircuit] = None, 27 | ): 28 | """Initialize the pass 29 | 30 | Limitations: The current implementation of the pass does not permute the mixer. 31 | Therefore mixers with local bias fields, such as in warm-start methods, will not 32 | result in the correct circuits. 33 | 34 | Args: 35 | num_layers: The number of QAOA layers to apply. 36 | init_state: The initial state to use. This must match the anticipated number 37 | of qubits otherwise an error will be raised when `run` is called. If this 38 | is not given we will default to the equal superposition initial state. 39 | mixer_layer: The mixer layer to use. This must match the anticipated number 40 | of qubits otherwise an error will be raised when `run` is called. If this 41 | is not given we default to the standard mixer made of X gates. 42 | """ 43 | super().__init__() 44 | 45 | self.num_layers = num_layers 46 | self.init_state = init_state 47 | self.mixer_layer = mixer_layer 48 | 49 | def run(self, cost_layer_dag: DAGCircuit): 50 | num_qubits = cost_layer_dag.num_qubits() 51 | 52 | # Make the initial state and the mixer. 53 | if self.init_state is None: 54 | init_state = QuantumCircuit(num_qubits) 55 | init_state.h(range(num_qubits)) 56 | else: 57 | init_state = self.init_state 58 | 59 | if self.mixer_layer is None: 60 | mixer_layer = QuantumCircuit(num_qubits) 61 | beta = Parameter("β") 62 | mixer_layer.rx(2 * beta, range(num_qubits)) 63 | else: 64 | mixer_layer = self.mixer_layer 65 | 66 | # Do some sanity checks on qubit numbers. 67 | if init_state.num_qubits != num_qubits: 68 | raise TranspilerError( 69 | "Number of qubits in the initial state does not match the number in the DAG. " 70 | f"{init_state.num_qubits} != {num_qubits}" 71 | ) 72 | 73 | if mixer_layer.num_qubits != num_qubits: 74 | raise TranspilerError( 75 | "Number of qubits in the mixer does not match the number in the DAG. " 76 | f"{init_state.num_qubits} != {num_qubits}" 77 | ) 78 | 79 | # Note: converting to circuit is inefficent. Update to DAG only. 80 | cost_layer = dag_to_circuit(cost_layer_dag) 81 | qaoa_circuit = QuantumCircuit(num_qubits, num_qubits) 82 | 83 | # Re-parametrize the circuit 84 | gammas = ParameterVector("γ", self.num_layers) 85 | betas = ParameterVector("β", self.num_layers) 86 | 87 | # Add initial state 88 | qaoa_circuit.compose(init_state, inplace=True) 89 | 90 | # iterate over number of qaoa layers 91 | # and alternate cost/reversed cost and mixer 92 | for layer in range(self.num_layers): 93 | bind_dict = {cost_layer.parameters[0]: gammas[layer]} 94 | bound_cost_layer = cost_layer.assign_parameters(bind_dict) 95 | 96 | bind_dict = {mixer_layer.parameters[0]: betas[layer]} 97 | bound_mixer_layer = mixer_layer.assign_parameters(bind_dict) 98 | 99 | if layer % 2 == 0: 100 | # even layer -> append cost 101 | qaoa_circuit.compose(bound_cost_layer, range(num_qubits), inplace=True) 102 | else: 103 | # odd layer -> append reversed cost 104 | qaoa_circuit.compose( 105 | bound_cost_layer.reverse_ops(), range(num_qubits), inplace=True 106 | ) 107 | 108 | # the mixer layer is not reversed and not permuted. 109 | qaoa_circuit.compose(bound_mixer_layer, range(num_qubits), inplace=True) 110 | 111 | if self.num_layers % 2 == 1: 112 | # iterate over layout permutations to recover measurements 113 | if self.property_set["virtual_permutation_layout"]: 114 | for cidx, qidx in ( 115 | self.property_set["virtual_permutation_layout"].get_physical_bits().items() 116 | ): 117 | qaoa_circuit.measure(qidx, cidx) 118 | else: 119 | print("layout not found, assigining trivial layout") 120 | for idx in range(num_qubits): 121 | qaoa_circuit.measure(idx, idx) 122 | else: 123 | for idx in range(num_qubits): 124 | qaoa_circuit.measure(idx, idx) 125 | 126 | return circuit_to_dag(qaoa_circuit) 127 | -------------------------------------------------------------------------------- /qopt_best_practices/transpilation/swap_cancellation_pass.py: -------------------------------------------------------------------------------- 1 | """Pass to remove SWAP gates that are not needed.""" 2 | 3 | from qiskit.dagcircuit import DAGOutNode, DAGCircuit 4 | from qiskit.transpiler import TransformationPass 5 | 6 | 7 | class SwapToFinalMapping(TransformationPass): 8 | """Absorb any redundent SWAPs in the final layout. 9 | 10 | This pass should be executed after a SWAPStrategy has been applied to a block 11 | of commuting gates. It will remove any final redundent SWAP gates and absorb 12 | them into the virtual layout. This effectively undoes any possibly redundent 13 | SWAP gates that the SWAPStrategy may have inserted. 14 | """ 15 | 16 | def run(self, dag: DAGCircuit): 17 | """run the pass.""" 18 | 19 | qmap = self.property_set["virtual_permutation_layout"] 20 | 21 | # This will remove SWAP gates that are applied before anything else 22 | # This remove is executed multiple times until there are no more SWAP 23 | # gates left to remove. Note: a more inteligent DAG traversal could 24 | # be implemented here. 25 | 26 | done = False 27 | 28 | while not done: 29 | permuted = False 30 | for node in dag.topological_op_nodes(): 31 | if node.op.name == "swap": 32 | successors = list(dag.successors(node)) 33 | if len(successors) == 2: 34 | if all(isinstance(successors[idx], DAGOutNode) for idx in [0, 1]): 35 | qmap.swap(node.qargs[0], node.qargs[1]) 36 | dag.remove_op_node(node) 37 | permuted = True 38 | 39 | done = not permuted 40 | 41 | return dag 42 | -------------------------------------------------------------------------------- /qopt_best_practices/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utils""" 2 | 3 | 4 | from .graph_utils import build_max_cut_graph, build_max_cut_paulis 5 | 6 | __all__ = ["build_max_cut_graph", "build_max_cut_paulis"] 7 | -------------------------------------------------------------------------------- /qopt_best_practices/utils/graph_utils.py: -------------------------------------------------------------------------------- 1 | """Graph utils""" 2 | 3 | from __future__ import annotations 4 | import networkx as nx 5 | 6 | 7 | def build_max_cut_graph(paulis: list[tuple[str, float]]) -> nx.Graph: 8 | """Create a graph by parsing the pauli strings. 9 | 10 | Args: 11 | paulis: A list of Paulis given as tuple of Pauli string and 12 | coefficient. E.g., `[("IZZI", 1.0), ("ZIZI", 1.0)]`. Each 13 | pauli is guaranteed to have two Z's. 14 | 15 | Returns: 16 | A networkx graph. 17 | """ 18 | graph = nx.Graph() 19 | 20 | for pauli_str, weight in paulis: 21 | edge = [idx for idx, char in enumerate(pauli_str[::-1]) if char == "Z"] 22 | graph.add_edge(edge[0], edge[1], weight=weight) 23 | 24 | return graph 25 | 26 | 27 | def build_max_cut_paulis(graph: nx.Graph) -> list[tuple[str, float]]: 28 | """Convert the graph to Pauli list. 29 | 30 | This function does the inverse of `build_max_cut_graph` 31 | """ 32 | pauli_list = [] 33 | for edge in graph.edges(): 34 | paulis = ["I"] * len(graph) 35 | paulis[edge[0]], paulis[edge[1]] = "Z", "Z" 36 | 37 | weight = graph.get_edge_data(edge[0], edge[1]).get("weight", 1.0) 38 | 39 | pauli_list.append(("".join(paulis)[::-1], weight)) 40 | 41 | return pauli_list 42 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | coverage>=4.4.0,<7.0 2 | black[jupyter]~=22.0 3 | pylint>=2.15.0 4 | ddt>=1.2.0,!=1.4.0,!=1.4.3 5 | stestr>=2.0.0 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-sat 2 | networkx 3 | qiskit>=1.0 4 | qiskit-ibm-runtime 5 | -------------------------------------------------------------------------------- /run/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/run/__init__.py -------------------------------------------------------------------------------- /run/data/hardware_native_127.json: -------------------------------------------------------------------------------- 1 | {"paulis": [["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZI", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIZIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIZIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIZ", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIZIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIIIZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIIIZIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIZIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["ZIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIIIIZIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1], ["IIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["IZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", -1], ["ZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", 1]]} -------------------------------------------------------------------------------- /run/hw_native_test_1_circ.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | file = "data/hardware_native_127.json" 4 | data = json.load(open(file, "r")) 5 | paulis = data["paulis"] 6 | num_qubits = len(paulis[0][0]) 7 | print(num_qubits) 8 | 9 | from qiskit.quantum_info import SparsePauliOp 10 | 11 | # define a qiskit SparsePauliOp from the list of paulis 12 | hamiltonian = SparsePauliOp.from_list(paulis) 13 | print(hamiltonian) 14 | 15 | from qiskit.circuit.library import QAOAAnsatz 16 | 17 | qaoa_circ = QAOAAnsatz(hamiltonian, reps=3) 18 | qaoa_circ.measure_all() 19 | print(qaoa_circ.num_parameters) 20 | 21 | from qiskit import transpile 22 | 23 | basis_gates = ["rz", "sx", "x", "ecr"] 24 | # Now transpile to sx, rz, x, cx basis 25 | qaoa_circ = transpile(qaoa_circ, basis_gates=basis_gates) 26 | print("transpilation done") 27 | 28 | from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options 29 | 30 | service = QiskitRuntimeService(channel="ibm_quantum") 31 | backend = service.get_backend("ibm_nazca") 32 | options = Options() 33 | options.transpilation.skip_transpilation = True 34 | options.execution.shots = 100000 35 | 36 | sampler = Sampler(backend=backend, options=options) 37 | 38 | import numpy as np 39 | 40 | # TQA initialization parameters 41 | dt = 0.75 42 | p = 3 # 3 qaoa layers 43 | grid = np.arange(1, p + 1) - 0.5 44 | init_params = np.concatenate((1 - grid * dt / p, grid * dt / p)) 45 | print(init_params) 46 | 47 | job = sampler.run(qaoa_circ, init_params) 48 | 49 | print(job.job_id()) 50 | print(job.result()) 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | long_description = """Repository for best practices in quantum optimization.""" 5 | 6 | with open("requirements.txt") as f: 7 | REQUIREMENTS = f.read().splitlines() 8 | 9 | VERSION_PATH = os.path.join(os.path.dirname(__file__), "qopt_best_practices", "VERSION.txt") 10 | with open(VERSION_PATH, "r") as version_file: 11 | VERSION = version_file.read().strip() 12 | 13 | setuptools.setup( 14 | name="qopt_best_practices", 15 | version=VERSION, 16 | description="Best practices quantum opt.", 17 | long_description=long_description, 18 | long_description_content_type="text/markdown", 19 | url="https://github.com/ElePT/q-optimization-best-practices", 20 | author="Quantum optimization working group", 21 | license="Apache 2.0", 22 | classifiers=[ 23 | "Environment :: Console", 24 | "License :: OSI Approved :: Apache Software License", 25 | "Intended Audience :: Developers", 26 | "Intended Audience :: Science/Research", 27 | "Operating System :: Microsoft :: Windows", 28 | "Operating System :: MacOS", 29 | "Operating System :: POSIX :: Linux", 30 | "Programming Language :: Python :: 3 :: Only", 31 | "Programming Language :: Python :: 3.6", 32 | "Programming Language :: Python :: 3.7", 33 | "Programming Language :: Python :: 3.8", 34 | "Programming Language :: Python :: 3.9", 35 | "Topic :: Scientific/Engineering", 36 | ], 37 | keywords="qiskit quantum optimization", 38 | packages=setuptools.find_packages(include=["qopt_best_practices", "qopt_best_practices.*"]), 39 | install_requires=REQUIREMENTS, 40 | include_package_data=True, 41 | python_requires=">=3.7", 42 | zip_safe=False, 43 | ) 44 | -------------------------------------------------------------------------------- /stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./test -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qopt-best-practices/4e4ee2859ce95f75d5c84dad991b949ac06caa45/test/__init__.py -------------------------------------------------------------------------------- /test/data/graph_2layers_0seed.json: -------------------------------------------------------------------------------- 1 | {"Original graph": [[0, 7], [0, 3], [0, 5], [7, 9], [7, 8], [1, 2], [1, 4], [1, 6], [2, 3], [2, 8], [5, 8], [5, 3], [4, 9], [4, 6], [9, 6]], "SAT mapping": {"0": 6, "1": 2, "2": 7, "3": 8, "4": 1, "5": 9, "6": 3, "7": 5, "8": 4, "9": 0}, "paulis": [["IIIIIZZIII", 1.0], ["IIIIIIZIZI", 1.0], ["IIIIIIZIIZ", 1.0], ["ZIIIIZIIII", 1.0], ["IIIIZZIIII", 1.0], ["IIZIIIIZII", 1.0], ["IZZIIIIIII", 1.0], ["IIZZIIIIII", 1.0], ["IIIIIIIZZI", 1.0], ["IIIIZIIZII", 1.0], ["IIIIZIIIIZ", 1.0], ["IIIIIIIIZZ", 1.0], ["ZZIIIIIIII", 1.0], ["IZIZIIIIII", 1.0], ["ZIIZIIIIII", 1.0]], "description": "paulis are remapped with `SAT mapping`.", "min swap layers": 2, "nx seed": 0} -------------------------------------------------------------------------------- /test/test_cost_function.py: -------------------------------------------------------------------------------- 1 | """Tests for Cost Function Utils""" 2 | -------------------------------------------------------------------------------- /test/test_graph_utils.py: -------------------------------------------------------------------------------- 1 | """Tests for Graph Utils""" 2 | 3 | from unittest import TestCase 4 | import networkx as nx 5 | 6 | from qopt_best_practices.utils import build_max_cut_graph, build_max_cut_paulis 7 | 8 | 9 | class TestGraphRoundTrip(TestCase): 10 | """Test that we can convert between graph and Paulis.""" 11 | 12 | @staticmethod 13 | def _test_edge_equality(graph_1: nx.Graph, graph_2: nx.Graph): 14 | """Test equality of edges.""" 15 | if len(graph_1.edges) != len(graph_2.edges): 16 | return False 17 | 18 | g_set = set(graph_1.edges) 19 | for edge in graph_2.edges: 20 | if edge not in g_set and edge[::-1] not in g_set: 21 | return False 22 | 23 | return True 24 | 25 | def test_round_trip(self): 26 | """Test that we can easily round-trip Pauli the graphs.""" 27 | 28 | for seed in range(5): 29 | graph1 = nx.random_regular_graph(3, 10, seed=seed) 30 | graph2 = build_max_cut_graph(build_max_cut_paulis(graph1)) 31 | 32 | self.assertTrue(self._test_edge_equality(graph1, graph2)) 33 | 34 | def test_weighted_graph(self): 35 | """Test the construction of a weighted graph.""" 36 | 37 | graph = build_max_cut_graph([("IIZZ", 1), ("IZZI", -1), ("ZIZI", 1)]) 38 | 39 | self.assertEqual(graph.get_edge_data(0, 1)["weight"], 1) 40 | self.assertEqual(graph.get_edge_data(1, 2)["weight"], -1) 41 | self.assertEqual(graph.get_edge_data(1, 3)["weight"], 1) 42 | -------------------------------------------------------------------------------- /test/test_qaoa_construction.py: -------------------------------------------------------------------------------- 1 | """Test the construction of the QAOA ansatz.""" 2 | 3 | 4 | from unittest import TestCase 5 | 6 | from qiskit import QuantumCircuit, transpile 7 | from qiskit.circuit import Parameter 8 | from qiskit.circuit.library import QAOAAnsatz 9 | from qiskit.primitives import StatevectorEstimator 10 | from qiskit.quantum_info import SparsePauliOp 11 | from qiskit.transpiler import PassManager 12 | from qiskit.transpiler.passes import HighLevelSynthesis 13 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import ( 14 | SwapStrategy, 15 | FindCommutingPauliEvolutions, 16 | Commuting2qGateRouter, 17 | ) 18 | 19 | from qopt_best_practices.transpilation.qaoa_construction_pass import QAOAConstructionPass 20 | from qopt_best_practices.transpilation.preset_qaoa_passmanager import qaoa_swap_strategy_pm 21 | from qopt_best_practices.transpilation.swap_cancellation_pass import SwapToFinalMapping 22 | 23 | 24 | class TestQAOAConstruction(TestCase): 25 | """Test the construction of the QAOA ansatz.""" 26 | 27 | def setUp(self): 28 | """Set up re-used variables.""" 29 | self.estimator = StatevectorEstimator() 30 | 31 | gamma = Parameter("γ") 32 | cost_op = QuantumCircuit(4) 33 | cost_op.rzz(2 * gamma, 0, 1) 34 | cost_op.rzz(2 * gamma, 2, 3) 35 | cost_op.swap(0, 1) 36 | cost_op.swap(2, 3) 37 | cost_op.rzz(2 * gamma, 1, 2) 38 | 39 | self.cost_op_circ = transpile(cost_op, basis_gates=["sx", "cx", "x", "rz"]) 40 | 41 | self.cost_op = SparsePauliOp.from_list([("IIZZ", 1), ("ZZII", 1), ("ZIIZ", 1)]) 42 | 43 | self.config = { 44 | "swap_strategy": SwapStrategy.from_line(list(range(4))), 45 | "edge_coloring": {(idx, idx + 1): (idx + 1) % 2 for idx in range(4)}, 46 | } 47 | 48 | def test_depth_one(self): 49 | """Compare the pass with the SWAPs and ensure the measurements are ordered properly.""" 50 | qaoa_pm = qaoa_swap_strategy_pm(self.config) 51 | 52 | cost_op_circ = QAOAAnsatz( 53 | self.cost_op, initial_state=QuantumCircuit(4), mixer_operator=QuantumCircuit(4) 54 | ).decompose(reps=1) 55 | 56 | ansatz = qaoa_pm.run(cost_op_circ) 57 | 58 | # 1. Check the measurement map 59 | qreg = ansatz.qregs[0] 60 | creg = ansatz.cregs[0] 61 | 62 | expected_meas_map = {0: 1, 1: 0, 2: 3, 3: 2} 63 | 64 | for inst in ansatz.data: 65 | if inst.operation.name == "measure": 66 | qubit = qreg.index(inst.qubits[0]) 67 | cbit = creg.index(inst.clbits[0]) 68 | self.assertEqual(cbit, expected_meas_map[qubit]) 69 | 70 | # 2. Check the expectation value. Note that to use the estimator we need to 71 | # Remove the final measurements and correspondingly permute the cost op. 72 | ansatz.remove_final_measurements(inplace=True) 73 | permuted_cost_op = SparsePauliOp.from_list([("IIZZ", 1), ("ZZII", 1), ("IZZI", 1)]) 74 | value = self.estimator.run([(ansatz, permuted_cost_op, [1, 2])]).result()[0].data.evs 75 | 76 | library_ansatz = QAOAAnsatz(self.cost_op, reps=1) 77 | library_ansatz = transpile(library_ansatz, basis_gates=["cx", "rz", "rx", "h"]) 78 | 79 | expected = self.estimator.run([(library_ansatz, self.cost_op, [1, 2])]).result()[0].data.evs 80 | 81 | self.assertAlmostEqual(value, expected) 82 | 83 | def test_depth_two_qaoa_pass(self): 84 | """Compare the pass with the SWAPs to an all-to-all construction. 85 | 86 | Note: this test only works as is because p is even and we don't have the previous 87 | passes to give us the qubit permutations. 88 | """ 89 | qaoa_pm = PassManager([QAOAConstructionPass(num_layers=2)]) 90 | 91 | ansatz = qaoa_pm.run(self.cost_op_circ) 92 | ansatz.remove_final_measurements(inplace=True) 93 | 94 | value = self.estimator.run([(ansatz, self.cost_op, [1, 2, 3, 4])]).result()[0].data.evs 95 | 96 | library_ansatz = QAOAAnsatz(self.cost_op, reps=2) 97 | library_ansatz = transpile(library_ansatz, basis_gates=["cx", "rz", "rx", "h"]) 98 | 99 | expected = ( 100 | self.estimator.run([(library_ansatz, self.cost_op, [1, 2, 3, 4])]).result()[0].data.evs 101 | ) 102 | 103 | self.assertAlmostEqual(value, expected) 104 | 105 | def test_swap_construction(self): 106 | """Test that redundent SWAP gates are removed.""" 107 | cost_op = SparsePauliOp.from_list( 108 | [("IIIIZZ", 1), ("IIZZII", 1), ("ZZIIII", 1), ("IIZIIZ", 1)], 109 | ) 110 | 111 | ansatz = QAOAAnsatz( 112 | cost_op, reps=1, initial_state=QuantumCircuit(6), mixer_operator=QuantumCircuit(6) 113 | ) 114 | 115 | # Test with the SWAP removal 116 | qaoa_pm = PassManager( 117 | [ 118 | HighLevelSynthesis(basis_gates=["PauliEvolution"]), 119 | FindCommutingPauliEvolutions(), 120 | Commuting2qGateRouter(SwapStrategy.from_line(range(6))), 121 | SwapToFinalMapping(), 122 | ] 123 | ) 124 | 125 | self.assertEqual(qaoa_pm.run(ansatz).count_ops()["swap"], 2) 126 | 127 | # Test without the SWAP removal 128 | qaoa_pm = PassManager( 129 | [ 130 | HighLevelSynthesis(basis_gates=["PauliEvolution"]), 131 | FindCommutingPauliEvolutions(), 132 | Commuting2qGateRouter(SwapStrategy.from_line(range(6))), 133 | ] 134 | ) 135 | 136 | self.assertEqual(qaoa_pm.run(ansatz).count_ops()["swap"], 3) 137 | -------------------------------------------------------------------------------- /test/test_qubit_selection.py: -------------------------------------------------------------------------------- 1 | """Tests for Qubit Selection Utils""" 2 | 3 | import json 4 | import os 5 | from unittest import TestCase 6 | 7 | from qiskit_ibm_runtime.fake_provider import FakeSherbrooke, FakeHanoiV2 8 | from qiskit.providers.fake_provider import GenericBackendV2 9 | 10 | 11 | from qopt_best_practices.utils import build_max_cut_graph 12 | from qopt_best_practices.qubit_selection import BackendEvaluator, find_lines 13 | 14 | 15 | class TestQubitSelection(TestCase): 16 | """Unit test for QAOA workflow.""" 17 | 18 | def setUp(self): 19 | super().setUp() 20 | 21 | # load data 22 | graph_file = os.path.join(os.path.dirname(__file__), "data/graph_2layers_0seed.json") 23 | with open(graph_file, "r") as file: 24 | data = json.load(file) 25 | 26 | # 10 qubit graph 27 | self.mapped_paulis = [tuple(pauli) for pauli in data["paulis"]] 28 | self.mapped_graph = build_max_cut_graph(self.mapped_paulis) 29 | self.backend = FakeSherbrooke() 30 | 31 | def test_find_lines(self): 32 | """Test backend evaluation""" 33 | paths = find_lines(len(self.mapped_graph), self.backend) 34 | 35 | self.assertEqual(len(paths), 1336) 36 | self.assertEqual(len(paths[0]), len(self.mapped_graph)) 37 | self.assertIsInstance(paths[0][0], int) 38 | 39 | def test_find_lines_directed(self): 40 | """Test backend with directed (asymmetric) coupling map""" 41 | directed_fake_backend = GenericBackendV2(4, coupling_map=[[0, 1], [1, 2], [3, 2], [3, 0]]) 42 | lines = find_lines(3, backend=directed_fake_backend) 43 | 44 | expected_lines = [[0, 1, 2], [0, 3, 2], [1, 2, 3], [1, 0, 3]] 45 | self.assertEqual(set(tuple(i) for i in lines), set(tuple(i) for i in expected_lines)) 46 | 47 | def test_qubit_selection(self): 48 | """Test backend evaluation""" 49 | path_finder = BackendEvaluator(self.backend) 50 | path, _, _ = path_finder.evaluate(len(self.mapped_graph)) 51 | 52 | expected_path = [33, 39, 40, 72, 41, 81, 53, 60, 61, 62] 53 | self.assertEqual(set(path), set(expected_path)) 54 | 55 | def test_qubit_selection_v2(self): 56 | """Test backend evaluation for 10 qubit line""" 57 | path_finder = BackendEvaluator(FakeHanoiV2()) 58 | path, _, _ = path_finder.evaluate(len(self.mapped_graph)) 59 | expected_path = [8, 9, 11, 12, 13, 14, 15, 18, 21, 23] 60 | self.assertEqual(set(path), set(expected_path)) 61 | -------------------------------------------------------------------------------- /test/test_sat_mapping.py: -------------------------------------------------------------------------------- 1 | """Tests for SAT Mapping Utils""" 2 | 3 | from unittest import TestCase 4 | import json 5 | import os 6 | import networkx as nx 7 | 8 | from qiskit.transpiler import CouplingMap 9 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import SwapStrategy 10 | 11 | from qopt_best_practices.utils import build_max_cut_graph, build_max_cut_paulis 12 | from qopt_best_practices.sat_mapping import SATMapper 13 | 14 | 15 | class TestSwapStrategies(TestCase): 16 | """Unit test for SWAP strategies functionality.""" 17 | 18 | def setUp(self): 19 | super().setUp() 20 | 21 | # load data 22 | graph_file = os.path.join(os.path.dirname(__file__), "data/graph_2layers_0seed.json") 23 | 24 | with open(graph_file, "r") as file: 25 | data = json.load(file) 26 | 27 | self.original_graph = nx.from_edgelist(data["Original graph"]) 28 | self.original_paulis = build_max_cut_paulis(self.original_graph) 29 | 30 | self.mapped_paulis = [tuple(pauli) for pauli in data["paulis"]] 31 | self.mapped_graph = build_max_cut_graph(self.mapped_paulis) 32 | 33 | self.sat_mapping = {int(key): value for key, value in data["SAT mapping"].items()} 34 | self.min_k = data["min swap layers"] 35 | self.swap_strategy = SwapStrategy.from_line(list(range(len(self.original_graph.nodes)))) 36 | self.basic_graphs = [nx.path_graph(5), nx.cycle_graph(7)] 37 | 38 | def test_find_initial_mappings(self): 39 | """Test find_initial_mappings""" 40 | 41 | mapper = SATMapper() 42 | 43 | results = mapper.find_initial_mappings(self.original_graph, self.swap_strategy) 44 | min_k = min((k for k, v in results.items() if v.satisfiable)) 45 | # edge_map = dict(results[min_k].mapping) 46 | 47 | # edge maps are not equal, but same min_k 48 | self.assertEqual(min_k, self.min_k) 49 | 50 | # Find better test 51 | # self.assertEqual(edge_map, self.sat_mapping) 52 | 53 | def test_remap_graph_with_sat(self): 54 | """Test remap_graph_with_sat""" 55 | 56 | mapper = SATMapper() 57 | 58 | remapped_g, _, _ = mapper.remap_graph_with_sat( 59 | graph=self.original_graph, swap_strategy=self.swap_strategy 60 | ) 61 | 62 | self.assertTrue(nx.is_isomorphic(remapped_g, self.mapped_graph)) 63 | 64 | def test_deficient_strategy(self): 65 | """Test that the SAT mapper works when the SWAP strategy is deficient. 66 | 67 | Note: a deficient strategy does not result in full connectivity but 68 | may still be useful. 69 | """ 70 | cmap = CouplingMap([(idx, idx + 1) for idx in range(10)]) 71 | 72 | # This swap strategy is deficient but can route the graph below. 73 | swaps = ( 74 | ((0, 1), (2, 3), (4, 5), (6, 7), (8, 9)), 75 | ((1, 2), (3, 4), (5, 6), (7, 8)), 76 | ((2, 3), (4, 5), (6, 7), (8, 9)), 77 | (), 78 | (), 79 | (), 80 | (), 81 | (), 82 | ) 83 | swap_strategy = SwapStrategy(cmap, swaps) 84 | graph = nx.random_regular_graph(3, 10, seed=2) 85 | 86 | mapper = SATMapper() 87 | 88 | _, permutation, min_layer = mapper.remap_graph_with_sat(graph, swap_strategy) 89 | 90 | # Spot check a few permutations. 91 | self.assertEqual(permutation[0], 9) 92 | self.assertEqual(permutation[8], 1) 93 | 94 | # Crucially, if the `connectivity_matrix` in `find_initial_mappings` we get a wrong result. 95 | self.assertEqual(min_layer, 3) 96 | 97 | def test_full_connectivity(self): 98 | """Test that the SAT mapper works when the SWAP strategy has full connectivity.""" 99 | graph = nx.random_regular_graph(3, 6, seed=1) 100 | swap_strategy = SwapStrategy.from_line(list(range(6))) 101 | sat_mapper = SATMapper() 102 | _, _, min_sat_layers = sat_mapper.remap_graph_with_sat( 103 | graph=graph, 104 | swap_strategy=swap_strategy, 105 | ) 106 | self.assertEqual(min_sat_layers, 4) 107 | 108 | def test_unable_to_remap(self): 109 | """Test that the SAT mapper works when the SWAP strategy is unable to remap.""" 110 | graph = nx.random_regular_graph(3, 6, seed=1) 111 | cmap = CouplingMap([(idx, idx + 1) for idx in range(5)]) 112 | swap_strategy = SwapStrategy(cmap, []) 113 | sat_mapper = SATMapper() 114 | remapped_g, edge_map, min_sat_layers = sat_mapper.remap_graph_with_sat( 115 | graph=graph, 116 | swap_strategy=swap_strategy, 117 | ) 118 | self.assertIsNone(remapped_g) 119 | self.assertIsNone(edge_map) 120 | self.assertIsNone(min_sat_layers) 121 | -------------------------------------------------------------------------------- /test/test_swap_cancellation.py: -------------------------------------------------------------------------------- 1 | """The the cancellation of unneeded SWAP gates.""" 2 | 3 | 4 | from unittest import TestCase 5 | 6 | from qiskit import QuantumCircuit 7 | from qiskit.converters import circuit_to_dag 8 | from qiskit.transpiler import Layout 9 | 10 | from qopt_best_practices.transpilation.swap_cancellation_pass import SwapToFinalMapping 11 | 12 | 13 | class TestSwapCancellation(TestCase): 14 | """Test the swap cancellation pass.""" 15 | 16 | def test_simple(self): 17 | """Simple test.""" 18 | 19 | qc = QuantumCircuit(4, 4) 20 | qc.swap(3, 2) 21 | qc.rzz(1.234, 1, 2) 22 | qc.swap(1, 2) 23 | 24 | qreg = next(iter(qc.qregs)) 25 | 26 | swap_pass = SwapToFinalMapping() 27 | layout = Layout( 28 | { 29 | 0: qreg[0], 30 | 1: qreg[2], 31 | 2: qreg[3], 32 | 3: qreg[1], 33 | } 34 | ) 35 | swap_pass.property_set["virtual_permutation_layout"] = layout 36 | 37 | dag = circuit_to_dag(qc) 38 | _ = swap_pass.run(dag) 39 | 40 | new_layout = swap_pass.property_set["virtual_permutation_layout"] 41 | 42 | self.assertTrue(new_layout[0] == qreg[0]) 43 | self.assertTrue(new_layout[1] == qreg[1]) 44 | self.assertTrue(new_layout[2] == qreg[3]) 45 | self.assertTrue(new_layout[3] == qreg[2]) 46 | -------------------------------------------------------------------------------- /test/test_swap_strategies.py: -------------------------------------------------------------------------------- 1 | """Tests for Swap Strategies""" 2 | 3 | from unittest import TestCase 4 | import json 5 | import os 6 | 7 | from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import SwapStrategy 8 | from qiskit.quantum_info import SparsePauliOp 9 | 10 | from qopt_best_practices.swap_strategies import create_qaoa_swap_circuit 11 | 12 | 13 | class TestSwapStrategies(TestCase): 14 | """Unit test for SWAP strategies functionality.""" 15 | 16 | def setUp(self): 17 | super().setUp() 18 | 19 | # load data 20 | graph_file = os.path.join(os.path.dirname(__file__), "data/graph_2layers_0seed.json") 21 | 22 | with open(graph_file, "r") as file: 23 | data = json.load(file) 24 | 25 | self.mapped_paulis = [tuple(pauli) for pauli in data["paulis"]] 26 | 27 | self.hamiltonian = SparsePauliOp.from_list(self.mapped_paulis) 28 | 29 | self.swap_strategy = SwapStrategy.from_line(list(range(self.hamiltonian.num_qubits))) 30 | 31 | def test_qaoa_circuit(self): 32 | """Test building the QAOA circuit.""" 33 | 34 | edge_coloring = { 35 | (idx, idx + 1): (idx + 1) % 2 for idx in range(self.hamiltonian.num_qubits) 36 | } 37 | 38 | for layers in range(1, 6): 39 | qaoa_circ = create_qaoa_swap_circuit( 40 | self.hamiltonian, self.swap_strategy, edge_coloring, qaoa_layers=layers 41 | ) 42 | 43 | self.assertEqual(len(qaoa_circ.parameters), layers * 2) 44 | 45 | # TODO: expand tests 46 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | # Sets this min.version because of differences with env_tmp_dir env. 3 | minversion = 4.0.2 4 | envlist = py38, py39, py310, py311, lint 5 | skipsdist = True 6 | 7 | [testenv] 8 | usedevelop = True 9 | install_command = pip install -c constraints.txt -U {opts} {packages} 10 | passenv = * 11 | setenv = 12 | VIRTUAL_ENV={envdir} 13 | LANGUAGE=en_US 14 | LC_ALL=en_US.utf-8 15 | ARGS="-V" 16 | deps = git+https://github.com/Qiskit/qiskit.git 17 | -r{toxinidir}/requirements.txt 18 | -r{toxinidir}/requirements-dev.txt 19 | commands = 20 | stestr run {posargs} 21 | 22 | [testenv:lint] 23 | envdir = .tox/lint 24 | basepython = python3 25 | commands = 26 | black --check {posargs} qopt_best_practices test 27 | pylint -rn qopt_best_practices test 28 | 29 | [testenv:black] 30 | envdir = .tox/lint 31 | commands = black {posargs} qopt_best_practices test 32 | 33 | [testenv:coverage] 34 | basepython = python3 35 | setenv = 36 | {[testenv]setenv} 37 | PYTHON=coverage3 run --source qopt_best_practices --parallel-mode 38 | commands = 39 | stestr run {posargs} 40 | coverage3 combine 41 | coverage3 report 42 | --------------------------------------------------------------------------------