├── tests ├── __init__.py └── test_jn.py ├── helpers ├── __init__.py ├── qpu.py └── draw.py ├── CODEOWNERS ├── images ├── cover.png └── kerberos.png ├── .gitpod.yml ├── requirements.txt ├── .circleci └── config.yml ├── .devcontainer └── devcontainer.json ├── README.md ├── LICENSE ├── 01-hybrid-computing-getting-started.ipynb ├── 03-hybrid-computing-components.ipynb └── 02-hybrid-computing-workflows.ipynb /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @JoelPasvolsky 2 | -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/hybrid-computing-notebook/HEAD/images/cover.png -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: Run jupyter notebook 3 | command: | 4 | jupyter notebook *.ipynb 5 | -------------------------------------------------------------------------------- /images/kerberos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/hybrid-computing-notebook/HEAD/images/kerberos.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dwave-ocean-sdk>=3.3.0 2 | 3 | notebook~=7.0 4 | jupyter 5 | nbformat 6 | 7 | matplotlib~=3.0 8 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | dwave: dwave/orb-examples@2 5 | 6 | workflows: 7 | version: 2.1 8 | tests: 9 | jobs: 10 | - dwave/test-linux 11 | - dwave/test-osx 12 | - dwave/test-win 13 | 14 | # Run weekly tests on all python versions 15 | weekly: 16 | triggers: 17 | - schedule: 18 | cron: "0 2 * * 0" 19 | filters: 20 | branches: 21 | only: 22 | - master 23 | - main 24 | jobs: 25 | - dwave/test-linux: 26 | integration-tests: "canary" 27 | - dwave/test-osx: 28 | integration-tests: "skip" 29 | - dwave/test-win: 30 | integration-tests: "skip" 31 | -------------------------------------------------------------------------------- /helpers/qpu.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper functions related to D-Wave systems 16 | """ 17 | 18 | import dwave_networkx as dnx 19 | 20 | def qpu_working_graph(qpu): 21 | "Return a dwave_networkx graph representing the working graph of a given QPU." 22 | 23 | dnx_graphs = {'chimera': dnx.chimera_graph, 'pegasus': dnx.pegasus_graph} 24 | 25 | dnx_graph = dnx_graphs[qpu.properties["topology"]["type"]] 26 | 27 | return dnx_graph(qpu.properties["topology"]["shape"][0], 28 | node_list=qpu.nodelist, 29 | edge_list=qpu.edgelist) 30 | 31 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Ocean Development Environment", 5 | 6 | // python 3.11 on debian, with latest Ocean and optional packages 7 | // source repo: https://github.com/dwavesystems/ocean-dev-docker 8 | "image": "docker.io/dwavesys/ocean-dev:latest", 9 | 10 | // install repo requirements on create and content update 11 | "updateContentCommand": "pip install -r requirements.txt", 12 | 13 | // forward/expose container services (relevant only when run locally) 14 | "forwardPorts": [ 15 | // dwave-inspector web app 16 | 18000, 18001, 18002, 18003, 18004, 17 | // OAuth connect redirect URIs 18 | 36000, 36001, 36002, 36003, 36004 19 | ], 20 | 21 | "portsAttributes": { 22 | "18000-18004": { 23 | "label": "D-Wave Problem Inspector", 24 | "requireLocalPort": true 25 | }, 26 | "36000-36004": { 27 | "label": "OAuth 2.0 authorization code redirect URI", 28 | "requireLocalPort": true 29 | } 30 | }, 31 | 32 | // Configure tool-specific properties. 33 | "customizations": { 34 | // Configure properties specific to VS Code. 35 | "vscode": { 36 | // Set *default* container specific settings.json values on container create. 37 | "settings": { 38 | "workbench": { 39 | "editorAssociations": { 40 | "*.md": "vscode.markdown.preview.editor" 41 | }, 42 | "startupEditor": "readme" 43 | } 44 | }, 45 | "extensions": [ 46 | "ms-python.python", 47 | "ms-toolsai.jupyter" 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /helpers/draw.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Helper plot functions 16 | """ 17 | 18 | import numpy as np 19 | import networkx as nx 20 | 21 | import matplotlib.pyplot as plt 22 | import matplotlib 23 | # `Qt5Agg` works well in ipython/console 24 | #matplotlib.use('Qt5Agg') 25 | from matplotlib.gridspec import GridSpec 26 | 27 | import dwave_networkx as dnx 28 | import dimod 29 | 30 | 31 | def plot(G, subgraphs=None, max_subs=3, subtitles=False, pos=None): 32 | """Plot utility""" 33 | 34 | if pos is None: 35 | pos = nx.get_node_attributes(G, 'pos') 36 | if not pos: 37 | pos = nx.spring_layout(G) 38 | 39 | if not subgraphs: 40 | axes = 1 41 | graph_edge_alpha = 0.2 42 | graph_node_alpha = 0.9 43 | 44 | else: 45 | axes = min(max_subs, len(subgraphs)) 46 | graph_edge_alpha = 0.05 47 | graph_node_alpha = 0.1 48 | 49 | subgraph_edge_alpha = 0.4 50 | subgraph_node_alpha = 0.9 51 | 52 | fig = plt.figure(figsize=(15,5)) 53 | gs = GridSpec(1, axes) 54 | 55 | for i in range(axes): 56 | ax = 'ax_' + str(i) 57 | ax = plt.subplot(gs[0, i]) 58 | if subtitles: 59 | ax.set_title("Subproblem "+str(i+1)) 60 | 61 | # edges for full graph 62 | nx.draw_networkx_edges(G, pos, alpha=graph_edge_alpha) 63 | 64 | # edges for subgraph 65 | if subgraphs: 66 | nx.draw_networkx_edges(subgraphs[i], pos, alpha=subgraph_edge_alpha) 67 | 68 | # nodes for full graph 69 | nodelist = G.nodes.keys() 70 | max_degree = max(dict(G.degree).values()) 71 | 72 | # top 70% of reds cmap 73 | normalized_degrees = [0.3 + 0.7 * G.degree[n] / max_degree for n in nodelist] 74 | node_color = plt.cm.Reds([0] + normalized_degrees)[1:] 75 | nx.draw_networkx_nodes(G, pos, nodelist=nodelist, node_size=80, node_color=node_color, alpha=graph_node_alpha) 76 | 77 | # nodes for subgraph 78 | if subgraphs: 79 | sub_nodelist = subgraphs[i].nodes.keys() 80 | sub_node_color = [node_color[n] for n in sub_nodelist] 81 | nx.draw_networkx_nodes(subgraphs[i], pos, nodelist=sub_nodelist, node_size=80, node_color=sub_node_color, alpha=subgraph_node_alpha) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open in GitHub Codespaces]( 2 | https://img.shields.io/badge/Open%20in%20GitHub%20Codespaces-333?logo=github)]( 3 | https://codespaces.new/dwave-examples/hybrid-computing-notebook?quickstart=1) 4 | [![Linux/Mac/Windows build status]( 5 | https://circleci.com/gh/dwave-examples/hybrid-computing-notebook.svg?style=shield)]( 6 | https://circleci.com/gh/dwave-examples/hybrid-computing-notebook) 7 | 8 | # Hybrid Computing 9 | 10 | These notebooks demonstrate how you can apply hybrid solvers to your problem, 11 | create hybrid workflows, and develop custom hybrid components. 12 | 13 | * **Notebook 01** will start you off with Leap's cloud-based hybrid solvers and 14 | out-of-the-box dwave-hybrid samplers and workflows, and requires only familiarity 15 | with the 16 | [binary quadratic model](https://docs.ocean.dwavesys.com/en/stable/concepts/bqm.html) 17 | (BQM) approach to formulating problems for solution on quantum computers. 18 | * **Notebook 02** shows how you create your own workflows using existing dwave-hybrid 19 | components. 20 | * **Notebook 03** is aimed at developers interested in developing their own hybrid 21 | components for optimal performance. 22 | 23 | ## A Few Words on Hybrid Computing 24 | 25 | Quantum-classical hybrid is the use of both classical and quantum resources to 26 | solve problems, exploiting the complementary strengths that each provides. As 27 | quantum processors grow in size, offloading hard optimization problems to quantum 28 | computers promises performance benefits similar to CPUs' outsourcing of 29 | compute-intensive graphics-display processing to GPUs. 30 | 31 | This [Medium Article](https://medium.com/d-wave/three-truths-and-the-advent-of-hybrid-quantum-computing-1941ba46ff8c) 32 | provides an overview of, and motivation for, hybrid computing. 33 | 34 | D-Wave's [Leap quantum cloud service](https://cloud.dwavesys.com/leap) provides 35 | cloud-based hybrid solvers you can submit arbitrary quadratic models to. These 36 | solvers, which implement state-of-the-art classical algorithms together with 37 | intelligent allocation of the quantum processing unit (QPU) to parts of the 38 | problem where it benefits most, are designed to accommodate even very large 39 | problems. Leap's solvers can relieve you of the burden of any current and future 40 | development and optimization of hybrid algorithms that best solve your problem. 41 | 42 | [*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) 43 | provides you with a Python framework for building a variety of flexible hybrid 44 | workflows. These use quantum and classical resources together to find good 45 | solutions to your problem. For example, a hybrid workflow might use classical 46 | resources to find a problem’s hard core and send that to the QPU, or break a large 47 | problem into smaller pieces that can be solved on a QPU and then recombined. 48 | 49 | The *dwave-hybrid* framework enables rapid development of experimental prototypes, 50 | which provide insight into expected performance of the productized versions. It 51 | provides reference samplers and workflows you can quickly plug into your 52 | application code. You can easily experiment with customizing workflows that best 53 | solve your problem. You can also develop your own hybrid components to optimize 54 | performance. 55 | 56 | ![hybrid workflow](images/kerberos.png) 57 | 58 | ## Installation 59 | 60 | You can run this example without installation in cloud-based IDEs that support 61 | the [Development Containers specification](https://containers.dev/supporting) 62 | (aka "devcontainers"). 63 | 64 | For development environments that do not support ``devcontainers``, install 65 | requirements: 66 | 67 | pip install -r requirements.txt 68 | 69 | If you are cloning the repo to your local system, working in a 70 | [virtual environment](https://docs.python.org/3/library/venv.html) is 71 | recommended. 72 | 73 | ## Usage 74 | 75 | Your development environment should be configured to 76 | [access Leap’s Solvers](https://docs.ocean.dwavesys.com/en/stable/overview/sapi.html). 77 | You can see information about supported IDEs and authorizing access to your 78 | Leap account [here](https://docs.dwavesys.com/docs/latest/doc_leap_dev_env.html). 79 | 80 | The notebooks can be opened by clicking on the ``.ipynb`` files 81 | (e.g., ``01-hybrid-computing-getting-started.ipynb``) in VS Code-based IDEs. 82 | 83 | To run a locally installed notebook: 84 | 85 | ```bash 86 | jupyter notebook 87 | ``` 88 | 89 | ## License 90 | 91 | Released under the Apache License 2.0. See LICENSE file. 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/test_jn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import nbformat 17 | from nbconvert.preprocessors import ExecutePreprocessor 18 | import unittest 19 | import re 20 | 21 | def run_jn(jn, timeout): 22 | 23 | open_jn = open(jn, "r", encoding='utf-8') 24 | notebook = nbformat.read(open_jn, nbformat.current_nbformat) 25 | open_jn.close() 26 | 27 | preprocessor = ExecutePreprocessor(timeout=timeout, kernel_name='python3') 28 | preprocessor.allow_errors = True 29 | preprocessor.preprocess(notebook, {'metadata': {'path': os.path.dirname(jn)}}) 30 | 31 | return notebook 32 | 33 | def collect_jn_errors(nb): 34 | 35 | errors = [] 36 | for cell in nb.cells: 37 | if 'outputs' in cell: 38 | for output in cell['outputs']: 39 | if output.output_type == 'error': 40 | errors.append(output) 41 | 42 | return errors 43 | 44 | def embedding_fail(error_list): 45 | return error_list and error_list[0].evalue == 'no embedding found' 46 | 47 | def robust_run_jn(jn, timeout, retries): 48 | 49 | run_num = 1 50 | notebook = run_jn(jn, timeout) 51 | errors = collect_jn_errors(notebook) 52 | 53 | while embedding_fail(errors) and run_num < retries: 54 | run_num += 1 55 | notebook = run_jn(jn, timeout) 56 | errors = collect_jn_errors(notebook) 57 | 58 | return notebook, errors 59 | 60 | def cell_text(nb, cell): 61 | return nb["cells"][cell]["outputs"][0]["text"] 62 | 63 | jn_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 64 | 65 | class TestJupyterNotebook(unittest.TestCase): 66 | 67 | MAX_EMBEDDING_RETRIES = 3 68 | 69 | @unittest.skipIf(os.getenv('SKIP_INT_TESTS'), "Skipping integration test.") 70 | def test_jn1(self): 71 | # Smoketest 72 | MAX_RUN_TIME = 800 73 | 74 | def _check_set_energy(cell): 75 | """Check set>0 and energy<0.""" 76 | cell_output = re.findall(r'[-+]?\d+\.?\d+', cell["outputs"][0]["text"]) 77 | self.assertGreater(float(cell_output[0]), 0) 78 | self.assertLess(float(cell_output[1]), 0) 79 | 80 | jn_file = os.path.join(jn_dir, '01-hybrid-computing-getting-started.ipynb') 81 | nb, errors = robust_run_jn(jn_file, MAX_RUN_TIME, self.MAX_EMBEDDING_RETRIES) 82 | 83 | self.assertEqual(errors, []) 84 | 85 | # Test cell outputs: 86 | 87 | # Section A Sample Problem, create a BQM 88 | self.assertIn("-1.0", cell_text(nb, 7)) 89 | 90 | # Section Using Leap's Hybrid Solvers, run default, print set size & energy 91 | _check_set_energy(nb["cells"][10]) 92 | 93 | # Section Sampler: Kerberos, run default, print set size & energy 94 | _check_set_energy(nb["cells"][15]) 95 | 96 | # Section Sampler: Kerberos, max_iter=10, print set size & energy 97 | _check_set_energy(nb["cells"][17]) 98 | 99 | # Section Configuring the Sampler, exercise, return more sample sets 100 | cell_output_str = cell_text(nb, 20) 101 | cell_output = list(map(float, re.findall(r'\[(.*?)\]', cell_output_str)[0].split())) 102 | for energy in cell_output: 103 | self.assertLess(energy, 0) 104 | 105 | # Section Configuring the Sampler, advanced exercise, feature-based selection 106 | _check_set_energy(nb["cells"][23]) 107 | 108 | # Section Workflow: Parallel Tempering, default run, print set size & energy 109 | _check_set_energy(nb["cells"][26]) 110 | 111 | # Section Workflow: Parallel Tempering, configured parameters, print set size & energy 112 | _check_set_energy(nb["cells"][30]) 113 | 114 | # Section Operational Utilities, print_structure() 115 | self.assertIn("FixedTemperatureSampler", cell_text(nb, 33)) 116 | 117 | # Section Operational Utilities, print_counters() 118 | self.assertIn("FixedTemperatureSampler", cell_text(nb, 35)) 119 | 120 | # Section Operational Utilities, print_counters() 121 | self.assertIn("INFO", cell_text(nb, 37)) 122 | 123 | @unittest.skipIf(os.getenv('SKIP_INT_TESTS'), "Skipping integration test.") 124 | def test_jn2(self): 125 | # Smoketest 126 | MAX_RUN_TIME = 800 127 | 128 | def _check_energy(cell): 129 | """Check energy<0.""" 130 | energy_str = re.findall(r'-\d+\.?\d+', cell["outputs"][0]["text"]) 131 | self.assertLess(float(energy_str[0]), 0) 132 | 133 | jn_file = os.path.join(jn_dir, '02-hybrid-computing-workflows.ipynb') 134 | nb, errors = robust_run_jn(jn_file, MAX_RUN_TIME, self.MAX_EMBEDDING_RETRIES) 135 | 136 | self.assertEqual(errors, []) 137 | 138 | # Test cell outputs: 139 | 140 | # Section Basic Workflows, subsection States, create initial state 141 | self.assertIn("problem", cell_text(nb, 6)) 142 | 143 | # Section Basic Workflows, subsection Runnables, run Identity 144 | self.assertIn("True", cell_text(nb, 9)) 145 | 146 | # Section Basic Workflows, subsection Runnables, run Duplicate 147 | self.assertIn("2", cell_text(nb, 11)) 148 | 149 | # Section Basic Workflows, subsection Runnables, Const for samples=None 150 | self.assertIn("True", cell_text(nb, 14)) 151 | 152 | # Section Basic Workflows, subsection Runnables, default tabu 153 | _check_energy(nb["cells"][16]) 154 | 155 | # Section Basic Workflows, subsection Runnables, tabu with long timeout 156 | _check_energy(nb["cells"][19]) 157 | 158 | # Section Basic Workflows, subsection Runnables 159 | self.assertIn("info", cell_text(nb, 22)) 160 | 161 | # Section Basic Workflows, subsection Branch, print_counters() 162 | self.assertIn("Branch", cell_text(nb, 24)) 163 | 164 | # Section Basic Workflows, subsection Branch, tabu generates initial samples 165 | _check_energy(nb["cells"][26]) 166 | 167 | # Section Basic Workflows, subsection Branch, tabu all-1s initial samples 168 | _check_energy(nb["cells"][29]) 169 | 170 | # Section A Practical Example, initial energy 171 | _check_energy(nb["cells"][31]) 172 | 173 | # Section Parallel Branches: Blocking, SIMO 174 | self.assertIn("2", cell_text(nb, 33)) 175 | 176 | # Section Parallel Branches: Blocking, MIMO, problem versus subproblem 177 | cell_output = re.findall(r'\d+', cell_text(nb, 35)) 178 | self.assertGreater(int(cell_output[0]), int(cell_output[1])) 179 | 180 | # Section Parallel Branches: Blocking, MIMO, exercise with two-branch energies 181 | cell_output_str = cell_text(nb, 38) 182 | energies_str = re.findall(r'\[(.*?)\]', cell_output_str)[0].split(',') 183 | self.assertLess(float(energies_str[0]), 0) 184 | self.assertLess(float(energies_str[1]), 0) 185 | 186 | # Section Parallel Branches Non-Blocking, sample sets in two states 187 | cell_output_str = cell_text(nb, 40) 188 | energies_str = re.findall(r'\d+', cell_output_str) 189 | self.assertGreater(int(energies_str[0]), 0) 190 | self.assertGreater(int(energies_str[1]), 0) 191 | 192 | # Section Selecting Samples, lowest energy 193 | _check_energy(nb["cells"][42]) 194 | 195 | # Section Customizing Sample Selection, default tabu search, lowest energy 196 | _check_energy(nb["cells"][44]) 197 | 198 | # Section Customizing Sample Selection, lowest energy weighted by runtime 199 | cell_output_str = cell_text(nb, 47) 200 | energies_str = re.findall(r'-\d+', cell_output_str) 201 | self.assertEqual(len(energies_str), 7) 202 | 203 | # Section Customizing Sample Selection, bonus exercise, lowest energy 204 | _check_energy(nb["cells"][50]) 205 | 206 | # Section Customizing Sample Selection, bonus exercise, test cell 207 | cell_output_str = cell_text(nb, 51) 208 | energies_str = re.findall(r'-\d+.\d+', cell_output_str) 209 | self.assertGreater(len(energies_str), 0) 210 | 211 | # Section Iterating, print_counters() 212 | self.assertIn("Loop", cell_text(nb, 53)) 213 | 214 | # Section Sample Workflows, Decomposition, Unwind with rolling_history 215 | cell_output_str = cell_text(nb, 70) 216 | variables_str = re.findall(r'\d+', cell_output_str) 217 | self.assertGreater(len(variables_str), 20) 218 | 219 | # Section Sample Workflows, Postprocessing, num_reads=10, before pp 220 | _check_energy(nb["cells"][73]) 221 | 222 | # Section Sample Workflows, Postprocessing, num_reads=10, with pp 223 | _check_energy(nb["cells"][75]) 224 | 225 | # Section Experimenting with Auto-Embedding Vs Pre-Embedding, print_structure() 226 | self.assertIn("SplatComposer", cell_text(nb, 80)) 227 | 228 | # Section Experimenting with Auto-Embedding Vs Pre-Embedding, print_counters() 229 | self.assertIn("QPUSubproblemAutoEmbeddingSampler", cell_text(nb, 82)) 230 | 231 | @unittest.skipIf(os.getenv('SKIP_INT_TESTS'), "Skipping integration test.") 232 | def test_jn3(self): 233 | # Smoketest 234 | MAX_RUN_TIME = 800 235 | 236 | jn_file = os.path.join(jn_dir, '03-hybrid-computing-components.ipynb') 237 | nb, errors = robust_run_jn(jn_file, MAX_RUN_TIME, self.MAX_EMBEDDING_RETRIES) 238 | 239 | self.assertEqual(errors, []) 240 | 241 | # Test cell outputs: 242 | 243 | # Section Creating States, create a state from the example problem 244 | self.assertIn("hybrid.core.SampleSet", cell_text(nb, 7)) 245 | 246 | # Section Creating States, max_sample used, read energy 247 | energy_str = re.findall(r'\d+.\d+', cell_text(nb, 9)) 248 | self.assertGreater(len(energy_str), 0) 249 | 250 | # Section Updating States, updated() method 251 | self.assertIn("Some information", cell_text(nb, 11)) 252 | 253 | # Section Updating States, updated() method plus dup() 254 | self.assertIn("Some more information", cell_text(nb, 13)) 255 | 256 | # Section Resolving States 257 | self.assertIn("state=", cell_text(nb, 15)) 258 | 259 | # Section Resolving States, using result() 260 | self.assertIn("Future", cell_text(nb, 17)) 261 | 262 | # Section Resolving States, Run Identity in a loop 263 | energy_str = re.findall(r'\d+.\d+', cell_text(nb, 19)) 264 | self.assertGreater(len(energy_str), 0) 265 | 266 | # Section Executing Runnables, using error() 267 | self.assertIn("int", cell_text(nb, 22)) 268 | 269 | # Section Executing Runnables, using next() 270 | energy_str = re.findall(r'\d+.\d+', cell_text(nb, 24)) 271 | self.assertGreater(len(energy_str), 0) 272 | 273 | # Section Terminating Runnables, using stop() 274 | self.assertIn("state=", cell_text(nb, 27)) 275 | 276 | # Section Terminating Runnables, using stop() 277 | self.assertIn("state=finished", cell_text(nb, 29)) 278 | 279 | # Section A Simple Runnable, counter after one execution 280 | self.assertIn("1", cell_text(nb, 34)) 281 | 282 | # Section A Simple Runnable, Exercise, counter after one execution 283 | self.assertIn("1", cell_text(nb, 38)) 284 | 285 | # Section Customizing Termination, run less than 10 sec 286 | counter_str = re.findall(r'\d+', cell_text(nb, 42)) 287 | self.assertGreater(int(counter_str[0]), 0) 288 | 289 | # Section Customizing Termination, Loop implements @stoppable 290 | counter_str = re.findall(r'\d+', cell_text(nb, 44)) 291 | self.assertGreater(int(counter_str[0]), 0) 292 | 293 | # Section Section Customizing Termination, halt() method 294 | self.assertIn("Terminated", cell_text(nb, 47)) 295 | 296 | # Section Customizing Termination, exercise, halt() method 297 | counter_str = re.findall(r'\d+', cell_text(nb, 51)) 298 | self.assertGreater(int(counter_str[0]), 0) 299 | 300 | # Section Example Trait: SISO, IncrementCount on a States-class object 301 | self.assertIn("object has no attribute", cell_text(nb, 55)) 302 | 303 | # Section Example Trait: SISO, IncrementCount with the SISO trait 304 | self.assertIn("single state required", cell_text(nb, 59)) 305 | 306 | # Section Example Trait: MIMO, exercise 307 | #self.assertIn("state sequence required", cell_text(nb, 65)) 308 | 309 | # Section Example: Existing Trait SamplesProcessor 310 | #self.assertIn("input state is missing", nb["cells"][70]["outputs"][1]["text"]) 311 | 312 | # Section Example: New Trait TraitCounterIntaking 313 | self.assertIn("input state is missing", cell_text(nb, 73)) 314 | 315 | # Section A Custom Sampler, exercise output 316 | filtered_nodes = re.findall(r'\[(.*?)\]', cell_text(nb, 78))[0].split() 317 | self.assertGreater(len(filtered_nodes), 0) 318 | 319 | # Section Dimod Conversion 320 | energy_str = re.findall(r'-\d+.\d+', cell_text(nb, 82)) 321 | self.assertLess(float(energy_str[0]), 0) 322 | 323 | # Section Dimod Conversion, run the converted workflow 324 | energy_str = re.findall(r'-\d+.\d+', cell_text(nb, 86)) 325 | self.assertLess(float(energy_str[0]), 0) 326 | 327 | # Section Dimod Conversion, specifying selected nodes in filter_nodes 328 | filtered_nodes = re.findall(r'\[(.*?)\]', cell_text(nb, 90))[0].split() 329 | self.assertGreater(len(filtered_nodes), 0) 330 | 331 | # Section Dimod Conversion, converted TabuProblemSamplerFilteredNodes 332 | filtered_nodes = re.findall(r'\[(.*?)\]', cell_text(nb, 94))[0].split() 333 | self.assertGreater(len(filtered_nodes), 0) 334 | -------------------------------------------------------------------------------- /01-hybrid-computing-getting-started.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Quantum-Classical Hybrid Computing: Getting Started\n", 8 | "\n", 9 | "D-Wave offers two complementary approaches to hybrid computing: \n", 10 | "\n", 11 | "* [Leap Quantum Application Environment](https://cloud.dwavesys.com/leap) maintains state-of-the-art hybrid solvers.\n", 12 | "* Ocean software's [*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) provides reference\n", 13 | " samplers and a Python framework for developing custom hybrid workflows and components.\n", 14 | "\n", 15 | "These notebooks demonstrate how you can apply Leap's cloud-based hybrid solvers and the *dwave-hybrid* Python framework's samplers to your problem, create hybrid workflows, and develop custom hybrid components, expanding the content presented in the D-Wave webinar [Hybrid Quantum Programming](https://www.youtube.com/watch?v=EW44reo8Bn0).\n", 16 | "\n", 17 | "This first notebook starts you off with Leap's hybrid solvers and *dwave-hybrid*'s out-of-the-box reference samplers and workflows:\n", 18 | "\n", 19 | "1. [A Few Words on Hybrid Computing](#A-Few-Words-on-Hybrid-Computing) section gives a very short introduction to quantum-classical hybrid computing. \n", 20 | "2. [A Sample Problem](#A-Sample-Problem) section provides an example problem used throughout the notebooks.\n", 21 | "3. [Using Leap's Hybrid Solvers](#Using-Leap's-Hybrid-Solvers) section demonstrates using Leap's cloud-based hybrid solvers.\n", 22 | "4. [Reference Samplers and Workflows](#Reference-Samplers-and-Workflows) section demonstrates using *dwave-hybrid* reference samplers and workflows. \n", 23 | "5. [Operational Utilities](#Operational-Utilities) section shows utilities to help your coding. \n", 24 | "\n", 25 | "More-advanced content is provided in the following notebooks:\n", 26 | "\n", 27 | "* [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook shows how you use *dwave-hybrid* components to create custom workflows for your problem.\n", 28 | "* [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook shows how to develop your own hybrid components for optimal performance. \n", 29 | "\n", 30 | "## Prerequisite Knowledge\n", 31 | "\n", 32 | "These notebooks assume basic familiarity with [Ocean Software](https://docs.ocean.dwavesys.com/en/stable/index.html) and its binary quadratic model ([BQM](https://docs.ocean.dwavesys.com/en/stable/concepts/bqm.html)) approach to formulating problems on quantum computers. [Leap's](https://cloud.dwavesys.com/leap) *Structural Imbalance* and *Factoring* Jupyter Notebooks are good introductions to the basics of solving problems on a quantum computer, as is the [Getting Started with the D-Wave System](https://docs.dwavesys.com/docs/latest/doc_getting_started.html) guide. For an introduction to Ocean software, see [Ocean Software documentation](https://docs.ocean.dwavesys.com/en/stable/getting_started.html#gs). " 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "**New to Jupyter Notebooks?** JNs are divided into text or code cells. Pressing the **Run** button in the menu bar moves to the next cell. Code cells are marked by an \"In: \\[\\]\" to the left; when run, an asterisk displays until code completion: \"In: \\[\\*\\]\"." 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "# A Few Words on Hybrid Computing\n", 47 | "Quantum-classical hybrid is the use of both classical and quantum resources to solve problems, exploiting the complementary strengths that each provides. As quantum processors grow in size, offloading hard optimization problems to quantum computers promises performance benefits similar to CPUs' outsourcing of compute-intensive graphics-display processing to GPUs. \n", 48 | "\n", 49 | "This [Medium Article](https://medium.com/d-wave/three-truths-and-the-advent-of-hybrid-quantum-computing-1941ba46ff8c) provides an overview of, and motivation for, hybrid computing. \n", 50 | "\n", 51 | "D-Wave's [Leap quantum cloud service](https://cloud.dwavesys.com/leap) provides cloud-based hybrid solvers you can submit arbitrary BQMs to. These solvers, which implement state-of-the-art classical algorithms together with intelligent allocation of the quantum processing unit (QPU) to parts of the problem where it benefits most, are designed to accommodate even very large problems. Leap's solvers can relieve you of the burden of any current and future development and optimization of hybrid algorithms that best solve your problem. \n", 52 | "\n", 53 | "[*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) provides you with a Python framework for building a variety of flexible hybrid workflows. These use quantum and classical resources together to find good solutions to your problem. For example, a hybrid workflow might use classical resources to find a problem’s hard core and send that to the QPU, or break a large problem into smaller pieces that can be solved on a QPU and then recombined.\n", 54 | "\n", 55 | "The *dwave-hybrid* framework enables rapid development of experimental prototypes, which provide insight into expected performance of the productized versions. It provides reference samplers and workflows you can quickly plug into your application code. You can easily experiment with customizing workflows that best solve your problem. You can also develop your own hybrid components to optimize performance. " 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "# A Sample Problem\n", 63 | "This section creates an example problem used in the following sections and notebooks. \n", 64 | "\n", 65 | "Each of the two sets of nodes in the illustrative graph below, one red and the other blue, form an *independent set*; that is, a set of vertices with no edge connecting any of its member pairs. \n", 66 | "\n", 67 | "\n", 68 | "\n", 69 | "Such a graph might represent the problem of [antenna selection](https://github.com/dwave-examples/antenna-selection), finding good coverage for a sprinkler system, or many other real-world optimization problems. If the graph represents a sprinkler system, with edges corresponding to overlaps in sprinklers' watering, a [maximum independent set](https://en.wikipedia.org/wiki/Independent_set_(graph_theory)) solves the problem of watering a lawn evenly with the lowest number of sprinklers. " 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "The first code cell below uses [networkx](https://networkx.github.io) to build a random sparse graph—the `random_geometric_graph()` function places uniformly at random a specified number of nodes, `problem_node_count`, in a unit cube, joining edges of any two if the distance is below a given `radius`—and utility graphics code to plot it. To see this graphics code, select **Jupyter File Explorer View** from the Online Learning page and navigate to the folder for this notebook.\n", 77 | "\n", 78 | "
Note: Problem size (nodes and density of edges) selected below ensures that runtimes on compute resources (virtual CPUs) provided by the Leap environment do not exceed a few minutes.
" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "import networkx as nx\n", 88 | "from helpers.draw import plot \n", 89 | "%matplotlib inline\n", 90 | "\n", 91 | "problem_node_count = 300\n", 92 | "\n", 93 | "G = nx.random_geometric_graph(problem_node_count, radius=0.0005*problem_node_count)\n", 94 | "\n", 95 | "plot(G)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "As with solving any problem on the quantum computer, the first step of finding the graph's maximum independent set is to [formulate the problem as a BQM](https://docs.ocean.dwavesys.com/en/stable/overview/solving_problems.html). Use Ocean software's [dwave_networkx](https://docs.ocean.dwavesys.com/en/stable/docs_dnx/sdk_index.html) and [dimod](https://docs.ocean.dwavesys.com/en/stable/docs_dimod/sdk_index.html) to create and hold an appropriate BQM. " 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "import dwave_networkx as dnx\n", 112 | "import dimod\n", 113 | "\n", 114 | "qubo = dnx.algorithms.independent_set.maximum_weighted_independent_set_qubo(G)\n", 115 | "bqm = dimod.BQM.from_qubo(qubo)\n", 116 | "\n", 117 | "print(\"BQM sets all node biases to {} and edge biases to {}.\".format(\n", 118 | " bqm.linear[list(G.edges)[0][0]], bqm.quadratic[list(G.edges)[0]]))" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "# Using Leap's Hybrid Solvers\n", 126 | "D-Wave's [Leap quantum cloud service](https://cloud.dwavesys.com/leap) provides state-of-the-art hybrid solvers you can submit arbitrary BQMs to, efficiently outsourcing the development and optimization of hybrid algorithms to D-Wave's scientists with their many years of expertise in this field. This enables you to deploy a tested hybrid solver to solve your application code's BQM with minimal code updates.\n", 127 | "\n", 128 | "This section provides a demonstration of using a Leap hybrid solver.\n", 129 | "\n", 130 | "
Note: Not all accounts have access to this type of solver. This is the only section of the Hybrid Computing notebooks that uses these solvers—if you do not see solvers in the Hybrid section of Supported Solvers in your Leap dashboard, skip to the next section.
" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "Run `LeapHybridSampler` with its default parameters on the BQM representing the problem graph. \n", 138 | "\n", 139 | "The resulting samples represent nodes of a maximum independent set with ones. The `print` line below uses [NumPy](https://numpy.org/) to count and report the size of the set. " 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "from dwave.system import LeapHybridSampler\n", 149 | "import numpy as np\n", 150 | "\n", 151 | "result = LeapHybridSampler().sample(bqm, label='Notebook - Hybrid Computing 1')\n", 152 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.record.sample), \n", 153 | " result.first.energy))" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "Plot the problem with the selected nodes colored deeper red than the background graph. " 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "plot(G, subgraphs=[G.subgraph(np.flatnonzero(result.record.sample==1))])" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "# Reference Samplers and Workflows\n", 177 | "*dwave-hybrid* provides [reference workflows and samplers](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/reference.html) that you can import into your application code. These are not optimized to a particular problem, but are a good starting point for your hybrid solution.\n", 178 | "\n", 179 | "The purpose of the rest of this notebook is to show you how you can quickly deploy these ready-made components into your application code. The [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) and [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebooks will explain the components themselves in more detail than the minimum needed for usage given here." 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "## Sampler: Kerberos \n", 187 | "You can run the Kerberos sampler, an out-of-the-box hybrid sampler provided in the *dwave-hybrid* package, in the same way you typically run any dimod sampler. \n", 188 | "\n", 189 | "Run `KerberosSampler` with its default parameters on the BQM representing the problem graph. " 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "import hybrid \n", 199 | "import numpy as np\n", 200 | "\n", 201 | "result = hybrid.KerberosSampler().sample(bqm, \n", 202 | " qpu_params={'label': 'Notebook - Hybrid Computing 1'})\n", 203 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.record.sample), \n", 204 | " result.first.energy))" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "metadata": {}, 210 | "source": [ 211 | "### Configuring the Sampler\n", 212 | "Kerberos default parameters work reasonably well with a large variety of problems but are not optimized to any particular class of problem. Once your application code runs with a reference sampler, experiment with adjusting its parameters to improve performance.\n", 213 | "\n", 214 | "To configure Kerberos for your application, it's sufficient to know that Kerberos loops over three sampling branches in parallel, selecting after each iteration the best results from tabu search, simulated annealing, and QPU sampling of a subproblem.\n", 215 | "\n", 216 | "\n", 217 | "\n", 218 | "Internally, the Kerberos structure includes the following components:\n", 219 | "\n", 220 | "* *Interruptable Tabu Sampler* and *Interruptable SA Sampler* are classical samplers that \n", 221 | " run on CPU until interrupted by the QPU branch. \n", 222 | "* *Energy Impact Decomposer* selects a subset of the problem's variables (those that\n", 223 | " maximally contribute to the problem energy); *QPU Sampler* submits subproblems to the\n", 224 | " quantum computer; and *SplatComposer* inserts subproblems' samples into problem \n", 225 | " samples.\n", 226 | "* *ArgMin* selects the best samples from an iteration, which terminates when the quantum \n", 227 | " computer returns samples. \n", 228 | "* *Loop* iterates these *Racing Branches* until a termination condition is reached. \n", 229 | "\n", 230 | "The [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook dives into the details of *dwave-hybrid* components and structuring workflows. \n", 231 | "\n", 232 | "\n", 233 | "The call below limits the maximum number of iterations to `max_iter=10`. " 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "result = hybrid.KerberosSampler().sample(bqm, \n", 243 | " max_iter=10, \n", 244 | " qpu_params={'label': 'Notebook - Hybrid Computing 1'})\n", 245 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.record.sample), \n", 246 | " result.first.energy))" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "
\n", 254 | " Exercise: Kerberos provides a large set of \n", 255 | " configurable parameters. In the placeholder code cell below, experiment with a few: (1) Return more sample sets (2) Increase the number of QPU sample sets (reads) per submission (3) Reduce the tabu search to 200 ms.\n", 256 | "\n", 257 | "Open the hidden code cell below it for a solution.\n", 258 | "\n", 259 | "Remember that for large problems, CPU runtimes can be long.\n", 260 | "
" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": { 267 | "solution2": "hidden", 268 | "solution2_first": true 269 | }, 270 | "outputs": [], 271 | "source": [ 272 | "# Placeholder cell for exercise. Modify and run your code here.\n", 273 | "\n", 274 | "result = hybrid.KerberosSampler().sample(bqm, \n", 275 | " max_iter=10, \n", 276 | " qpu_params={'label': 'Notebook - Hybrid Computing 1'})\n", 277 | "print(\"Found solutions with energy\", result.record.energy)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": { 284 | "jupyter": { 285 | "source_hidden": true 286 | }, 287 | "solution2": "hidden" 288 | }, 289 | "outputs": [], 290 | "source": [ 291 | "# Click here to see solution:\n", 292 | "result = hybrid.KerberosSampler().sample(bqm, \n", 293 | " num_reads=3, \n", 294 | " max_iter=10, \n", 295 | " qpu_reads=100, \n", 296 | " tabu_timeout=200,\n", 297 | " qpu_params={'label': 'Notebook - Hybrid Computing 1'})\n", 298 | "print(\"Found solutions with energy {}.\".format(result.record.energy))" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "
\n", 306 | " Advanced Exercise: Increase the size of the subproblems submitted to the QPU to 70 variables while ensuring that Kerberos selects a QPU with over 1800 qubits. Open the hidden code cell below it for a solution.\n", 307 | "\n", 308 | "

Hint: The D-Wave Cloud Client Client.get_solvers() method handles filtering for feature-based selection of QPUs.

\n", 309 | "
" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": null, 315 | "metadata": { 316 | "solution2": "hidden", 317 | "solution2_first": true 318 | }, 319 | "outputs": [], 320 | "source": [ 321 | "# Write and run your exercise code here:\n" 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": null, 327 | "metadata": { 328 | "jupyter": { 329 | "source_hidden": true 330 | }, 331 | "solution2": "hidden" 332 | }, 333 | "outputs": [], 334 | "source": [ 335 | "# Click here to see solution:\n", 336 | "from dwave.system import DWaveSampler\n", 337 | "\n", 338 | "sampler_qpu = DWaveSampler(solver={'num_qubits__gt':1800})\n", 339 | "\n", 340 | "result = hybrid.KerberosSampler().sample(bqm, max_iter=10, \n", 341 | " max_subproblem_size=70,\n", 342 | " qpu_sampler=sampler_qpu,\n", 343 | " qpu_params={'label': 'Notebook - Hybrid Computing 1'})\n", 344 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.record.sample), \n", 345 | " result.first.energy))" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": {}, 351 | "source": [ 352 | "## Workflow: Parallel Tempering \n", 353 | "*dwave-hybrid* provides tested [reference workflows](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/reference.html) that you can plug into your application code. \n", 354 | "\n", 355 | "To simply use such workflows, it's sufficient to know that the *dwave-hybrid* workflows comprise two main types of components:\n", 356 | "\n", 357 | "* *State*-class components hold a problem, samples, and optionally additional information.\n", 358 | "* *Runnable*-class components take input states, run for an iteration, and output updated states. \n", 359 | "\n", 360 | "Workflows—which are themselves Runnables—input states representing a problem BQM and aim to produce states updated with samples that are good solutions to the problem or parts of it. Sometimes you may be in a position to provide initial samples with values better than random for your problem; for example, if you are intermittently running a very large problem, possibly on more than one solver, and saving intermediate results. Workflows may run for a single iteration or many iterations. \n", 361 | "\n", 362 | "The [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook dives into the details of *dwave-hybrid* workflows." 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "metadata": {}, 368 | "source": [ 369 | "[Parallel Tempering (PT)](https://en.wikipedia.org/wiki/Parallel_tempering) runs a number of replicas of simulated annealing at different temperatures and swaps some low- and high-energy samples between replicas. In this way, a low-temperature replica injected with a high-energy sample might escape a local minimum. \n", 370 | "\n", 371 | "In the next cell, an initial state is produced from the BQM by the `State.from_problem` utility and input into workflow `pt_workflow`, generated by `ParallelTempering`. The example calls two methods:\n", 372 | "\n", 373 | "* Runnable [run()](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) method to execute an iteration of the PT reference workflow (workflows are Runnables). \n", 374 | "* State [result()](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) method to resolve the result, which is returned in a [Future](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future)-like object. \n", 375 | "\n", 376 | "Note: Because the default PT workflow results in long CPU runtimes on the example problem of this notebook, parameters are specified below for a shorter execution. " 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": null, 382 | "metadata": {}, 383 | "outputs": [], 384 | "source": [ 385 | "state_start = hybrid.State.from_problem(bqm)\n", 386 | "\n", 387 | "pt_workflow = hybrid.ParallelTempering(num_replicas=3, max_iter=5)\n", 388 | "\n", 389 | "result = pt_workflow.run(state_start, max_time=4).result()\n", 390 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.samples.record.sample),\n", 391 | " result.samples.first.energy))" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "metadata": {}, 397 | "source": [ 398 | "Because the `KerberosSampler` of the [Sampler: Kerberos](#Sampler:-Kerberos) section is itself just a workflow, you can replace `ParallelTempering` with the [Kerberos reference workflow](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/reference.html), configuring the appropriate parameters, and run the Kerberos workflow directly instead. \n", 399 | "\n", 400 | "The [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook demonstrates how you can convert hybrid workflows into dimod samplers. " 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "metadata": {}, 406 | "source": [ 407 | "### Configuring the Workflow\n", 408 | "
\n", 409 | " Exercise: Try adjusting some workflow parameters and observing the effect on performance. Open the hidden code cell below it for a solution.\n", 410 | " Remember that for large problems, CPU runtimes can be long.\n", 411 | "
" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "execution_count": null, 417 | "metadata": { 418 | "solution2": "hidden", 419 | "solution2_first": true 420 | }, 421 | "outputs": [], 422 | "source": [ 423 | "# Write and run your exercise code here:\n" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": null, 429 | "metadata": { 430 | "jupyter": { 431 | "source_hidden": true 432 | }, 433 | "solution2": "hidden" 434 | }, 435 | "outputs": [], 436 | "source": [ 437 | "# Click here to see solution:\n", 438 | "result = pt_workflow.run(state_start, max_iter=1, max_time=7, num_sweeps=1000).result()\n", 439 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.samples.record.sample), \n", 440 | " result.samples.first.energy))" 441 | ] 442 | }, 443 | { 444 | "cell_type": "markdown", 445 | "metadata": {}, 446 | "source": [ 447 | "You can try other implemented [reference workflows](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/reference.html), such as hybridized PT and population annealing. In the [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook you will build your own such workflows to best fit your problem." 448 | ] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "metadata": {}, 453 | "source": [ 454 | "# Operational Utilities\n", 455 | "This section demonstrates a few useful operational utilities that can help you understand and debug your code.\n", 456 | "\n", 457 | "## Structure\n", 458 | "The `print_structure` method prints the hybrid workflow's structure of nested components." 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "hybrid.print_structure(pt_workflow)" 468 | ] 469 | }, 470 | { 471 | "cell_type": "markdown", 472 | "metadata": {}, 473 | "source": [ 474 | "## Counters\n", 475 | "The `print_counters` method helps you examine the execution of a hybrid workflow. " 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": null, 481 | "metadata": {}, 482 | "outputs": [], 483 | "source": [ 484 | "hybrid.print_counters(pt_workflow)" 485 | ] 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "metadata": {}, 490 | "source": [ 491 | "## Logging\n", 492 | "The `logger.setLevel` method sets logging level as described under [Using the Framework](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/intro/using.html). \n", 493 | "\n", 494 | "*dwave-hybrid* supports logging levels TRACE, DEBUG, INFO, WARNING, ERROR, and CRITICAL in ascending order of severity. Try setting lower levels to learn what additional information you have available for understanding and debugging your hybrid application code. \n", 495 | "\n", 496 | "Afterwards, return the logging level to ERROR (the default level) or to any level you prefer for the following notebooks. " 497 | ] 498 | }, 499 | { 500 | "cell_type": "code", 501 | "execution_count": null, 502 | "metadata": {}, 503 | "outputs": [], 504 | "source": [ 505 | "hybrid.logger.setLevel(hybrid.logging.INFO) \n", 506 | "\n", 507 | "result = pt_workflow.run(state_start, max_iter=1).result()\n", 508 | "\n", 509 | "print(\"Found solution with {} nodes at energy {}.\".format(np.sum(result.samples.record.sample), \n", 510 | " result.samples.first.energy))\n", 511 | "hybrid.logger.setLevel(hybrid.logging.ERROR) " 512 | ] 513 | }, 514 | { 515 | "cell_type": "markdown", 516 | "metadata": {}, 517 | "source": [ 518 | "Copyright © 2020 D-Wave Systems, Inc\n", 519 | "\n", 520 | "The software is licensed under the Apache License, Version 2.0 (the \"License\");\n", 521 | "you may not use this file except in compliance with the License.\n", 522 | "You may obtain a copy of the License at\n", 523 | "\n", 524 | " http://www.apache.org/licenses/LICENSE-2.0\n", 525 | "\n", 526 | "Unless required by applicable law or agreed to in writing, software\n", 527 | "distributed under the License is distributed on an \"AS IS\" BASIS,\n", 528 | "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 529 | "See the License for the specific language governing permissions and\n", 530 | "limitations under the License.\n", 531 | "\n", 532 | "\"Creative
This Jupyter Notebook is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License" 533 | ] 534 | } 535 | ], 536 | "metadata": { 537 | "kernelspec": { 538 | "display_name": "Python 3", 539 | "language": "python", 540 | "name": "python3" 541 | }, 542 | "language_info": { 543 | "codemirror_mode": { 544 | "name": "ipython", 545 | "version": 3 546 | }, 547 | "file_extension": ".py", 548 | "mimetype": "text/x-python", 549 | "name": "python", 550 | "nbconvert_exporter": "python", 551 | "pygments_lexer": "ipython3", 552 | "version": "3.7.0" 553 | }, 554 | "latex_envs": { 555 | "LaTeX_envs_menu_present": true, 556 | "autoclose": false, 557 | "autocomplete": true, 558 | "bibliofile": "biblio.bib", 559 | "cite_by": "apalike", 560 | "current_citInitial": 1, 561 | "eqLabelWithNumbers": true, 562 | "eqNumInitial": 1, 563 | "hotkeys": { 564 | "equation": "Ctrl-E", 565 | "itemize": "Ctrl-I" 566 | }, 567 | "labels_anchors": false, 568 | "latex_user_defs": false, 569 | "report_style_numbering": false, 570 | "user_envs_cfg": false 571 | }, 572 | "toc": { 573 | "base_numbering": 1, 574 | "nav_menu": {}, 575 | "number_sections": false, 576 | "sideBar": true, 577 | "skip_h1_title": false, 578 | "title_cell": "Table of Contents", 579 | "title_sidebar": "Contents", 580 | "toc_cell": false, 581 | "toc_position": { 582 | "height": "calc(100% - 180px)", 583 | "left": "10px", 584 | "top": "150px", 585 | "width": "165px" 586 | }, 587 | "toc_section_display": true, 588 | "toc_window_display": true 589 | } 590 | }, 591 | "nbformat": 4, 592 | "nbformat_minor": 2 593 | } 594 | -------------------------------------------------------------------------------- /03-hybrid-computing-components.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Quantum-Classical Hybrid Computing: Components\n", 8 | "\n", 9 | "These notebooks expand the content presented in the D-Wave webinar [Hybrid Quantum Programming](https://www.youtube.com/watch?v=EW44reo8Bn0), demonstrating how you can apply [Leap's](https://cloud.dwavesys.com/leap) cloud-based hybrid solvers and Ocean software's [*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) solvers to your problem, create hybrid workflows, and develop custom hybrid components.\n", 10 | "\n", 11 | "This **third** notebook examines and develops *dwave-hybrid* components:\n", 12 | "\n", 13 | "1. [A Sample Problem](#A-Sample-Problem) section reproduces the [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) example problem for use in this notebook.\n", 14 | "2. [Working with Components](#Working-with-Components) section is a close look at hybrid components and their main methods.\n", 15 | "3. [Developing and Modifying Components](#Developing-and-Modifying-Components) section demonstrates developing your own components to optimize performance.\n", 16 | "\n", 17 | "More-basic content is provided in the previous notebooks:\n", 18 | "\n", 19 | "* [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) notebook starts you off with Leap hybrid solvers and *dwave-hybrid* solvers and workflows.\n", 20 | "* [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook shows how you create your own workflows using *dwave-hybrid* components. " 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# A Sample Problem\n", 28 | "This section recreates the [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) problem for use in the following sections. \n", 29 | "\n", 30 | "
Note: Problem size (nodes and density of edges) selected below ensures that runtimes on compute resources (virtual CPUs) provided by the Leap environment do not exceed a few minutes.
" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "import matplotlib.pyplot as plt\n", 40 | "from helpers.draw import plot \n", 41 | "%matplotlib inline\n", 42 | "import networkx as nx\n", 43 | "import dwave_networkx as dnx\n", 44 | "import dimod\n", 45 | "import numpy as np\n", 46 | "\n", 47 | "problem_node_count = 300\n", 48 | "\n", 49 | "G = nx.random_geometric_graph(problem_node_count, radius=0.0005*problem_node_count)\n", 50 | "\n", 51 | "qubo = dnx.algorithms.independent_set.maximum_weighted_independent_set_qubo(G)\n", 52 | "bqm = dimod.BQM.from_qubo(qubo)\n", 53 | "\n", 54 | "plot(G)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "# Working with Components\n", 62 | "\n", 63 | "*dwave-hybrid* provides a variety of components that enable you to build workflows optimized for your problem. For many problems, using just these components is sufficient. This section examines these components and their methods to help you best exploit them and to provide a foundation for section [Developing and Modifying Components](#Developing-and-Modifying-Components)." 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "## States\n", 71 | "[State](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) is a subclass of a *dwave-hybrid* class, *PliableDict*, which extends the [dict](https://docs.python.org/3/library/stdtypes.html#dict) class with attribute accessors acting as item accessors. It is used to pass computation states to and from components and typically contains at least *samples* and *problem* fields. As you saw in the [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook, standard *dwave-hybrid* Runnables add fields such as *subproblem* and *subsamples* when decomposing problems, and you can add custom fields of your own. \n", 72 | "\n", 73 | "A closely related class, [States](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html), is a list of state objects. It is used, for example, to hold the multiple states produced by a workflow's parallel branches. " 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "### Creating States\n", 81 | "*dwave-hybrid* provides several utility methods for creating states. Because Runnable components require an input state, you typically need to provide some initial state to your workflows. " 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Create a state from the example problem. The *problem* field is a [dimod](https://docs.ocean.dwavesys.com/en/stable/docs_dimod/sdk_index.html) binary quadratic model ([BQM](https://docs.ocean.dwavesys.com/en/stable/concepts/bqm.html)) and the *samples* field is a subclass of the *dimod* [SampleSet](https://docs.ocean.dwavesys.com/en/stable/docs_dimod/reference/sampleset.html). " 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "import hybrid\n", 98 | "\n", 99 | "state = hybrid.State.from_problem(bqm)\n", 100 | "print(\"Field problem is of type: {}\\nField samples is of type: {}\".format(\n", 101 | " type(state.problem), type(state.samples)))" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "You can set samples' values—for some initial samples you may be in a position to provide values you know are better than zeros or random for your problem; for example, if you are intermittently running a very large problem, possibly on more than one solver, and saving intermediate results. \n", 109 | "\n", 110 | "Here, for simplicity, `max_sample` is used to set values." 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "state = hybrid.State.from_sample(hybrid.max_sample(bqm), bqm)\n", 120 | "print(\"Initial state's sampleset has energy {}.\".format(state.samples.first.energy))" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "### Updating States\n", 128 | "The *updated()* method, which you saw demonstrated in the [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook through Runnable [Lambda](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/flow.html), returns a deep copy of the state updated by specified arguments. " 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "state_info = state.updated(info=\"Some information\")\n", 138 | "print(\"Content of info field: {}.\".format(state_info.info))" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "The method can also update the States class. Runnable `Dup` duplicates `n` times an input state to an output States class. " 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "states_info = hybrid.Dup(2).run(state_info).result()\n", 155 | "\n", 156 | "states_info2 = states_info.updated(info2=\"Some more information\")\n", 157 | "\n", 158 | "for s in states_info2:\n", 159 | " print(\"Content of info2 field: {}.\".format(s.info2))" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "### Resolving States\n", 167 | "States created by Runnables are held in [Future](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future)-like objects. By default, hybrid components use *hybrid.concurrency.thread_executor* to create an asynchronous call. The *result()* method implements `concurrent.Future`-compatible result resolution. \n", 168 | "\n", 169 | "While the loop below iterates `max_iter` times on Runnable `Identity`, which copies its input state to its output state, the output state is a Future with status \"running\". Once execution terminates, the status changes to \"finished\". " 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "from time import sleep\n", 179 | "\n", 180 | "output_state = hybrid.Loop(hybrid.Identity(), max_iter=50).run(state)\n", 181 | "\n", 182 | "print(\"State during execution: {}.\".format(output_state))\n", 183 | "sleep(3)\n", 184 | "print(\"State after execution: {}.\".format(output_state))" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "Trying to access the output state directly raises an error. Use the *result()* method to retrieve the encapsulated output state from the Future." 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "try:\n", 201 | " print(output_state.samples.first.energy)\n", 202 | "except AttributeError as err:\n", 203 | " print(\"Accessing state before resolution: {}.\".format(err))\n", 204 | "\n", 205 | "output_state = output_state.result()\n", 206 | "print(\"Accessing state after resolution: {}.\".format(output_state.samples.first.energy))" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "Runnables are by default asynchronous, but you can specify a blocking call. For a blocking call, the state is returned in a [Present](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) class, which is a `Future` resolved at construction time. \n", 214 | "\n", 215 | "Run `Identity` in a loop again, but as a blocking call. The `print` command cannot execute until execution terminates, with the status of the Present containing the output state set to \"finished\". " 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "output_state = hybrid.Loop(hybrid.Identity(), max_iter=300).run(state, \n", 225 | " executor=hybrid.immediate_executor)\n", 226 | "\n", 227 | "output_state = output_state.result()\n", 228 | "print(\"After termination and resolution: {}.\".format(output_state.samples.first.energy))" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "## Runnables\n", 236 | "[Runnable](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) is the key superclass in *dwave-hybrid*: all basic components—samplers, decomposers, composers—and flow-structuring components such as branches inherit from this class. \n", 237 | "\n", 238 | "A Runnable executes for an iteration in which it updates the state it receives. Its main methods are `run` or `next` to execute an iteration and `stop` to terminate the Runnable." 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "### Executing Runnables\n", 246 | "By this point you have run a number of components using the *run()* method. Runnable class's *run()* handles some housekeeping work and passes the Future-encapsulated input state to the *dispatch()* method for resolution. This dispatcher does some verification and passes the state to either *error()* method to handle resolution errors or to *next()* method.\n", 247 | "\n", 248 | "By default, *error()* method just raises the exception. In the [Developing and Modifying Components](#Developing-and-Modifying-Components) section you will overwrite the base Runnable *error()* method to customize error handling." 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "invalid_state_in_future = hybrid.Identity().run(1)\n", 258 | "\n", 259 | "try:\n", 260 | " hybrid.Dup(2).dispatch(invalid_state_in_future)\n", 261 | "except Exception as exc:\n", 262 | " print(\"Default error handling raises message: {}.\".format(exc))" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "The *next()* method executes one blocking iteration of an instantiated Runnable with a valid state as input. In the [Developing and Modifying Components](#Developing-and-Modifying-Components) section you will overwrite the base Runnable *next()* method to customize your own components." 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "output_state = hybrid.Identity().next(state)\n", 279 | "\n", 280 | "print(\"Energy: {}.\".format(state.samples.first.energy))" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "Note that in contrast to *run()*, which inputs a Future, the input to *next()* is a state resolved and validated by *dispatch()*." 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": {}, 293 | "source": [ 294 | "### Terminating Runnables\n", 295 | "Runnable components might not terminate on their own; for example, your workflow might run a non-terminating sampler or a loop without terminating conditions. In such cases, your workflow will use the *stop()* method to terminate execution. (Note that not all components are interruptible.) " 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": null, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "workflow = hybrid.Loop(hybrid.Identity())\n", 305 | "\n", 306 | "output_state = workflow.run(state)\n", 307 | "\n", 308 | "sleep(0.2)\n", 309 | "print(\"Workflow after run(): {}.\".format(output_state))\n", 310 | "workflow.stop()\n", 311 | "sleep(0.2)\n", 312 | "print(\"Workflow after stop(): {}.\".format(output_state))" 313 | ] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "metadata": {}, 318 | "source": [ 319 | "The base Runnable *stop()* method calls the *halt()* method, which is not implemented by the base Runnable itself. As you will do in the [Developing and Modifying Components](#Developing-and-Modifying-Components) section, Runnable components overwrite the *halt()* method." 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [ 328 | "workflow = hybrid.Loop(hybrid.Identity())\n", 329 | "\n", 330 | "output_state = workflow.run(state)\n", 331 | "\n", 332 | "workflow.halt()\n", 333 | "sleep(0.2)\n", 334 | "print(\"Workflow after halt(): {}.\".format(output_state))" 335 | ] 336 | }, 337 | { 338 | "cell_type": "markdown", 339 | "metadata": {}, 340 | "source": [ 341 | "# Developing and Modifying Components\n", 342 | "Although *dwave-hybrid* provides a variety of components, some problems can benefit from non-standard processing or you might have fresh ideas on ways to solve a problem. This section demonstrates how to write new hybrid components and modify existing components. " 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "metadata": {}, 348 | "source": [ 349 | "## A Simple Runnable\n", 350 | "You can create a simple hybrid component of your own by overwriting the Runnable base class's *next()* method, which executes one blocking iteration of an instantiated Runnable with a valid state as input. \n", 351 | "\n", 352 | "`IncrementCount` is a very simple example: it updates a counter in its input state, similar to such updates that the [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook demonstrated using Runnable `Lambda`. " 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "metadata": {}, 359 | "outputs": [], 360 | "source": [ 361 | "class IncrementCount(hybrid.Runnable):\n", 362 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 363 | " \n", 364 | " def next(self, state, **kwargs):\n", 365 | " return state.updated(counter=state.counter+1)" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": {}, 371 | "source": [ 372 | "Set an initial value for the `counter` field and instantiate and execute your simple component. In the [Adding Custom Traits](#Adding-Custom-Traits) subsection below, you will see how to ensure the input state contains any such required field." 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": null, 378 | "metadata": {}, 379 | "outputs": [], 380 | "source": [ 381 | "state_count_0 = state.updated(counter=0)\n", 382 | "\n", 383 | "output_state = IncrementCount().run(state_count_0).result()\n", 384 | "print(\"Counter field value after one execution: {}.\".format(output_state.counter))" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": {}, 390 | "source": [ 391 | "
\n", 392 | " Exercise: In the placeholder code cell below, modify IncrementCount to also print the state's best energy. Test your code in the EXERCISE TEST CELL below. Open the hidden code cell above it for a solution.\n", 393 | "

Note: a samples field is present in the tested input state, state_count_0, and for simplicity, assume this is always the case. In the Adding Custom Traits subsection below, you will see how to ensure this is always the case.

" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": null, 399 | "metadata": { 400 | "solution2": "hidden", 401 | "solution2_first": true 402 | }, 403 | "outputs": [], 404 | "source": [ 405 | "# Placeholder cell for exercise code. Modify the code lines below:\n", 406 | "\n", 407 | "class IncrementCountPrintEnergy(hybrid.Runnable):\n", 408 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 409 | " \n", 410 | " def next(self, state, **kwargs):\n", 411 | " return state.updated(counter=state.counter+1)" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "execution_count": null, 417 | "metadata": { 418 | "jupyter": { 419 | "source_hidden": true 420 | }, 421 | "solution2": "hidden" 422 | }, 423 | "outputs": [], 424 | "source": [ 425 | "# Click here to see solution:\n", 426 | "class IncrementCountIncrementCountPrintEnergy(hybrid.Runnable):\n", 427 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 428 | " \n", 429 | " def next(self, state, **kwargs):\n", 430 | " print(\"Best energy in current state is {}.\".format(state.samples.first.energy))\n", 431 | " return state.updated(counter=state.counter+1)" 432 | ] 433 | }, 434 | { 435 | "cell_type": "code", 436 | "execution_count": null, 437 | "metadata": {}, 438 | "outputs": [], 439 | "source": [ 440 | "# EXERCISE TEST CELL\n", 441 | "\n", 442 | "output_state = IncrementCountIncrementCountPrintEnergy().run(state_count_0).result()\n", 443 | "print(\"Counter field value after one execution: {}.\".format(output_state.counter))" 444 | ] 445 | }, 446 | { 447 | "cell_type": "markdown", 448 | "metadata": {}, 449 | "source": [ 450 | "## Customizing Termination\n", 451 | "The `@stoppable` [decorator](https://www.python.org/dev/peps/pep-0318/) extends Runnable subclasses with a `stop_signal` [semaphore/event](https://docs.python.org/2/library/threading.html). \n", 452 | "\n", 453 | "The `IncrementCountSeconds` example Runnable increments a counter in its input state every second for up to 10 seconds, unless terminated by *stop()*. " 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": null, 459 | "metadata": {}, 460 | "outputs": [], 461 | "source": [ 462 | "import time\n", 463 | "\n", 464 | "@hybrid.stoppable\n", 465 | "class IncrementCountSeconds(hybrid.Runnable):\n", 466 | " \"\"\"An example Runnable that increments a counter every second.\"\"\"\n", 467 | "\n", 468 | " def next(self, state, **kwargs):\n", 469 | " cnt = state.counter\n", 470 | " t0 = time.time()\n", 471 | " self.stop_signal.wait(timeout=10)\n", 472 | " cnt += time.time() - t0\n", 473 | " return state.updated(counter=int(cnt))" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "Your Runnable inherits the base *stop()* and *halt()* methods for termination. Run `IncrementCountSeconds` but terminate in less than 10 seconds. " 481 | ] 482 | }, 483 | { 484 | "cell_type": "code", 485 | "execution_count": null, 486 | "metadata": {}, 487 | "outputs": [], 488 | "source": [ 489 | "workflow = IncrementCountSeconds()\n", 490 | "\n", 491 | "output_state = workflow.run(state_count_0)\n", 492 | "\n", 493 | "sleep(2)\n", 494 | "workflow.stop()\n", 495 | "\n", 496 | "print(\"Counter is now {}.\".format(output_state.result().counter))" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "metadata": {}, 502 | "source": [ 503 | "Runnable `Loop` implements the `@stoppable` decorator and can be terminated. " 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": null, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [ 512 | "workflow = hybrid.Loop(IncrementCount())\n", 513 | "\n", 514 | "output_state = workflow.run(state_count_0)\n", 515 | "\n", 516 | "sleep(0.5)\n", 517 | "workflow.stop()\n", 518 | "print(\"Counter is now {}.\".format(output_state.result().counter))" 519 | ] 520 | }, 521 | { 522 | "cell_type": "markdown", 523 | "metadata": {}, 524 | "source": [ 525 | "If you need to implement a custom termination of your Runnable, overwrite the base *halt()* method, which is called by *stop()* for that purpose, rather than the *stop()* method. " 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "execution_count": null, 531 | "metadata": {}, 532 | "outputs": [], 533 | "source": [ 534 | "class IncrementCount(hybrid.Runnable):\n", 535 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 536 | " \n", 537 | " def next(self, state, **kwargs):\n", 538 | " return state.updated(counter=state.counter+1)\n", 539 | " \n", 540 | " def halt(self):\n", 541 | " print(\"\\nTerminated.\\n\")" 542 | ] 543 | }, 544 | { 545 | "cell_type": "code", 546 | "execution_count": null, 547 | "metadata": {}, 548 | "outputs": [], 549 | "source": [ 550 | "workflow = hybrid.Loop(IncrementCount())\n", 551 | "\n", 552 | "output_state = workflow.run(state_count_0)\n", 553 | "\n", 554 | "sleep(0.5)\n", 555 | "workflow.stop()\n", 556 | "print(\"Counter is now {}.\".format(output_state.result().counter))" 557 | ] 558 | }, 559 | { 560 | "cell_type": "markdown", 561 | "metadata": {}, 562 | "source": [ 563 | "
\n", 564 | " Exercise: Modify IncrementCount below to automatically print the counter value when terminated. Test your code in the EXERCISE TEST CELL below. Open the hidden code cell above it for a solution.\n", 565 | "

Note: Although your component does not accept parameters, the placeholder code's Runnable base initialization includes the runopts parameter.

" 566 | ] 567 | }, 568 | { 569 | "cell_type": "code", 570 | "execution_count": null, 571 | "metadata": { 572 | "solution2": "hidden", 573 | "solution2_first": true 574 | }, 575 | "outputs": [], 576 | "source": [ 577 | "# Write your exercise code here:\n", 578 | "\n", 579 | "class IncrementCount(hybrid.Runnable):\n", 580 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 581 | "\n", 582 | " def __init__(self, **runopts):\n", 583 | " super(IncrementCount, self).__init__(**runopts)\n", 584 | " self.count = 0\n", 585 | "\n", 586 | " def next(self, state, **kwargs):\n", 587 | " return state.updated(counter=state.counter+1)\n", 588 | " \n", 589 | " def halt(self):\n", 590 | " print(\"\\nTerminated.\\n\")" 591 | ] 592 | }, 593 | { 594 | "cell_type": "code", 595 | "execution_count": null, 596 | "metadata": { 597 | "jupyter": { 598 | "source_hidden": true 599 | }, 600 | "solution2": "hidden" 601 | }, 602 | "outputs": [], 603 | "source": [ 604 | "# Click here to see solution:\n", 605 | "class IncrementCount(hybrid.Runnable):\n", 606 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 607 | "\n", 608 | " def __init__(self, **runopts):\n", 609 | " super(IncrementCount, self).__init__(**runopts)\n", 610 | " self.count = 0\n", 611 | "\n", 612 | " def next(self, state, **kwargs):\n", 613 | " self.count = state.counter+1\n", 614 | " return state.updated(counter=self.count)\n", 615 | " \n", 616 | " def halt(self):\n", 617 | " print(\"\\nTerminated with counter = {}.\\n\".format(self.count))" 618 | ] 619 | }, 620 | { 621 | "cell_type": "code", 622 | "execution_count": null, 623 | "metadata": {}, 624 | "outputs": [], 625 | "source": [ 626 | "# EXERCISE TEST CELL\n", 627 | "workflow = hybrid.Loop(IncrementCount())\n", 628 | "\n", 629 | "output_state = workflow.run(state_count_0)\n", 630 | "\n", 631 | "sleep(0.2)\n", 632 | "workflow.stop()" 633 | ] 634 | }, 635 | { 636 | "cell_type": "markdown", 637 | "metadata": {}, 638 | "source": [ 639 | "## Specifying Traits" 640 | ] 641 | }, 642 | { 643 | "cell_type": "markdown", 644 | "metadata": {}, 645 | "source": [ 646 | "State [traits](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/traits.html) are verified for all Runnable objects that inherit from *StateTraits* or its subclasses. Traits of a new Runnable must be expressed and modified at construction time by its parent. When developing new Runnable classes, constructing composite traits can be nontrivial for some advanced flow-control Runnables." 647 | ] 648 | }, 649 | { 650 | "cell_type": "markdown", 651 | "metadata": {}, 652 | "source": [ 653 | "### Example Trait: SISO\n", 654 | "Your simple Runnable can only process a single-state input. A complex workflow might incorrectly provide it with a `States` input, in which case the error message to the user might be confusing. \n", 655 | "\n", 656 | "Try running `IncrementCount` on a *States*-class object:" 657 | ] 658 | }, 659 | { 660 | "cell_type": "code", 661 | "execution_count": null, 662 | "metadata": {}, 663 | "outputs": [], 664 | "source": [ 665 | "states_count_0 = hybrid.States(state_count_0, state_count_0)\n", 666 | "\n", 667 | "try:\n", 668 | " output_states = IncrementCount().run(states_count_0).result()\n", 669 | "except Exception as exc:\n", 670 | " print(\"Low-level error message: {}.\".format(exc))" 671 | ] 672 | }, 673 | { 674 | "cell_type": "markdown", 675 | "metadata": {}, 676 | "source": [ 677 | "Add the trait `SISO` (single input, single output) to the Runnable to validate the input." 678 | ] 679 | }, 680 | { 681 | "cell_type": "code", 682 | "execution_count": null, 683 | "metadata": {}, 684 | "outputs": [], 685 | "source": [ 686 | "class IncrementCount(hybrid.traits.SISO, hybrid.Runnable):\n", 687 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 688 | " \n", 689 | " def next(self, state, **kwargs):\n", 690 | " return state.updated(counter=state.counter+1)\n", 691 | " \n", 692 | " def halt(self):\n", 693 | " print(\"\\nTerminated.\\n\")" 694 | ] 695 | }, 696 | { 697 | "cell_type": "markdown", 698 | "metadata": {}, 699 | "source": [ 700 | "Try running `IncrementCount` with the SISO trait on a *States*-class object:" 701 | ] 702 | }, 703 | { 704 | "cell_type": "code", 705 | "execution_count": null, 706 | "metadata": {}, 707 | "outputs": [], 708 | "source": [ 709 | "try:\n", 710 | " output_states = IncrementCount().run(states_count_0).result()\n", 711 | "except hybrid.exceptions.StateDimensionalityError as exc:\n", 712 | " print(\"High-level error message: {}.\".format(exc))\n", 713 | "except Exception as exc:\n", 714 | " print(\"Low-level error message: {}.\".format(exc))" 715 | ] 716 | }, 717 | { 718 | "cell_type": "markdown", 719 | "metadata": {}, 720 | "source": [ 721 | "### Example Trait: MIMO" 722 | ] 723 | }, 724 | { 725 | "cell_type": "markdown", 726 | "metadata": {}, 727 | "source": [ 728 | "
\n", 729 | " Exercise: Modify IncrementCount from inheriting the SISO trait to inheriting the MIMO (multiple input, multiple output) trait and test it in the EXERCISE TEST CELL below. Open the hidden code cell above it for a solution.\n", 730 | "
" 731 | ] 732 | }, 733 | { 734 | "cell_type": "code", 735 | "execution_count": null, 736 | "metadata": { 737 | "solution2": "hidden", 738 | "solution2_first": true 739 | }, 740 | "outputs": [], 741 | "source": [ 742 | "# Write your exercise code here:\n", 743 | "\n", 744 | "class IncrementCountMIMO(hybrid.traits.MIMO, hybrid.Runnable):\n", 745 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 746 | " \n", 747 | " def next(self, state, **kwargs):\n", 748 | " return state.updated(counter=state.counter+1)\n", 749 | " \n", 750 | " def halt(self):\n", 751 | " print(\"\\nTerminated.\\n\")" 752 | ] 753 | }, 754 | { 755 | "cell_type": "code", 756 | "execution_count": null, 757 | "metadata": { 758 | "jupyter": { 759 | "source_hidden": true 760 | }, 761 | "solution2": "hidden" 762 | }, 763 | "outputs": [], 764 | "source": [ 765 | "# Click here to see solution:\n", 766 | "class IncrementCountMIMO(hybrid.traits.MIMO, hybrid.Runnable):\n", 767 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 768 | " \n", 769 | " def next(self, states, **kwargs):\n", 770 | " for i in range(len(states)):\n", 771 | " states[i] = states[i].updated(counter=states[i].counter+1)\n", 772 | " return states\n", 773 | " \n", 774 | " def halt(self):\n", 775 | " print(\"\\nTerminated\\n\")" 776 | ] 777 | }, 778 | { 779 | "cell_type": "markdown", 780 | "metadata": {}, 781 | "source": [ 782 | "Test your exercise solution by running the next cell, which uses `IncrementCount` on a *States*-class object:" 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": null, 788 | "metadata": {}, 789 | "outputs": [], 790 | "source": [ 791 | "#EXERCISE TEST CELL\n", 792 | "from random import randint\n", 793 | "\n", 794 | "# test case: input MIMO (should work)\n", 795 | "print(\"Test MIMO:\")\n", 796 | "\n", 797 | "states_count_initial = hybrid.States(state_count_0.updated(counter=randint(0, 10)), \n", 798 | " state_count_0.updated(counter=randint(0, 10)))\n", 799 | "\n", 800 | "try:\n", 801 | " output_states = IncrementCountMIMO().run(states_count_initial).result()\n", 802 | " for ind, s in enumerate(output_states):\n", 803 | " print(\"Counter value of state {} is {}.\".format(ind, s.counter))\n", 804 | "except hybrid.exceptions.StateDimensionalityError as exc:\n", 805 | " print(\"High-level error message: {}.\".format(exc))\n", 806 | "except Exception as exc:\n", 807 | " print(\"Low-level error message: {}.\".format(exc))\n", 808 | " \n", 809 | "# test case: input SISO (should give error)\n", 810 | "print(\"\\nTest SISO:\")\n", 811 | "\n", 812 | "try:\n", 813 | " output_states = IncrementCountMIMO().run(state_count_0).result()\n", 814 | " print(\"Counter value is {}.\".format(output_states.counter))\n", 815 | "except hybrid.exceptions.StateDimensionalityError as exc:\n", 816 | " print(\"High-level error message: {}.\".format(exc))\n", 817 | "except Exception as exc:\n", 818 | " print(\"Low-level error message: {}.\".format(exc))" 819 | ] 820 | }, 821 | { 822 | "cell_type": "markdown", 823 | "metadata": {}, 824 | "source": [ 825 | "## Adding Custom Traits\n", 826 | "\n", 827 | "The hybrid framework makes it easy to add your own traits. \n", 828 | "\n", 829 | "### Example: Existing Trait SamplesProcessor\n", 830 | "\n", 831 | "The `SamplesProcessor` trait requires that the Runnable input samples and output samples. It does so by simply specifying two existing traits: \n", 832 | "\n", 833 | "`class SamplesProcessor(SamplesIntaking, SamplesProducing):\n", 834 | " pass`\n", 835 | "\n", 836 | "The `SamplesIntaking` trait, for example, simply adds \"samples\" to the list of validated traits by the `InputValidated` trait:\n", 837 | "\n", 838 | "`class SamplesIntaking(InputValidated, StateTraits):\n", 839 | " def __init__(self):\n", 840 | " super(SamplesIntaking, self).__init__()\n", 841 | " self.inputs.add('samples')`" 842 | ] 843 | }, 844 | { 845 | "cell_type": "markdown", 846 | "metadata": {}, 847 | "source": [ 848 | "You can see how it works by adding it to a tabu search:" 849 | ] 850 | }, 851 | { 852 | "cell_type": "code", 853 | "execution_count": null, 854 | "metadata": {}, 855 | "outputs": [], 856 | "source": [ 857 | "class TabuProblemSamplerInitialized(hybrid.traits.SamplesProcessor, \n", 858 | " hybrid.traits.ProblemSampler, \n", 859 | " hybrid.traits.SISO, \n", 860 | " hybrid.Runnable):\n", 861 | " \"\"\"A tabu search with the SamplesProcessor state trait.\"\"\"\n", 862 | " \n", 863 | " def next(self, state, **kwargs):\n", 864 | " samples = hybrid.TabuProblemSampler().run(state).result().samples\n", 865 | " return state.updated(samples=samples) " 866 | ] 867 | }, 868 | { 869 | "cell_type": "markdown", 870 | "metadata": {}, 871 | "source": [ 872 | "The following test artificially removes the `samples` field from the input state. " 873 | ] 874 | }, 875 | { 876 | "cell_type": "code", 877 | "execution_count": null, 878 | "metadata": {}, 879 | "outputs": [], 880 | "source": [ 881 | "# test case: input state has samples should work\n", 882 | "print(\"Test with samples:\")\n", 883 | "\n", 884 | "state = hybrid.State.from_problem(bqm)\n", 885 | "\n", 886 | "try:\n", 887 | " output_states = TabuProblemSamplerInitialized().run(state).result()\n", 888 | " print(\"works.\")\n", 889 | "except Exception as exc:\n", 890 | " print(\"Low-level error message: {}.\".format(exc))\n", 891 | " \n", 892 | "# test case: input state has no samples should give error\n", 893 | "print(\"Test without samples:\")\n", 894 | "\n", 895 | "del state['samples']\n", 896 | "\n", 897 | "try:\n", 898 | " output_states = TabuProblemSamplerInitialized().run(state).result()\n", 899 | " print(\"Works.\")\n", 900 | "except hybrid.exceptions.StateTraitMissingError as exc:\n", 901 | " print(\"High-level error message: {}.\".format(exc))\n", 902 | "except Exception as exc:\n", 903 | " print(\"Low-level error message: {}.\".format(exc))" 904 | ] 905 | }, 906 | { 907 | "cell_type": "markdown", 908 | "metadata": {}, 909 | "source": [ 910 | "### Example: New Trait TraitCounterIntaking\n", 911 | "In previous subsections, you used fields in the input state such as `info` or `counter`, relying on the user to initialize the state with the required field. Now you know how to ensure any fields you require are there: define a subclass of `InputValidated` that adds the required field and inherit your new trait in your component. " 912 | ] 913 | }, 914 | { 915 | "cell_type": "code", 916 | "execution_count": null, 917 | "metadata": {}, 918 | "outputs": [], 919 | "source": [ 920 | "class TraitCounterIntaking(hybrid.traits.InputValidated, hybrid.traits.StateTraits):\n", 921 | " def __init__(self):\n", 922 | " super(TraitCounterIntaking, self).__init__()\n", 923 | " self.inputs.add('counter')\n", 924 | "\n", 925 | "class IncrementCount(TraitCounterIntaking, \n", 926 | " hybrid.traits.SISO, \n", 927 | " hybrid.Runnable):\n", 928 | " \"\"\"An example Runnable that increments a counter in the input state.\"\"\"\n", 929 | " \n", 930 | " def next(self, state, **kwargs):\n", 931 | " return state.updated(counter=state.counter+1)" 932 | ] 933 | }, 934 | { 935 | "cell_type": "code", 936 | "execution_count": null, 937 | "metadata": {}, 938 | "outputs": [], 939 | "source": [ 940 | "try:\n", 941 | " output_states = IncrementCount().run(state).result()\n", 942 | "except hybrid.exceptions.StateTraitMissingError as exc:\n", 943 | " print(\"High-level error message: {}.\".format(exc))\n", 944 | "except Exception as exc:\n", 945 | " print(\"Low-level error message: {}.\".format(exc))" 946 | ] 947 | }, 948 | { 949 | "cell_type": "markdown", 950 | "metadata": {}, 951 | "source": [ 952 | "## A Custom Sampler\n", 953 | "\n", 954 | "Consider again an example where G represents a sprinkler system and you build a hybrid workflow that finds which sprinklers to use to water a lawn evenly with the lowest number of sprinklers. Say that at some mid-point in your workflow some sprinklers are marked as defective in the input state to a branch that runs tabu search.\n", 955 | "\n", 956 | "
\n", 957 | " Summary Exercise: Customize a sampler that uses TabuProblemSampler but checks its best solution against a list of specified sprinklers in the state's info field, as shown in the EXERCISE TEST CELL below. It updates that field if those sprinklers are selected. Open the hidden code cell above it for a solution.\n", 958 | "
" 959 | ] 960 | }, 961 | { 962 | "cell_type": "code", 963 | "execution_count": null, 964 | "metadata": { 965 | "solution2": "hidden", 966 | "solution2_first": true 967 | }, 968 | "outputs": [], 969 | "source": [ 970 | "# Write your exercise code here:\n", 971 | "\n", 972 | "class TabuProblemSamplerFilteredNodes(hybrid.traits.ProblemSampler, \n", 973 | " hybrid.traits.SISO, \n", 974 | " hybrid.Runnable):\n", 975 | " \"\"\"A tabu search that marks samples that select specified nodes.\"\"\"\n", 976 | " \n", 977 | " def next(self, state, **kwargs):\n", 978 | " samples = hybrid.TabuProblemSampler().run(state).result().samples\n", 979 | " pass " 980 | ] 981 | }, 982 | { 983 | "cell_type": "code", 984 | "execution_count": null, 985 | "metadata": { 986 | "jupyter": { 987 | "source_hidden": true 988 | }, 989 | "solution2": "hidden" 990 | }, 991 | "outputs": [], 992 | "source": [ 993 | "# Click here to see solution:\n", 994 | "class TraitInfoIntaking(hybrid.traits.InputValidated, hybrid.traits.StateTraits):\n", 995 | " def __init__(self):\n", 996 | " super(TraitInfoIntaking, self).__init__()\n", 997 | " self.inputs.add('info')\n", 998 | "\n", 999 | "\n", 1000 | "class TabuProblemSamplerFilteredNodes(TraitInfoIntaking, \n", 1001 | " hybrid.traits.ProblemSampler, \n", 1002 | " hybrid.traits.SISO, \n", 1003 | " hybrid.Runnable):\n", 1004 | " \"\"\"A tabu search that marks samples that select specified nodes.\"\"\"\n", 1005 | "\n", 1006 | " def next(self, state, **kwargs):\n", 1007 | " info = state.info\n", 1008 | " info[\"filtered nodes selected\"] = False\n", 1009 | " \n", 1010 | " samples = hybrid.TabuProblemSampler().run(state).result().samples\n", 1011 | " best_sample = samples.first.sample\n", 1012 | " \n", 1013 | " for node, val in best_sample.items(): \n", 1014 | " if node in state.info[\"filter nodes\"] and val==1:\n", 1015 | " info[\"filtered nodes selected\"] = True\n", 1016 | " break \n", 1017 | " \n", 1018 | " return state.updated(samples=samples, info=info)" 1019 | ] 1020 | }, 1021 | { 1022 | "cell_type": "markdown", 1023 | "metadata": {}, 1024 | "source": [ 1025 | "Here, the input state to `TabuProblemSamplerFilteredNodes` is set to specify two random sprinklers:" 1026 | ] 1027 | }, 1028 | { 1029 | "cell_type": "code", 1030 | "execution_count": null, 1031 | "metadata": {}, 1032 | "outputs": [], 1033 | "source": [ 1034 | "#EXERCISE TEST CELL\n", 1035 | "state_filtered_nodes = state.updated(info={\"filter nodes\": list(np.random.randint(0, problem_node_count, 2))})\n", 1036 | "\n", 1037 | "output_state = TabuProblemSamplerFilteredNodes().run(state_filtered_nodes).result()\n", 1038 | "\n", 1039 | "print(output_state.info)" 1040 | ] 1041 | }, 1042 | { 1043 | "cell_type": "markdown", 1044 | "metadata": {}, 1045 | "source": [ 1046 | "You now have enough familiarity with *dwave-hybrid* to start experimenting on your own. When building new components, reference the source code directly for up-to-date information and helpful inline comments written for developers. \n", 1047 | "\n", 1048 | "Satisfied with your new component? Share it with the community by making a pull request to https://github.com/dwavesystems/dwave-hybrid. \n", 1049 | "\n", 1050 | "Have a great idea and need some implementation advice or assistance? Post it to the [Leap community](https://support.dwavesys.com/hc/en-us/community/topics)." 1051 | ] 1052 | }, 1053 | { 1054 | "cell_type": "markdown", 1055 | "metadata": {}, 1056 | "source": [ 1057 | "# Dimod Conversion\n", 1058 | "*dwave-hybrid* provides classes that convert between hybrid and [dimod](https://docs.ocean.dwavesys.com/en/stable/docs_dimod/sdk_index.html) samplers. " 1059 | ] 1060 | }, 1061 | { 1062 | "cell_type": "markdown", 1063 | "metadata": {}, 1064 | "source": [ 1065 | "In the [Quantum-Classical Hybrid Computing: Workflows](02-hybrid-computing-workflows.ipynb) notebook you built a hybrid workflow that runs tabu and simulated annealing in parallel. " 1066 | ] 1067 | }, 1068 | { 1069 | "cell_type": "code", 1070 | "execution_count": null, 1071 | "metadata": {}, 1072 | "outputs": [], 1073 | "source": [ 1074 | "workflow = (hybrid.Parallel(hybrid.TabuProblemSampler(num_reads=2, timeout=10),\n", 1075 | " hybrid.SimulatedAnnealingProblemSampler(num_reads=1)) |\n", 1076 | " hybrid.ArgMin() )\n", 1077 | "\n", 1078 | "state_updated = workflow.run(state).result()\n", 1079 | "print(\"Updated state has {} sample sets with lowest energy {}.\".format(\n", 1080 | " len(state_updated.samples), state_updated.samples.first.energy))" 1081 | ] 1082 | }, 1083 | { 1084 | "cell_type": "markdown", 1085 | "metadata": {}, 1086 | "source": [ 1087 | "The `HybridSampler` class enables you to convert a Runnable, in this case a workflow, into a dimod sampler: " 1088 | ] 1089 | }, 1090 | { 1091 | "cell_type": "code", 1092 | "execution_count": null, 1093 | "metadata": {}, 1094 | "outputs": [], 1095 | "source": [ 1096 | "dimod_sampler = hybrid.HybridSampler(workflow)" 1097 | ] 1098 | }, 1099 | { 1100 | "cell_type": "markdown", 1101 | "metadata": {}, 1102 | "source": [ 1103 | "Run the converted workflow. Remember that *dimod* samplers input a BQM, not a state. Here, the original problem BQM is provided:" 1104 | ] 1105 | }, 1106 | { 1107 | "cell_type": "code", 1108 | "execution_count": null, 1109 | "metadata": {}, 1110 | "outputs": [], 1111 | "source": [ 1112 | "solution = dimod_sampler.sample(bqm)\n", 1113 | "print(\"Best energy found is {}.\".format(solution.first.energy))" 1114 | ] 1115 | }, 1116 | { 1117 | "cell_type": "markdown", 1118 | "metadata": {}, 1119 | "source": [ 1120 | "You can also convert your custom components. The `TabuProblemSamplerFilteredNodes` below recodes the previous example—it checks its best solution against a list of specified sprinklers and notifies the user if those sprinklers are selected—but this time accepts a list of nodes as a runtime parameter. " 1121 | ] 1122 | }, 1123 | { 1124 | "cell_type": "code", 1125 | "execution_count": null, 1126 | "metadata": {}, 1127 | "outputs": [], 1128 | "source": [ 1129 | "class TabuProblemSamplerFilteredNodes(hybrid.traits.ProblemSampler, \n", 1130 | " hybrid.traits.SISO, \n", 1131 | " hybrid.Runnable):\n", 1132 | " \"\"\"A tabu search that marks samples that select specified nodes.\"\"\"\n", 1133 | " \n", 1134 | " def next(self, state, filter_nodes=[], **runopts):\n", 1135 | " state = state.updated(info={\"filter nodes\":filter_nodes})\n", 1136 | " state.info[\"filtered nodes selected\"] = False\n", 1137 | " \n", 1138 | " samples = hybrid.TabuProblemSampler().run(state).result().samples\n", 1139 | " best_sample = samples.first.sample\n", 1140 | " \n", 1141 | " for node, val in best_sample.items(): \n", 1142 | " if node in state.info[\"filter nodes\"] and val==1:\n", 1143 | " state.info[\"filtered nodes selected\"] = True\n", 1144 | " break \n", 1145 | " \n", 1146 | " return state.updated(samples=samples, info=state.info)" 1147 | ] 1148 | }, 1149 | { 1150 | "cell_type": "markdown", 1151 | "metadata": {}, 1152 | "source": [ 1153 | "You can execute this hybrid Runnable subclass on a given state, specifying the selected nodes in the `filter_nodes` argument:" 1154 | ] 1155 | }, 1156 | { 1157 | "cell_type": "code", 1158 | "execution_count": null, 1159 | "metadata": {}, 1160 | "outputs": [], 1161 | "source": [ 1162 | "selected_nodes = list(np.random.randint(0, problem_node_count, 2))\n", 1163 | "\n", 1164 | "output_state = TabuProblemSamplerFilteredNodes().run(state, filter_nodes=selected_nodes).result()\n", 1165 | "print(output_state.info)" 1166 | ] 1167 | }, 1168 | { 1169 | "cell_type": "markdown", 1170 | "metadata": {}, 1171 | "source": [ 1172 | "Or use the `HybridSampler` class to convert your Runnable into a dimod sampler: " 1173 | ] 1174 | }, 1175 | { 1176 | "cell_type": "code", 1177 | "execution_count": null, 1178 | "metadata": {}, 1179 | "outputs": [], 1180 | "source": [ 1181 | "dimod_sampler = hybrid.HybridSampler(TabuProblemSamplerFilteredNodes())" 1182 | ] 1183 | }, 1184 | { 1185 | "cell_type": "markdown", 1186 | "metadata": {}, 1187 | "source": [ 1188 | "Run the converted `TabuProblemSamplerFilteredNodes`. Setting the `return_state` parameter to True copies the final state, with its updated `info` field, into the `info` field of the returned *SampleSet*. " 1189 | ] 1190 | }, 1191 | { 1192 | "cell_type": "code", 1193 | "execution_count": null, 1194 | "metadata": {}, 1195 | "outputs": [], 1196 | "source": [ 1197 | "solution = dimod_sampler.sample(bqm, filter_nodes=[2, 9, 6], return_state=True)\n", 1198 | "print(solution.info[\"state\"][\"info\"])" 1199 | ] 1200 | }, 1201 | { 1202 | "cell_type": "markdown", 1203 | "metadata": {}, 1204 | "source": [ 1205 | "Copyright © 2020 D-Wave Systems, Inc\n", 1206 | "\n", 1207 | "The software is licensed under the Apache License, Version 2.0 (the \"License\");\n", 1208 | "you may not use this file except in compliance with the License.\n", 1209 | "You may obtain a copy of the License at\n", 1210 | "\n", 1211 | " http://www.apache.org/licenses/LICENSE-2.0\n", 1212 | "\n", 1213 | "Unless required by applicable law or agreed to in writing, software\n", 1214 | "distributed under the License is distributed on an \"AS IS\" BASIS,\n", 1215 | "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 1216 | "See the License for the specific language governing permissions and\n", 1217 | "limitations under the License.\n", 1218 | "\n", 1219 | "\"Creative
This Jupyter Notebook is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License" 1220 | ] 1221 | } 1222 | ], 1223 | "metadata": { 1224 | "kernelspec": { 1225 | "display_name": "Python 3", 1226 | "language": "python", 1227 | "name": "python3" 1228 | }, 1229 | "language_info": { 1230 | "codemirror_mode": { 1231 | "name": "ipython", 1232 | "version": 3 1233 | }, 1234 | "file_extension": ".py", 1235 | "mimetype": "text/x-python", 1236 | "name": "python", 1237 | "nbconvert_exporter": "python", 1238 | "pygments_lexer": "ipython3", 1239 | "version": "3.7.0" 1240 | }, 1241 | "latex_envs": { 1242 | "LaTeX_envs_menu_present": true, 1243 | "autoclose": false, 1244 | "autocomplete": true, 1245 | "bibliofile": "biblio.bib", 1246 | "cite_by": "apalike", 1247 | "current_citInitial": 1, 1248 | "eqLabelWithNumbers": true, 1249 | "eqNumInitial": 1, 1250 | "hotkeys": { 1251 | "equation": "Ctrl-E", 1252 | "itemize": "Ctrl-I" 1253 | }, 1254 | "labels_anchors": false, 1255 | "latex_user_defs": false, 1256 | "report_style_numbering": false, 1257 | "user_envs_cfg": false 1258 | }, 1259 | "toc": { 1260 | "base_numbering": 1, 1261 | "nav_menu": {}, 1262 | "number_sections": false, 1263 | "sideBar": true, 1264 | "skip_h1_title": false, 1265 | "title_cell": "Table of Contents", 1266 | "title_sidebar": "Contents", 1267 | "toc_cell": false, 1268 | "toc_position": { 1269 | "height": "calc(100% - 180px)", 1270 | "left": "10px", 1271 | "top": "150px", 1272 | "width": "165px" 1273 | }, 1274 | "toc_section_display": true, 1275 | "toc_window_display": true 1276 | } 1277 | }, 1278 | "nbformat": 4, 1279 | "nbformat_minor": 2 1280 | } 1281 | -------------------------------------------------------------------------------- /02-hybrid-computing-workflows.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Quantum-Classical Hybrid Computing: Workflows\n", 8 | "\n", 9 | "These notebooks expand the content presented in the D-Wave webinar [Hybrid Quantum Programming](https://www.youtube.com/watch?v=EW44reo8Bn0), demonstrating how you can apply [Leap's](https://cloud.dwavesys.com/leap) cloud-based hybrid solvers and Ocean software's [*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) solvers to your problem, create hybrid workflows, and develop custom hybrid components.\n", 10 | "\n", 11 | "This **second** notebook shows how you use *dwave-hybrid* components to create custom workflows for your problem: \n", 12 | "\n", 13 | "1. [A Sample Problem](#A-Sample-Problem) section reproduces the [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) example problem for use in this notebook.\n", 14 | "2. [Basic Workflows](#Basic-Workflows) section demonstrates using basic *dwave-hybrid* components to build workflows. \n", 15 | "3. [Sample Workflows](#Sample-Workflows) section builds a few more-complex workflows.\n", 16 | "\n", 17 | "Other content is provided in the following notebooks:\n", 18 | "\n", 19 | "* [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) notebook starts you off with Leap hybrid solvers and *dwave-hybrid* solvers and workflows.\n", 20 | "* [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook shows how you can develop your own hybrid components for optimal performance. " 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# A Sample Problem\n", 28 | "This section recreates the [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) problem for use in the following sections. \n", 29 | "\n", 30 | "
Note: Problem size (nodes and density of edges) selected below ensures that runtimes on compute resources (virtual CPUs) provided by the Leap environment do not exceed a few minutes.
\n", 31 | "\n", 32 | "
Note: The code cell below imports from helpers, a folder colocated with this Jupyter Notebook. Users running it in Leap can see helper functions by selecting Jupyter File Explorer View on the Online Learning page.
" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "import matplotlib.pyplot as plt\n", 42 | "from helpers.draw import plot \n", 43 | "%matplotlib inline\n", 44 | "import networkx as nx\n", 45 | "import dwave_networkx as dnx\n", 46 | "import dimod\n", 47 | "\n", 48 | "problem_node_count = 300\n", 49 | "\n", 50 | "G = nx.random_geometric_graph(problem_node_count, radius=0.0005*problem_node_count)\n", 51 | "\n", 52 | "qubo = dnx.algorithms.independent_set.maximum_weighted_independent_set_qubo(G)\n", 53 | "bqm = dimod.BQM.from_qubo(qubo)\n", 54 | "\n", 55 | "plot(G)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "# Basic Workflows\n", 63 | "A typical first step in developing a working hybrid application is to use a [*dwave-hybrid*](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/sdk_index.html) reference sampler or workflow. Your next step might be to customize the sampler or workflow for performance. \n", 64 | "\n", 65 | "For example, Kerberos runs classical samplers tabu and simulated annealing in parallel, with a D-Wave computer contributing to high-energy parts of the problem. Your application's problem may best fit one of these classical algorithms, or a different one entirely. *dwave-hybrid* enables you to quickly experiment with switching, adding, and removing workflow components. \n", 66 | "\n", 67 | "This notebook uses only existing *dwave-hybrid* components. The [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook shows how to modify components and create new components such as samplers. " 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "## States\n", 75 | "\n", 76 | "The [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) notebook demonstrated how workflows input [states](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) representing a problem's binary quadratic model ([BQM](https://docs.ocean.dwavesys.com/en/stable/concepts/bqm.html)) and aim to produce, over one or more iterations, states updated with samples that are good solutions to the problem or parts of it. It mentioned that sometimes you may be in a position to provide initial samples with values better than random for your problem; for example, if you are intermittently running a very large problem, possibly on more than one solver, and saving intermediate results.\n", 77 | "\n", 78 | "For the purpose of creating hybrid workflows, you should also understand that the *State* class is used to pass computation states between components and typical instantiations contain at least *samples* and *problem* fields. Some *dwave-hybrid* Runnables add fields such as *subproblem* and *subsamples* when decomposing large problems to run on the quantum computer. You can also add custom fields of your own. *dwave-hybrid* provides you with state-creation [methods](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) and [utilities](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/utilities.html).\n", 79 | "\n", 80 | "The [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook describes the state-class and its methods in greater detail for the purpose of creating new hybrid components. " 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "Create an initial state using utility function `from_problem`, which, as used here, sets samples to the minimum value. " 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "import hybrid\n", 97 | "\n", 98 | "initial_state = hybrid.State.from_problem(bqm)\n", 99 | "print(\"Initial state has fields: {}.\".format(initial_state.keys()))" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "## Runnables\n", 107 | "Workflows can start very simply and grow in complexity. A simple workflow might consist of just one [Runnable](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html), such as the first workflows below. \n", 108 | "\n", 109 | "These demonstrate two *dwave-hybrid* classes (primitives): \n", 110 | "\n", 111 | "* *State*-class components that hold a problem, samples, and optionally additional information.\n", 112 | "* *Runnable*-class components that take input states, run for an iteration, and output updated states. \n", 113 | "\n", 114 | "For experimenting with and optimizing quantum-classical hybrid workflows using existing *dwave-hybrid* components, you typically need just two methods:\n", 115 | "\n", 116 | "* Runnable [run()](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) method to execute an iteration of the workflow. It accepts a state in a [Future](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future)-like object and returns a new state in a Future-like object. By default, it uses *hybrid.concurrency.thread_executor* to create an asynchronous call. \n", 117 | "* State [result()](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/core.html) method to resolve the result using a *Future*-compatible resolution interface. \n", 118 | "\n", 119 | "The [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook provides more details on these and other methods, asynchronous and blocking calls between *dwave-hybrid* components, and additional information needed to modify and create your own components. " 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "The Runnable `Identity` below outputs a copy of its input state. Given input state, `initial_state`, the single-Runnable workflow below produces an output state, `output_state`, that is identical to the input state." 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "workflow = hybrid.Identity()\n", 136 | "\n", 137 | "output_state = workflow.run(initial_state).result()\n", 138 | "print(output_state == initial_state)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "The Runnable `Dup` below duplicates `n=2` times the input state. In this workflow, output `output_states` is actually of type *States* class, a list of states. Such a Runnable can be used to build workflows with parallel processing. " 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "workflow = hybrid.Dup(2)\n", 155 | "\n", 156 | "output_states = workflow.run(initial_state).result()\n", 157 | "print(\"Output is a list containing {} states.\".format(len(output_states)))" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "Runnables may accept or require single or multiple states and output single or multiple states. *dwave-hybrid's* [state traits](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/traits.html) check compatibility during workflow construction and runtime. You will see some examples in the following sections and use them in the [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook. " 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "Useful workflows typically process the input states in some way. The Runnable `Const` below, which sets values for state variables, is used for a simple manipulation of the input state. Such a Runnable enables you to manipulate an input state at any point in a workflow. An upcoming example makes a more-practical use of this capability." 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "print(\"Input state has samples: {}.\".format(initial_state.samples.record.sample is not None))\n", 181 | "\n", 182 | "output_state = hybrid.Const(samples=None).run(initial_state).result()\n", 183 | "print(\"Output state has samples: {}.\".format(output_state.samples is not None))" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "*dwave-hybrid* **samplers** are Runnables. A sampler's processing of input states usually aims to produce samples in its output state that are solutions to the problem, or part of it, given in its input state. " 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "workflow = hybrid.TabuProblemSampler()\n", 200 | "\n", 201 | "state_updated = workflow.run(initial_state).result()\n", 202 | "\n", 203 | "updated_state_energy = state_updated.samples.first.energy\n", 204 | "initial_state_energy = initial_state.samples.first.energy\n", 205 | "print(\"Updated state has energy {} versus initial state's {}.\".format(updated_state_energy, \n", 206 | " initial_state_energy))" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "
\n", 214 | " Exercise: By default, TabuProblemSampler runs for 100 ms. In the placeholder code cell below, modify the previous example to increase the timeout parameter or the tabu tenure and see if that produces a better solution. Open the hidden code cell below it for a solution. \n", 215 | "
" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": { 222 | "solution2": "hidden", 223 | "solution2_first": true 224 | }, 225 | "outputs": [], 226 | "source": [ 227 | "# Placeholder cell for exercise code\n", 228 | "\n", 229 | "workflow = hybrid.TabuProblemSampler()\n", 230 | "\n", 231 | "state_updated = workflow.run(initial_state).result()\n", 232 | "\n", 233 | "updated_state_energy = state_updated.samples.first.energy\n", 234 | "print(\"Updated state has energy {} versus initial state's {}.\".format(updated_state_energy, \n", 235 | " initial_state_energy))" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": { 242 | "jupyter": { 243 | "source_hidden": true 244 | }, 245 | "solution2": "hidden" 246 | }, 247 | "outputs": [], 248 | "source": [ 249 | "# Click here to see solution:\n", 250 | "workflow = hybrid.TabuProblemSampler(timeout=3000)\n", 251 | "\n", 252 | "state_updated = workflow.run(initial_state).result()\n", 253 | "\n", 254 | "updated_state_energy = state_updated.samples.first.energy\n", 255 | "print(\"Updated state has energy {} versus initial state's {}.\".format(updated_state_energy, \n", 256 | " initial_state_energy))" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "
\n", 264 | " Exercise: Use the Runnable Lambda to add an \"info\" field to an input state. Open the hidden code cell below it for a solution.\n", 265 | "\n", 266 | "

Hint 1: Lambda creates a Runnable on the fly. You can use Lambda(next)—where callable next can be a lambda expression that accepts the Runnable instance and state instance—to process an input state. For example, hybrid.Lambda(lambda _, s: s), like the Identity used above, returns a copy of the input state.

Hint 2: For this exercise, use the State method updated() that returns a copy of a state updated from specified arguments.

\n", 267 | "
" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "metadata": { 274 | "solution2": "hidden", 275 | "solution2_first": true 276 | }, 277 | "outputs": [], 278 | "source": [ 279 | "# Write and run your exercise code here:\n" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": null, 285 | "metadata": { 286 | "jupyter": { 287 | "source_hidden": true 288 | }, 289 | "solution2": "hidden" 290 | }, 291 | "outputs": [], 292 | "source": [ 293 | "# Click here to see solution:\n", 294 | "add_info_runnable = hybrid.Lambda(lambda _, s: s.updated(info=\"Added an info field\"))\n", 295 | "\n", 296 | "state_updated = add_info_runnable.run(initial_state).result()\n", 297 | "print(state_updated.info)" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "## Branches\n", 305 | "The updated state of a Runnable can be pipelined into a second Runnable. Below, two tabu searches with different parameters are run in sequence. Such sequentially executed components constitute a *Branch*, a subclass of Runnable. " 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": null, 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [ 314 | "workflow = hybrid.TabuProblemSampler(tenure=5) | hybrid.TabuProblemSampler(tenure=30)\n", 315 | "\n", 316 | "state_updated = workflow.run(initial_state).result()\n", 317 | "hybrid.print_counters(workflow)" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "The previous subsection demonstrated using Runnable `Const` to configure an input state at some point in a workflow. Here it ensures that `TabuProblemSampler` generates its own initial set of random samples rather than start its search on the input state's samples, which were initialized to all zeros. An upcoming subsection makes further use of this capability. " 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [ 333 | "branch = (hybrid.Const(samples=None) | hybrid.TabuProblemSampler())\n", 334 | "\n", 335 | "state_updated = branch.run(initial_state).result()\n", 336 | "print(\"Updated state has energy {}.\".format(state_updated.samples.first.energy))" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "metadata": {}, 342 | "source": [ 343 | "
\n", 344 | " Exercise: Use Runnable Const to set the sample values to all ones in the input state to TabuProblemSampler.\n", 345 | " Open the hidden code cell below it for a solution.\n", 346 | " \n", 347 | "

Hint: You have seen that the samples field of a state is of type dimod SampleSet, which has methods to convert from samples, and state has its own convenience method, from_sample, for constructing a state from a raw (dict) sample.

\n", 348 | "
\n", 349 | "\n", 350 | "
Note: When using a hybrid tabu sampler, be sure to correctly configure how it expands input-state samples into its initial states.
" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "metadata": { 357 | "solution2": "hidden", 358 | "solution2_first": true 359 | }, 360 | "outputs": [], 361 | "source": [ 362 | "# Write and run your exercise code here:\n" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": null, 368 | "metadata": { 369 | "jupyter": { 370 | "source_hidden": true 371 | }, 372 | "solution2": "hidden" 373 | }, 374 | "outputs": [], 375 | "source": [ 376 | "# Click here to see solution:\n", 377 | "all_ones = {var: 1 for var in initial_state.samples.variables}\n", 378 | "samples_initial = hybrid.State.from_sample(all_ones, bqm).samples\n", 379 | "\n", 380 | "# Alternatively:\n", 381 | "#samples_initial = dimod.SampleSet.from_samples({var: 1 for var in initial_state.samples.variables}, \n", 382 | "# \"BINARY\", 0)\n", 383 | "\n", 384 | "states_updated = (hybrid.Const(samples=samples_initial) \n", 385 | " | hybrid.TabuProblemSampler()\n", 386 | " ).run(initial_state).result()\n", 387 | "print(\"Updated state has energy {}.\".format(state_updated.samples.first.energy))" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "metadata": {}, 393 | "source": [ 394 | "### A Practical Example \n", 395 | "A standard branch in hybrid workflows is the sequence of processing that enables a quantum computer to participate in solving problems of arbitrary size. Unless your problem is small enough to fit in its entirety on a quantum processing unit (QPU), you must decompose it into smaller parts. These can be submitted to a D-Wave computer and the returned samples merged into the larger problem's samples. \n", 396 | "\n", 397 | "Subsection [Decomposition](#Decomposition) below demonstrates that there are numerous ways to decompose a problem. Deferring detailed explanation to that subsection, the code here uses the `EnergyImpactDecomposer` decomposer to select a subproblem of `sub_size=40` variables maximally contributing to the problem energy and the `SplatComposer` composer to then overwrite current samples with subproblem samples. `QPUSubproblemAutoEmbeddingSampler` handles the submission of the subproblem to the quantum computer. " 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": null, 403 | "metadata": {}, 404 | "outputs": [], 405 | "source": [ 406 | "sub_size = 40\n", 407 | "branch_quantum = ( hybrid.EnergyImpactDecomposer(size=sub_size) |\n", 408 | " hybrid.QPUSubproblemAutoEmbeddingSampler(num_reads=10) |\n", 409 | " hybrid.SplatComposer() )\n", 410 | "\n", 411 | "state_updated = branch_quantum.run(initial_state).result()\n", 412 | "\n", 413 | "initial_state_energy = initial_state.samples.first.energy \n", 414 | "updated_state_energy = state_updated.samples.first.energy \n", 415 | "subproblem_variables = state_updated.subsamples.variables\n", 416 | "print(\"Initial energy: {}. Energy of {} achieved by incorporating samples for subproblem variables {}.\".format(\n", 417 | " initial_state_energy, updated_state_energy, subproblem_variables))" 418 | ] 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "metadata": {}, 423 | "source": [ 424 | "### Parallel Branches: Blocking, SIMO\n", 425 | "You can run several branches in parallel. The *Parallel* class (a Runnable subclass, of course) inputs a single state, executes all its branches until the **last** one terminates, and outputs a list of states. \n", 426 | "\n", 427 | "The workflow below runs the `branch_quantum` from the previous example in parallel to a tabu search. Each branch outputs a state and these accumulate in the output states of `Parallel`. This workflow is an example that complies with the SIMO [state trait](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/traits.html): single input, multiple output." 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "workflow = hybrid.Parallel(hybrid.TabuProblemSampler(),\n", 437 | " branch_quantum)\n", 438 | "\n", 439 | "states_updated = workflow.run(initial_state).result()\n", 440 | "print(\"Output is a `States` object with {} states.\".format(len(states_updated)))" 441 | ] 442 | }, 443 | { 444 | "cell_type": "markdown", 445 | "metadata": {}, 446 | "source": [ 447 | "### Parallel Branches: Blocking, MIMO\n", 448 | "The Runnable `Branches` is similar to `Parallel` but it inputs a state for each of its branches. In the example below, the `Branches` component complies with the MIMO [state trait](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/traits.html): multiple inputs, multiple outputs. " 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": null, 454 | "metadata": {}, 455 | "outputs": [], 456 | "source": [ 457 | "workflow = hybrid.Dup(2) | hybrid.Branches(hybrid.TabuProblemSampler(),\n", 458 | " branch_quantum)\n", 459 | "\n", 460 | "states_updated = workflow.run(initial_state).result()\n", 461 | "\n", 462 | "num_problem_variables = len(states_updated[0].problem.variables)\n", 463 | "num_subproblem_variables = len(states_updated[1].subproblem.variables)\n", 464 | "print(\"Tabu ran on {} variables and the quantum branch on {} of those.\".format(\n", 465 | " num_problem_variables, num_subproblem_variables))" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": {}, 471 | "source": [ 472 | "
\n", 473 | " Exercise: Rewrite the previous workflow using Runnable Map to run a TabuProblemSampler in both branches.\n", 474 | " Open the hidden code cell below it for a solution.\n", 475 | "
" 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": null, 481 | "metadata": { 482 | "solution2": "hidden", 483 | "solution2_first": true 484 | }, 485 | "outputs": [], 486 | "source": [ 487 | "# Write and run your exercise code here:\n" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": null, 493 | "metadata": { 494 | "jupyter": { 495 | "source_hidden": true 496 | }, 497 | "solution2": "hidden" 498 | }, 499 | "outputs": [], 500 | "source": [ 501 | "# Click here to see solution:\n", 502 | "workflow = hybrid.Dup(2) | hybrid.Map(hybrid.TabuProblemSampler())\n", 503 | "\n", 504 | "states_updated = workflow.run(initial_state).result()\n", 505 | "\n", 506 | "updated_energies = [states_updated[i].samples.first.energy for i in [0, 1]]\n", 507 | "print(\"Updated states have energies {}.\".format(updated_energies))" 508 | ] 509 | }, 510 | { 511 | "cell_type": "markdown", 512 | "metadata": {}, 513 | "source": [ 514 | "### Parallel Branches: Non-Blocking\n", 515 | "The *Race* class executes all its branches until the **first** one terminates. It inputs a single state and outputs a *States*-class object, which might have partial results for some of the branches. \n", 516 | "\n", 517 | "Use it to run a short tabu search in parallel to a long simulated annealing. Branch execution depends on the execution environment's resources and their allocation. Typically, for this notebook's example problem, the output state for simulated annealing has fewer than the requested number of sample sets (if your results differ, modify the parameters, for example, increase `num_reads` for the simulated annealing). " 518 | ] 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": null, 523 | "metadata": {}, 524 | "outputs": [], 525 | "source": [ 526 | "workflow = hybrid.Race(hybrid.TabuProblemSampler(num_reads=2, timeout=10),\n", 527 | " hybrid.SimulatedAnnealingProblemSampler(num_reads=1000))\n", 528 | "\n", 529 | "states_updated = workflow.run(initial_state).result()\n", 530 | "print(\"Number of sample sets in the two states are {} and {}.\".format(\n", 531 | " len(states_updated[0].samples), len(states_updated[1].samples)))" 532 | ] 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "metadata": {}, 537 | "source": [ 538 | "## Selecting Samples\n", 539 | "In many workflows that execute parallel sampling, the next stage selects the best states, according to some criterion. Runnable `ArgMin` inputs a *States* list of states and by default outputs the state with lowest energy. " 540 | ] 541 | }, 542 | { 543 | "cell_type": "code", 544 | "execution_count": null, 545 | "metadata": {}, 546 | "outputs": [], 547 | "source": [ 548 | "workflow = (hybrid.Parallel(hybrid.TabuProblemSampler(num_reads=2, timeout=10),\n", 549 | " hybrid.SimulatedAnnealingProblemSampler(num_reads=1)) |\n", 550 | " hybrid.ArgMin() )\n", 551 | "\n", 552 | "state_updated = workflow.run(initial_state).result()\n", 553 | "print(\"Updated state has {} sample sets with lowest energy {}.\".format(\n", 554 | " len(state_updated.samples), state_updated.samples.first.energy))" 555 | ] 556 | }, 557 | { 558 | "cell_type": "markdown", 559 | "metadata": {}, 560 | "source": [ 561 | "### Customizing Sample Selection\n", 562 | "Your application might have additional considerations for selecting the best solutions. The next (pedagogical-only) example demonstrates selection that prioritizes quicker sampling over potentially better solutions. \n", 563 | "\n", 564 | "The first cell sets `known_best` to the best solution found running the default tabu search `num_read=10` times. \n", 565 | "\n", 566 | "The second cell defines three samplers with longer runtimes that use `Const` to tag their output state with those runtimes. Function `penalty` calculates a penalty proportional to runtime. Function `prefer_fast` returns the best energy of each output state penalized by runtime, and is substituted in `ArgMin` for the default key callable, which selects the state with best energy." 567 | ] 568 | }, 569 | { 570 | "cell_type": "code", 571 | "execution_count": null, 572 | "metadata": {}, 573 | "outputs": [], 574 | "source": [ 575 | "result = hybrid.TabuProblemSampler(num_reads=10).run(initial_state).result()\n", 576 | "known_best = result.samples.first.energy\n", 577 | "\n", 578 | "print(\"Best energy found with default tabu run time is {}.\".format(known_best))" 579 | ] 580 | }, 581 | { 582 | "cell_type": "code", 583 | "execution_count": null, 584 | "metadata": {}, 585 | "outputs": [], 586 | "source": [ 587 | "sampler_200 = hybrid.Const(info=200) | hybrid.TabuProblemSampler(num_reads=3, timeout=200)\n", 588 | "sampler_400 = hybrid.Const(info=400) | hybrid.TabuProblemSampler(num_reads=3, timeout=400)\n", 589 | "sampler_600 = hybrid.Const(info=600) | hybrid.TabuProblemSampler(num_reads=3, timeout=600)\n", 590 | "\n", 591 | "def penalty(timeout):\n", 592 | " return -0.01 * known_best * timeout/100\n", 593 | "\n", 594 | "def prefer_fast(si):\n", 595 | " weighted_energy = si.samples.first.energy + penalty(si.info)\n", 596 | " print (\"Sampler_{} energy {} is penalized {} for time-weighted energy: {}.\".\n", 597 | " format(si.info, si.samples.first.energy, penalty(si.info), weighted_energy))\n", 598 | " return weighted_energy" 599 | ] 600 | }, 601 | { 602 | "cell_type": "markdown", 603 | "metadata": {}, 604 | "source": [ 605 | "Run three tabu searches and select the output state with lowest energy weighted by runtime: " 606 | ] 607 | }, 608 | { 609 | "cell_type": "code", 610 | "execution_count": null, 611 | "metadata": { 612 | "scrolled": true 613 | }, 614 | "outputs": [], 615 | "source": [ 616 | "workflow = (hybrid.Parallel(sampler_200, sampler_400, sampler_600)|\n", 617 | " hybrid.ArgMin(key=prefer_fast))\n", 618 | "\n", 619 | "state_updated = workflow.run(initial_state).result()\n", 620 | "print(\"ArgMin using prefer_fast selected a state with energy {}.\".format(state_updated.samples.first.energy))" 621 | ] 622 | }, 623 | { 624 | "cell_type": "markdown", 625 | "metadata": {}, 626 | "source": [ 627 | "
\n", 628 | " Bonus Exercise: Run a few parallel tabu searches and have ArgMin prioritize the nodes with indices in the bottom third of example graph G. Test your code in the EXERCISE TEST CELL below. Open the hidden code cell above it for a solution.\n", 629 | "

Consider an example where G represents a sprinkler system and the lowest-numbered sprinklers water more important crops than the higher-numbered sprinklers. The recommended approach for solving such a problem graph is to weight the nodes by importance as part of the QUBO formulation but just for fun, try writing an `ArgMin` key that, given a choice of solutions with energies that are no higher than 5% of the previously found best solution, prefers a solution with more sprinklers for the important crops.

" 630 | ] 631 | }, 632 | { 633 | "cell_type": "code", 634 | "execution_count": null, 635 | "metadata": { 636 | "solution2": "hidden", 637 | "solution2_first": true 638 | }, 639 | "outputs": [], 640 | "source": [ 641 | "# Write your exercise code here:\n", 642 | "known_best = hybrid.TabuProblemSampler(num_reads=10).run(initial_state).result().samples.first\n", 643 | "\n", 644 | "def penalty(sample):\n", 645 | " pass\n", 646 | "\n", 647 | "def prefer_first_third(si):\n", 648 | " \"\"\"Prefer solutions within 5% of best with maximum bottom-third nodes.\"\"\"\n", 649 | " pass" 650 | ] 651 | }, 652 | { 653 | "cell_type": "code", 654 | "execution_count": null, 655 | "metadata": { 656 | "jupyter": { 657 | "source_hidden": true 658 | }, 659 | "solution2": "hidden" 660 | }, 661 | "outputs": [], 662 | "source": [ 663 | "# Click here to see solution:\n", 664 | "known_best = hybrid.TabuProblemSampler(num_reads=10).run(initial_state).result().samples.first\n", 665 | "print(\"Best energy found is {}.\".format(known_best.energy)) \n", 666 | "\n", 667 | "def penalty(sample):\n", 668 | " \"\"\"Set penalty equal to the number of selected bottom-third nodes.\"\"\"\n", 669 | " return -len([val for key, val in sample.items() if key<0.3*problem_node_count and val>0])\n", 670 | "\n", 671 | "def prefer_first_third(si):\n", 672 | " \"\"\"Prefer solutions within 5% of best with maximum bottom-third nodes.\"\"\"\n", 673 | " if si.samples.first.energy > 0.95*known_best.energy:\n", 674 | " return si.samples.first.energy\n", 675 | " else: \n", 676 | " weighted_energy = si.samples.first.energy + penalty(si.samples.first.sample)\n", 677 | " print (\"Sample energy {} is penalized {} for node-weighted energy: {}.\".format\n", 678 | " (si.samples.first.energy, penalty(si.samples.first.sample), weighted_energy))\n", 679 | " return weighted_energy " 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": null, 685 | "metadata": {}, 686 | "outputs": [], 687 | "source": [ 688 | "# EXERCISE TEST CELL\n", 689 | "workflow = (hybrid.Dup(4) | hybrid.Map(hybrid.TabuProblemSampler()) |\n", 690 | " hybrid.ArgMin(key=prefer_first_third) )\n", 691 | "\n", 692 | "state_updated = workflow.run(initial_state).result()\n", 693 | "print(\"Updated state has {} first-third nodes selected and energy {}.\".format(len([val for key, val in state_updated.samples.first.sample.items() if key<0.3*problem_node_count]), state_updated.samples.first.energy))" 694 | ] 695 | }, 696 | { 697 | "cell_type": "markdown", 698 | "metadata": {}, 699 | "source": [ 700 | "## Iterating\n", 701 | "There are various reasons for iterating over the sampling process; for example, a sampler might not have appropriate logic to terminate to your preferred criterion, your application might take advantage of seeding one sampler with the output of another, and so on. \n", 702 | "\n", 703 | "The *LoopUntilNoImprovement* class (alias *Loop*, another Runnable subclass) iterates its components until an explicitly defined or default termination, feeding back the output state of one iteration as the input state to the next. The simple example below terminates after a maximum of `max_iter` executions of Runnable `TabuProblemSampler`. " 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": null, 709 | "metadata": {}, 710 | "outputs": [], 711 | "source": [ 712 | "workflow = hybrid.Loop(hybrid.TabuProblemSampler(num_reads=2, timeout=5), max_iter=3) \n", 713 | "\n", 714 | "state_updated = workflow.run(initial_state).result()\n", 715 | "hybrid.print_counters(workflow)" 716 | ] 717 | }, 718 | { 719 | "cell_type": "markdown", 720 | "metadata": {}, 721 | "source": [ 722 | "The [Branches](#Branches) subsection mentioned that you can prevent `TabuProblemSampler` from starting each iteration from the samples of the previous iteration by using `Const` to set `samples=None` in the state. An exercise in subsection [Runnables](#Runnables) used `Lambda` to add an *info* field to an input state. \n", 723 | "\n", 724 | "The next example compares two tabu searches iterated in Runnable `Loop`: one is initialized with the previous iteration's samples; another uses `Const` to start each iteration from a random sample. `Lambda` is used to track, in a *tracked_energy* field, the energy found in each iteration." 725 | ] 726 | }, 727 | { 728 | "cell_type": "code", 729 | "execution_count": null, 730 | "metadata": {}, 731 | "outputs": [], 732 | "source": [ 733 | "runtime = 20\n", 734 | "iterations = 50\n", 735 | "\n", 736 | "energy_tracker = hybrid.Lambda(lambda _, s: s.updated(\n", 737 | " tracked_energy=s.tracked_energy + [s.samples.first.energy]))\n", 738 | "\n", 739 | "sampler_init_previous = hybrid.TabuProblemSampler(timeout=runtime) | energy_tracker\n", 740 | "sampler_init_random = ( hybrid.Const(samples=None) | \n", 741 | " hybrid.TabuProblemSampler(timeout=runtime) | \n", 742 | " energy_tracker )\n", 743 | "\n", 744 | "workflow_init_previous = hybrid.Loop(sampler_init_previous, max_iter=iterations)\n", 745 | "workflow_init_random = hybrid.Loop(sampler_init_random, max_iter=iterations)\n", 746 | "\n", 747 | "energy_init_previous = workflow_init_previous.run(initial_state.updated(\n", 748 | " tracked_energy=[])).result().tracked_energy\n", 749 | "energy_init_random = workflow_init_random.run(initial_state.updated(\n", 750 | " tracked_energy=[])).result().tracked_energy\n", 751 | "\n", 752 | "plt.figure(figsize=(10,5))\n", 753 | "plt.plot(range(iterations), energy_init_previous, 'r.-', label='Initialized to previous iteration') \n", 754 | "plt.plot(range(iterations), energy_init_random, 'b.-', label='Initialized to random')\n", 755 | "plt.legend()" 756 | ] 757 | }, 758 | { 759 | "cell_type": "markdown", 760 | "metadata": {}, 761 | "source": [ 762 | "Try increasing the tabu search runtime to see if performance of the randomly initialized sampler improves:\n", 763 | "\n", 764 | "* runtime = 200\n", 765 | "* runtime = 2000\n", 766 | "\n", 767 | "Note: For tabu searches of 2 seconds, keeping 50 iterations will result in **several minutes' runtime**. You might want to decrease the number of iterations from the current `iterations = 50`. " 768 | ] 769 | }, 770 | { 771 | "cell_type": "markdown", 772 | "metadata": {}, 773 | "source": [ 774 | "
\n", 775 | " Exercise: Instead of using Const to force the tabu search to start from a random sample, use it to initialize the search from a good solution and compare that to the search initialized from the previous output state. Open the hidden code cell below it for a solution.\n", 776 | "
" 777 | ] 778 | }, 779 | { 780 | "cell_type": "code", 781 | "execution_count": null, 782 | "metadata": { 783 | "solution2": "hidden", 784 | "solution2_first": true 785 | }, 786 | "outputs": [], 787 | "source": [ 788 | "# Write and run your exercise code here:\n" 789 | ] 790 | }, 791 | { 792 | "cell_type": "code", 793 | "execution_count": null, 794 | "metadata": { 795 | "jupyter": { 796 | "source_hidden": true 797 | }, 798 | "solution2": "hidden" 799 | }, 800 | "outputs": [], 801 | "source": [ 802 | "# Click here to see solution:\n", 803 | "runtime = 20\n", 804 | "iterations = 50\n", 805 | "\n", 806 | "energy_tracker = hybrid.Lambda(lambda _, s: s.updated(\n", 807 | " tracked_energy=s.tracked_energy + [s.samples.first.energy]))\n", 808 | "\n", 809 | "good = hybrid.TabuProblemSampler(num_reads=10, timeout=100).run(initial_state).result()\n", 810 | "samples_initial = hybrid.State.from_sample(good.samples.first.sample, good.problem).samples\n", 811 | "\n", 812 | "sampler_init_previous = hybrid.TabuProblemSampler(timeout=runtime) | energy_tracker\n", 813 | "sampler_init_good = ( hybrid.Const(samples=samples_initial) | \n", 814 | " hybrid.TabuProblemSampler(timeout=runtime) | \n", 815 | " energy_tracker )\n", 816 | "\n", 817 | "workflow_init_previous = hybrid.Loop(sampler_init_previous, max_iter=iterations)\n", 818 | "workflow_init_good = hybrid.Loop(sampler_init_good, max_iter=iterations)\n", 819 | "\n", 820 | "energy_init_previous = workflow_init_previous.run(initial_state.updated(\n", 821 | " tracked_energy=[])).result().tracked_energy\n", 822 | "energy_init_good = workflow_init_good.run(initial_state.updated(\n", 823 | " tracked_energy=[])).result().tracked_energy\n", 824 | "\n", 825 | "plt.figure(figsize=(10,5))\n", 826 | "plt.plot(range(iterations), energy_init_previous, 'r.-', label='Initialized to previous iteration') \n", 827 | "plt.plot(range(iterations), energy_init_good, 'b.-', label='Initialized to a good solution')\n", 828 | "plt.legend()" 829 | ] 830 | }, 831 | { 832 | "cell_type": "markdown", 833 | "metadata": {}, 834 | "source": [ 835 | "## Revisiting Kerberos\n", 836 | "\n", 837 | "The [Quantum-Classical Hybrid Computing: Getting Started](01-hybrid-computing-getting-started.ipynb) notebook introduced you to Kerberos, an out-of-the-box hybrid sampler. \n", 838 | "\n", 839 | "\n", 840 | "\n", 841 | "That notebook described it as looping over three sampling branches in parallel, selecting after each iteration the best results from tabu search, simulated annealing, and QPU sampling of a subproblem. Now you understand how this workflow is structured:\n", 842 | "\n", 843 | "* Classical samplers *Interruptable Tabu Sampler* and *Interruptable SA Sampler* \n", 844 | " run (on CPU) until interrupted by the QPU branch. \n", 845 | "* *Energy Impact Decomposer* selects a subset of the problem's variables (those\n", 846 | " that maximally contribute to the problem energy); *QPU Sampler* submits \n", 847 | " subproblems to the quantum computer; and *SplatComposer* inserts \n", 848 | " subproblems' samples into problem samples.\n", 849 | "* *ArgMin* selects the best samples from an iteration, which terminates when \n", 850 | " the quantum computer returns samples. \n", 851 | "* *Loop* iterates these *Racing Branches* until a termination condition is reached. \n", 852 | "\n", 853 | "The next section provides details on the decomposition that is at the heart of adding the quantum computer into the workflow. The [Quantum-Classical Hybrid Computing: Components](03-hybrid-computing-components.ipynb) notebook provides a deeper understanding of the components. " 854 | ] 855 | }, 856 | { 857 | "cell_type": "markdown", 858 | "metadata": {}, 859 | "source": [ 860 | "# Sample Workflows\n", 861 | "This section demonstrates a few example workflows." 862 | ] 863 | }, 864 | { 865 | "cell_type": "markdown", 866 | "metadata": {}, 867 | "source": [ 868 | "## Decomposition\n", 869 | "Decomposition is crucial for efficient quantum-classical hybrid workflows: for the quantum computer to contribute, workflows must allocate to it the appropriate parts of the problem. Intelligent decomposition is in itself a challenging problem. \n", 870 | "\n", 871 | "*dwave-hybrid* provides an `EnergyImpactDecomposer` component that selects a subproblem of variables maximally contributing to the problem energy. It provides a number of configuration options and in conjunction with additional components can execute a variety of decompositions. " 872 | ] 873 | }, 874 | { 875 | "cell_type": "markdown", 876 | "metadata": {}, 877 | "source": [ 878 | "Given an initial state, `EnergyImpactDecomposer` returns a state updated with a `subproblem` field containing selected variables:" 879 | ] 880 | }, 881 | { 882 | "cell_type": "code", 883 | "execution_count": null, 884 | "metadata": {}, 885 | "outputs": [], 886 | "source": [ 887 | "sub_size = 40\n", 888 | "\n", 889 | "state_updated = hybrid.EnergyImpactDecomposer(size=sub_size).run(initial_state).result()\n", 890 | "\n", 891 | "plot(G, subgraphs=[G.subgraph(state_updated.subproblem.variables)])" 892 | ] 893 | }, 894 | { 895 | "cell_type": "markdown", 896 | "metadata": {}, 897 | "source": [ 898 | "For the problem created in the [A Sample Problem](#A-Sample-Problem) section, in which all variables contribute equally, decomposing by maximum energy contribution tends to select an indexical sequence of variables. You can specify different traversal methods through the problem variables with the `traversal` [parameter](https://docs.ocean.dwavesys.com/en/stable/docs_hybrid/reference/decomposers.html); for example, breadth-first traversal (\"bfs\") or priority-first traversal (\"pfs\"). " 899 | ] 900 | }, 901 | { 902 | "cell_type": "code", 903 | "execution_count": null, 904 | "metadata": {}, 905 | "outputs": [], 906 | "source": [ 907 | "state_updated = hybrid.EnergyImpactDecomposer(size=sub_size, traversal=\"bfs\").run(initial_state).result()\n", 908 | "\n", 909 | "plot(G, subgraphs=[G.subgraph(state_updated.subproblem.variables)])" 910 | ] 911 | }, 912 | { 913 | "cell_type": "markdown", 914 | "metadata": {}, 915 | "source": [ 916 | "The `rolling` parameter configures the component so that successive calls for the same problem produce subproblems on different variables, selected by rolling down the list of all variables sorted by decreasing impact, until a fraction of the problem size set by the `rolling_history` parameter. \n", 917 | "\n", 918 | "The next cell loops `max_iter` times on the `EnergyImpactDecomposer`. To see which variables are selected, a `Lambda` is added that accumulates the `sub_size` selected variables to a custom informational field, `rolled_variables`, in the updated state. \n", 919 | "\n", 920 | "Try experimenting with the `traversal` and `size` parameters." 921 | ] 922 | }, 923 | { 924 | "cell_type": "code", 925 | "execution_count": null, 926 | "metadata": {}, 927 | "outputs": [], 928 | "source": [ 929 | "sub_size = 15\n", 930 | "subgraphs = 3\n", 931 | "\n", 932 | "iteration = (hybrid.EnergyImpactDecomposer(size=sub_size, rolling_history=0.85, traversal=\"bfs\") | \n", 933 | " hybrid.Lambda(lambda _, s: s.updated(\n", 934 | " rolled_variables=s.rolled_variables+[list(s.subproblem.variables)])))\n", 935 | "\n", 936 | "workflow = hybrid.LoopUntilNoImprovement(iteration, max_iter=3) \n", 937 | "\n", 938 | "state_updated = workflow.run(initial_state.updated(rolled_variables=[])).result()\n", 939 | "\n", 940 | "plot(G, subgraphs=[G.subgraph(state_updated.rolled_variables[i]) for i in range(len(state_updated.rolled_variables))], \n", 941 | " max_subs=subgraphs, subtitles=True)" 942 | ] 943 | }, 944 | { 945 | "cell_type": "markdown", 946 | "metadata": {}, 947 | "source": [ 948 | "Runnable `Unwind` iterates over a given Runnable until it raises an `EndOfStream`. Given the `EnergyImpactDecomposer`, it decomposes the problem into the number of subproblems of the specified size, `sub_size`, that can fit into the specified `rolling_history` fraction of the problem size. " 949 | ] 950 | }, 951 | { 952 | "cell_type": "code", 953 | "execution_count": null, 954 | "metadata": {}, 955 | "outputs": [], 956 | "source": [ 957 | "sub_size = 20\n", 958 | "\n", 959 | "workflow = hybrid.Unwind(hybrid.EnergyImpactDecomposer(size=sub_size, rolling_history=0.85))\n", 960 | "states_updated = workflow.run(initial_state).result()\n", 961 | "\n", 962 | "print(\"Iterated over {} subproblems: \\n\".format(len(states_updated)))\n", 963 | "for i in range(len(states_updated)):\n", 964 | " print(set(states_updated[i].subproblem.variables))" 965 | ] 966 | }, 967 | { 968 | "cell_type": "markdown", 969 | "metadata": {}, 970 | "source": [ 971 | "## Postprocessing \n", 972 | "The quantum computer's strength is in quickly finding diverse good solutions to hard problems. Its strength is not in precision numerical calculations, so often the best solution it provides is close but not quite the best in the vicinity. At that point, it is often useful to run a quick classical search to refine the solutions. " 973 | ] 974 | }, 975 | { 976 | "cell_type": "markdown", 977 | "metadata": {}, 978 | "source": [ 979 | "This example creates a random binary quadratic model that fits the working graph of a QPU. Such problems can have very large numbers of local minima, making the QPU less likely to return the exact solution, especially when, as here, a low number of reads, `num_reads`, is requested." 980 | ] 981 | }, 982 | { 983 | "cell_type": "code", 984 | "execution_count": null, 985 | "metadata": {}, 986 | "outputs": [], 987 | "source": [ 988 | "from dwave.system import DWaveSampler\n", 989 | "sampler_qpu = DWaveSampler()\n", 990 | "\n", 991 | "bqm_ran5 = dimod.generators.random.ran_r(r=5, graph=(sampler_qpu.nodelist, sampler_qpu.edgelist))\n", 992 | "#bqm = dimod.generators.random.uniform(graph=(sampler_qpu.nodelist, sampler_qpu.edgelist), \n", 993 | "# vartype='SPIN', low=-1, high=1)\n", 994 | "\n", 995 | "solution_qpu = sampler_qpu.sample(bqm_ran5, num_reads=10)\n", 996 | "\n", 997 | "print(\"QPU found energy {}.\".format(solution_qpu.first.energy))" 998 | ] 999 | }, 1000 | { 1001 | "cell_type": "markdown", 1002 | "metadata": {}, 1003 | "source": [ 1004 | "The best solution returned from the QPU is used as the initial sample for a tabu search of one second (for a problem of this size, typically postprocessing time is much shorter). \n", 1005 | "\n", 1006 | "Due to the random construction of the problem, probabilistic nature of quantum sampling, and pseudo-random search algorithm, the postprocessing solution is not guaranteed to be better, and in fact might be worse. Try running both cells of this example a few times. " 1007 | ] 1008 | }, 1009 | { 1010 | "cell_type": "code", 1011 | "execution_count": null, 1012 | "metadata": {}, 1013 | "outputs": [], 1014 | "source": [ 1015 | "initial_state_ran5 = hybrid.State.from_sample(solution_qpu.first.sample, bqm_ran5)\n", 1016 | "\n", 1017 | "solution_pp = hybrid.TabuProblemSampler(timeout=1000).run(initial_state_ran5).result()\n", 1018 | "\n", 1019 | "print(\"Postprocessing found energy {}.\".format(solution_pp.samples.first.energy))" 1020 | ] 1021 | }, 1022 | { 1023 | "cell_type": "markdown", 1024 | "metadata": {}, 1025 | "source": [ 1026 | "## Experimenting with Auto-Embedding Vs Pre-Embedding\n", 1027 | "As an example of using the *dwave-hybrid* framework for experimenting with optimizing problem solving, consider a hypothesis that pre-calculating a generic minor-embedding for reuse in all iterations of QPU sampling might reduce execution time. " 1028 | ] 1029 | }, 1030 | { 1031 | "cell_type": "markdown", 1032 | "metadata": {}, 1033 | "source": [ 1034 | "Sampler `sampler_pre` is created by calculating a clique (fully-connected graph) minor-embedding that can be used for any problem of up to the target subproblem size, `sub_size`. \n", 1035 | "\n", 1036 | "Helper function `qpu_working_graph` creates a [*dwave-networkx*](https://docs.ocean.dwavesys.com/en/stable/docs_dnx/sdk_index.html) graph that represents the *working graph* of the QPU selected above by `DWaveSampler`, a Pegasus or Chimera graph with the same sets of nodes (qubits) and edges (couplers) as the QPU. Ocean software's [*minorminer*](https://docs.ocean.dwavesys.com/en/stable/docs_minorminer/sdk_index.html) finds an embedding for the required clique size in the working graph. " 1037 | ] 1038 | }, 1039 | { 1040 | "cell_type": "code", 1041 | "execution_count": null, 1042 | "metadata": {}, 1043 | "outputs": [], 1044 | "source": [ 1045 | "from helpers.qpu import qpu_working_graph \n", 1046 | "from dwave.system import FixedEmbeddingComposite\n", 1047 | "from minorminer.busclique import find_clique_embedding\n", 1048 | "\n", 1049 | "sub_size = 40\n", 1050 | "qpu_working_graph = qpu_working_graph(sampler_qpu)\n", 1051 | "clique_embedding = find_clique_embedding(sub_size, qpu_working_graph)\n", 1052 | "\n", 1053 | "sampler_pre = FixedEmbeddingComposite(sampler_qpu, clique_embedding)" 1054 | ] 1055 | }, 1056 | { 1057 | "cell_type": "markdown", 1058 | "metadata": {}, 1059 | "source": [ 1060 | "Define two QPU branches: \n", 1061 | "\n", 1062 | "* `branch_auto` uses the standard `QPUSubproblemAutoEmbeddingSampler`. \n", 1063 | "* `branch_pre` uses a `Lambda` for mapping subproblem variables to clique variables and adds\n", 1064 | " that as an `embedding` field in the current state. It then uses the\n", 1065 | " `QPUSubproblemExternalEmbeddingSampler` sampler, which uses the specified minor-embedding. " 1066 | ] 1067 | }, 1068 | { 1069 | "cell_type": "code", 1070 | "execution_count": null, 1071 | "metadata": {}, 1072 | "outputs": [], 1073 | "source": [ 1074 | "branch_auto = ( hybrid.QPUSubproblemAutoEmbeddingSampler(num_reads=10, qpu_sampler=sampler_qpu) |\n", 1075 | " hybrid.SplatComposer() )\n", 1076 | "branch_pre = ( hybrid.Lambda(lambda _, s: s.updated(embedding={val: [ind] for ind, val in enumerate(s.subproblem.variables)})) |\n", 1077 | " hybrid.QPUSubproblemExternalEmbeddingSampler(num_reads=10, qpu_sampler=sampler_pre) |\n", 1078 | " hybrid.SplatComposer() )\n", 1079 | "\n", 1080 | "iteration = ( hybrid.EnergyImpactDecomposer(size=sub_size) | \n", 1081 | " hybrid.Parallel(branch_auto, branch_pre) | \n", 1082 | " hybrid.ArgMin() ) \n", 1083 | "\n", 1084 | "workflow = hybrid.Loop(iteration, max_iter=5)\n", 1085 | "hybrid.print_structure(workflow)" 1086 | ] 1087 | }, 1088 | { 1089 | "cell_type": "markdown", 1090 | "metadata": {}, 1091 | "source": [ 1092 | "Run both branches and compare the runtimes." 1093 | ] 1094 | }, 1095 | { 1096 | "cell_type": "code", 1097 | "execution_count": null, 1098 | "metadata": {}, 1099 | "outputs": [], 1100 | "source": [ 1101 | "result = workflow.run(initial_state).result()\n", 1102 | "hybrid.print_counters(workflow)" 1103 | ] 1104 | }, 1105 | { 1106 | "cell_type": "markdown", 1107 | "metadata": {}, 1108 | "source": [ 1109 | "Copyright © 2020 D-Wave Systems, Inc\n", 1110 | "\n", 1111 | "The software is licensed under the Apache License, Version 2.0 (the \"License\");\n", 1112 | "you may not use this file except in compliance with the License.\n", 1113 | "You may obtain a copy of the License at\n", 1114 | "\n", 1115 | " http://www.apache.org/licenses/LICENSE-2.0\n", 1116 | "\n", 1117 | "Unless required by applicable law or agreed to in writing, software\n", 1118 | "distributed under the License is distributed on an \"AS IS\" BASIS,\n", 1119 | "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 1120 | "See the License for the specific language governing permissions and\n", 1121 | "limitations under the License.\n", 1122 | "\n", 1123 | "\"Creative
This Jupyter Notebook is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License" 1124 | ] 1125 | } 1126 | ], 1127 | "metadata": { 1128 | "kernelspec": { 1129 | "display_name": "Python 3", 1130 | "language": "python", 1131 | "name": "python3" 1132 | }, 1133 | "language_info": { 1134 | "codemirror_mode": { 1135 | "name": "ipython", 1136 | "version": 3 1137 | }, 1138 | "file_extension": ".py", 1139 | "mimetype": "text/x-python", 1140 | "name": "python", 1141 | "nbconvert_exporter": "python", 1142 | "pygments_lexer": "ipython3", 1143 | "version": "3.7.0" 1144 | }, 1145 | "latex_envs": { 1146 | "LaTeX_envs_menu_present": true, 1147 | "autoclose": false, 1148 | "autocomplete": true, 1149 | "bibliofile": "biblio.bib", 1150 | "cite_by": "apalike", 1151 | "current_citInitial": 1, 1152 | "eqLabelWithNumbers": true, 1153 | "eqNumInitial": 1, 1154 | "hotkeys": { 1155 | "equation": "Ctrl-E", 1156 | "itemize": "Ctrl-I" 1157 | }, 1158 | "labels_anchors": false, 1159 | "latex_user_defs": false, 1160 | "report_style_numbering": false, 1161 | "user_envs_cfg": false 1162 | }, 1163 | "toc": { 1164 | "base_numbering": 1, 1165 | "nav_menu": {}, 1166 | "number_sections": false, 1167 | "sideBar": true, 1168 | "skip_h1_title": false, 1169 | "title_cell": "Table of Contents", 1170 | "title_sidebar": "Contents", 1171 | "toc_cell": false, 1172 | "toc_position": { 1173 | "height": "calc(100% - 180px)", 1174 | "left": "10px", 1175 | "top": "150px", 1176 | "width": "165px" 1177 | }, 1178 | "toc_section_display": true, 1179 | "toc_window_display": true 1180 | } 1181 | }, 1182 | "nbformat": 4, 1183 | "nbformat_minor": 2 1184 | } 1185 | --------------------------------------------------------------------------------