├── .circleci └── config.yml ├── .coveragerc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE.txt ├── README.rst ├── docs ├── Makefile ├── README.rst ├── _images │ ├── ChimeraUnitCell.png │ ├── Cover.png │ ├── Match.png │ ├── MaxCut.png │ ├── Partitioning.png │ ├── Problem_MapColoring.png │ ├── Social.png │ ├── chimera.png │ ├── chimera_layout_0-rightside.png │ ├── clique.png │ ├── find_chimera-unitcell.png │ ├── pegasus_layout_h_on_g.png │ ├── pegasus_qubits.png │ ├── pegasus_roadway.png │ ├── tsp.png │ └── zephyr_embedding_5clique.png ├── _static │ ├── cookie_notice.css │ └── cookie_notice.js ├── algorithms.rst ├── api_ref.rst ├── conf.py ├── default_sampler.rst ├── drawing.rst ├── generators.rst ├── index.rst ├── requirements.txt └── utilities.rst ├── dwave_networkx ├── __init__.py ├── algorithms │ ├── __init__.py │ ├── canonicalization.py │ ├── clique.py │ ├── coloring.py │ ├── cover.py │ ├── elimination_ordering.py │ ├── independent_set.py │ ├── markov.py │ ├── matching.py │ ├── max_cut.py │ ├── partition.py │ ├── social.py │ └── tsp.py ├── default_sampler.py ├── drawing │ ├── __init__.py │ ├── chimera_layout.py │ ├── distinguishable_colors.py │ ├── parallel_embeddings.py │ ├── pegasus_layout.py │ ├── qubit_layout.py │ └── zephyr_layout.py ├── examples │ ├── chimera_graph.py │ ├── max_cut.py │ ├── max_independent_set.py │ ├── min_maximal_matching.py │ ├── min_vertex_color.py │ └── min_vertex_cover.py ├── exceptions.py ├── generators │ ├── __init__.py │ ├── chimera.py │ ├── common.py │ ├── markov.py │ ├── pegasus.py │ └── zephyr.py ├── package_info.py └── utils │ ├── __init__.py │ └── decorators.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── requirements.txt ├── test_canonicalization.py ├── test_chimera_layout.py ├── test_clique.py ├── test_coloring.py ├── test_cover.py ├── test_decorators.py ├── test_elimination_ordering.py ├── test_generator_chimera.py ├── test_generator_markov.py ├── test_generator_pegasus.py ├── test_generator_zephyr.py ├── test_independent_set.py ├── test_markov.py ├── test_matching.py ├── test_max_cut.py ├── test_parallel_embeddings.py ├── test_partitioning.py ├── test_pegasus_layout.py ├── test_qubit_layout.py ├── test_regular_coloring.py ├── test_social.py ├── test_tsp.py └── test_zephyr_layout.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | win: circleci/windows@5.0 5 | codecov: codecov/codecov@3 6 | 7 | jobs: 8 | test-linux: 9 | parameters: 10 | dependency-versions: 11 | type: string 12 | python-version: 13 | type: string 14 | 15 | docker: 16 | - image: python:<< parameters.python-version >> 17 | 18 | steps: 19 | - checkout 20 | 21 | - restore_cache: &restore-cache-env 22 | key: pip-{{ checksum "requirements.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ .Environment.CIRCLE_JOB }} 23 | 24 | - run: &create-venv-install-deps 25 | name: Create virtual environment and install dependencies 26 | command: | 27 | python -m venv env 28 | . env/bin/activate 29 | pip install -U pip setuptools wheel 30 | pip install -U twine 31 | pip install -r requirements.txt -r tests/requirements.txt 32 | 33 | - run: 34 | name: Install dependencies 35 | command: | 36 | . env/bin/activate 37 | pip install --upgrade << parameters.dependency-versions >> 38 | 39 | - save_cache: &save-cache-env 40 | key: pip-{{ checksum "requirements.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ .Environment.CIRCLE_JOB }} 41 | paths: 42 | - ~/.cache/pip 43 | 44 | - run: &run-tests 45 | name: Run unittests 46 | command: | 47 | . env/bin/activate 48 | coverage run -m unittest discover 49 | coverage xml 50 | 51 | - codecov/upload: &upload-python-code-coverage 52 | file: coverage.xml 53 | 54 | test-osx: 55 | parameters: 56 | python-version: 57 | type: string 58 | xcode: 59 | type: string 60 | default: "16.0.0" 61 | 62 | macos: 63 | xcode: << parameters.xcode >> 64 | 65 | steps: 66 | - checkout 67 | 68 | - restore_cache: &restore-cache-pyenv 69 | key: &brew-pyenv-cache-key v1-brew-pyenv-{{ .Environment.CIRCLE_JOB }}-xcode-<< parameters.xcode >> 70 | 71 | - run: 72 | name: Install pyenv 73 | command: | 74 | brew update 75 | brew install pyenv 76 | 77 | - run: 78 | name: Install python 79 | command: | 80 | pyenv install << parameters.python-version>> -s 81 | 82 | - save_cache: &save-cache-pyenv 83 | key: *brew-pyenv-cache-key 84 | paths: 85 | - /Users/distiller/Library/Caches/Homebrew 86 | - /usr/local/Homebrew 87 | - ~/.pyenv 88 | 89 | - restore_cache: *restore-cache-env 90 | 91 | - run: 92 | name: Create virtualenv and install dependencies 93 | command: | 94 | eval "$(pyenv init --path)" 95 | pyenv local << parameters.python-version >> 96 | python -m venv env 97 | . env/bin/activate 98 | python --version 99 | pip install pip --upgrade 100 | pip install -r requirements.txt -r tests/requirements.txt 101 | 102 | - save_cache: *save-cache-env 103 | 104 | - run: *run-tests 105 | 106 | test-win: 107 | parameters: 108 | python-version: 109 | type: string 110 | 111 | executor: 112 | name: win/default 113 | 114 | steps: 115 | - checkout 116 | 117 | - run: 118 | name: Install python and create virtualenv 119 | shell: bash -eo pipefail 120 | command: | 121 | # resolve python MAJOR.MINOR version to latest MAJOR.MINOR.PATCH version available on NuGet 122 | full_version=$( 123 | curl -s 'https://azuresearch-usnc.nuget.org/query?q=python' \ 124 | | jq -r '.data[] | select(.id == "python") .versions[] | .version' \ 125 | | awk -F. -v ver='<< parameters.python-version >>' \ 126 | 'index($0, ver".") == 1 && $3 >= m { m = $3; v = $0 } END { print v }' 127 | ) 128 | nuget install python -Version "$full_version" -ExcludeVersion 129 | python/tools/python -V 130 | python/tools/python -m venv env 131 | 132 | - run: 133 | name: Install dependencies 134 | command: | 135 | env\Scripts\activate.ps1 136 | python --version 137 | pip install -r requirements.txt 138 | pip install -r tests\requirements.txt 139 | 140 | - run: 141 | name: Run unittests 142 | command: | 143 | env\Scripts\activate.ps1 144 | python -m unittest discover 145 | 146 | test-doctest: 147 | docker: 148 | - image: python:3.12 149 | 150 | steps: 151 | - checkout 152 | 153 | - restore_cache: *restore-cache-env 154 | 155 | - run: *create-venv-install-deps 156 | 157 | - run: 158 | name: Install docs dependencies 159 | command: | 160 | . env/bin/activate 161 | pip install -r docs/requirements.txt 162 | 163 | - save_cache: *save-cache-env 164 | 165 | - run: 166 | name: build docs 167 | command: | 168 | . env/bin/activate 169 | make -C docs/ html 170 | 171 | - store_artifacts: 172 | path: ./docs/build/html 173 | 174 | - run: 175 | name: doctest 176 | command: | 177 | . env/bin/activate 178 | make -C docs/ doctest 179 | 180 | - run: 181 | name: linkcheck 182 | command: | 183 | . env/bin/activate 184 | make -C docs/ linkcheck 185 | 186 | deploy: 187 | docker: 188 | - image: python:3.12 189 | 190 | steps: 191 | - checkout 192 | 193 | - run: *create-venv-install-deps 194 | 195 | - run: 196 | name: Verify version matches tag 197 | command: | 198 | . env/bin/activate 199 | pip install . 200 | [[ "$(pip show dwave-networkx 2>/dev/null | grep Version)" == "Version: $CIRCLE_TAG" ]] 201 | 202 | - run: 203 | name: Build sdist and bdist 204 | command: | 205 | . env/bin/activate 206 | pip install setuptools wheel 207 | python setup.py sdist bdist_wheel 208 | 209 | - run: 210 | name: Upload to PyPI 211 | command: | 212 | . env/bin/activate 213 | twine upload -u $PYPI_USERNAME -p $PYPI_PASSWORD --skip-existing ./dist/* 214 | 215 | workflows: 216 | test: 217 | jobs: 218 | - test-linux: 219 | matrix: 220 | parameters: 221 | python-version: &python-versions ["3.9", "3.10", "3.11", "3.12", "3.13"] 222 | dependency-versions: ["dimod==0.10.8 networkx==2.4 oldest-supported-numpy", # oldest supported 223 | "dimod networkx numpy", # latest 224 | ] 225 | exclude: 226 | # dimod 0.10.8 doesn't support Python 3.11+ 227 | - python-version: "3.11" 228 | dependency-versions: "dimod==0.10.8 networkx==2.4 oldest-supported-numpy" 229 | - python-version: "3.12" 230 | dependency-versions: "dimod==0.10.8 networkx==2.4 oldest-supported-numpy" 231 | - python-version: "3.13" 232 | dependency-versions: "dimod==0.10.8 networkx==2.4 oldest-supported-numpy" 233 | - test-osx: 234 | matrix: 235 | parameters: 236 | python-version: *python-versions 237 | - test-win: 238 | matrix: 239 | parameters: 240 | python-version: *python-versions 241 | - test-doctest 242 | 243 | deploy: 244 | jobs: 245 | - deploy: 246 | filters: 247 | tags: 248 | only: /^[0-9]+(\.[0-9]+)*((\.dev|rc)([0-9]+)?)?$/ 249 | branches: 250 | ignore: /.*/ 251 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | # omit virtualenv and test files 3 | omit = venv/*, */tests/* 4 | source=dwave_networkx 5 | 6 | [report] 7 | exclude_lines: 8 | pragma: no cover 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Description** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Stack Overflow provides an excellent guide on [how to create a Minimal, Complete and Verifiable example.](https://stackoverflow.com/help/mcve) 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Environment:** 17 | - OS: [Ubuntu 22.04.3 LTS] 18 | - Python version: [e.g. 3.12.0] 19 | 20 | **Additional context** 21 | Add any other context about the problem here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Current Problem** 8 | Is your feature request related to a problem? Please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Proposed Solution** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Alternatives Considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | _build 3 | dist 4 | dwave_networkx.egg-info 5 | *.pyc 6 | 7 | venv/ 8 | 9 | docs/generated/ 10 | docs/algorithms/generated/ 11 | docs/drawing/generated/ 12 | 13 | htmlcov/ 14 | .coverage 15 | 16 | # sublime stuff 17 | *.sublime-project 18 | *.sublime-workspace 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/dwave-networkx.svg 2 | :target: https://pypi.org/project/dwave-networkx 3 | 4 | .. image:: https://codecov.io/gh/dwavesystems/dwave-networkx/branch/main/graph/badge.svg 5 | :target: https://codecov.io/gh/dwavesystems/dwave-networkx 6 | 7 | .. image:: https://circleci.com/gh/dwavesystems/dwave-networkx.svg?style=svg 8 | :target: https://circleci.com/gh/dwavesystems/dwave-networkx 9 | 10 | ============== 11 | dwave-networkx 12 | ============== 13 | 14 | .. start_dnx_about 15 | 16 | dwave-networkx is an extension of `NetworkX `_\ ---a 17 | Python language package for exploration and analysis of networks and network 18 | algorithms---for users of D-Wave quantum computers. It provides tools for 19 | working with quantum processing unit (QPU) topology graphs, such as the Pegasus 20 | used on the Advantage\ :sup:`TM` quantum computer, and implementations of 21 | graph-theory algorithms on D-Wave quantum computers and other binary quadratic 22 | model (BQM) samplers. 23 | 24 | This example generates a Pegasus graph of the size used by Advantage QPUs. 25 | 26 | >>> import dwave_networkx as dnx 27 | >>> graph = dnx.pegasus_graph(16) 28 | 29 | .. end_dnx_about 30 | 31 | Installation 32 | ============ 33 | 34 | **Installation from PyPi:** 35 | 36 | .. code-block:: bash 37 | 38 | pip install dwave_networkx 39 | 40 | **Installation from source:** 41 | 42 | .. code-block:: bash 43 | 44 | pip install -r requirements.txt 45 | python setup.py install 46 | 47 | License 48 | ======= 49 | 50 | Released under the Apache License 2.0. 51 | 52 | Contributing 53 | ============ 54 | 55 | Ocean's `contributing guide `_ 56 | has guidelines for contributing to Ocean packages. 57 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = -q 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = DWaveNetworkx 8 | SOURCEDIR = . 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | clean: 16 | rm -rf build reference/generated/ reference/algorithms/generated/ 17 | 18 | .PHONY: help Makefile 19 | 20 | # Catch-all target: route all unknown targets to Sphinx using the new 21 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 22 | %: Makefile 23 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 24 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /docs/_images/ChimeraUnitCell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/ChimeraUnitCell.png -------------------------------------------------------------------------------- /docs/_images/Cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/Cover.png -------------------------------------------------------------------------------- /docs/_images/Match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/Match.png -------------------------------------------------------------------------------- /docs/_images/MaxCut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/MaxCut.png -------------------------------------------------------------------------------- /docs/_images/Partitioning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/Partitioning.png -------------------------------------------------------------------------------- /docs/_images/Problem_MapColoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/Problem_MapColoring.png -------------------------------------------------------------------------------- /docs/_images/Social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/Social.png -------------------------------------------------------------------------------- /docs/_images/chimera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/chimera.png -------------------------------------------------------------------------------- /docs/_images/chimera_layout_0-rightside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/chimera_layout_0-rightside.png -------------------------------------------------------------------------------- /docs/_images/clique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/clique.png -------------------------------------------------------------------------------- /docs/_images/find_chimera-unitcell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/find_chimera-unitcell.png -------------------------------------------------------------------------------- /docs/_images/pegasus_layout_h_on_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/pegasus_layout_h_on_g.png -------------------------------------------------------------------------------- /docs/_images/pegasus_qubits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/pegasus_qubits.png -------------------------------------------------------------------------------- /docs/_images/pegasus_roadway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/pegasus_roadway.png -------------------------------------------------------------------------------- /docs/_images/tsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/tsp.png -------------------------------------------------------------------------------- /docs/_images/zephyr_embedding_5clique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/docs/_images/zephyr_embedding_5clique.png -------------------------------------------------------------------------------- /docs/_static/cookie_notice.css: -------------------------------------------------------------------------------- 1 | #cookie-notice { 2 | display: none; 3 | bottom: 0; 4 | position: fixed; 5 | width: 100%; 6 | background: white; 7 | z-index: 1000000; 8 | height: auto; 9 | text-align: center; 10 | } 11 | .cookie-button { 12 | border: none; 13 | display: inline-block; 14 | justify-content: center; 15 | height: 48px; 16 | font-size: 16px; 17 | font-weight: 600; 18 | border-radius: 4px; 19 | background: rgb(41, 128, 185); 20 | box-shadow: 0 5px 14px 0 rgba(29,30,36,0.19); 21 | color: #fff; 22 | cursor: pointer; 23 | padding-top: 12px; 24 | padding-bottom: 12px; 25 | padding-left: 42px; 26 | padding-right: 42px; 27 | margin: 1em; 28 | margin-left: 2em; 29 | white-space: nowrap; 30 | vertical-align: middle; 31 | } 32 | -------------------------------------------------------------------------------- /docs/_static/cookie_notice.js: -------------------------------------------------------------------------------- 1 | const send_ga = () => { 2 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 3 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 4 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 5 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 6 | 7 | ga('create', 'UA-124167090-6', 'auto'); 8 | ga('send', 'pageview'); 9 | } 10 | 11 | let cookieAccepted = localStorage.cookieAccepted; 12 | if (cookieAccepted == undefined) { 13 | $(document).ready(function () { 14 | var cookieDiv = document.createElement('div') 15 | cookieDiv.id = 'cookie-notice' 16 | cookieDiv.innerHTML = "

Privacy and Cookies: This site uses cookies. By continuing to use this website, you agree to their use. To find out more, see our Privacy Policy.

