├── .circleci └── config.yml ├── .devcontainer └── devcontainer.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── circuit_fault_diagnosis ├── __init__.py ├── circuits.py └── gates.py ├── demo.py ├── requirements.txt └── tests ├── __init__.py └── test_integration.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | dwave: dwave/orb-examples@2 5 | 6 | workflows: 7 | version: 2.1 8 | tests: 9 | jobs: 10 | - dwave/test-linux 11 | - dwave/test-osx 12 | - dwave/test-win 13 | 14 | weekly: 15 | triggers: 16 | - schedule: 17 | cron: "0 4 * * 5" 18 | filters: 19 | branches: 20 | only: 21 | - master 22 | - main 23 | jobs: 24 | - dwave/test-linux: 25 | integration-tests: "canary" 26 | - dwave/test-osx: 27 | integration-tests: "skip" 28 | - dwave/test-win: 29 | integration-tests: "skip" 30 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Ocean Development Environment", 5 | 6 | // python 3.11 on debian, with latest Ocean and optional packages 7 | // source repo: https://github.com/dwavesystems/ocean-dev-docker 8 | "image": "docker.io/dwavesys/ocean-dev:latest", 9 | 10 | // install repo requirements on create and content update 11 | "updateContentCommand": "pip install -r requirements.txt", 12 | 13 | // forward/expose container services (relevant only when run locally) 14 | "forwardPorts": [ 15 | // dwave-inspector web app 16 | 18000, 18001, 18002, 18003, 18004, 17 | // OAuth connect redirect URIs 18 | 36000, 36001, 36002, 36003, 36004 19 | ], 20 | 21 | "portsAttributes": { 22 | "18000-18004": { 23 | "label": "D-Wave Problem Inspector", 24 | "requireLocalPort": true 25 | }, 26 | "36000-36004": { 27 | "label": "OAuth 2.0 authorization code redirect URI", 28 | "requireLocalPort": true 29 | } 30 | }, 31 | 32 | // Configure tool-specific properties. 33 | "customizations": { 34 | // Configure properties specific to VS Code. 35 | "vscode": { 36 | // Set *default* container specific settings.json values on container create. 37 | "settings": { 38 | "workbench": { 39 | "editorAssociations": { 40 | "*.md": "vscode.markdown.preview.editor" 41 | }, 42 | "startupEditor": "readme" 43 | } 44 | }, 45 | "extensions": [ 46 | "ms-python.python", 47 | "ms-toolsai.jupyter" 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.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 16.04.4 LTS] 18 | - Python version: [e.g., 3.7.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 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open in GitHub Codespaces]( 2 | https://img.shields.io/badge/Open%20in%20GitHub%20Codespaces-333?logo=github)]( 3 | https://codespaces.new/dwave-examples/circuit-fault-diagnosis?quickstart=1) 4 | [![Linux/Mac/Windows build status]( 5 | https://circleci.com/gh/dwave-examples/circuit-fault-diagnosis.svg?style=shield)]( 6 | https://circleci.com/gh/dwave-examples/circuit-fault-diagnosis) 7 | 8 | # Circuit Fault Diagnosis 9 | 10 | Fault diagnosis is the combinational problem of quickly localizing failures as 11 | soon as they are detected in systems. Circuit fault diagnosis is the problem of 12 | identifying a minimum-sized set of components that, if faulty, explains an 13 | observation of incorrect outputs given a set of inputs. 14 | 15 | ## Usage 16 | 17 | Run `demo.py` and then follow the on-screen instructions. 18 | 19 | ```bash 20 | python demo.py 21 | ``` 22 | 23 | ## Code Overview 24 | 25 | This code demonstrates the use of the D-Wave system to solve such a problem in 26 | the case of a three-bit multiplier circuit. The user is prompted to enter three 27 | integers: A and B, which are the inputs the circuit is expected to multiply, 28 | and the circuit's output, P, which represents either a valid or incorrect 29 | product of the inputs. 30 | 31 | ```bash 32 | Input multiplier ( 0 <= A <= 7): 33 | Input multiplicand ( 0 <= B <= 7): 34 | Input product ( 0 <= P <= 63): 35 | ``` 36 | 37 | The algorithm returns the minimum fault diagnosis (the smallest number of 38 | faulty components it found to cause the given inputs and product) and the 39 | number of distinct fault states with this many faults it observed. 40 | 41 | ## Code Specifics 42 | 43 | ### Advanced Options 44 | 45 | The `--verbose` option displays the valid/fault status of each component for 46 | each minimum fault diagnosis. 47 | 48 | ```bash 49 | python demo.py --verbose 50 | ``` 51 | 52 | ### Interesting Use Cases 53 | 54 | A single faulty component leads to five incorrect bits in the product's six 55 | bits (due to the commutative property of multiplication, these are two 56 | isomorphic sets) in these four cases: 57 | 58 | ```bash 59 | A = 6; B = 5; P = 32 60 | A = 5; B = 6; P = 32 61 | A = 7; B = 4; P = 34 62 | A = 4; B = 7; P = 34 63 | ``` 64 | 65 | Two faulty components lead to all the product's six bits being incorrect (this 66 | is due to the least significant bit being determined solely by one AND gate) in 67 | these four cases: 68 | 69 | ```bash 70 | A = 6; B = 5; P = 33 71 | A = 5; B = 6; P = 33 72 | A = 7; B = 4; P = 35 73 | A = 4; B = 7; P = 35 74 | ``` 75 | 76 | Four faulty components, which is the maximum number of faulty components for a 77 | minimum fault diagnosis for this circuit, lead to five incorrect bits in the 78 | product's six bits in this case (one of many such cases): 79 | 80 | ```bash 81 | A = 7; B = 6; P = 1 82 | ``` 83 | 84 | In general, the number of incorrect bits in the product is greater than or 85 | equal to the number of faulty components. 86 | 87 | ## References 88 | 89 | Z. Bian, F. Chudak, R. B. Israel, B. Lackey, W. G. Macready, and A. Roy, 90 | “Mapping constrained optimization problems to quantum annealing with 91 | application to fault diagnosis,” Frontiers in ICT, vol. 3, p. 14, 2016. 92 | https://www.frontiersin.org/articles/10.3389/fict.2016.00014/full 93 | 94 | A. Perdomo-Ortiz, J. Fluegemann, S. Narasimhan, R. Biswas, and V. N. 95 | Smelyanskiy, “A quantum annealing approach for fault detection and diagnosis of 96 | graph-based systems,” European Physical Journal Special Topics, vol. 224, Feb. 97 | 2015. 98 | https://arxiv.org/abs/1406.7601v2 99 | 100 | ## License 101 | 102 | Released under the Apache License 2.0. See [LICENSE](LICENSE) file. 103 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/circuit-fault-diagnosis/612b2836473bdd29b4ba892a5a8d061e3966f737/__init__.py -------------------------------------------------------------------------------- /circuit_fault_diagnosis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/circuit-fault-diagnosis/612b2836473bdd29b4ba892a5a8d061e3966f737/circuit_fault_diagnosis/__init__.py -------------------------------------------------------------------------------- /circuit_fault_diagnosis/circuits.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 | # This Python file uses the following encoding: utf-8 16 | 17 | import sys 18 | 19 | import dimod 20 | 21 | from circuit_fault_diagnosis.gates import gate_model, GATES 22 | 23 | _PY2 = sys.version_info.major == 2 24 | 25 | if _PY2: 26 | def iteritems(d): 27 | return d.iteritems() 28 | 29 | def itervalues(d): 30 | return d.itervalues() 31 | 32 | else: 33 | def iteritems(d): 34 | return d.items() 35 | 36 | def itervalues(d): 37 | return d.values() 38 | 39 | 40 | # adapted from dwave_constraint_compilers.stitch() 41 | def stitch(models): 42 | """ 43 | Take a list of :class:`pm.PenaltyModel` models, and 'stitch' them together: 44 | The variable set of the new model is the additive union of the variable sets of the models, 45 | the relations set of the new model is the additive union of the relation sets of the models. 46 | 47 | That is, the new widget contains every variable and coupler that is in any widget, 48 | and the bias of a variable or relation is the sum of the biases in of the variable 49 | or relation in all models that contain it. 50 | 51 | Similarly, the offset is summed across all models. 52 | 53 | All constraints are converted to :class:`pm.Vartype.SPIN`. 54 | 55 | Args: 56 | models (list[pm.PenaltyModel]): A list of penalty models to be stiched together. 57 | 58 | Returns: 59 | :class:`dimod.BinaryQuadraticModel`: The resulting :class:`BinaryQuadraticModel`. 60 | 61 | """ 62 | linear = {} 63 | quadratic = {} 64 | offset = 0 65 | for widget in models: 66 | for variable, bias in iteritems(widget.model.linear): 67 | linear[variable] = linear.get(variable, 0) + bias 68 | 69 | for relation, bias in iteritems(widget.model.quadratic): 70 | quadratic[relation] = quadratic.get(relation, 0) + bias 71 | 72 | offset += widget.model.offset 73 | 74 | return dimod.BinaryQuadraticModel(linear, quadratic, offset, dimod.SPIN) 75 | 76 | 77 | def new_pmodel(pmodel, old_labels, new_labels): 78 | def new_aux(): 79 | new_pmodel.counter += 1 80 | return "aux%i" % new_pmodel.counter 81 | 82 | mapping = dict(zip(old_labels, new_labels)) 83 | mapping.update({x: new_aux() for x in pmodel.graph.nodes if x not in old_labels}) 84 | return pmodel.relabel_variables(mapping, inplace=False) 85 | new_pmodel.counter = 0 86 | 87 | 88 | def three_bit_multiplier(fault=True): 89 | #################################################################################################### 90 | # get basic gate fault models 91 | #################################################################################################### 92 | 93 | # print("AND gate fault model") 94 | pmodel_and = gate_model('AND', fault) 95 | 96 | # print("half adder fault model") 97 | pmodel_half_add = gate_model('HALF_ADD', fault) 98 | 99 | # print("full adder fault model") 100 | pmodel_full_add = gate_model('FULL_ADD', fault) 101 | 102 | #################################################################################################### 103 | # wire the whole thing up 104 | #################################################################################################### 105 | 106 | models = [] 107 | labels = {} 108 | 109 | # a2 & b0 a1 & b0 a0 & b0 110 | # a2 & b1 a1 & b1 a0 & b1 111 | # a2 & b2 a1 & b2 a0 & b2 112 | # ──────────────────────────────────────────────────── 113 | # p5 p4 p3 p2 p1 p0 114 | 115 | # and20 and10 and00 116 | # and21 and11 and01 117 | # and22 and12 and02 118 | # ──────────────────────────────────────── 119 | # p5 p4 p3 p2 p1 p0 120 | 121 | labels_and, _ = GATES['AND'] 122 | labels['AND'] = {} 123 | 124 | labels['AND']['and00'] = ('a0', 'b0', 'p0') 125 | labels['AND']['and01'] = ('a0', 'b1', 'and01') 126 | labels['AND']['and02'] = ('a0', 'b2', 'and02') 127 | labels['AND']['and10'] = ('a1', 'b0', 'and10') 128 | labels['AND']['and11'] = ('a1', 'b1', 'and11') 129 | labels['AND']['and12'] = ('a1', 'b2', 'and12') 130 | labels['AND']['and20'] = ('a2', 'b0', 'and20') 131 | labels['AND']['and21'] = ('a2', 'b1', 'and21') 132 | labels['AND']['and22'] = ('a2', 'b2', 'and22') 133 | 134 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and00'])) 135 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and01'])) 136 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and02'])) 137 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and10'])) 138 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and11'])) 139 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and12'])) 140 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and20'])) 141 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and21'])) 142 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND']['and22'])) 143 | 144 | # and20 and10 and00 145 | # | | | 146 | # and21 add11──and11 add01──and01 | 147 | # |┌───────────┘|┌───────────┘| | 148 | # and22 add12──and12 add02──and02 | | 149 | # |┌───────────┘|┌───────────┘| | | 150 | # add13─────────add03 | | | 151 | # ┌───────────┘| | | | | 152 | # p5 p4 p3 p2 p1 p0 153 | 154 | labels_half_add, _ = GATES['HALF_ADD'] 155 | labels['HALF_ADD'] = {} 156 | 157 | labels['HALF_ADD']['add01'] = ('and01', 'and10', 'p1', 'carry01') 158 | labels['HALF_ADD']['add03'] = ('carry02', 'sum12', 'p3', 'carry03') 159 | labels['HALF_ADD']['add11'] = ('and11', 'and20', 'sum11', 'carry11') 160 | 161 | models.append(new_pmodel(pmodel_half_add, labels_half_add, labels['HALF_ADD']['add01'])) 162 | models.append(new_pmodel(pmodel_half_add, labels_half_add, labels['HALF_ADD']['add03'])) 163 | models.append(new_pmodel(pmodel_half_add, labels_half_add, labels['HALF_ADD']['add11'])) 164 | 165 | labels_full_add, _ = GATES['FULL_ADD'] 166 | labels['FULL_ADD'] = {} 167 | 168 | labels['FULL_ADD']['add02'] = ('and02', 'sum11', 'carry01', 'p2', 'carry02') 169 | labels['FULL_ADD']['add12'] = ('and12', 'and21', 'carry11', 'sum12', 'carry12') 170 | labels['FULL_ADD']['add13'] = ('carry03', 'and22', 'carry12', 'p4', 'p5') 171 | 172 | models.append(new_pmodel(pmodel_full_add, labels_full_add, labels['FULL_ADD']['add02'])) 173 | models.append(new_pmodel(pmodel_full_add, labels_full_add, labels['FULL_ADD']['add12'])) 174 | models.append(new_pmodel(pmodel_full_add, labels_full_add, labels['FULL_ADD']['add13'])) 175 | 176 | #################################################################################################### 177 | # combine into one binary quadratic model 178 | #################################################################################################### 179 | 180 | bqm = stitch(models) 181 | # print("three-bit multiplier fault model") 182 | # print('h: {}'.format(bqm.linear)) 183 | # print('J: {}\n'.format(bqm.quadratic)) 184 | return (bqm, labels) 185 | 186 | 187 | def half_adder(fault=True): 188 | #################################################################################################### 189 | # get basic gate fault models 190 | #################################################################################################### 191 | 192 | # print("XOR gate fault model") 193 | pmodel_xor = gate_model('XOR', fault) 194 | 195 | # print("AND gate fault model") 196 | pmodel_and = gate_model('AND', fault) 197 | 198 | #################################################################################################### 199 | # wire the whole thing up 200 | #################################################################################################### 201 | 202 | models = [] 203 | labels = {} 204 | 205 | labels_xor, _ = GATES['XOR'] 206 | labels['XOR'] = ('augend', 'addend', 'sum') 207 | models.append(new_pmodel(pmodel_xor, labels_xor, labels['XOR'])) 208 | 209 | labels_and, _ = GATES['AND'] 210 | labels['AND'] = ('augend', 'addend', 'carry_out') 211 | models.append(new_pmodel(pmodel_and, labels_and, labels['AND'])) 212 | 213 | #################################################################################################### 214 | # combine into one binary quadratic model 215 | #################################################################################################### 216 | 217 | bqm = stitch(models) 218 | # print("half adder fault model") 219 | # print('h: {}'.format(bqm.linear)) 220 | # print('J: {}\n'.format(bqm.quadratic)) 221 | return (bqm, labels) 222 | 223 | 224 | def full_adder(fault=True): 225 | #################################################################################################### 226 | # get basic gate fault models 227 | #################################################################################################### 228 | 229 | # print("half adder fault model") 230 | pmodel_half_add = gate_model('HALF_ADD', fault) 231 | 232 | # print("OR gate fault model") 233 | pmodel_or = gate_model('OR', fault) 234 | 235 | #################################################################################################### 236 | # wire the whole thing up 237 | #################################################################################################### 238 | 239 | models = [] 240 | labels = {} 241 | 242 | labels_half_add, _ = GATES['HALF_ADD'] 243 | labels['HALF_ADD'] = {} 244 | 245 | labels['HALF_ADD']['add1'] = ('augend', 'addend', 'sum1', 'carry_out1') 246 | labels['HALF_ADD']['add2'] = ('sum1','carry_in','sum','carry_out2') 247 | 248 | models.append(new_pmodel(pmodel_half_add, labels_half_add, labels['HALF_ADD']['add1'])) 249 | models.append(new_pmodel(pmodel_half_add, labels_half_add, labels['HALF_ADD']['add2'])) 250 | 251 | labels_or, _ = GATES['OR'] 252 | labels['OR'] = {} 253 | labels['OR']['or'] = ('carry_out1', 'carry_out2', 'carry_out') 254 | models.append(new_pmodel(pmodel_or, labels_or, labels['OR']['or'])) 255 | 256 | #################################################################################################### 257 | # combine into one binary quadratic model 258 | #################################################################################################### 259 | 260 | bqm = stitch(models) 261 | # print("full adder fault model") 262 | # print('h: {}'.format(bqm.linear)) 263 | # print('J: {}\n'.format(bqm.quadratic)) 264 | return (bqm, labels) 265 | -------------------------------------------------------------------------------- /circuit_fault_diagnosis/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 itertools 16 | 17 | import networkx as nx 18 | import penaltymodel.core as pm 19 | import dimod 20 | 21 | GATES = {} 22 | 23 | GATES['AND'] = (('in1', 'in2', 'out'), 24 | {(-1, -1, -1): 0., 25 | (-1, +1, -1): 0., 26 | (+1, -1, -1): 0., 27 | (+1, +1, +1): 0., }) 28 | 29 | GATES['OR'] = (('in1', 'in2', 'out'), 30 | {(-1, -1, -1): 0., 31 | (-1, +1, +1): 0., 32 | (+1, -1, +1): 0., 33 | (+1, +1, +1): 0., }) 34 | 35 | GATES['XOR'] = (('in1', 'in2', 'out'), 36 | {(-1, -1, -1): 0., 37 | (-1, +1, +1): 0., 38 | (+1, -1, +1): 0., 39 | (+1, +1, -1): 0., }) 40 | 41 | GATES['HALF_ADD'] = (('augend', 'addend', 'sum', 'carry_out'), 42 | {(-1, -1, -1, -1): 0., 43 | (-1, +1, +1, -1): 0., 44 | (+1, -1, +1, -1): 0., 45 | (+1, +1, -1, +1): 0.}) 46 | 47 | GATES['FULL_ADD'] = (('augend', 'addend', 'carry_in', 'sum', 'carry_out'), 48 | {(-1, -1, -1, -1, -1): 0., 49 | (-1, -1, +1, +1, -1): 0., 50 | (-1, +1, -1, +1, -1): 0., 51 | (-1, +1, +1, -1, +1): 0., 52 | (+1, -1, -1, +1, -1): 0., 53 | (+1, -1, +1, -1, +1): 0., 54 | (+1, +1, -1, -1, +1): 0., 55 | (+1, +1, +1, +1, +1): 0.}) 56 | 57 | 58 | def fault_gate(gate, explicit_gap): 59 | nV = len(next(iter(gate))) # Assume all the same length 60 | fc = {} 61 | for config in itertools.product((-1, 1), repeat=nV): 62 | if config in gate: 63 | fc[config] = 0 64 | else: 65 | fc[config] = explicit_gap 66 | return fc 67 | 68 | 69 | FAULT_GAP = .5 70 | 71 | 72 | def gate_model(gate_type, fault=True): 73 | labels, configurations = GATES[gate_type] 74 | if fault: 75 | configurations = fault_gate(configurations, FAULT_GAP) 76 | num_variables = len(next(iter(configurations))) 77 | for size in range(num_variables, num_variables+4): # reasonable margin 78 | G = nx.complete_graph(size) 79 | nx.relabel_nodes(G, dict(enumerate(labels)), copy=False) 80 | spec = pm.Specification(G, labels, configurations, dimod.SPIN) 81 | try: 82 | pmodel = pm.get_penalty_model(spec) 83 | if pmodel is not None: 84 | return pmodel 85 | except pm.ImpossiblePenaltyModel: 86 | pass 87 | 88 | raise ValueError("unable to get the penalty model from factories") 89 | -------------------------------------------------------------------------------- /demo.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 sys 16 | import pandas as pd 17 | 18 | from dwave.system.samplers import DWaveSampler 19 | from dwave.system.composites import EmbeddingComposite 20 | 21 | from circuit_fault_diagnosis.circuits import three_bit_multiplier 22 | from circuit_fault_diagnosis.gates import GATES 23 | 24 | _PY2 = sys.version_info.major == 2 25 | if _PY2: 26 | input = raw_input 27 | 28 | 29 | def sanitised_input(description, variable, range_): 30 | start = range_[0] 31 | stop = range_[-1] 32 | 33 | while True: 34 | ui = input("Input {:15}({:2} <= {:1} <= {:2}): ".format(description, start, variable, stop)) 35 | 36 | try: 37 | ui = int(ui) 38 | except ValueError: 39 | print("Input type must be int") 40 | continue 41 | 42 | if ui not in range_: 43 | print("Input must be between {} and {}".format(start, stop)) 44 | continue 45 | 46 | return ui 47 | 48 | 49 | NUM_READS = 1000 50 | 51 | if __name__ == '__main__': 52 | #################################################################################################### 53 | # get circuit 54 | #################################################################################################### 55 | bqm, labels = three_bit_multiplier() 56 | 57 | #################################################################################################### 58 | # get input from user 59 | #################################################################################################### 60 | 61 | fixed_variables = {} 62 | 63 | print("Enter the test conditions") 64 | 65 | A = sanitised_input("multiplier", "A", range(2**3)) 66 | fixed_variables.update(zip(('a2', 'a1', 'a0'), "{:03b}".format(A))) 67 | 68 | B = sanitised_input("multiplicand", "B", range(2**3)) 69 | fixed_variables.update(zip(('b2', 'b1', 'b0'), "{:03b}".format(B))) 70 | 71 | P = sanitised_input("product", "P", range(2**6)) 72 | fixed_variables.update(zip(('p5', 'p4', 'p3', 'p2', 'p1', 'p0'), "{:06b}".format(P))) 73 | 74 | print("\nA = {:03b}".format(A)) 75 | print("B = {:03b}".format(B)) 76 | print("A*B = {:06b}".format(A * B)) 77 | print("P = {:06b}\n".format(P)) 78 | 79 | fixed_variables = {var: 1 if x == '1' else -1 for (var, x) in fixed_variables.items()} 80 | 81 | # fix variables 82 | for var, value in fixed_variables.items(): 83 | bqm.fix_variable(var, value) 84 | # 'aux1' becomes disconnected, so needs to be fixed 85 | bqm.fix_variable('aux1', 1) # don't care value 86 | 87 | # find embedding and put on system 88 | print("Running using QPU\n") 89 | sampler = EmbeddingComposite(DWaveSampler()) 90 | response = sampler.sample_ising(bqm.linear, 91 | bqm.quadratic, 92 | num_reads=NUM_READS, 93 | label='Example - Circuit Fault Diagnosis') 94 | 95 | #################################################################################################### 96 | # output results 97 | #################################################################################################### 98 | 99 | # responses are sorted in order of increasing energy, so the first energy is the minimum 100 | min_energy = next(response.data()).energy 101 | 102 | best_samples = [dict(datum.sample) for datum in response.data() if datum.energy == min_energy] 103 | for sample in best_samples: 104 | for variable in list(sample.keys()): 105 | if 'aux' in variable: 106 | sample.pop(variable) 107 | sample.update(fixed_variables) 108 | 109 | best_results = [] 110 | for sample in best_samples: 111 | result = {} 112 | for gate_type, gates in sorted(labels.items()): 113 | _, configurations = GATES[gate_type] 114 | for gate_name, gate in sorted(gates.items()): 115 | result[gate_name] = 'valid' if tuple(sample[var] for var in gate) in configurations else 'fault' 116 | best_results.append(result) 117 | best_results = pd.DataFrame(best_results) 118 | 119 | # at this point, our filtered "best results" all have the same number of faults, so just grab the first one 120 | num_faults = next(best_results.itertuples()).count('fault') 121 | 122 | # num_ground_samples = len(best_results) 123 | # best_results = best_results.groupby(best_results.columns.tolist(), as_index=False).size().reset_index().set_index(0) 124 | best_results = best_results.drop_duplicates().reset_index(drop=True) 125 | num_ground_states = len(best_results) 126 | 127 | print("The minimum fault diagnosis found is {} faulty component(s)".format(num_faults)) 128 | # print("Out of {} samples, the following {} ground states were returned a total of {} times:".format( 129 | # NUM_READS, num_ground_states, num_ground_samples)) 130 | print("{} distinct fault state(s) with this many faults observed".format(num_ground_states)) 131 | 132 | # verbose output 133 | if len(sys.argv) == 2 and sys.argv[1] == '--verbose': 134 | pd.set_option('display.width', 120) 135 | print(best_results) 136 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dwave-ocean-sdk>=3.3.0 2 | pandas>=1.2.1,<3 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwave-examples/circuit-fault-diagnosis/612b2836473bdd29b4ba892a5a8d061e3966f737/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | import unittest 18 | from subprocess import Popen, PIPE ,STDOUT 19 | 20 | project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | 22 | 23 | class IntegrationTests(unittest.TestCase): 24 | @unittest.skipIf(os.getenv('SKIP_INT_TESTS'), "Skipping integration test.") 25 | def test_circuit_fault_diagnosis(self): 26 | demo_file = os.path.join(project_dir, 'demo.py') 27 | p = Popen([sys.executable, demo_file], 28 | stdout=PIPE, stdin=PIPE, stderr=STDOUT) 29 | p.stdin.write(b'3\n') 30 | p.stdin.write(b'5\n') 31 | p.stdin.write(b'45\n') 32 | output = p.communicate()[0] 33 | output = str(output).upper() 34 | 35 | if os.getenv('DEBUG_OUTPUT'): 36 | print("Example output \n" + output) 37 | 38 | with self.subTest(msg="Verify if output contains 'The minimum fault diagnosis found' \n"): 39 | self.assertIn("The minimum fault diagnosis found".upper(), output) 40 | with self.subTest(msg="Verify if error string contains in output \n"): 41 | self.assertNotIn("ERROR", output) 42 | with self.subTest(msg="Verify if warning string contains in output \n"): 43 | self.assertNotIn("WARNING", output) 44 | 45 | if __name__ == '__main__': 46 | unittest.main() 47 | --------------------------------------------------------------------------------