├── .circleci └── config.yml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── README.rst ├── _images │ ├── FA_from_HA.png │ ├── HA.png │ ├── Problem_MapColoring.png │ ├── and.png │ └── map_coloring_CSP4colors.png ├── _static │ ├── cookie_notice.css │ └── cookie_notice.js ├── conf.py ├── index.rst ├── reference │ ├── api_ref.rst │ ├── compilers.rst │ ├── constraint.rst │ ├── csp.rst │ ├── factory_constraints.rst │ ├── loading.rst │ └── reduction.rst └── requirements.txt ├── dwavebinarycsp ├── __init__.py ├── compilers │ ├── __init__.py │ └── stitcher.py ├── core │ ├── __init__.py │ ├── constraint.py │ └── csp.py ├── exceptions.py ├── factories │ ├── __init__.py │ ├── constraint │ │ ├── __init__.py │ │ ├── gates.py │ │ └── sat.py │ └── csp │ │ ├── __init__.py │ │ ├── circuits.py │ │ └── sat.py ├── io │ ├── __init__.py │ └── cnf.py ├── package_info.py ├── reduction.py └── testing.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── data ├── test0.cnf └── test1.cnf ├── requirements.txt ├── test_constraint.py ├── test_csp.py ├── test_documentation.py ├── test_factories_constraint.py ├── test_factories_csp.py ├── test_int_stitcher.py ├── test_io_cnf.py ├── test_reduction.py └── test_stitcher.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | win: circleci/windows@5.0 5 | 6 | jobs: 7 | test-linux: 8 | parameters: 9 | python-version: 10 | type: string 11 | pip-constraints: 12 | type: string 13 | 14 | docker: 15 | - image: python:<< parameters.python-version >> 16 | 17 | steps: 18 | - checkout 19 | 20 | - restore_cache: &restore-cache-env 21 | key: v1-dependencies-{{ checksum "requirements.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ .Environment.CIRCLE_JOB }} 22 | 23 | - run: &create-virtualenv 24 | name: Create virtual environment 25 | command: | 26 | python -m venv env 27 | 28 | - run: &install-requirements 29 | name: Install requirements 30 | command: | 31 | . env/bin/activate 32 | pip install -U pip wheel twine setuptools 33 | pip install -r requirements.txt -r tests/requirements.txt 34 | 35 | - save_cache: &save-cache-env 36 | key: v1-dependencies-{{ checksum "requirements.txt" }}-{{ checksum "tests/requirements.txt" }}-{{ .Environment.CIRCLE_JOB }} 37 | paths: 38 | - ~/.cache/pip 39 | 40 | - run: &build-package 41 | name: Build package 42 | command: env/bin/python setup.py sdist bdist_wheel 43 | 44 | - run: 45 | name: Install package (with constraints) 46 | command: | 47 | . env/bin/activate 48 | pip install . -c <(printf '%s\n' << parameters.pip-constraints >>) 49 | 50 | - run: &run-python-tests 51 | name: Run Python tests 52 | command: env/bin/coverage run -m unittest discover 53 | 54 | - run: 55 | name: Upload code coverage 56 | command: | 57 | . env/bin/activate 58 | codecov # calls `coverage xml`, so we must activate venv 59 | 60 | test-macos: 61 | parameters: 62 | python-version: 63 | type: string 64 | xcode: 65 | type: string 66 | 67 | macos: 68 | xcode: << parameters.xcode >> 69 | 70 | steps: 71 | - checkout 72 | 73 | # install `python-version` and cache it 74 | - run: 75 | name: Install pyenv 76 | command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pyenv 77 | 78 | - restore_cache: &restore-cache-pyenv 79 | keys: 80 | - v1-pyenv-{{ .Environment.CIRCLE_JOB }}-xcode-<< parameters.xcode >> 81 | 82 | - run: &pyenv-install-python 83 | name: Install python 84 | command: pyenv install << parameters.python-version >> -s 85 | 86 | - run: 87 | name: Set system python 88 | command: | 89 | echo -e '\n\n# Initialize pyenv' >> ~/.bash_profile 90 | echo 'eval "$(pyenv init --path 2>/dev/null || true)"' >> ~/.bash_profile 91 | echo 'eval "$(pyenv init -)"' >> ~/.bash_profile 92 | pyenv global << parameters.python-version >> 93 | 94 | - save_cache: &save-cache-pyenv 95 | key: v1-pyenv-{{ .Environment.CIRCLE_JOB }}-xcode-<< parameters.xcode >> 96 | paths: 97 | - ~/.pyenv 98 | 99 | # install dependencies and cache them 100 | - restore_cache: *restore-cache-env 101 | 102 | - run: *create-virtualenv 103 | 104 | - run: *install-requirements 105 | 106 | - save_cache: *save-cache-env 107 | 108 | - run: &install-package 109 | name: Install package 110 | command: env/bin/pip install . 111 | 112 | - run: *run-python-tests 113 | 114 | test-win: 115 | parameters: 116 | python-version: 117 | type: string 118 | 119 | executor: 120 | name: win/default 121 | 122 | steps: 123 | - checkout 124 | 125 | - run: 126 | name: Install python and create virtualenv 127 | shell: bash -eo pipefail 128 | command: | 129 | # resolve python MAJOR.MINOR version to latest MAJOR.MINOR.PATCH version available on NuGet 130 | full_version=$( 131 | curl -s 'https://azuresearch-usnc.nuget.org/query?q=python' \ 132 | | jq -r '.data[] | select(.id == "python") .versions[] | .version' \ 133 | | awk -F. -v ver='<< parameters.python-version >>' \ 134 | 'index($0, ver".") == 1 && $3 >= m { m = $3; v = $0 } END { print v }' 135 | ) 136 | nuget install python -Version "$full_version" -ExcludeVersion 137 | python/tools/python -V 138 | python/tools/python -m venv env 139 | 140 | - run: 141 | name: Install dependencies 142 | command: | 143 | env\Scripts\activate.ps1 144 | python --version 145 | pip install -r requirements.txt 146 | pip install -r tests\requirements.txt 147 | 148 | - run: 149 | name: Run unittests 150 | command: | 151 | env\Scripts\activate.ps1 152 | python -m unittest discover 153 | 154 | test-docs: 155 | docker: 156 | - image: python:3.9 157 | 158 | steps: 159 | - checkout 160 | 161 | - restore_cache: *restore-cache-env 162 | 163 | - run: *create-virtualenv 164 | 165 | - run: 166 | name: Install requirements 167 | command: | 168 | . env/bin/activate 169 | pip install -U pip wheel twine setuptools 170 | pip install -r requirements.txt -r tests/requirements.txt -r docs/requirements.txt 171 | 172 | - save_cache: *save-cache-env 173 | 174 | - run: *install-package 175 | 176 | - run: 177 | name: Build docs 178 | command: | 179 | . env/bin/activate 180 | make -C docs/ html 181 | 182 | - store_artifacts: 183 | path: ./docs/_build/html 184 | 185 | - run: 186 | name: Test docs 187 | command: | 188 | . env/bin/activate 189 | make -C docs/ doctest 190 | 191 | - run: 192 | name: Test links 193 | command: | 194 | . env/bin/activate 195 | make -C docs/ linkcheck 196 | 197 | pypi-deploy: 198 | docker: 199 | - image: python:3.9 200 | 201 | steps: 202 | - checkout 203 | 204 | - restore_cache: *restore-cache-env 205 | 206 | - run: *create-virtualenv 207 | 208 | - run: *install-requirements 209 | 210 | - save_cache: *save-cache-env 211 | 212 | - run: *build-package 213 | 214 | - store_artifacts: 215 | path: ./dist 216 | 217 | - run: 218 | name: Upload package to PyPI 219 | command: env/bin/twine upload -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD" --skip-existing ./dist/* 220 | 221 | workflows: 222 | test: 223 | jobs: 224 | - test-linux: 225 | name: test-linux-<< matrix.python-version >> | << matrix.pip-constraints >> 226 | matrix: 227 | parameters: 228 | python-version: &python-versions ["3.9", "3.10", "3.11", "3.12", "3.13"] 229 | # Oldest and latest. The NumPy is needed because otherwise networkx==2.4 throws exceptions 230 | pip-constraints: ["dimod==0.10.9 networkx==2.4 penaltymodel==1.0.0 numpy==1.20.0", 231 | "dimod networkx penaltymodel"] 232 | exclude: 233 | # dimod 0.10.9 only support Python < 3.10 234 | - python-version: "3.10" 235 | pip-constraints: "dimod==0.10.9 networkx==2.4 penaltymodel==1.0.0 numpy==1.20.0" 236 | - python-version: "3.11" 237 | pip-constraints: "dimod==0.10.9 networkx==2.4 penaltymodel==1.0.0 numpy==1.20.0" 238 | - python-version: "3.12" 239 | pip-constraints: "dimod==0.10.9 networkx==2.4 penaltymodel==1.0.0 numpy==1.20.0" 240 | - python-version: "3.13" 241 | pip-constraints: "dimod==0.10.9 networkx==2.4 penaltymodel==1.0.0 numpy==1.20.0" 242 | - test-macos: 243 | name: test-macos-<< matrix.python-version >> 244 | matrix: 245 | parameters: 246 | python-version: *python-versions 247 | xcode: ["16.2.0"] 248 | - test-win: 249 | matrix: 250 | parameters: 251 | python-version: *python-versions 252 | - test-docs 253 | 254 | deploy: 255 | jobs: 256 | - pypi-deploy: 257 | filters: 258 | tags: 259 | only: /^[0-9]+(\.[0-9]+)*((\.dev|rc)([0-9]+)?)?$/ 260 | branches: 261 | ignore: /.*/ 262 | -------------------------------------------------------------------------------- /.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 the bug. 9 | 10 | **Steps 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: [e.g. Ubuntu 22.04.3 LTS] 18 | - Python version: [e.g. 3.12.0] 19 | 20 | **Additional Context** 21 | Add any other background information about the problem. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Application** 8 | What need does this feature fulfill? If your feature relates to a problem, please provide 9 | a clear and concise description of that problem; e.g., I'm always frustrated when [...]. 10 | If it relates to a class of real-world problems, please provide an example. 11 | 12 | **Proposed Solution** 13 | A clear and concise description of what the software should do. 14 | 15 | **Alternatives Considered** 16 | Description of any alternative solutions or features you considered. 17 | 18 | **Additional Context** 19 | Add any other background information here. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | .static_storage/ 58 | .media/ 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | docs/reference/generated/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | venv*/ 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | :warning: *dwavebinarycsp* is deprecated. For solving problems with constraints, 2 | we recommend using the hybrid solvers in the Leap :tm: service. You can find 3 | documentation for the hybrid solvers at https://docs.ocean.dwavesys.com. 4 | 5 | .. image:: https://img.shields.io/pypi/v/dwavebinarycsp.svg 6 | :target: https://pypi.org/project/dwavebinarycsp 7 | 8 | .. image:: https://codecov.io/gh/dwavesystems/dwavebinarycsp/branch/master/graph/badge.svg 9 | :target: https://codecov.io/gh/dwavesystems/dwavebinarycsp 10 | 11 | .. image:: https://circleci.com/gh/dwavesystems/dwavebinarycsp.svg?style=svg 12 | :target: https://circleci.com/gh/dwavesystems/dwavebinarycsp 13 | 14 | 15 | ============== 16 | dwavebinarycsp 17 | ============== 18 | 19 | .. start_binarycsp_about 20 | 21 | Library to construct a binary quadratic model from a constraint satisfaction problem with 22 | small constraints over binary variables. 23 | 24 | Below is an example usage: 25 | 26 | .. code-block:: python 27 | 28 | import dwavebinarycsp 29 | import dimod 30 | 31 | csp = dwavebinarycsp.factories.random_2in4sat(8, 4) # 8 variables, 4 clauses 32 | 33 | bqm = dwavebinarycsp.stitch(csp) 34 | 35 | resp = dimod.ExactSolver().sample(bqm) 36 | 37 | for sample, energy in resp.data(['sample', 'energy']): 38 | print(sample, csp.check(sample), energy) 39 | 40 | .. end_binarycsp_about 41 | 42 | Installation 43 | ============ 44 | 45 | To install: 46 | 47 | .. code-block:: bash 48 | 49 | pip install dwavebinarycsp 50 | 51 | To build from source: 52 | 53 | .. code-block:: bash 54 | 55 | pip install -r requirements.txt 56 | python setup.py install 57 | 58 | License 59 | ======= 60 | 61 | Released under the Apache License 2.0. See LICENSE file. 62 | 63 | Contributing 64 | ============ 65 | 66 | Ocean's `contributing guide `_ 67 | has guidelines for contributing to Ocean packages. -------------------------------------------------------------------------------- /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 = dwave_constraint_compiler 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 | .PHONY: help Makefile 16 | 17 | clean: 18 | -rm -rf build/* 19 | -rm -rf generated/* 20 | 21 | # Catch-all target: route all unknown targets to Sphinx using the new 22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 23 | %: Makefile 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /docs/_images/FA_from_HA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/docs/_images/FA_from_HA.png -------------------------------------------------------------------------------- /docs/_images/HA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/docs/_images/HA.png -------------------------------------------------------------------------------- /docs/_images/Problem_MapColoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/docs/_images/Problem_MapColoring.png -------------------------------------------------------------------------------- /docs/_images/and.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/docs/_images/and.png -------------------------------------------------------------------------------- /docs/_images/map_coloring_CSP4colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/docs/_images/map_coloring_CSP4colors.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/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # DWaveNetworkX documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jul 26 10:55:26 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | import sys 21 | sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | # import sphinx 26 | # if sphinx.__version__ # can check here 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autosummary', 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.coverage', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.mathjax', 38 | 'sphinx.ext.napoleon', 39 | 'sphinx.ext.todo', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.githubpages', 42 | 'sphinx.ext.ifconfig', 43 | ] 44 | 45 | autosummary_generate = True 46 | 47 | # templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | source_suffix = ['.rst', '.md'] 53 | source_parsers = {'.md': 'recommonmark.parser.CommonMarkParser'} 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # General information about the project. 59 | project = u'dwavebinarycsp' 60 | copyright = u'2018, D-Wave Systems Inc' 61 | author = u'D-Wave Systems Inc' 62 | 63 | # The version info for the project you're documenting, acts as replacement for 64 | # |version| and |release|, also used in various other places throughout the 65 | # built documents. 66 | # 67 | import dwavebinarycsp.package_info as pckinfo 68 | # The short X.Y version. 69 | version = pckinfo.__version__ 70 | # The full version, including alpha/beta/rc tags. 71 | release = pckinfo.__version__ 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | 81 | add_module_names = False 82 | # List of patterns, relative to source directory, that match files and 83 | # directories to ignore when looking for source files. 84 | # This patterns also effect to html_static_path and html_extra_path 85 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'sdk_index.rst'] 86 | 87 | linkcheck_retries = 2 88 | linkcheck_anchors = False 89 | linkcheck_ignore = [r'https://cloud.dwavesys.com/leap', # redirects, many checks 90 | ] 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # If true, `todo` and `todoList` produce output, else they produce nothing. 96 | todo_include_todos = True 97 | 98 | modindex_common_prefix = ['dwavebinarycsp.'] 99 | 100 | doctest_global_setup = "import dwavebinarycsp, operator" 101 | 102 | 103 | # -- Options for HTML output ---------------------------------------------- 104 | 105 | # The theme to use for HTML and HTML Help pages. See the documentation for 106 | # a list of builtin themes. 107 | # 108 | import sphinx_rtd_theme 109 | html_theme = 'sphinx_rtd_theme' 110 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 111 | 112 | # Theme options are theme-specific and customize the look and feel of a theme 113 | # further. For a list of options available for each theme, see the 114 | # documentation. 115 | # 116 | # html_theme_options = {} 117 | 118 | # Add any paths that contain custom static files (such as style sheets) here, 119 | # relative to this directory. They are copied after the builtin static files, 120 | # so a file named "default.css" will overwrite the builtin "default.css". 121 | html_static_path = ['_static'] 122 | 123 | def setup(app): 124 | app.add_css_file('cookie_notice.css') 125 | app.add_js_file('cookie_notice.js') 126 | app.add_config_value('target', 'repo', 'env') 127 | 128 | # -- Options for HTMLHelp output ------------------------------------------ 129 | 130 | # Output file base name for HTML help builder. 131 | htmlhelp_basename = 'dwavebinarycsp' 132 | 133 | 134 | # -- Options for LaTeX output --------------------------------------------- 135 | 136 | latex_elements = { 137 | # The paper size ('letterpaper' or 'a4paper'). 138 | # 139 | # 'papersize': 'letterpaper', 140 | 141 | # The font size ('10pt', '11pt' or '12pt'). 142 | # 143 | # 'pointsize': '10pt', 144 | 145 | # Additional stuff for the LaTeX preamble. 146 | # 147 | # 'preamble': '', 148 | 149 | # Latex figure (float) alignment 150 | # 151 | # 'figure_align': 'htbp', 152 | } 153 | 154 | # Grouping the document tree into LaTeX files. List of tuples 155 | # (source start file, target name, title, 156 | # author, documentclass [howto, manual, or own class]). 157 | latex_documents = [ 158 | (master_doc, 'dwavebinarycsp.tex', u'dwavebinarycsp', 159 | u'D-Wave Systems Inc', 'manual'), 160 | ] 161 | 162 | 163 | # -- Options for manual page output --------------------------------------- 164 | 165 | # One entry per manual page. List of tuples 166 | # (source start file, name, description, authors, manual section). 167 | man_pages = [ 168 | (master_doc, 'dwavebinarycsp', u'dwavebinarycsp', 169 | [author], 1) 170 | ] 171 | 172 | 173 | # -- Options for Texinfo output ------------------------------------------- 174 | 175 | # Grouping the document tree into Texinfo files. List of tuples 176 | # (source start file, target name, title, author, 177 | # dir menu entry, description, category) 178 | texinfo_documents = [ 179 | (master_doc, 'dwavebinarycsp', u'dwavebinarycsp', 180 | author, 'dwavebinarycsp', 'One line description of project.', 181 | 'Miscellaneous'), 182 | ] 183 | 184 | 185 | # -- Options for Epub output ---------------------------------------------- 186 | 187 | # Bibliographic Dublin Core info. 188 | epub_title = project 189 | epub_author = author 190 | epub_publisher = author 191 | epub_copyright = copyright 192 | 193 | # The unique identifier of the text. This can be a ISBN number 194 | # or the project homepage. 195 | # 196 | # epub_identifier = '' 197 | 198 | # A unique identification for the text. 199 | # 200 | # epub_uid = '' 201 | 202 | # A list of files that should not be packed into the epub file. 203 | epub_exclude_files = ['search.html'] 204 | 205 | 206 | # Example configuration for intersphinx: refer to the Python standard library. 207 | intersphinx_mapping = {'python': ('https://docs.python.org/3', None), 208 | 'networkx': ('https://networkx.github.io/documentation/stable/', None), 209 | 'dwave': ('https://docs.dwavequantum.com/en/latest/', None), 210 | } 211 | 212 | # sort documentation they way the appear in the source file 213 | autodoc_member_order = 'bysource' 214 | 215 | # show inherited members 216 | # autodoc_default_flags = ['members', 'undoc-members', 'inherited-members', 'show-inheritance'] 217 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index_binarycsp: 2 | 3 | ============== 4 | dwavebinarycsp 5 | ============== 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommend using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | .. toctree:: 15 | :caption: Reference documentation for dwavebinarycsp: 16 | :maxdepth: 1 17 | 18 | reference/api_ref 19 | 20 | About dwavebinarycsp 21 | ==================== 22 | 23 | .. include:: README.rst 24 | :start-after: start_binarycsp_about 25 | :end-before: end_binarycsp_about -------------------------------------------------------------------------------- /docs/reference/api_ref.rst: -------------------------------------------------------------------------------- 1 | .. _binarycsp_api_ref: 2 | 3 | ============= 4 | API Reference 5 | ============= 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | csp 11 | compilers 12 | loading 13 | reduction 14 | constraint 15 | factory_constraints 16 | -------------------------------------------------------------------------------- /docs/reference/compilers.rst: -------------------------------------------------------------------------------- 1 | .. _compilers_csp: 2 | 3 | ====================================== 4 | Converting to a Binary Quadratic Model 5 | ====================================== 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | Constraint satisfaction problems can be converted to binary quadratic models to be solved 15 | on samplers such as the D-Wave system. 16 | 17 | .. currentmodule:: dwavebinarycsp 18 | 19 | Compilers 20 | ========= 21 | 22 | Compilers accept a constraint satisfaction problem and return a 23 | :obj:`dimod.BinaryQuadraticModel`. 24 | 25 | .. 26 | DEV NOTE: in the future compilers should be organized by type. For instance stitch simply builds 27 | a bqm for each constraint and then adds them together. But other more sophisticated compilers 28 | might be 'structured' or 'embedding' or similar. 29 | 30 | .. autosummary:: 31 | :toctree: generated/ 32 | 33 | stitch 34 | -------------------------------------------------------------------------------- /docs/reference/constraint.rst: -------------------------------------------------------------------------------- 1 | .. _constraint_csp: 2 | 3 | ==================== 4 | Defining Constraints 5 | ==================== 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | .. automodule:: dwavebinarycsp.core.constraint 15 | 16 | Class 17 | ===== 18 | 19 | .. currentmodule:: dwavebinarycsp 20 | .. autoclass:: Constraint 21 | 22 | 23 | Methods 24 | ======= 25 | 26 | Construction 27 | ------------ 28 | 29 | .. autosummary:: 30 | :toctree: generated/ 31 | 32 | Constraint.from_configurations 33 | Constraint.from_func 34 | 35 | Satisfiability 36 | -------------- 37 | 38 | .. autosummary:: 39 | :toctree: generated/ 40 | 41 | Constraint.check 42 | 43 | Transformations 44 | --------------- 45 | 46 | .. autosummary:: 47 | :toctree: generated/ 48 | 49 | Constraint.fix_variable 50 | Constraint.flip_variable 51 | 52 | Copies and projections 53 | ---------------------- 54 | 55 | .. autosummary:: 56 | :toctree: generated/ 57 | 58 | Constraint.copy 59 | Constraint.projection 60 | -------------------------------------------------------------------------------- /docs/reference/csp.rst: -------------------------------------------------------------------------------- 1 | .. _csp: 2 | 3 | ========================================= 4 | Defining Constraint Satisfaction Problems 5 | ========================================= 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | .. automodule:: dwavebinarycsp.core.csp 15 | 16 | Class 17 | ===== 18 | 19 | .. currentmodule:: dwavebinarycsp 20 | .. autoclass:: ConstraintSatisfactionProblem 21 | 22 | 23 | Methods 24 | ======= 25 | 26 | Adding variables and constraints 27 | -------------------------------- 28 | 29 | .. autosummary:: 30 | :toctree: generated/ 31 | 32 | ConstraintSatisfactionProblem.add_constraint 33 | ConstraintSatisfactionProblem.add_variable 34 | 35 | 36 | Satisfiability 37 | -------------- 38 | 39 | .. autosummary:: 40 | :toctree: generated/ 41 | 42 | ConstraintSatisfactionProblem.check 43 | 44 | 45 | Transformations 46 | --------------- 47 | 48 | .. autosummary:: 49 | :toctree: generated/ 50 | 51 | ConstraintSatisfactionProblem.fix_variable 52 | -------------------------------------------------------------------------------- /docs/reference/factory_constraints.rst: -------------------------------------------------------------------------------- 1 | .. _factory_constraints: 2 | 3 | ========= 4 | Factories 5 | ========= 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | `dwavebinarycsp` currently provides factories for constraints representing 15 | Boolean gates and satisfiability problems and CSPs for circuits and satisfiability 16 | problems. 17 | 18 | Constraints 19 | =========== 20 | 21 | .. automodule:: dwavebinarycsp.factories.constraint 22 | 23 | Gates 24 | ----- 25 | 26 | .. autosummary:: 27 | :toctree: generated/ 28 | 29 | gates.and_gate 30 | gates.or_gate 31 | gates.xor_gate 32 | gates.halfadder_gate 33 | gates.fulladder_gate 34 | 35 | Satisfiability Problems 36 | ----------------------- 37 | 38 | .. autosummary:: 39 | :toctree: generated/ 40 | 41 | sat.sat2in4 42 | 43 | CSPs 44 | ==== 45 | 46 | .. automodule:: dwavebinarycsp.factories.csp 47 | 48 | .. autosummary:: 49 | :toctree: generated/ 50 | 51 | circuits.multiplication_circuit 52 | sat.random_2in4sat 53 | sat.random_xorsat 54 | 55 | -------------------------------------------------------------------------------- /docs/reference/loading.rst: -------------------------------------------------------------------------------- 1 | .. _io_csp: 2 | 3 | ================= 4 | Other CSP Formats 5 | ================= 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | DIMACS 15 | ====== 16 | 17 | .. automodule:: dwavebinarycsp.io.cnf 18 | 19 | CNF 20 | --- 21 | 22 | .. autosummary:: 23 | :toctree: generated/ 24 | 25 | load_cnf 26 | -------------------------------------------------------------------------------- /docs/reference/reduction.rst: -------------------------------------------------------------------------------- 1 | .. _reduction_csp: 2 | 3 | ==================== 4 | Reducing Constraints 5 | ==================== 6 | 7 | .. deprecated:: 0.3.1 8 | 9 | ``dwavebinarycsp`` is deprecated and will be removed in Ocean 10. 10 | For solving problems with constraints, we recommand using the hybrid 11 | solvers in the Leap service. 12 | You can find documentation for the hybrid solvers at :ref:`opt_index_hybrid`. 13 | 14 | .. automodule:: dwavebinarycsp.reduction 15 | .. currentmodule:: dwavebinarycsp 16 | 17 | Functions 18 | ========= 19 | 20 | .. autosummary:: 21 | :toctree: generated/ 22 | 23 | irreducible_components 24 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | recommonmark 4 | 5 | dwave-samplers 6 | -------------------------------------------------------------------------------- /dwavebinarycsp/__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 warnings import warn as _warn # so it doesn't get pulled in by import * 16 | 17 | __version__ = '0.3.1' 18 | 19 | from dwavebinarycsp.compilers import * 20 | import dwavebinarycsp.compilers 21 | 22 | from dwavebinarycsp.core import * 23 | import dwavebinarycsp.core 24 | 25 | from dwavebinarycsp.reduction import * 26 | import dwavebinarycsp.reduction 27 | 28 | import dwavebinarycsp.exceptions 29 | 30 | import dwavebinarycsp.factories 31 | 32 | from dwavebinarycsp.io import * 33 | import dwavebinarycsp.io 34 | 35 | from dwavebinarycsp.package_info import __author__, __authoremail__, __description__ 36 | 37 | import dwavebinarycsp.testing 38 | 39 | # import dimod.Vartype, dimod.SPIN and dimod.BINARY into dwavebinarycsp namespace for convenience 40 | from dimod import Vartype, SPIN, BINARY 41 | 42 | 43 | def assert_penaltymodel_factory_available(): 44 | """Legacy - does nothing.""" 45 | pass 46 | 47 | 48 | _warn("dwavebinarycsp is deprecated and will be removed in Ocean 10. " 49 | "For solving problems with constraints, " 50 | "we recommend using the hybrid solvers in the Leap service. " 51 | "You can find documentation for the hybrid solvers at https://docs.ocean.dwavesys.com.", 52 | category=DeprecationWarning, 53 | stacklevel=2) 54 | -------------------------------------------------------------------------------- /dwavebinarycsp/compilers/__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 dwavebinarycsp.compilers.stitcher import * 16 | -------------------------------------------------------------------------------- /dwavebinarycsp/compilers/stitcher.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 itertools import combinations, count, product 16 | import operator 17 | 18 | import networkx as nx 19 | import penaltymodel 20 | import dimod 21 | 22 | from dwavebinarycsp.core.constraint import Constraint 23 | from dwavebinarycsp.exceptions import ImpossibleBQM 24 | from dwavebinarycsp.reduction import irreducible_components 25 | import dwavebinarycsp 26 | 27 | __all__ = ['stitch'] 28 | 29 | 30 | def stitch(csp, min_classical_gap=2.0, max_graph_size=8): 31 | """Build a binary quadratic model with minimal energy levels at solutions to the specified constraint satisfaction 32 | problem. 33 | 34 | Args: 35 | csp (:obj:`.ConstraintSatisfactionProblem`): 36 | Constraint satisfaction problem. 37 | 38 | min_classical_gap (float, optional, default=2.0): 39 | Minimum energy gap from ground. Each constraint violated by the solution increases 40 | the energy level of the binary quadratic model by at least this much relative 41 | to ground energy. 42 | 43 | max_graph_size (int, optional, default=8): 44 | Maximum number of variables in the binary quadratic model that can be used to 45 | represent a single constraint. 46 | 47 | Returns: 48 | :class:`~dimod.BinaryQuadraticModel` 49 | 50 | Notes: 51 | For a `min_classical_gap` > 2 or constraints with more than two variables, requires 52 | access to factories from the penaltymodel_ ecosystem to construct the binary quadratic 53 | model. 54 | 55 | .. _penaltymodel: https://github.com/dwavesystems/penaltymodel 56 | 57 | Examples: 58 | This example creates a binary-valued constraint satisfaction problem 59 | with two constraints, :math:`a = b` and :math:`b \\ne c`, and builds 60 | a binary quadratic model such that 61 | each constraint violation by a solution adds the default minimum energy gap. 62 | 63 | >>> import operator 64 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 65 | >>> csp.add_constraint(operator.eq, ['a', 'b']) # a == b 66 | >>> csp.add_constraint(operator.ne, ['b', 'c']) # b != c 67 | >>> bqm = dwavebinarycsp.stitch(csp) 68 | 69 | Variable assignments that satisfy the CSP above, violate one, then two constraints, 70 | produce energy increases of the default minimum classical gap: 71 | 72 | >>> bqm.energy({'a': 0, 'b': 0, 'c': 1}) # doctest: +SKIP 73 | -2.0 74 | >>> bqm.energy({'a': 0, 'b': 0, 'c': 0}) # doctest: +SKIP 75 | 0.0 76 | >>> bqm.energy({'a': 1, 'b': 0, 'c': 0}) # doctest: +SKIP 77 | 2.0 78 | 79 | This example creates a binary-valued constraint satisfaction problem 80 | with two constraints, :math:`a = b` and :math:`b \\ne c`, and builds 81 | a binary quadratic model with a minimum energy gap of 4. 82 | Note that in this case the conversion to binary quadratic model adds two 83 | ancillary variables that must be minimized over when solving. 84 | 85 | >>> import operator 86 | >>> import itertools 87 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 88 | >>> csp.add_constraint(operator.eq, ['a', 'b']) # a == b 89 | >>> csp.add_constraint(operator.ne, ['b', 'c']) # b != c 90 | >>> bqm = dwavebinarycsp.stitch(csp, min_classical_gap=4.0) 91 | >>> list(bqm) # doctest: +SKIP 92 | ['a', 'aux1', 'aux0', 'b', 'c'] 93 | 94 | Variable assignments that satisfy the CSP above, violate one, then two constraints, 95 | produce energy increases of the specified minimum classical gap: 96 | 97 | >>> min([bqm.energy({'a': 0, 'b': 0, 'c': 1, 'aux0': aux0, 'aux1': aux1}) for 98 | ... aux0, aux1 in list(itertools.product([0, 1], repeat=2))]) # doctest: +SKIP 99 | -6.0 100 | >>> min([bqm.energy({'a': 0, 'b': 0, 'c': 0, 'aux0': aux0, 'aux1': aux1}) for 101 | ... aux0, aux1 in list(itertools.product([0, 1], repeat=2))]) # doctest: +SKIP 102 | -2.0 103 | >>> min([bqm.energy({'a': 1, 'b': 0, 'c': 0, 'aux0': aux0, 'aux1': aux1}) for 104 | ... aux0, aux1 in list(itertools.product([0, 1], repeat=2))]) # doctest: +SKIP 105 | 2.0 106 | 107 | This example finds for the previous example the minimum graph size. 108 | 109 | >>> import operator 110 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 111 | >>> csp.add_constraint(operator.eq, ['a', 'b']) # a == b 112 | >>> csp.add_constraint(operator.ne, ['b', 'c']) # b != c 113 | >>> for n in range(8, 1, -1): 114 | ... try: 115 | ... bqm = dwavebinarycsp.stitch(csp, min_classical_gap=4.0, max_graph_size=n) 116 | ... except dwavebinarycsp.exceptions.ImpossibleBQM: 117 | ... print(n+1) 118 | ... 119 | 3 120 | 121 | """ 122 | 123 | def aux_factory(): 124 | for i in count(): 125 | yield 'aux{}'.format(i) 126 | 127 | aux = aux_factory() 128 | 129 | bqm = dimod.BinaryQuadraticModel.empty(csp.vartype) 130 | 131 | # developer note: we could cache them and relabel, for now though let's do the simple thing 132 | # penalty_models = {} 133 | for const in csp.constraints: 134 | configurations = const.configurations 135 | 136 | if len(const.variables) > max_graph_size: 137 | msg = ("The given csp contains a constraint {const} with {num_var} variables. " 138 | "This cannot be mapped to a graph with {max_graph_size} nodes. " 139 | "Consider checking whether your constraint is irreducible." 140 | "").format(const=const, num_var=len(const.variables), max_graph_size=max_graph_size) 141 | raise ImpossibleBQM(msg) 142 | 143 | pmodel = None 144 | 145 | if len(const) == 0: 146 | # empty constraint 147 | continue 148 | 149 | # at the moment, penaltymodel-cache cannot handle 1-variable PMs, so 150 | # we handle that case here 151 | if min_classical_gap <= 2.0 and len(const) == 1 and max_graph_size >= 1: 152 | bqm.update(_bqm_from_1sat(const)) 153 | continue 154 | 155 | # turn the configurations into a sampleset 156 | samples_like = (list(configurations), const.variables) 157 | 158 | for G in iter_complete_graphs(const.variables, max_graph_size + 1, aux): 159 | 160 | try: 161 | pmodel, classical_gap = penaltymodel.get_penalty_model( 162 | samples_like, 163 | G, 164 | min_classical_gap=min_classical_gap 165 | ) 166 | except penaltymodel.ImpossiblePenaltyModel: 167 | # not able to be built on this graph 168 | continue 169 | 170 | pmodel.change_vartype(csp.vartype, inplace=True) 171 | 172 | if classical_gap >= min_classical_gap: 173 | break 174 | 175 | else: 176 | msg = ("No penalty model can be built for constraint {}".format(const)) 177 | raise ImpossibleBQM(msg) 178 | 179 | bqm.update(pmodel) 180 | 181 | return bqm 182 | 183 | 184 | def _bqm_from_1sat(constraint): 185 | """create a bqm for a constraint with only one variable 186 | 187 | bqm will have exactly classical gap 2. 188 | """ 189 | configurations = constraint.configurations 190 | num_configurations = len(configurations) 191 | 192 | bqm = dimod.BinaryQuadraticModel.empty(dimod.SPIN) 193 | 194 | if num_configurations == 1: 195 | val, = next(iter(configurations)) 196 | v, = constraint.variables 197 | bqm.add_variable(v, -1 if val > 0 else +1) 198 | else: 199 | bqm.add_variables_from((v, 0.0) for v in constraint.variables) 200 | 201 | return bqm.change_vartype(constraint.vartype) 202 | 203 | 204 | @nx.utils.nodes_or_number(0) 205 | def iter_complete_graphs(start, stop, factory=None): 206 | """Iterate over complete graphs. 207 | 208 | Args: 209 | start (int/iterable): 210 | Define the size of the starting graph. 211 | If an int, the nodes will be index-labeled, otherwise should be an iterable of node 212 | labels. 213 | 214 | stop (int): 215 | Stops yielding graphs when the size equals stop. 216 | 217 | factory (iterator, optional): 218 | If provided, nodes added will be labeled according to the values returned by factory. 219 | Otherwise the extra nodes will be index-labeled. 220 | 221 | Yields: 222 | :class:`nx.Graph` 223 | 224 | """ 225 | _, nodes = start 226 | nodes = list(nodes) # we'll be appending 227 | 228 | if factory is None: 229 | factory = count() 230 | 231 | while len(nodes) < stop: 232 | # we need to construct a new graph each time, this is actually faster than copy and add 233 | # the new edges in any case 234 | G = nx.complete_graph(nodes) 235 | yield G 236 | 237 | v = next(factory) 238 | while v in G: 239 | v = next(factory) 240 | 241 | nodes.append(v) 242 | -------------------------------------------------------------------------------- /dwavebinarycsp/core/__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 dwavebinarycsp.core.constraint import * 16 | from dwavebinarycsp.core.csp import * 17 | -------------------------------------------------------------------------------- /dwavebinarycsp/core/constraint.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 | Solutions to a constraint satisfaction problem must satisfy certains conditions, the 17 | constraints of the problem, such as equality and inequality constraints. 18 | The :class:`Constraint` class defines constraints and provides functionality to 19 | assist in constraint definition, such as verifying whether a candidate solution satisfies 20 | a constraint. 21 | """ 22 | import itertools 23 | 24 | from collections.abc import Sized, Callable 25 | 26 | import dimod 27 | 28 | from dwavebinarycsp.exceptions import UnsatError 29 | 30 | __all__ = ['Constraint'] 31 | 32 | 33 | class Constraint(Sized): 34 | """A constraint. 35 | 36 | Attributes: 37 | variables (tuple): 38 | Variables associated with the constraint. 39 | 40 | func (function): 41 | Function that returns True for configurations of variables that satisfy the 42 | constraint. Inputs to the function are ordered by :attr:`~Constraint.variables`. 43 | 44 | configurations (frozenset[tuple]): 45 | Valid configurations of the variables. Each configuration is a tuple of variable 46 | assignments ordered by :attr:`~Constraint.variables`. 47 | 48 | vartype (:class:`dimod.Vartype`): 49 | Variable type for the constraint. Accepted input values: 50 | 51 | * :attr:`~dimod.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` 52 | * :attr:`~dimod.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` 53 | 54 | name (str): 55 | Name for the constraint. If not provided on construction, defaults to 56 | 'Constraint'. 57 | 58 | Examples: 59 | This example defines a constraint, named "plus1", based on a function that 60 | is True for :math:`(y1,y0) = (x1,x0)+1` on binary variables, and demonstrates 61 | some of the constraint's functionality. 62 | 63 | >>> def plus_one(y1, y0, x1, x0): # y=x++ for two bit binary numbers 64 | ... return (y1, y0, x1, x0) in [(0, 1, 0, 0), (1, 0, 0, 1), (1, 1, 1, 0)] 65 | ... 66 | >>> const = dwavebinarycsp.Constraint.from_func( 67 | ... plus_one, 68 | ... ['out1', 'out0', 'in1', 'in0'], 69 | ... dwavebinarycsp.BINARY, 70 | ... name='plus1') 71 | >>> print(const.name) # Check constraint defined as intended 72 | plus1 73 | >>> len(const) 74 | 4 75 | >>> in0, in1, out0, out1 = 0, 0, 1, 0 76 | >>> const.func(out1, out0, in1, in0) # Order matches variables 77 | True 78 | 79 | This example defines a constraint based on specified valid configurations 80 | that represents an AND gate for spin variables, and demonstrates some of 81 | the constraint's functionality. 82 | 83 | >>> const = dwavebinarycsp.Constraint.from_configurations( 84 | ... [(-1, -1, -1), (-1, -1, 1), (-1, 1, -1), (1, 1, 1)], 85 | ... ['y', 'x1', 'x2'], 86 | ... dwavebinarycsp.SPIN) 87 | >>> print(const.name) # Check constraint defined as intended 88 | Constraint 89 | >>> isinstance(const, dwavebinarycsp.core.constraint.Constraint) 90 | True 91 | >>> (-1, 1, -1) in const.configurations # Order matches variables: y,x1,x2 92 | True 93 | 94 | """ 95 | 96 | __slots__ = ('vartype', 'variables', 'configurations', 'func', 'name') 97 | 98 | # 99 | # Construction 100 | # 101 | 102 | @dimod.decorators.vartype_argument('vartype') 103 | def __init__(self, func, configurations, variables, vartype, name=None): 104 | 105 | self.vartype = vartype # checked by decorator 106 | 107 | if not isinstance(func, Callable): 108 | raise TypeError("expected input 'func' to be callable") 109 | self.func = func 110 | 111 | self.variables = variables = tuple(variables) 112 | num_variables = len(variables) 113 | 114 | if not isinstance(configurations, frozenset): 115 | configurations = frozenset(tuple(config) for config in configurations) # cast to tuples 116 | if len(configurations) == 0 and num_variables > 0: 117 | raise ValueError("constraint must have at least one feasible configuration") 118 | if not all(len(config) == num_variables for config in configurations): 119 | raise ValueError("all configurations should be of the same length") 120 | if len(vartype.value.union(*configurations)) >= 3: 121 | raise ValueError("configurations do not match vartype") 122 | self.configurations = configurations 123 | 124 | if name is None: 125 | name = 'Constraint' 126 | self.name = name 127 | 128 | @classmethod 129 | @dimod.decorators.vartype_argument('vartype') 130 | def from_func(cls, func, variables, vartype, name=None): 131 | """Construct a constraint from a validation function. 132 | 133 | Args: 134 | func (function): 135 | Function that evaluates True when the variables satisfy the constraint. 136 | 137 | variables (iterable): 138 | Iterable of variable labels. 139 | 140 | vartype (:class:`~dimod.Vartype`/str/set): 141 | Variable type for the constraint. Accepted input values: 142 | 143 | * :attr:`~dimod.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` 144 | * :attr:`~dimod.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` 145 | 146 | name (string, optional, default='Constraint'): 147 | Name for the constraint. 148 | 149 | Examples: 150 | This example creates a constraint that binary variables `a` and `b` 151 | are not equal. 152 | 153 | >>> import operator 154 | >>> const = dwavebinarycsp.Constraint.from_func(operator.ne, ['a', 'b'], 'BINARY') 155 | >>> print(const.name) 156 | Constraint 157 | >>> (0, 1) in const.configurations 158 | True 159 | 160 | This example creates a constraint that :math:`out = NOT(x)` 161 | for spin variables. 162 | 163 | >>> def not_(y, x): # y=NOT(x) for spin variables 164 | ... return (y == -x) 165 | ... 166 | >>> const = dwavebinarycsp.Constraint.from_func( 167 | ... not_, 168 | ... ['out', 'in'], 169 | ... {1, -1}, 170 | ... name='not_spin') 171 | >>> print(const.name) 172 | not_spin 173 | >>> (1, -1) in const.configurations 174 | True 175 | 176 | """ 177 | variables = tuple(variables) 178 | 179 | configurations = frozenset(config 180 | for config in itertools.product(vartype.value, repeat=len(variables)) 181 | if func(*config)) 182 | 183 | return cls(func, configurations, variables, vartype, name) 184 | 185 | @classmethod 186 | def from_configurations(cls, configurations, variables, vartype, name=None): 187 | """Construct a constraint from valid configurations. 188 | 189 | Args: 190 | configurations (iterable[tuple]): 191 | Valid configurations of the variables. Each configuration is a tuple of variable 192 | assignments ordered by :attr:`~Constraint.variables`. 193 | 194 | variables (iterable): 195 | Iterable of variable labels. 196 | 197 | vartype (:class:`~dimod.Vartype`/str/set): 198 | Variable type for the constraint. Accepted input values: 199 | 200 | * :attr:`~dimod.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` 201 | * :attr:`~dimod.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` 202 | 203 | name (string, optional, default='Constraint'): 204 | Name for the constraint. 205 | 206 | Examples: 207 | 208 | This example creates a constraint that variables `a` and `b` are not equal. 209 | 210 | >>> const = dwavebinarycsp.Constraint.from_configurations([(0, 1), (1, 0)], 211 | ... ['a', 'b'], dwavebinarycsp.BINARY) 212 | >>> print(const.name) 213 | Constraint 214 | >>> (0, 0) in const.configurations # Order matches variables: a,b 215 | False 216 | 217 | This example creates a constraint based on specified valid configurations 218 | that represents an OR gate for spin variables. 219 | 220 | >>> const = dwavebinarycsp.Constraint.from_configurations( 221 | ... [(-1, -1, -1), (1, -1, 1), (1, 1, -1), (1, 1, 1)], 222 | ... ['y', 'x1', 'x2'], 223 | ... dwavebinarycsp.SPIN, name='or_spin') 224 | >>> print(const.name) 225 | or_spin 226 | >>> (1, 1, -1) in const.configurations # Order matches variables: y,x1,x2 227 | True 228 | 229 | """ 230 | def func(*args): return args in configurations 231 | 232 | return cls(func, configurations, variables, vartype, name) 233 | 234 | # 235 | # Special Methods 236 | # 237 | 238 | def __len__(self): 239 | """The number of variables.""" 240 | return self.variables.__len__() 241 | 242 | def __repr__(self): 243 | return "Constraint.from_configurations({}, {}, {}, name='{}')".format(self.configurations, 244 | self.variables, 245 | self.vartype, 246 | self.name) 247 | 248 | def __eq__(self, constraint): 249 | return self.variables == constraint.variables and self.configurations == constraint.configurations 250 | 251 | def __ne__(self, constraint): 252 | return not self.__eq__(constraint) 253 | 254 | def __hash__(self): 255 | # uniquely defined by configurations/variables 256 | return hash((self.configurations, self.variables)) 257 | 258 | def __or__(self, const): 259 | if not isinstance(const, Constraint): 260 | raise TypeError("unsupported operand type(s) for |: 'Constraint' and '{}'".format(type(const).__name__)) 261 | 262 | if const and self and self.vartype is not const.vartype: 263 | raise ValueError("operand | only meaningful for Constraints with matching vartype") 264 | 265 | shared_variables = set(self.variables).intersection(const.variables) 266 | 267 | # dev note: if they share all variables, we could just act on the configurations 268 | 269 | if not shared_variables: 270 | # in this case we just append 271 | variables = self.variables + const.variables 272 | 273 | n = len(self) # need to know how to divide up the variables 274 | 275 | def union(*args): 276 | return self.func(*args[:n]) or const.func(*args[n:]) 277 | 278 | return self.from_func(union, variables, self.vartype, name='{} | {}'.format(self.name, const.name)) 279 | 280 | variables = self.variables + tuple(v for v in const.variables if v not in shared_variables) 281 | 282 | def union(*args): 283 | solution = dict(zip(variables, args)) 284 | return self.check(solution) or const.check(solution) 285 | 286 | return self.from_func(union, variables, self.vartype, name='{} | {}'.format(self.name, const.name)) 287 | 288 | def __and__(self, const): 289 | if not isinstance(const, Constraint): 290 | raise TypeError("unsupported operand type(s) for &: 'Constraint' and '{}'".format(type(const).__name__)) 291 | 292 | if const and self and self.vartype is not const.vartype: 293 | raise ValueError("operand & only meaningful for Constraints with matching vartype") 294 | 295 | shared_variables = set(self.variables).intersection(const.variables) 296 | 297 | # dev note: if they share all variables, we could just act on the configurations 298 | name = '{} & {}'.format(self.name, const.name) 299 | 300 | if not shared_variables: 301 | # in this case we just append 302 | variables = self.variables + const.variables 303 | 304 | n = len(self) # need to know how to divide up the variables 305 | 306 | def intersection(*args): 307 | return self.func(*args[:n]) and const.func(*args[n:]) 308 | 309 | return self.from_func(intersection, variables, self.vartype, name=name) 310 | 311 | variables = self.variables + tuple(v for v in const.variables if v not in shared_variables) 312 | 313 | def intersection(*args): 314 | solution = dict(zip(variables, args)) 315 | return self.check(solution) and const.check(solution) 316 | 317 | return self.from_func(intersection, variables, self.vartype, name=name) 318 | 319 | # 320 | # verification 321 | # 322 | 323 | def check(self, solution): 324 | """Check that a solution satisfies the constraint. 325 | 326 | Args: 327 | solution (container): 328 | An assignment for the variables in the constraint. 329 | 330 | Returns: 331 | bool: True if the solution satisfies the constraint; otherwise False. 332 | 333 | Examples: 334 | This example creates a constraint that :math:`a \\ne b` on binary variables 335 | and tests it for two candidate solutions, with additional unconstrained 336 | variable c. 337 | 338 | >>> const = dwavebinarycsp.Constraint.from_configurations([(0, 1), (1, 0)], 339 | ... ['a', 'b'], dwavebinarycsp.BINARY) 340 | >>> solution = {'a': 1, 'b': 1, 'c': 0} 341 | >>> const.check(solution) 342 | False 343 | >>> solution = {'a': 1, 'b': 0, 'c': 0} 344 | >>> const.check(solution) 345 | True 346 | 347 | """ 348 | return self.func(*(solution[v] for v in self.variables)) 349 | 350 | # 351 | # transformation 352 | # 353 | 354 | def fix_variable(self, v, value): 355 | """Fix the value of a variable and remove it from the constraint. 356 | 357 | Args: 358 | v (variable): 359 | Variable in the constraint to be set to a constant value. 360 | 361 | val (int): 362 | Value assigned to the variable. Values must match the :class:`.Vartype` of the 363 | constraint. 364 | 365 | Examples: 366 | This example creates a constraint that :math:`a \\ne b` on binary variables, 367 | fixes variable a to 0, and tests two candidate solutions. 368 | 369 | >>> const = dwavebinarycsp.Constraint.from_func(operator.ne, 370 | ... ['a', 'b'], dwavebinarycsp.BINARY) 371 | >>> const.fix_variable('a', 0) 372 | >>> const.check({'b': 1}) 373 | True 374 | >>> const.check({'b': 0}) 375 | False 376 | 377 | """ 378 | variables = self.variables 379 | try: 380 | idx = variables.index(v) 381 | except ValueError: 382 | raise ValueError("given variable {} is not part of the constraint".format(v)) 383 | 384 | if value not in self.vartype.value: 385 | raise ValueError("expected value to be in {}, received {} instead".format(self.vartype.value, value)) 386 | 387 | configurations = frozenset(config[:idx] + config[idx + 1:] # exclude the fixed var 388 | for config in self.configurations 389 | if config[idx] == value) 390 | 391 | if not configurations: 392 | raise UnsatError("fixing {} to {} makes this constraint unsatisfiable".format(v, value)) 393 | 394 | variables = variables[:idx] + variables[idx + 1:] 395 | 396 | self.configurations = configurations 397 | self.variables = variables 398 | 399 | def func(*args): return args in configurations 400 | self.func = func 401 | 402 | self.name = '{} ({} fixed to {})'.format(self.name, v, value) 403 | 404 | def flip_variable(self, v): 405 | """Flip a variable in the constraint. 406 | 407 | Args: 408 | v (variable): 409 | Variable in the constraint to take the complementary value of its 410 | construction value. 411 | 412 | Examples: 413 | This example creates a constraint that :math:`a = b` on binary variables 414 | and flips variable a. 415 | 416 | >>> const = dwavebinarycsp.Constraint.from_func(operator.eq, 417 | ... ['a', 'b'], dwavebinarycsp.BINARY) 418 | >>> const.check({'a': 0, 'b': 0}) 419 | True 420 | >>> const.flip_variable('a') 421 | >>> const.check({'a': 1, 'b': 0}) 422 | True 423 | >>> const.check({'a': 0, 'b': 0}) 424 | False 425 | 426 | """ 427 | try: 428 | idx = self.variables.index(v) 429 | except ValueError: 430 | raise ValueError("variable {} is not a variable in constraint {}".format(v, self.name)) 431 | 432 | if self.vartype is dimod.BINARY: 433 | 434 | original_func = self.func 435 | 436 | def func(*args): 437 | new_args = list(args) 438 | new_args[idx] = 1 - new_args[idx] # negate v 439 | return original_func(*new_args) 440 | 441 | self.func = func 442 | 443 | self.configurations = frozenset(config[:idx] + (1 - config[idx],) + config[idx + 1:] 444 | for config in self.configurations) 445 | 446 | else: # SPIN 447 | 448 | original_func = self.func 449 | 450 | def func(*args): 451 | new_args = list(args) 452 | new_args[idx] = -new_args[idx] # negate v 453 | return original_func(*new_args) 454 | 455 | self.func = func 456 | 457 | self.configurations = frozenset(config[:idx] + (-config[idx],) + config[idx + 1:] 458 | for config in self.configurations) 459 | 460 | self.name = '{} ({} flipped)'.format(self.name, v) 461 | 462 | # 463 | # copies and projections 464 | # 465 | 466 | def copy(self): 467 | """Create a copy. 468 | 469 | Examples: 470 | This example copies constraint :math:`a \\ne b` and tests a solution 471 | on the copied constraint. 472 | 473 | >>> import operator 474 | >>> const = dwavebinarycsp.Constraint.from_func(operator.ne, 475 | ... ['a', 'b'], 'BINARY') 476 | >>> const2 = const.copy() 477 | >>> const2 is const 478 | False 479 | >>> const2.check({'a': 1, 'b': 1}) 480 | False 481 | 482 | """ 483 | # each object is itself immutable (except the function) 484 | return self.__class__(self.func, self.configurations, self.variables, self.vartype, name=self.name) 485 | 486 | def projection(self, variables): 487 | """Create a new constraint that is the projection onto a subset of the variables. 488 | 489 | Args: 490 | variables (iterable): 491 | Subset of the constraint's variables. 492 | 493 | Returns: 494 | :obj:`.Constraint`: A new constraint over a subset of the variables. 495 | 496 | Examples: 497 | 498 | >>> const = dwavebinarycsp.Constraint.from_configurations([(0, 0), (0, 1)], 499 | ... ['a', 'b'], 500 | ... dwavebinarycsp.BINARY) 501 | >>> proj = const.projection(['a']) 502 | >>> proj.variables 503 | ('a',) 504 | >>> proj.configurations 505 | frozenset({(0,)}) 506 | 507 | """ 508 | # resolve iterables or mutability problems by casting the variables to a set 509 | variables = set(variables) 510 | 511 | if not variables.issubset(self.variables): 512 | raise ValueError("Cannot project to variables not in the constraint.") 513 | 514 | idxs = [i for i, v in enumerate(self.variables) if v in variables] 515 | 516 | configurations = frozenset(tuple(config[i] for i in idxs) for config in self.configurations) 517 | variables = tuple(self.variables[i] for i in idxs) 518 | 519 | return self.from_configurations(configurations, variables, self.vartype) 520 | -------------------------------------------------------------------------------- /dwavebinarycsp/core/csp.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 | Constraint satisfaction problems require that all a problem's variables be assigned 17 | values, out of a finite domain, that result in the satisfying of all constraints. 18 | The :class:`ConstraintSatisfactionProblem` class aggregates all constraints and variables 19 | defined for a problem and provides functionality to assist in problem solution, such 20 | as verifying whether a candidate solution satisfies the constraints. 21 | """ 22 | from collections import defaultdict 23 | from collections.abc import Callable, Iterable 24 | 25 | import dimod 26 | 27 | from dwavebinarycsp.core.constraint import Constraint 28 | 29 | 30 | class ConstraintSatisfactionProblem(object): 31 | """A constraint satisfaction problem. 32 | 33 | Args: 34 | vartype (:class:`~dimod.Vartype`/str/set): 35 | Variable type for the binary quadratic model. Supported values are: 36 | 37 | * :attr:`~dimod.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` 38 | * :attr:`~dimod.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` 39 | 40 | Attributes: 41 | constraints (list[:obj:`.Constraint`]): 42 | Constraints that together constitute the constraint satisfaction problem. Valid solutions 43 | satisfy all of the constraints. 44 | 45 | variables (dict[variable, list[:obj:`.Constraint`]]): 46 | Variables of the constraint satisfaction problem as a dict, where keys are the variables 47 | and values a list of all of constraints associated with the variable. 48 | 49 | vartype (:class:`dimod.Vartype`): 50 | Enumeration of valid variable types. Supported values are :attr:`~dimod.Vartype.SPIN` 51 | or :attr:`~dimod.Vartype.BINARY`. If `vartype` is SPIN, variables can be assigned -1 or 1; 52 | if BINARY, variables can be assigned 0 or 1. 53 | 54 | Example: 55 | This example creates a binary-valued constraint satisfaction problem, adds two constraints, 56 | :math:`a = b` and :math:`b \\ne c`, and tests :math:`a,b,c = 1,1,0`. 57 | 58 | >>> import operator 59 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem('BINARY') 60 | >>> csp.add_constraint(operator.eq, ['a', 'b']) 61 | >>> csp.add_constraint(operator.ne, ['b', 'c']) 62 | >>> csp.check({'a': 1, 'b': 1, 'c': 0}) 63 | True 64 | 65 | """ 66 | @dimod.decorators.vartype_argument('vartype') 67 | def __init__(self, vartype): 68 | self.vartype = vartype 69 | self.constraints = [] 70 | self.variables = defaultdict(list) 71 | 72 | def __len__(self): 73 | return self.constraints.__len__() 74 | 75 | def add_constraint(self, constraint, variables=tuple()): 76 | """Add a constraint. 77 | 78 | Args: 79 | constraint (function/iterable/:obj:`.Constraint`): 80 | Constraint definition in one of the supported formats: 81 | 82 | 1. Function, with input arguments matching the order and 83 | :attr:`~.ConstraintSatisfactionProblem.vartype` type of the `variables` 84 | argument, that evaluates True when the constraint is satisfied. 85 | 2. List explicitly specifying each allowed configuration as a tuple. 86 | 3. :obj:`.Constraint` object built either explicitly or by :mod:`dwavebinarycsp.factories`. 87 | 88 | variables(iterable): 89 | Variables associated with the constraint. Not required when `constraint` is 90 | a :obj:`.Constraint` object. 91 | 92 | Examples: 93 | This example defines a function that evaluates True when the constraint is satisfied. 94 | The function's input arguments match the order and type of the `variables` argument. 95 | 96 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 97 | >>> def all_equal(a, b, c): # works for both dwavebinarycsp.BINARY and dwavebinarycsp.SPIN 98 | ... return (a == b) and (b == c) 99 | >>> csp.add_constraint(all_equal, ['a', 'b', 'c']) 100 | >>> csp.check({'a': 0, 'b': 0, 'c': 0}) 101 | True 102 | >>> csp.check({'a': 0, 'b': 0, 'c': 1}) 103 | False 104 | 105 | This example explicitly lists allowed configurations. 106 | 107 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) 108 | >>> eq_configurations = {(-1, -1), (1, 1)} 109 | >>> csp.add_constraint(eq_configurations, ['v0', 'v1']) 110 | >>> csp.check({'v0': -1, 'v1': +1}) 111 | False 112 | >>> csp.check({'v0': -1, 'v1': -1}) 113 | True 114 | 115 | This example uses a :obj:`.Constraint` object built by :mod:`dwavebinarycsp.factories`. 116 | 117 | >>> import dwavebinarycsp.factories.constraint.gates as gates 118 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 119 | >>> csp.add_constraint(gates.and_gate(['a', 'b', 'c'])) # add an AND gate 120 | >>> csp.add_constraint(gates.xor_gate(['a', 'c', 'd'])) # add an XOR gate 121 | >>> csp.check({'a': 1, 'b': 0, 'c': 0, 'd': 1}) 122 | True 123 | 124 | """ 125 | if isinstance(constraint, Constraint): 126 | if variables and (tuple(variables) != constraint.variables): 127 | raise ValueError("mismatched variables and Constraint") 128 | elif isinstance(constraint, Callable): 129 | constraint = Constraint.from_func(constraint, variables, self.vartype) 130 | elif isinstance(constraint, Iterable): 131 | constraint = Constraint.from_configurations(constraint, variables, self.vartype) 132 | else: 133 | raise TypeError("Unknown constraint type given") 134 | 135 | self.constraints.append(constraint) 136 | for v in constraint.variables: 137 | self.variables[v].append(constraint) 138 | 139 | def add_variable(self, v): 140 | """Add a variable. 141 | 142 | Args: 143 | v (variable): 144 | Variable in the constraint satisfaction problem. May be of any type that 145 | can be a dict key. 146 | 147 | Examples: 148 | This example adds two variables, one of which is already used in a constraint 149 | of the constraint satisfaction problem. 150 | 151 | >>> import operator 152 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) 153 | >>> csp.add_constraint(operator.eq, ['a', 'b']) 154 | >>> csp.add_variable('a') # does nothing, already added as part of the constraint 155 | >>> csp.add_variable('c') 156 | >>> csp.check({'a': -1, 'b': -1, 'c': 1}) 157 | True 158 | >>> csp.check({'a': -1, 'b': -1, 'c': -1}) 159 | True 160 | 161 | """ 162 | self.variables[v] # because defaultdict will create it if it's not there 163 | 164 | def check(self, solution): 165 | """Check that a solution satisfies all of the constraints. 166 | 167 | Args: 168 | solution (container): 169 | An assignment of values for the variables in the constraint satisfaction problem. 170 | 171 | Returns: 172 | bool: True if the solution satisfies all of the constraints; False otherwise. 173 | 174 | Examples: 175 | This example creates a binary-valued constraint satisfaction problem, adds 176 | two logic gates implementing Boolean constraints, :math:`c = a \wedge b` 177 | and :math:`d = a \oplus c`, and verifies that the combined problem is satisfied 178 | for a given assignment. 179 | 180 | >>> import dwavebinarycsp.factories.constraint.gates as gates 181 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 182 | >>> csp.add_constraint(gates.and_gate(['a', 'b', 'c'])) # add an AND gate 183 | >>> csp.add_constraint(gates.xor_gate(['a', 'c', 'd'])) # add an XOR gate 184 | >>> csp.check({'a': 1, 'b': 0, 'c': 0, 'd': 1}) 185 | True 186 | 187 | """ 188 | return all(constraint.check(solution) for constraint in self.constraints) 189 | 190 | def fix_variable(self, v, value): 191 | """Fix the value of a variable and remove it from the constraint satisfaction problem. 192 | 193 | Args: 194 | v (variable): 195 | Variable to be fixed in the constraint satisfaction problem. 196 | 197 | value (int): 198 | Value assigned to the variable. Values must match the 199 | :attr:`~.ConstraintSatisfactionProblem.vartype` of the constraint 200 | satisfaction problem. 201 | 202 | Examples: 203 | This example creates a spin-valued constraint satisfaction problem, adds two constraints, 204 | :math:`a = b` and :math:`b \\ne c`, and fixes variable b to +1. 205 | 206 | >>> import operator 207 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) 208 | >>> csp.add_constraint(operator.eq, ['a', 'b']) 209 | >>> csp.add_constraint(operator.ne, ['b', 'c']) 210 | >>> csp.check({'a': +1, 'b': +1, 'c': -1}) 211 | True 212 | >>> csp.check({'a': -1, 'b': -1, 'c': +1}) 213 | True 214 | >>> csp.fix_variable('b', +1) 215 | >>> csp.check({'a': +1, 'b': +1, 'c': -1}) # 'b' is ignored 216 | True 217 | >>> csp.check({'a': -1, 'b': -1, 'c': +1}) 218 | False 219 | >>> csp.check({'a': +1, 'c': -1}) 220 | True 221 | >>> csp.check({'a': -1, 'c': +1}) 222 | False 223 | 224 | """ 225 | if v not in self.variables: 226 | raise ValueError("given variable {} is not part of the constraint satisfaction problem".format(v)) 227 | 228 | for constraint in self.variables[v]: 229 | constraint.fix_variable(v, value) 230 | 231 | del self.variables[v] # delete the variable 232 | 233 | 234 | CSP = ConstraintSatisfactionProblem 235 | """An alias for :class:`.ConstraintSatisfactionProblem`.""" 236 | -------------------------------------------------------------------------------- /dwavebinarycsp/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 | import penaltymodel 16 | 17 | __all__ = ['UnsatError', 'ImpossibleBQM'] 18 | 19 | 20 | class UnsatError(Exception): 21 | """Constraint or csp cannot be satisfied""" 22 | 23 | 24 | class ImpossibleBQM(penaltymodel.ImpossiblePenaltyModel): 25 | """When a BQM cannot be built from a constraint/penaltymodel""" 26 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/__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 dwavebinarycsp.factories.constraint import * 16 | from dwavebinarycsp.factories.csp import * 17 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/constraint/__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 dwavebinarycsp.factories.constraint.gates import * 16 | from dwavebinarycsp.factories.constraint.sat import * 17 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/constraint/gates.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 dimod 16 | 17 | from dwavebinarycsp.core.constraint import Constraint 18 | 19 | __all__ = ['and_gate', 20 | 'or_gate', 21 | 'xor_gate', 22 | 'halfadder_gate', 23 | 'fulladder_gate'] 24 | 25 | 26 | @dimod.decorators.vartype_argument('vartype') 27 | def and_gate(variables, vartype=dimod.BINARY, name='AND'): 28 | """AND gate. 29 | 30 | Args: 31 | variables (list): Variable labels for the and gate as `[in1, in2, out]`, 32 | where `in1, in2` are inputs and `out` the gate's output. 33 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 34 | input values: 35 | 36 | * Vartype.SPIN, 'SPIN', {-1, 1} 37 | * Vartype.BINARY, 'BINARY', {0, 1} 38 | name (str, optional, default='AND'): Name for the constraint. 39 | 40 | Returns: 41 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 42 | assigned values that match the valid states of an AND gate. 43 | 44 | Examples: 45 | >>> import dwavebinarycsp.factories.constraint.gates as gates 46 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 47 | >>> csp.add_constraint(gates.and_gate(['a', 'b', 'c'], name='AND1')) 48 | >>> csp.check({'a': 1, 'b': 0, 'c': 0}) 49 | True 50 | """ 51 | 52 | variables = tuple(variables) 53 | 54 | if vartype is dimod.BINARY: 55 | configurations = frozenset([(0, 0, 0), 56 | (0, 1, 0), 57 | (1, 0, 0), 58 | (1, 1, 1)]) 59 | 60 | def func(in1, in2, out): return (in1 and in2) == out 61 | 62 | else: 63 | # SPIN, vartype is checked by the decorator 64 | configurations = frozenset([(-1, -1, -1), 65 | (-1, +1, -1), 66 | (+1, -1, -1), 67 | (+1, +1, +1)]) 68 | 69 | def func(in1, in2, out): return ((in1 > 0) and (in2 > 0)) == (out > 0) 70 | 71 | return Constraint(func, configurations, variables, vartype=vartype, name=name) 72 | 73 | 74 | @dimod.decorators.vartype_argument('vartype') 75 | def or_gate(variables, vartype=dimod.BINARY, name='OR'): 76 | """OR gate. 77 | 78 | Args: 79 | variables (list): Variable labels for the and gate as `[in1, in2, out]`, 80 | where `in1, in2` are inputs and `out` the gate's output. 81 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 82 | input values: 83 | 84 | * Vartype.SPIN, 'SPIN', {-1, 1} 85 | * Vartype.BINARY, 'BINARY', {0, 1} 86 | name (str, optional, default='OR'): Name for the constraint. 87 | 88 | Returns: 89 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 90 | assigned values that match the valid states of an OR gate. 91 | 92 | Examples: 93 | >>> import dwavebinarycsp.factories.constraint.gates as gates 94 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) 95 | >>> csp.add_constraint(gates.or_gate(['x', 'y', 'z'], {-1,1}, name='OR1')) 96 | >>> csp.check({'x': 1, 'y': -1, 'z': 1}) 97 | True 98 | """ 99 | 100 | variables = tuple(variables) 101 | 102 | if vartype is dimod.BINARY: 103 | configs = frozenset([(0, 0, 0), 104 | (0, 1, 1), 105 | (1, 0, 1), 106 | (1, 1, 1)]) 107 | 108 | def func(in1, in2, out): return (in1 or in2) == out 109 | 110 | else: 111 | # SPIN, vartype is checked by the decorator 112 | configs = frozenset([(-1, -1, -1), 113 | (-1, +1, +1), 114 | (+1, -1, +1), 115 | (+1, +1, +1)]) 116 | 117 | def func(in1, in2, out): return ((in1 > 0) or (in2 > 0)) == (out > 0) 118 | 119 | return Constraint(func, configs, variables, vartype=vartype, name=name) 120 | 121 | 122 | @dimod.decorators.vartype_argument('vartype') 123 | def xor_gate(variables, vartype=dimod.BINARY, name='XOR'): 124 | """XOR gate. 125 | 126 | Args: 127 | variables (list): Variable labels for the and gate as `[in1, in2, out]`, 128 | where `in1, in2` are inputs and `out` the gate's output. 129 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 130 | input values: 131 | 132 | * Vartype.SPIN, 'SPIN', {-1, 1} 133 | * Vartype.BINARY, 'BINARY', {0, 1} 134 | name (str, optional, default='XOR'): Name for the constraint. 135 | 136 | Returns: 137 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 138 | assigned values that match the valid states of an XOR gate. 139 | 140 | Examples: 141 | >>> import dwavebinarycsp.factories.constraint.gates as gates 142 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 143 | >>> csp.add_constraint(gates.xor_gate(['x', 'y', 'z'], name='XOR1')) 144 | >>> csp.check({'x': 1, 'y': 1, 'z': 1}) 145 | False 146 | """ 147 | 148 | variables = tuple(variables) 149 | if vartype is dimod.BINARY: 150 | configs = frozenset([(0, 0, 0), 151 | (0, 1, 1), 152 | (1, 0, 1), 153 | (1, 1, 0)]) 154 | 155 | def func(in1, in2, out): return (in1 != in2) == out 156 | 157 | else: 158 | # SPIN, vartype is checked by the decorator 159 | configs = frozenset([(-1, -1, -1), 160 | (-1, +1, +1), 161 | (+1, -1, +1), 162 | (+1, +1, -1)]) 163 | 164 | def func(in1, in2, out): return ((in1 > 0) != (in2 > 0)) == (out > 0) 165 | 166 | return Constraint(func, configs, variables, vartype=vartype, name=name) 167 | 168 | 169 | @dimod.decorators.vartype_argument('vartype') 170 | def halfadder_gate(variables, vartype=dimod.BINARY, name='HALF_ADDER'): 171 | """Half adder. 172 | 173 | Args: 174 | variables (list): Variable labels for the and gate as `[in1, in2, sum, carry]`, 175 | where `in1, in2` are inputs to be added and `sum` and 'carry' the resultant 176 | outputs. 177 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 178 | input values: 179 | 180 | * Vartype.SPIN, 'SPIN', {-1, 1} 181 | * Vartype.BINARY, 'BINARY', {0, 1} 182 | name (str, optional, default='HALF_ADDER'): Name for the constraint. 183 | 184 | Returns: 185 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 186 | assigned values that match the valid states of a Boolean half adder. 187 | 188 | Examples: 189 | >>> import dwavebinarycsp.factories.constraint.gates as gates 190 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 191 | >>> csp.add_constraint(gates.halfadder_gate(['a', 'b', 'total', 'carry'], name='HA1')) 192 | >>> csp.check({'a': 1, 'b': 1, 'total': 0, 'carry': 1}) 193 | True 194 | 195 | """ 196 | 197 | variables = tuple(variables) 198 | 199 | if vartype is dimod.BINARY: 200 | configs = frozenset([(0, 0, 0, 0), 201 | (0, 1, 1, 0), 202 | (1, 0, 1, 0), 203 | (1, 1, 0, 1)]) 204 | 205 | else: 206 | # SPIN, vartype is checked by the decorator 207 | configs = frozenset([(-1, -1, -1, -1), 208 | (-1, +1, +1, -1), 209 | (+1, -1, +1, -1), 210 | (+1, +1, -1, +1)]) 211 | 212 | def func(augend, addend, sum_, carry): 213 | total = (augend > 0) + (addend > 0) 214 | if total == 0: 215 | return (sum_ <= 0) and (carry <= 0) 216 | elif total == 1: 217 | return (sum_ > 0) and (carry <= 0) 218 | elif total == 2: 219 | return (sum_ <= 0) and (carry > 0) 220 | else: 221 | raise ValueError("func recieved unexpected values") 222 | 223 | return Constraint(func, configs, variables, vartype=vartype, name=name) 224 | 225 | 226 | @dimod.decorators.vartype_argument('vartype') 227 | def fulladder_gate(variables, vartype=dimod.BINARY, name='FULL_ADDER'): 228 | """Full adder. 229 | 230 | Args: 231 | variables (list): Variable labels for the and gate as `[in1, in2, in3, sum, carry]`, 232 | where `in1, in2, in3` are inputs to be added and `sum` and 'carry' the resultant 233 | outputs. 234 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 235 | input values: 236 | 237 | * Vartype.SPIN, 'SPIN', {-1, 1} 238 | * Vartype.BINARY, 'BINARY', {0, 1} 239 | name (str, optional, default='FULL_ADDER'): Name for the constraint. 240 | 241 | Returns: 242 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 243 | assigned values that match the valid states of a Boolean full adder. 244 | 245 | Examples: 246 | >>> import dwavebinarycsp.factories.constraint.gates as gates 247 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 248 | >>> csp.add_constraint(gates.fulladder_gate(['a', 'b', 'c_in', 'total', 'c_out'], name='FA1')) 249 | >>> csp.check({'a': 1, 'b': 0, 'c_in': 1, 'total': 0, 'c_out': 1}) 250 | True 251 | 252 | """ 253 | 254 | variables = tuple(variables) 255 | 256 | if vartype is dimod.BINARY: 257 | configs = frozenset([(0, 0, 0, 0, 0), 258 | (0, 0, 1, 1, 0), 259 | (0, 1, 0, 1, 0), 260 | (0, 1, 1, 0, 1), 261 | (1, 0, 0, 1, 0), 262 | (1, 0, 1, 0, 1), 263 | (1, 1, 0, 0, 1), 264 | (1, 1, 1, 1, 1)]) 265 | 266 | else: 267 | # SPIN, vartype is checked by the decorator 268 | configs = frozenset([(-1, -1, -1, -1, -1), 269 | (-1, -1, +1, +1, -1), 270 | (-1, +1, -1, +1, -1), 271 | (-1, +1, +1, -1, +1), 272 | (+1, -1, -1, +1, -1), 273 | (+1, -1, +1, -1, +1), 274 | (+1, +1, -1, -1, +1), 275 | (+1, +1, +1, +1, +1)]) 276 | 277 | def func(in1, in2, in3, sum_, carry): 278 | total = (in1 > 0) + (in2 > 0) + (in3 > 0) 279 | if total == 0: 280 | return (sum_ <= 0) and (carry <= 0) 281 | elif total == 1: 282 | return (sum_ > 0) and (carry <= 0) 283 | elif total == 2: 284 | return (sum_ <= 0) and (carry > 0) 285 | elif total == 3: 286 | return (sum_ > 0) and (carry > 0) 287 | else: 288 | raise ValueError("func recieved unexpected values") 289 | 290 | return Constraint(func, configs, variables, vartype=vartype, name=name) 291 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/constraint/sat.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 | import dimod 18 | 19 | from dwavebinarycsp.core.constraint import Constraint 20 | 21 | __all__ = ['sat2in4'] 22 | 23 | 24 | @dimod.decorators.vartype_argument('vartype') 25 | def sat2in4(pos, neg=tuple(), vartype=dimod.BINARY, name='2-in-4'): 26 | """Two-in-four (2-in-4) satisfiability. 27 | 28 | Args: 29 | pos (iterable): 30 | Variable labels, as an iterable, for non-negated variables of the constraint. 31 | Exactly four variables are specified by `pos` and `neg` together. 32 | neg (tuple): 33 | Variable labels, as an iterable, for negated variables of the constraint. 34 | Exactly four variables are specified by `pos` and `neg` together. 35 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 36 | input values: 37 | 38 | * Vartype.SPIN, 'SPIN', {-1, 1} 39 | * Vartype.BINARY, 'BINARY', {0, 1} 40 | name (str, optional, default='2-in-4'): Name for the constraint. 41 | 42 | Returns: 43 | Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are 44 | assigned values that satisfy a two-in-four satisfiability problem. 45 | 46 | Examples: 47 | >>> import dwavebinarycsp.factories.constraint.sat as sat 48 | >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 49 | >>> csp.add_constraint(sat.sat2in4(['w', 'x', 'y', 'z'], vartype='BINARY', name='sat1')) 50 | >>> csp.check({'w': 1, 'x': 1, 'y': 0, 'z': 0}) 51 | True 52 | 53 | """ 54 | pos = tuple(pos) 55 | neg = tuple(neg) 56 | 57 | variables = pos + neg 58 | 59 | if len(variables) != 4: 60 | raise ValueError("") 61 | 62 | if neg and (len(neg) < 4): 63 | # because 2-in-4 sat is symmetric, all negated is the same as none negated 64 | 65 | const = sat2in4(pos=variables, vartype=vartype) # make one that has no negations 66 | for v in neg: 67 | const.flip_variable(v) 68 | const.name = name # overwrite the name directly 69 | 70 | return const 71 | 72 | # we can just construct them directly for speed 73 | if vartype is dimod.BINARY: 74 | configurations = frozenset([(0, 0, 1, 1), 75 | (0, 1, 0, 1), 76 | (1, 0, 0, 1), 77 | (0, 1, 1, 0), 78 | (1, 0, 1, 0), 79 | (1, 1, 0, 0)]) 80 | else: 81 | # SPIN, vartype is checked by the decorator 82 | configurations = frozenset([(-1, -1, +1, +1), 83 | (-1, +1, -1, +1), 84 | (+1, -1, -1, +1), 85 | (-1, +1, +1, -1), 86 | (+1, -1, +1, -1), 87 | (+1, +1, -1, -1)]) 88 | 89 | def func(a, b, c, d): 90 | if a == b: 91 | return (b != c) and (c == d) 92 | elif a == c: 93 | # a != b 94 | return b == d 95 | else: 96 | # a != b, a != c => b == c 97 | return a == d 98 | 99 | return Constraint(func, configurations, variables, vartype=vartype, name=name) 100 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/csp/__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 dwavebinarycsp.factories.csp.circuits import * 16 | from dwavebinarycsp.factories.csp.sat import * 17 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/csp/circuits.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2018 D-Wave Systems Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from collections import defaultdict 17 | 18 | import dimod 19 | 20 | from dwavebinarycsp.core.csp import ConstraintSatisfactionProblem 21 | from dwavebinarycsp.factories.constraint.gates import and_gate, halfadder_gate, fulladder_gate 22 | 23 | __all__ = ['multiplication_circuit'] 24 | 25 | 26 | def multiplication_circuit(nbit, vartype=dimod.BINARY): 27 | """Multiplication circuit constraint satisfaction problem. 28 | 29 | A constraint satisfaction problem that represents the binary multiplication :math:`ab=p`, 30 | where the multiplicands are binary variables of length `nbit`; for example, 31 | :math:`2^ma_{nbit} + ... + 4a_2 + 2a_1 + a0`. 32 | 33 | The square below shows a graphic representation of the circuit:: 34 | 35 | ________________________________________________________________________________ 36 | | and20 and10 and00 | 37 | | | | | | 38 | | and21 add11──and11 add01──and01 | | 39 | | |┌───────────┘|┌───────────┘| | | 40 | | and22 add12──and12 add02──and02 | | | 41 | | |┌───────────┘|┌───────────┘| | | | 42 | | add13─────────add03 | | | | 43 | | ┌───────────┘| | | | | | 44 | | p5 p4 p3 p2 p1 p0 | 45 | -------------------------------------------------------------------------------- 46 | 47 | Args: 48 | nbit (int): Number of bits in the multiplicands. 49 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 50 | input values: 51 | 52 | * Vartype.SPIN, 'SPIN', {-1, 1} 53 | * Vartype.BINARY, 'BINARY', {0, 1} 54 | 55 | Returns: 56 | CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when variables 57 | :math:`a,b,p` are assigned values that correctly solve binary multiplication :math:`ab=p`. 58 | 59 | Examples: 60 | This example creates a multiplication circuit CSP that multiplies two 3-bit numbers, 61 | which is then formulated as a binary quadratic model (BQM). It fixes the multiplacands 62 | as :math:`a=5, b=3` (:math:`101` and :math:`011`) and uses a simulated annealing sampler 63 | to find the product, :math:`p=15` (:math:`001111`). 64 | 65 | >>> from dwavebinarycsp.factories.csp.circuits import multiplication_circuit 66 | >>> import dwave.samplers 67 | >>> csp = multiplication_circuit(3) 68 | >>> bqm = dwavebinarycsp.stitch(csp) 69 | >>> bqm.fix_variable('a0', 1); bqm.fix_variable('a1', 0); bqm.fix_variable('a2', 1) 70 | >>> bqm.fix_variable('b0', 1); bqm.fix_variable('b1', 1); bqm.fix_variable('b2', 0) 71 | >>> sampler = dwave.samplers.SimulatedAnnealingSampler() 72 | >>> response = sampler.sample(bqm) 73 | >>> p = next(response.samples(n=1, sorted_by='energy')) 74 | >>> print(p['p5'], p['p4'], p['p3'], p['p2'], p['p1'], p['p0']) # doctest: +SKIP 75 | 0 0 1 1 1 1 76 | 77 | """ 78 | 79 | if nbit < 1: 80 | raise ValueError("num_multiplier_bits, num_multiplicand_bits must be positive integers") 81 | 82 | num_multiplier_bits = num_multiplicand_bits = nbit 83 | 84 | # also checks the vartype argument 85 | csp = ConstraintSatisfactionProblem(vartype) 86 | 87 | # throughout, we will use the following convention: 88 | # i to refer to the bits of the multiplier 89 | # j to refer to the bits of the multiplicand 90 | # k to refer to the bits of the product 91 | 92 | # create the variables corresponding to the input and output wires for the circuit 93 | a = {i: 'a%d' % i for i in range(nbit)} 94 | b = {j: 'b%d' % j for j in range(nbit)} 95 | p = {k: 'p%d' % k for k in range(nbit + nbit)} 96 | 97 | # we will want to store the internal variables somewhere 98 | AND = defaultdict(dict) # the output of the AND gate associated with ai, bj is stored in AND[i][j] 99 | SUM = defaultdict(dict) # the sum of the ADDER gate associated with ai, bj is stored in SUM[i][j] 100 | CARRY = defaultdict(dict) # the carry of the ADDER gate associated with ai, bj is stored in CARRY[i][j] 101 | 102 | # we follow a shift adder 103 | for i in range(num_multiplier_bits): 104 | for j in range(num_multiplicand_bits): 105 | 106 | ai = a[i] 107 | bj = b[j] 108 | 109 | if i == 0 and j == 0: 110 | # in this case there are no inputs from lower bits, so our only input is the AND 111 | # gate. And since we only have one bit to add, we don't need an adder, no have a 112 | # carry out 113 | andij = AND[i][j] = p[0] 114 | 115 | gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) 116 | csp.add_constraint(gate) 117 | 118 | continue 119 | 120 | # we always need an AND gate 121 | andij = AND[i][j] = 'and%s,%s' % (i, j) 122 | 123 | gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) 124 | csp.add_constraint(gate) 125 | 126 | # the number of inputs will determine the type of adder 127 | inputs = [andij] 128 | 129 | # determine if there is a carry in 130 | if i - 1 in CARRY and j in CARRY[i - 1]: 131 | inputs.append(CARRY[i - 1][j]) 132 | 133 | # determine if there is a sum in 134 | if i - 1 in SUM and j + 1 in SUM[i - 1]: 135 | inputs.append(SUM[i - 1][j + 1]) 136 | 137 | # ok, add create adders if necessary 138 | if len(inputs) == 1: 139 | # we don't need an adder and we don't have a carry 140 | SUM[i][j] = andij 141 | elif len(inputs) == 2: 142 | # we need a HALFADDER so we have a sum and a carry 143 | 144 | if j == 0: 145 | sumij = SUM[i][j] = p[i] 146 | else: 147 | sumij = SUM[i][j] = 'sum%d,%d' % (i, j) 148 | 149 | carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) 150 | 151 | name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumij, carryij) 152 | gate = halfadder_gate([inputs[0], inputs[1], sumij, carryij], vartype=vartype, name=name) 153 | csp.add_constraint(gate) 154 | else: 155 | assert len(inputs) == 3, 'unexpected number of inputs' 156 | 157 | # we need a FULLADDER so we have a sum and a carry 158 | 159 | if j == 0: 160 | sumij = SUM[i][j] = p[i] 161 | else: 162 | sumij = SUM[i][j] = 'sum%d,%d' % (i, j) 163 | 164 | carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) 165 | 166 | name = 'FULLADDER(%s, %s, %s) = %s, %s' % (inputs[0], inputs[1], inputs[2], sumij, carryij) 167 | gate = fulladder_gate([inputs[0], inputs[1], inputs[2], sumij, carryij], vartype=vartype, name=name) 168 | csp.add_constraint(gate) 169 | 170 | # now we have a final row of full adders 171 | for col in range(nbit - 1): 172 | inputs = [CARRY[nbit - 1][col], SUM[nbit - 1][col + 1]] 173 | 174 | if col == 0: 175 | sumout = p[nbit + col] 176 | carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) 177 | 178 | name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumout, carryout) 179 | gate = halfadder_gate([inputs[0], inputs[1], sumout, carryout], vartype=vartype, name=name) 180 | csp.add_constraint(gate) 181 | 182 | continue 183 | 184 | inputs.append(CARRY[nbit][col - 1]) 185 | 186 | sumout = p[nbit + col] 187 | if col < nbit - 2: 188 | carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) 189 | else: 190 | carryout = p[2 * nbit - 1] 191 | 192 | name = 'FULLADDER(%s, %s, %s) = %s, %s' % (inputs[0], inputs[1], inputs[2], sumout, carryout) 193 | gate = fulladder_gate([inputs[0], inputs[1], inputs[2], sumout, carryout], vartype=vartype, name=name) 194 | csp.add_constraint(gate) 195 | 196 | return csp 197 | -------------------------------------------------------------------------------- /dwavebinarycsp/factories/csp/sat.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 functools import reduce 16 | from math import factorial 17 | from operator import mul 18 | from random import choice, sample, random 19 | 20 | import dimod 21 | 22 | from dwavebinarycsp.core.csp import ConstraintSatisfactionProblem 23 | from dwavebinarycsp.factories.constraint.sat import sat2in4 24 | from dwavebinarycsp.factories.constraint.gates import xor_gate 25 | 26 | __all__ = ['random_2in4sat', 27 | 'random_xorsat'] 28 | 29 | 30 | def random_2in4sat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): 31 | """Random two-in-four (2-in-4) constraint satisfaction problem. 32 | 33 | Args: 34 | num_variables (integer): Number of variables (at least four). 35 | num_clauses (integer): Number of constraints that together constitute the 36 | constraint satisfaction problem. 37 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 38 | input values: 39 | 40 | * Vartype.SPIN, 'SPIN', {-1, 1} 41 | * Vartype.BINARY, 'BINARY', {0, 1} 42 | satisfiable (bool, optional, default=True): True if the CSP can be satisfied. 43 | 44 | Returns: 45 | CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when its variables 46 | are assigned values that satisfy a two-in-four satisfiability problem. 47 | 48 | Examples: 49 | This example creates a CSP with 6 variables and two random constraints and checks 50 | whether a particular assignment of variables satisifies it. 51 | 52 | >>> import dwavebinarycsp.factories as sat 53 | >>> csp = sat.random_2in4sat(6, 2) 54 | >>> csp.constraints # doctest: +SKIP 55 | [Constraint.from_configurations(frozenset({(1, 0, 1, 0), (1, 0, 0, 1), (1, 1, 1, 1), (0, 1, 1, 0), (0, 0, 0, 0), 56 | (0, 1, 0, 1)}), (2, 4, 0, 1), Vartype.BINARY, name='2-in-4'), 57 | Constraint.from_configurations(frozenset({(1, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0), (0, 0, 0, 1), 58 | (0, 1, 0, 0), (0, 0, 1, 0)}), (1, 2, 4, 5), Vartype.BINARY, name='2-in-4')] 59 | >>> csp.check({0: 1, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}) # doctest: +SKIP 60 | True 61 | 62 | 63 | """ 64 | 65 | if num_variables < 4: 66 | raise ValueError("a 2in4 problem needs at least 4 variables") 67 | if num_clauses > 16 * _nchoosek(num_variables, 4): # 16 different negation patterns 68 | raise ValueError("too many clauses") 69 | 70 | # also checks the vartype argument 71 | csp = ConstraintSatisfactionProblem(vartype) 72 | 73 | variables = list(range(num_variables)) 74 | 75 | constraints = set() 76 | 77 | if satisfiable: 78 | values = tuple(vartype.value) 79 | planted_solution = {v: choice(values) for v in variables} 80 | 81 | configurations = [(0, 0, 1, 1), (0, 1, 0, 1), (1, 0, 0, 1), 82 | (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 0)] 83 | 84 | while len(constraints) < num_clauses: 85 | # sort the variables because constraints are hashed on configurations/variables 86 | # because 2-in-4 sat is symmetric, we would not get a hash conflict for different 87 | # variable orders 88 | constraint_variables = sorted(sample(variables, 4)) 89 | 90 | # pick (uniformly) a configuration and determine which variables we need to negate to 91 | # match the chosen configuration 92 | config = choice(configurations) 93 | pos = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] == (planted_solution[v] > 0)) 94 | neg = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] != (planted_solution[v] > 0)) 95 | 96 | const = sat2in4(pos=pos, neg=neg, vartype=vartype) 97 | 98 | assert const.check(planted_solution) 99 | 100 | constraints.add(const) 101 | else: 102 | while len(constraints) < num_clauses: 103 | # sort the variables because constraints are hashed on configurations/variables 104 | # because 2-in-4 sat is symmetric, we would not get a hash conflict for different 105 | # variable orders 106 | constraint_variables = sorted(sample(variables, 4)) 107 | 108 | # randomly determine negations 109 | pos = tuple(v for v in constraint_variables if random() > .5) 110 | neg = tuple(v for v in constraint_variables if v not in pos) 111 | 112 | const = sat2in4(pos=pos, neg=neg, vartype=vartype) 113 | 114 | constraints.add(const) 115 | 116 | for const in constraints: 117 | csp.add_constraint(const) 118 | 119 | # in case any variables didn't make it in 120 | for v in variables: 121 | csp.add_variable(v) 122 | 123 | return csp 124 | 125 | 126 | def random_xorsat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): 127 | """Random XOR constraint satisfaction problem. 128 | 129 | Args: 130 | num_variables (integer): Number of variables (at least three). 131 | num_clauses (integer): Number of constraints that together constitute the 132 | constraint satisfaction problem. 133 | vartype (Vartype, optional, default='BINARY'): Variable type. Accepted 134 | input values: 135 | 136 | * Vartype.SPIN, 'SPIN', {-1, 1} 137 | * Vartype.BINARY, 'BINARY', {0, 1} 138 | satisfiable (bool, optional, default=True): True if the CSP can be satisfied. 139 | 140 | Returns: 141 | CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when its variables 142 | are assigned values that satisfy a XOR satisfiability problem. 143 | 144 | Examples: 145 | This example creates a CSP with 5 variables and two random constraints and checks 146 | whether a particular assignment of variables satisifies it. 147 | 148 | >>> import dwavebinarycsp.factories as sat 149 | >>> csp = sat.random_xorsat(5, 2) 150 | >>> csp.constraints # doctest: +SKIP 151 | [Constraint.from_configurations(frozenset({(1, 0, 0), (1, 1, 1), (0, 1, 0), (0, 0, 1)}), (4, 3, 0), 152 | Vartype.BINARY, name='XOR (0 flipped)'), 153 | Constraint.from_configurations(frozenset({(1, 1, 0), (0, 1, 1), (0, 0, 0), (1, 0, 1)}), (2, 0, 4), 154 | Vartype.BINARY, name='XOR (2 flipped) (0 flipped)')] 155 | >>> csp.check({0: 1, 1: 0, 2: 0, 3: 1, 4: 1}) # doctest: +SKIP 156 | True 157 | 158 | """ 159 | if num_variables < 3: 160 | raise ValueError("a xor problem needs at least 3 variables") 161 | if num_clauses > 8 * _nchoosek(num_variables, 3): # 8 different negation patterns 162 | raise ValueError("too many clauses") 163 | 164 | # also checks the vartype argument 165 | csp = ConstraintSatisfactionProblem(vartype) 166 | 167 | variables = list(range(num_variables)) 168 | 169 | constraints = set() 170 | 171 | if satisfiable: 172 | values = tuple(vartype.value) 173 | planted_solution = {v: choice(values) for v in variables} 174 | 175 | configurations = [(0, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0)] 176 | 177 | while len(constraints) < num_clauses: 178 | # because constraints are hashed on configurations/variables, and because the inputs 179 | # to xor can be swapped without loss of generality, we can order them 180 | x, y, z = sample(variables, 3) 181 | if y > x: 182 | x, y = y, x 183 | 184 | # get the constraint 185 | const = xor_gate([x, y, z], vartype=vartype) 186 | 187 | # pick (uniformly) a configuration and determine which variables we need to negate to 188 | # match the chosen configuration 189 | config = choice(configurations) 190 | 191 | for idx, v in enumerate(const.variables): 192 | if config[idx] != (planted_solution[v] > 0): 193 | const.flip_variable(v) 194 | 195 | assert const.check(planted_solution) 196 | 197 | constraints.add(const) 198 | else: 199 | while len(constraints) < num_clauses: 200 | # because constraints are hashed on configurations/variables, and because the inputs 201 | # to xor can be swapped without loss of generality, we can order them 202 | x, y, z = sample(variables, 3) 203 | if y > x: 204 | x, y = y, x 205 | 206 | # get the constraint 207 | const = xor_gate([x, y, z], vartype=vartype) 208 | 209 | # randomly flip each variable in the constraint 210 | for idx, v in enumerate(const.variables): 211 | if random() > .5: 212 | const.flip_variable(v) 213 | 214 | assert const.check(planted_solution) 215 | 216 | constraints.add(const) 217 | 218 | for const in constraints: 219 | csp.add_constraint(const) 220 | 221 | # in case any variables didn't make it in 222 | for v in variables: 223 | csp.add_variable(v) 224 | 225 | return csp 226 | 227 | 228 | def _nchoosek(n, k): 229 | return reduce(mul, range(n, n - k, -1), 1) // factorial(k) 230 | -------------------------------------------------------------------------------- /dwavebinarycsp/io/__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 dwavebinarycsp.io.cnf 16 | -------------------------------------------------------------------------------- /dwavebinarycsp/io/cnf.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 | The DIMACS format is used to encode boolean satisfiability problems in conjunctive normal form. 17 | """ 18 | import re 19 | 20 | import dimod 21 | 22 | from dwavebinarycsp import ConstraintSatisfactionProblem 23 | 24 | _PROBLEM_REGEX = r'^p cnf (\d+) (\d+)' 25 | _CLAUSE_REGEX = r'^-?[0-9]\d*(?:\W-?[1-9]\d*)*\W0$' 26 | 27 | 28 | def load_cnf(fp): 29 | """Load a constraint satisfaction problem from a .cnf file. 30 | 31 | Args: 32 | fp (file, optional): 33 | `.write()`-supporting `file object`_ DIMACS CNF formatted_ file. 34 | 35 | Returns: 36 | :obj:`.ConstraintSatisfactionProblem` a binary-valued SAT problem. 37 | 38 | Examples: 39 | 40 | >>> import dwavebinarycsp as dbcsp 41 | ... 42 | >>> with open('test.cnf', 'r') as fp: # doctest: +SKIP 43 | ... csp = dbcsp.cnf.load_cnf(fp) 44 | 45 | .. _file object: https://docs.python.org/3/glossary.html#term-file-object 46 | 47 | .. _formatted: http://www.satcompetition.org 48 | 49 | 50 | """ 51 | 52 | fp = iter(fp) # handle lists/tuples/etc 53 | 54 | csp = ConstraintSatisfactionProblem(dimod.BINARY) 55 | 56 | # first look for the problem 57 | num_clauses = num_variables = 0 58 | problem_pattern = re.compile(_PROBLEM_REGEX) 59 | for line in fp: 60 | matches = problem_pattern.findall(line) 61 | if matches: 62 | if len(matches) > 1: 63 | raise ValueError 64 | nv, nc = matches[0] 65 | num_variables, num_clauses = int(nv), int(nc) 66 | break 67 | 68 | # now parse the clauses, picking up where we left off looking for the header 69 | clause_pattern = re.compile(_CLAUSE_REGEX) 70 | for line in fp: 71 | if clause_pattern.match(line) is not None: 72 | clause = [int(v) for v in line.split(' ')[:-1]] # line ends with a trailing 0 73 | 74 | # -1 is the notation for NOT(1) 75 | variables = [abs(v) for v in clause] 76 | 77 | f = _cnf_or(clause) 78 | 79 | csp.add_constraint(f, variables) 80 | 81 | for v in range(1, num_variables+1): 82 | csp.add_variable(v) 83 | for v in csp.variables: 84 | if v > num_variables: 85 | msg = ("given .cnf file's header defines variables [1, {}] and {} clauses " 86 | "but constraints a reference to variable {}").format(num_variables, num_clauses, v) 87 | raise ValueError(msg) 88 | 89 | if len(csp) != num_clauses: 90 | msg = ("given .cnf file's header defines {} " 91 | "clauses but the file contains {}").format(num_clauses, len(csp)) 92 | raise ValueError(msg) 93 | 94 | return csp 95 | 96 | 97 | def _cnf_or(clause): 98 | def f(*args): 99 | return any(v == int(c > 0) for v, c in zip(args, clause)) 100 | return f 101 | -------------------------------------------------------------------------------- /dwavebinarycsp/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 | from dwavebinarycsp import __version__ 16 | 17 | # keep description, author and author email up to date with setup.cfg 18 | __author__ = 'D-Wave Systems Inc.' 19 | __authoremail__ = 'tools@dwavesys.com' 20 | __description__ = 'Solves constraints satisfaction problems with binary quadratic model samplers' 21 | -------------------------------------------------------------------------------- /dwavebinarycsp/reduction.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 | Constraints can sometimes be reduced into several smaller constraints. 17 | """ 18 | 19 | import itertools 20 | 21 | from collections import defaultdict 22 | 23 | 24 | def irreducible_components(constraint): 25 | """Determine the sets of variables that are irreducible. 26 | 27 | Let V(C) denote the variables of constraint C. For a configuration x, let x|A denote the 28 | restriction of the configuration to the variables of A. Constraint C is reducible if there 29 | is a partition of V(C) into nonempty subsets A and B, and two constraints C_A and C_B, with 30 | V(C_A) = A and C_B V(C_B) = B, such that a configuration x is feasible in C if and only if x|A 31 | is feasible in C_A and x|B is feasible in C_B. A constraint is irreducible if it is not 32 | reducible. 33 | 34 | Args: 35 | constraint (:obj:`.Constraint`): 36 | Constraint to attempt to reduce. 37 | 38 | Returns: 39 | list[tuple]: List of tuples in which each tuple is a set of variables that is irreducible. 40 | 41 | Examples: 42 | This example reduces a constraint, created by specifying its valid configurations, to two 43 | constraints. The original constraint, that valid configurations for a,b,c are 0,0,1 and 44 | 1,1,1, can be represented by two reduced constraints, for example, (c=1) & (a=b). For 45 | comparison, an attempt to reduce a constraint representing an AND gate fails to find a 46 | valid reduction. 47 | 48 | >>> const = dwavebinarycsp.Constraint.from_configurations([(0, 0, 1), (1, 1, 1)], 49 | ... ['a', 'b', 'c'], dwavebinarycsp.BINARY) 50 | >>> dwavebinarycsp.irreducible_components(const) 51 | [('c',), ('a', 'b')] 52 | >>> const_and = dwavebinarycsp.Constraint.from_configurations([(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 1)], 53 | ... ['a', 'b', 'c'], dwavebinarycsp.BINARY) 54 | >>> dwavebinarycsp.irreducible_components(const_and) 55 | [('a', 'b', 'c')] 56 | 57 | """ 58 | 59 | # developer note: we could calculate the correlation on the variables and thereby reduce the 60 | # number of subsets we need to check in the next step. 61 | 62 | return _irreducible_components(constraint.configurations, constraint.variables) 63 | 64 | 65 | def _irreducible_components(configurations, variables): 66 | 67 | num_variables = len(variables) 68 | 69 | # if len(configurations) <= 1: 70 | # # if there is only one configuration then it is irreducible 71 | # return [variables] 72 | 73 | # for every not-trivial subset (and it's complement), check if the contraint 74 | # is composed of the product of complement and subset 75 | # subset and complement are defined over the indices in the configurations for simplicity 76 | for i in range(1, num_variables // 2 + 1): 77 | for subset in itertools.combinations(range(num_variables), i): 78 | complement = tuple(v for v in range(num_variables) if v not in subset) 79 | 80 | # by using sets we only keep the unique configurations 81 | subset_configurations = {tuple(config[v] for v in subset) for config in configurations} 82 | complement_configurations = {tuple(config[v] for v in complement) for config in configurations} 83 | 84 | if len(configurations) == len(subset_configurations) * len(complement_configurations): 85 | 86 | subset_variables = tuple(variables[v] for v in subset) 87 | complement_variables = tuple(variables[v] for v in complement) 88 | 89 | subset_components = _irreducible_components(subset_configurations, subset_variables) 90 | complement_components = _irreducible_components(complement_configurations, complement_variables) 91 | 92 | return subset_components + complement_components 93 | 94 | return [variables] 95 | -------------------------------------------------------------------------------- /dwavebinarycsp/testing.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 dimod import Vartype 18 | 19 | from dwavebinarycsp.core import Constraint 20 | 21 | 22 | def assert_consistent_constraint(const): 23 | assert isinstance(const, Constraint) 24 | 25 | assert hasattr(const, 'configurations') 26 | assert isinstance(const.configurations, frozenset) 27 | 28 | assert hasattr(const, 'func') 29 | assert callable(const.func) 30 | 31 | assert hasattr(const, 'variables') 32 | assert isinstance(const.variables, tuple) 33 | 34 | assert len(const) == len(const.variables) 35 | 36 | assert hasattr(const, 'vartype') 37 | assert isinstance(const.vartype, Vartype) 38 | 39 | for config in const.configurations: 40 | assert isinstance(config, tuple) 41 | assert len(config) == len(const.variables) 42 | 43 | msg = "config {} does not match constraint vartype '{}'".format(config, const.vartype) 44 | assert set(config).issubset(const.vartype.value), msg 45 | 46 | assert const.func(*config), 'Constraint.func does not evaluate True for {}'.format(config) 47 | assert const.check(dict(zip(const.variables, config))) 48 | 49 | for config in itertools.product(const.vartype.value, repeat=len(const)): 50 | if config in const.configurations: 51 | assert const.func(*config) 52 | assert const.check(dict(zip(const.variables, config))) 53 | else: 54 | assert not const.func(*config) 55 | assert not const.check(dict(zip(const.variables, config))) 56 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=46.4.0", 4 | "wheel", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.coverage.run] 9 | source = [ 10 | "dwavebinarycsp/", 11 | ] 12 | 13 | [tool.coverage.report] 14 | exclude_lines = [ 15 | "pragma: no cover", 16 | "RuntimeError", 17 | "pass", 18 | ] 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | penaltymodel==1.2.0 2 | networkx==3.2 # newest that supports 3.9 3 | dimod==0.12.17 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | classifiers = 3 | License :: OSI Approved :: Apache Software License 4 | Operating System :: OS Independent 5 | Programming Language :: Python :: 3 :: Only 6 | Programming Language :: Python :: 3.9 7 | Programming Language :: Python :: 3.10 8 | Programming Language :: Python :: 3.11 9 | Programming Language :: Python :: 3.12 10 | Programming Language :: Python :: 3.13 11 | license = Apache 2.0 12 | long_description = file: README.rst 13 | long_description_content_type = text/x-rst 14 | name = dwavebinarycsp 15 | project_urls = 16 | Changes = https://github.com/dwavesystems/dwavebinarycsp/releases 17 | Documentation = https://docs.ocean.dwavesys.com 18 | Souce Code = https://github.com/dwavesystems/dwavebinarycsp 19 | url = https://github.com/dwavesystems/dwavebinarycsp 20 | version = attr: dwavebinarycsp.__version__ 21 | 22 | # keep description, author and author email up to date with dwavebinarycsp.package_info 23 | author = D-Wave Systems Inc. 24 | author_email = tools@dwavesys.com 25 | description = Solves constraints satisfaction problems with binary quadratic model samplers 26 | 27 | [options] 28 | install_requires = 29 | penaltymodel>=1.0.0 30 | networkx>=2.4 # lowest version supported by penaltymodel 31 | dimod>=0.10.9 32 | packages = 33 | dwavebinarycsp 34 | dwavebinarycsp.compilers 35 | dwavebinarycsp.core 36 | dwavebinarycsp.factories 37 | dwavebinarycsp.factories.constraint 38 | dwavebinarycsp.factories.csp 39 | dwavebinarycsp.io 40 | python_requires = >=3.9 41 | 42 | [options.extras_require] 43 | mip = 44 | maxgap = 45 | 46 | [pycodestyle] 47 | max-line-length = 100 48 | -------------------------------------------------------------------------------- /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 | from setuptools import setup 16 | 17 | setup() 18 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwavesystems/dwavebinarycsp/2885d236a12652fc9d9b730571847b6591a08f98/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/test0.cnf: -------------------------------------------------------------------------------- 1 | p cnf 17 192 2 | 1 3 5 12 7 -14 0 3 | 1 3 5 12 -7 14 0 4 | 1 3 5 12 -7 -14 0 5 | 1 3 5 -12 7 14 0 6 | 1 3 5 -12 7 -14 0 7 | 1 3 5 -12 -7 -14 0 8 | 1 3 -5 12 7 14 0 9 | 1 3 -5 12 7 -14 0 10 | 1 3 -5 12 -7 -14 0 11 | 1 3 -5 -12 7 14 0 12 | 1 3 -5 -12 -7 14 0 13 | 1 3 -5 -12 -7 -14 0 14 | 1 -3 5 12 7 -14 0 15 | 1 -3 5 12 -7 14 0 16 | 1 -3 5 12 -7 -14 0 17 | 1 -3 5 -12 7 14 0 18 | 1 -3 5 -12 7 -14 0 19 | 1 -3 5 -12 -7 -14 0 20 | 1 -3 -5 12 7 14 0 21 | 1 -3 -5 12 7 -14 0 22 | 1 -3 -5 12 -7 -14 0 23 | 1 -3 -5 -12 7 14 0 24 | 1 -3 -5 -12 -7 14 0 25 | 1 -3 -5 -12 -7 -14 0 26 | -1 3 5 12 7 -14 0 27 | -1 3 5 12 -7 14 0 28 | -1 3 5 12 -7 -14 0 29 | -1 3 5 -12 7 14 0 30 | -1 3 5 -12 7 -14 0 31 | -1 3 5 -12 -7 -14 0 32 | -1 3 -5 12 7 14 0 33 | -1 3 -5 12 7 -14 0 34 | -1 3 -5 12 -7 -14 0 35 | -1 3 -5 -12 7 14 0 36 | -1 3 -5 -12 -7 14 0 37 | -1 3 -5 -12 -7 -14 0 38 | -1 -3 5 12 7 14 0 39 | -1 -3 5 12 7 -14 0 40 | -1 -3 5 12 -7 -14 0 41 | -1 -3 5 -12 7 14 0 42 | -1 -3 5 -12 -7 14 0 43 | -1 -3 5 -12 -7 -14 0 44 | -1 -3 -5 12 7 14 0 45 | -1 -3 -5 12 -7 14 0 46 | -1 -3 -5 12 -7 -14 0 47 | -1 -3 -5 -12 7 14 0 48 | -1 -3 -5 -12 7 -14 0 49 | -1 -3 -5 -12 -7 14 0 50 | 2 3 6 13 8 -15 0 51 | 2 3 6 13 -8 15 0 52 | 2 3 6 13 -8 -15 0 53 | 2 3 6 -13 8 15 0 54 | 2 3 6 -13 8 -15 0 55 | 2 3 6 -13 -8 -15 0 56 | 2 3 -6 13 8 15 0 57 | 2 3 -6 13 8 -15 0 58 | 2 3 -6 13 -8 -15 0 59 | 2 3 -6 -13 8 15 0 60 | 2 3 -6 -13 -8 15 0 61 | 2 3 -6 -13 -8 -15 0 62 | 2 -3 6 13 8 -15 0 63 | 2 -3 6 13 -8 15 0 64 | 2 -3 6 13 -8 -15 0 65 | 2 -3 6 -13 8 15 0 66 | 2 -3 6 -13 8 -15 0 67 | 2 -3 6 -13 -8 -15 0 68 | 2 -3 -6 13 8 15 0 69 | 2 -3 -6 13 8 -15 0 70 | 2 -3 -6 13 -8 -15 0 71 | 2 -3 -6 -13 8 15 0 72 | 2 -3 -6 -13 -8 15 0 73 | 2 -3 -6 -13 -8 -15 0 74 | -2 3 6 13 8 -15 0 75 | -2 3 6 13 -8 15 0 76 | -2 3 6 13 -8 -15 0 77 | -2 3 6 -13 8 15 0 78 | -2 3 6 -13 8 -15 0 79 | -2 3 6 -13 -8 -15 0 80 | -2 3 -6 13 8 15 0 81 | -2 3 -6 13 8 -15 0 82 | -2 3 -6 13 -8 -15 0 83 | -2 3 -6 -13 8 15 0 84 | -2 3 -6 -13 -8 15 0 85 | -2 3 -6 -13 -8 -15 0 86 | -2 -3 6 13 8 15 0 87 | -2 -3 6 13 8 -15 0 88 | -2 -3 6 13 -8 -15 0 89 | -2 -3 6 -13 8 15 0 90 | -2 -3 6 -13 -8 15 0 91 | -2 -3 6 -13 -8 -15 0 92 | -2 -3 -6 13 8 15 0 93 | -2 -3 -6 13 -8 15 0 94 | -2 -3 -6 13 -8 -15 0 95 | -2 -3 -6 -13 8 15 0 96 | -2 -3 -6 -13 8 -15 0 97 | -2 -3 -6 -13 -8 15 0 98 | 1 4 8 14 10 -16 0 99 | 1 4 8 14 -10 16 0 100 | 1 4 8 14 -10 -16 0 101 | 1 4 8 -14 10 16 0 102 | 1 4 8 -14 10 -16 0 103 | 1 4 8 -14 -10 -16 0 104 | 1 4 -8 14 10 16 0 105 | 1 4 -8 14 10 -16 0 106 | 1 4 -8 14 -10 -16 0 107 | 1 4 -8 -14 10 16 0 108 | 1 4 -8 -14 -10 16 0 109 | 1 4 -8 -14 -10 -16 0 110 | 1 -4 8 14 10 -16 0 111 | 1 -4 8 14 -10 16 0 112 | 1 -4 8 14 -10 -16 0 113 | 1 -4 8 -14 10 16 0 114 | 1 -4 8 -14 10 -16 0 115 | 1 -4 8 -14 -10 -16 0 116 | 1 -4 -8 14 10 16 0 117 | 1 -4 -8 14 10 -16 0 118 | 1 -4 -8 14 -10 -16 0 119 | 1 -4 -8 -14 10 16 0 120 | 1 -4 -8 -14 -10 16 0 121 | 1 -4 -8 -14 -10 -16 0 122 | -1 4 8 14 10 -16 0 123 | -1 4 8 14 -10 16 0 124 | -1 4 8 14 -10 -16 0 125 | -1 4 8 -14 10 16 0 126 | -1 4 8 -14 10 -16 0 127 | -1 4 8 -14 -10 -16 0 128 | -1 4 -8 14 10 16 0 129 | -1 4 -8 14 10 -16 0 130 | -1 4 -8 14 -10 -16 0 131 | -1 4 -8 -14 10 16 0 132 | -1 4 -8 -14 -10 16 0 133 | -1 4 -8 -14 -10 -16 0 134 | -1 -4 8 14 10 16 0 135 | -1 -4 8 14 10 -16 0 136 | -1 -4 8 14 -10 -16 0 137 | -1 -4 8 -14 10 16 0 138 | -1 -4 8 -14 -10 16 0 139 | -1 -4 8 -14 -10 -16 0 140 | -1 -4 -8 14 10 16 0 141 | -1 -4 -8 14 -10 16 0 142 | -1 -4 -8 14 -10 -16 0 143 | -1 -4 -8 -14 10 16 0 144 | -1 -4 -8 -14 10 -16 0 145 | -1 -4 -8 -14 -10 16 0 146 | 2 4 9 15 11 -17 0 147 | 2 4 9 15 -11 17 0 148 | 2 4 9 15 -11 -17 0 149 | 2 4 9 -15 11 17 0 150 | 2 4 9 -15 11 -17 0 151 | 2 4 9 -15 -11 -17 0 152 | 2 4 -9 15 11 17 0 153 | 2 4 -9 15 11 -17 0 154 | 2 4 -9 15 -11 -17 0 155 | 2 4 -9 -15 11 17 0 156 | 2 4 -9 -15 -11 17 0 157 | 2 4 -9 -15 -11 -17 0 158 | 2 -4 9 15 11 -17 0 159 | 2 -4 9 15 -11 17 0 160 | 2 -4 9 15 -11 -17 0 161 | 2 -4 9 -15 11 17 0 162 | 2 -4 9 -15 11 -17 0 163 | 2 -4 9 -15 -11 -17 0 164 | 2 -4 -9 15 11 17 0 165 | 2 -4 -9 15 11 -17 0 166 | 2 -4 -9 15 -11 -17 0 167 | 2 -4 -9 -15 11 17 0 168 | 2 -4 -9 -15 -11 17 0 169 | 2 -4 -9 -15 -11 -17 0 170 | -2 4 9 15 11 -17 0 171 | -2 4 9 15 -11 17 0 172 | -2 4 9 15 -11 -17 0 173 | -2 4 9 -15 11 17 0 174 | -2 4 9 -15 11 -17 0 175 | -2 4 9 -15 -11 -17 0 176 | -2 4 -9 15 11 17 0 177 | -2 4 -9 15 11 -17 0 178 | -2 4 -9 15 -11 -17 0 179 | -2 4 -9 -15 11 17 0 180 | -2 4 -9 -15 -11 17 0 181 | -2 4 -9 -15 -11 -17 0 182 | -2 -4 9 15 11 17 0 183 | -2 -4 9 15 11 -17 0 184 | -2 -4 9 15 -11 -17 0 185 | -2 -4 9 -15 11 17 0 186 | -2 -4 9 -15 -11 17 0 187 | -2 -4 9 -15 -11 -17 0 188 | -2 -4 -9 15 11 17 0 189 | -2 -4 -9 15 -11 17 0 190 | -2 -4 -9 15 -11 -17 0 191 | -2 -4 -9 -15 11 17 0 192 | -2 -4 -9 -15 11 -17 0 193 | -2 -4 -9 -15 -11 17 0 194 | -------------------------------------------------------------------------------- /tests/data/test1.cnf: -------------------------------------------------------------------------------- 1 | c test1.cnf 2 | c 3 | p cnf 98 153 4 | 1 2 0 5 | 2 4 0 6 | 3 4 0 7 | 4 6 0 8 | 5 6 0 9 | 2 8 0 10 | 6 8 0 11 | 7 8 0 12 | 8 10 0 13 | 9 10 0 14 | 10 12 0 15 | 11 12 0 16 | 8 14 0 17 | 12 14 0 18 | 13 14 0 19 | 1 15 0 20 | 15 16 0 21 | 3 17 0 22 | 16 18 0 23 | 17 18 0 24 | 5 19 0 25 | 18 20 0 26 | 19 20 0 27 | 7 21 0 28 | 20 22 0 29 | 21 22 0 30 | 9 23 0 31 | 18 24 0 32 | 22 24 0 33 | 23 24 0 34 | 11 25 0 35 | 24 26 0 36 | 25 26 0 37 | 13 27 0 38 | 26 28 0 39 | 27 28 0 40 | 15 29 0 41 | 29 30 0 42 | 17 31 0 43 | 30 32 0 44 | 31 32 0 45 | 19 33 0 46 | 32 34 0 47 | 33 34 0 48 | 21 35 0 49 | 34 36 0 50 | 35 36 0 51 | 23 37 0 52 | 36 38 0 53 | 37 38 0 54 | 25 39 0 55 | 34 40 0 56 | 38 40 0 57 | 39 40 0 58 | 27 41 0 59 | 40 42 0 60 | 41 42 0 61 | 1 43 0 62 | 29 43 0 63 | 43 44 0 64 | 31 45 0 65 | 44 46 0 66 | 45 46 0 67 | 33 47 0 68 | 46 48 0 69 | 47 48 0 70 | 7 49 0 71 | 35 49 0 72 | 44 50 0 73 | 48 50 0 74 | 49 50 0 75 | 37 51 0 76 | 50 52 0 77 | 51 52 0 78 | 39 53 0 79 | 52 54 0 80 | 53 54 0 81 | 13 55 0 82 | 41 55 0 83 | 50 56 0 84 | 54 56 0 85 | 55 56 0 86 | 43 57 0 87 | 57 58 0 88 | 17 59 0 89 | 45 59 0 90 | 58 60 0 91 | 59 60 0 92 | 47 61 0 93 | 60 62 0 94 | 61 62 0 95 | 49 63 0 96 | 62 64 0 97 | 63 64 0 98 | 23 65 0 99 | 51 65 0 100 | 60 66 0 101 | 64 66 0 102 | 65 66 0 103 | 53 67 0 104 | 66 68 0 105 | 67 68 0 106 | 55 69 0 107 | 68 70 0 108 | 69 70 0 109 | 57 71 0 110 | 71 72 0 111 | 59 73 0 112 | 72 74 0 113 | 73 74 0 114 | 33 75 0 115 | 61 75 0 116 | 74 76 0 117 | 75 76 0 118 | 63 77 0 119 | 76 78 0 120 | 77 78 0 121 | 65 79 0 122 | 78 80 0 123 | 79 80 0 124 | 39 81 0 125 | 67 81 0 126 | 76 82 0 127 | 80 82 0 128 | 81 82 0 129 | 69 83 0 130 | 82 84 0 131 | 83 84 0 132 | 43 85 0 133 | 71 85 0 134 | 85 86 0 135 | 73 87 0 136 | 86 88 0 137 | 87 88 0 138 | 75 89 0 139 | 88 90 0 140 | 89 90 0 141 | 49 91 0 142 | 77 91 0 143 | 86 92 0 144 | 90 92 0 145 | 91 92 0 146 | 79 93 0 147 | 92 94 0 148 | 93 94 0 149 | 81 95 0 150 | 94 96 0 151 | 95 96 0 152 | 55 97 0 153 | 83 97 0 154 | 92 98 0 155 | 96 98 0 156 | 97 98 0 157 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | coverage[toml] 2 | codecov 3 | -------------------------------------------------------------------------------- /tests/test_constraint.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 operator 17 | import itertools 18 | 19 | import dwavebinarycsp 20 | import dwavebinarycsp.testing as dcspt 21 | 22 | 23 | class TestConstraint(unittest.TestCase): 24 | 25 | def test__len__(self): 26 | 27 | const = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 28 | dcspt.assert_consistent_constraint(const) 29 | self.assertEqual(len(const), 2) 30 | 31 | def test_from_func(self): 32 | const = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 33 | 34 | dcspt.assert_consistent_constraint(const) 35 | 36 | self.assertEqual(const.configurations, frozenset([(-1, -1), (1, 1)])) 37 | self.assertEqual(('a', 'b'), const.variables) 38 | 39 | def test_from_configurations(self): 40 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1), (1, 1)], ['a', 'b'], dwavebinarycsp.SPIN) 41 | 42 | dcspt.assert_consistent_constraint(const) 43 | 44 | self.assertEqual(const.configurations, frozenset([(-1, 1), (1, 1)])) 45 | self.assertEqual(const.variables, ('a', 'b')) 46 | 47 | def test_fix_variable(self): 48 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1), (1, 1)], ['a', 'b'], dwavebinarycsp.SPIN) 49 | 50 | const.fix_variable('a', -1) 51 | 52 | dcspt.assert_consistent_constraint(const) 53 | 54 | self.assertEqual(const.configurations, frozenset([(1,)])) 55 | self.assertEqual(const.variables, ('b',)) 56 | 57 | def test_fix_variable_unsat(self): 58 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1), (1, 1)], ['a', 'b'], dwavebinarycsp.SPIN) 59 | 60 | with self.assertRaises(dwavebinarycsp.exceptions.UnsatError): 61 | const.fix_variable('b', -1) 62 | 63 | def test__or__disjoint(self): 64 | eq_a_b = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 65 | ne_c_d = dwavebinarycsp.Constraint.from_func(operator.ne, ['c', 'd'], dwavebinarycsp.SPIN) 66 | 67 | const = eq_a_b | ne_c_d 68 | 69 | dcspt.assert_consistent_constraint(const) 70 | 71 | self.assertEqual(const.variables, ('a', 'b', 'c', 'd')) 72 | 73 | self.assertTrue(const.check({'a': 0, 'b': 0, 'c': 0, 'd': 0})) # only eq_a_b is satisfied 74 | self.assertFalse(const.check({'a': 1, 'b': 0, 'c': 0, 'd': 0})) # neither satisified 75 | 76 | def test__or__(self): 77 | eq_a_b = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 78 | ne_b_c = dwavebinarycsp.Constraint.from_func(operator.ne, ['c', 'b'], dwavebinarycsp.SPIN) 79 | 80 | const = eq_a_b | ne_b_c 81 | 82 | dcspt.assert_consistent_constraint(const) 83 | 84 | self.assertEqual(const.variables, ('a', 'b', 'c')) 85 | 86 | self.assertTrue(const.check({'a': 0, 'b': 0, 'c': 0})) # only eq_a_b is satisfied 87 | self.assertFalse(const.check({'a': 1, 'b': 0, 'c': 0})) # neither satisfied 88 | self.assertTrue(const.check({'a': 0, 'b': 1, 'c': 0})) # only ne_b_c is satisfied 89 | 90 | def test__and__disjoint(self): 91 | eq_a_b = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 92 | ne_c_d = dwavebinarycsp.Constraint.from_func(operator.ne, ['c', 'd'], dwavebinarycsp.SPIN) 93 | 94 | const = eq_a_b & ne_c_d 95 | 96 | dcspt.assert_consistent_constraint(const) 97 | 98 | self.assertEqual(const.configurations, frozenset([(-1, -1, -1, 1), (1, 1, -1, 1), 99 | (-1, -1, 1, -1), (1, 1, 1, -1)])) 100 | self.assertEqual(const.variables, ('a', 'b', 'c', 'd')) 101 | 102 | def test__and__(self): 103 | eq_a_b = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 104 | ne_b_c = dwavebinarycsp.Constraint.from_func(operator.ne, ['c', 'b'], dwavebinarycsp.SPIN) 105 | 106 | const = eq_a_b & ne_b_c 107 | 108 | dcspt.assert_consistent_constraint(const) 109 | 110 | self.assertEqual(const.configurations, frozenset([(-1, -1, 1), (1, 1, -1)])) 111 | self.assertEqual(const.variables, ('a', 'b', 'c')) 112 | 113 | def test_negate_variables_binary(self): 114 | const = dwavebinarycsp.Constraint.from_configurations([(0, 1), (1, 0)], ['a', 'b'], vartype=dwavebinarycsp.BINARY) 115 | 116 | const.flip_variable('a') 117 | 118 | dcspt.assert_consistent_constraint(const) 119 | 120 | self.assertEqual(const.configurations, frozenset([(1, 1), (0, 0)])) 121 | 122 | # 123 | 124 | const = dwavebinarycsp.Constraint.from_configurations([(0, 1), (1, 0)], ['a', 'b'], vartype=dwavebinarycsp.BINARY) 125 | 126 | const.flip_variable('b') 127 | 128 | dcspt.assert_consistent_constraint(const) 129 | 130 | self.assertEqual(const.configurations, frozenset([(1, 1), (0, 0)])) 131 | 132 | # 133 | 134 | const = dwavebinarycsp.Constraint.from_configurations([(0, 1, 1), (1, 0, 0)], 135 | ['a', 'b', 'c'], 136 | vartype=dwavebinarycsp.BINARY) 137 | 138 | const.flip_variable('b') 139 | 140 | dcspt.assert_consistent_constraint(const) 141 | 142 | self.assertEqual(const.configurations, frozenset([(1, 1, 0), (0, 0, 1)])) 143 | 144 | def test_negate_variables_spi(self): 145 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1), (1, -1)], ['a', 'b'], vartype=dwavebinarycsp.SPIN) 146 | 147 | const.flip_variable('a') 148 | 149 | dcspt.assert_consistent_constraint(const) 150 | 151 | self.assertEqual(const.configurations, frozenset([(1, 1), (-1, -1)])) 152 | 153 | # 154 | 155 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1), (1, -1)], ['a', 'b'], vartype=dwavebinarycsp.SPIN) 156 | 157 | const.flip_variable('b') 158 | 159 | dcspt.assert_consistent_constraint(const) 160 | 161 | self.assertEqual(const.configurations, frozenset([(1, 1), (-1, -1)])) 162 | 163 | # 164 | 165 | const = dwavebinarycsp.Constraint.from_configurations([(-1, 1, 1), (1, -1, -1)], 166 | ['a', 'b', 'c'], 167 | vartype=dwavebinarycsp.SPIN) 168 | 169 | const.flip_variable('b') 170 | 171 | dcspt.assert_consistent_constraint(const) 172 | 173 | self.assertEqual(const.configurations, frozenset([(1, 1, -1), (-1, -1, 1)])) 174 | 175 | def test_construction_typechecking(self): 176 | 177 | # not function 178 | with self.assertRaises(TypeError): 179 | dwavebinarycsp.Constraint(1, {}, [], dwavebinarycsp.BINARY) 180 | 181 | def f(): 182 | pass 183 | 184 | # mismatched lengths 185 | with self.assertRaises(ValueError): 186 | dwavebinarycsp.Constraint(f, {tuple(), (1,)}, [], dwavebinarycsp.BINARY) 187 | with self.assertRaises(ValueError): 188 | dwavebinarycsp.Constraint(f, {(0,), (1,)}, ['a', 'b'], dwavebinarycsp.BINARY) 189 | 190 | # bad vartype 191 | with self.assertRaises(ValueError): 192 | dwavebinarycsp.Constraint(f, {(-1,), (0,)}, ['a'], dwavebinarycsp.BINARY) 193 | 194 | def test_copy(self): 195 | const = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 196 | new_const = const.copy() 197 | 198 | self.assertEqual(const, new_const) 199 | self.assertIsNot(const, new_const) 200 | 201 | def test_projection_identity(self): 202 | const = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 203 | 204 | proj = const.projection(['a', 'b']) 205 | 206 | self.assertEqual(const, proj) 207 | self.assertIsNot(const, proj) 208 | 209 | def test_projection_unknown_variables(self): 210 | const = dwavebinarycsp.Constraint.from_func(operator.eq, ['a', 'b'], dwavebinarycsp.SPIN) 211 | 212 | with self.assertRaises(ValueError): 213 | proj = const.projection(['a', 'c']) 214 | 215 | def test_projection_reducible(self): 216 | const = dwavebinarycsp.Constraint.from_configurations([(0, 0), (0, 1)], ['a', 'b'], dwavebinarycsp.BINARY) 217 | 218 | a = dwavebinarycsp.Constraint.from_configurations([(0,)], ['a'], dwavebinarycsp.BINARY) 219 | b = dwavebinarycsp.Constraint.from_configurations([(0,), (1,)], ['b'], dwavebinarycsp.BINARY) 220 | 221 | self.assertEqual(const.projection(['b']), b) 222 | self.assertEqual(const.projection(['a']), a) 223 | -------------------------------------------------------------------------------- /tests/test_csp.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 operator 17 | 18 | import dwavebinarycsp 19 | 20 | 21 | class TestCSP(unittest.TestCase): 22 | def test_add_constraint_function(self): 23 | csp = dwavebinarycsp.CSP(dwavebinarycsp.BINARY) 24 | 25 | def f(a, b, c): return a * b == c 26 | 27 | csp.add_constraint(f, ['a', 'b', 'c']) 28 | 29 | self.assertTrue(csp.check({'a': 0, 'b': 0, 'c': 0})) 30 | self.assertFalse(csp.check({'a': 1, 'b': 0, 'c': 1})) 31 | 32 | def test_add_constraint_table_BINARY(self): 33 | csp = dwavebinarycsp.CSP(dwavebinarycsp.BINARY) 34 | 35 | neq = frozenset([(0, 1), (1, 0)]) 36 | 37 | csp.add_constraint(neq, ['a', 'b']) 38 | 39 | self.assertTrue(csp.check({'a': 0, 'b': 1})) 40 | self.assertTrue(csp.check({'a': 1, 'b': 0})) 41 | 42 | self.assertFalse(csp.check({'a': 0, 'b': 0})) 43 | self.assertFalse(csp.check({'a': 1, 'b': 1})) 44 | 45 | eq = frozenset([(0, 0), (1, 1)]) 46 | 47 | csp.add_constraint(eq, ['b', 'c']) 48 | 49 | self.assertTrue(csp.check({'a': 0, 'b': 1, 'c': 1})) 50 | self.assertTrue(csp.check({'a': 1, 'b': 0, 'c': 0})) 51 | 52 | self.assertFalse(csp.check({'a': 0, 'b': 0, 'c': 0})) 53 | self.assertFalse(csp.check({'a': 1, 'b': 1, 'c': 1})) 54 | self.assertFalse(csp.check({'a': 0, 'b': 1, 'c': 0})) 55 | self.assertFalse(csp.check({'a': 1, 'b': 0, 'c': 1})) 56 | self.assertFalse(csp.check({'a': 0, 'b': 0, 'c': 1})) 57 | self.assertFalse(csp.check({'a': 1, 'b': 1, 'c': 0})) 58 | 59 | def test_fix_variable(self): 60 | csp = dwavebinarycsp.CSP(dwavebinarycsp.BINARY) 61 | 62 | csp.add_constraint(operator.eq, ['a', 'b']) 63 | csp.add_constraint(operator.ne, ['b', 'c']) 64 | csp.add_constraint(operator.eq, ['c', 'd']) 65 | 66 | self.assertTrue(csp.check({'a': 1, 'b': 1, 'c': 0, 'd': 0})) 67 | self.assertTrue(csp.check({'a': 0, 'b': 0, 'c': 1, 'd': 1})) 68 | 69 | csp.fix_variable('d', 1) 70 | 71 | self.assertTrue(csp.check({'a': 0, 'b': 0, 'c': 1, 'd': 1})) 72 | self.assertFalse(csp.check({'a': 1, 'b': 1, 'c': 0, 'd': 0})) 73 | 74 | self.assertTrue(csp.check({'a': 0, 'b': 0, 'c': 1})) 75 | -------------------------------------------------------------------------------- /tests/test_documentation.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2018 D-Wave Systems Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import unittest 17 | 18 | import dimod 19 | import networkx as nx 20 | 21 | import dwavebinarycsp 22 | 23 | 24 | class TestDocumentation(unittest.TestCase): 25 | def test_intro_example(self): 26 | # Dev note: test that the example in the intro documentation still works, if this fails go 27 | # update the example! 28 | 29 | # from dwave.system.samplers import DWaveSampler 30 | # from dwave.system.composites import EmbeddingComposite 31 | # import networkx as nx 32 | # import matplotlib.pyplot as plt 33 | 34 | # Represent the map as the nodes and edges of a graph 35 | provinces = ['AB', 'BC', 'MB', 'NB', 'NL', 'NS', 'NT', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT'] 36 | neighbors = [('AB', 'BC'), ('AB', 'NT'), ('AB', 'SK'), ('BC', 'NT'), ('BC', 'YT'), ('MB', 'NU'), 37 | ('MB', 'ON'), ('MB', 'SK'), ('NB', 'NS'), ('NB', 'QC'), ('NL', 'QC'), ('NT', 'NU'), 38 | ('NT', 'SK'), ('NT', 'YT'), ('ON', 'QC')] 39 | 40 | # Function for the constraint that two nodes with a shared edge not both select one color 41 | def not_both_1(v, u): 42 | return not (v and u) 43 | 44 | # # Function that plots a returned sample 45 | # def plot_map(sample): 46 | # G = nx.Graph() 47 | # G.add_nodes_from(provinces) 48 | # G.add_edges_from(neighbors) 49 | # # Translate from binary to integer color representation 50 | # color_map = {} 51 | # for province in provinces: 52 | # for i in range(colors): 53 | # if sample[province+str(i)]: 54 | # color_map[province] = i 55 | # # Plot the sample with color-coded nodes 56 | # node_colors = [color_map.get(node) for node in G.nodes()] 57 | # nx.draw_circular(G, with_labels=True, node_color=node_colors, node_size=3000, cmap=plt.cm.rainbow) 58 | # plt.show() 59 | 60 | # Valid configurations for the constraint that each node select a single color 61 | one_color_configurations = {(0, 0, 0, 1), (0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)} 62 | colors = len(one_color_configurations) 63 | 64 | # Create a binary constraint satisfaction problem 65 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 66 | 67 | # Add constraint that each node (province) select a single color 68 | for province in provinces: 69 | variables = [province+str(i) for i in range(colors)] 70 | csp.add_constraint(one_color_configurations, variables) 71 | 72 | # Add constraint that each pair of nodes with a shared edge not both select one color 73 | for neighbor in neighbors: 74 | v, u = neighbor 75 | for i in range(colors): 76 | variables = [v+str(i), u+str(i)] 77 | csp.add_constraint(not_both_1, variables) 78 | 79 | # Convert the binary constraint satisfaction problem to a binary quadratic model 80 | bqm = dwavebinarycsp.stitch(csp) 81 | 82 | sampler = dimod.SimulatedAnnealingSampler() 83 | # # Set up a solver using the local system’s default D-Wave Cloud Client configuration file 84 | # # and sample 50 times 85 | # sampler = EmbeddingComposite(DWaveSampler()) # doctest: +SKIP 86 | response = sampler.sample(bqm, num_reads=10) 87 | 88 | # # Plot the lowest-energy sample if it meets the constraints 89 | sample = response.first.sample # doctest: +SKIP 90 | # if not csp.check(sample): # doctest: +SKIP 91 | # print("Failed to color map") 92 | # else: 93 | # plot_map(sample) 94 | 95 | if not csp.check(sample): 96 | pass 97 | -------------------------------------------------------------------------------- /tests/test_factories_constraint.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 dimod 19 | 20 | import dwavebinarycsp 21 | import dwavebinarycsp.testing as dcspt 22 | import dwavebinarycsp.factories.constraint as constraint 23 | 24 | 25 | class TestGates(unittest.TestCase): 26 | 27 | def test_AND(self): 28 | and_ = constraint.and_gate([0, 1, 2], vartype=dwavebinarycsp.BINARY) 29 | dcspt.assert_consistent_constraint(and_) 30 | 31 | self.assertEqual(and_.name, 'AND') 32 | 33 | and_ = constraint.and_gate([0, 1, 2], vartype=dwavebinarycsp.SPIN) 34 | dcspt.assert_consistent_constraint(and_) 35 | 36 | self.assertEqual(and_.name, 'AND') 37 | 38 | def test_OR(self): 39 | or_ = constraint.or_gate([0, 1, 2], vartype=dwavebinarycsp.BINARY) 40 | dcspt.assert_consistent_constraint(or_) 41 | 42 | self.assertEqual(or_.name, 'OR') 43 | 44 | or_ = constraint.or_gate([0, 1, 2], vartype=dwavebinarycsp.SPIN) 45 | dcspt.assert_consistent_constraint(or_) 46 | 47 | self.assertEqual(or_.name, 'OR') 48 | 49 | def test_XOR(self): 50 | or_ = constraint.xor_gate([0, 1, 2], vartype=dwavebinarycsp.BINARY) 51 | dcspt.assert_consistent_constraint(or_) 52 | 53 | self.assertEqual(or_.name, 'XOR') 54 | 55 | or_ = constraint.xor_gate([0, 1, 2], vartype=dwavebinarycsp.SPIN) 56 | dcspt.assert_consistent_constraint(or_) 57 | 58 | self.assertEqual(or_.name, 'XOR') 59 | 60 | def test_HALF_ADDER(self): 61 | or_ = constraint.halfadder_gate([0, 1, 2, 3], vartype=dwavebinarycsp.BINARY) 62 | dcspt.assert_consistent_constraint(or_) 63 | 64 | self.assertEqual(or_.name, 'HALF_ADDER') 65 | 66 | or_ = constraint.halfadder_gate([0, 1, 2, 3], vartype=dwavebinarycsp.SPIN) 67 | dcspt.assert_consistent_constraint(or_) 68 | 69 | self.assertEqual(or_.name, 'HALF_ADDER') 70 | 71 | def test_FULL_ADDER(self): 72 | or_ = constraint.fulladder_gate([0, 1, 2, 'a', 'b'], vartype=dwavebinarycsp.BINARY) 73 | dcspt.assert_consistent_constraint(or_) 74 | 75 | self.assertEqual(or_.name, 'FULL_ADDER') 76 | 77 | or_ = constraint.fulladder_gate([0, 1, 2, 'a', 'b'], vartype=dwavebinarycsp.SPIN) 78 | dcspt.assert_consistent_constraint(or_) 79 | 80 | self.assertEqual(or_.name, 'FULL_ADDER') 81 | 82 | 83 | class TestSat(unittest.TestCase): 84 | def test_sat2in4(self): 85 | const = constraint.sat2in4(['a', 'b', 'c', 'd']) 86 | dcspt.assert_consistent_constraint(const) 87 | 88 | valid = set() 89 | for u, v in itertools.combinations(range(4), 2): 90 | config = [0, 0, 0, 0] 91 | config[u], config[v] = 1, 1 92 | 93 | valid.add(tuple(config)) 94 | 95 | for config in itertools.product([0, 1], repeat=4): 96 | if config in valid: 97 | self.assertTrue(const.func(*config)) 98 | else: 99 | self.assertFalse(const.func(*config)) 100 | 101 | def test_sat2in4_with_negation(self): 102 | const = constraint.sat2in4(pos=('a', 'd'), neg=('c', 'b')) 103 | dcspt.assert_consistent_constraint(const) 104 | 105 | self.assertTrue(const.check({'a': 1, 'b': 1, 'c': 1, 'd': 1})) 106 | -------------------------------------------------------------------------------- /tests/test_factories_csp.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 dwavebinarycsp.factories.csp as problem 18 | 19 | import logging 20 | 21 | logging.basicConfig(format='%(message)s', level=logging.DEBUG) 22 | 23 | 24 | class TestSAT(unittest.TestCase): 25 | def test_random_2in4sat(self): 26 | csp = problem.random_2in4sat(4, 1) 27 | 28 | def test_random_xorsat(self): 29 | csp = problem.random_xorsat(3, 1) 30 | 31 | 32 | class TestCircuits(unittest.TestCase): 33 | def test_multiplication_circuit(self): 34 | csp = problem.multiplication_circuit(3) 35 | -------------------------------------------------------------------------------- /tests/test_int_stitcher.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2018 D-Wave Systems Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import unittest 17 | import operator 18 | 19 | import dimod 20 | 21 | import dwavebinarycsp 22 | 23 | import penaltymodel as pm 24 | 25 | 26 | class TestStitch(unittest.TestCase): 27 | 28 | def test_stitch_multiplication_circuit(self): 29 | 30 | circuit = dwavebinarycsp.factories.multiplication_circuit(3) # 3x3=6 bit 31 | 32 | # the circuit csp is too large for dimod's exact solver to solve quickly, so let's go ahead 33 | # and fix the inputs and outputs to ones that satisfy the csp and solve for the aux variables 34 | 35 | # 15 = 3 * 5 36 | 37 | fixed_variables = dict([('p0', 1), ('p1', 1), ('p2', 1), ('p3', 1), ('p4', 0), ('p5', 0), # 15 38 | ('a0', 1), ('a1', 1), ('a2', 0), # 3 39 | ('b0', 1), ('b1', 0), ('b2', 1)]) # 5 40 | 41 | for v, val in fixed_variables.items(): 42 | circuit.fix_variable(v, val) 43 | 44 | # original circuit 45 | original_circuit = dwavebinarycsp.factories.multiplication_circuit(3) 46 | 47 | # we are using an exact solver, so we only need a positive classical gap 48 | bqm = dwavebinarycsp.stitch(circuit, min_classical_gap=.1) 49 | 50 | resp = dimod.ExactSolver().sample(bqm) 51 | 52 | ground_energy = min(resp.record['energy']) 53 | 54 | for sample, energy in resp.data(['sample', 'energy']): 55 | if energy == ground_energy: 56 | self.assertTrue(circuit.check(sample)) 57 | else: 58 | self.assertFalse(circuit.check(sample)) 59 | 60 | # check against the original circuit 61 | fixed = fixed_variables.copy() 62 | fixed.update(sample) 63 | 64 | if energy == ground_energy: 65 | self.assertTrue(original_circuit.check(fixed)) 66 | else: 67 | self.assertFalse(original_circuit.check(fixed)) 68 | 69 | def test_csp_one_xor(self): 70 | 71 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 72 | 73 | variables = ['a', 'b', 'c'] 74 | xor = dwavebinarycsp.factories.constraint.gates.xor_gate(variables) 75 | csp.add_constraint(xor) 76 | bqm = dwavebinarycsp.stitch(csp) 77 | 78 | resp = dimod.ExactSolver().sample(bqm) 79 | 80 | ground_energy = min(resp.record['energy']) 81 | 82 | for sample, energy in resp.data(['sample', 'energy']): 83 | if energy == ground_energy: 84 | self.assertTrue(csp.check(sample)) 85 | else: 86 | if abs(energy - ground_energy) < 2: 87 | # if classical gap is less than 2 88 | self.assertTrue(csp.check(sample)) 89 | 90 | def test_csp_one_xor_impossible(self): 91 | 92 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 93 | 94 | variables = ['a', 'b', 'c'] 95 | xor = dwavebinarycsp.factories.constraint.gates.xor_gate(variables) 96 | csp.add_constraint(xor) 97 | 98 | with self.assertRaises(pm.ImpossiblePenaltyModel): 99 | bqm = dwavebinarycsp.stitch(csp, max_graph_size=3) 100 | 101 | def test_eight_variable_constraint_smoketest(self): 102 | 103 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 104 | 105 | variables = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 106 | 107 | # this is reducible but for our purposes here that's fine 108 | def f(a, b, c, d, e, f, g, h): 109 | if a and b: 110 | return False 111 | if c and d: 112 | return False 113 | if e and f: 114 | return False 115 | return not (g and h) 116 | 117 | csp.add_constraint(f, variables) 118 | 119 | bqm = dwavebinarycsp.stitch(csp) 120 | 121 | resp = dimod.ExactSolver().sample(bqm) 122 | 123 | ground_energy = min(resp.record['energy']) 124 | 125 | for sample, energy in resp.data(['sample', 'energy']): 126 | if energy == ground_energy: 127 | self.assertTrue(csp.check(sample)) 128 | else: 129 | if abs(energy - ground_energy) < 2: 130 | # if classical gap is less than 2 131 | self.assertTrue(csp.check(sample)) 132 | -------------------------------------------------------------------------------- /tests/test_io_cnf.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 | import unittest 17 | import os.path as path 18 | 19 | import dwavebinarycsp as dbcsp 20 | 21 | 22 | class Test_cnf_to_csp(unittest.TestCase): 23 | def test_simple(self): 24 | cnf = ['c simple_v3_c2.cnf', 25 | 'c', 26 | 'p cnf 3 2', 27 | '1 -3 0', 28 | '2 3 -1 0', 29 | ] 30 | 31 | csp = dbcsp.cnf.load_cnf(cnf) 32 | 33 | for config in itertools.product((0, 1), repeat=3): 34 | sample = dict(zip([1, 2, 3], config)) 35 | 36 | if (sample[1] or 1 - sample[3]) and (sample[2] or sample[3] or 1 - sample[1]): 37 | self.assertTrue(csp.check(sample)) 38 | else: 39 | self.assertFalse(csp.check(sample)) 40 | 41 | def test_file0(self): 42 | filepath = path.join(path.dirname(path.abspath(__file__)), 'data', 'test0.cnf') 43 | with open(filepath, 'r') as fp: 44 | csp = dbcsp.cnf.load_cnf(fp) 45 | 46 | self.assertEqual(len(csp), 192) 47 | self.assertEqual(len(csp.variables), 17) 48 | 49 | def test_file1(self): 50 | filepath = path.join(path.dirname(path.abspath(__file__)), 'data', 'test1.cnf') 51 | with open(filepath, 'r') as fp: 52 | csp = dbcsp.cnf.load_cnf(fp) 53 | 54 | self.assertEqual(len(csp), 153) 55 | self.assertEqual(len(csp.variables), 98) 56 | -------------------------------------------------------------------------------- /tests/test_reduction.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 dwavebinarycsp 18 | 19 | 20 | class TestDecomposition(unittest.TestCase): 21 | def test_irreducible_components(self): 22 | const = dwavebinarycsp.Constraint.from_configurations([(0, 0, 1), (1, 1, 1)], ['a', 'b', 'c'], dwavebinarycsp.BINARY) 23 | 24 | self.assertEqual(set(dwavebinarycsp.irreducible_components(const)), {('a', 'b'), ('c',)}) 25 | 26 | def test_irreducible_components_one_fixed(self): 27 | const = dwavebinarycsp.Constraint.from_configurations(frozenset([(0, 1), (0, 0)]), ('a', 'b'), dwavebinarycsp.BINARY) 28 | 29 | self.assertEqual(set(dwavebinarycsp.irreducible_components(const)), {('a',), ('b',)}) 30 | -------------------------------------------------------------------------------- /tests/test_stitcher.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 operator 17 | import itertools 18 | 19 | import networkx as nx 20 | import dimod 21 | 22 | import dwavebinarycsp 23 | from dwavebinarycsp.compilers import stitcher 24 | 25 | 26 | class TestIterCompleteGraph(unittest.TestCase): 27 | def test_iter_complete_graph_simple(self): 28 | 29 | graphs = list(stitcher.iter_complete_graphs(4, 6)) 30 | self.assertEqual(len(graphs), 2) 31 | 32 | self.assertEqual(graphs[0].adj, nx.complete_graph(4).adj) 33 | self.assertEqual(graphs[1].adj, nx.complete_graph(5).adj) 34 | 35 | def test_iter_complete_graph_seed_nodes(self): 36 | 37 | # implicit that the length is 2 38 | G0, G1 = stitcher.iter_complete_graphs(['a', 'b'], 4) 39 | 40 | self.assertIn('a', G0) 41 | self.assertIn('a', G1) 42 | self.assertIn('b', G0) 43 | self.assertIn('b', G1) 44 | 45 | self.assertEqual(set(G1), {'a', 'b', 0}) # aux var should be index-labeled 46 | 47 | def test_iter_complete_graph_seed_node_index(self): 48 | 49 | # implicit that the length is 3 50 | G0, G1, G2 = stitcher.iter_complete_graphs([1], 4) 51 | 52 | self.assertIn(1, G0) 53 | self.assertIn(1, G1) 54 | 55 | self.assertEqual(set(G0), {1}) # start with label 1 56 | self.assertEqual(set(G1), {0, 1}) 57 | self.assertEqual(set(G2), {0, 1, 2}) 58 | 59 | def test_iter_complete_graph_empty(self): 60 | 61 | # should produce empty lists rather than failing 62 | self.assertFalse(list(stitcher.iter_complete_graphs(['a', 'b', 'c'], 2))) 63 | self.assertFalse(list(stitcher.iter_complete_graphs(3, 2))) 64 | 65 | def test_iter_complete_graph_factory(self): 66 | 67 | def factory(): 68 | i = 0 69 | while True: 70 | yield 'aux{}'.format(i) 71 | i += 1 72 | 73 | G0, G1, G2 = stitcher.iter_complete_graphs(['a', 'b'], 5, factory=factory()) 74 | 75 | self.assertEqual(set(G2), {'a', 'b', 'aux0', 'aux1'}) 76 | 77 | def test_start_8_stop_8(self): 78 | variables = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 79 | 80 | # should produce at least one graph 81 | G, = stitcher.iter_complete_graphs(variables, 9) 82 | 83 | G0 = nx.complete_graph(variables) 84 | 85 | self.assertEqual(G.nodes, G0.nodes) 86 | self.assertEqual(G.edges, G0.edges) 87 | 88 | 89 | class TestStitch(unittest.TestCase): 90 | def test__bqm_from_1sat(self): 91 | const = dwavebinarycsp.Constraint.from_configurations([(0,)], ['a'], dwavebinarycsp.BINARY) 92 | 93 | bqm = stitcher._bqm_from_1sat(const) 94 | 95 | self.assertTrue(bqm.energy({'a': 0}) + 2 <= bqm.energy({'a': 1})) 96 | 97 | # 98 | 99 | const = dwavebinarycsp.Constraint.from_configurations([(1,)], ['a'], dwavebinarycsp.BINARY) 100 | 101 | bqm = stitcher._bqm_from_1sat(const) 102 | 103 | self.assertTrue(bqm.energy({'a': 1}) + 2 <= bqm.energy({'a': 0})) 104 | 105 | # 106 | 107 | const = dwavebinarycsp.Constraint.from_configurations([(-1,)], ['a'], dwavebinarycsp.SPIN) 108 | 109 | bqm = stitcher._bqm_from_1sat(const) 110 | 111 | self.assertTrue(bqm.energy({'a': -1}) + 2 <= bqm.energy({'a': 1})) 112 | 113 | # 114 | 115 | const = dwavebinarycsp.Constraint.from_configurations([(+1,)], ['a'], dwavebinarycsp.SPIN) 116 | 117 | bqm = stitcher._bqm_from_1sat(const) 118 | 119 | self.assertTrue(bqm.energy({'a': 1}) + 2 <= bqm.energy({'a': -1})) 120 | 121 | # 122 | 123 | const = dwavebinarycsp.Constraint.from_configurations([(-1,), (+1,)], ['a'], dwavebinarycsp.SPIN) 124 | 125 | bqm = stitcher._bqm_from_1sat(const) 126 | 127 | self.assertAlmostEqual(bqm.energy({'a': -1}), bqm.energy({'a': 1})) 128 | 129 | # 130 | 131 | const = dwavebinarycsp.Constraint.from_configurations([(0,), (1,)], ['a'], dwavebinarycsp.BINARY) 132 | 133 | bqm = stitcher._bqm_from_1sat(const) 134 | 135 | self.assertAlmostEqual(bqm.energy({'a': 0}), bqm.energy({'a': 1})) 136 | 137 | def test_stitch_2sat(self): 138 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) 139 | for v in range(10): 140 | csp.add_constraint(operator.eq, [v, v+1]) 141 | 142 | bqm = stitcher.stitch(csp) 143 | 144 | for bias in bqm.linear.values(): 145 | self.assertAlmostEqual(bias, 0) 146 | for bias in bqm.quadratic.values(): 147 | self.assertAlmostEqual(bias, -1) 148 | 149 | def test_stitch_max_graph_size_is_1(self): 150 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 151 | 152 | csp.add_constraint(operator.eq, ['a', 'b']) 153 | csp.add_constraint(operator.ne, ['b', 'c']) 154 | 155 | with self.assertRaises(dwavebinarycsp.exceptions.ImpossibleBQM): 156 | bqm = dwavebinarycsp.stitch(csp, max_graph_size=1) 157 | 158 | def test_stitch_constraint_too_large(self): 159 | csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) 160 | 161 | def f(*args): 162 | return all(args) 163 | 164 | csp.add_constraint(f, list('abcdefghijk')) # 11 variables 165 | 166 | with self.assertRaises(dwavebinarycsp.exceptions.ImpossibleBQM): 167 | bqm = dwavebinarycsp.stitch(csp, max_graph_size=8) 168 | 169 | def test_returned_gap(self): 170 | """Verify that stitch is only allowing gaps that satisfy min_classical_gap to be returned. 171 | """ 172 | # Set up CSP 173 | csp = dwavebinarycsp.ConstraintSatisfactionProblem("SPIN") 174 | csp.add_constraint(operator.ne, ['a', 'b']) 175 | 176 | # Show that CSP has a valid BQM 177 | small_gap = 2 178 | bqm = dwavebinarycsp.stitch(csp, min_classical_gap=small_gap, max_graph_size=2) 179 | 180 | # Verify the gap based on returned bqm 181 | sampleset = dimod.ExactSolver().sample(bqm) 182 | energy_array = sampleset.record['energy'] 183 | gap = max(energy_array) - min(energy_array) 184 | self.assertGreaterEqual(gap, small_gap) 185 | 186 | # Same CSP with a larger min_classical_gap 187 | # Note: Even though there is a BQM for this CSP (shown above), stitch should throw an 188 | # exception because the BQM does not satisfy the following min_classical_gap requirement. 189 | with self.assertRaises(dwavebinarycsp.exceptions.ImpossibleBQM): 190 | dwavebinarycsp.stitch(csp, min_classical_gap=4, max_graph_size=2) 191 | 192 | def test_returned_gap_with_aux(self): 193 | """Verify that stitch is only allowing gaps that satisfy min_classical_gap to be returned. 194 | In this case, by allowing an auxiliary variable, the min_classical_gap should be achieved 195 | and stitch should allow a bqm to be returned. 196 | """ 197 | csp = dwavebinarycsp.ConstraintSatisfactionProblem("SPIN") 198 | csp.add_constraint(operator.eq, ['a', 'b']) 199 | min_classical_gap = 3 200 | 201 | # No aux case: max_graph_size=2 202 | # Note: Should not be possible to satisfy min_classical_gap 203 | with self.assertRaises(dwavebinarycsp.exceptions.ImpossibleBQM): 204 | dwavebinarycsp.stitch(csp, min_classical_gap=min_classical_gap, max_graph_size=2) 205 | 206 | # One aux case: max_graph_size=3 207 | # Note: min_classical_gap should be satisfied when we have three nodes 208 | bqm = dwavebinarycsp.stitch(csp, min_classical_gap=min_classical_gap, max_graph_size=3) 209 | 210 | # Verify one aux case 211 | sampleset = dimod.ExactSolver().sample(bqm) 212 | energy_array = sampleset.record['energy'] 213 | gap = max(energy_array) - min(energy_array) 214 | self.assertGreaterEqual(gap, min_classical_gap) 215 | 216 | 217 | def powerset(iterable): 218 | "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" 219 | s = list(iterable) 220 | return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s)+1)) 221 | 222 | 223 | if __name__ == '__main__': 224 | unittest.main() 225 | --------------------------------------------------------------------------------