├── .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 | [](
3 | https://codespaces.new/dwave-examples/distributed-computing?quickstart=1)
4 | [](
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 | 
59 |
60 | The second highlights the partition of the population into groups.
61 |
62 | 
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 |
--------------------------------------------------------------------------------