" 17 | 18 | $('body').append(cookieDiv).ready(() => { 19 | $('#cookie-notice').fadeIn('slow'); 20 | $("#cookie-accept").click(function () { 21 | localStorage.setItem("cookieAccepted", true) 22 | $('#cookie-notice').fadeOut('slow'); 23 | send_ga() 24 | }); 25 | }) 26 | }) 27 | } else if (cookieAccepted == "true") { 28 | send_ga() 29 | } 30 | -------------------------------------------------------------------------------- /docs/algorithms.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_algorithms: 2 | 3 | ========== 4 | Algorithms 5 | ========== 6 | 7 | Implementations of graph-theory algorithms on the D-Wave system and other 8 | binary quadratic model samplers. 9 | 10 | .. currentmodule:: dwave_networkx 11 | 12 | .. _dnx_canonicalization: 13 | 14 | Canonicalization 15 | ================ 16 | 17 | .. automodule:: dwave_networkx.algorithms.canonicalization 18 | 19 | .. autosummary:: 20 | :toctree: generated/ 21 | 22 | canonical_chimera_labeling 23 | 24 | .. _dnx_clique: 25 | 26 | Clique 27 | ====== 28 | 29 | A clique in an undirected graph G = (V, E) is a subset of the vertex set such 30 | that for every two vertices in C there exists an edge connecting the two. 31 | 32 | .. figure:: _images/clique.png 33 | :name: Cover 34 | :alt: image 35 | :align: center 36 | :scale: 40 % 37 | 38 | .. automodule:: dwave_networkx.algorithms.clique 39 | 40 | .. autosummary:: 41 | :toctree: generated/ 42 | 43 | maximum_clique 44 | clique_number 45 | is_clique 46 | 47 | .. _dnx_coloring: 48 | 49 | Coloring 50 | ======== 51 | 52 | Graph coloring is the problem of assigning a color to the vertices of a graph in 53 | a way that no adjacent vertices have the same color. 54 | 55 | Example 56 | ------- 57 | 58 | The map-coloring problem is to assign a color to each region of a map 59 | (represented by a vertex on a graph) such that any two regions sharing a border 60 | (represented by an edge of the graph) have different colors. 61 | 62 | .. figure:: _images/Problem_MapColoring.png 63 | :name: Problem_MapColoringColoring 64 | :alt: image 65 | :align: center 66 | :scale: 70 % 67 | 68 | Coloring a map of Canada with four colors. 69 | 70 | .. automodule:: dwave_networkx.algorithms.coloring 71 | 72 | .. autosummary:: 73 | :toctree: generated/ 74 | 75 | is_vertex_coloring 76 | min_vertex_color 77 | min_vertex_color_qubo 78 | vertex_color 79 | vertex_color_qubo 80 | 81 | .. _dnx_cover: 82 | 83 | Cover 84 | ===== 85 | 86 | Vertex covering is the problem of finding a set of vertices such that all the 87 | edges of the graph are incident to at least one of the vertices in the set. 88 | 89 | .. figure:: _images/Cover.png 90 | :name: CoverCover 91 | :alt: image 92 | :align: center 93 | :scale: 40 % 94 | 95 | Cover for a Chimera unit cell: the nodes of both the blue set of vertices 96 | (the horizontal tile of the Chimera unit cell) and the red set (vertical 97 | tile) connect to all 16 edges of the graph. 98 | 99 | .. automodule:: dwave_networkx.algorithms.cover 100 | 101 | .. autosummary:: 102 | :toctree: generated/ 103 | 104 | is_vertex_cover 105 | min_weighted_vertex_cover 106 | min_vertex_cover 107 | 108 | .. _dnx_elimination_ordering: 109 | 110 | Elimination Ordering 111 | ==================== 112 | 113 | Many algorithms for NP-hard problems are exponential in treewidth. However, 114 | finding a lower bound on treewidth is in itself NP-complete. [Gog2004]_ 115 | describes a branch-and-bound algorithm for computing the treewidth of an 116 | undirected graph by searching in the space of *perfect elimination ordering* of 117 | vertices of the graph. 118 | 119 | A *clique* of a graph is a fully-connected subset of vertices; that is, every 120 | pair of vertices in the clique share an edge. A *simplicial* vertex is one 121 | whose neighborhood induces a clique. A perfect elimination ordering is an 122 | ordering of vertices :math:`1..n` such that any vertex :math:`i` is simplicial 123 | for the subset of vertices :math:`i..n`. 124 | 125 | .. automodule:: dwave_networkx.algorithms.elimination_ordering 126 | 127 | .. autosummary:: 128 | :toctree: generated/ 129 | 130 | chimera_elimination_order 131 | elimination_order_width 132 | is_almost_simplicial 133 | is_simplicial 134 | max_cardinality_heuristic 135 | minor_min_width 136 | min_fill_heuristic 137 | min_width_heuristic 138 | pegasus_elimination_order 139 | treewidth_branch_and_bound 140 | 141 | .. _dnx_markov: 142 | 143 | Markov Networks 144 | =============== 145 | 146 | .. automodule:: dwave_networkx.algorithms.markov 147 | 148 | .. autosummary:: 149 | :toctree: generated/ 150 | 151 | sample_markov_network 152 | markov_network_bqm 153 | 154 | .. _dnx_matching: 155 | 156 | Matching 157 | ======== 158 | 159 | A matching is a subset of graph edges in which no vertex occurs more than once. 160 | 161 | .. figure:: _images/Match.png 162 | :name: Matching 163 | :alt: image 164 | :align: center 165 | :scale: 40 % 166 | 167 | A matching for a Chimera unit cell: no vertex is incident to more than one 168 | edge in the set of blue edges 169 | 170 | .. automodule:: dwave_networkx.algorithms.matching 171 | 172 | .. autosummary:: 173 | :toctree: generated/ 174 | 175 | matching_bqm 176 | maximal_matching_bqm 177 | min_maximal_matching_bqm 178 | min_maximal_matching 179 | 180 | .. _dnx_maximum_cut: 181 | 182 | Maximum Cut 183 | =========== 184 | 185 | A maximum cut is a subset of a graph's vertices such that the number of edges 186 | between this subset and the remaining vertices is as large as possible. 187 | 188 | .. figure:: _images/MaxCut.png 189 | :name: Cut 190 | :alt: image 191 | :align: center 192 | :scale: 60 % 193 | 194 | Maximum cut for a Chimera unit cell: the blue line around the subset of 195 | nodes {4, 5, 6, 7} cuts 16 edges; adding or removing a node decreases the 196 | number of edges between the two complementary subsets of the graph. 197 | 198 | .. automodule:: dwave_networkx.algorithms.max_cut 199 | 200 | .. autosummary:: 201 | :toctree: generated/ 202 | 203 | maximum_cut 204 | weighted_maximum_cut 205 | 206 | .. _dnx_independent_set: 207 | 208 | Independent Set 209 | =============== 210 | 211 | An independent set is a set of a graph's vertices with no edge connecting any of 212 | its member pairs. 213 | 214 | .. figure:: _images/Cover.png 215 | :name: CoverIndependentSet 216 | :alt: image 217 | :align: center 218 | :scale: 40 % 219 | 220 | Independent sets for a Chimera unit cell: the nodes of both the blue set of 221 | vertices (the horizontal tile of the Chimera unit cell) and the red set 222 | (vertical tile) are independent sets of the graph, with no blue node 223 | adjacent to another blue node and likewise for red nodes. 224 | 225 | .. automodule:: dwave_networkx.algorithms.independent_set 226 | 227 | .. autosummary:: 228 | :toctree: generated/ 229 | 230 | maximum_weighted_independent_set 231 | maximum_independent_set 232 | is_independent_set 233 | 234 | Helper Functions 235 | ---------------- 236 | 237 | .. autosummary:: 238 | :toctree: generated/ 239 | 240 | maximum_weighted_independent_set_qubo 241 | 242 | .. _dnx_partitioning: 243 | 244 | Partitioning 245 | ============ 246 | 247 | A `k`-partition consists of `k` disjoint and equally sized subsets of a graph's 248 | vertices such that the total number of edges between nodes in distinct subsets 249 | is as small as possible. 250 | 251 | .. figure:: _images/Partitioning.png 252 | :name: Partition 253 | :alt: image 254 | :align: center 255 | :scale: 60 % 256 | 257 | A 2-partition for a simple graph: the nodes in blue are in the '0' subset, 258 | and the nodes in red are in the '1' subset. There are no other arrangements 259 | with fewer edges between two equally sized subsets. 260 | 261 | .. automodule:: dwave_networkx.algorithms.partition 262 | 263 | .. autosummary:: 264 | :toctree: generated/ 265 | 266 | partition 267 | 268 | .. _dnx_social: 269 | 270 | Social 271 | ====== 272 | 273 | A signed social network graph is a graph whose signed edges represent 274 | friendly/hostile interactions between vertices. 275 | 276 | .. figure:: _images/Social.png 277 | :name: Social 278 | :alt: image 279 | :align: center 280 | :scale: 80 % 281 | 282 | A signed social graph for three nodes, where Eve and Bob are friendly with 283 | each other and hostile to Alice. This network is balanced because it can be 284 | cleanly divided into two subsets, {Bob, Eve} and {Alice}, with friendly 285 | relations within each subset and only hostile relations between the subsets. 286 | 287 | .. automodule:: dwave_networkx.algorithms.social 288 | 289 | .. autosummary:: 290 | :toctree: generated/ 291 | 292 | structural_imbalance 293 | structural_imbalance_ising 294 | 295 | .. _dnx_traveling_salesperson: 296 | 297 | Traveling Salesperson 298 | ===================== 299 | 300 | A traveling salesperson route is an ordering of the vertices in a complete 301 | weighted graph. 302 | 303 | .. figure:: _images/tsp.png 304 | :name: TSP_route 305 | :alt: image 306 | :align: center 307 | :scale: 80 % 308 | 309 | A traveling salesperson route of [2, 1, 0, 3]. 310 | 311 | .. automodule:: dwave_networkx.algorithms.tsp 312 | 313 | .. autosummary:: 314 | :toctree: generated/ 315 | 316 | traveling_salesperson 317 | traveling_salesperson_qubo -------------------------------------------------------------------------------- /docs/api_ref.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_api_ref: 2 | 3 | ============= 4 | API Reference 5 | ============= 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | algorithms 11 | drawing 12 | generators 13 | utilities 14 | default_sampler -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # DWaveNetworkX documentation build configuration file, created by 2 | # sphinx-quickstart on Wed Jul 26 10:55:26 2017. 3 | # 4 | # This file only contains a selection of the most common options. For a full 5 | # list see the documentation: 6 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 7 | 8 | import os 9 | import sys 10 | sys.path.insert(0, os.path.abspath('.')) 11 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 12 | 13 | # -- General configuration ------------------------------------------------ 14 | 15 | extensions = [ 16 | 'sphinx.ext.autosummary', 17 | 'sphinx.ext.autodoc', 18 | 'sphinx.ext.coverage', 19 | 'sphinx.ext.doctest', 20 | 'sphinx.ext.intersphinx', 21 | 'sphinx.ext.mathjax', 22 | 'sphinx.ext.napoleon', 23 | 'sphinx.ext.todo', 24 | 'sphinx.ext.viewcode', 25 | 'sphinx.ext.ifconfig', 26 | ] 27 | 28 | autosummary_generate = True 29 | 30 | source_suffix = ['.rst'] 31 | 32 | master_doc = 'index' 33 | 34 | # General information about the project. 35 | project = u'D-Wave NetworkX' 36 | copyright = u'2017, D-Wave Systems Inc' 37 | author = u'D-Wave Systems Inc' 38 | 39 | import dwave_networkx 40 | version = dwave_networkx.__version__ 41 | release = dwave_networkx.__version__ 42 | 43 | language = 'en' 44 | 45 | add_module_names = False 46 | 47 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'sdk_index.rst'] 48 | 49 | linkcheck_retries = 2 50 | linkcheck_anchors = False 51 | linkcheck_ignore = [r'https://scipy.org', # Looks like it rejects robots 52 | r'https://cloud.dwavesys.com/leap', # redirects, many checks 53 | ] 54 | 55 | pygments_style = 'sphinx' 56 | 57 | todo_include_todos = True 58 | 59 | modindex_common_prefix = ['dwave_networkx.'] 60 | 61 | doctest_global_setup = \ 62 | """ 63 | import dwave_networkx as dnx 64 | import networkx as nx 65 | """ 66 | 67 | # -- Options for HTML output ---------------------------------------------- 68 | 69 | html_theme = 'pydata_sphinx_theme' 70 | html_theme_options = { 71 | "collapse_navigation": True, 72 | "show_prev_next": False, 73 | } 74 | html_sidebars = {"**": ["search-field", "sidebar-nav-bs"]} # remove ads 75 | 76 | intersphinx_mapping = {'python': ('https://docs.python.org/3', None), 77 | 'networkx': ('https://networkx.github.io/documentation/stable/', None), 78 | 'dwave': ('https://docs.dwavequantum.com/en/latest/', None), 79 | } 80 | 81 | # global substitutions 82 | rst_epilog = """ 83 | .. |dwave_short| unicode:: D-Wave 84 | """ -------------------------------------------------------------------------------- /docs/default_sampler.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_default_sampler: 2 | 3 | =============== 4 | Default sampler 5 | =============== 6 | 7 | .. currentmodule:: dwave_networkx.default_sampler 8 | 9 | .. automodule:: dwave_networkx.default_sampler 10 | 11 | Functions 12 | ========= 13 | 14 | .. autosummary:: 15 | :toctree: generated/ 16 | 17 | set_default_sampler 18 | unset_default_sampler 19 | get_default_sampler 20 | -------------------------------------------------------------------------------- /docs/drawing.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_drawing: 2 | 3 | ======= 4 | Drawing 5 | ======= 6 | 7 | Tools to visualize topologies of D-Wave QPUs and weighted :term:`graph` problems 8 | on them. 9 | 10 | .. currentmodule:: dwave_networkx 11 | 12 | Chimera Graph Functions 13 | ======================= 14 | 15 | .. automodule:: dwave_networkx.drawing.chimera_layout 16 | 17 | .. autosummary:: 18 | :toctree: generated/ 19 | 20 | chimera_layout 21 | chimera_node_placer_2d 22 | draw_chimera 23 | draw_chimera_embedding 24 | draw_chimera_yield 25 | 26 | Example 27 | ------- 28 | 29 | This example uses the :func:`.chimera_layout()` function to show the positions 30 | of nodes of a simple 5-node NetworkX graph in a Chimera lattice. It then uses the 31 | :func:`.chimera_graph()` and :func:`.draw_chimera()` functions to display those 32 | positions on a Chimera unit cell. 33 | 34 | .. code-block:: python 35 | 36 | >>> import networkx as nx 37 | >>> import dwave_networkx as dnx 38 | >>> import matplotlib.pyplot as plt 39 | >>> H = nx.Graph() 40 | >>> H.add_nodes_from([0, 4, 5, 6, 7]) 41 | >>> H.add_edges_from([(0, 4), (0, 5), (0, 6), (0, 7)]) 42 | >>> pos=dnx.chimera_layout(H) 43 | >>> pos 44 | {0: array([ 0. , -0.5]), 45 | 4: array([ 0.5, 0. ]), 46 | 5: array([ 0.5 , -0.25]), 47 | 6: array([ 0.5 , -0.75]), 48 | 7: array([ 0.5, -1. ])} 49 | >>> # Show graph H on a Chimera unit cell 50 | >>> f, axes = plt.subplots(1, 1) 51 | >>> G=dnx.chimera_graph(1, 1, 4) # Draw a Chimera unit cell 52 | >>> dnx.draw_chimera(G, node_color='r', ax=axes) 53 | >>> dnx.draw_chimera(H, node_color='b', node_shape='*', style='dashed', edge_color='b', width=3, ax=axes) 54 | >>> plt.show() 55 | >>> # matplotlib commands to add labels to graphic not shown 56 | 57 | .. figure:: _images/chimera_layout_0-rightside.png 58 | :align: center 59 | :name: chimera_layout_0-rightside 60 | :scale: 60 % 61 | :alt: Graph H overlaid on a Chimera unit cell. 62 | 63 | Graph ``H`` (blue) overlaid on a Chimera unit cell (red nodes and black edges), 64 | which is rendered in a cross layout. 65 | 66 | 67 | Pegasus Graph Functions 68 | ======================= 69 | 70 | .. automodule:: dwave_networkx.drawing.pegasus_layout 71 | 72 | .. autosummary:: 73 | :toctree: generated/ 74 | 75 | draw_pegasus 76 | draw_pegasus_embedding 77 | draw_pegasus_yield 78 | pegasus_layout 79 | pegasus_node_placer_2d 80 | 81 | Example 82 | ------- 83 | 84 | This example uses the :func:`.draw_pegasus()` function to show the positions 85 | of nodes of a simple 5-node graph on a small Pegasus lattice. 86 | 87 | .. code-block:: python 88 | 89 | >>> import dwave_networkx as dnx 90 | >>> import matplotlib.pyplot as plt 91 | >>> G = dnx.pegasus_graph(2) 92 | >>> H = dnx.pegasus_graph(2, node_list=[4, 40, 41, 42, 43], 93 | edge_list=[(4, 40), (4, 41), (4, 42), (4, 43)]) 94 | >>> # Show graph H on a small Pegasus lattice 95 | >>> f, axes = plt.subplots(1, 1) 96 | >>> dnx.draw_pegasus(G, with_labels=True, crosses=True, node_color="Yellow", ax=axes) 97 | >>> dnx.draw_pegasus(H, crosses=True, node_color='b', style='dashed', 98 | edge_color='b', width=3, ax=axes) 99 | >>> plt.show() 100 | 101 | .. figure:: _images/pegasus_layout_h_on_g.png 102 | :align: center 103 | :name: pegasus_layout_h_on_g.png 104 | :scale: 60 % 105 | :alt: Graph ``H`` overlaid on a Pegasus lattice size 2. 106 | 107 | Graph ``H`` (blue) overlaid on a small Pegasus lattice (yellow nodes and black edges), 108 | which is rendered in a cross layout. 109 | 110 | Zephyr Graph Functions 111 | ====================== 112 | 113 | .. automodule:: dwave_networkx.drawing.zephyr_layout 114 | 115 | .. autosummary:: 116 | :toctree: generated/ 117 | 118 | draw_zephyr 119 | draw_zephyr_embedding 120 | draw_zephyr_yield 121 | zephyr_layout 122 | zephyr_node_placer_2d 123 | 124 | Example 125 | ------- 126 | 127 | This example uses the :func:`.draw_zephyr_embedding` function to show the positions 128 | of a five-node clique on a small Zephyr graph. 129 | 130 | .. code-block:: python 131 | 132 | >>> import dwave_networkx as dnx 133 | >>> import matplotlib.pyplot as plt 134 | >>> import networkx as nx 135 | ... 136 | >>> G = dnx.zephyr_graph(1) 137 | >>> embedding = {"N1": [13, 44], "N2": [11], "N3": [41], "N4": [40], "N5": [9, 37]} 138 | ... 139 | >>> plt.ion() 140 | >>> dnx.draw_zephyr_embedding(G, embedding, show_labels=True) 141 | 142 | .. figure:: _images/zephyr_embedding_5clique.png 143 | :align: center 144 | :name: zephyr_embedding_5clique.png 145 | :scale: 60 % 146 | :alt: Five-node clique embedded in a small Zephyr graph. 147 | 148 | Five-node clique embedded in a small Zephyr graph. 149 | -------------------------------------------------------------------------------- /docs/generators.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_generators: 2 | 3 | ================ 4 | Graph Generators 5 | ================ 6 | 7 | Generators for graphs, such the graphs (topologies) of D-Wave System QPUs. 8 | 9 | .. currentmodule:: dwave_networkx 10 | 11 | D-Wave Systems 12 | ============== 13 | 14 | .. autosummary:: 15 | :toctree: generated/ 16 | 17 | chimera_graph 18 | pegasus_graph 19 | zephyr_graph 20 | 21 | Example 22 | ------- 23 | 24 | This example uses the the `chimera_graph()` function to create a Chimera lattice 25 | of size (1, 1, 4), which is a single unit cell in Chimera topology, and 26 | the `find_chimera()` function to determine the Chimera indices. 27 | 28 | .. code-block:: python 29 | 30 | >>> import networkx as nx 31 | >>> import dwave_networkx as dnx 32 | >>> G = dnx.chimera_graph(1, 1, 4) 33 | >>> chimera_indices = dnx.find_chimera_indices(G) 34 | >>> print chimera_indices 35 | {0: (0, 0, 0, 0), 36 | 1: (0, 0, 0, 1), 37 | 2: (0, 0, 0, 2), 38 | 3: (0, 0, 0, 3), 39 | 4: (0, 0, 1, 0), 40 | 5: (0, 0, 1, 1), 41 | 6: (0, 0, 1, 2), 42 | 7: (0, 0, 1, 3)} 43 | 44 | .. figure:: _images/find_chimera-unitcell.png 45 | :align: center 46 | :name: find_chimera-unitcell 47 | :scale: 30 % 48 | :alt: Indices of a Chimera unit cell. 49 | 50 | Indices of a Chimera unit cell found by creating a lattice of size (1, 1, 4). 51 | 52 | Toruses 53 | ======= 54 | 55 | .. autosummary:: 56 | :toctree: generated/ 57 | 58 | chimera_torus 59 | pegasus_torus 60 | zephyr_torus 61 | 62 | Other Graphs 63 | ============ 64 | 65 | .. autosummary:: 66 | :toctree: generated/ 67 | 68 | markov_network 69 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index_dnx: 2 | 3 | ============== 4 | dwave-networkx 5 | ============== 6 | 7 | 8 | .. toctree:: 9 | :caption: Reference documentation for dwave-networkx: 10 | :maxdepth: 1 11 | 12 | api_ref 13 | 14 | 15 | About dwave-networkx 16 | ==================== 17 | 18 | .. include:: README.rst 19 | :start-after: start_dnx_about 20 | :end-before: end_dnx_about 21 | 22 | Functions such as :func:`.draw_pegasus` provide easy visualization for 23 | :term:`Pegasus` graphs while functions such as 24 | :func:`~dwave_networkx.algorithms.max_cut.maximum_cut` or 25 | :func:`~dwave_networkx.algorithms.cover.min_vertex_cover` provide graph 26 | algorithms useful to optimization problems that fit well with |dwave_short| 27 | quantum computers. 28 | 29 | Like |dwave_short| quantum computers, all other supported samplers must have 30 | ``sample_qubo`` and ``sample_ising`` methods for solving :term:`Ising` and 31 | :term:`QUBO` models and return an iterable of samples in order of increasing 32 | energy. You can set a default sampler using the 33 | :func:`~dwave_networkx.default_sampler.set_default_sampler` function. 34 | 35 | Usage Information 36 | ================= 37 | 38 | * :ref:`index_concepts` for terminology 39 | * :ref:`qpu_topologies` for an introduction to :term:`QPU` 40 | :term:`topologies ` such as the :term:`Pegasus` graph 41 | * :ref:`concept_models_bqm` for an introduction to binary quadratic models 42 | (:term:`BQM`) 43 | * :ref:`concept_samplers` for an introduction to :term:`samplers ` -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | pydata-sphinx-theme==0.14.3 2 | sphinx==7.3.7 -------------------------------------------------------------------------------- /docs/utilities.rst: -------------------------------------------------------------------------------- 1 | .. _dnx_utilities: 2 | 3 | ========= 4 | Utilities 5 | ========= 6 | 7 | .. automodule:: dwave_networkx.utils 8 | .. currentmodule:: dwave_networkx.utils 9 | 10 | Decorators 11 | ========== 12 | 13 | .. automodule:: dwave_networkx.utils.decorators 14 | .. autosummary:: 15 | :toctree: generated/ 16 | 17 | binary_quadratic_model_sampler 18 | 19 | .. currentmodule:: dwave_networkx 20 | 21 | Graph Indexing 22 | ============== 23 | 24 | See the :ref:`dnx_coordinates_conversion` subsection on instantiating the needed 25 | lattice size and setting correct domain and range for coordinates in a QPU 26 | working graph. For the iterator versions of these functions, see the code. 27 | 28 | Chimera 29 | ------- 30 | 31 | .. autosummary:: 32 | :toctree: generated/ 33 | 34 | chimera_coordinates.chimera_to_linear 35 | chimera_coordinates.graph_to_chimera 36 | chimera_coordinates.graph_to_linear 37 | chimera_coordinates.linear_to_chimera 38 | chimera_sublattice_mappings 39 | find_chimera_indices 40 | 41 | Pegasus 42 | ------- 43 | 44 | .. autosummary:: 45 | :toctree: generated/ 46 | 47 | pegasus_coordinates.graph_to_linear 48 | pegasus_coordinates.graph_to_nice 49 | pegasus_coordinates.graph_to_pegasus 50 | pegasus_coordinates.linear_to_nice 51 | pegasus_coordinates.linear_to_pegasus 52 | pegasus_coordinates.nice_to_linear 53 | pegasus_coordinates.nice_to_pegasus 54 | pegasus_coordinates.pegasus_to_linear 55 | pegasus_coordinates.pegasus_to_nice 56 | pegasus_sublattice_mappings 57 | 58 | 59 | Zephyr 60 | ------ 61 | 62 | .. autosummary:: 63 | :toctree: generated/ 64 | 65 | zephyr_coordinates.graph_to_linear 66 | zephyr_coordinates.graph_to_zephyr 67 | zephyr_coordinates.linear_to_zephyr 68 | zephyr_coordinates.zephyr_to_linear 69 | zephyr_sublattice_mappings 70 | 71 | 72 | .. _dnx_coordinates_conversion: 73 | 74 | Coordinates Conversion 75 | ---------------------- 76 | 77 | .. automodule:: dwave_networkx 78 | 79 | .. autoclass:: chimera_coordinates 80 | 81 | .. autoclass:: pegasus_coordinates 82 | 83 | .. autoclass:: zephyr_coordinates 84 | 85 | 86 | 87 | Exceptions 88 | ========== 89 | 90 | .. automodule:: dwave_networkx.exceptions 91 | .. autosummary:: 92 | :toctree: generated/ 93 | 94 | DWaveNetworkXException 95 | DWaveNetworkXMissingSampler 96 | -------------------------------------------------------------------------------- /dwave_networkx/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 dwave_networkx.generators 16 | from dwave_networkx.generators import * 17 | 18 | import dwave_networkx.algorithms 19 | from dwave_networkx.algorithms import * 20 | 21 | import dwave_networkx.utils 22 | from dwave_networkx.exceptions import * 23 | 24 | import dwave_networkx.default_sampler 25 | from dwave_networkx.default_sampler import * 26 | 27 | import dwave_networkx.drawing 28 | from dwave_networkx.drawing import * 29 | 30 | from dwave_networkx.package_info import __version__, __author__, \ 31 | __authoremail__, __description__ 32 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.algorithms.independent_set import * 16 | from dwave_networkx.algorithms.canonicalization import * 17 | from dwave_networkx.algorithms.clique import * 18 | from dwave_networkx.algorithms.cover import * 19 | from dwave_networkx.algorithms.matching import * 20 | from dwave_networkx.algorithms.social import * 21 | from dwave_networkx.algorithms.elimination_ordering import * 22 | from dwave_networkx.algorithms.coloring import * 23 | from dwave_networkx.algorithms.max_cut import * 24 | from dwave_networkx.algorithms.markov import * 25 | from dwave_networkx.algorithms.tsp import * 26 | from dwave_networkx.algorithms.partition import * 27 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/canonicalization.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 math 16 | 17 | from dwave_networkx.generators.chimera import chimera_coordinates 18 | 19 | __all__ = ['canonical_chimera_labeling'] 20 | 21 | 22 | def canonical_chimera_labeling(G, t=None): 23 | """Returns a mapping from the labels of G to chimera-indexed labeling. 24 | 25 | Parameters 26 | ---------- 27 | G : NetworkX graph 28 | A Chimera-structured graph. 29 | t : int (optional, default 4) 30 | Size of the shore within each Chimera tile. 31 | 32 | Returns 33 | ------- 34 | chimera_indices: dict 35 | A mapping from the current labels to a 4-tuple of Chimera indices. 36 | 37 | """ 38 | adj = G.adj 39 | 40 | if t is None: 41 | if hasattr(G, 'edges'): 42 | num_edges = len(G.edges) 43 | else: 44 | num_edges = len(G.quadratic) 45 | t = _chimera_shore_size(adj, num_edges) 46 | 47 | chimera_indices = {} 48 | 49 | row = col = 0 50 | 51 | # need to find a node in a corner 52 | root_edge = min(((u, v) for u in adj for v in adj[u]), 53 | key=lambda edge: len(adj[edge[0]]) + len(adj[edge[1]])) 54 | root, _ = root_edge 55 | 56 | horiz, verti = rooted_tile(adj, root, t) 57 | while len(chimera_indices) < len(adj): 58 | 59 | new_indices = {} 60 | 61 | if row == 0: 62 | # if we're in the 0th row, we can assign the horizontal randomly 63 | for si, v in enumerate(horiz): 64 | new_indices[v] = (row, col, 0, si) 65 | else: 66 | # we need to match the row above 67 | for v in horiz: 68 | north = [u for u in adj[v] if u in chimera_indices] 69 | assert len(north) == 1 70 | i, j, u, si = chimera_indices[north[0]] 71 | assert i == row - 1 and j == col and u == 0 72 | new_indices[v] = (row, col, 0, si) 73 | 74 | if col == 0: 75 | # if we're in the 0th col, we can assign the vertical randomly 76 | for si, v in enumerate(verti): 77 | new_indices[v] = (row, col, 1, si) 78 | else: 79 | # we need to match the column to the east 80 | for v in verti: 81 | east = [u for u in adj[v] if u in chimera_indices] 82 | assert len(east) == 1 83 | i, j, u, si = chimera_indices[east[0]] 84 | assert i == row and j == col - 1 and u == 1 85 | new_indices[v] = (row, col, 1, si) 86 | 87 | chimera_indices.update(new_indices) 88 | 89 | # get the next root 90 | root_neighbours = [v for v in adj[root] if v not in chimera_indices] 91 | if len(root_neighbours) == 1: 92 | # we can increment the row 93 | root = root_neighbours[0] 94 | horiz, verti = rooted_tile(adj, root, t) 95 | 96 | row += 1 97 | else: 98 | # need to go back to row 0, and increment the column 99 | assert not root_neighbours # should be empty 100 | 101 | # we want (0, col, 1, 0), we could cache this, but for now let's just go look for it 102 | # the slow way 103 | vert_root = [v for v in chimera_indices if chimera_indices[v] == (0, col, 1, 0)][0] 104 | 105 | vert_root_neighbours = [v for v in adj[vert_root] if v not in chimera_indices] 106 | 107 | if vert_root_neighbours: 108 | 109 | verti, horiz = rooted_tile(adj, vert_root_neighbours[0], t) 110 | root = next(iter(horiz)) 111 | 112 | row = 0 113 | col += 1 114 | 115 | return chimera_indices 116 | 117 | 118 | def rooted_tile(adj, n, t): 119 | horiz = {n} 120 | vert = set() 121 | 122 | # get all of the nodes that are two steps away from n 123 | two_steps = {v for u in adj[n] for v in adj[u] if v != n} 124 | 125 | # find the subset of two_steps that share exactly t neighbours 126 | for v in two_steps: 127 | shared = set(adj[n]).intersection(adj[v]) 128 | 129 | if len(shared) == t: 130 | assert v not in horiz 131 | horiz.add(v) 132 | vert |= shared 133 | 134 | assert len(vert) == t 135 | return horiz, vert 136 | 137 | 138 | def _chimera_shore_size(adj, num_edges): 139 | # we know |E| = m*n*t*t + (2*m*n-m-n)*t 140 | 141 | num_nodes = len(adj) 142 | 143 | max_degree = max(len(adj[v]) for v in adj) 144 | 145 | if num_nodes == 2 * max_degree: 146 | return max_degree 147 | 148 | def a(t): 149 | return -2*t 150 | 151 | def b(t): 152 | return (t + 2) * num_nodes - 2 * num_edges 153 | 154 | def c(t): 155 | return -num_nodes 156 | 157 | t = max_degree - 1 158 | m = (-b(t) + math.sqrt(b(t)**2 - 4*a(t)*c(t))) / (2 * a(t)) 159 | 160 | if m.is_integer(): 161 | return t 162 | 163 | return max_degree - 2 164 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/clique.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 networkx as nx 16 | import dwave_networkx as dnx 17 | 18 | from dwave_networkx.utils import binary_quadratic_model_sampler 19 | 20 | __all__ = ["maximum_clique", "clique_number", "is_clique"] 21 | 22 | @binary_quadratic_model_sampler(1) 23 | def maximum_clique(G, sampler=None, lagrange=2.0, **sampler_args): 24 | r"""Returns an approximate maximum clique. 25 | 26 | A clique in an undirected graph, G = (V, E), is a subset of the vertex set 27 | :math:`C \subseteq V` such that for every two vertices in C there exists an edge 28 | connecting the two. This is equivalent to saying that the subgraph 29 | induced by C is complete (in some cases, the term clique may also refer 30 | to the subgraph). A maximum clique is a clique of the largest 31 | possible size in a given graph. 32 | 33 | This function works by finding the maximum independent set of the compliment 34 | graph of the given graph G which is equivalent to finding maximum clique. 35 | It defines a QUBO with ground states corresponding 36 | to a maximum weighted independent set and uses the sampler to sample from it. 37 | 38 | Parameters 39 | ---------- 40 | G : NetworkX graph 41 | The graph on which to find a maximum clique. 42 | 43 | sampler 44 | A binary quadratic model sampler. A sampler is a process that 45 | samples from low energy states in models defined by an Ising 46 | equation or a Quadratic Unconstrained Binary Optimization 47 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 48 | and 'sample_ising' method. A sampler is expected to return an 49 | iterable of samples, in order of increasing energy. If no 50 | sampler is provided, one must be provided using the 51 | `set_default_sampler` function. 52 | 53 | lagrange : optional (default 2) 54 | Lagrange parameter to weight constraints (no edges within set) 55 | versus objective (largest set possible). 56 | 57 | sampler_args 58 | Additional keyword parameters are passed to the sampler. 59 | 60 | Returns 61 | ------- 62 | clique_nodes : list 63 | List of nodes that form a maximum clique, as 64 | determined by the given sampler. 65 | 66 | Notes 67 | ----- 68 | Samplers by their nature may not return the optimal solution. This 69 | function does not attempt to confirm the quality of the returned 70 | sample. 71 | 72 | References 73 | ---------- 74 | 75 | `Maximum Clique on Wikipedia `_ 76 | 77 | `Independent Set on Wikipedia `_ 78 | 79 | `QUBO on Wikipedia `_ 80 | 81 | Lucas, A. (2014). Ising formulations of many NP problems. 82 | Frontiers in Physics, Volume 2, Article 5. 83 | """ 84 | if G is None: 85 | raise ValueError("Expected NetworkX graph!") 86 | 87 | # finding the maximum clique in a graph is equivalent to finding 88 | # the independent set in the complementary graph 89 | complement_G = nx.complement(G) 90 | return dnx.maximum_independent_set(complement_G, sampler, lagrange, **sampler_args) 91 | 92 | 93 | @binary_quadratic_model_sampler(1) 94 | def clique_number(G, sampler=None, lagrange=2.0, **sampler_args): 95 | r"""Returns the number of vertices in the maximum clique of a graph. 96 | 97 | A maximum clique is a clique of the largest possible size in a given graph. 98 | The clique number math:`\omega(G)` of a graph G is the number of 99 | vertices in a maximum clique in G. The intersection number of 100 | G is the smallest number of cliques that together cover all edges of G. 101 | 102 | This function works by finding the maximum independent set of the compliment 103 | graph of the given graph G which is equivalent to finding maximum clique. 104 | It defines a QUBO with ground states corresponding 105 | to a maximum weighted independent set and uses the sampler to sample from it. 106 | 107 | Parameters 108 | ---------- 109 | G : NetworkX graph 110 | The graph on which to find a maximum clique. 111 | 112 | sampler 113 | A binary quadratic model sampler. A sampler is a process that 114 | samples from low energy states in models defined by an Ising 115 | equation or a Quadratic Unconstrained Binary Optimization 116 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 117 | and 'sample_ising' method. A sampler is expected to return an 118 | iterable of samples, in order of increasing energy. If no 119 | sampler is provided, one must be provided using the 120 | `set_default_sampler` function. 121 | 122 | lagrange : optional (default 2) 123 | Lagrange parameter to weight constraints (no edges within set) 124 | versus objective (largest set possible). 125 | 126 | sampler_args 127 | Additional keyword parameters are passed to the sampler. 128 | 129 | Returns 130 | ------- 131 | clique_nodes : list 132 | List of nodes that form a maximum clique, as 133 | determined by the given sampler. 134 | 135 | Notes 136 | ----- 137 | Samplers by their nature may not return the optimal solution. This 138 | function does not attempt to confirm the quality of the returned 139 | sample. 140 | 141 | References 142 | ---------- 143 | 144 | `Maximum Clique on Wikipedia `_ 145 | """ 146 | return len(maximum_clique(G, sampler, lagrange, **sampler_args)) 147 | 148 | def is_clique(G, clique_nodes): 149 | """Determines whether the given nodes form a clique. 150 | 151 | A clique is a subset of nodes of an undirected graph such that every two 152 | distinct nodes in the clique are adjacent. 153 | 154 | Parameters 155 | ---------- 156 | G : NetworkX graph 157 | The graph on which to check the clique nodes. 158 | 159 | clique_nodes : list 160 | List of nodes that form a clique, as 161 | determined by the given sampler. 162 | 163 | Returns 164 | ------- 165 | is_clique : bool 166 | True if clique_nodes forms a clique. 167 | 168 | Example 169 | ------- 170 | This example checks two sets of nodes, both derived from a 171 | single Chimera unit cell, for an independent set. The first set is 172 | the horizontal tile's nodes; the second has nodes from the horizontal and 173 | verical tiles. 174 | 175 | >>> import dwave_networkx as dnx 176 | >>> G = dnx.chimera_graph(1, 1, 4) 177 | >>> dnx.is_clique(G, [0, 1, 2, 3]) 178 | False 179 | >>> dnx.is_clique(G, [0, 4]) 180 | True 181 | """ 182 | for x in clique_nodes: 183 | for y in clique_nodes: 184 | if x != y: 185 | if not(G.has_edge(x,y)): 186 | return False 187 | return True 188 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/cover.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.algorithms.independent_set import maximum_weighted_independent_set 16 | from dwave_networkx.utils import binary_quadratic_model_sampler 17 | 18 | __all__ = ['min_weighted_vertex_cover', 'min_vertex_cover', 'is_vertex_cover'] 19 | 20 | 21 | @binary_quadratic_model_sampler(2) 22 | def min_weighted_vertex_cover(G, weight=None, sampler=None, lagrange=2.0, **sampler_args): 23 | """Returns an approximate minimum weighted vertex cover. 24 | 25 | Defines a QUBO with ground states corresponding to a minimum weighted 26 | vertex cover and uses the sampler to sample from it. 27 | 28 | A vertex cover is a set of vertices such that each edge of the graph 29 | is incident with at least one vertex in the set. A minimum weighted 30 | vertex cover is the vertex cover of minimum total node weight. 31 | 32 | Parameters 33 | ---------- 34 | G : NetworkX graph 35 | 36 | weight : string, optional (default None) 37 | If None, every node has equal weight. If a string, use this node 38 | attribute as the node weight. A node without this attribute is 39 | assumed to have max weight. 40 | 41 | sampler 42 | A binary quadratic model sampler. A sampler is a process that 43 | samples from low energy states in models defined by an Ising 44 | equation or a Quadratic Unconstrained Binary Optimization 45 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 46 | and 'sample_ising' method. A sampler is expected to return an 47 | iterable of samples, in order of increasing energy. If no 48 | sampler is provided, one must be provided using the 49 | `set_default_sampler` function. 50 | 51 | lagrange : optional (default 2) 52 | Lagrange parameter to weight constraints versus objective. 53 | 54 | sampler_args 55 | Additional keyword parameters are passed to the sampler. 56 | 57 | Returns 58 | ------- 59 | vertex_cover : list 60 | List of nodes that the form a the minimum weighted vertex cover, as 61 | determined by the given sampler. 62 | 63 | Notes 64 | ----- 65 | Samplers by their nature may not return the optimal solution. This 66 | function does not attempt to confirm the quality of the returned 67 | sample. 68 | 69 | https://en.wikipedia.org/wiki/Vertex_cover 70 | 71 | https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization 72 | 73 | References 74 | ---------- 75 | Based on the formulation presented in [Luc2014]_. 76 | 77 | """ 78 | indep_nodes = set(maximum_weighted_independent_set(G, weight, sampler, lagrange, **sampler_args)) 79 | return [v for v in G if v not in indep_nodes] 80 | 81 | 82 | @binary_quadratic_model_sampler(1) 83 | def min_vertex_cover(G, sampler=None, lagrange=2.0, **sampler_args): 84 | """Returns an approximate minimum vertex cover. 85 | 86 | Defines a QUBO with ground states corresponding to a minimum 87 | vertex cover and uses the sampler to sample from it. 88 | 89 | A vertex cover is a set of vertices such that each edge of the graph 90 | is incident with at least one vertex in the set. A minimum vertex cover 91 | is the vertex cover of smallest size. 92 | 93 | Parameters 94 | ---------- 95 | G : NetworkX graph 96 | The graph on which to find a minimum vertex cover. 97 | 98 | sampler 99 | A binary quadratic model sampler. A sampler is a process that 100 | samples from low energy states in models defined by an Ising 101 | equation or a Quadratic Unconstrained Binary Optimization 102 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 103 | and 'sample_ising' method. A sampler is expected to return an 104 | iterable of samples, in order of increasing energy. If no 105 | sampler is provided, one must be provided using the 106 | `set_default_sampler` function. 107 | 108 | lagrange : optional (default 2) 109 | Lagrange parameter to weight constraints versus objective. 110 | 111 | sampler_args 112 | Additional keyword parameters are passed to the sampler. 113 | 114 | Returns 115 | ------- 116 | vertex_cover : list 117 | List of nodes that form a minimum vertex cover, as 118 | determined by the given sampler. 119 | 120 | Examples 121 | -------- 122 | This example uses a sampler from 123 | `dimod `_ to find a minimum vertex 124 | cover for a Chimera unit cell. Both the horizontal (vertices 0,1,2,3) and 125 | vertical (vertices 4,5,6,7) tiles connect to all 16 edges, so repeated 126 | executions can return either set. 127 | 128 | >>> import dwave_networkx as dnx 129 | >>> import dimod 130 | >>> sampler = dimod.ExactSolver() # small testing sampler 131 | >>> G = dnx.chimera_graph(1, 1, 4) 132 | >>> G.remove_node(7) # to give a unique solution 133 | >>> dnx.min_vertex_cover(G, sampler, lagrange=2.0) 134 | [4, 5, 6] 135 | 136 | Notes 137 | ----- 138 | Samplers by their nature may not return the optimal solution. This 139 | function does not attempt to confirm the quality of the returned 140 | sample. 141 | 142 | References 143 | ---------- 144 | https://en.wikipedia.org/wiki/Vertex_cover 145 | 146 | https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization 147 | 148 | Lucas, A. (2014). Ising formulations of many NP problems. 149 | Frontiers in Physics, Volume 2, Article 5. 150 | 151 | """ 152 | return min_weighted_vertex_cover(G, None, sampler, lagrange, **sampler_args) 153 | 154 | 155 | def is_vertex_cover(G, vertex_cover): 156 | """Determines whether the given set of vertices is a vertex cover of graph G. 157 | 158 | A vertex cover is a set of vertices such that each edge of the graph 159 | is incident with at least one vertex in the set. 160 | 161 | Parameters 162 | ---------- 163 | G : NetworkX graph 164 | The graph on which to check the vertex cover. 165 | 166 | vertex_cover : 167 | Iterable of nodes. 168 | 169 | Returns 170 | ------- 171 | is_cover : bool 172 | True if the given iterable forms a vertex cover. 173 | 174 | Examples 175 | -------- 176 | This example checks two covers for a graph, G, of a single Chimera 177 | unit cell. The first uses the set of the four horizontal qubits, which 178 | do constitute a cover; the second set removes one node. 179 | 180 | >>> import dwave_networkx as dnx 181 | >>> G = dnx.chimera_graph(1, 1, 4) 182 | >>> cover = [0, 1, 2, 3] 183 | >>> dnx.is_vertex_cover(G,cover) 184 | True 185 | >>> cover = [0, 1, 2] 186 | >>> dnx.is_vertex_cover(G,cover) 187 | False 188 | 189 | """ 190 | cover = set(vertex_cover) 191 | return all(u in cover or v in cover for u, v in G.edges) 192 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/markov.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 | import dimod 16 | 17 | from dwave_networkx.utils import binary_quadratic_model_sampler 18 | 19 | __all__ = ['sample_markov_network', 'markov_network_bqm'] 20 | 21 | 22 | ############################################################################### 23 | # The following code is partially based on https://github.com/tbabej/gibbs 24 | # 25 | # MIT License 26 | # =========== 27 | # 28 | # Copyright 2017 Tomas Babej 29 | # https://github.com/tbabej/gibbs 30 | # 31 | # This software is released under MIT licence. 32 | # 33 | # Permission is hereby granted, free of charge, to any person obtaining 34 | # a copy of this software and associated documentation files (the 35 | # "Software"), to deal in the Software without restriction, including 36 | # without limitation the rights to use, copy, modify, merge, publish, 37 | # distribute, sublicense, and/or sell copies of the Software, and to 38 | # permit persons to whom the Software is furnished to do so, subject to 39 | # the following conditions: 40 | # 41 | # The above copyright notice and this permission notice shall be 42 | # included in all copies or substantial portions of the Software. 43 | # 44 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 45 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 46 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 47 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 48 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 49 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 50 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 51 | # 52 | 53 | 54 | @binary_quadratic_model_sampler(1) 55 | def sample_markov_network(MN, sampler=None, fixed_variables=None, 56 | return_sampleset=False, 57 | **sampler_args): 58 | """Samples from a markov network using the provided sampler. 59 | 60 | Parameters 61 | ---------- 62 | G : NetworkX graph 63 | A Markov Network as returned by :func:`.markov_network` 64 | 65 | sampler 66 | A binary quadratic model sampler. A sampler is a process that 67 | samples from low energy states in models defined by an Ising 68 | equation or a Quadratic Unconstrained Binary Optimization 69 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 70 | and 'sample_ising' method. A sampler is expected to return an 71 | iterable of samples, in order of increasing energy. If no 72 | sampler is provided, one must be provided using the 73 | `set_default_sampler` function. 74 | 75 | fixed_variables : dict 76 | A dictionary of variable assignments to be fixed in the markov network. 77 | 78 | return_sampleset : bool (optional, default=False) 79 | If True, returns a :obj:`dimod.SampleSet` rather than a list of samples. 80 | 81 | **sampler_args 82 | Additional keyword parameters are passed to the sampler. 83 | 84 | Returns 85 | ------- 86 | samples : list[dict]/:obj:`dimod.SampleSet` 87 | A list of samples ordered from low-to-high energy or a sample set. 88 | 89 | Examples 90 | -------- 91 | 92 | >>> import dimod 93 | ... 94 | >>> potentials = {('a', 'b'): {(0, 0): -1, 95 | ... (0, 1): .5, 96 | ... (1, 0): .5, 97 | ... (1, 1): 2}} 98 | >>> MN = dnx.markov_network(potentials) 99 | >>> sampler = dimod.ExactSolver() 100 | >>> samples = dnx.sample_markov_network(MN, sampler) 101 | >>> samples[0] # doctest: +SKIP 102 | {'a': 0, 'b': 0} 103 | 104 | >>> import dimod 105 | ... 106 | >>> potentials = {('a', 'b'): {(0, 0): -1, 107 | ... (0, 1): .5, 108 | ... (1, 0): .5, 109 | ... (1, 1): 2}} 110 | >>> MN = dnx.markov_network(potentials) 111 | >>> sampler = dimod.ExactSolver() 112 | >>> samples = dnx.sample_markov_network(MN, sampler, return_sampleset=True) 113 | >>> samples.first # doctest: +SKIP 114 | Sample(sample={'a': 0, 'b': 0}, energy=-1.0, num_occurrences=1) 115 | 116 | >>> import dimod 117 | ... 118 | >>> potentials = {('a', 'b'): {(0, 0): -1, 119 | ... (0, 1): .5, 120 | ... (1, 0): .5, 121 | ... (1, 1): 2}, 122 | ... ('b', 'c'): {(0, 0): -9, 123 | ... (0, 1): 1.2, 124 | ... (1, 0): 7.2, 125 | ... (1, 1): 5}} 126 | >>> MN = dnx.markov_network(potentials) 127 | >>> sampler = dimod.ExactSolver() 128 | >>> samples = dnx.sample_markov_network(MN, sampler, fixed_variables={'b': 0}) 129 | >>> samples[0] # doctest: +SKIP 130 | {'a': 0, 'c': 0, 'b': 0} 131 | 132 | Notes 133 | ----- 134 | Samplers by their nature may not return the optimal solution. This 135 | function does not attempt to confirm the quality of the returned 136 | sample. 137 | 138 | """ 139 | 140 | bqm = markov_network_bqm(MN) 141 | 142 | if fixed_variables: 143 | # we can modify in-place since we just made it 144 | bqm.fix_variables(fixed_variables) 145 | 146 | sampleset = sampler.sample(bqm, **sampler_args) 147 | 148 | if fixed_variables: 149 | # add the variables back in 150 | sampleset = dimod.append_variables(sampleset, fixed_variables) 151 | 152 | if return_sampleset: 153 | return sampleset 154 | else: 155 | return list(map(dict, sampleset.samples())) 156 | 157 | 158 | def markov_network_bqm(MN): 159 | """Construct a binary quadratic model for a markov network. 160 | 161 | 162 | Parameters 163 | ---------- 164 | G : NetworkX graph 165 | A Markov Network as returned by :func:`.markov_network` 166 | 167 | Returns 168 | ------- 169 | bqm : :obj:`dimod.BinaryQuadraticModel` 170 | A binary quadratic model. 171 | 172 | """ 173 | 174 | bqm = dimod.BinaryQuadraticModel.empty(dimod.BINARY) 175 | 176 | # the variable potentials 177 | for v, ddict in MN.nodes(data=True, default=None): 178 | potential = ddict.get('potential', None) 179 | 180 | if potential is None: 181 | continue 182 | 183 | # for single nodes we don't need to worry about order 184 | 185 | phi0 = potential[(0,)] 186 | phi1 = potential[(1,)] 187 | 188 | bqm.add_variable(v, phi1 - phi0) 189 | bqm.offset += phi0 190 | 191 | # the interaction potentials 192 | for u, v, ddict in MN.edges(data=True, default=None): 193 | potential = ddict.get('potential', None) 194 | 195 | if potential is None: 196 | continue 197 | 198 | # in python<=3.5 the edge order might not be consistent so we use the 199 | # one that was stored 200 | order = ddict['order'] 201 | u, v = order 202 | 203 | phi00 = potential[(0, 0)] 204 | phi01 = potential[(0, 1)] 205 | phi10 = potential[(1, 0)] 206 | phi11 = potential[(1, 1)] 207 | 208 | bqm.add_variable(u, phi10 - phi00) 209 | bqm.add_variable(v, phi01 - phi00) 210 | bqm.add_interaction(u, v, phi11 - phi10 - phi01 + phi00) 211 | bqm.offset += phi00 212 | 213 | return bqm 214 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/max_cut.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.exceptions import DWaveNetworkXException 16 | from dwave_networkx.utils import binary_quadratic_model_sampler 17 | 18 | __all__ = ["maximum_cut", "weighted_maximum_cut"] 19 | 20 | 21 | @binary_quadratic_model_sampler(1) 22 | def maximum_cut(G, sampler=None, **sampler_args): 23 | """Returns an approximate maximum cut. 24 | 25 | Defines an Ising problem with ground states corresponding to 26 | a maximum cut and uses the sampler to sample from it. 27 | 28 | A maximum cut is a subset S of the vertices of G such that 29 | the number of edges between S and the complementary subset 30 | is as large as possible. 31 | 32 | Parameters 33 | ---------- 34 | G : NetworkX graph 35 | The graph on which to find a maximum cut. 36 | 37 | sampler 38 | A binary quadratic model sampler. A sampler is a process that 39 | samples from low energy states in models defined by an Ising 40 | equation or a Quadratic Unconstrained Binary Optimization 41 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 42 | and 'sample_ising' method. A sampler is expected to return an 43 | iterable of samples, in order of increasing energy. If no 44 | sampler is provided, one must be provided using the 45 | `set_default_sampler` function. 46 | 47 | sampler_args 48 | Additional keyword parameters are passed to the sampler. 49 | 50 | Returns 51 | ------- 52 | S : set 53 | A maximum cut of G. 54 | 55 | Example 56 | ------- 57 | This example uses a sampler from 58 | `dimod `_ to find a maximum cut 59 | for a graph of a Chimera unit cell created using the `chimera_graph()` 60 | function. 61 | 62 | >>> import dimod 63 | ... 64 | >>> sampler = dimod.SimulatedAnnealingSampler() 65 | >>> G = dnx.chimera_graph(1, 1, 4) 66 | >>> cut = dnx.maximum_cut(G, sampler) 67 | 68 | Notes 69 | ----- 70 | Samplers by their nature may not return the optimal solution. This 71 | function does not attempt to confirm the quality of the returned 72 | sample. 73 | 74 | """ 75 | # In order to form the Ising problem, we want to increase the 76 | # energy by 1 for each edge between two nodes of the same color. 77 | # The linear biases can all be 0. 78 | h = {v: 0. for v in G} 79 | J = {(u, v): 1 for u, v in G.edges} 80 | 81 | # draw the lowest energy sample from the sampler 82 | response = sampler.sample_ising(h, J, **sampler_args) 83 | sample = next(iter(response)) 84 | 85 | return set(v for v in G if sample[v] >= 0) 86 | 87 | 88 | @binary_quadratic_model_sampler(1) 89 | def weighted_maximum_cut(G, sampler=None, **sampler_args): 90 | """Returns an approximate weighted maximum cut. 91 | 92 | Defines an Ising problem with ground states corresponding to 93 | a weighted maximum cut and uses the sampler to sample from it. 94 | 95 | A weighted maximum cut is a subset S of the vertices of G that 96 | maximizes the sum of the edge weights between S and its 97 | complementary subset. 98 | 99 | Parameters 100 | ---------- 101 | G : NetworkX graph 102 | The graph on which to find a weighted maximum cut. Each edge in G should 103 | have a numeric `weight` attribute. 104 | 105 | sampler 106 | A binary quadratic model sampler. A sampler is a process that 107 | samples from low energy states in models defined by an Ising 108 | equation or a Quadratic Unconstrained Binary Optimization 109 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 110 | and 'sample_ising' method. A sampler is expected to return an 111 | iterable of samples, in order of increasing energy. If no 112 | sampler is provided, one must be provided using the 113 | `set_default_sampler` function. 114 | 115 | sampler_args 116 | Additional keyword parameters are passed to the sampler. 117 | 118 | Returns 119 | ------- 120 | S : set 121 | A maximum cut of G. 122 | 123 | Notes 124 | ----- 125 | Samplers by their nature may not return the optimal solution. This 126 | function does not attempt to confirm the quality of the returned 127 | sample. 128 | 129 | """ 130 | # In order to form the Ising problem, we want to increase the 131 | # energy by 1 for each edge between two nodes of the same color. 132 | # The linear biases can all be 0. 133 | h = {v: 0. for v in G} 134 | try: 135 | J = {(u, v): G[u][v]['weight'] for u, v in G.edges} 136 | except KeyError: 137 | raise DWaveNetworkXException("edges must have 'weight' attribute") 138 | 139 | # draw the lowest energy sample from the sampler 140 | response = sampler.sample_ising(h, J, **sampler_args) 141 | sample = next(iter(response)) 142 | 143 | return set(v for v in G if sample[v] >= 0) 144 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/partition.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 itertools 16 | import math 17 | 18 | import dimod 19 | 20 | __all__ = ["partition", 21 | "graph_partition_cqm", 22 | ] 23 | 24 | 25 | def partition(G, num_partitions=2, sampler=None, **sampler_args): 26 | """Returns an approximate k-partition of G. 27 | 28 | Defines an CQM with ground states corresponding to a 29 | balanced k-partition of G and uses the sampler to sample from it. 30 | A k-partition is a collection of k subsets of the vertices 31 | of G such that each vertex is in exactly one subset, and 32 | the number of edges between vertices in different subsets 33 | is as small as possible. If G is a weighted graph, the sum 34 | of weights over those edges are minimized. 35 | 36 | Parameters 37 | ---------- 38 | G : NetworkX graph 39 | The graph to partition. 40 | num_partitions : int, optional (default 2) 41 | The number of subsets in the desired partition. 42 | sampler : 43 | A constrained quadratic model sampler. A sampler is a process that 44 | samples from low energy states in models defined by an Ising 45 | equation or a Quadratic Model, with or without constraints. The sampler 46 | is expected to have a 'sample_cqm' method. A sampler is expected to 47 | return an iterable of samples, in order of increasing energy. 48 | sampler_args 49 | Additional keyword parameters are passed to the sampler. 50 | 51 | Returns 52 | ------- 53 | node_partition : dict 54 | The partition as a dictionary mapping each node to subsets labelled 55 | as integers 0, 1, 2, ... num_partitions. 56 | 57 | Example 58 | ------- 59 | This example uses a sampler from 60 | `dimod `_ to find a 2-partition 61 | for a graph of a Chimera unit cell created using the `chimera_graph()` 62 | function. 63 | 64 | >>> import dimod 65 | >>> sampler = dimod.ExactCQMSolver() 66 | >>> G = dnx.chimera_graph(1, 1, 4) 67 | >>> partitions = dnx.partition(G, sampler=sampler) 68 | 69 | Notes 70 | ----- 71 | Samplers by their nature may not return the optimal solution. This 72 | function does not attempt to confirm the quality of the returned 73 | sample. 74 | """ 75 | if not len(G.nodes): 76 | return {} 77 | 78 | cqm = graph_partition_cqm(G, num_partitions) 79 | 80 | # Solve the problem using the CQM solver 81 | response = sampler.sample_cqm(cqm, **sampler_args) 82 | 83 | # Consider only results satisfying all constraints 84 | possible_partitions = response.filter(lambda d: d.is_feasible) 85 | 86 | if not possible_partitions: 87 | raise RuntimeError("No feasible solution could be found for this problem instance.") 88 | 89 | # Reinterpret result as partition assignment over nodes 90 | indicators = (key for key, value in possible_partitions.first.sample.items() if math.isclose(value, 1.)) 91 | node_partition = {key[0]: key[1] for key in indicators} 92 | 93 | return node_partition 94 | 95 | 96 | def graph_partition_cqm(G, num_partitions): 97 | """Find a constrained quadratic model for the graph's partitions. 98 | 99 | Defines an CQM with ground states corresponding to a 100 | balanced k-partition of G and uses the sampler to sample from it. 101 | A k-partition is a collection of k subsets of the vertices 102 | of G such that each vertex is in exactly one subset, and 103 | the number of edges between vertices in different subsets 104 | is as small as possible. If G is a weighted graph, the sum 105 | of weights over those edges are minimized. 106 | 107 | Parameters 108 | ---------- 109 | G : NetworkX graph 110 | The graph to partition. 111 | num_partitions : int 112 | The number of subsets in the desired partition. 113 | 114 | Returns 115 | ------- 116 | cqm : :class:`dimod.ConstrainedQuadraticModel` 117 | A constrained quadratic model with ground states corresponding to a 118 | partition problem. The nodes of `G` are discrete logical variables 119 | of the CQM, where the cases are the different partitions the node 120 | can be assigned to. The objective is given as the number of edges 121 | connecting nodes in different partitions. 122 | 123 | """ 124 | partition_size = G.number_of_nodes()/num_partitions 125 | partitions = range(num_partitions) 126 | cqm = dimod.ConstrainedQuadraticModel() 127 | 128 | # Variables will be added using the discrete method in CQM 129 | x = {vk: dimod.Binary(vk) for vk in itertools.product(G.nodes, partitions)} 130 | 131 | for v in G.nodes: 132 | cqm.add_discrete(((v, k) for k in partitions), label=v) 133 | 134 | 135 | if not math.isclose(partition_size, int(partition_size)): 136 | # if number of nodes don't divide into num_partitions, 137 | # accept partitions of size ceil() or floor() 138 | floor, ceil = int(partition_size), int(partition_size+1) 139 | for k in partitions: 140 | cqm.add_constraint(dimod.quicksum((x[u, k] for u in G.nodes)) >= floor, label='equal_partition_low_%s' %k) 141 | cqm.add_constraint(dimod.quicksum((x[u, k] for u in G.nodes)) <= ceil, label='equal_partition_high_%s' %k) 142 | else: 143 | # each partition must have partition_size elements 144 | for k in partitions: 145 | cqm.add_constraint(dimod.quicksum((x[u, k] for u in G.nodes)) == int(partition_size), label='equal_partition_%s' %k) 146 | 147 | cuts = 0 148 | for (u, v, d) in G.edges(data=True): 149 | for k in partitions: 150 | w = d.get('weight',1) 151 | cuts += w * x[u,k] * x[v,k] 152 | 153 | if cuts: 154 | cqm.set_objective(-cuts) 155 | 156 | return cqm -------------------------------------------------------------------------------- /dwave_networkx/algorithms/social.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.utils import binary_quadratic_model_sampler 16 | 17 | __all__ = ["structural_imbalance"] 18 | 19 | 20 | @binary_quadratic_model_sampler(1) 21 | def structural_imbalance(S, sampler=None, **sampler_args): 22 | """Returns an approximate set of frustrated edges and a bicoloring. 23 | 24 | A signed social network graph is a graph whose signed edges 25 | represent friendly/hostile interactions between nodes. A 26 | signed social network is considered balanced if it can be cleanly 27 | divided into two factions, where all relations within a faction are 28 | friendly, and all relations between factions are hostile. The measure 29 | of imbalance or frustration is the minimum number of edges that 30 | violate this rule. 31 | 32 | Parameters 33 | ---------- 34 | S : NetworkX graph 35 | A social graph on which each edge has a 'sign' 36 | attribute with a numeric value. 37 | 38 | sampler 39 | A binary quadratic model sampler. A sampler is a process that 40 | samples from low energy states in models defined by an Ising 41 | equation or a Quadratic Unconstrainted Binary Optimization 42 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 43 | and 'sample_ising' method. A sampler is expected to return an 44 | iterable of samples, in order of increasing energy. If no 45 | sampler is provided, one must be provided using the 46 | `set_default_sampler` function. 47 | 48 | sampler_args 49 | Additional keyword parameters are passed to the sampler. 50 | 51 | Returns 52 | ------- 53 | frustrated_edges : dict 54 | A dictionary of the edges that violate the edge sign. The imbalance 55 | of the network is the length of frustrated_edges. 56 | 57 | colors: dict 58 | A bicoloring of the nodes into two factions. 59 | 60 | Raises 61 | ------ 62 | ValueError 63 | If any edge does not have a 'sign' attribute. 64 | 65 | Examples 66 | -------- 67 | >>> import dimod 68 | >>> sampler = dimod.ExactSolver() 69 | >>> S = nx.Graph() 70 | >>> S.add_edge('Alice', 'Bob', sign=1) # Alice and Bob are friendly 71 | >>> S.add_edge('Alice', 'Eve', sign=-1) # Alice and Eve are hostile 72 | >>> S.add_edge('Bob', 'Eve', sign=-1) # Bob and Eve are hostile 73 | >>> frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 74 | >>> print(frustrated_edges) 75 | {} 76 | >>> print(colors) # doctest: +SKIP 77 | {'Alice': 0, 'Bob': 0, 'Eve': 1} 78 | >>> S.add_edge('Ted', 'Bob', sign=1) # Ted is friendly with all 79 | >>> S.add_edge('Ted', 'Alice', sign=1) 80 | >>> S.add_edge('Ted', 'Eve', sign=1) 81 | >>> frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 82 | >>> print(frustrated_edges) # doctest: +SKIP 83 | {('Ted', 'Eve'): {'sign': 1}} 84 | >>> print(colors) # doctest: +SKIP 85 | {'Bob': 1, 'Ted': 1, 'Alice': 1, 'Eve': 0} 86 | 87 | Notes 88 | ----- 89 | Samplers by their nature may not return the optimal solution. This 90 | function does not attempt to confirm the quality of the returned 91 | sample. 92 | 93 | References 94 | ---------- 95 | 96 | `Ising model on Wikipedia `_ 97 | 98 | Facchetti, G., Iacono G., and Altafini C. (2011). 99 | Computing global structural balance in large-scale signed social networks. 100 | PNAS, 108, no. 52, 20953-20958 101 | 102 | """ 103 | h, J = structural_imbalance_ising(S) 104 | 105 | # use the sampler to find low energy states 106 | response = sampler.sample_ising(h, J, **sampler_args) 107 | 108 | # we want the lowest energy sample 109 | sample = next(iter(response)) 110 | 111 | # spins determine the color 112 | colors = {v: (spin + 1) // 2 for v, spin in sample.items()} 113 | 114 | # frustrated edges are the ones that are violated 115 | frustrated_edges = {} 116 | for u, v, data in S.edges(data=True): 117 | sign = data['sign'] 118 | 119 | if sign > 0 and colors[u] != colors[v]: 120 | frustrated_edges[(u, v)] = data 121 | elif sign < 0 and colors[u] == colors[v]: 122 | frustrated_edges[(u, v)] = data 123 | # else: not frustrated or sign == 0, no relation to violate 124 | 125 | return frustrated_edges, colors 126 | 127 | 128 | def structural_imbalance_ising(S): 129 | """Construct the Ising problem to calculate the structural imbalance of a signed social network. 130 | 131 | A signed social network graph is a graph whose signed edges 132 | represent friendly/hostile interactions between nodes. A 133 | signed social network is considered balanced if it can be cleanly 134 | divided into two factions, where all relations within a faction are 135 | friendly, and all relations between factions are hostile. The measure 136 | of imbalance or frustration is the minimum number of edges that 137 | violate this rule. 138 | 139 | Parameters 140 | ---------- 141 | S : NetworkX graph 142 | A social graph on which each edge has a 'sign' attribute with a numeric value. 143 | 144 | Returns 145 | ------- 146 | h : dict 147 | The linear biases of the Ising problem. Each variable in the Ising problem represent 148 | a node in the signed social network. The solution that minimized the Ising problem 149 | will assign each variable a value, either -1 or 1. This bi-coloring defines the factions. 150 | 151 | J : dict 152 | The quadratic biases of the Ising problem. 153 | 154 | Raises 155 | ------ 156 | ValueError 157 | If any edge does not have a 'sign' attribute. 158 | 159 | Examples 160 | -------- 161 | >>> import dimod 162 | >>> from dwave_networkx.algorithms.social import structural_imbalance_ising 163 | ... 164 | >>> S = nx.Graph() 165 | >>> S.add_edge('Alice', 'Bob', sign=1) # Alice and Bob are friendly 166 | >>> S.add_edge('Alice', 'Eve', sign=-1) # Alice and Eve are hostile 167 | >>> S.add_edge('Bob', 'Eve', sign=-1) # Bob and Eve are hostile 168 | ... 169 | >>> h, J = structural_imbalance_ising(S) 170 | >>> h # doctest: +SKIP 171 | {'Alice': 0.0, 'Bob': 0.0, 'Eve': 0.0} 172 | >>> J # doctest: +SKIP 173 | {('Alice', 'Bob'): -1.0, ('Alice', 'Eve'): 1.0, ('Bob', 'Eve'): 1.0} 174 | 175 | """ 176 | h = {v: 0.0 for v in S} 177 | J = {} 178 | for u, v, data in S.edges(data=True): 179 | try: 180 | J[(u, v)] = -1. * data['sign'] 181 | except KeyError: 182 | raise ValueError(("graph should be a signed social graph," 183 | "each edge should have a 'sign' attr")) 184 | 185 | return h, J 186 | -------------------------------------------------------------------------------- /dwave_networkx/algorithms/tsp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 itertools 16 | 17 | from collections import defaultdict 18 | 19 | from dwave_networkx.utils import binary_quadratic_model_sampler 20 | 21 | __all__ = ["traveling_salesperson", 22 | "traveling_salesperson_qubo", 23 | "traveling_salesman", 24 | "traveling_salesman_qubo", 25 | "is_hamiltonian_path", 26 | ] 27 | 28 | 29 | @binary_quadratic_model_sampler(1) 30 | def traveling_salesperson(G, sampler=None, lagrange=None, weight='weight', 31 | start=None, **sampler_args): 32 | """Returns an approximate minimum traveling salesperson route. 33 | 34 | Defines a QUBO with ground states corresponding to the 35 | minimum routes and uses the sampler to sample 36 | from it. 37 | 38 | A route is a cycle in the graph that reaches each node exactly once. 39 | A minimum route is a route with the smallest total edge weight. 40 | 41 | Parameters 42 | ---------- 43 | G : NetworkX graph 44 | The graph on which to find a minimum traveling salesperson route. 45 | This should be a complete graph with non-zero weights on every edge. 46 | 47 | sampler : 48 | A binary quadratic model sampler. A sampler is a process that 49 | samples from low energy states in models defined by an Ising 50 | equation or a Quadratic Unconstrained Binary Optimization 51 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 52 | and 'sample_ising' method. A sampler is expected to return an 53 | iterable of samples, in order of increasing energy. If no 54 | sampler is provided, one must be provided using the 55 | `set_default_sampler` function. 56 | 57 | lagrange : number, optional (default None) 58 | Lagrange parameter to weight constraints (visit every city once) 59 | versus objective (shortest distance route). 60 | 61 | weight : optional (default 'weight') 62 | The name of the edge attribute containing the weight. 63 | 64 | start : node, optional 65 | If provided, the route will begin at `start`. 66 | 67 | sampler_args : 68 | Additional keyword parameters are passed to the sampler. 69 | 70 | Returns 71 | ------- 72 | route : list 73 | List of nodes in order to be visited on a route 74 | 75 | Examples 76 | -------- 77 | 78 | >>> import dimod 79 | ... 80 | >>> G = nx.Graph() 81 | >>> G.add_weighted_edges_from({(0, 1, .1), (0, 2, .5), (0, 3, .1), (1, 2, .1), 82 | ... (1, 3, .5), (2, 3, .1)}) 83 | >>> dnx.traveling_salesperson(G, dimod.ExactSolver(), start=0) # doctest: +SKIP 84 | [0, 1, 2, 3] 85 | 86 | Notes 87 | ----- 88 | Samplers by their nature may not return the optimal solution. This 89 | function does not attempt to confirm the quality of the returned 90 | sample. 91 | 92 | """ 93 | # Get a QUBO representation of the problem 94 | Q = traveling_salesperson_qubo(G, lagrange, weight) 95 | 96 | # use the sampler to find low energy states 97 | response = sampler.sample_qubo(Q, **sampler_args) 98 | 99 | sample = response.first.sample 100 | 101 | route = [None]*len(G) 102 | for (city, time), val in sample.items(): 103 | if val: 104 | route[time] = city 105 | 106 | if start is not None and route[0] != start: 107 | # rotate to put the start in front 108 | idx = route.index(start) 109 | route = route[idx:] + route[:idx] 110 | 111 | return route 112 | 113 | 114 | traveling_salesman = traveling_salesperson 115 | 116 | 117 | def traveling_salesperson_qubo(G, lagrange=None, weight='weight', missing_edge_weight=None): 118 | """Return the QUBO with ground states corresponding to a minimum TSP route. 119 | 120 | If :math:`|G|` is the number of nodes in the graph, the resulting qubo will have: 121 | 122 | * :math:`|G|^2` variables/nodes 123 | * :math:`2 |G|^2 (|G| - 1)` interactions/edges 124 | 125 | Parameters 126 | ---------- 127 | G : NetworkX graph 128 | A complete graph in which each edge has a attribute giving its weight. 129 | 130 | lagrange : number, optional (default None) 131 | Lagrange parameter to weight constraints (no edges within set) 132 | versus objective (largest set possible). 133 | 134 | weight : optional (default 'weight') 135 | The name of the edge attribute containing the weight. 136 | 137 | missing_edge_weight : number, optional (default None) 138 | For bi-directional graphs, the weight given to missing edges. 139 | If None is given (the default), missing edges will be set to 140 | the sum of all weights. 141 | 142 | Returns 143 | ------- 144 | QUBO : dict 145 | The QUBO with ground states corresponding to a minimum travelling 146 | salesperson route. The QUBO variables are labelled `(c, t)` where `c` 147 | is a node in `G` and `t` is the time index. For instance, if `('a', 0)` 148 | is 1 in the ground state, that means the node 'a' is visted first. 149 | 150 | """ 151 | N = G.number_of_nodes() 152 | 153 | if lagrange is None: 154 | # If no lagrange parameter provided, set to 'average' tour length. 155 | # Usually a good estimate for a lagrange parameter is between 75-150% 156 | # of the objective function value, so we come up with an estimate for 157 | # tour length and use that. 158 | if G.number_of_edges()>0: 159 | lagrange = G.size(weight=weight)*G.number_of_nodes()/G.number_of_edges() 160 | else: 161 | lagrange = 2 162 | 163 | # calculate default missing_edge_weight if required 164 | if missing_edge_weight is None: 165 | # networkx method to calculate sum of all weights 166 | missing_edge_weight = G.size(weight=weight) 167 | 168 | # some input checking 169 | if N in (1, 2): 170 | msg = "graph must have at least 3 nodes or be empty" 171 | raise ValueError(msg) 172 | 173 | # Creating the QUBO 174 | Q = defaultdict(float) 175 | 176 | # Constraint that each row has exactly one 1 177 | for node in G: 178 | for pos_1 in range(N): 179 | Q[((node, pos_1), (node, pos_1))] -= lagrange 180 | for pos_2 in range(pos_1+1, N): 181 | Q[((node, pos_1), (node, pos_2))] += 2.0*lagrange 182 | 183 | # Constraint that each col has exactly one 1 184 | for pos in range(N): 185 | for node_1 in G: 186 | Q[((node_1, pos), (node_1, pos))] -= lagrange 187 | for node_2 in set(G)-{node_1}: 188 | # QUBO coefficient is 2*lagrange, but we are placing this value 189 | # above *and* below the diagonal, so we put half in each position. 190 | Q[((node_1, pos), (node_2, pos))] += lagrange 191 | 192 | # Objective that minimizes distance 193 | for u, v in itertools.combinations(G.nodes, 2): 194 | for pos in range(N): 195 | nextpos = (pos + 1) % N 196 | 197 | # going from u -> v 198 | try: 199 | value = G[u][v][weight] 200 | except KeyError: 201 | value = missing_edge_weight 202 | 203 | Q[((u, pos), (v, nextpos))] += value 204 | 205 | # going from v -> u 206 | try: 207 | value = G[v][u][weight] 208 | except KeyError: 209 | value = missing_edge_weight 210 | 211 | Q[((v, pos), (u, nextpos))] += value 212 | 213 | return Q 214 | 215 | 216 | traveling_salesman_qubo = traveling_salesperson_qubo 217 | 218 | 219 | def is_hamiltonian_path(G, route): 220 | """Determines whether the given list forms a valid TSP route. 221 | 222 | A travelling salesperson route must visit each city exactly once. 223 | 224 | Parameters 225 | ---------- 226 | G : NetworkX graph 227 | 228 | The graph on which to check the route. 229 | 230 | route : list 231 | 232 | List of nodes in the order that they are visited. 233 | 234 | Returns 235 | ------- 236 | is_valid : bool 237 | True if route forms a valid travelling salesperson route. 238 | 239 | """ 240 | 241 | return (set(route) == set(G)) 242 | -------------------------------------------------------------------------------- /dwave_networkx/default_sampler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | """Sets a binary quadratic model sampler used by default 16 | for functions that require a sample when none is specified. 17 | 18 | A sampler is a process that samples 19 | from low-energy states in models defined by an Ising equation 20 | or a Quadratic Unconstrained Binary Optimization Problem 21 | (QUBO). 22 | 23 | Sampler API 24 | ----------- 25 | * Required Methods: 'sample_qubo' and 'sample_ising' 26 | * Return value: iterable of samples, in order of increasing energy 27 | 28 | See `dimod `_ for details. 29 | 30 | Example 31 | ------- 32 | This example creates and uses a placeholder for binary quadratic model 33 | samplers that returns a correct response only in the case of finding an 34 | independent set on a complete graph (where one node is always an 35 | independent set). The placeholder sampler can be used to test the simple 36 | examples of the functions for configuring a default sampler. 37 | 38 | >>> # Create a placeholder sampler 39 | >>> class ExampleSampler: 40 | ... # an example sampler, only works for independent set on complete 41 | ... # graphs 42 | ... def __init__(self, name): 43 | ... self.name = name 44 | ... def sample_ising(self, h, J): 45 | ... sample = {v: -1 for v in h} 46 | ... sample[0] = 1 # set one node to true 47 | ... return [sample] 48 | ... def sample_qubo(self, Q): 49 | ... sample = {v: 0 for v in set().union(*Q)} 50 | ... sample[0] = 1 # set one node to true 51 | ... return [sample] 52 | ... def __str__(self): 53 | ... return self.name 54 | ... 55 | >>> # Identify the new sampler as the default sampler 56 | >>> sampler0 = ExampleSampler('sampler0') 57 | >>> dnx.set_default_sampler(sampler0) 58 | >>> # Find an independent set using the default sampler 59 | >>> G = nx.complete_graph(5) 60 | >>> dnx.maximum_independent_set(G) 61 | [0] 62 | 63 | """ 64 | 65 | from dwave_networkx.utils.decorators import binary_quadratic_model_sampler 66 | 67 | __all__ = ['set_default_sampler', 'get_default_sampler', 'unset_default_sampler'] 68 | 69 | 70 | _SAMPLER = None 71 | 72 | 73 | @binary_quadratic_model_sampler(0) 74 | def set_default_sampler(sampler): 75 | """Sets a default binary quadratic model sampler. 76 | 77 | Parameters 78 | ---------- 79 | sampler 80 | A binary quadratic model sampler. A sampler is a process that 81 | samples from low-energy states in models defined by an Ising 82 | equation or a Quadratic Unconstrained Binary Optimization 83 | Problem (QUBO). A sampler is expected to have a 'sample_qubo' 84 | and 'sample_ising' method. A sampler is expected to return an 85 | iterable of samples, in order of increasing energy. 86 | 87 | Examples 88 | -------- 89 | This example sets sampler0 as the default sampler and finds an independent 90 | set for graph G, first using the default sampler and then overriding it by 91 | specifying a different sampler. 92 | 93 | >>> dnx.set_default_sampler(sampler0) # doctest: +SKIP 94 | >>> indep_set = dnx.maximum_independent_set_dm(G) # doctest: +SKIP 95 | >>> indep_set = dnx.maximum_independent_set_dm(G, sampler1) # doctest: +SKIP 96 | 97 | """ 98 | global _SAMPLER 99 | _SAMPLER = sampler 100 | 101 | 102 | def unset_default_sampler(): 103 | """Resets the default sampler back to None. 104 | 105 | Examples 106 | -------- 107 | This example sets sampler0 as the default sampler, verifies the setting, 108 | then resets the default, and verifies the resetting. 109 | 110 | >>> dnx.set_default_sampler(sampler0) # doctest: +SKIP 111 | >>> print(dnx.get_default_sampler()) # doctest: +SKIP 112 | 'sampler0' 113 | >>> dnx.unset_default_sampler() # doctest: +SKIP 114 | >>> print(dnx.get_default_sampler()) # doctest: +SKIP 115 | None 116 | """ 117 | global _SAMPLER 118 | _SAMPLER = None 119 | 120 | 121 | def get_default_sampler(): 122 | """Queries the current default sampler. 123 | 124 | Examples 125 | -------- 126 | This example queries the default sampler before and after specifying 127 | a default sampler. 128 | 129 | >>> print(dnx.get_default_sampler()) # doctest: +SKIP 130 | None 131 | >>> dnx.set_default_sampler(sampler) # doctest: +SKIP 132 | >>> print(dnx.get_default_sampler()) # doctest: +SKIP 133 | 'sampler' 134 | 135 | """ 136 | return _SAMPLER 137 | -------------------------------------------------------------------------------- /dwave_networkx/drawing/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.drawing.chimera_layout import * 16 | from dwave_networkx.drawing.pegasus_layout import * 17 | from dwave_networkx.drawing.zephyr_layout import * 18 | from dwave_networkx.drawing.parallel_embeddings import * 19 | -------------------------------------------------------------------------------- /dwave_networkx/drawing/parallel_embeddings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 D-Wave 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 | from typing import Optional 15 | import math 16 | 17 | import networkx as nx 18 | import numpy 19 | 20 | from dwave_networkx.drawing.chimera_layout import draw_chimera, chimera_layout 21 | from dwave_networkx.drawing.pegasus_layout import draw_pegasus, pegasus_layout 22 | from dwave_networkx.drawing.zephyr_layout import draw_zephyr, zephyr_layout 23 | 24 | __all__ = ["draw_parallel_embeddings"] 25 | 26 | 27 | def _generate_node_color_dict( 28 | G: nx.Graph, 29 | embeddings: list[dict], 30 | S: Optional[nx.Graph] = None, 31 | one_to_iterable: bool = False, 32 | shuffle_colormap: bool = True, 33 | seed: Optional[int] = None, 34 | ) -> tuple[dict, list[dict]]: 35 | """Generate a node color dictionary mapping each node in G to an embedding index or NaN. 36 | 37 | Args: 38 | G: The target graph. 39 | embeddings: A list of embeddings (each embedding a dict from source nodes to target nodes). 40 | S: The optional source graph. 41 | one_to_iterable: If True, a single source node maps to multiple target nodes. 42 | shuffle_colormap: If True, embeddings are shuffled before assigning colors. 43 | seed: A seed for the random number generator. Only used when ``shuffle_colormap`` is True. 44 | 45 | Returns: 46 | node_color_dict: A dictionary mapping each node in G to either an embedding index or NaN. 47 | embeddings_: The potentially shuffled embeddings list used for assigning colors. 48 | """ 49 | node_color_dict = {q: float("nan") for q in G.nodes()} 50 | 51 | if shuffle_colormap: 52 | embeddings_ = embeddings.copy() 53 | rng = numpy.random.default_rng(seed=seed) 54 | rng.shuffle(embeddings_) 55 | else: 56 | embeddings_ = embeddings 57 | 58 | if S is None: 59 | # If there is no source graph, color all nodes in the embeddings 60 | if one_to_iterable: 61 | # Multiple target nodes per source node 62 | node_color_dict.update( 63 | { 64 | q: idx 65 | for idx, emb in enumerate(embeddings_) 66 | for c in emb.values() 67 | for q in c 68 | } 69 | ) 70 | else: 71 | # One-to-one mapping 72 | node_color_dict.update( 73 | {q: idx for idx, emb in enumerate(embeddings_) for q in emb.values()} 74 | ) 75 | else: 76 | # If a source graph is provided, only color nodes corresponding to S 77 | node_set = set(S.nodes()) 78 | if one_to_iterable: 79 | node_color_dict.update( 80 | { 81 | q: idx if n in node_set else float("nan") 82 | for idx, emb in enumerate(embeddings_) 83 | for n, c in emb.items() 84 | for q in c 85 | } 86 | ) 87 | else: 88 | node_color_dict.update( 89 | { 90 | q: idx 91 | for idx, emb in enumerate(embeddings_) 92 | for n, q in emb.items() 93 | if n in node_set 94 | } 95 | ) 96 | 97 | return node_color_dict, embeddings_ 98 | 99 | 100 | def _generate_edge_color_dict( 101 | G: nx.Graph, 102 | embeddings: list[dict], 103 | S: Optional[nx.Graph], 104 | one_to_iterable: bool, 105 | node_color_dict: dict, 106 | ) -> dict: 107 | """Generate an edge color dictionary mapping each edge in G to an embedding index or NaN. 108 | 109 | Args: 110 | G: The target graph. 111 | embeddings: A list of embeddings (each embedding a dict from source nodes to target nodes). 112 | S: The optional source graph (if None, edges are colored based on node colors). 113 | one_to_iterable: If True, a single source node maps to multiple target nodes. 114 | node_color_dict: The node color dictionary to reference for edge coloring. 115 | 116 | Returns: 117 | edge_color_dict: A dictionary mapping each edge in G to either an embedding index or NaN. 118 | """ 119 | if S is not None: 120 | # Edges corresponding to source graph embeddings 121 | edge_color_dict = { 122 | (tu, tv): idx 123 | for idx, emb in enumerate(embeddings) 124 | for u, v in S.edges() 125 | if u in emb and v in emb 126 | for tu in (emb[u] if one_to_iterable else [emb[u]]) 127 | for tv in (emb[v] if one_to_iterable else [emb[v]]) 128 | if G.has_edge(tu, tv) 129 | } 130 | 131 | if one_to_iterable: 132 | # Add chain edges 133 | for idx, emb in enumerate(embeddings): 134 | for chain in emb.values(): 135 | Gchain = G.subgraph(chain) 136 | edge_color_dict.update({e: idx for e in Gchain.edges()}) 137 | else: 138 | # If no source graph, color edges where both endpoints share the same embedding color 139 | edge_color_dict = { 140 | (v1, v2): node_color_dict[v1] 141 | for v1, v2 in G.edges() 142 | if node_color_dict[v1] == node_color_dict[v2] 143 | } 144 | 145 | return edge_color_dict 146 | 147 | 148 | def draw_parallel_embeddings( 149 | G: nx.Graph, 150 | embeddings: list[dict], 151 | S: Optional[nx.Graph] = None, 152 | one_to_iterable: bool = True, 153 | shuffle_colormap: bool = True, 154 | seed: Optional[int] = None, 155 | use_plt: bool = True, 156 | **kwargs, 157 | ): 158 | """Visualizes the embeddings using dwave_networkx's layout functions. 159 | 160 | Args: 161 | G: The target graph to be visualized. 162 | embeddings: A list of embeddings, each embedding is a dictionary. A 163 | list of embeddings can be created using methods within the module 164 | `minorminer.utils.parallel_embeddings` 165 | S: The source graph to visualize. If provided then only 166 | edges relevant to the source graph are shown, as opposed to all 167 | those supported by the embedding. 168 | one_to_iterable: If True, allow multiple target nodes per source node, 169 | values of the embedding are iterables on nodes of G. 170 | If False, embedding values should be nodes of G. 171 | shuffle_colormap: If True, randomize the colormap assignment. 172 | seed: A seed for the random number generator. Only used when ``shuffle_colormap`` is True. 173 | **kwargs: Additional keyword arguments for the drawing functions. 174 | 175 | Examples 176 | -------- 177 | This example plots 3 embeddings of a length-8 path on Chimera[m=2], with 178 | no embedding in the bottom right. 179 | 180 | >>> import networkx as nx 181 | >>> import dwave_networkx as dnx 182 | >>> import matplotlib.pyplot as plt # doctest: +SKIP 183 | >>> G = dnx.chimera_graph(2) 184 | >>> S = nx.from_edgelist({(i,i+1) for i in range(7)}) 185 | >>> emb = {i: (i // 2) + 4*(i % 2) for i in range(8)} # Top-left embedding 186 | >>> embs = [{k: (v+8*offset,) for k,v in emb.items()} for offset in range(3)] 187 | >>> dnx.draw_parallel_embeddings(G, embs) # doctest: +SKIP 188 | >>> plt.show() # doctest: +SKIP 189 | """ 190 | try: 191 | import matplotlib.pyplot as plt 192 | except ImportError: 193 | raise ImportError("Matplotlib is required for draw_parallel_embeddings()") 194 | 195 | ax = plt.gca() 196 | cmap = plt.get_cmap("turbo").copy() 197 | cmap.set_bad("lightgrey") 198 | 199 | # Generate node_color_dict and embeddings to use 200 | node_color_dict, _embeddings = _generate_node_color_dict( 201 | G, embeddings, S, one_to_iterable, shuffle_colormap, seed 202 | ) 203 | 204 | # Generate edge_color_dict 205 | edge_color_dict = _generate_edge_color_dict( 206 | G, _embeddings, S, one_to_iterable, node_color_dict 207 | ) 208 | 209 | # Default drawing arguments 210 | draw_kwargs = { 211 | "G": G, 212 | "node_color": [node_color_dict[q] for q in G.nodes()], 213 | "edge_color": "lightgrey", 214 | "node_shape": "o", 215 | "ax": ax, 216 | "with_labels": False, 217 | "width": 1, 218 | "cmap": cmap, 219 | "edge_cmap": cmap, 220 | "node_size": 300 / math.sqrt(G.number_of_nodes()), 221 | } 222 | draw_kwargs.update(kwargs) 223 | 224 | topology = G.graph.get("family") 225 | # Draw the combined graph with color mappings 226 | if topology == "chimera": 227 | pos = chimera_layout(G) 228 | draw_chimera(**draw_kwargs) 229 | elif topology == "pegasus": 230 | pos = pegasus_layout(G) 231 | draw_pegasus(**draw_kwargs) 232 | elif topology == "zephyr": 233 | pos = zephyr_layout(G) 234 | draw_zephyr(**draw_kwargs) 235 | else: 236 | pos = nx.spring_layout(G) 237 | nx.draw_networkx(**draw_kwargs) 238 | 239 | if len(edge_color_dict) > 0: 240 | # Recolor specific edges on top of the original graph 241 | nx.draw_networkx_edges( 242 | G, 243 | pos=pos, 244 | edgelist=list(edge_color_dict.keys()), 245 | edge_color=list(edge_color_dict.values()), 246 | width=1, 247 | edge_cmap=cmap, 248 | ax=ax, 249 | ) 250 | -------------------------------------------------------------------------------- /dwave_networkx/examples/chimera_graph.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | 16 | import matplotlib.pyplot as plt 17 | import dwave_networkx as dnx 18 | import networkx as nx 19 | 20 | G = dnx.chimera_graph(2, 2, 4) 21 | dnx.draw_chimera(G) 22 | plt.show() 23 | -------------------------------------------------------------------------------- /dwave_networkx/examples/max_cut.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 dwave_networkx as dnx 16 | import networkx as nx 17 | import dimod 18 | 19 | # Use basic simulated annealer 20 | sampler = dimod.SimulatedAnnealingSampler() 21 | 22 | # Set up a Networkx Graph 23 | G = nx.Graph() 24 | G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (3, 5), (4, 5), (5, 2)]) 25 | 26 | # Get the max cut 27 | candidate = dnx.maximum_cut(G, sampler) 28 | if len(candidate) == 3: 29 | print (candidate, " is the right length") 30 | else: 31 | print (candidate, " is not the right length") 32 | -------------------------------------------------------------------------------- /dwave_networkx/examples/max_independent_set.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 networkx as nx 16 | import dwave_networkx as dnx 17 | import dimod 18 | 19 | # Use basic simulated annealer 20 | sampler = dimod.SimulatedAnnealingSampler() 21 | 22 | # Set up a Networkx Graph 23 | G = nx.Graph() 24 | G.add_edges_from([(1,2),(1,3),(2,3),(3,4),(3,5),(4,5),(4,6),(5,6),(6,7)]) 25 | 26 | # Find the maximum independent set, which is known in this case to be of length 3 27 | candidate = dnx.maximum_independent_set(G, sampler) 28 | if dnx.is_independent_set(G, candidate) and len(candidate) == 3: 29 | print(candidate, " is a maximum independent set") 30 | else: 31 | print(candidate, " is not a minimum vertex coloring") -------------------------------------------------------------------------------- /dwave_networkx/examples/min_maximal_matching.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 dwave_networkx as dnx 16 | import dimod 17 | 18 | # Use basic simulated annealer 19 | sampler = dimod.SimulatedAnnealingSampler() 20 | 21 | G = dnx.chimera_graph(1, 1, 4) 22 | # Get the minimum maximal matching, which is known in this case to be of 23 | # length 4 24 | candidate = dnx.min_maximal_matching(G, sampler) 25 | 26 | if dnx.is_maximal_matching(G, candidate) and len(candidate) == 4: 27 | print (candidate, " is a minimum maximal matching") 28 | else: 29 | print (candidate, " is not a minimum maximal matching") 30 | -------------------------------------------------------------------------------- /dwave_networkx/examples/min_vertex_color.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 dwave_networkx as dnx 16 | import networkx as nx 17 | import dimod 18 | 19 | # Use basic simulated annealer 20 | sampler = dimod.SimulatedAnnealingSampler() 21 | 22 | # Set up a Networkx Graph 23 | G = nx.Graph() 24 | G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (3, 5), (4, 5), (5, 2)]) 25 | 26 | # Get the minimum vertex coloring, which is known in this case to be of 27 | # length 6 28 | candidate = dnx.min_vertex_coloring(G, sampler) 29 | if dnx.is_vertex_coloring(G, candidate) and len(candidate) == 6: 30 | print(candidate, " is a minimum vertex coloring") 31 | else: 32 | print(candidate, " is not a minimum vertex coloring") 33 | -------------------------------------------------------------------------------- /dwave_networkx/examples/min_vertex_cover.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 dwave_networkx as dnx 16 | import networkx as nx 17 | import dimod 18 | 19 | # Use basic simulated annealer 20 | sampler = dimod.SimulatedAnnealingSampler() 21 | 22 | # The definition of a minimum vertex cover set is that each edge in the graph 23 | # must have a vertex in the minimum vertex cover set, and we also want the 24 | # vertex cover set to be as small as possible. 25 | 26 | # Set up a Networkx Graph 27 | G = nx.Graph() 28 | G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 5), (4, 5), (3, 6), (4, 7), (5, 6), (5, 7), (6, 8), (7, 8), (8, 9)]) 29 | 30 | # Get the minimum vertex cover, which is known in this case to be of 31 | # length 5 32 | candidate = dnx.min_vertex_cover(G, sampler) 33 | if dnx.is_vertex_cover(G, candidate) and len(candidate) == 5: 34 | print (candidate, " is a minimum vertex cover") 35 | else: 36 | print (candidate, " is not a minimum vertex cover") 37 | -------------------------------------------------------------------------------- /dwave_networkx/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | """ 16 | Base exceptions and errors for D-Wave NetworkX. 17 | 18 | All exceptions are derived from NetworkXException. 19 | 20 | """ 21 | 22 | from networkx import NetworkXException 23 | 24 | 25 | class DWaveNetworkXException(NetworkXException): 26 | """Base class for exceptions in DWaveNetworkX.""" 27 | 28 | 29 | class DWaveNetworkXMissingSampler(DWaveNetworkXException): 30 | """Exception raised by an algorithm requiring a discrete model 31 | sampler when none is provided.""" 32 | -------------------------------------------------------------------------------- /dwave_networkx/generators/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | from dwave_networkx.generators.chimera import * 16 | from dwave_networkx.generators.markov import markov_network 17 | from dwave_networkx.generators.pegasus import * 18 | from dwave_networkx.generators.zephyr import * 19 | -------------------------------------------------------------------------------- /dwave_networkx/generators/common.py: -------------------------------------------------------------------------------- 1 | 2 | def _add_compatible_edges(G, edge_list): 3 | # Check edge_list defines a subgraph of G and create subgraph. 4 | # Slow when edge_list is large, but clear (non-defaulted behaviour, so fine): 5 | if edge_list is not None: 6 | if not all(G.has_edge(*e) for e in edge_list): 7 | raise ValueError("edge_list contains edges incompatible with G") 8 | # Hard to check edge_list consistency owing to directedness, etc. Brute force 9 | G.remove_edges_from(list(G.edges)) 10 | G.add_edges_from(edge_list) 11 | if G.number_of_edges() < len(edge_list): 12 | raise ValueError('edge_list contains duplicates.') 13 | 14 | def _add_compatible_nodes(G, node_list): 15 | if node_list is not None: 16 | if not all(G.has_node(n) for n in node_list): 17 | raise ValueError("node_list contains nodes incompatible with G") 18 | nodes = set(node_list) 19 | remove_nodes = set(G) - nodes 20 | G.remove_nodes_from(remove_nodes) 21 | if G.number_of_nodes() < len(node_list): 22 | raise ValueError('node_list contains duplicates.') 23 | 24 | def _add_compatible_terms(G, node_list, edge_list): 25 | _add_compatible_edges(G, edge_list) 26 | _add_compatible_nodes(G, node_list) 27 | #Check node deletion hasn't caused edge deletion: 28 | if edge_list is not None and len(edge_list) != G.number_of_edges(): 29 | raise ValueError('The edge_list contains nodes absent from the node_list') 30 | -------------------------------------------------------------------------------- /dwave_networkx/generators/markov.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 | import itertools 16 | import collections.abc as abc 17 | 18 | from collections import namedtuple 19 | 20 | import networkx as nx 21 | 22 | 23 | __all__ = 'markov_network', 24 | 25 | 26 | ############################################################################### 27 | # The following code is partially based on https://github.com/tbabej/gibbs 28 | # 29 | # MIT License 30 | # =========== 31 | # 32 | # Copyright 2017 Tomas Babej 33 | # https://github.com/tbabej/gibbs 34 | # 35 | # This software is released under MIT licence. 36 | # 37 | # Permission is hereby granted, free of charge, to any person obtaining 38 | # a copy of this software and associated documentation files (the 39 | # "Software"), to deal in the Software without restriction, including 40 | # without limitation the rights to use, copy, modify, merge, publish, 41 | # distribute, sublicense, and/or sell copies of the Software, and to 42 | # permit persons to whom the Software is furnished to do so, subject to 43 | # the following conditions: 44 | # 45 | # The above copyright notice and this permission notice shall be 46 | # included in all copies or substantial portions of the Software. 47 | # 48 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 52 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 53 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 54 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 55 | # 56 | 57 | 58 | def markov_network(potentials): 59 | """Creates a Markov Network from potentials. 60 | 61 | A Markov Network is also knows as a `Markov Random Field`_ 62 | 63 | Parameters 64 | ---------- 65 | potentials : dict[tuple, dict] 66 | A dict where the keys are either nodes or edges and the values are a 67 | dictionary of potentials. The potential dict should map each possible 68 | assignment of the nodes/edges to their energy. 69 | 70 | Returns 71 | ------- 72 | MN : :obj:`networkx.Graph` 73 | A markov network as a graph where each node/edge stores its potential 74 | dict as above. 75 | 76 | Examples 77 | -------- 78 | >>> potentials = {('a', 'b'): {(0, 0): -1, 79 | ... (0, 1): .5, 80 | ... (1, 0): .5, 81 | ... (1, 1): 2}} 82 | >>> MN = dnx.markov_network(potentials) 83 | >>> MN['a']['b']['potential'][(0, 0)] 84 | -1 85 | 86 | .. _Markov Random Field: https://en.wikipedia.org/wiki/Markov_random_field 87 | 88 | """ 89 | 90 | G = nx.Graph() 91 | 92 | G.name = 'markov_network({!r})'.format(potentials) 93 | 94 | # we use 'clique' because the keys of potentials can be either nodes or 95 | # edges, but in either case they are fully connected. 96 | for clique, phis in potentials.items(): 97 | 98 | num_vars = len(clique) 99 | 100 | # because this data potentially wont be used for a while, let's do some 101 | # input checking now and save some debugging issues later 102 | if not isinstance(phis, abc.Mapping): 103 | raise TypeError("phis should be a dict") 104 | elif not all(config in phis for config in itertools.product((0, 1), repeat=num_vars)): 105 | raise ValueError("not all potentials provided for {!r}".format(clique)) 106 | 107 | if num_vars == 1: 108 | u, = clique 109 | G.add_node(u, potential=phis) 110 | elif num_vars == 2: 111 | u, v = clique 112 | # in python<=3.5 the edge order might not be consistent so we store 113 | # the relevant order of the variables relative to the potentials 114 | G.add_edge(u, v, potential=phis, order=(u, v)) 115 | else: 116 | # developer note: in principle supporting larger cliques can be done 117 | # using higher-order, but it would make the use of networkx graphs 118 | # far more difficult 119 | raise ValueError("Only supports cliques up to size 2") 120 | 121 | return G 122 | -------------------------------------------------------------------------------- /dwave_networkx/package_info.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | __version__ = '0.8.18' 16 | __author__ = 'D-Wave Systems Inc.' 17 | __authoremail__ = 'tools@dwavesys.com' 18 | __description__ = 'A NetworkX extension providing graphs and algorithms relevant to working with the D-Wave System' 19 | -------------------------------------------------------------------------------- /dwave_networkx/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | # ================================================================================================ 16 | from dwave_networkx.utils.decorators import * 17 | -------------------------------------------------------------------------------- /dwave_networkx/utils/decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | """ 16 | Decorators allow for input checking and default parameter setting for 17 | algorithms. 18 | """ 19 | 20 | import functools 21 | import inspect 22 | 23 | import dwave_networkx as dnx 24 | 25 | __all__ = ['binary_quadratic_model_sampler'] 26 | 27 | 28 | def binary_quadratic_model_sampler(which_args): 29 | """Decorator to validate sampler arguments. 30 | 31 | Parameters 32 | ---------- 33 | which_args : int or sequence of ints 34 | Location of the sampler arguments of the input function in the form 35 | `function_name(args, *kw)`. If more than one 36 | sampler is allowed, can be a list of locations. 37 | 38 | """ 39 | def decorator(f): 40 | @functools.wraps(f) 41 | def func(*args, **kwargs): 42 | bound_arguments = inspect.signature(f).bind(*args, **kwargs) 43 | bound_arguments.apply_defaults() 44 | 45 | args = bound_arguments.args 46 | kw = bound_arguments.kwargs 47 | 48 | if isinstance(which_args, int): 49 | iter_args = (which_args,) 50 | else: 51 | iter_args = iter(which_args) 52 | 53 | # check each sampler for the correct methods 54 | new_args = [arg for arg in args] 55 | for idx in iter_args: 56 | sampler = args[idx] 57 | 58 | # if no sampler is provided, get the default sampler if it has 59 | # been set 60 | if sampler is None: 61 | # this sampler has already been vetted 62 | default_sampler = dnx.get_default_sampler() 63 | if default_sampler is None: 64 | raise dnx.DWaveNetworkXMissingSampler('no default sampler set') 65 | new_args[idx] = default_sampler 66 | continue 67 | 68 | if not hasattr(sampler, "sample_qubo") or not callable(sampler.sample_qubo): 69 | raise TypeError("expected sampler to have a 'sample_qubo' method") 70 | if not hasattr(sampler, "sample_ising") or not callable(sampler.sample_ising): 71 | raise TypeError("expected sampler to have a 'sample_ising' method") 72 | 73 | # now run the function and return the results 74 | return f(*new_args, **kw) 75 | return func 76 | return decorator 77 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx==3.0 2 | dimod==0.12.17 3 | 4 | oldest-supported-numpy 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license-file = LICENSE.txt 3 | description-file = README.rst 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 | 17 | from setuptools import setup 18 | 19 | # run from the base path 20 | my_loc = os.path.dirname(os.path.abspath(__file__)) 21 | os.chdir(my_loc) 22 | 23 | exec(open(os.path.join(".", "dwave_networkx", "package_info.py")).read()) 24 | 25 | packages = ['dwave_networkx', 26 | 'dwave_networkx.algorithms', 27 | 'dwave_networkx.utils', 28 | 'dwave_networkx.drawing', 29 | 'dwave_networkx.generators', 30 | ] 31 | 32 | install_requires = ['networkx>=2.4', 33 | 'dimod>=0.10.8', 34 | 'numpy>=1.17.3', 35 | ] 36 | 37 | python_requires = ">=3.9" 38 | 39 | classifiers = [ 40 | 'License :: OSI Approved :: Apache Software License', 41 | 'Operating System :: OS Independent', 42 | 'Programming Language :: Python :: 3 :: Only', 43 | 'Programming Language :: Python :: 3.9', 44 | 'Programming Language :: Python :: 3.10', 45 | 'Programming Language :: Python :: 3.11', 46 | 'Programming Language :: Python :: 3.12', 47 | 'Programming Language :: Python :: 3.13', 48 | ] 49 | 50 | setup( 51 | name='dwave_networkx', 52 | version=__version__, 53 | author=__author__, 54 | author_email=__authoremail__, 55 | description=__description__, 56 | long_description=open('README.rst').read(), 57 | url='https://github.com/dwavesystems/dwave_networkx', 58 | download_url='https://github.com/dwavesystems/dwave_networkx/releases', 59 | packages=packages, 60 | license='Apache 2.0', 61 | install_requires=install_requires, 62 | python_requires=python_requires, 63 | classifiers=classifiers, 64 | ) 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwave-networkx/b36c43a076e52669f7a9940c7a6832e24f6849de/tests/__init__.py -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | parameterized==0.7.4 3 | -------------------------------------------------------------------------------- /tests/test_canonicalization.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | import collections 17 | 18 | import dimod 19 | import dwave_networkx as dnx 20 | import networkx as nx 21 | 22 | 23 | class TestRootedTile(unittest.TestCase): 24 | def test_C33_tiles(self): 25 | # not a public function 26 | rooted_tile = dnx.algorithms.canonicalization.rooted_tile 27 | 28 | C33 = dnx.chimera_graph(3, 3, 4) 29 | 30 | for root in range(0, len(C33), 8): 31 | 32 | horiz, vert = rooted_tile(C33.adj, root, 4) 33 | 34 | self.assertEqual(horiz, set(range(root, root+4))) 35 | self.assertEqual(vert, set(range(root+4, root+8))) 36 | 37 | 38 | class TestCanonicalChimeraLabeling(unittest.TestCase): 39 | def test_tile_identity(self): 40 | C1 = dnx.chimera_graph(1) 41 | coord = dnx.chimera_coordinates(1, 1, 4) 42 | 43 | labels = dnx.canonical_chimera_labeling(C1) 44 | labels = {v: coord.chimera_to_linear(labels[v]) for v in labels} 45 | 46 | G = nx.relabel_nodes(C1, labels, copy=True) 47 | 48 | self.assertTrue(nx.is_isomorphic(G, C1)) 49 | self.assertEqual(set(G), set(C1)) 50 | 51 | def test_bqm_tile_identity(self): 52 | J = {e: -1 for e in dnx.chimera_graph(1).edges} 53 | C1bqm = dimod.BinaryQuadraticModel.from_ising({}, J) 54 | coord = dnx.chimera_coordinates(1, 1, 4) 55 | 56 | labels = dnx.canonical_chimera_labeling(C1bqm) 57 | labels = {v: coord.chimera_to_linear(labels[v]) for v in labels} 58 | 59 | bqm = C1bqm.relabel_variables(labels, inplace=False) 60 | 61 | self.assertEqual(bqm, C1bqm) 62 | 63 | def test_row_identity(self): 64 | C41 = dnx.chimera_graph(4, 1) 65 | coord = dnx.chimera_coordinates(4, 1, 4) 66 | 67 | labels = dnx.canonical_chimera_labeling(C41) 68 | labels = {v: coord.chimera_to_linear(labels[v]) for v in labels} 69 | 70 | G = nx.relabel_nodes(C41, labels, copy=True) 71 | 72 | self.assertTrue(nx.is_isomorphic(G, C41)) 73 | 74 | def test_3x3_identity(self): 75 | C33 = dnx.chimera_graph(3, 3) 76 | coord = dnx.chimera_coordinates(3, 3, 4) 77 | 78 | labels = dnx.canonical_chimera_labeling(C33) 79 | labels = {v: coord.chimera_to_linear(labels[v]) for v in labels} 80 | 81 | G = nx.relabel_nodes(C33, labels, copy=True) 82 | 83 | self.assertTrue(nx.is_isomorphic(G, C33)) 84 | 85 | def test_construction_string_labels(self): 86 | C22 = dnx.chimera_graph(2, 2, 3) 87 | coord = dnx.chimera_coordinates(2, 2, 3) 88 | 89 | alpha = 'abcdefghijklmnopqrstuvwxyz' 90 | 91 | bqm = dimod.BinaryQuadraticModel.empty(dimod.BINARY) 92 | 93 | for u, v in reversed(list(C22.edges)): 94 | bqm.add_interaction(alpha[u], alpha[v], 1) 95 | 96 | assert len(bqm.quadratic) == len(C22.edges) 97 | assert len(bqm) == len(C22) 98 | 99 | labels = dnx.canonical_chimera_labeling(bqm) 100 | labels = {v: alpha[coord.chimera_to_linear(labels[v])] for v in labels} 101 | 102 | bqm2 = bqm.relabel_variables(labels, inplace=False) 103 | 104 | self.assertEqual(bqm, bqm2) 105 | 106 | def test_reversed(self): 107 | C33 = nx.Graph() #Ordering is guaranteed Python>=3.7, OrderedGraph is deprecated 108 | C33.add_nodes_from(reversed(range(3*3*4))) 109 | C33.add_edges_from(dnx.chimera_graph(3, 3, 4).edges) 110 | coord = dnx.chimera_coordinates(3, 3, 4) 111 | 112 | labels = dnx.canonical_chimera_labeling(C33) 113 | labels = {v: coord.chimera_to_linear(labels[v]) for v in labels} 114 | 115 | G = nx.relabel_nodes(C33, labels, copy=True) 116 | 117 | self.assertTrue(nx.is_isomorphic(G, C33)) 118 | 119 | def test__shore_size_tiles(self): 120 | shore_size = dnx.algorithms.canonicalization._chimera_shore_size 121 | 122 | for t in range(1, 8): 123 | G = dnx.chimera_graph(1, 1, t) 124 | self.assertEqual(shore_size(G.adj, len(G.edges)), t) 125 | 126 | def test__shore_size_columns(self): 127 | shore_size = dnx.algorithms.canonicalization._chimera_shore_size 128 | 129 | # 2, 1, 1 is the same as 1, 1, 2 130 | for m in range(2, 11): 131 | for t in range(9, 1, -1): 132 | G = dnx.chimera_graph(m, 1, t) 133 | self.assertEqual(shore_size(G.adj, len(G.edges)), t) 134 | 135 | def test__shore_size_rectangles(self): 136 | shore_size = dnx.algorithms.canonicalization._chimera_shore_size 137 | 138 | # 2, 1, 1 is the same as 1, 1, 2 139 | for m in range(2, 7): 140 | for n in range(2, 7): 141 | for t in range(1, 6): 142 | G = dnx.chimera_graph(m, n, t) 143 | self.assertEqual(shore_size(G.adj, len(G.edges)), t) 144 | -------------------------------------------------------------------------------- /tests/test_chimera_layout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 17 | 18 | import networkx as nx 19 | import dwave_networkx as dnx 20 | 21 | try: 22 | import matplotlib.pyplot as plt 23 | except ImportError: 24 | plt = False 25 | 26 | try: 27 | import numpy as np 28 | except ImportError: 29 | np = False 30 | 31 | _display = os.environ.get('DISPLAY', '') != '' 32 | 33 | 34 | @unittest.skipUnless(np and plt, "No numpy or matplotlib") 35 | class TestDrawing(unittest.TestCase): 36 | def test_chimera_layout_basic(self): 37 | G = dnx.chimera_graph(1, 1, 4) 38 | pos = dnx.chimera_layout(G) 39 | 40 | def test_chimera_layout_typical(self): 41 | G = dnx.chimera_graph(2, 2, 4) 42 | pos = dnx.chimera_layout(G) 43 | 44 | def test_chimera_layout_center(self): 45 | G = dnx.chimera_graph(2, 2, 4) 46 | pos = dnx.chimera_layout(G, center=(5, 5)) 47 | with self.assertRaises(ValueError): 48 | pos = dnx.chimera_layout(G, center=(5, 5, 5)) 49 | 50 | def test_chimera_layout_lowdim(self): 51 | G = dnx.chimera_graph(2, 2, 4) 52 | with self.assertRaises(ValueError): 53 | pos = dnx.chimera_layout(G, dim=1) 54 | 55 | def test_chimera_layout_weird_nodata(self): 56 | G = dnx.chimera_graph(2, 2, 4) 57 | del G.graph["family"] 58 | with self.assertRaises(ValueError): 59 | pos = dnx.chimera_layout(G, dim=1) 60 | 61 | def test_chimera_layout_no_chimera_indices(self): 62 | G = nx.Graph() 63 | G.add_edges_from([(0, 2), (1, 2), (1, 3), (0, 3)]) 64 | pos = dnx.chimera_layout(G) 65 | pos2 = dnx.chimera_layout(dnx.chimera_graph(1, 1, 2)) 66 | 67 | for v in pos: 68 | self.assertTrue(all(pos[v] == pos2[v])) 69 | for v in pos2: 70 | self.assertIn(v, pos) 71 | 72 | def test_chimera_layout_coords(self): 73 | G = dnx.chimera_graph(2, 2, 4, coordinates=True) 74 | pos = dnx.chimera_layout(G) 75 | 76 | def test_chimera_layout_nodata(self): 77 | G = dnx.chimera_graph(2, 2, 4, data=False) 78 | pos = dnx.chimera_layout(G) 79 | 80 | def test_chimera_layout_edgelist_singletile(self): 81 | G = dnx.chimera_graph(1, 1, 16, data=False) 82 | pos = dnx.chimera_layout(G.edges()) 83 | 84 | @unittest.skipUnless(_display, " No display found") 85 | def test_draw_chimera_yield(self): 86 | G = dnx.chimera_graph(2, 2, 4, data=False) 87 | G.remove_edges_from([(0,6),(10,13),(26,31)]) 88 | G.remove_nodes_from([18,23]) 89 | dnx.draw_chimera_yield(G) 90 | 91 | @unittest.skipUnless(_display, " No display found") 92 | def test_draw_chimera_biases(self): 93 | G = dnx.chimera_graph(8) 94 | h = {v: v % 12 for v in G} 95 | J = {(u, v) if u % 2 else (v, u): (u+v) % 24 for u, v in G.edges()} 96 | for v in G: 97 | J[v, v] = .1 98 | 99 | dnx.draw_chimera(G, linear_biases=h, quadratic_biases=J) 100 | 101 | @unittest.skipUnless(_display, " No display found") 102 | def test_draw_chimera_embedding(self): 103 | C = dnx.chimera_graph(4) 104 | G = nx.grid_graph([2, 3, 2]) 105 | emb = {(0, 0, 0): [80, 48], (0, 0, 1): [50, 52], (0, 1, 0): [85, 93], 106 | (0, 1, 1): [84, 82], (0, 2, 0): [89], (0, 2, 1): [92], 107 | (1, 0, 0): [49, 54], (1, 0, 1): [83, 51], (1, 1, 0): [81], 108 | (1, 1, 1): [86, 94], (1, 2, 0): [87, 95], (1, 2, 1): [91]} 109 | dnx.draw_chimera_embedding(C, emb) 110 | dnx.draw_chimera_embedding(C, emb, embedded_graph=G) 111 | dnx.draw_chimera_embedding(C, emb, interaction_edges=C.edges()) 112 | 113 | @unittest.skipUnless(_display, " No display found") 114 | def test_draw_overlapped_chimera_embedding(self): 115 | C = dnx.chimera_graph(2) 116 | emb = {0: [1, 5], 1: [5, 9, 13], 2: [25, 29], 3: [17, 21]} 117 | dnx.draw_chimera_embedding(C, emb, overlapped_embedding=True) 118 | dnx.draw_chimera_embedding(C, emb, overlapped_embedding=True, show_labels=True) 119 | -------------------------------------------------------------------------------- /tests/test_clique.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | import networkx as nx 17 | import dimod 18 | import dwave_networkx as dnx 19 | 20 | 21 | class TestIsClique(unittest.TestCase): 22 | def test_empty(self): 23 | G = nx.Graph() 24 | self.assertTrue(dnx.is_clique(G, [])) 25 | 26 | def test_K1(self): 27 | G = nx.complete_graph(1) 28 | self.assertTrue(dnx.is_clique(G, [0])) 29 | self.assertTrue(dnx.is_clique(G, [])) 30 | 31 | def test_K2(self): 32 | G = nx.complete_graph(2) 33 | self.assertTrue(dnx.is_clique(G, [0])) 34 | self.assertTrue(dnx.is_clique(G, [])) 35 | self.assertTrue(dnx.is_clique(G, [0, 1])) 36 | 37 | def test_path3(self): 38 | G = nx.path_graph(3) 39 | self.assertTrue(dnx.is_clique(G, [0])) 40 | self.assertTrue(dnx.is_clique(G, [0, 1])) 41 | self.assertTrue(dnx.is_clique(G, [])) 42 | self.assertFalse(dnx.is_clique(G, [0, 2])) 43 | 44 | 45 | class TestMaxClique(unittest.TestCase): 46 | def test_maximum_independent_set_basic(self): 47 | """Runs the function on some small and simple graphs, just to make 48 | sure it works in basic functionality. 49 | """ 50 | G = dnx.chimera_graph(1, 2, 2) 51 | clique = dnx.maximum_clique(G, dimod.ExactSolver()) 52 | self.assertTrue(dnx.is_clique(G, clique)) 53 | 54 | G = nx.path_graph(5) 55 | clique = dnx.maximum_clique(G, dimod.ExactSolver()) 56 | self.assertTrue(dnx.is_clique(G, clique)) 57 | 58 | def test_default_sampler(self): 59 | G = nx.complete_graph(5) 60 | dnx.set_default_sampler(dimod.ExactSolver()) 61 | self.assertIsNot(dnx.get_default_sampler(), None) 62 | 63 | clique = dnx.maximum_clique(G) 64 | dnx.unset_default_sampler() 65 | self.assertEqual(dnx.get_default_sampler(), None, 66 | "sampler did not unset correctly") 67 | 68 | def test_two_cliques(self): 69 | # This graph has two major cliques [0,1,2,3,4] and [11,12,13,14] 70 | # but the first one is bigger so that's the maximum_clique. 71 | G = nx.complete_graph(5) 72 | nx.add_path(G, [4, 5, 6, 7, 8]) 73 | nx.add_path(G, [2, 9, 10]) 74 | nx.add_path(G, [9, 11]) 75 | nx.add_path(G, [11, 12, 13, 14, 11]) 76 | nx.add_path(G, [12, 14]) 77 | nx.add_path(G, [13, 11]) 78 | clique = dnx.maximum_clique(G, dimod.ExactSolver()) 79 | self.assertEqual(clique, [0, 1, 2, 3, 4]) 80 | 81 | 82 | class TestCliqueNumber(unittest.TestCase): 83 | def test_complete_graph(self): 84 | # In a complete graph the whole graph is a clique so every vertex is a 85 | # part of the clique. 86 | G = nx.complete_graph(17) 87 | clique_number = dnx.clique_number(G, dimod.ExactSolver()) 88 | self.assertEqual(clique_number, 17) 89 | 90 | def test_two_cliques_num(self): 91 | # This graph has two major cliques [0,1,2,3,4] and [11,12,13,14] 92 | # but the first one is bigger so that's the maximum_clique. 93 | G = nx.complete_graph(5) 94 | nx.add_path(G, [4, 5, 6, 7, 8]) 95 | nx.add_path(G, [2, 9, 10]) 96 | nx.add_path(G, [9, 11]) 97 | nx.add_path(G, [11, 12, 13, 14, 11]) 98 | nx.add_path(G, [12, 14]) 99 | nx.add_path(G, [13, 11]) 100 | clique_number = dnx.clique_number(G, dimod.ExactSolver()) 101 | self.assertEqual(clique_number, 5) 102 | -------------------------------------------------------------------------------- /tests/test_coloring.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | import itertools 17 | 18 | import networkx as nx 19 | 20 | import dimod 21 | 22 | import dwave_networkx as dnx 23 | 24 | 25 | class TestMinVertexColor(unittest.TestCase): 26 | 27 | def test_5path(self): 28 | G = nx.path_graph(5) 29 | coloring = dnx.min_vertex_coloring(G, dimod.ExactSolver()) 30 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 31 | self.assertEqual(len(set(coloring.values())), 2) # bipartite 32 | 33 | def test_odd_cycle_graph(self): 34 | """Graph that is an odd circle""" 35 | G = nx.cycle_graph(5) 36 | coloring = dnx.min_vertex_coloring(G, dimod.ExactSolver()) 37 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 38 | 39 | def test_disconnected_graph(self): 40 | """One edge and one disconnected node""" 41 | G = nx.path_graph(2) 42 | G.add_node(3) 43 | 44 | coloring = dnx.min_vertex_coloring(G, dimod.ExactSolver()) 45 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 46 | 47 | def test_disconnected_cycle_graph(self): 48 | G = nx.complete_graph(3) # odd 3-cycle 49 | G.add_node(4) # floating node 50 | coloring = dnx.min_vertex_coloring(G, dimod.ExactSolver()) 51 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 52 | 53 | 54 | class TestMinVertexColorQubo(unittest.TestCase): 55 | def test_chromatic_number(self): 56 | G = nx.cycle_graph('abcd') 57 | 58 | # when the chromatic number is fixed this is exactly vertex_color 59 | self.assertEqual(dnx.min_vertex_color_qubo(G, chromatic_lb=2, 60 | chromatic_ub=2), 61 | dnx.vertex_color_qubo(G, 2)) 62 | 63 | 64 | class TestVertexColor(unittest.TestCase): 65 | def test_4cycle(self): 66 | G = nx.cycle_graph('abcd') 67 | 68 | coloring = dnx.vertex_color(G, 2, dimod.ExactSolver()) 69 | 70 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 71 | 72 | def test_4cycle_with_chord(self): 73 | G = nx.cycle_graph(4) 74 | G.add_edge(0, 2) 75 | 76 | # need 3 colors in this case 77 | coloring = dnx.vertex_color(G, 3, dimod.ExactSolver()) 78 | 79 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 80 | 81 | def test_5cycle(self): 82 | G = nx.cycle_graph(5) 83 | coloring = dnx.vertex_color(G, 3, dimod.ExactSolver()) 84 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 85 | 86 | def test_disconnected_cycle_graph(self): 87 | G = nx.complete_graph(3) # odd 3-cycle 88 | G.add_node(4) # floating node 89 | coloring = dnx.vertex_color(G, 3, dimod.ExactSolver()) 90 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 91 | 92 | 93 | class TestVertexColorQUBO(unittest.TestCase): 94 | def test_single_node(self): 95 | G = nx.Graph() 96 | G.add_node('a') 97 | 98 | # a single color 99 | Q = dnx.vertex_color_qubo(G, ['red']) 100 | 101 | self.assertEqual(Q, {(('a', 'red'), ('a', 'red')): -1}) 102 | 103 | def test_4cycle(self): 104 | G = nx.cycle_graph('abcd') 105 | 106 | Q = dnx.vertex_color_qubo(G, 2) 107 | 108 | sampleset = dimod.ExactSolver().sample_qubo(Q) 109 | 110 | # check that the ground state is a valid coloring 111 | ground_energy = sampleset.first.energy 112 | 113 | colorings = [] 114 | for sample, en in sampleset.data(['sample', 'energy']): 115 | if en > ground_energy: 116 | break 117 | 118 | coloring = {} 119 | for (v, c), val in sample.items(): 120 | if val: 121 | coloring[v] = c 122 | 123 | self.assertTrue(dnx.is_vertex_coloring(G, coloring)) 124 | 125 | colorings.append(coloring) 126 | 127 | # there are two valid colorings 128 | self.assertEqual(len(colorings), 2) 129 | 130 | self.assertEqual(ground_energy, -len(G)) 131 | 132 | def test_num_variables(self): 133 | G = nx.Graph() 134 | G.add_nodes_from(range(15)) 135 | 136 | Q = dnx.vertex_color_qubo(G, 7) 137 | bqm = dimod.BinaryQuadraticModel.from_qubo(Q) 138 | self.assertEqual(len(bqm.quadratic), len(G)*7*(7-1)/2) 139 | 140 | # add one edge 141 | G.add_edge(0, 1) 142 | Q = dnx.vertex_color_qubo(G, 7) 143 | bqm = dimod.BinaryQuadraticModel.from_qubo(Q) 144 | self.assertEqual(len(bqm.quadratic), len(G)*7*(7-1)/2 + 7) 145 | 146 | def test_docstring_stats(self): 147 | # get a complex-ish graph 148 | G = nx.karate_club_graph() 149 | 150 | colors = range(10) 151 | 152 | Q = dnx.vertex_color_qubo(G, colors) 153 | 154 | bqm = dimod.BinaryQuadraticModel.from_qubo(Q) 155 | 156 | self.assertEqual(len(bqm), len(G)*len(colors)) 157 | self.assertEqual(len(bqm.quadratic), len(G)*len(colors)*(len(colors)-1)/2 158 | + len(G.edges)*len(colors)) 159 | -------------------------------------------------------------------------------- /tests/test_cover.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | import random 17 | 18 | import networkx as nx 19 | import dwave_networkx as dnx 20 | 21 | from dimod import ExactSolver, SimulatedAnnealingSampler 22 | 23 | 24 | class TestCover(unittest.TestCase): 25 | 26 | def test_vertex_cover_basic(self): 27 | """Runs the function on some small and simple graphs, just to make 28 | sure it works in basic functionality. 29 | """ 30 | G = dnx.chimera_graph(1, 2, 2) 31 | cover = dnx.min_vertex_cover(G, ExactSolver()) 32 | self.vertex_cover_check(G, cover) 33 | 34 | G = nx.path_graph(5) 35 | cover = dnx.min_vertex_cover(G, ExactSolver()) 36 | self.vertex_cover_check(G, cover) 37 | 38 | for __ in range(10): 39 | G = nx.gnp_random_graph(5, .5) 40 | cover = dnx.min_vertex_cover(G, ExactSolver()) 41 | self.vertex_cover_check(G, cover) 42 | 43 | def test_vertex_cover_weighted(self): 44 | weight = 'weight' 45 | G = nx.path_graph(6) 46 | 47 | # favor even nodes 48 | nx.set_node_attributes(G, {node: node % 2 + 1 for node in G}, weight) 49 | cover = dnx.min_weighted_vertex_cover(G, weight, ExactSolver()) 50 | self.assertEqual(set(cover), {0, 2, 4}) 51 | 52 | # favor odd nodes 53 | nx.set_node_attributes(G, {node: (node + 1) % 2 + 1 for node in G}, weight) 54 | cover = dnx.min_weighted_vertex_cover(G, weight, ExactSolver()) 55 | self.assertEqual(set(cover), {1, 3, 5}) 56 | 57 | # make nodes 1 and 4 unlikely 58 | nx.set_node_attributes(G, {0: 1, 1: 3, 2: 1, 3: 1, 4: 3, 5: 1}, weight) 59 | cover = dnx.min_weighted_vertex_cover(G, weight, ExactSolver()) 60 | self.assertEqual(set(cover), {0, 2, 3, 5}) 61 | 62 | for __ in range(10): 63 | G = nx.gnp_random_graph(5, .5) 64 | nx.set_node_attributes(G, {node: random.random() for node in G}, weight) 65 | cover = dnx.min_weighted_vertex_cover(G, weight, ExactSolver()) 66 | self.vertex_cover_check(G, cover) 67 | 68 | def test_default_sampler(self): 69 | G = nx.complete_graph(5) 70 | 71 | dnx.set_default_sampler(ExactSolver()) 72 | self.assertIsNot(dnx.get_default_sampler(), None) 73 | cover = dnx.min_vertex_cover(G) 74 | dnx.unset_default_sampler() 75 | self.assertEqual(dnx.get_default_sampler(), None, "sampler did not unset correctly") 76 | 77 | def test_dimod_vs_list(self): 78 | G = nx.path_graph(5) 79 | 80 | cover = dnx.min_vertex_cover(G, ExactSolver()) 81 | cover = dnx.min_vertex_cover(G, SimulatedAnnealingSampler()) 82 | 83 | def vertex_cover_check(self, G, cover): 84 | # each node in the vertex cover should be in G 85 | self.assertTrue(dnx.is_vertex_cover(G, cover)) 86 | -------------------------------------------------------------------------------- /tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | 17 | import dwave_networkx as dnx 18 | from dwave_networkx.utils import binary_quadratic_model_sampler 19 | 20 | 21 | class MockSampler: 22 | def sample_ising(self, h, J): 23 | pass 24 | 25 | def sample_qubo(self, Q): 26 | pass 27 | 28 | 29 | @binary_quadratic_model_sampler(1) 30 | def mock_function(G, sampler=None, **sampler_args): 31 | assert sampler is not None 32 | 33 | 34 | class TestDecorators(unittest.TestCase): 35 | 36 | def test_default_set(self): 37 | dnx.set_default_sampler(MockSampler()) 38 | mock_function(0) 39 | dnx.unset_default_sampler() 40 | 41 | self.assertEqual(dnx.get_default_sampler(), None, 42 | "sampler did not unset correctly") 43 | 44 | def test_no_sampler_set(self): 45 | with self.assertRaises(dnx.DWaveNetworkXMissingSampler): 46 | mock_function(0) 47 | 48 | def test_sampler_provided(self): 49 | mock_function(0, MockSampler()) 50 | -------------------------------------------------------------------------------- /tests/test_generator_markov.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 | import unittest 16 | 17 | import dwave_networkx as dnx 18 | 19 | 20 | class Test_markov_network(unittest.TestCase): 21 | def test_empty(self): 22 | MN = dnx.markov_network({}) 23 | 24 | def test_one_node(self): 25 | MN = dnx.markov_network({'a': {(0,): 1.2, (1,): .4}}) 26 | 27 | def test_one_edge(self): 28 | MN = dnx.markov_network({'ab': {(0, 0): 1.2, (1, 0): .4, (0, 1): 1.3, (1, 1): -4}}) 29 | -------------------------------------------------------------------------------- /tests/test_independent_set.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | 17 | import networkx as nx 18 | import dimod 19 | import dwave_networkx as dnx 20 | 21 | 22 | class TestIsIndependentSet(unittest.TestCase): 23 | def test_empty(self): 24 | G = nx.Graph() 25 | 26 | self.assertTrue(dnx.is_independent_set(G, [])) 27 | 28 | def test_K1(self): 29 | G = nx.complete_graph(1) 30 | 31 | self.assertTrue(dnx.is_independent_set(G, [0])) 32 | self.assertTrue(dnx.is_independent_set(G, [])) 33 | 34 | def test_K2(self): 35 | G = nx.complete_graph(2) 36 | 37 | self.assertTrue(dnx.is_independent_set(G, [0])) 38 | self.assertTrue(dnx.is_independent_set(G, [])) 39 | self.assertFalse(dnx.is_independent_set(G, [0, 1])) 40 | 41 | def test_path3(self): 42 | G = nx.path_graph(3) 43 | 44 | self.assertTrue(dnx.is_independent_set(G, [0])) 45 | self.assertTrue(dnx.is_independent_set(G, [0, 2])) 46 | self.assertTrue(dnx.is_independent_set(G, [])) 47 | self.assertFalse(dnx.is_independent_set(G, [0, 1])) 48 | 49 | 50 | class TestWeightedMaximumIndependentSet(unittest.TestCase): 51 | def test_empty(self): 52 | G = nx.Graph() 53 | 54 | Q = dnx.maximum_weighted_independent_set_qubo(G) 55 | 56 | self.assertEqual(Q, {}) 57 | 58 | def test_K1_no_weights(self): 59 | G = nx.complete_graph(1) 60 | 61 | Q = dnx.maximum_weighted_independent_set_qubo(G) 62 | 63 | self.assertEqual(Q, {(0, 0): -1}) 64 | 65 | def test_K1_weighted(self): 66 | G = nx.Graph() 67 | G.add_node(0, weight=.5) 68 | 69 | Q = dnx.maximum_weighted_independent_set_qubo(G) 70 | 71 | self.assertEqual(Q, {(0, 0): -1.}) # should be scaled to 1 72 | 73 | def test_K2_weighted(self): 74 | G = nx.Graph() 75 | G.add_node(0, weight=.5) 76 | G.add_node(1, weight=1) 77 | G.add_edge(0, 1) 78 | 79 | Q = dnx.maximum_weighted_independent_set_qubo(G, weight='weight') 80 | 81 | self.assertEqual(Q, {(0, 0): -.5, (1, 1): -1, (0, 1): 2.0}) 82 | 83 | def test_K2_partially_weighted(self): 84 | G = nx.Graph() 85 | G.add_node(0, weight=.5) 86 | G.add_node(1) 87 | G.add_edge(0, 1) 88 | 89 | Q = dnx.maximum_weighted_independent_set_qubo(G, weight='weight') 90 | 91 | self.assertEqual(Q, {(0, 0): -.5, (1, 1): -1, (0, 1): 2.0}) 92 | 93 | def test_path3_weighted(self): 94 | G = nx.path_graph(3) 95 | G.nodes[1]['weight'] = 2.1 96 | 97 | Q = dnx.maximum_weighted_independent_set_qubo(G, weight='weight') 98 | 99 | self.assertLess(dimod.qubo_energy({0: 0, 1: 1, 2: 0}, Q), 100 | dimod.qubo_energy({0: 1, 1: 0, 2: 1}, Q)) 101 | 102 | 103 | class TestIndepSet(unittest.TestCase): 104 | 105 | def test_maximum_independent_set_basic(self): 106 | """Runs the function on some small and simple graphs, just to make 107 | sure it works in basic functionality. 108 | """ 109 | G = dnx.chimera_graph(1, 2, 2) 110 | indep_set = dnx.maximum_independent_set(G, dimod.ExactSolver()) 111 | self.assertTrue(dnx.is_independent_set(G, indep_set)) 112 | 113 | G = nx.path_graph(5) 114 | indep_set = dnx.maximum_independent_set(G, dimod.ExactSolver()) 115 | self.assertTrue(dnx.is_independent_set(G, indep_set)) 116 | 117 | def test_maximum_independent_set_weighted(self): 118 | weight = 'weight' 119 | G = nx.path_graph(6) 120 | 121 | # favor odd nodes 122 | nx.set_node_attributes(G, {node: node % 2 + 1 for node in G}, weight) 123 | indep_set = dnx.maximum_weighted_independent_set(G, weight, dimod.ExactSolver()) 124 | self.assertEqual(set(indep_set), {1, 3, 5}) 125 | 126 | # favor even nodes 127 | nx.set_node_attributes(G, {node: (node + 1) % 2 + 1 for node in G}, weight) 128 | indep_set = dnx.maximum_weighted_independent_set(G, weight, dimod.ExactSolver()) 129 | self.assertEqual(set(indep_set), {0, 2, 4}) 130 | 131 | # make nodes 1 and 4 likely 132 | nx.set_node_attributes(G, {0: 1, 1: 3, 2: 1, 3: 1, 4: 3, 5: 1}, weight) 133 | indep_set = dnx.maximum_weighted_independent_set(G, weight, dimod.ExactSolver()) 134 | self.assertEqual(set(indep_set), {1, 4}) 135 | 136 | def test_default_sampler(self): 137 | G = nx.complete_graph(5) 138 | 139 | dnx.set_default_sampler(dimod.ExactSolver()) 140 | self.assertIsNot(dnx.get_default_sampler(), None) 141 | indep_set = dnx.maximum_independent_set(G) 142 | dnx.unset_default_sampler() 143 | self.assertEqual(dnx.get_default_sampler(), None, "sampler did not unset correctly") 144 | 145 | def test_dimod_vs_list(self): 146 | G = nx.path_graph(5) 147 | 148 | indep_set = dnx.maximum_independent_set(G, dimod.ExactSolver()) 149 | indep_set = dnx.maximum_independent_set(G, dimod.SimulatedAnnealingSampler()) 150 | -------------------------------------------------------------------------------- /tests/test_markov.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 | import unittest 16 | import dimod 17 | 18 | import dwave_networkx as dnx 19 | 20 | 21 | class Test_sample_markov_network_bqm(unittest.TestCase): 22 | def test_one_node(self): 23 | potentials = {'a': {(0,): 1.2, (1,): .4}} 24 | 25 | bqm = dnx.markov_network_bqm(dnx.markov_network(potentials)) 26 | 27 | for edge, potential in potentials.items(): 28 | for config, energy in potential.items(): 29 | sample = dict(zip(edge, config)) 30 | self.assertAlmostEqual(bqm.energy(sample), energy) 31 | 32 | def test_one_edge(self): 33 | potentials = {'ab': {(0, 0): 1.2, (1, 0): .4, 34 | (0, 1): 1.3, (1, 1): -4}} 35 | 36 | bqm = dnx.markov_network_bqm(dnx.markov_network(potentials)) 37 | 38 | for edge, potential in potentials.items(): 39 | for config, energy in potential.items(): 40 | sample = dict(zip(edge, config)) 41 | self.assertAlmostEqual(bqm.energy(sample), energy) 42 | 43 | def test_typical(self): 44 | potentials = {'a': {(0,): 1.5, (1,): -.5}, 45 | 'ab': {(0, 0): 1.2, (1, 0): .4, 46 | (0, 1): 1.3, (1, 1): -4}, 47 | 'bc': {(0, 0): 1.7, (1, 0): .4, 48 | (0, 1): -1, (1, 1): -4}, 49 | 'd': {(0,): -.5, (1,): 1.6}} 50 | 51 | bqm = dnx.markov_network_bqm(dnx.markov_network(potentials)) 52 | 53 | samples = dimod.ExactSolver().sample(bqm) 54 | 55 | for sample, energy in samples.data(['sample', 'energy']): 56 | 57 | en = 0 58 | for interaction, potential in potentials.items(): 59 | config = tuple(sample[v] for v in interaction) 60 | en += potential[config] 61 | 62 | self.assertAlmostEqual(en, energy) 63 | 64 | 65 | class Test_sample_markov_network(unittest.TestCase): 66 | def test_typical_return_sampleset(self): 67 | potentials = {'a': {(0,): 1.5, (1,): -.5}, 68 | 'ab': {(0, 0): 1.2, (1, 0): .4, 69 | (0, 1): 1.3, (1, 1): -4}, 70 | 'bc': {(0, 0): 1.7, (1, 0): .4, 71 | (0, 1): -1, (1, 1): -4}, 72 | 'd': {(0,): -.5, (1,): 1.6}} 73 | 74 | MN = dnx.markov_network(potentials) 75 | 76 | samples = dnx.sample_markov_network(MN, dimod.ExactSolver(), 77 | fixed_variables={'c': 0}, 78 | return_sampleset=True) 79 | 80 | for sample, energy in samples.data(['sample', 'energy']): 81 | self.assertEqual(sample['c'], 0) 82 | 83 | en = 0 84 | for interaction, potential in potentials.items(): 85 | config = tuple(sample[v] for v in interaction) 86 | en += potential[config] 87 | 88 | self.assertAlmostEqual(en, energy) 89 | 90 | def test_typical(self): 91 | potentials = {'a': {(0,): 1.5, (1,): -.5}, 92 | 'ab': {(0, 0): 1.2, (1, 0): .4, 93 | (0, 1): 1.3, (1, 1): -4}, 94 | 'bc': {(0, 0): 1.7, (1, 0): .4, 95 | (0, 1): -1, (1, 1): -4}, 96 | 'd': {(0,): -.5, (1,): 1.6}} 97 | 98 | MN = dnx.markov_network(potentials) 99 | 100 | bqm = dnx.markov_network_bqm(MN) 101 | 102 | samples = dnx.sample_markov_network(MN, dimod.ExactSolver(), 103 | fixed_variables={'c': 0}) 104 | 105 | for sample in samples: 106 | self.assertEqual(sample['c'], 0) 107 | 108 | en = 0 109 | for interaction, potential in potentials.items(): 110 | config = tuple(sample[v] for v in interaction) 111 | en += potential[config] 112 | 113 | self.assertAlmostEqual(en, bqm.energy(sample)) 114 | -------------------------------------------------------------------------------- /tests/test_matching.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | 17 | import dimod 18 | import networkx as nx 19 | import parameterized 20 | 21 | import dwave_networkx as dnx 22 | 23 | 24 | @parameterized.parameterized_class( 25 | 'graph', 26 | [[nx.Graph()], 27 | [nx.path_graph(10)], 28 | [nx.complete_graph(4)], 29 | [nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (2, 4), (3, 4), (3, 5)])], 30 | [nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (1, 5)])], 31 | [nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (2, 4), (3, 4)])], 32 | [nx.Graph({0: [], 1: [6], 2: [5], 3: [4], 4: [3], 2: [5], 6: [1]})] 33 | # [nx.Graph([(0, 3), (0, 5), (0, 6), (1, 2), (1, 3), (1, 4), (1, 7), 34 | # (2, 4), (2, 6), (3, 6), (3, 7), (4, 7), (6, 7)])], # slow 35 | ] 36 | ) 37 | class TestMatching(unittest.TestCase): 38 | def test_matching_bqm(self): 39 | bqm = dnx.matching_bqm(self.graph) 40 | 41 | # the ground states should be exactly the matchings of G 42 | sampleset = dimod.ExactSolver().sample(bqm) 43 | 44 | for sample, energy in sampleset.data(['sample', 'energy']): 45 | edges = [v for v, val in sample.items() if val > 0] 46 | self.assertEqual(nx.is_matching(self.graph, edges), energy == 0) 47 | self.assertTrue(energy == 0 or energy >= 1) 48 | 49 | # while we're at it, test deprecated is_matching 50 | with self.assertWarns(DeprecationWarning): 51 | self.assertEqual(nx.is_matching(self.graph, edges), 52 | dnx.is_matching(edges)) 53 | 54 | def test_maximal_matching(self): 55 | matching = dnx.algorithms.matching.maximal_matching( 56 | self.graph, dimod.ExactSolver()) 57 | self.assertTrue(nx.is_maximal_matching(self.graph, matching)) 58 | 59 | def test_maximal_matching_bqm(self): 60 | bqm = dnx.maximal_matching_bqm(self.graph) 61 | 62 | # the ground states should be exactly the maximal matchings of G 63 | sampleset = dimod.ExactSolver().sample(bqm) 64 | 65 | for sample, energy in sampleset.data(['sample', 'energy']): 66 | edges = set(v for v, val in sample.items() if val > 0) 67 | self.assertEqual(nx.is_maximal_matching(self.graph, edges), 68 | energy == 0) 69 | self.assertGreaterEqual(energy, 0) 70 | 71 | # while we're at it, test deprecated is_maximal_matching 72 | with self.assertWarns(DeprecationWarning): 73 | self.assertEqual(nx.is_maximal_matching(self.graph, edges), 74 | dnx.is_maximal_matching(self.graph, edges)) 75 | 76 | def test_min_maximal_matching(self): 77 | matching = dnx.min_maximal_matching(self.graph, dimod.ExactSolver()) 78 | self.assertTrue(nx.is_maximal_matching(self.graph, matching)) 79 | 80 | def test_min_maximal_matching_bqm(self): 81 | bqm = dnx.min_maximal_matching_bqm(self.graph) 82 | 83 | if len(self.graph) == 0: 84 | self.assertEqual(len(bqm.linear), 0) 85 | return 86 | 87 | # the ground states should be exactly the minimum maximal matchings of 88 | # G 89 | sampleset = dimod.ExactSolver().sample(bqm) 90 | 91 | # we'd like to use sampleset.lowest() but it didn't exist in dimod 92 | # 0.8.0 93 | ground_energy = sampleset.first.energy 94 | cardinalities = set() 95 | for sample, energy in sampleset.data(['sample', 'energy']): 96 | if energy > ground_energy: 97 | continue 98 | edges = set(v for v, val in sample.items() if val > 0) 99 | self.assertTrue(nx.is_maximal_matching(self.graph, edges)) 100 | cardinalities.add(len(edges)) 101 | 102 | # all ground have the same cardinality (or it's empty) 103 | self.assertEqual(len(cardinalities), 1) 104 | cardinality, = cardinalities 105 | 106 | # everything that's not ground has a higher energy 107 | for sample, energy in sampleset.data(['sample', 'energy']): 108 | edges = set(v for v, val in sample.items() if val > 0) 109 | if energy != sampleset.first.energy: 110 | if nx.is_maximal_matching(self.graph, edges): 111 | self.assertGreater(len(edges), cardinality) 112 | 113 | 114 | class TestMinMaximalMatching(unittest.TestCase): 115 | def test_default_sampler(self): 116 | G = nx.complete_graph(5) 117 | 118 | dnx.set_default_sampler(dimod.ExactSolver()) 119 | self.assertIsNot(dnx.get_default_sampler(), None) 120 | matching = dnx.algorithms.matching.maximal_matching(G) 121 | matching = dnx.min_maximal_matching(G) 122 | dnx.unset_default_sampler() 123 | self.assertEqual(dnx.get_default_sampler(), None, 124 | "sampler did not unset correctly") 125 | -------------------------------------------------------------------------------- /tests/test_max_cut.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | 17 | import networkx as nx 18 | import dwave_networkx as dnx 19 | 20 | from dimod import ExactSolver, SimulatedAnnealingSampler, qubo_energy 21 | 22 | 23 | class TestMaxCut(unittest.TestCase): 24 | # def test_edge_cases(self): 25 | # # get the empty graph 26 | # G = nx.Graph() 27 | 28 | # S = dnx.maximum_cut(G, ExactSolver()) 29 | # self.assertTrue(len(S) == 0) 30 | 31 | # S = dnx.weighted_maximum_cut(G, ExactSolver()) 32 | # self.assertTrue(len(S) == 0) 33 | 34 | def test_typical_cases(self): 35 | 36 | G = nx.complete_graph(10) 37 | 38 | S = dnx.maximum_cut(G, ExactSolver()) 39 | self.assertTrue(len(S) == 5) # half of the nodes 40 | 41 | with self.assertRaises(dnx.DWaveNetworkXException): 42 | S = dnx.weighted_maximum_cut(G, ExactSolver()) 43 | 44 | nx.set_edge_attributes(G, 1, 'weight') 45 | S = dnx.weighted_maximum_cut(G, ExactSolver()) 46 | self.assertTrue(len(S) == 5) # half of the nodes 47 | 48 | G = nx.Graph() 49 | G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 3), (3, 4), (2, 4)]) 50 | S = dnx.maximum_cut(G, ExactSolver()) 51 | self.assertTrue(len(S) in (2, 3)) 52 | 53 | # this needs another one for weight 54 | -------------------------------------------------------------------------------- /tests/test_parallel_embeddings.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 D-Wave 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 | import os 15 | import unittest 16 | from itertools import product 17 | 18 | import networkx as nx 19 | 20 | from dwave_networkx.drawing.parallel_embeddings import ( 21 | _generate_edge_color_dict, 22 | _generate_node_color_dict, 23 | draw_parallel_embeddings, 24 | ) 25 | from dwave_networkx import chimera_graph 26 | 27 | try: 28 | import matplotlib 29 | 30 | _plt = True 31 | except ImportError: 32 | _plt = False 33 | 34 | try: 35 | import numpy as np 36 | 37 | _np = True 38 | except ImportError: 39 | _np = False 40 | 41 | _display = os.environ.get("DISPLAY", "") != "" 42 | 43 | 44 | @unittest.skipUnless(_np and _plt, "matplotlib and numpy required") 45 | class TestEmbeddings(unittest.TestCase): 46 | 47 | @unittest.skipUnless(_display, " No display found") 48 | def test_draw_parallel_embeddings(self): 49 | S = chimera_graph(1) 50 | G = chimera_graph(2) 51 | for one_to_iterable in [True, False]: 52 | if one_to_iterable: 53 | embeddings = [ 54 | {i: (i + 8 * offset,) for i in range(8)} for offset in range(4) 55 | ] 56 | else: 57 | embeddings = [ 58 | {i: i + 8 * offset for i in range(8)} for offset in range(4) 59 | ] 60 | draw_parallel_embeddings( 61 | G=G, embeddings=embeddings, S=S, one_to_iterable=one_to_iterable 62 | ) 63 | 64 | 65 | class TestEmbeddingsHelpers(unittest.TestCase): 66 | def test_color_dict(self): 67 | # The refactored code defines these helpers: 68 | # _generate_node_color_dict(G, embeddings, S=None, one_to_iterable=False, shuffle_colormap=True, seed=None) 69 | # _generate_edge_color_dict(G, embeddings, S, one_to_iterable, node_color_dict) 70 | 71 | T = chimera_graph(2) 72 | embeddings = [{}] 73 | 74 | # Test empty embeddings: All nodes should be NaN, no edges 75 | n, _emb = _generate_node_color_dict( 76 | T, embeddings, S=None, one_to_iterable=False, shuffle_colormap=False 77 | ) 78 | e = _generate_edge_color_dict( 79 | T, _emb, S=None, one_to_iterable=False, node_color_dict=n 80 | ) 81 | self.assertTrue(np.all(np.isnan(list(n.values())))) 82 | self.assertEqual(len(e), 0) 83 | 84 | blocks_of = [1, 8] 85 | one_to_iterable = [True, False] 86 | for b, o in product(blocks_of, one_to_iterable): 87 | if o: 88 | embeddings = [ 89 | {0: tuple(n + idx * b for n in range(b))} 90 | for idx in range(T.number_of_nodes() // b) 91 | ] 92 | else: 93 | embeddings = [ 94 | {n: n + idx * b for n in range(b)} 95 | for idx in range(T.number_of_nodes() // b) 96 | ] 97 | 98 | # No shuffle 99 | n, _emb = _generate_node_color_dict( 100 | T, embeddings, S=None, one_to_iterable=o, shuffle_colormap=False 101 | ) 102 | e = _generate_edge_color_dict( 103 | T, _emb, S=None, one_to_iterable=o, node_color_dict=n 104 | ) 105 | # length of e should match certain pattern 106 | self.assertEqual(len(e), len(embeddings) * (b // 2) * (b // 2)) 107 | 108 | # With shuffle and seed 109 | n1, _emb1 = _generate_node_color_dict( 110 | T, embeddings, S=None, one_to_iterable=o, shuffle_colormap=True, seed=42 111 | ) 112 | e1 = _generate_edge_color_dict( 113 | T, _emb1, S=None, one_to_iterable=o, node_color_dict=n1 114 | ) 115 | 116 | if b == 1: 117 | # Check color ordering behavior 118 | vals_n1 = np.array(list(n1.values())) 119 | # Shuffled version (n1) vs non-shuffled (n) 120 | vals_n = np.array(list(n.values())) 121 | # checkeordering differences 122 | # Highly unlikely to have aligned colors 123 | self.assertFalse( 124 | all(vals_n1[i] <= vals_n1[i + 1] for i in range(len(vals_n1) - 1)) 125 | ) 126 | vals_n = np.array([n[v] for v in sorted(n.keys())]) 127 | self.assertTrue( 128 | all(vals_n[i] <= vals_n[i + 1] for i in range(len(vals_n) - 1)) 129 | ) 130 | 131 | # Re-generate with same seed 132 | n2, _emb2 = _generate_node_color_dict( 133 | T, embeddings, S=None, one_to_iterable=o, shuffle_colormap=True, seed=42 134 | ) 135 | e2 = _generate_edge_color_dict( 136 | T, _emb2, S=None, one_to_iterable=o, node_color_dict=n2 137 | ) 138 | self.assertEqual(n1, n2) 139 | self.assertEqual(e1, e2) 140 | 141 | S = nx.Graph() 142 | S.add_node(0) 143 | embs = [{0: n} for n in T.nodes] 144 | 145 | # With S specified but no edges in S 146 | n, _emb = _generate_node_color_dict( 147 | T, embs, S=S, one_to_iterable=False, shuffle_colormap=False 148 | ) 149 | e = _generate_edge_color_dict( 150 | T, _emb, S=S, one_to_iterable=False, node_color_dict=n 151 | ) 152 | vals = np.array(list(n.values())) 153 | self.assertTrue(np.all(np.logical_and(vals < len(embs), vals >= 0))) 154 | self.assertEqual(len(e), 0) 155 | 156 | # Add edges to S 157 | S.add_edges_from(list(T.edges)[:2]) 158 | emb = {n: n for n in T.nodes} 159 | n, _emb = _generate_node_color_dict( 160 | T, embeddings=[emb], S=S, one_to_iterable=False, shuffle_colormap=False 161 | ) 162 | e = _generate_edge_color_dict( 163 | T, _emb, S=S, one_to_iterable=False, node_color_dict=n 164 | ) 165 | vals = np.array(list(n.values())) 166 | self.assertEqual(len(vals), T.number_of_nodes()) 167 | # 3 nodes should be colored, rest NaN 168 | self.assertEqual(np.sum(np.isnan(vals)), T.number_of_nodes() - 3) 169 | self.assertEqual(len(e), 2) 170 | 171 | # Without S, all nodes/edges colored 172 | n, _emb = _generate_node_color_dict( 173 | T, embeddings=[emb], S=None, one_to_iterable=False, shuffle_colormap=False 174 | ) 175 | e = _generate_edge_color_dict( 176 | T, _emb, S=None, one_to_iterable=False, node_color_dict=n 177 | ) 178 | self.assertEqual(len(e), T.number_of_edges()) 179 | self.assertEqual(len(n), T.number_of_nodes()) 180 | 181 | S = nx.from_edgelist({(i, i + 1) for i in range(2)}) 182 | embedding = {i: (i, i + 4) for i in range(3)} 183 | embeddings = [ 184 | embedding, 185 | {k: tuple(v + 8 for v in c) for k, c in embedding.items()}, 186 | ] 187 | # Test one_to_iterable with S 188 | n, _emb = _generate_node_color_dict( 189 | T, embeddings, S=S, one_to_iterable=True, shuffle_colormap=False 190 | ) 191 | e = _generate_edge_color_dict( 192 | T, _emb, S=S, one_to_iterable=True, node_color_dict=n 193 | ) 194 | vals = np.array(list(n.values())) 195 | self.assertEqual(len(e), 7 * 2) # Matches original test logic 196 | self.assertEqual(len(vals), T.number_of_nodes()) 197 | self.assertEqual(np.sum(np.isnan(vals)), T.number_of_nodes() - 6 * 2) 198 | -------------------------------------------------------------------------------- /tests/test_partitioning.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 unittest 16 | 17 | import networkx as nx 18 | import dwave_networkx as dnx 19 | from dimod import ExactCQMSolver 20 | 21 | 22 | class TestPartitioning(unittest.TestCase): 23 | def test_edge_cases(self): 24 | # get the empty graph 25 | G = nx.Graph() 26 | 27 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 28 | self.assertTrue(node_partitions == {}) 29 | 30 | def test_typical_cases(self): 31 | 32 | G = nx.complete_graph(8) 33 | 34 | node_partitions = dnx.partition(G, num_partitions=4, sampler=ExactCQMSolver()) 35 | for i in range(4): 36 | self.assertTrue(sum(x == i for x in node_partitions.values()) == 2) # 4 equally sized subsets 37 | 38 | 39 | G = nx.complete_graph(10) 40 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 41 | self.assertTrue(sum(x == 0 for x in node_partitions.values()) == 5) # half of the nodes in subset '0' 42 | 43 | 44 | nx.set_edge_attributes(G, 1, 'weight') 45 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 46 | self.assertTrue(sum(x == 0 for x in node_partitions.values()) == 5) # half of the nodes in subset '0' 47 | 48 | 49 | G = nx.Graph() 50 | G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 3), (3, 4), (2, 4)]) 51 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 52 | self.assertTrue(sum(x == 0 for x in node_partitions.values()) in (2, 3)) # either 2 or 3 nodes in subset '0' (ditto '1') 53 | 54 | G = nx.Graph() 55 | G.add_edges_from([(0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (3, 5), (4, 5)]) 56 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 57 | self.assertTrue(node_partitions[0] == node_partitions[1] == node_partitions[2]) 58 | self.assertTrue(node_partitions[3] == node_partitions[4] == node_partitions[5]) 59 | 60 | 61 | nx.set_edge_attributes(G, values = 1, name = 'weight') 62 | nx.set_edge_attributes(G, values = {(2, 3): 100}, name='weight') 63 | node_partitions = dnx.partition(G, sampler=ExactCQMSolver()) 64 | self.assertTrue(node_partitions[2] == node_partitions[3]) # weight edges are respected -------------------------------------------------------------------------------- /tests/test_pegasus_layout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 17 | 18 | import networkx as nx 19 | import dwave_networkx as dnx 20 | 21 | try: 22 | import matplotlib.pyplot as plt 23 | except ImportError: 24 | plt = False 25 | 26 | try: 27 | import numpy as np 28 | except ImportError: 29 | np = False 30 | 31 | _display = os.environ.get('DISPLAY', '') != '' 32 | 33 | 34 | @unittest.skipUnless(np and plt, "No numpy or matplotlib") 35 | class TestDrawing(unittest.TestCase): 36 | def test_pegasus_layout_coords(self): 37 | G = dnx.pegasus_graph(2, coordinates=True) 38 | pos = dnx.pegasus_layout(G) 39 | 40 | def test_pegasus_layout_ints(self): 41 | G = dnx.pegasus_graph(2) 42 | pos = dnx.pegasus_layout(G) 43 | 44 | def test_pegasus_layout_chim(self): 45 | G = dnx.pegasus_graph(2, nice_coordinates=True) 46 | pos = dnx.pegasus_layout(G) 47 | 48 | def test_pegasus_layout_ints_nodata(self): 49 | G = dnx.pegasus_graph(2, data=False) 50 | pos = dnx.pegasus_layout(G) 51 | 52 | def test_pegasus_layout_crosses(self): 53 | G = dnx.pegasus_graph(2) 54 | pos = dnx.pegasus_layout(G, crosses=True) 55 | 56 | def test_pegasus_layout_ints_badcenter(self): 57 | G = dnx.pegasus_graph(2, data=False) 58 | with self.assertRaises(ValueError): 59 | pos = dnx.pegasus_layout(G, center=(0, 0, 0, 0)) 60 | 61 | def test_pegasus_layout_ints_noinfo(self): 62 | G = dnx.pegasus_graph(2, data=False) 63 | badG = nx.Graph() 64 | badG.add_edges_from(G.edges()) 65 | with self.assertRaises(ValueError): 66 | pos = dnx.pegasus_layout(badG) 67 | 68 | def test_pegasus_layout_xrange_typical(self): 69 | G = dnx.pegasus_graph(2) 70 | pos = dnx.pegasus_layout(G) 71 | x_coords = [val[0] for val in pos.values()] 72 | min_x, max_x = min(x_coords), max(x_coords) 73 | self.assertAlmostEqual(min_x, 0, delta=1e-5, msg="min_x should be approximately 0") 74 | self.assertAlmostEqual(max_x, 1, delta=1e-5, msg="max_x should be approximately 1") 75 | 76 | def test_pegasus_layout_yrange_typical(self): 77 | G = dnx.pegasus_graph(2) 78 | pos = dnx.pegasus_layout(G) 79 | y_coords = [val[1] for val in pos.values()] 80 | min_y, max_y = min(y_coords), max(y_coords) 81 | self.assertAlmostEqual(min_y, -1, delta=1e-5, msg="min_y should be approximately -1") 82 | self.assertAlmostEqual(max_y, 0, delta=1e-5, msg="max_y should be approximately 0") 83 | 84 | def test_pegasus_layout_xrange(self): 85 | G = dnx.pegasus_graph(2) 86 | pos = dnx.pegasus_layout(G, scale=5) 87 | x_coords = [val[0] for val in pos.values()] 88 | min_x, max_x = min(x_coords), max(x_coords) 89 | self.assertAlmostEqual(min_x, 0, delta=1e-5, msg="min_x should be approximately 0") 90 | self.assertAlmostEqual(max_x, 5, delta=1e-5, msg="max_x should be approximately 5") 91 | 92 | def test_pegasus_layout_yrange(self): 93 | G = dnx.pegasus_graph(2) 94 | pos = dnx.pegasus_layout(G, scale=5) 95 | y_coords = [val[1] for val in pos.values()] 96 | min_y, max_y = min(y_coords), max(y_coords) 97 | self.assertAlmostEqual(min_y, -5, delta=1e-5, msg="min_y should be approximately -5") 98 | self.assertAlmostEqual(max_y, 0, delta=1e-5, msg="max_y should be approximately 0") 99 | 100 | @unittest.skipUnless(_display, " No display found") 101 | def test_draw_pegasus_yield(self): 102 | G = dnx.pegasus_graph(3, data=False) 103 | G.remove_edges_from([(5,104),(12,96),(23,112)]) 104 | G.remove_nodes_from([109,139]) 105 | dnx.draw_pegasus_yield(G) 106 | 107 | @unittest.skipUnless(_display, " No display found") 108 | def test_draw_pegasus_biases(self): 109 | G = dnx.pegasus_graph(2) 110 | h = {v: v % 12 for v in G} 111 | J = {(u, v) if u % 2 else (v, u): (u+v) % 24 for u, v in G.edges()} 112 | for v in G: 113 | J[v, v] = .1 114 | 115 | dnx.draw_pegasus(G, linear_biases=h, quadratic_biases=J) 116 | 117 | @unittest.skipUnless(_display, " No display found") 118 | def test_draw_pegasus_embedding(self): 119 | P = dnx.pegasus_graph(2) 120 | G = nx.grid_graph([3, 3, 2]) 121 | emb = {(0, 0, 0): [35], (0, 0, 1): [12], (0, 0, 2): [31], (0, 1, 0): [16], 122 | (0, 1, 1): [36], (0, 1, 2): [11], (0, 2, 0): [39], (0, 2, 1): [6], 123 | (0, 2, 2): [41], (1, 0, 0): [34], (1, 0, 1): [13], (1, 0, 2): [30], 124 | (1, 1, 0): [17], (1, 1, 1): [37], (1, 1, 2): [10], (1, 2, 0): [38], 125 | (1, 2, 1): [7], (1, 2, 2): [40]} 126 | dnx.draw_pegasus_embedding(P, emb) 127 | dnx.draw_pegasus_embedding(P, emb, embedded_graph=G) 128 | dnx.draw_pegasus_embedding(P, emb, interaction_edges=P.edges()) 129 | dnx.draw_pegasus_embedding(P, emb, crosses=True) 130 | 131 | @unittest.skipUnless(_display, " No display found") 132 | def test_draw_overlapped_chimera_embedding(self): 133 | C = dnx.pegasus_graph(2) 134 | emb = {0: [12, 35], 1: [12, 31], 2: [32], 3: [14]} 135 | dnx.draw_pegasus_embedding(C, emb, overlapped_embedding=True) 136 | dnx.draw_pegasus_embedding(C, emb, overlapped_embedding=True, show_labels=True) 137 | -------------------------------------------------------------------------------- /tests/test_qubit_layout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 17 | 18 | import dwave_networkx as dnx 19 | 20 | try: 21 | import matplotlib.pyplot as plt 22 | except ImportError: 23 | plt = False 24 | 25 | try: 26 | import numpy as np 27 | except ImportError: 28 | np = False 29 | 30 | _display = os.environ.get('DISPLAY', '') != '' 31 | 32 | 33 | @unittest.skipUnless(np and plt, "No numpy or matplotlib") 34 | class TestDrawing(unittest.TestCase): 35 | @unittest.skipUnless(_display, " No display found") 36 | def test_draw_qubit_graph_kwargs(self): 37 | G = dnx.chimera_graph(2, 2, 4) 38 | pos = dnx.chimera_layout(G) 39 | linear_biases = {v: -v / max(G) if v % 2 == 0 else v / max(G) for v in G} 40 | quadratic_biases = {(u, v): (u - v) / max(abs(u), abs(v)) for u, v in G.edges} 41 | cm = plt.get_cmap("spring_r") 42 | 43 | # Don't supply biases 44 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos) 45 | 46 | # Supply both biases 47 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, quadratic_biases) 48 | 49 | # Supply linear but not quadratic biases 50 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases) 51 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None) 52 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=None) 53 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=cm) 54 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, vmin=-0.1, vmax=0) 55 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, vmin=0.0, vmax=10) 56 | 57 | # Supply quadratic but not linear biases 58 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, {}, quadratic_biases) 59 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases) 60 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=None) 61 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=cm) 62 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, 63 | edge_vmin=-0.1, edge_vmax=0) 64 | dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, 65 | edge_vmin=0.0, edge_vmax=10) 66 | -------------------------------------------------------------------------------- /tests/test_regular_coloring.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 D-Wave 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 unittest 16 | 17 | import numpy as np 18 | 19 | import dwave_networkx as dnx 20 | 21 | class TestRegularColoring(unittest.TestCase): 22 | 23 | def test_valid(self): 24 | test_cases = {'chimera': [(3,3), (4,4)], 25 | 'pegasus': [(2,), (4,)], 26 | 'zephyr-0': [(1,2), (3,4)], 27 | 'zephyr-1': [(1,3), (3,2)]} 28 | 29 | for topology_type, topology_shapes in test_cases.items(): 30 | if topology_type == 'zephyr-0': 31 | graph = dnx.zephyr_graph 32 | color = lambda q: dnx.zephyr_four_color(q, scheme=0) 33 | num_colors = 4 34 | elif topology_type == 'zephyr-1': 35 | graph = dnx.zephyr_graph 36 | color = lambda q: dnx.zephyr_four_color(q, scheme=1) 37 | num_colors = 4 38 | elif topology_type == 'pegasus': 39 | graph = dnx.pegasus_graph 40 | color = dnx.pegasus_four_color 41 | num_colors = 4 42 | elif topology_type == 'chimera': 43 | graph = dnx.chimera_graph 44 | color = dnx.chimera_two_color 45 | num_colors = 2 46 | else: 47 | raise ValueError('unrecognized topology') 48 | 49 | for topology_shape in topology_shapes: 50 | G = graph(*topology_shape, coordinates=True) 51 | col_dict = {q: color(q) for q in G.nodes} 52 | self.assertSetEqual(set(col_dict.values()), 53 | set(range(num_colors))) 54 | self.assertTrue(all(col_dict[q1] != col_dict[q2] 55 | for q1, q2 in G.edges), 56 | f'{topology_type}[{topology_shape}]') 57 | 58 | def test_invalid_scheme(self): 59 | q = (0, 0, 0, 0, 0) 60 | self.assertRaises(ValueError, dnx.zephyr_four_color, q, scheme=2) 61 | -------------------------------------------------------------------------------- /tests/test_social.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 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 unittest 16 | import itertools 17 | 18 | import networkx as nx 19 | import dwave_networkx as dnx 20 | from dimod import ExactSolver, SimulatedAnnealingSampler 21 | 22 | 23 | class TestSocial(unittest.TestCase): 24 | def check_bicolor(self, colors): 25 | # colors should be ints and either 0 or 1 26 | for c in colors.values(): 27 | self.assertTrue(c in (0, 1)) 28 | 29 | def test_structural_imbalance_basic(self): 30 | sampler = ExactSolver() 31 | 32 | blueteam = ['Alice', 'Bob', 'Carol'] 33 | redteam0 = ['Eve'] 34 | redteam1 = ['Mallory', 'Trudy'] 35 | 36 | S = nx.Graph() 37 | for p0, p1 in itertools.combinations(blueteam, 2): 38 | S.add_edge(p0, p1, sign=1) 39 | S.add_edge(*redteam1, sign=1) 40 | for p0 in blueteam: 41 | for p1 in redteam0: 42 | S.add_edge(p0, p1, sign=-1) 43 | for p1 in redteam1: 44 | S.add_edge(p0, p1, sign=-1) 45 | 46 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 47 | self.check_bicolor(colors) 48 | 49 | greenteam = ['Ted'] 50 | for p0 in set(S.nodes): 51 | for p1 in greenteam: 52 | S.add_edge(p0, p1, sign=1) 53 | 54 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 55 | self.check_bicolor(colors) 56 | 57 | def test_structural_imbalance_docstring_example(self): 58 | sampler = ExactSolver() 59 | 60 | S = nx.Graph() 61 | S.add_edge('Alice', 'Bob', sign=1) # Alice and Bob are friendly 62 | S.add_edge('Alice', 'Eve', sign=-1) # Alice and Eve are hostile 63 | S.add_edge('Bob', 'Eve', sign=-1) # Bob and Eve are hostile 64 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 65 | self.check_bicolor(colors) 66 | self.assertEqual(frustrated_edges, {}) 67 | S.add_edge('Ted', 'Bob', sign=1) # Ted is friendly with all 68 | S.add_edge('Ted', 'Alice', sign=1) 69 | S.add_edge('Ted', 'Eve', sign=1) 70 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 71 | self.check_bicolor(colors) 72 | self.assertTrue(frustrated_edges == {('Ted', 'Eve'): {'sign': 1}} or 73 | frustrated_edges == {('Eve', 'Ted'): {'sign': 1}}) 74 | 75 | def test_default_sampler(self): 76 | S = nx.Graph() 77 | S.add_edge('Alice', 'Bob', sign=1) # Alice and Bob are friendly 78 | S.add_edge('Alice', 'Eve', sign=-1) # Alice and Eve are hostile 79 | S.add_edge('Bob', 'Eve', sign=-1) # Bob and Eve are hostile 80 | 81 | dnx.set_default_sampler(ExactSolver()) 82 | self.assertIsNot(dnx.get_default_sampler(), None) 83 | frustrated_edges, colors = dnx.structural_imbalance(S) 84 | dnx.unset_default_sampler() 85 | self.assertEqual(dnx.get_default_sampler(), None, "sampler did not unset correctly") 86 | 87 | def test_invalid_graph(self): 88 | """should throw an error with a graph without sign attribute""" 89 | S = nx.Graph() 90 | S.add_edge('Alice', 'Bob', sign=1) 91 | S.add_edge('Alice', 'Eve', sign=-1) 92 | S.add_edge('Bob', 'Eve') # invalid edge 93 | 94 | with self.assertRaises(ValueError): 95 | frustrated_edges, colors = dnx.structural_imbalance(S, ExactSolver()) 96 | 97 | def test_sign_zero(self): 98 | """though not documentented, agents with no relation can have sign 0. 99 | This is less performant than just not having an edge in most cases.""" 100 | sampler = ExactSolver() 101 | 102 | S = nx.Graph() 103 | S.add_edge('Alice', 'Bob', sign=1) # Alice and Bob are friendly 104 | S.add_edge('Alice', 'Eve', sign=-1) # Alice and Eve are hostile 105 | S.add_edge('Bob', 'Eve', sign=0) # Bob and Eve have no relation 106 | 107 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 108 | 109 | self.assertEqual(frustrated_edges, {}) # should be no frustration 110 | 111 | def test_frustrated_hostile_edge(self): 112 | """Set up a graph where the frustrated edge should be hostile""" 113 | sampler = ExactSolver() 114 | 115 | S = nx.florentine_families_graph() 116 | 117 | # set all hostile 118 | nx.set_edge_attributes(S, -1, 'sign') 119 | 120 | # smoke test 121 | frustrated_edges, colors = dnx.structural_imbalance(S, sampler) 122 | self.check_bicolor(colors) 123 | -------------------------------------------------------------------------------- /tests/test_zephyr_layout.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 os 16 | import unittest 17 | 18 | import networkx as nx 19 | import dwave_networkx as dnx 20 | 21 | try: 22 | import matplotlib.pyplot as plt 23 | except ImportError: 24 | plt = False 25 | 26 | try: 27 | import numpy as np 28 | except ImportError: 29 | np = False 30 | 31 | _display = os.environ.get('DISPLAY', '') != '' 32 | 33 | @unittest.skipUnless(np and plt, "No numpy or matplotlib") 34 | class TestDrawing(unittest.TestCase): 35 | def test_zephyr_layout_basic(self): 36 | G = dnx.zephyr_graph(1, 4) 37 | pos = dnx.zephyr_layout(G) 38 | 39 | def test_zephyr_layout_typical(self): 40 | G = dnx.zephyr_graph(2, 4) 41 | pos = dnx.zephyr_layout(G) 42 | 43 | def test_zephyr_layout_center(self): 44 | G = dnx.zephyr_graph(2, 4) 45 | pos = dnx.zephyr_layout(G, center=(5, 5)) 46 | with self.assertRaises(ValueError): 47 | pos = dnx.zephyr_layout(G, center=(5, 5, 5)) 48 | 49 | def test_zephyr_layout_lowdim(self): 50 | G = dnx.zephyr_graph(2, 4) 51 | with self.assertRaises(ValueError): 52 | pos = dnx.zephyr_layout(G, dim=1) 53 | 54 | def test_zephyr_layout_weird_nodata(self): 55 | G = dnx.zephyr_graph(2, 4) 56 | del G.graph["family"] 57 | with self.assertRaises(ValueError): 58 | pos = dnx.zephyr_layout(G, dim=1) 59 | 60 | def test_zephyr_layout_no_zephyr_indices(self): 61 | pos = dnx.zephyr_layout(dnx.zephyr_graph(1, 1, data=False)) 62 | pos2 = dnx.zephyr_layout(dnx.zephyr_graph(1, 1)) 63 | coord = dnx.zephyr_coordinates(1, 1) 64 | for v in pos: 65 | self.assertTrue(all(pos[v] == pos2[v])) 66 | for v in pos2: 67 | self.assertIn(v, pos) 68 | 69 | def test_zephyr_layout_coords(self): 70 | G = dnx.zephyr_graph(2, 4, coordinates=True) 71 | pos = dnx.zephyr_layout(G) 72 | 73 | def test_zephyr_layout_nodata(self): 74 | G = dnx.zephyr_graph(2, 4, data=False) 75 | pos = dnx.zephyr_layout(G) 76 | 77 | def test_zephyr_layout_xrange_typical(self): 78 | G = dnx.zephyr_graph(2, 4) 79 | pos = dnx.zephyr_layout(G) 80 | x_coords = [val[0] for val in pos.values()] 81 | min_x, max_x = min(x_coords), max(x_coords) 82 | self.assertAlmostEqual(min_x, 0, delta=1e-5, msg="min_x should be approximately 0") 83 | self.assertAlmostEqual(max_x, 1, delta=1e-5, msg="max_x should be approximately 1") 84 | 85 | def test_zephyr_layout_yrange_typical(self): 86 | G = dnx.zephyr_graph(2, 4) 87 | pos = dnx.zephyr_layout(G) 88 | y_coords = [val[1] for val in pos.values()] 89 | min_y, max_y = min(y_coords), max(y_coords) 90 | self.assertAlmostEqual(min_y, -1, delta=1e-5, msg="min_y should be approximately -1") 91 | self.assertAlmostEqual(max_y, 0, delta=1e-5, msg="max_y should be approximately 0") 92 | 93 | def test_zephyr_layout_xrange(self): 94 | G = dnx.zephyr_graph(2, 4) 95 | pos = dnx.zephyr_layout(G, scale=10) 96 | x_coords = [val[0] for val in pos.values()] 97 | min_x, max_x = min(x_coords), max(x_coords) 98 | self.assertAlmostEqual(min_x, 0, delta=1e-5, msg="min_x should be approximately 0") 99 | self.assertAlmostEqual(max_x, 10, delta=1e-5, msg="max_x should be approximately 10") 100 | 101 | def test_zephyr_layout_yrange(self): 102 | G = dnx.zephyr_graph(2, 4) 103 | pos = dnx.zephyr_layout(G, scale=10) 104 | y_coords = [val[1] for val in pos.values()] 105 | min_y, max_y = min(y_coords), max(y_coords) 106 | self.assertAlmostEqual(min_y, -10, delta=1e-5, msg="min_y should be approximately -10") 107 | self.assertAlmostEqual(max_y, 0, delta=1e-5, msg="max_y should be approximately 0") 108 | 109 | @unittest.skipUnless(_display, " No display found") 110 | def test_draw_zephyr_yield(self): 111 | G = dnx.zephyr_graph(2, 4, data=False) 112 | G.remove_edges_from([(51, 159),(51, 159),(24, 98)]) 113 | G.remove_nodes_from([18,23]) 114 | dnx.draw_zephyr_yield(G) 115 | 116 | @unittest.skipUnless(_display, " No display found") 117 | def test_draw_zephyr_biases(self): 118 | G = dnx.zephyr_graph(8) 119 | h = {v: v % 12 for v in G} 120 | J = {(u, v) if u % 2 else (v, u): (u+v) % 24 for u, v in G.edges()} 121 | for v in G: 122 | J[v, v] = .1 123 | 124 | dnx.draw_zephyr(G, linear_biases=h, quadratic_biases=J) 125 | 126 | @unittest.skipUnless(_display, " No display found") 127 | def test_draw_zephyr_embedding(self): 128 | C = dnx.zephyr_graph(4) 129 | G = nx.grid_graph([2, 3, 2]) 130 | emb = {(0, 0, 0): [80, 48], (0, 0, 1): [50, 52], (0, 1, 0): [85, 93], 131 | (0, 1, 1): [84, 82], (0, 2, 0): [89], (0, 2, 1): [92], 132 | (1, 0, 0): [49, 54], (1, 0, 1): [83, 51], (1, 1, 0): [81], 133 | (1, 1, 1): [86, 94], (1, 2, 0): [87, 95], (1, 2, 1): [91]} 134 | dnx.draw_zephyr_embedding(C, emb) 135 | dnx.draw_zephyr_embedding(C, emb, embedded_graph=G) 136 | dnx.draw_zephyr_embedding(C, emb, interaction_edges=C.edges()) 137 | 138 | @unittest.skipUnless(_display, " No display found") 139 | def test_draw_overlapped_zephyr_embedding(self): 140 | C = dnx.zephyr_graph(2) 141 | emb = {0: [1, 3], 1: [3, 128, 15], 2: [25, 140], 3: [17, 122]} 142 | dnx.draw_zephyr_embedding(C, emb, overlapped_embedding=True) 143 | dnx.draw_zephyr_embedding(C, emb, overlapped_embedding=True, show_labels=True) 144 | --------------------------------------------------------------------------------