├── .circleci └── config.yml ├── .devcontainer └── devcontainer.json ├── LICENSE ├── README.md ├── demo.py ├── readme_imgs ├── not_partition_yet.png └── partition.png ├── requirements.txt └── tests ├── __init__.py └── test_integration.py /.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 | weekly: 15 | triggers: 16 | - schedule: 17 | cron: "0 1 * * 2" 18 | filters: 19 | branches: 20 | only: 21 | - master 22 | - main 23 | jobs: 24 | - dwave/test-linux 25 | - dwave/test-osx 26 | - dwave/test-win 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/distributed-computing?quickstart=1) 4 | [![Linux/Mac/Windows build status]( 5 | https://circleci.com/gh/dwave-examples/distributed-computing.svg?style=shield)]( 6 | https://circleci.com/gh/dwave-examples/distributed-computing) 7 | 8 | # Distributed Computing 9 | 10 | In [distributed computing systems](https://en.wikipedia.org/wiki/Distributed_computing), a group of computers work together to achieve 11 | a common goal. For example, a group of computers might work together to 12 | analyze a large data set. In these types of computing systems, each computer 13 | manages a piece of the problem and interacts with the other computing 14 | systems by passing messages. Each computer handles a subset of the required 15 | operations, and some operations might require inputs computed by a different 16 | computer. By passing a message containing the required input, the operation can 17 | then be completed. These messages might contain information required to 18 | continue the computations, and so can become a bottleneck to efficient 19 | computation. By minimizing the number of messages required between computers we 20 | can also minimize the number of dependencies between the operations performed 21 | on different systems, making the overall computation faster and more efficient 22 | by minimizing waiting time. 23 | 24 | ## Modeling the Problem as a Graph 25 | 26 | To solve the problem of minimizing messaging between computers in a distributed 27 | computing system, we build a graph or network model. Each operation for the 28 | overall computation is represented by a node, or vertex, in the graph, and an 29 | edge between nodes indicates that there is a dependency between two operations. 30 | To minimize the number of messages passed, we would like to partition the 31 | operations amongst the available computers so that the number of messages 32 | between computers (or partitions) is minimized. Additionally, we would also 33 | like to balance the workload across our available computers by partitioning the 34 | operations evenly. 35 | 36 | To solve this problem in our graph model, we are looking to partition the set 37 | of nodes into a fixed number of subsets of equal size so that the total number 38 | of edges between subsets is minimized. This is known as the graph 39 | k-partitioning problem. In the case where k = 2, it is straightforward to use 40 | binary variables to indicate the subsets for each operation and solve using a 41 | binary quadratic model, as shown in the [graph partitioning code example](https://github.com/dwave-examples/graph-partitioning). For 42 | k > 2, the problem becomes significantly more complex. 43 | 44 | ## Usage 45 | 46 | To run the demo, type: 47 | 48 | ```python demo.py``` 49 | 50 | Additional options are available to select different graphs to run the problem 51 | on. To see the full list of options, type: 52 | 53 | ```python demo.py -h``` 54 | 55 | During a successful run of the program, two images are produced and saved. The 56 | first is the original input graph, saved as `input_graph.png`. 57 | 58 | ![Example Input](readme_imgs/not_partition_yet.png) 59 | 60 | The second highlights the partition of the population into groups. 61 | 62 | ![Example Output](readme_imgs/partition.png) 63 | 64 | ### Graphs Available 65 | 66 | Several different types of graphs or networks are available for this demo using 67 | the options provided. These are all built using NetworkX graph generator 68 | functions, and the details of these functions can be found [here](https://networkx.org/documentation/stable//reference/generators.html#). 69 | 70 | - `partition`: Partition graph; specify number of nodes, number of partitions, 71 | and inter- and intra-partition edge probabilities. 72 | - `internet`: Internet Autonomous System network; specify number of nodes 73 | and partitions. 74 | - `rand-reg`: A random d-regular graph; specify number of nodes and value for d. 75 | - `ER`: Erdos-Renyi random graph; specify number of nodes and edge probability. 76 | - `SF`: Barabasi-Albert scale-free graph; specify number of nodes and number of 77 | edges to add from a new node to any existing nodes. 78 | 79 | The default graph is the partition graph on 100 nodes with 4 partitions with 80 | inter-partition edge probability of 0.5 and intra-partition edge probability of 81 | 0.001. The largest number of nodes times the number of partitions allowed for 82 | any problem instance can be at most 5,000. 83 | 84 | ## Code Overview 85 | 86 | The demo program formulates this graph k-partitioning problem as a constrained 87 | quadratic model (CQM), and solves it using the hybrid CQM solver. 88 | 89 | ### Variables 90 | 91 | The formulation of this problem defines a binary variable x for each pair 92 | (n, p), where n is a node in the graph and p is a partition. If the solution 93 | returns variable (n, p) = 1, then node n is assigned to partition p. Otherwise, 94 | if the solution returns variable (n, p) = 0, then node n is *not* assigned to 95 | partition p. 96 | 97 | ### Objective 98 | 99 | The objective for this problem is to minimize the number of inter-partition 100 | edges. We can formulate this as a binary quadratic expression that needs to be 101 | minimized by considering an arbitrary edge (i, j) between nodes i and j in the 102 | graph. For each partition p, we add the expression 103 | (i, p) + (j, p) - 2\*(i, p)\*(j, p) to decrease the overall cost when i and j 104 | are assigned to the same partition, and increase the overall cost when they 105 | are not. To see how this expression maps to these costs, we examine the 106 | following table which demonstrates the cost of edge (i, j), depending on 107 | whether i and j are each assigned to partition p. 108 | 109 | | (i, k) | (j, k) | edge (i,j) | cost | 110 | | :---: | :---: | :---: | :---: | 111 | | 0 | 0 | intra | 0 | 112 | | 0 | 1 | inter | 1 | 113 | | 1 | 0 | inter | 1 | 114 | | 1 | 1 | intra | 0 | 115 | 116 | Now that we have an expression for the appropriate cost for each edge and each 117 | partition, we simply sum over all edges and all partitions to build the 118 | objective function that will minimize the number of inter-partition edges in 119 | the entire graph. 120 | 121 | **Objective:** minimize Σ(i,j) Σp x(i,p) + x(j,p) - 2\*x(i,p)\*x(j,p) 122 | 123 | ### Constraints 124 | 125 | #### One-Hot Constraint 126 | 127 | Each node in our graph must be assigned to exactly one partition, so we must 128 | enforce a [one-hot constraint](https://en.wikipedia.org/wiki/One-hot ) on each node. That is, for each node i, we must 129 | have that the sum of all binary variables associated with i is equal to 1. 130 | 131 | **Constraint 1:** Σk x(i,p) = 1, for each node i 132 | 133 | #### Partition Size Constraint 134 | 135 | To efficiently distribute the operational load across computers in our system, 136 | we would like the partitions to have equal size. If N is the total number of 137 | nodes in the graph and k is the number of partitions available, each partition 138 | should have size N/k. We enforce this by requiring that the sum of binary 139 | variables associated with each partition is equal to N/k. Note that this 140 | requires that N is evenly divisble by k, and so the demo file will adjust N as 141 | needed to enforce this requirement. 142 | 143 | **Constraint 2:** Σi x(i,p) = N/k, for each partition p. 144 | 145 | ## License 146 | 147 | Released under the Apache License 2.0. See [LICENSE](LICENSE) file. 148 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 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 | """Graph partitioning with CQM solver.""" 16 | 17 | from random import random 18 | from collections import defaultdict 19 | import sys 20 | 21 | import networkx as nx 22 | import numpy as np 23 | import click 24 | import matplotlib 25 | from dimod import Binary, ConstrainedQuadraticModel, quicksum 26 | from dwave.system import LeapHybridCQMSampler 27 | 28 | try: 29 | import matplotlib.pyplot as plt 30 | except ImportError: 31 | matplotlib.use("agg") 32 | import matplotlib.pyplot as plt 33 | 34 | 35 | 36 | def build_graph(graph, nodes, degree, prob, p_in, p_out, new_edges, k_partition): 37 | """Builds graph from user specified parameters or use defaults. 38 | 39 | Args: 40 | See @click decorator before main. 41 | 42 | Returns: 43 | G (Graph): The graph to be partitioned 44 | """ 45 | 46 | k = k_partition 47 | 48 | if k * nodes > 5000: 49 | raise ValueError("Problem size is too large.") 50 | elif nodes % k != 0: 51 | raise ValueError("Number of nodes must be divisible by k.") 52 | 53 | # Build graph using networkx 54 | if graph == 'partition': 55 | print("\nBuilding partition graph...") 56 | G = nx.random_partition_graph([int(nodes/k)]*k, p_in, p_out) 57 | 58 | elif graph == 'internet': 59 | print("\nReading in internet graph of size", nodes, "...") 60 | G = nx.random_internet_as_graph(nodes) 61 | 62 | elif graph == 'rand-reg': 63 | if degree >= nodes: 64 | raise ValueError("degree must be less than number of nodes") 65 | if degree * nodes % 2 == 1: 66 | raise ValueError("degree * nodes must be even") 67 | print("\nGenerating random regular graph...") 68 | G = nx.random_regular_graph(degree, nodes) 69 | 70 | elif graph == 'ER': 71 | print("\nGenerating Erdos-Renyi graph...") 72 | G = nx.erdos_renyi_graph(nodes, prob) 73 | 74 | elif graph == 'SF': 75 | if new_edges > nodes: 76 | raise ValueError("Number of edges must be less than number of nodes") 77 | print("\nGenerating Barabasi-Albert scale-free graph...") 78 | G = nx.barabasi_albert_graph(nodes, new_edges) 79 | 80 | else: 81 | # Should not be reachable, due to click argument validation 82 | raise ValueError(f"Unexpected graph type: {graph}") 83 | 84 | return G 85 | 86 | 87 | # Visualize the input graph 88 | def visualize_input_graph(G): 89 | """Visualize the graph to be partitioned. 90 | Args: 91 | G (Graph): Input graph to be partitioned 92 | 93 | Returns: 94 | None. Image saved as input_graph.png. 95 | """ 96 | 97 | pos = nx.random_layout(G) 98 | nx.draw_networkx_nodes(G, pos, node_size=20, node_color='r', edgecolors='k') 99 | nx.draw_networkx_edges(G, pos, edgelist=G.edges(), style='solid', edge_color='#808080') 100 | plt.draw() 101 | plt.savefig('input_graph.png') 102 | plt.close() 103 | 104 | 105 | def build_cqm(G, k): 106 | """Build the CQM. 107 | Args: 108 | G (Graph): Input graph to be partitioned 109 | k (int): Number of partitions to be used 110 | 111 | Returns: 112 | cqm (ConstrainedQuadraticModel): The CQM for our problem 113 | """ 114 | 115 | # Set up the partitions 116 | partitions = range(k) 117 | 118 | # Initialize the CQM object 119 | print("\nBuilding constrained quadratic model...") 120 | cqm = ConstrainedQuadraticModel() 121 | 122 | # Add binary variables, one for each node and each partition in the graph 123 | print("\nAdding variables....") 124 | v = [[Binary(f'v_{i},{p}') for p in partitions] for i in G.nodes] 125 | 126 | # One-hot constraint: each node is assigned to exactly one partition 127 | print("\nAdding one-hot constraints...") 128 | for i in G.nodes: 129 | # print("\nAdding one-hot for node", i) 130 | cqm.add_discrete([f'v_{i},{p}' for p in partitions], label=f"one-hot-node-{i}") 131 | 132 | # Constraint: Partitions have equal size 133 | print("\nAdding partition size constraint...") 134 | for p in partitions: 135 | # print("\nAdding partition size constraint for partition", p) 136 | cqm.add_constraint(quicksum(v[i][p] for i in G.nodes) == G.number_of_nodes()/k, label=f'partition-size-{p}') 137 | 138 | # Objective: minimize edges between partitions 139 | print("\nAdding objective...") 140 | min_edges = [] 141 | for i,j in G.edges: 142 | for p in partitions: 143 | min_edges.append(v[i][p]+v[j][p]-2*v[i][p]*v[j][p]) 144 | cqm.set_objective(sum(min_edges)) 145 | 146 | return cqm 147 | 148 | 149 | def run_cqm_and_collect_solutions(cqm, sampler): 150 | """Send the CQM to the sampler and return the best sample found. 151 | Args: 152 | cqm (ConstrainedQuadraticModel): The CQM for our problem 153 | sampler: The CQM sampler to be used. Must have sample_cqm function. 154 | 155 | Returns: 156 | dict: The first feasible solution found 157 | """ 158 | 159 | # Initialize the solver 160 | print("\nSending to the solver...") 161 | 162 | # Solve the CQM problem using the solver 163 | sampleset = sampler.sample_cqm(cqm, label='Example - Graph Partitioning') 164 | 165 | feasible_sampleset = sampleset.filter(lambda row: row.is_feasible) 166 | 167 | # Return the first feasible solution 168 | if not len(feasible_sampleset): 169 | print("\nNo feasible solution found.\n") 170 | return None 171 | 172 | return feasible_sampleset.first.sample 173 | 174 | 175 | def process_sample(sample, G, k, verbose=True): 176 | """Interpret the sample found in terms of our graph. 177 | Args: 178 | sample (dict): Sample to be used 179 | G (graph): Original input graph 180 | k (int): Number of partitions 181 | verbose (bool): Trigger to print output to command-line 182 | 183 | Returns: 184 | soln (list): List of partitions, indexed by node 185 | partitions (dict): Each item is partition: [nodes in partition] 186 | """ 187 | 188 | partitions = defaultdict(list) 189 | soln = [-1]*G.number_of_nodes() 190 | 191 | for node in G.nodes: 192 | for p in range(k): 193 | if sample[f'v_{node},{p}'] == 1: 194 | partitions[p].append(node) 195 | soln[node] = p 196 | 197 | # Count the nodes in each partition 198 | counts = np.zeros(k) 199 | for p in partitions: 200 | counts[p] += len(partitions[p]) 201 | 202 | # Compute the number of links between different partitions 203 | sum_diff = 0 204 | for i, j in G.edges: 205 | if soln[i] != soln[j]: 206 | sum_diff += 1 207 | 208 | if verbose: 209 | print("Counts in each partition: ", counts) 210 | print("Number of links between partitions: ", sum_diff) 211 | print("Number of links within partitions:", len(G.edges)-sum_diff) 212 | 213 | return soln, partitions 214 | 215 | 216 | def visualize_results(G, partitions, soln): 217 | """Visualize the partition. 218 | Args: 219 | G (graph): Original input graph 220 | partitions (dict): Each item is partition: [nodes in partition] 221 | soln (list): List of partitions, indexed by node 222 | 223 | Returns: 224 | None. Output is saved as output_graph.png. 225 | """ 226 | 227 | print("\nVisualizing output...") 228 | 229 | # Build hypergraph of partitions 230 | hypergraph = nx.Graph() 231 | hypergraph.add_nodes_from(partitions.keys()) 232 | pos_h = nx.circular_layout(hypergraph, scale=2.) 233 | 234 | # Place nodes within partition 235 | pos_full = {} 236 | assignments = {node: soln[node] for node in range(len(soln))} 237 | for node, partition in assignments.items(): 238 | pos_full[node] = pos_h[partition] 239 | 240 | pos_g = {} 241 | for _, nodes in partitions.items(): 242 | subgraph = G.subgraph(nodes) 243 | pos_subgraph = nx.random_layout(subgraph) 244 | pos_g.update(pos_subgraph) 245 | 246 | # Combine hypergraph and partition graph positions 247 | pos = {} 248 | for node in G.nodes(): 249 | pos[node] = pos_full[node] + pos_g[node] 250 | nx.draw_networkx_nodes(G, pos, node_size=40, node_color=soln, edgecolors='k') 251 | 252 | # Draw good and bad edges in different colors 253 | bad_edges = [(u, v) for u, v in G.edges if soln[u] != soln[v]] 254 | good_edges = [(u,v) for u, v, in G.edges if soln[u] == soln[v]] 255 | 256 | nx.draw_networkx_edges(G, pos, edgelist=good_edges, style='solid', edge_color='#7f7f7f') 257 | nx.draw_networkx_edges(G, pos, edgelist=bad_edges, style='solid', edge_color='k') 258 | 259 | # Save the output image 260 | plt.draw() 261 | output_name = 'output_graph.png' 262 | plt.savefig(output_name) 263 | 264 | print("\tOutput stored in", output_name) 265 | 266 | 267 | @click.command(context_settings=dict(help_option_names=['-h', '--help'])) 268 | @click.option("-g", "--graph", type=click.Choice(['partition', 'internet', 'rand-reg', 'ER', 'SF']), 269 | help="Graph to partition.", default='partition', show_default=True) 270 | @click.option("-n", "--nodes", help="Set graph size for graph.", default=100, type=click.IntRange(1), 271 | show_default=True) 272 | @click.option("-d", "--degree", help="Set node degree for random regular graph.", default=4, 273 | type=click.IntRange(1), show_default=True) 274 | @click.option("-p", "--prob", help="Set graph edge probability for ER graph. Must be between 0 and 1.", 275 | type=click.FloatRange(0, 1), default=0.25, show_default=True) 276 | @click.option("-i", "--p-in", help="Set probability of edges within groups for partition graph. Must be between 0 and 1.", 277 | type=click.FloatRange(0, 1), default=0.5, show_default=True) 278 | @click.option("-o", "--p-out", help="Set probability of edges between groups for partition graph. Must be between 0 and 1.", 279 | type=click.FloatRange(0, 1), default=0.001, show_default=True) 280 | @click.option("-e", "--new-edges", help="Set number of edges from new node to existing node in SF graph.", 281 | default=4, type=click.IntRange(1), show_default=True) 282 | @click.option("-k", "--k-partition", help="Set number of partitions to divide graph into.", default=4, 283 | type=click.IntRange(2), show_default=True) 284 | def main(graph, nodes, degree, prob, p_in, p_out, new_edges, k_partition): 285 | 286 | G = build_graph(graph, nodes, degree, prob, p_in, p_out, new_edges, k_partition) 287 | 288 | visualize_input_graph(G) 289 | 290 | cqm = build_cqm(G, k_partition) 291 | 292 | # Initialize the CQM solver 293 | print("\nOptimizing on LeapHybridCQMSampler...") 294 | sampler = LeapHybridCQMSampler() 295 | 296 | sample = run_cqm_and_collect_solutions(cqm, sampler) 297 | 298 | if sample is not None: 299 | soln, partitions = process_sample(sample, G, k_partition) 300 | 301 | visualize_results(G, partitions, soln) 302 | 303 | 304 | if __name__ == '__main__': 305 | # pylint: disable=no-value-for-parameter 306 | main() 307 | -------------------------------------------------------------------------------- /readme_imgs/not_partition_yet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/distributed-computing/5bfa1fa804bc26988ffdf04747c95785aa60f198/readme_imgs/not_partition_yet.png -------------------------------------------------------------------------------- /readme_imgs/partition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/distributed-computing/5bfa1fa804bc26988ffdf04747c95785aa60f198/readme_imgs/partition.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dwave-ocean-sdk>=4.1 2 | matplotlib~=3.0 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/distributed-computing/5bfa1fa804bc26988ffdf04747c95785aa60f198/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 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 subprocess 16 | import unittest 17 | import os 18 | import sys 19 | 20 | from dwave.system import LeapHybridCQMSampler 21 | import networkx as nx 22 | 23 | import demo 24 | 25 | project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 26 | 27 | class TestSmoke(unittest.TestCase): 28 | @unittest.skipIf(os.getenv('SKIP_INT_TESTS'), "Skipping integration test.") 29 | def test_smoke(self): 30 | """Run demo.py and check that nothing crashes""" 31 | 32 | demo_file = os.path.join(project_dir, 'demo.py') 33 | subprocess.check_output([sys.executable, demo_file]) 34 | 35 | class TestDemo(unittest.TestCase): 36 | def test_default_soln(self): 37 | """Check the default solution quality.""" 38 | 39 | n = 100 40 | k = 4 41 | p_in = 0.5 42 | p_out = 0.01 43 | 44 | G = nx.random_partition_graph([int(n/k)]*k, p_in, p_out) 45 | 46 | cqm = demo.build_cqm(G, k) 47 | 48 | sampler = LeapHybridCQMSampler() 49 | 50 | sample = demo.run_cqm_and_collect_solutions(cqm, sampler) 51 | 52 | _, partitions = demo.process_sample(sample, G, k, verbose=False) 53 | self.assertEqual(len(partitions), k) 54 | 55 | # Check that constraints were followed 56 | nodes = list(G.nodes) 57 | for key, partition in partitions.items(): 58 | self.assertEqual(len(partition), n/k) 59 | for node in partition: 60 | nodes.remove(node) 61 | 62 | self.assertFalse(nodes) 63 | --------------------------------------------------------------------------------