├── .python_version ├── .gitattributes ├── docs ├── Support.rst ├── Contributing.rst ├── _static │ ├── mqt_dark.png │ ├── encodings.png │ ├── mqt_light.png │ └── custom.css ├── References.rst ├── refs.bib ├── pathfinder │ ├── TSPLib.rst │ ├── GUI.rst │ ├── JSON.rst │ ├── index.rst │ ├── Encodings.rst │ └── Constraints.rst ├── Installation.rst ├── index.rst ├── QuboGenerator.rst ├── _templates │ └── page.html ├── Quickstart.ipynb ├── DevelopmentGuide.rst └── conf.py ├── tests ├── __init__.py ├── pathfinder │ ├── __init__.py │ ├── resources │ │ ├── tsplib │ │ │ ├── hcp-5.hcp │ │ │ ├── tsp-5.tsp │ │ │ ├── sop-5.sop │ │ │ ├── forced-edges.tsp │ │ │ ├── atsp-5.atsp │ │ │ └── fail │ │ │ │ └── cvrp-7.vrp │ │ └── json │ │ │ ├── fail │ │ │ ├── unknown_type.json │ │ │ ├── missing_field.json │ │ │ ├── too_few_elements.json │ │ │ └── excess_field.json │ │ │ ├── with_weight.json │ │ │ ├── alternative_options.json │ │ │ └── all.json │ ├── test_tsplib_input.py │ ├── test_pathfinder.py │ ├── test_json_input.py │ └── utils_test.py ├── test_graph.py ├── test_device.py └── test_circuit_generation.py ├── stubs └── sympy │ ├── core │ └── __init__.pyi │ └── __init__.pyi ├── src └── mqt │ └── qubomaker │ ├── py.typed │ ├── pathfinder │ ├── resources │ │ ├── constraints │ │ │ ├── path_maximize_length.json │ │ │ ├── path_minimize_length.json │ │ │ ├── path_is_valid.json │ │ │ ├── paths_share_no_edges.json │ │ │ ├── paths_share_no_vertices.json │ │ │ ├── path_ends_at.json │ │ │ ├── path_starts_at.json │ │ │ ├── path_position_is.json │ │ │ ├── path_contains_vertices_at_most_once.json │ │ │ ├── path_contains_vertices_exactly_once.json │ │ │ ├── path_contains_vertices_at_least_once.json │ │ │ ├── path_contains_edges_at_least_once.json │ │ │ ├── path_contains_edges_at_most_once.json │ │ │ ├── path_contains_edges_exactly_once.json │ │ │ └── path_vertex_precedence.json │ │ ├── input-format.json │ │ └── constraint.json │ ├── __init__.py │ ├── README.md │ └── tsplib.py │ ├── __init__.py │ ├── utils.py │ ├── graph.py │ └── device.py ├── .git_archival.txt ├── CITATION.bib ├── notebooks ├── input │ ├── tsp.json │ ├── graph │ ├── gr17.tsp │ ├── gr24.tsp │ └── u574.tsp ├── tsplib.ipynb └── tsp.ipynb ├── .github ├── codecov.yml ├── workflows │ ├── release-drafter.yml │ ├── cd.yml │ ├── ci.yml │ └── nextjs.yml ├── pull_request_template.md ├── support.rst ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml ├── renovate.json5 ├── release-drafter.yml └── contributing.rst ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── .pre-commit-config.yaml ├── noxfile.py ├── README.md └── pyproject.toml /.python_version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .git_archival.txt export-subst 2 | -------------------------------------------------------------------------------- /docs/Support.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../.github/support.rst 2 | -------------------------------------------------------------------------------- /docs/Contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../.github/contributing.rst 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """General tests for the qubomaker tool.""" 2 | -------------------------------------------------------------------------------- /stubs/sympy/core/__init__.pyi: -------------------------------------------------------------------------------- 1 | from sympy import Expr 2 | 3 | class Integer(Expr): ... 4 | -------------------------------------------------------------------------------- /docs/_static/mqt_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cda-tum/mqt-qubomaker/HEAD/docs/_static/mqt_dark.png -------------------------------------------------------------------------------- /docs/_static/encodings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cda-tum/mqt-qubomaker/HEAD/docs/_static/encodings.png -------------------------------------------------------------------------------- /docs/_static/mqt_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cda-tum/mqt-qubomaker/HEAD/docs/_static/mqt_light.png -------------------------------------------------------------------------------- /tests/pathfinder/__init__.py: -------------------------------------------------------------------------------- 1 | """A set of tests for the `pathfinder` module of the `QuboMaker` package.""" 2 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/py.typed: -------------------------------------------------------------------------------- 1 | # This file is used to indicate that the directory contains type hints for Python. 2 | # It does not contain any actual code. 3 | -------------------------------------------------------------------------------- /.git_archival.txt: -------------------------------------------------------------------------------- 1 | node: a240d058cf288b580be4ce3b85e81894993e6988 2 | node-date: 2025-10-27T14:07:36+01:00 3 | describe-name: v1.0.1-75-ga240d05 4 | ref-names: HEAD -> main 5 | -------------------------------------------------------------------------------- /docs/References.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | If you use *MQT QUBOMaker* in your work, we would appreciate if you cited :cite:labelpar:`rovara2024pathfindingframework`. 5 | 6 | .. bibliography:: 7 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/hcp-5.hcp: -------------------------------------------------------------------------------- 1 | NAME: hcp-test-5 2 | TYPE: HCP 3 | COMMENT: 5 cities 4 | DIMENSION: 5 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 7 | EDGE_WEIGHT_SECTION 8 | 0 5 4 3 4 9 | 5 0 2 3 5 10 | 4 2 0 6 2 11 | 3 3 6 0 4 12 | 4 5 2 4 0 13 | EOF 14 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/tsp-5.tsp: -------------------------------------------------------------------------------- 1 | NAME: tsp-test-5 2 | TYPE: TSP 3 | COMMENT: 5 cities 4 | DIMENSION: 5 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 7 | EDGE_WEIGHT_SECTION 8 | 0 5 4 3 4 9 | 5 0 2 3 5 10 | 4 2 0 6 2 11 | 3 3 6 0 4 12 | 4 5 2 4 0 13 | EOF 14 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/sop-5.sop: -------------------------------------------------------------------------------- 1 | NAME: sop-test-5 2 | TYPE: SOP 3 | COMMENT: 5 cities 4 | DIMENSION: 5 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 7 | EDGE_WEIGHT_SECTION 8 | 0 5 4 3 4 9 | -1 0 2 3 5 10 | 4 2 0 6 2 11 | 3 -1 6 0 4 12 | -1 -1 2 4 0 13 | EOF 14 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @MISC{rovara2024pathfindingframework, 2 | AUTHOR = {Damian Rovara and Nils Quetschlich and Robert Wille}, 3 | TITLE = {A Framework to Formulate Pathfinding Problems for Quantum Computing}, 4 | YEAR = {2024}, 5 | EPRINT = {2404.10820}, 6 | EPRINTTYPE = {arxiv}, 7 | } 8 | -------------------------------------------------------------------------------- /docs/refs.bib: -------------------------------------------------------------------------------- 1 | @MISC{rovara2024pathfindingframework, 2 | AUTHOR = {Damian Rovara and Nils Quetschlich and Robert Wille}, 3 | TITLE = {A Framework to Formulate Pathfinding Problems for Quantum Computing}, 4 | YEAR = {2024}, 5 | EPRINT = {2404.10820}, 6 | EPRINTTYPE = {arxiv}, 7 | } 8 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/forced-edges.tsp: -------------------------------------------------------------------------------- 1 | NAME: tsp-test-5 2 | TYPE: TSP 3 | COMMENT: 5 cities 4 | DIMENSION: 5 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 7 | FIXED_EDGES_SECTION 8 | 0 4 9 | 4 2 10 | -1 11 | EDGE_WEIGHT_SECTION 12 | 0 5 4 3 4 13 | 5 0 2 3 5 14 | 4 2 0 6 2 15 | 3 3 6 0 4 16 | 4 5 2 4 0 17 | EOF 18 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/fail/unknown_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 2, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "ThisConstraintDoesNotExist" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/fail/missing_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 2, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "PathPositionIs", 14 | "position": 1 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/fail/too_few_elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 2, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "PathsShareNoVertices", 14 | "path_ids": [1] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/with_weight.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "UNARY", 4 | "n_paths": 1, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "objective_function": { 9 | "type": "MaximizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "PathContainsVerticesExactlyOnce", 14 | "weight": 500 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /notebooks/input/tsp.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 1, 5 | "max_path_length": 4, 6 | "loops": true 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "PathIsValid" 14 | }, 15 | { 16 | "type": "PathContainsVerticesExactlyOnce" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /notebooks/input/graph: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 4.000000000000000000e+00 2.000000000000000000e+00 0.000000000000000000e+00 2 | 3.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 2.000000000000000000e+00 3 | 2.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 5.000000000000000000e+00 4 | 2.000000000000000000e+00 0.000000000000000000e+00 5.000000000000000000e+00 0.000000000000000000e+00 5 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/atsp-5.atsp: -------------------------------------------------------------------------------- 1 | NAME: atsp-test-5 2 | TYPE: ATSP 3 | COMMENT: 5 cities 4 | DIMENSION: 5 5 | EDGE_DATA_FORMAT: ADJ_LIST 6 | EDGE_WEIGHT_TYPE: EXPLICIT 7 | EDGE_WEIGHT_FORMAT: FULL_MATRIX 8 | EDGE_DATA_SECTION 9 | 1 2 3 5 -1 10 | 2 1 3 4 5 -1 11 | 3 4 5 -1 12 | 4 1 2 5 -1 13 | 5 1 2 3 4 -1 14 | -1 15 | EDGE_WEIGHT_SECTION 16 | 0 5 4 0 4 17 | 3 0 2 3 5 18 | 0 0 0 6 2 19 | 2 3 0 0 4 20 | 2 2 3 1 0 21 | EOF 22 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_maximize_length.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "MaximizePathLength" 7 | }, 8 | "path_ids": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [1] 14 | } 15 | }, 16 | "required": ["type"], 17 | "additionalProperties": false 18 | } 19 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_minimize_length.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "MinimizePathLength" 7 | }, 8 | "path_ids": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [1] 14 | } 15 | }, 16 | "required": ["type"], 17 | "additionalProperties": false 18 | } 19 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/fail/excess_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 2, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength" 10 | }, 11 | "constraints": [ 12 | { 13 | "type": "PathPositionIs", 14 | "position": 1, 15 | "vertices": [1], 16 | "thisFieldDoesNotExist": 1 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_is_valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathIsValid" 7 | }, 8 | "path_ids": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [1] 14 | }, 15 | "weight": { 16 | "type": "number", 17 | "default": null 18 | } 19 | }, 20 | "required": ["type"], 21 | "additionalProperties": false 22 | } 23 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/tsplib/fail/cvrp-7.vrp: -------------------------------------------------------------------------------- 1 | NAME : eil7 2 | COMMENT : (Eilon et al.) 3 | TYPE : CVRP 4 | DIMENSION : 7 5 | EDGE_WEIGHT_TYPE : EXPLICIT 6 | EDGE_WEIGHT_FORMAT: LOWER_COL 7 | DISPLAY_DATA_TYPE: NO_DISPLAY 8 | CAPACITY : 3 9 | EDGE_WEIGHT_SECTION 10 | 10 20 25 25 20 10 12 20 25 30 11 | 20 10 11 22 30 2 11 25 10 20 12 | 12 13 | DEMAND_SECTION 14 | 1 0 15 | 2 1 16 | 3 1 17 | 4 1 18 | 5 1 19 | 6 1 20 | 7 1 21 | DEPOT_SECTION 22 | 1 23 | -1 24 | EOF 25 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/paths_share_no_edges.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathsShareNoEdges" 7 | }, 8 | "path_ids": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "minItems": 2 14 | }, 15 | "weight": { 16 | "type": "number", 17 | "default": null 18 | } 19 | }, 20 | "required": ["type", "path_ids"], 21 | "additionalProperties": false 22 | } 23 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/paths_share_no_vertices.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathsShareNoVertices" 7 | }, 8 | "path_ids": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "minItems": 2 14 | }, 15 | "weight": { 16 | "type": "number", 17 | "default": null 18 | } 19 | }, 20 | "required": ["type", "path_ids"], 21 | "additionalProperties": false 22 | } 23 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "notebooks/*" 3 | - "docs/*" 4 | 5 | component_management: 6 | individual_components: 7 | - component_id: qubo-pathfinder 8 | name: pathfinder 9 | paths: 10 | - src/mqt/qubomaker/pathfinder 11 | - component_id: qubo-maker 12 | name: qubomaker 13 | paths: 14 | - src/mqt/qubomaker 15 | 16 | coverage: 17 | range: 60..90 18 | precision: 1 19 | status: 20 | project: 21 | default: 22 | threshold: 0.5% 23 | patch: 24 | default: 25 | threshold: 1% 26 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | pull_request_target: 10 | types: [opened, reopened, synchronize] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | update_release_draft: 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: release-drafter/release-drafter@v6 23 | env: 24 | GITHUB_TOKEN: ${{ github.token }} 25 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_ends_at.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathEndsAt" 7 | }, 8 | "vertices": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | } 13 | }, 14 | "path_id": { 15 | "type": "integer", 16 | "default": 1 17 | }, 18 | "weight": { 19 | "type": "number", 20 | "default": null 21 | } 22 | }, 23 | "required": ["vertices", "type"], 24 | "additionalProperties": false 25 | } 26 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_starts_at.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathStartsAt" 7 | }, 8 | "vertices": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | } 13 | }, 14 | "path_id": { 15 | "type": "integer", 16 | "default": 1 17 | }, 18 | "weight": { 19 | "type": "number", 20 | "default": null 21 | } 22 | }, 23 | "required": ["type", "vertices"], 24 | "additionalProperties": false 25 | } 26 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_position_is.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathPositionIs" 7 | }, 8 | "position": { "type": "integer" }, 9 | "vertices": { 10 | "type": "array", 11 | "items": { 12 | "type": "integer" 13 | } 14 | }, 15 | "path_id": { 16 | "type": "integer", 17 | "default": 1 18 | }, 19 | "weight": { 20 | "type": "number", 21 | "default": null 22 | } 23 | }, 24 | "required": ["type", "position", "vertices"], 25 | "additionalProperties": false 26 | } 27 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_vertices_at_most_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsVerticesAtMostOnce" 7 | }, 8 | "vertices": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [] 14 | }, 15 | "path_ids": { 16 | "type": "array", 17 | "items": { 18 | "type": "integer" 19 | }, 20 | "default": [1] 21 | }, 22 | "weight": { 23 | "type": "number", 24 | "default": null 25 | } 26 | }, 27 | "required": ["type"], 28 | "additionalProperties": false 29 | } 30 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_vertices_exactly_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsVerticesExactlyOnce" 7 | }, 8 | "vertices": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [] 14 | }, 15 | "path_ids": { 16 | "type": "array", 17 | "items": { 18 | "type": "integer" 19 | }, 20 | "default": [1] 21 | }, 22 | "weight": { 23 | "type": "number", 24 | "default": null 25 | } 26 | }, 27 | "required": ["type"], 28 | "additionalProperties": false 29 | } 30 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_vertices_at_least_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsVerticesAtLeastOnce" 7 | }, 8 | "vertices": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | }, 13 | "default": [] 14 | }, 15 | "path_ids": { 16 | "type": "array", 17 | "items": { 18 | "type": "integer" 19 | }, 20 | "default": [1] 21 | }, 22 | "weight": { 23 | "type": "number", 24 | "default": null 25 | } 26 | }, 27 | "required": ["type"], 28 | "additionalProperties": false 29 | } 30 | -------------------------------------------------------------------------------- /docs/pathfinder/TSPLib.rst: -------------------------------------------------------------------------------- 1 | TSPLib Input 2 | ============ 3 | 4 | `TSPLib `_ provides a library of sample 5 | instances for different pathfinding problems, as well as a format for defining them. 6 | 7 | This module provides ways of reading TSPLib input files using the `tsplib95 python library `_ and converting them into a QuboGenerator with 8 | the corresponding constraints. 9 | 10 | .. code-block:: python 11 | 12 | from mqt.qubomaker.pathfinder import EncodingType, from_tsplib_problem 13 | import tsplib95 14 | 15 | problem = tsplib95.load("tsp_lib_file.tsp") 16 | 17 | generator = from_tsplib_problem(problem, EncodingType.ONE_HOT) 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | */__pycache__ 3 | .vscode 4 | .ruff_cache 5 | *.egg-info/ 6 | build 7 | .mypy_cache 8 | /local-tests 9 | .pytest_cache 10 | /dist/* 11 | /docs/_build/* 12 | 13 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 14 | 15 | # dependencies 16 | /node_modules 17 | /.pnp 18 | .pnp.js 19 | .yarn/install-state.gz 20 | 21 | # testing 22 | /coverage 23 | 24 | # next.js 25 | /.next/ 26 | /out/ 27 | 28 | # production 29 | /build 30 | 31 | # misc 32 | .DS_Store 33 | *.pem 34 | 35 | # debug 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # local env files 41 | .env*.local 42 | 43 | # vercel 44 | .vercel 45 | 46 | # typescript 47 | *.tsbuildinfo 48 | next-env.d.ts 49 | 50 | experiments 51 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/alternative_options.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "BINARY", 4 | "n_paths": 2, 5 | "max_path_length": 5, 6 | "loops": false 7 | }, 8 | "constraints": [ 9 | { 10 | "type": "PathContainsVerticesExactlyOnce" 11 | }, 12 | { 13 | "type": "PathContainsVerticesAtLeastOnce", 14 | "vertices": [1, 2] 15 | }, 16 | { 17 | "type": "PathContainsVerticesAtMostOnce", 18 | "path_ids": [1, 2] 19 | }, 20 | { 21 | "type": "PrecedenceConstraint", 22 | "precedences": [ 23 | { 24 | "before": 1, 25 | "after": 2 26 | }, 27 | { 28 | "before": 2, 29 | "after": 3 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes #(issue) 6 | 7 | ## Checklist: 8 | 9 | 12 | 13 | - [ ] The pull request only contains commits that are related to it. 14 | - [ ] I have added appropriate tests and documentation. 15 | - [ ] I have made sure that all CI jobs on GitHub pass. 16 | - [ ] The pull request introduces no new warnings and follows the project's style guidelines. 17 | -------------------------------------------------------------------------------- /notebooks/input/gr17.tsp: -------------------------------------------------------------------------------- 1 | NAME: gr17 2 | TYPE: TSP 3 | COMMENT: 17-city problem (Groetschel) 4 | DIMENSION: 17 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW 7 | EDGE_WEIGHT_SECTION 8 | 0 633 0 257 390 0 91 661 228 0 412 227 9 | 169 383 0 150 488 112 120 267 0 80 572 196 10 | 77 351 63 0 134 530 154 105 309 34 29 0 11 | 259 555 372 175 338 264 232 249 0 505 289 262 12 | 476 196 360 444 402 495 0 353 282 110 324 61 13 | 208 292 250 352 154 0 324 638 437 240 421 329 14 | 297 314 95 578 435 0 70 567 191 27 346 83 15 | 47 68 189 439 287 254 0 211 466 74 182 243 16 | 105 150 108 326 336 184 391 145 0 268 420 53 17 | 239 199 123 207 165 383 240 140 448 202 57 0 18 | 246 745 472 237 528 364 332 349 202 685 542 157 19 | 289 426 483 0 121 518 142 84 297 35 29 36 20 | 236 390 238 301 55 96 153 336 0 21 | EOF 22 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_edges_at_least_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsEdgesAtLeastOnce" 7 | }, 8 | "edges": { 9 | "type": "array", 10 | "items": { 11 | "type": "array", 12 | "items": { 13 | "type": "integer" 14 | }, 15 | "minItems": 2, 16 | "maxItems": 2 17 | }, 18 | "default": [] 19 | }, 20 | "path_ids": { 21 | "type": "array", 22 | "items": { 23 | "type": "integer" 24 | }, 25 | "default": [1] 26 | }, 27 | "weight": { 28 | "type": "number", 29 | "default": null 30 | } 31 | }, 32 | "required": ["type"], 33 | "additionalProperties": false 34 | } 35 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_edges_at_most_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsEdgesAtMostOnce" 7 | }, 8 | "edges": { 9 | "type": "array", 10 | "items": { 11 | "type": "array", 12 | "items": { 13 | "type": "integer" 14 | }, 15 | "minItems": 2, 16 | "maxItems": 2 17 | }, 18 | "default": [] 19 | }, 20 | "path_ids": { 21 | "type": "array", 22 | "items": { 23 | "type": "integer" 24 | }, 25 | "default": [1] 26 | }, 27 | "weight": { 28 | "type": "number", 29 | "default": null 30 | } 31 | }, 32 | "required": ["type"], 33 | "additionalProperties": false 34 | } 35 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_contains_edges_exactly_once.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PathContainsEdgesExactlyOnce" 7 | }, 8 | "edges": { 9 | "type": "array", 10 | "items": { 11 | "type": "array", 12 | "items": { 13 | "type": "integer" 14 | }, 15 | "minItems": 2, 16 | "maxItems": 2 17 | }, 18 | "default": [] 19 | }, 20 | "path_ids": { 21 | "type": "array", 22 | "items": { 23 | "type": "integer" 24 | }, 25 | "default": [1] 26 | }, 27 | "weight": { 28 | "type": "number", 29 | "default": null 30 | } 31 | }, 32 | "required": ["type"], 33 | "additionalProperties": false 34 | } 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | submodules: 4 | include: all 5 | recursive: true 6 | 7 | build: 8 | os: ubuntu-22.04 9 | tools: 10 | python: "3.11" 11 | jobs: 12 | post_checkout: 13 | # Skip docs build if the commit message contains "skip ci" 14 | - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 15 | # Skip docs build if there are no changes related to docs 16 | - | 17 | if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ .readthedocs.yaml src/mqt/ .github/contributing* .github/workflows/support*; 18 | then 19 | exit 183; 20 | fi 21 | 22 | sphinx: 23 | configuration: docs/conf.py 24 | 25 | python: 26 | install: 27 | - method: pip 28 | path: . 29 | extra_requirements: 30 | - docs 31 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/input-format.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "settings": { 5 | "type": "object", 6 | "properties": { 7 | "encoding": { 8 | "type": "string", 9 | "enum": ["ONE_HOT", "UNARY", "DOMAIN_WALL", "BINARY"] 10 | }, 11 | "n_paths": { "type": "integer", "default": 1 }, 12 | "max_path_length": { "type": "integer", "default": 0 }, 13 | "loops": { "type": "boolean", "default": false } 14 | }, 15 | "required": ["encoding"], 16 | "additionalProperties": false 17 | }, 18 | "objective_function": { "$ref": "constraint.json" }, 19 | "constraints": { 20 | "type": "array", 21 | "items": { "$ref": "constraint.json" } 22 | } 23 | }, 24 | "required": ["settings"], 25 | "additionalProperties": false 26 | } 27 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraint.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { "$ref": "path_is_valid.json" }, 4 | { "$ref": "path_position_is.json" }, 5 | { "$ref": "path_starts_at.json" }, 6 | { "$ref": "path_ends_at.json" }, 7 | { "$ref": "path_contains_edges_at_least_once.json" }, 8 | { "$ref": "path_contains_edges_at_most_once.json" }, 9 | { "$ref": "path_contains_edges_exactly_once.json" }, 10 | { "$ref": "path_contains_vertices_at_least_once.json" }, 11 | { "$ref": "path_contains_vertices_at_most_once.json" }, 12 | { "$ref": "path_contains_vertices_exactly_once.json" }, 13 | { "$ref": "paths_share_no_vertices.json" }, 14 | { "$ref": "paths_share_no_edges.json" }, 15 | { "$ref": "path_vertex_precedence.json" }, 16 | { "$ref": "path_minimize_length.json" }, 17 | { "$ref": "path_maximize_length.json" } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /docs/pathfinder/GUI.rst: -------------------------------------------------------------------------------- 1 | Graphical User Interface 2 | ======================== 3 | 4 | A web-based GUI for the Pathfinder submodule can be accessed through `GitHub pages `_. 5 | The GUI is a simple interface that allows users to define their problem graph through its adjacency matrix, 6 | and then select a set of constraints to define a problem instance. 7 | 8 | The GUI supports all main constraint types implemented for the *Pathfinder* submodule. However, specific 9 | constraint-specific settings (such as applying "*path contains*"-constraints just to single paths) are not always supported. 10 | 11 | Once the constraints have been chosen and a desired encoding is selected, the "generate" button can be used to generate a JSON representation of 12 | the problem instance. This can then be used as input for the *Pathfinder* submodule, as defined in the :doc:`JSON documentation `. 13 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/__init__.py: -------------------------------------------------------------------------------- 1 | """A package for generating QUBO formulations automatically from a set of constraints QUBOs for different problem classes. 2 | 3 | It allows users to create a `QuboGenerator` object, and gradually add penalty terms and constraints to it. 4 | When done, the object can be used to construct a QUBO formulation of the project on multiple granularity levels. 5 | 6 | ## Available Subpackages 7 | - `pathfinder`: This module implements the pathfinding functionalities of the QuboMaker. 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | from . import pathfinder 13 | from .device import Calibration 14 | from .graph import Graph 15 | from .qubo_generator import QuboGenerator 16 | from .utils import optimize_classically, print_matrix 17 | 18 | __all__ = [ 19 | "Calibration", 20 | "Graph", 21 | "QuboGenerator", 22 | "optimize_classically", 23 | "pathfinder", 24 | "print_matrix", 25 | ] 26 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/resources/constraints/path_vertex_precedence.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "type": { 5 | "type": "string", 6 | "const": "PrecedenceConstraint" 7 | }, 8 | "precedences": { 9 | "type": "array", 10 | "items": { 11 | "type": "object", 12 | "properties": { 13 | "before": { "type": "integer" }, 14 | "after": { "type": "integer" } 15 | }, 16 | "required": ["before", "after"], 17 | "additionalProperties": false 18 | }, 19 | "minItems": 1 20 | }, 21 | "path_ids": { 22 | "type": "array", 23 | "items": { 24 | "type": "integer" 25 | }, 26 | "default": [1] 27 | }, 28 | "weight": { 29 | "type": "number", 30 | "default": null 31 | } 32 | }, 33 | "required": ["type", "precedences"], 34 | "additionalProperties": false 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | pull_request: 7 | paths: 8 | - .github/workflows/cd.yml 9 | 10 | jobs: 11 | python-packaging: 12 | name: 🐍 Packaging 13 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging.yml@v1.10 14 | with: 15 | pure-python: true 16 | 17 | deploy: 18 | if: github.event_name == 'release' && github.event.action == 'published' 19 | name: 🚀 Deploy to PyPI 20 | runs-on: ubuntu-latest 21 | environment: 22 | name: pypi 23 | url: https://pypi.org/p/mqt.qubomaker 24 | permissions: 25 | id-token: write 26 | needs: [python-packaging] 27 | steps: 28 | - uses: actions/download-artifact@v4 29 | with: 30 | pattern: cibw-* 31 | path: dist 32 | merge-multiple: true 33 | - uses: pypa/gh-action-pypi-publish@release/v1 34 | -------------------------------------------------------------------------------- /.github/support.rst: -------------------------------------------------------------------------------- 1 | Support 2 | ======= 3 | 4 | If you are stuck with a problem using MQT QUBOMaker or have questions, please do get in touch at our `Issues `_ or `Discussions `_. We'd love to help. 5 | 6 | You can save time by following this procedure when reporting a problem: 7 | 8 | - Do try to solve the problem on your own first. Make sure to consult the `Documentation `_. 9 | - Search through past `Issues `_ to see if someone else already had the same problem. 10 | - Before filing a bug report, try to create a minimal working example (MWE) that reproduces the problem. It's much easier to identify the cause for the problem if a handful of lines suffice to show that something isn't working. 11 | 12 | You can also always reach us at `quantum.cda@xcit.tum.de `_. 13 | -------------------------------------------------------------------------------- /docs/Installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ================ 3 | 4 | Installation via pip package 5 | ---------------------------- 6 | 7 | MQT QUBOMaker is available via `PyPI `_ 8 | 9 | .. code-block:: console 10 | 11 | (venv) $ pip install mqt.qubomaker 12 | 13 | After installation, the package can be imported: 14 | 15 | .. code-block:: python 16 | 17 | import mqt.qubomaker as qm 18 | 19 | Similarly, the :code:`pathfinder` submodule can be imported as: 20 | 21 | .. code-block:: python 22 | 23 | import mqt.qubomaker.pathfinder as pf 24 | 25 | Installation via source code 26 | ---------------------------- 27 | 28 | The source code for MQT QUBOMaker is available on GitHub and can be cloned and used locally: 29 | 30 | .. code-block:: console 31 | 32 | (venv) $ git clone https://github.com/cda-tum/mqt-qubomaker 33 | (venv) $ cd mqt-qubomaker 34 | (venv) $ pip install . 35 | 36 | After installing the package, it can be imported as described above. 37 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .acknowledgements { 2 | margin-top: 1rem; 3 | padding-bottom: 1rem; 4 | padding-top: 1rem; 5 | border-top: 1px solid var(--color-background-border); 6 | font-size: var(--font-size--small); 7 | color: var(--color-foreground-secondary); 8 | } 9 | 10 | .acknowledgements-logos { 11 | display: grid; 12 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 13 | grid-gap: 1em; 14 | align-items: center; 15 | margin-top: 0.5rem; 16 | } 17 | .acknowledgement { 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | /* override the default background color for literal strings */ 25 | body:not([data-theme="light"]) .highlight .sa, 26 | .highlight .sb, 27 | .highlight .sc, 28 | .highlight .dl, 29 | .highlight .sd, 30 | .highlight .s2, 31 | .highlight .se, 32 | .highlight .sh, 33 | .highlight .si, 34 | .highlight .sx, 35 | .highlight .sr, 36 | .highlight .s1, 37 | .highlight .ss, 38 | .highlight .s1 { 39 | background-color: #00000001; 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest an idea 3 | title: "✨ " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | **Thank you for wanting to suggest a feature for this project!** 9 | 10 | ⚠ 11 | Verify first that your idea is not [already requested on GitHub](https://github.com/cda-tum/mqt-qubomaker/search?q=is%3Aissue&type=issues). 12 | 13 | - type: textarea 14 | attributes: 15 | label: What's the problem this feature will solve? 16 | description: >- 17 | What are you trying to do, that you are unable to achieve as it currently stands? 18 | placeholder: >- 19 | I'm trying to do X and I'm missing feature Y for this to be 20 | easily achievable. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: Describe the solution you'd like 27 | description: > 28 | Clear and concise description of what you want to happen. 29 | placeholder: >- 30 | When I do X, I want to achieve Y in a situation when Z. 31 | validations: 32 | required: true 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Damian Rovara, Nils Quetschlich, Lukas Burgholzer, and Robert Wille 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: ["config:recommended", ":gitSignOff"], 4 | prHourlyLimit: 10, 5 | enabledManagers: ["github-actions", "pre-commit", "pep621"], 6 | "pre-commit": { 7 | enabled: true 8 | }, 9 | lockFileMaintenance: { 10 | "enabled": true, 11 | // "automerge": true, disabled due to endless update loops caused by setuptools_scm 12 | }, 13 | configMigration: true, 14 | labels: ["dependencies"], 15 | schedule: ["every weekend"], 16 | packageRules: [ 17 | { 18 | matchManagers: ["github-actions"], 19 | addLabels: ["github-actions"], 20 | commitMessagePrefix: "chore(github-actions): ⬆\uFE0F\uD83D\uDC68\u200D\uD83D\uDCBB" 21 | }, 22 | { 23 | matchManagers: ["pep621"], 24 | addLabels: ["python"], 25 | commitMessagePrefix: "chore(python-deps): ⬆\uFE0F\uD83D\uDC0D" 26 | }, 27 | { 28 | matchManagers: ["pre-commit"], 29 | addLabels: ["pre-commit"], 30 | commitMessagePrefix: "chore(pre-commit): ⬆\uFE0F\uD83E\uDE9D", 31 | }, 32 | { 33 | "description": "Automerge patch updates", 34 | "matchUpdateTypes": ["patch"], 35 | "automerge": true 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /docs/pathfinder/JSON.rst: -------------------------------------------------------------------------------- 1 | JSON Encoding 2 | ============= 3 | 4 | Instead of using the programming interface for creating a QuboGenerator, the *Pathfinder* submodule also supports a JSON format. 5 | 6 | With a given input file in JSON format, the QuboGenerator can be created using the following code: 7 | 8 | .. code-block:: python 9 | 10 | with Path.open("input.json") as file: 11 | generator_new = pf.PathFindingQuboGenerator.from_json(file.read(), graph) 12 | 13 | The JSON input contains the definitions of problem constraints to be used for the QUBO formulation, as well as general settings such as the desired encoding choice. 14 | 15 | The format for the JSON input is defined as: 16 | 17 | .. code-block:: 18 | 19 | { 20 | "settings": { 21 | "encoding": one of ["ONE_HOT", "UNARY", "DOMAIN_WALL", "BINARY"], 22 | "n_paths": integer, 23 | "max_path_length": integer, 24 | "loops": boolean, 25 | }, 26 | "objective_function": Constraint, 27 | "constraints": array[Constraint] 28 | } 29 | 30 | Individual constraints are defined based on their JSON definitions provided `here <https://github.com/cda-tum/mqt-qubomaker/tree/main/src/mqt/qubomaker/pathfinder/resources/constraints>`_. 31 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "MQT QUBOMaker $RESOLVED_VERSION Release" 2 | tag-template: "v$RESOLVED_VERSION" 3 | categories: 4 | - title: "🚀 Features and Enhancements" 5 | labels: 6 | - "feature" 7 | - "enhancement" 8 | - "usability" 9 | - "refactor" 10 | - title: "🐛 Bug Fixes" 11 | labels: 12 | - "bug" 13 | - "fix" 14 | - title: "📄 Documentation" 15 | labels: 16 | - "documentation" 17 | - title: "📦 Packaging" 18 | labels: 19 | - "packaging" 20 | - title: "🧹 Code Quality" 21 | labels: 22 | - "code quality" 23 | - title: "🤖 CI" 24 | labels: 25 | - "continuous integration" 26 | - title: "⬆️ Dependencies" 27 | collapse-after: 5 28 | labels: 29 | - "dependencies" 30 | - "submodules" 31 | - "github_actions" 32 | - "pre-commit" 33 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)" 34 | change-title-escapes: '\<*_&' 35 | version-resolver: 36 | major: 37 | labels: 38 | - "major" 39 | minor: 40 | labels: 41 | - "minor" 42 | patch: 43 | labels: 44 | - "patch" 45 | default: patch 46 | 47 | template: | 48 | ## 👀 What Changed 49 | 50 | $CHANGES 51 | 52 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 53 | -------------------------------------------------------------------------------- /notebooks/input/gr24.tsp: -------------------------------------------------------------------------------- 1 | NAME: gr24 2 | TYPE: TSP 3 | COMMENT: 24-city problem (Groetschel) 4 | DIMENSION: 24 5 | EDGE_WEIGHT_TYPE: EXPLICIT 6 | EDGE_WEIGHT_FORMAT: LOWER_DIAG_ROW 7 | EDGE_WEIGHT_SECTION 8 | 0 257 0 187 196 0 91 228 158 0 150 112 9 | 96 120 0 80 196 88 77 63 0 130 167 59 10 | 101 56 25 0 134 154 63 105 34 29 22 0 11 | 243 209 286 159 190 216 229 225 0 185 86 124 12 | 156 40 124 95 82 207 0 214 223 49 185 123 13 | 115 86 90 313 151 0 70 191 121 27 83 47 14 | 64 68 173 119 148 0 272 180 315 188 193 245 15 | 258 228 29 159 342 209 0 219 83 172 149 79 16 | 139 134 112 126 62 199 153 97 0 293 50 232 17 | 264 148 232 203 190 248 122 259 227 219 134 0 18 | 54 219 92 82 119 31 43 58 238 147 84 53 19 | 267 170 255 0 211 74 81 182 105 150 121 108 20 | 310 37 160 145 196 99 125 173 0 290 139 98 21 | 261 144 176 164 136 389 116 147 224 275 178 154 22 | 190 79 0 268 53 138 239 123 207 178 165 367 23 | 86 187 202 227 130 68 230 57 86 0 261 43 24 | 200 232 98 200 171 131 166 90 227 195 137 69 25 | 82 223 90 176 90 0 175 128 76 146 32 76 26 | 47 30 222 56 103 109 225 104 164 99 57 112 27 | 114 134 0 250 99 89 221 105 189 160 147 349 28 | 76 138 184 235 138 114 212 39 40 46 136 96 29 | 0 192 228 235 108 119 165 178 154 71 136 262 30 | 110 74 96 264 187 182 261 239 165 151 221 0 31 | 121 142 99 84 35 29 42 36 220 70 126 55 32 | 249 104 178 60 96 175 153 146 47 135 169 0 33 | EOF 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Something is not working correctly. 3 | title: "🐛 <title>" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: >- 8 | **Thank you for wanting to report a bug for this project!** 9 | 10 | ⚠ 11 | Verify first that your issue is not [already reported on GitHub](https://github.com/cda-tum/mqt-qubomaker/search?q=is%3Aissue&type=issues). 12 | 13 | If you have general questions, please consider [starting a discussion](https://github.com/cda-tum/mqt-qubomaker/discussions). 14 | - type: textarea 15 | attributes: 16 | label: Environment information 17 | description: >- 18 | Please provide information about your environment. For example, OS, Python version, mqt.qubomaker version etc. 19 | placeholder: | 20 | - OS: 21 | - Python version: 22 | - mqt.qubomaker version: 23 | - Additional environment information: 24 | validations: 25 | required: true 26 | - type: textarea 27 | attributes: 28 | label: Description 29 | description: A clear and concise description of what the bug is. 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Expected behavior 35 | description: A clear and concise description of what you expected to happen. 36 | - type: textarea 37 | attributes: 38 | label: How to Reproduce 39 | description: Please provide steps to reproduce this bug. 40 | placeholder: | 41 | 1. Get package from '...' 42 | 2. Then run '...' 43 | 3. An error occurs. 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | merge_group: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | change-detection: 16 | name: 🔍 Change 17 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@v1.10 18 | 19 | python-tests: 20 | name: 🐍 Test 21 | needs: change-detection 22 | permissions: 23 | id-token: write 24 | contents: read 25 | if: fromJSON(needs.change-detection.outputs.run-python-tests) 26 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-ci.yml@v1.10 27 | 28 | code-ql: 29 | name: 📝 CodeQL 30 | needs: change-detection 31 | if: fromJSON(needs.change-detection.outputs.run-code-ql) 32 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-code-ql-python.yml@v1.10 33 | 34 | required-checks-pass: # This job does nothing and is only used for branch protection 35 | name: 🚦 Check 36 | if: always() 37 | needs: 38 | - change-detection 39 | - python-tests 40 | - code-ql 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Decide whether the needed jobs succeeded or failed 44 | uses: re-actors/alls-green@release/v1 45 | with: 46 | allowed-skips: >- 47 | ${{ 48 | fromJSON(needs.change-detection.outputs.run-python-tests) 49 | && '' || 'python-tests,' 50 | }} 51 | ${{ 52 | fromJSON(needs.change-detection.outputs.run-code-ql) 53 | && '' || 'code-ql,' 54 | }} 55 | jobs: ${{ toJSON(needs) }} 56 | -------------------------------------------------------------------------------- /stubs/sympy/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import typing_extensions 4 | 5 | from . import core # noqa: F401 6 | 7 | AnyExpression: typing_extensions.TypeAlias = Basic | float | object 8 | 9 | class Basic: 10 | def __mul__(self, other: AnyExpression) -> Expr: ... 11 | def __add__(self, other: AnyExpression) -> Expr: ... 12 | def __radd__(self, other: AnyExpression) -> Expr: ... 13 | def __rmul__(self, other: AnyExpression) -> Expr: ... 14 | def __sub__(self, other: AnyExpression) -> Expr: ... 15 | def __rsub__(self, other: AnyExpression) -> Expr: ... 16 | def __truediv__(self, other: AnyExpression) -> Expr: ... 17 | def __rtruediv__(self, other: AnyExpression) -> Expr: ... 18 | def __pow__(self, other: AnyExpression) -> Expr: ... 19 | def __rpow__(self, other: AnyExpression) -> Expr: ... 20 | def subs(self, substitution: dict[Expr, Any] | Expr, replacement: AnyExpression = None) -> Expr: ... 21 | def __int__(self) -> int: ... 22 | def _latex(self, printer: StrPrinter, *args: Any, **kwargs: Any) -> str: ... # noqa: ANN401 23 | 24 | class Expr(Basic): 25 | args: tuple[Expr, ...] | Any 26 | def expand(self) -> Expr: ... 27 | def as_coefficients_dict(self) -> dict[Any, int]: ... 28 | def doit(self, **hints: AnyExpression) -> Expr: ... 29 | def evalf(self) -> float: ... 30 | def __init__(self, *args: AnyExpression) -> None: ... 31 | 32 | class Mul(Expr): ... 33 | class Sum(Expr): ... 34 | class Pow(Expr): ... 35 | class Product(Expr): ... 36 | 37 | class Integer(Expr): 38 | def __init__(self, value: int) -> None: ... 39 | 40 | class Symbol(Expr): 41 | def __init__(self, name: str) -> None: ... 42 | 43 | class Function(Expr): ... 44 | 45 | class StrPrinter: 46 | def doprint(self, expr: Expr) -> str: ... 47 | -------------------------------------------------------------------------------- /notebooks/tsplib.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# TSPLIB" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "The TSPLib input format is an established format for the specification of pathfinding problems.\n", 15 | "\n", 16 | "A `PathFindingQuboGenerator` can be created automatically from a given TSPLib specification." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from __future__ import annotations\n", 26 | "\n", 27 | "import tsplib95\n", 28 | "\n", 29 | "from mqt.qubomaker.pathfinder import EncodingType, from_tsplib_problem" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "problem = tsplib95.load(\"input/gr17.tsp\")\n", 39 | "\n", 40 | "generator = from_tsplib_problem(problem, EncodingType.ONE_HOT)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "matrix = generator.construct_qubo_matrix()\n", 50 | "matrix" 51 | ] 52 | } 53 | ], 54 | "metadata": { 55 | "kernelspec": { 56 | "display_name": "mqt-test", 57 | "language": "python", 58 | "name": "python3" 59 | }, 60 | "language_info": { 61 | "codemirror_mode": { 62 | "name": "ipython", 63 | "version": 3 64 | }, 65 | "file_extension": ".py", 66 | "mimetype": "text/x-python", 67 | "name": "python", 68 | "nbconvert_exporter": "python", 69 | "pygments_lexer": "ipython3" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 2 74 | } 75 | -------------------------------------------------------------------------------- /tests/pathfinder/resources/json/all.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "encoding": "ONE_HOT", 4 | "n_paths": 3, 5 | "max_path_length": 4, 6 | "loops": true 7 | }, 8 | "objective_function": { 9 | "type": "MinimizePathLength", 10 | "path_ids": [1] 11 | }, 12 | "constraints": [ 13 | { 14 | "type": "PathContainsEdgesAtLeastOnce", 15 | "edges": [[1, 2]], 16 | "path_ids": [1] 17 | }, 18 | { 19 | "type": "PathContainsEdgesAtMostOnce", 20 | "edges": [[1, 2]], 21 | "path_ids": [1] 22 | }, 23 | { 24 | "type": "PathContainsEdgesExactlyOnce", 25 | "edges": [[1, 2]], 26 | "path_ids": [1] 27 | }, 28 | { 29 | "type": "PathContainsVerticesAtLeastOnce", 30 | "vertices": [1, 2, 3], 31 | "path_ids": [1] 32 | }, 33 | { 34 | "type": "PathContainsVerticesAtMostOnce", 35 | "vertices": [1, 2, 3], 36 | "path_ids": [1] 37 | }, 38 | { 39 | "type": "PathContainsVerticesExactlyOnce", 40 | "vertices": [1, 2, 3], 41 | "path_ids": [1] 42 | }, 43 | { 44 | "type": "PathEndsAt", 45 | "vertices": [1, 2, 3], 46 | "path_id": 1 47 | }, 48 | { 49 | "type": "PathStartsAt", 50 | "vertices": [1, 2, 3], 51 | "path_id": 1 52 | }, 53 | { 54 | "type": "PathPositionIs", 55 | "position": 2, 56 | "vertices": [1, 2, 3], 57 | "path_id": 1 58 | }, 59 | { 60 | "type": "PrecedenceConstraint", 61 | "precedences": [ 62 | { 63 | "before": 1, 64 | "after": 2 65 | } 66 | ], 67 | "path_ids": [1] 68 | }, 69 | { 70 | "type": "PathsShareNoEdges", 71 | "path_ids": [1, 2, 3] 72 | }, 73 | { 74 | "type": "PathsShareNoVertices", 75 | "path_ids": [1, 2, 3] 76 | }, 77 | { 78 | "type": "PathIsValid", 79 | "path_ids": [1, 2, 3] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to MQT QUBOMaker's documentation! 2 | ========================================= 3 | 4 | MQT QUBOMaker is a framework for creating QUBO formulations for diverse optimization problems. These formulations can be used with a wide range of quantum algorithms to find approximate solutions to the problems. 5 | It is developed by the `Chair for Design Automation <https://www.cda.cit.tum.de/>`_ at the `Technical University of Munich <https://www.tum.de>`_ as part of the :doc:`Munich Quantum Toolkit <mqt:index>` (*MQT*). 6 | 7 | The framework is designed to be user-friendly and to provide a high-level interface for creating QUBO formulations, not requiring any background knowledge in quantum computing or QUBO problems to solve domain-specific problems. It is also designed to be extensible, so that new optimization problems can be added to the framework with relative ease. 8 | 9 | Currently, MQT QUBOMaker supports the following optimization problem types: 10 | - `Pathfinding Problems <pathfinder/Pathfinder.html>`_ 11 | 12 | In addition to a semantic selection of problem constraints, MQT QUBOMaker also provides a `graphical user interface <pathfinder/GUI.html>`_ for the Pathfinding submodule, which allows users to interactively define pathfinding problems to be passed to the framework. 13 | 14 | MQT QUBOMaker supports several encoding schemes, allowing end users to easily compare and evaluate different encodings without the need for manual rewriting of the problems. 15 | 16 | If you are interested in the theory behind MQT QUBOMaker, have a look at the publications in the :doc:`references list <References>`. 17 | 18 | --- 19 | 20 | .. toctree:: 21 | :hidden: 22 | 23 | self 24 | 25 | .. toctree:: 26 | :maxdepth: 1 27 | :caption: User Guide 28 | :glob: 29 | 30 | Installation 31 | QuboGenerator 32 | pathfinder/index 33 | References 34 | 35 | .. toctree:: 36 | :maxdepth: 1 37 | :caption: Developers 38 | :glob: 39 | 40 | Contributing 41 | DevelopmentGuide 42 | Support 43 | -------------------------------------------------------------------------------- /docs/QuboGenerator.rst: -------------------------------------------------------------------------------- 1 | QuboGenerator 2 | ================ 3 | 4 | The :code:`QuboGenerator` class is the main concept of the package. It provides methods for the 5 | automatic construction of QUBO formulations, including the computation of penalty factors for individual 6 | cost functions, the creation of auxiliary variables to reduce cost functions to quadratic order, and the 7 | translation into different output formats. 8 | 9 | A :code:`QuboGenerator` object represents a single problem instance and collects constraints and cost functions related to it. 10 | It is an abstract base class for specialized QUBO generators, such as the :code:`PathFindingQuboGenerator` to extend. 11 | 12 | It provides the following output formats: 13 | 14 | Simplified QUBO formula 15 | ------------------------ 16 | 17 | The QUBO formulation of the corresponding problem instance as a simplified formula, including sum expressions and additional functions 18 | if necessary, to provide a human-readable representation of the QUBO. 19 | 20 | Expanded QUBO formula 21 | --------------------- 22 | 23 | The QUBO formulation of the corresponding problem expanded as a polynomial expression, without any sums or additional functions. 24 | This expression also includes additional auxiliary variables, if necessary, to reduce the order of the products to quadratic expressions. 25 | 26 | QUBO matrix 27 | ----------- 28 | 29 | The QUBO as a triangular matrix :math:`Q`, such that the QUBO problem can be represented as 30 | 31 | $$\\mathbf{x}* = \\text{argmin}_{\\mathbf{x}} \\mathbf{x}^T Q \\mathbf{x}$$ 32 | 33 | Entry :math:`Q_{ij}` of the matrix represents the coefficient of the quadratic term :math:`x_i * x_j` in the expanded QUBO expression. 34 | 35 | Quantum Operator 36 | ---------------- 37 | 38 | The QUBO as a hamiltonian operator whose minimum eigenstate encodes to the optimal assignment. 39 | 40 | Quantum algorithms such as Grover adaptive search, QAOA, VQE, or QPE can be used to solve the eigenvalue problem. 41 | 42 | Quantum Circuit 43 | --------------- 44 | 45 | Qiskit's QAOA implementation to solve the given QUBO problem. Returns an instance of the qiskit :code:`QAOA` class that can be 46 | used to optimize the problem's hamiltonian operator. This allows end users to run the quantum algorithms without needing to interact with any 47 | quantum computing concepts directly themselves. 48 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/__init__.py: -------------------------------------------------------------------------------- 1 | """This module implements the pathfinding functionalities of the QuboMaker. 2 | 3 | This, in particular, includes all pathfinding-related cost functions and the specialized QUBO generator for pathfinding problems. 4 | 5 | Typical usage example: 6 | 7 | ```python 8 | import mqt.qubomaker.pathfinder as pf 9 | 10 | graph = pf.Graph(5, [(1, 2, 5), (2, 3, 3), (3, 4, 9), (4, 5, 8), (5, 1, 6)]) 11 | settings = pf.PathFindingQuboGeneratorSettings(pf.EncodingType.ONE_HOT, 1, 5, True) 12 | generator = pf.PathFindingQuboGenerator(pf.MinimizePathLength([1]), graph, settings) 13 | generator.add_constraint(pf.PathIsValid([1])) 14 | generator.add_constraint(pf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 15 | 16 | A = generator_new.construct_qubo_matrix() 17 | print(A) 18 | ``` 19 | """ 20 | 21 | from __future__ import annotations 22 | 23 | from .cost_functions import ( 24 | CompositeCostFunction, 25 | CostFunction, 26 | EncodingType, 27 | MaximizePathLength, 28 | MinimizePathLength, 29 | PathBound, 30 | PathComparison, 31 | PathContainsEdges, 32 | PathContainsEdgesAtLeastOnce, 33 | PathContainsEdgesAtMostOnce, 34 | PathContainsEdgesExactlyOnce, 35 | PathContainsVertices, 36 | PathContainsVerticesAtLeastOnce, 37 | PathContainsVerticesAtMostOnce, 38 | PathContainsVerticesExactlyOnce, 39 | PathEndsAt, 40 | PathIsValid, 41 | PathPositionIs, 42 | PathsShareNoEdges, 43 | PathsShareNoVertices, 44 | PathStartsAt, 45 | PrecedenceConstraint, 46 | ) 47 | from .pathfinder import PathFindingQuboGenerator, PathFindingQuboGeneratorSettings 48 | from .tsplib import from_tsplib_problem 49 | 50 | __all__ = [ 51 | "CompositeCostFunction", 52 | "CostFunction", 53 | "EncodingType", 54 | "MaximizePathLength", 55 | "MinimizePathLength", 56 | "PathBound", 57 | "PathComparison", 58 | "PathContainsEdges", 59 | "PathContainsEdgesAtLeastOnce", 60 | "PathContainsEdgesAtMostOnce", 61 | "PathContainsEdgesExactlyOnce", 62 | "PathContainsVertices", 63 | "PathContainsVerticesAtLeastOnce", 64 | "PathContainsVerticesAtMostOnce", 65 | "PathContainsVerticesExactlyOnce", 66 | "PathEndsAt", 67 | "PathFindingQuboGenerator", 68 | "PathFindingQuboGeneratorSettings", 69 | "PathIsValid", 70 | "PathPositionIs", 71 | "PathStartsAt", 72 | "PathsShareNoEdges", 73 | "PathsShareNoVertices", 74 | "PrecedenceConstraint", 75 | "from_tsplib_problem", 76 | ] 77 | -------------------------------------------------------------------------------- /docs/pathfinder/index.rst: -------------------------------------------------------------------------------- 1 | Pathfinder Submodule 2 | ==================== 3 | 4 | This module implements MQT QUBOMaker for pathfinding problems on directed and undirected graphs in the form of the :code:`PathFindingQuboGenerator` class, a specialization of the general :code:`QuboGenerator` class. 5 | 6 | It supports a set of various :doc:`constraints <Constraints>` that can be used to model a variety of different pathfinding problems. 7 | 8 | In addition to that, it also provides three :doc:`encoding schemes <Encodings>` that can be selected for the construction of QUBO formulations. 9 | 10 | Finally, the :doc:`GUI <GUI>` provides a graphical user interface for the module, which can be used to interactively define pathfinding problems. 11 | 12 | In addition to that, the submodule accepts several input formats for the problem instance. A :doc:`JSON format <JSON>` can be used to define problem constraints and settings. Furthermore, the established :doc:`TSPLib format<TSPLib>` can be passed to the framework directly, generating the required constraints from the problem instance. 13 | 14 | The :code:`PathFindingQuboGenerator` class can be instantiated like this: 15 | 16 | .. code-block:: python 17 | 18 | import mqt.qubomaker.pathfinder as pf 19 | 20 | ... 21 | 22 | generator = pf.PathFindingQuboGenerator( 23 | objective_function=pf.MinimizePathLength(path_ids=[1]), 24 | graph=graph, 25 | settings=settings, 26 | ) 27 | 28 | Here, the :code:`objective_function` parameter can represent any objective function for the optimization procedure (:code:`MinimizePathLength` or :code:`MaximizePathLength`). The :code:`graph` parameter is the graph on which the problem is defined. Finally the :code:`settings` parameter is a :code:`PathFindingQuboGeneratorSettings` object that defines settings for the QUBO generator: 29 | 30 | - :code:`encoding_type`: The encoding scheme to use for the QUBO formulation. 31 | - :code:`n_paths`: The number of paths to be searched for. 32 | - :code:`max_path_length`: The maximum length of a path. 33 | - :code:`loops`: A boolean value indicating, whether the found paths should be interpreted as loops. 34 | 35 | An example settings definition may look like: 36 | 37 | .. code-block:: python 38 | 39 | import mqt.qubomaker.pathfinder as pf 40 | 41 | settings = pf.PathFindingQuboGeneratorSettings( 42 | encoding_type=pf.EncodingType.BINARY, 43 | n_paths=1, 44 | max_path_length=4, 45 | loops=False, 46 | ) 47 | 48 | 49 | .. toctree:: 50 | :maxdepth: 1 51 | :caption: Pathfinder Features 52 | 53 | Constraints 54 | Encodings 55 | GUI 56 | JSON 57 | TSPLib 58 | -------------------------------------------------------------------------------- /docs/_templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "furo/page.html" %} {% block footer %} 2 | <div class="related-pages"> 3 | {% if next -%} 4 | <a class="next-page" href="{{ next.link }}"> 5 | <div class="page-info"> 6 | <div class="context"> 7 | <span>{{ _("Next") }}</span> 8 | </div> 9 | <div class="title">{{ next.title }}</div> 10 | </div> 11 | <svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg> 12 | </a> 13 | {%- endif %} {% if prev -%} 14 | <a class="prev-page" href="{{ prev.link }}"> 15 | <svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg> 16 | <div class="page-info"> 17 | <div class="context"> 18 | <span>{{ _("Previous") }}</span> 19 | </div> 20 | {% if prev.link == pathto(master_doc) %} 21 | <div class="title">{{ _("Home") }}</div> 22 | {% else %} 23 | <div class="title">{{ prev.title }}</div> 24 | {% endif %} 25 | </div> 26 | </a> 27 | {%- endif %} 28 | </div> 29 | <div class="acknowledgements"> 30 | The Munich Quantum Toolkit has been supported by the European Research Council 31 | (ERC) under the European Union's Horizon 2020 research and innovation program 32 | (grant agreement No. 101001318), the Bavarian State Ministry for Science and 33 | Arts through the Distinguished Professorship Program, as well as the Munich 34 | Quantum Valley, which is supported by the Bavarian state government with funds 35 | from the Hightech Agenda Bayern Plus. 36 | <div class="acknowledgements-logos"> 37 | <div class="acknowledgement only-light"> 38 | <img 39 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_light.svg" 40 | alt="TUM Logo" 41 | /> 42 | </div> 43 | <div class="acknowledgement only-dark"> 44 | <img 45 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_dark.svg" 46 | alt="TUM Logo" 47 | /> 48 | </div> 49 | <div class="acknowledgement"> 50 | <img 51 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-bavaria.svg" 52 | alt="Coat of Arms of Bavaria" 53 | style="width: 65%" 54 | /> 55 | </div> 56 | <div class="acknowledgement only-light"> 57 | <img 58 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_light.svg" 59 | alt="ERC Logo" 60 | /> 61 | </div> 62 | <div class="acknowledgement only-dark"> 63 | <img 64 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_dark.svg" 65 | alt="ERC Logo" 66 | /> 67 | </div> 68 | <div class="acknowledgement"> 69 | <img 70 | src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-mqv.svg" 71 | alt="MQV Logo" 72 | /> 73 | </div> 74 | </div> 75 | </div> 76 | {% endblock footer %} 77 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/utils.py: -------------------------------------------------------------------------------- 1 | """Provides utility functions that can be used with QuboMaker.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import no_type_check 6 | 7 | import numpy as np 8 | from IPython.display import Math, clear_output, display 9 | from ipywidgets import widgets 10 | 11 | 12 | @no_type_check 13 | def print_matrix(array: Iterable[Iterable[float]]) -> None: 14 | """Print a matrix data structure in LaTeX format. 15 | 16 | Args: 17 | array (Iterable[Iterable[float]]): The matrix to be printed. 18 | """ 19 | matrix = "" 20 | for row in array: 21 | try: 22 | for number in row: 23 | matrix += f"{number}&" 24 | except TypeError: 25 | matrix += f"{row}&" 26 | matrix = matrix[:-1] + r"\\" 27 | display(Math(r"Q = \begin{bmatrix}" + matrix + r"\end{bmatrix}")) 28 | 29 | 30 | @no_type_check 31 | def optimize_classically( 32 | qubo: npt.NDArray[np.int_ | np.float64], show_progress_bar: bool = False 33 | ) -> tuple[list[int], float]: 34 | """Classically optimizes a given QUBO problem of the form x^TQx. 35 | 36 | Args: 37 | qubo (npt.NDArray[np.int_ | np.float64]): A matrix representing the QUBO problem. 38 | show_progress_bar (bool, optional): If True, shows a progress bar in jupyter notebooks during calculation. Defaults to False. 39 | 40 | Returns: 41 | tuple[list[int], float]: The optimal solution and its corresponding score. 42 | """ 43 | progress_bar: widgets.FloatProgress | None = None 44 | if show_progress_bar: 45 | progress_bar = widgets.FloatProgress( 46 | value=0, 47 | min=0, 48 | max=1, 49 | description="Calculating:", 50 | bar_style="info", 51 | style={"bar_color": "#0055bb"}, 52 | orientation="horizontal", 53 | ) 54 | 55 | def int_to_fixed_length_binary(number: int, length: int) -> list[int]: 56 | binary_string = f"{number:b}" 57 | padding_zeros = max(0, length - len(binary_string)) 58 | binary_string = "0" * padding_zeros + binary_string 59 | return [int(bit) for bit in binary_string] 60 | 61 | all_tests = [int_to_fixed_length_binary(i, qubo.shape[0]) for i in range(2 ** qubo.shape[0])] 62 | 63 | best_test: list[int] = [] 64 | best_score = 999999999999 65 | 66 | for i, test in enumerate(all_tests): 67 | x = np.array(test) 68 | score = np.matmul(x.T, np.matmul(qubo, x)) 69 | if best_score > score: 70 | best_score = score 71 | best_test = test 72 | if i % 2000 == 0 and show_progress_bar and progress_bar is not None: 73 | progress_bar.value = i / len(all_tests) 74 | clear_output(True) 75 | display(progress_bar) 76 | 77 | if show_progress_bar and progress_bar is not None: 78 | progress_bar.value = 1 79 | clear_output(True) 80 | display(progress_bar) 81 | return (best_test, best_score) 82 | -------------------------------------------------------------------------------- /tests/test_graph.py: -------------------------------------------------------------------------------- 1 | """Tests the features of the `graph` module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from pathlib import Path 6 | from tempfile import NamedTemporaryFile 7 | 8 | import numpy as np 9 | 10 | from mqt.qubomaker import Graph 11 | 12 | 13 | def test_init_with_edge_list() -> None: 14 | """Tests the initialization of a `Graph` object with an edge list or an adjacency matrix.""" 15 | g = Graph(5, [(1, 2, 4), (3, 5, 2), (1, 3, 2), (4, 5, 5), (2, 4, 3), (5, 1)]) 16 | 17 | adjacency_matrix = [[0, 4, 2, 0, 0], [0, 0, 0, 3, 0], [0, 0, 0, 0, 2], [0, 0, 0, 0, 5], [1, 0, 0, 0, 0]] 18 | 19 | g2 = Graph.from_adjacency_matrix(adjacency_matrix) 20 | 21 | assert np.array_equal(g.adjacency_matrix, g2.adjacency_matrix) 22 | assert g.all_edges == g2.all_edges 23 | assert g.all_vertices == g2.all_vertices 24 | assert g.non_edges == g2.non_edges 25 | assert np.array_equal(g.adjacency_matrix, adjacency_matrix) 26 | assert g == g2 27 | 28 | 29 | def test_read_write() -> None: 30 | """Tests the read and write operations of the `Graph` class.""" 31 | g = Graph(5, [(1, 2, 4), (3, 5, 2), (1, 3, 2), (4, 5, 5), (2, 4, 3), (5, 1)]) 32 | 33 | with NamedTemporaryFile("w+", delete=False, encoding="utf-8") as temp_file: 34 | temp_file_path = temp_file.name 35 | 36 | with Path(str(temp_file_path)).open("w", encoding="utf-8") as file: 37 | g.store(file) 38 | 39 | with Path(str(temp_file_path)).open("r", encoding="utf-8") as file: 40 | g2 = Graph.read(file) 41 | 42 | assert g == g2 43 | 44 | with Path(str(temp_file_path)).open("w", encoding="utf-8") as file: 45 | file.write(g.serialize()) 46 | 47 | with Path(str(temp_file_path)).open("r", encoding="utf-8") as file: 48 | g2 = Graph.deserialize(file.read()) 49 | 50 | assert g == g2 51 | 52 | 53 | def test_eq() -> None: 54 | """Tests the equality operator on `Graph` objects.""" 55 | g1 = Graph(3, [(1, 2, 4), (1, 3, 1), (2, 3, 1), (2, 1, 5), (3, 1, 4), (3, 2, 5)]) 56 | 57 | g2 = Graph( 58 | 3, 59 | [ 60 | (3, 2, 5), 61 | (1, 2, 4), 62 | (2, 1, 5), 63 | (2, 3, 1), 64 | (3, 1, 4), 65 | (1, 3, 1), 66 | ], 67 | ) 68 | 69 | g3 = Graph(3, [(1, 2, 4), (1, 3, 1), (2, 3, 1), (3, 1, 4), (3, 2, 5)]) 70 | 71 | g4 = Graph(3, [(1, 2, 4), (1, 3, 1), (2, 3, 1), (2, 1, 3), (3, 1, 4), (3, 2, 5)]) 72 | 73 | g5 = Graph(3, [(1, 2, 4), (1, 3), (2, 3), (2, 1, 5), (3, 1, 4), (3, 2, 5)]) 74 | 75 | assert g1 == g2 76 | assert g1 == g5 77 | assert g2 == g5 78 | assert g1 != g3 79 | assert g1 != g4 80 | assert g2 != g3 81 | assert g2 != g4 82 | assert g5 != g3 83 | assert g5 != g4 84 | assert g3 != g4 85 | 86 | assert g2 == g1 87 | assert g5 == g1 88 | assert g5 == g2 89 | assert g3 != g1 90 | assert g4 != g1 91 | assert g3 != g2 92 | assert g4 != g2 93 | assert g3 != g5 94 | assert g4 != g5 95 | assert g4 != g3 96 | -------------------------------------------------------------------------------- /docs/Quickstart.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Quickstart Guide\n", 8 | "\n", 9 | "This document describes how to get started with MQT QUBOMaker, based on the `pathfinder` submodule." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "Import the necessary modules:" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "import mqt.qubomaker as qm\n", 26 | "import mqt.qubomaker.pathfinder as pf" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "Define an example graph for the problem:" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "graph = qm.Graph.from_adjacency_matrix([\n", 43 | " [0, 1, 3, 4],\n", 44 | " [2, 0, 4, 2],\n", 45 | " [1, 5, 0, 3],\n", 46 | " [3, 8, 1, 0],\n", 47 | "])" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "Select settings for the problem instance and the solution process:" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "settings = pf.PathFindingQuboGeneratorSettings(\n", 64 | " encoding_type=pf.EncodingType.ONE_HOT, n_paths=1, max_path_length=4, loops=True\n", 65 | ")" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "Define the `QuboGenerator` to be used for this example:" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "generator = pf.PathFindingQuboGenerator(\n", 82 | " objective_function=pf.MinimizePathLength(path_ids=[1]),\n", 83 | " graph=graph,\n", 84 | " settings=settings,\n", 85 | ")" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "Add constraints to the `QuboGenerator`" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "generator.add_constraint(pf.PathIsValid(path_ids=[1]))\n", 102 | "generator.add_constraint(pf.PathContainsVerticesExactlyOnce(vertex_ids=graph.all_vertices, path_ids=[1]))" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "Generate and view the problem's QUBO formulation as a QUBO matrix:" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "matrix = generator.construct_qubo_matrix()\n", 119 | "qm.print_matrix(matrix)" 120 | ] 121 | } 122 | ], 123 | "metadata": { 124 | "language_info": { 125 | "name": "python" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 2 130 | } 131 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # To run all pre-commit checks, use: 2 | # 3 | # pre-commit run -a 4 | # 5 | # To install pre-commit hooks that run every time you commit: 6 | # 7 | # pre-commit install 8 | # 9 | 10 | ci: 11 | autoupdate_commit_msg: "⬆️🪝 update pre-commit hooks" 12 | autofix_commit_msg: "🎨 pre-commit fixes" 13 | skip: [mypy] 14 | 15 | repos: 16 | # Standard hooks 17 | - repo: https://github.com/pre-commit/pre-commit-hooks 18 | rev: v5.0.0 19 | hooks: 20 | - id: check-added-large-files 21 | - id: check-case-conflict 22 | - id: check-docstring-first 23 | - id: check-merge-conflict 24 | - id: check-toml 25 | - id: check-yaml 26 | - id: debug-statements 27 | - id: end-of-file-fixer 28 | - id: mixed-line-ending 29 | - id: trailing-whitespace 30 | 31 | # Handling unwanted unicode characters 32 | - repo: https://github.com/sirosen/texthooks 33 | rev: 0.6.8 34 | hooks: 35 | - id: fix-ligatures 36 | - id: fix-smartquotes 37 | 38 | # Check for common mistakes 39 | - repo: https://github.com/pre-commit/pygrep-hooks 40 | rev: v1.10.0 41 | hooks: 42 | - id: rst-backticks 43 | - id: rst-directive-colons 44 | - id: rst-inline-touching-normal 45 | 46 | # Check for spelling 47 | - repo: https://github.com/crate-ci/typos 48 | rev: v1.32.0 49 | hooks: 50 | - id: typos 51 | 52 | # Format configuration files with prettier 53 | - repo: https://github.com/rbubley/mirrors-prettier 54 | rev: "v3.5.3" 55 | hooks: 56 | - id: prettier 57 | types_or: [yaml, markdown, html, css, javascript, json] 58 | 59 | - repo: https://github.com/astral-sh/ruff-pre-commit 60 | rev: v0.11.13 61 | hooks: 62 | - id: ruff 63 | args: ["--fix", "--show-fixes"] 64 | - id: ruff-format 65 | 66 | # Also run Black on examples in the documentation 67 | - repo: https://github.com/adamchainz/blacken-docs 68 | rev: 1.19.1 69 | hooks: 70 | - id: blacken-docs 71 | additional_dependencies: [black==24.*] 72 | 73 | # Clean jupyter notebooks 74 | - repo: https://github.com/srstevenson/nb-clean 75 | rev: "4.0.1" 76 | hooks: 77 | - id: nb-clean 78 | 79 | - repo: https://github.com/pre-commit/mirrors-mypy 80 | rev: v1.16.1 81 | hooks: 82 | - id: mypy 83 | files: ^(src|tests|noxfile.py) 84 | args: [] 85 | additional_dependencies: 86 | - importlib_resources 87 | - types-setuptools 88 | - networkx 89 | - pytest 90 | - sympy 91 | - ipywidgets 92 | - IPython 93 | - tsplib95 94 | - types-Pillow 95 | - referencing 96 | - nox 97 | 98 | # Catch common capitalization mistakes 99 | - repo: local 100 | hooks: 101 | - id: disallow-caps 102 | name: Disallow improper capitalization 103 | language: pygrep 104 | entry: PyBind|Numpy|Cmake|CCache|Github|PyTest|Mqt|Tum 105 | exclude: .pre-commit-config.yaml 106 | 107 | # Check best practices for scientific Python code 108 | - repo: https://github.com/scientific-python/cookie 109 | rev: 2025.05.02 110 | hooks: 111 | - id: sp-repo-review 112 | additional_dependencies: ["repo-review[cli]"] 113 | -------------------------------------------------------------------------------- /docs/DevelopmentGuide.rst: -------------------------------------------------------------------------------- 1 | Development Guide 2 | ================= 3 | 4 | Ready to contribute to the project? Here is how to set up a local development environment. 5 | 6 | Initial Setup 7 | ############# 8 | 9 | 1. Fork the `cda-tum/mqt-qubomaker <https://github.com/cda-tum/mqt-qubomaker>`_ repository on GitHub (see https://docs.github.com/en/get-started/quickstart/fork-a-repo). 10 | 11 | 2. Clone your fork locally 12 | 13 | .. code-block:: console 14 | 15 | $ git clone git@github.com:your_name_here/mqt-qubomaker 16 | 17 | 18 | 3. Change into the project directory 19 | 20 | .. code-block:: console 21 | 22 | $ cd mqt-qubomaker 23 | 24 | 4. Create a branch for local development 25 | 26 | .. code-block:: console 27 | 28 | $ git checkout -b name-of-your-bugfix-or-feature 29 | 30 | Now you can make your changes locally. 31 | 32 | 5. (Optional, **highly recommended**) Set up a virtual environment 33 | 34 | .. code-block:: console 35 | 36 | $ python3 -m venv venv 37 | $ source venv/bin/activate 38 | 39 | .. note:: 40 | 41 | If you are using Windows, you can use the following command instead: 42 | 43 | .. code-block:: console 44 | 45 | $ python3 -m venv venv 46 | $ venv\Scripts\activate.bat 47 | 48 | Ensure that pip, setuptools, and wheel are up to date: 49 | 50 | .. code-block:: console 51 | 52 | (venv) $ pip install --upgrade pip setuptools wheel 53 | 54 | 55 | 6. (Optional) Install `pre-commit <https://pre-commit.com/>`_ to automatically run a set of checks before each commit. 56 | 57 | .. code-block:: console 58 | 59 | (venv) $ pipx install pre-commit 60 | (venv) $ pre-commit install 61 | 62 | If you use macOS, then pre-commit is in brew, use :code:`brew install pre-commit`. 63 | 64 | Building the Python module 65 | ########################## 66 | 67 | The recommended way of building the Python module is to perform an editable install using `pip <https://pip.pypa.io/en/stable/>`_. 68 | 69 | .. code-block:: console 70 | 71 | (venv) $ pip install -e . 72 | 73 | The :code:`--editable` flag ensures that changes in the Python code are instantly available without re-running the command. 74 | 75 | Running Python Tests 76 | -------------------- 77 | 78 | The Python part of the code base is tested by unit tests using the `pytest <https://docs.pytest.org/en/latest/>`_ framework. 79 | The corresponding test files can be found in the :code:`tests/` directory. 80 | 81 | .. code-block:: console 82 | 83 | (venv) $ pip install -e ".[test]" 84 | (venv) $ pytest 85 | 86 | This installs all dependencies necessary to run the tests in an isolated environment, builds the Python package, and then runs the tests. 87 | 88 | Python Code Formatting and Linting 89 | ---------------------------------- 90 | 91 | The Python code is formatted and linted using a collection of `pre-commit hooks <https://pre-commit.com/>`_. 92 | This collection includes: 93 | 94 | - `ruff <https://docs.astral.sh/ruff/>`_ -- an extremely fast Python linter and formatter, written in Rust. 95 | - `mypy <http://mypy-lang.org/>`_ -- a static type checker for Python code 96 | 97 | 98 | You can install the hooks manually by running :code:`pre-commit install` in the project root directory. 99 | The hooks will then be executed automatically when committing changes. 100 | 101 | .. code-block:: console 102 | 103 | (venv) $ pre-commit run -a 104 | -------------------------------------------------------------------------------- /docs/pathfinder/Encodings.rst: -------------------------------------------------------------------------------- 1 | Encoding Schemes 2 | ================ 3 | 4 | The *Pathfinder* submodule provides three different encoding schemes. The encoding scheme determines, how 5 | many binary variables are required to represent the problem's cost function and how these variables are to be 6 | interpreted. 7 | 8 | An example of the same path represented in each of the three encoding schemes is shown below. 9 | 10 | .. image:: ../_static/encodings.png 11 | :align: center 12 | :alt: Encoding schemes 13 | 14 | 15 | For each encoding scheme, the function :math:`\delta(x, \pi^{(i)}, v, j)` is defined, returning 1 if vertex :math:`v` is located at position :math:`j` in path :math:`\pi^{(i)}`, and 0 otherwise. 16 | The complexity of this function depends on the encoding scheme used. 17 | 18 | Below, :math:`N` denotes the maximum length of a path, :math:`|V|` the number of vertices in the graph, and :math:`|\Pi|` the number of paths to be found. 19 | 20 | 21 | One-Hot 22 | ------- 23 | 24 | In the one-hot encoding scheme, :math:`N \cdot |V| \cdot |\Pi|` binary variables :math:`x_{v,j,\pi^{(i)}}` are 25 | used to represent the problem. 26 | 27 | An individual variable with value 1 indicates that the corresponding vertex :math:`v` is located at position 28 | :math:`j` in path :math:`\pi^{(i)}`. Assignments such that :math:`x_{v,j,\pi^{(i)}} = 1` for more than one :math:`v` and the same :math:`j` and :math:`\pi^{(i)}`` 29 | are invalid. 30 | 31 | This encoding scheme is very expressive, but also uses a large amount of binary variables. It is also 32 | very sparse, meaning that there exists a large number of invalid assignments. Moving from one valid assignment 33 | to another requires at least two bitflips. 34 | 35 | $$\\delta(x, \\pi^{(i)}, v, j) = x_{v, j, \\pi^{(i)}}$$ 36 | 37 | Domain-Wall 38 | ----------- 39 | 40 | In the domain-wall encoding scheme, :math:`N \cdot |V| \cdot |\Pi|` binary variables :math:`x_{v,j,\pi^{(i)}}` are used 41 | to represent the problem. 42 | 43 | For each position :math:`j` and path :math:`\pi^{(i)}`, the variables :math:`x_{v,j,\pi^{(i)}}` are read as a bitstring 44 | :math:`\overline{x_{j,\pi^{(i)}}}` of length :math:`|V|`. If the first :math:`n` bits of this bitstring are 1, this indicates that 45 | vertex :math:`v_n` is located at position :math:`j` in path :math:`\pi^{(i)}`. 46 | 47 | Compared to the one-hot encoding, it is easier to move from one valid assignment to another, as only one bitflip 48 | is required for that. However, there exist just as many invalid encodings. 49 | 50 | $$\\delta(x, \\pi^{(i)}, v, j) = x_{v, j, \\pi^{(i)}} - x_{v + 1, j, \\pi^{(i)}}$$ 51 | 52 | Binary 53 | ------ 54 | 55 | In binary encoding :math:`N \cdot \text{log}(|V|) \cdot |\Pi|` binary variables :math:`x_{v,j,\pi^{(i)}}` are used 56 | to represent the problem. 57 | 58 | For each position :math:`j` and path :math:`\pi^{(i)}`, the variables :math:`x_{v,j,\pi^{(i)}}` are read as a bitstring 59 | :math:`\overline{x_{j,\pi^{(i)}}}` of length :math:`\text{log}(|V|)`. This bitstring is interpreted as a binary number, 60 | representing the index of the vertex located at position :math:`j` in path :math:`\pi^{(i)}`. 61 | 62 | This is a dense encoding, as no invalid assignment exists. However, it is less expressive and more complex 63 | than the other encodings. In particular, cost functions using the binary encoding are rarely of quadratic order, 64 | and, therefore, often require additional auxiliary variables. 65 | 66 | $$\\delta(x, \\pi^{(i)}, v, j) = \\prod_{w=1}^{\\text{log}_2(\|V\| + 1)} (\\text{b}(v, w) x_{w, j, \\pi^{(i)}}) + ((1 - \\text{b}(v, w)) (1 - x_{w, j, \\pi^{(i)}}))$$ 67 | 68 | *where* :math:`\text{b}(n, i)` *denotes the* :math:`i\text{-th}` *bit of the binary representation of* :math:`n` *.* 69 | -------------------------------------------------------------------------------- /.github/workflows/nextjs.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages 2 | # 3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started 4 | # 5 | name: Deploy Next.js site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["ui"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Detect package manager 35 | id: detect-package-manager 36 | run: | 37 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then 38 | echo "manager=yarn" >> $GITHUB_OUTPUT 39 | echo "command=install" >> $GITHUB_OUTPUT 40 | echo "runner=yarn" >> $GITHUB_OUTPUT 41 | exit 0 42 | elif [ -f "${{ github.workspace }}/package.json" ]; then 43 | echo "manager=npm" >> $GITHUB_OUTPUT 44 | echo "command=ci" >> $GITHUB_OUTPUT 45 | echo "runner=npx --no-install" >> $GITHUB_OUTPUT 46 | exit 0 47 | else 48 | echo "Unable to determine package manager" 49 | exit 1 50 | fi 51 | - name: Setup Node 52 | uses: actions/setup-node@v4 53 | with: 54 | node-version: "22" 55 | cache: ${{ steps.detect-package-manager.outputs.manager }} 56 | - name: Setup Pages 57 | uses: actions/configure-pages@v5 58 | with: 59 | # Automatically inject basePath in your Next.js configuration file and disable 60 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). 61 | # 62 | # You may remove this line if you want to manage the configuration yourself. 63 | static_site_generator: next 64 | - name: Restore cache 65 | uses: actions/cache@v4 66 | with: 67 | path: | 68 | .next/cache 69 | # Generate a new cache whenever packages or source files change. 70 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 71 | # If source files changed but packages didn't, rebuild from a prior cache. 72 | restore-keys: | 73 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- 74 | - name: Install dependencies 75 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} 76 | - name: Build with Next.js 77 | run: ${{ steps.detect-package-manager.outputs.runner }} next build 78 | - name: Static HTML export with Next.js 79 | run: ${{ steps.detect-package-manager.outputs.runner }} next export 80 | - name: Upload artifact 81 | uses: actions/upload-pages-artifact@v3 82 | with: 83 | path: ./out 84 | 85 | # Deployment job 86 | deploy: 87 | environment: 88 | name: github-pages 89 | url: ${{ steps.deployment.outputs.page_url }} 90 | runs-on: ubuntu-latest 91 | needs: build 92 | steps: 93 | - name: Deploy to GitHub Pages 94 | id: deployment 95 | uses: actions/deploy-pages@v4 96 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | """Sphinx configuration file.""" 2 | 3 | from __future__ import annotations 4 | 5 | import warnings 6 | from importlib import metadata 7 | from pathlib import Path 8 | from typing import TYPE_CHECKING 9 | 10 | import pybtex.plugin 11 | from pybtex.style.formatting.unsrt import Style as UnsrtStyle 12 | from pybtex.style.template import field, href 13 | 14 | ROOT = Path(__file__).parent.parent.resolve() 15 | 16 | 17 | try: 18 | version = metadata.version("mqt.qubomaker") 19 | except ModuleNotFoundError: 20 | msg = ( 21 | "Package should be installed to produce documentation! " 22 | "Assuming a modern git archive was used for version discovery." 23 | ) 24 | warnings.warn(msg, stacklevel=1) 25 | 26 | from setuptools_scm import get_version 27 | 28 | version = get_version(root=str(ROOT), fallback_root=ROOT) 29 | 30 | # Filter git details from version 31 | release = version.split("+")[0] 32 | if TYPE_CHECKING: 33 | from pybtex.database import Entry 34 | from pybtex.richtext import HRef 35 | 36 | project = "MQT QUBOMaker" 37 | author = "Chair for Design Automation, Technical University of Munich" 38 | language = "en" 39 | project_copyright = "2024, Chair for Design Automation, Technical University of Munich" 40 | # -- General configuration --------------------------------------------------- 41 | 42 | master_doc = "index" 43 | 44 | templates_path = ["_templates"] 45 | html_css_files = ["custom.css"] 46 | 47 | extensions = [ 48 | "sphinx.ext.napoleon", 49 | "sphinx.ext.autodoc", 50 | "sphinx.ext.autosummary", 51 | "sphinx.ext.mathjax", 52 | "sphinx.ext.intersphinx", 53 | "sphinx.ext.autosectionlabel", 54 | "sphinx.ext.viewcode", 55 | "sphinx.ext.githubpages", 56 | "sphinxcontrib.bibtex", 57 | "sphinx_copybutton", 58 | "sphinxext.opengraph", 59 | ] 60 | 61 | pygments_style = "colorful" 62 | 63 | add_module_names = False 64 | 65 | modindex_common_prefix = ["mqt.qubomaker."] 66 | 67 | intersphinx_mapping = { 68 | "python": ("https://docs.python.org/3", None), 69 | "typing_extensions": ("https://typing-extensions.readthedocs.io/en/latest/", None), 70 | "qiskit": ("https://docs.quantum.ibm.com/api/qiskit/", None), 71 | "mqt": ("https://mqt.readthedocs.io/en/latest/", None), 72 | } 73 | 74 | highlight_language = "python3" 75 | 76 | autosectionlabel_prefix_document = True 77 | 78 | exclude_patterns = ["_build", "build", "**.ipynb_checkpoints", "Thumbs.db", ".DS_Store", ".env"] 79 | 80 | 81 | class CDAStyle(UnsrtStyle): 82 | """Custom style for including PDF links.""" 83 | 84 | def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301 85 | """Format URL field as a link to the PDF.""" 86 | url = field("url", raw=True) 87 | return href()[url, "[PDF]"] 88 | 89 | 90 | pybtex.plugin.register_plugin("pybtex.style.formatting", "cda_style", CDAStyle) 91 | 92 | bibtex_bibfiles = ["refs.bib"] 93 | bibtex_default_style = "cda_style" 94 | 95 | copybutton_prompt_text = r"(?:\(venv\) )?(?:\[.*\] )?\$ " 96 | copybutton_prompt_is_regexp = True 97 | copybutton_line_continuation_character = "\\" 98 | 99 | autosummary_generate = True 100 | 101 | 102 | typehints_use_rtype = False 103 | napoleon_use_rtype = False 104 | napoleon_google_docstring = True 105 | napoleon_numpy_docstring = False 106 | 107 | # -- Options for HTML output ------------------------------------------------- 108 | html_theme = "furo" 109 | html_static_path = ["_static"] 110 | html_theme_options = { 111 | "light_logo": "mqt_dark.png", 112 | "dark_logo": "mqt_light.png", 113 | "source_repository": "https://github.com/cda-tum/mqt-qubomaker/", 114 | "source_branch": "main", 115 | "source_directory": "docs/", 116 | "navigation_with_keys": True, 117 | } 118 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/README.md: -------------------------------------------------------------------------------- 1 | This submodule of MQT QUBOMaker is responsible for the QUBO formulation of pathfinding problems. 2 | 3 | ### Settings 4 | 5 | Constructing QUBO formulation for pathfinding problems requires a `PathfindingQuboGenerator` instance with the corresponding settings: 6 | 7 | - `encoding_type`: An element of the `EncodingTypes` enum that represents the variable encoding scheme to be used. One of `ONE_HOT`, `DOMAIN_WALL`, or `BINARY`. 8 | - `n_paths`: The number of paths to be found. For most problem instances, this value will be `1`. 9 | - `max_path_length`: The maximum number of vertices a found path can consist of. Required to determine the number of binary variables that have to be used for the QUBO formulation. 10 | - `loops`: Determines, whether the path represents a loop, i.e., the final vertex in its sequence is connected back to the starting vertex. 11 | 12 | An example settings instance can be constructed as follows: 13 | 14 | ```python3 15 | import mqt.qubomaker.pathfinder as pf 16 | 17 | settings = pf.PathFindingQuboGeneratorSettings( 18 | encoding_type=pf.EncodingType.ONE_HOT, n_paths=1, max_path_length=4, loops=True 19 | ) 20 | ``` 21 | 22 | ### PathFindingQuboGenerator 23 | 24 | The `PathFindingQuboGenerator` class represents the main QUBO factory for pathfinding problems. It can be set up with predefined settings and populated with problem-specific constraints. 25 | 26 | To create a `PathFindingQuboGenerator`, a `Graph` instance further has to be provided, representing the graph to be investigated for the problem instance. 27 | 28 | ```python3 29 | ... 30 | 31 | qubo_generator = pf.PathFindingQuboGenerator( 32 | objective_function=None, graph=my_graph, settings=settings 33 | ) 34 | ``` 35 | 36 | When creating a `PathFindingQuboGenerator` instance, an objective function, as discussed below, can be added to add an optimization criterion. 37 | 38 | ### Cost Functions 39 | 40 | The `pathfinder` module provides cost functions representing various constraints related to pathfinding problems. The following constraints are supported: 41 | 42 | - `PathIsValid`: Checks, whether the encoding represents a valid path. Should be included in most cases. 43 | - `PathPositionIs`: Enforces that one of a set of vertices is located at a given position of a graph. 44 | - `PathStartsAt`: Enforces that one of a set of vertices is located at the start of a graph. 45 | - `PathEndsAt`: Enforces that one of a set of vertices is located at the end of a graph. 46 | - `PathContainsVerticesExactlyOnce`: Enforces that each element of a given set of vertices appears exactly once in a path. 47 | - `PathContainsVerticesAtLeastOnce`: Enforces that each element of a given set of vertices appears at least once in a path. 48 | - `PathContainsVerticesAtMostOnce`: Enforces that each element of a given set of vertices appears at most once in a path. 49 | - `PathContainsEdgesExactlyOnce`: Enforces that each element of a given set of edges appears exactly once in a path. 50 | - `PathContainsEdgesAtLeastOnce`: Enforces that each element of a given set of edges appears at least once in a path. 51 | - `PathContainsEdgesAtMostOnce`: Enforces that each element of a given set of edges appears at most once in a path. 52 | - `PrecedenceConstraint`: Enforces, that a given vertex does not appear in a path that didn't visit another given vertex first. 53 | - `PathsShareNoVertices`: Enforces, that two provided paths do not share any vertices. 54 | - `PathsShareNoEdges`: Enforces, that two provided paths do not share any edges. 55 | 56 | Furthermore, the `pathfinder` module also provides two objective functions: 57 | 58 | - `MinimizePathLength`: Adds a cost to the total cost function that penalizes paths with a higher total weight. 59 | - `MaximizePathLength`: Adds a cost to the total cost function that penalizes paths with a lower total weight. 60 | 61 | These constraints are represented by classes, and instances of the classes can be created to define the specific constraint and added to the QuboGenerator. 62 | 63 | ```python3 64 | ... 65 | 66 | contains_all_vertices = pf.PathContainsVerticesExactlyOnce( 67 | vertex_ids=graph.all_vertices, path_ids=[1] 68 | ) 69 | starts_at_1 = pf.PathStartsAt(vertex_ids=[1], path=1) 70 | 71 | qubo_generator.add_constraint(contains_all_vertices) 72 | qubo_generator.add_constraint(starts_at_1) 73 | ``` 74 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/graph.py: -------------------------------------------------------------------------------- 1 | """Provides a simple implementation for graphs to be used with QuboMaker.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING, cast 6 | 7 | import numpy as np 8 | import numpy.typing as npt 9 | 10 | if TYPE_CHECKING: 11 | from io import TextIOWrapper 12 | 13 | Edge = tuple[int, int] | tuple[int, int, int] | tuple[int, int, float] 14 | 15 | 16 | class Graph: 17 | """Represents a graph to be used with QuboMaker. 18 | 19 | Attributes: 20 | n_vertices (int): The number of vertices in the graph. 21 | adjacency_matrix (npt.NDArray[np.int_ | np.float64]): The adjacency matrix of the graph. 22 | all_vertices (list[int]): A list of all vertices in the graph. 23 | all_edges (list[tuple[int, int]]): A list of all edges in the graph. 24 | non_edges (list[tuple[int, int]]): A list of all non-edges in the graph. 25 | """ 26 | 27 | n_vertices: int 28 | adjacency_matrix: npt.NDArray[np.int_ | np.float64] 29 | 30 | @property 31 | def all_vertices(self) -> list[int]: 32 | """A list of all vertices in the graph.""" 33 | return list(range(1, self.n_vertices + 1)) 34 | 35 | @property 36 | def all_edges(self) -> list[tuple[int, int]]: 37 | """A list of all edges in the graph.""" 38 | return [(i, j) for i in self.all_vertices for j in self.all_vertices if self.adjacency_matrix[i - 1, j - 1] > 0] 39 | 40 | @property 41 | def non_edges(self) -> list[tuple[int, int]]: 42 | """A list of all pairs `(i, j)` that are not edges in the graph.""" 43 | return [ 44 | (i + 1, j + 1) 45 | for i in range(self.n_vertices) 46 | for j in range(self.n_vertices) 47 | if self.adjacency_matrix[i, j] <= 0 48 | ] 49 | 50 | def __init__(self, n_vertices: int, edges: list[Edge]) -> None: 51 | """Initialises a Graph object. 52 | 53 | Args: 54 | n_vertices (int): The number of vertices in the graph. 55 | edges (list[Edge]): A list of edges in the graph. 56 | """ 57 | self.n_vertices = n_vertices 58 | self.adjacency_matrix = np.zeros((n_vertices, n_vertices)) 59 | for edge in edges: 60 | if len(edge) == 2: 61 | (from_vertex, to_vertex, weight) = (edge[0], edge[1], 1.0) 62 | else: 63 | (from_vertex, to_vertex, weight) = edge 64 | self.adjacency_matrix[from_vertex - 1, to_vertex - 1] = weight if weight != -1 else 0 65 | 66 | @staticmethod 67 | def read(file: TextIOWrapper) -> Graph: 68 | """Reads a graph from a file. 69 | 70 | Args: 71 | file (TextIOWrapper): The file to read the graph from. 72 | 73 | Returns: 74 | Graph: The graph read from the file. 75 | """ 76 | m = np.loadtxt(file) 77 | g = Graph(m.shape[0], []) 78 | g.adjacency_matrix = m 79 | return g 80 | 81 | def store(self, file: TextIOWrapper) -> None: 82 | """Stores the graph in a file. 83 | 84 | Args: 85 | file (TextIOWrapper): The file to store the graph in. 86 | """ 87 | np.savetxt(file, self.adjacency_matrix) 88 | 89 | @staticmethod 90 | def from_adjacency_matrix( 91 | adjacency_matrix: npt.NDArray[np.int_ | np.float64] | list[list[int]] | list[list[float]], 92 | ) -> Graph: 93 | """Creates a graph from an adjacency matrix. 94 | 95 | Args: 96 | adjacency_matrix (npt.NDArray[np.int_ | np.float64]): The adjacency matrix to create the graph from. 97 | 98 | Returns: 99 | Graph: The graph created from the adjacency matrix. 100 | """ 101 | if isinstance(adjacency_matrix, list): 102 | adjacency_matrix = np.array(adjacency_matrix) 103 | g = Graph(adjacency_matrix.shape[0], []) 104 | g.adjacency_matrix = adjacency_matrix 105 | return g 106 | 107 | @staticmethod 108 | def deserialize(encoding: str) -> Graph: 109 | """Deserializes a graph from a string. 110 | 111 | Args: 112 | encoding (str): The string to deserialize the graph from. 113 | 114 | Returns: 115 | Graph: The deserialized graph. 116 | """ 117 | m = np.array([[float(cell) for cell in line.split() if cell] for line in encoding.splitlines() if line.strip()]) 118 | g = Graph(m.shape[0], []) 119 | g.adjacency_matrix = m 120 | return g 121 | 122 | def serialize(self) -> str: 123 | """Serializes the graph into a string. 124 | 125 | Returns: 126 | str: The serialized graph as a string. 127 | """ 128 | return str(self.adjacency_matrix).replace("]", "").replace("[", "").replace("\n ", "\n") 129 | 130 | def __eq__(self, value: object) -> bool: 131 | """Checks if two graphs are equal. 132 | 133 | Args: 134 | value (object): The other graph to compare to. 135 | 136 | Returns: 137 | bool: True if the graphs are equal, False otherwise. 138 | """ 139 | if not isinstance(value, Graph): 140 | return False 141 | return cast("bool", np.array_equal(self.adjacency_matrix, value.adjacency_matrix)) 142 | 143 | def __hash__(self) -> int: 144 | """Returns the hash of the graph. 145 | 146 | Returns: 147 | int: The hash of the graph. 148 | """ 149 | return hash(self.adjacency_matrix) 150 | -------------------------------------------------------------------------------- /tests/pathfinder/test_tsplib_input.py: -------------------------------------------------------------------------------- 1 | """Tests the correctness of the tsplib input format.""" 2 | 3 | from __future__ import annotations 4 | 5 | import sys 6 | from pathlib import Path 7 | 8 | import pytest 9 | 10 | if sys.version_info >= (3, 13): 11 | pytest.skip("This module requires Python 3.12 or lower", allow_module_level=True) 12 | 13 | import tsplib95 14 | 15 | import mqt.qubomaker.pathfinder as pf 16 | import mqt.qubomaker.pathfinder.cost_functions as cf 17 | 18 | from .utils_test import check_equal, get_test_graph 19 | 20 | TEST_GRAPH = get_test_graph() 21 | 22 | 23 | def read_from_path(path: str, encoding: pf.EncodingType = pf.EncodingType.ONE_HOT) -> pf.PathFindingQuboGenerator: 24 | """Reads a tsplib input file and returns the corresponding `PathFindingQuboGenerator`. 25 | 26 | Args: 27 | path: The path to the tsplib input file. 28 | encoding: The encoding to use. 29 | 30 | Returns: 31 | The corresponding `PathFindingQuboGenerator`. 32 | """ 33 | pth = Path("tests") / "pathfinder" / "resources" / "tsplib" / path 34 | problem = tsplib95.load(str(pth)) 35 | 36 | return pf.from_tsplib_problem(problem, encoding) 37 | 38 | 39 | def test_hcp() -> None: 40 | """Tests a tsplib input file that represents a HCP problem.""" 41 | json_generator = read_from_path("hcp-5.hcp") 42 | graph = json_generator.graph 43 | 44 | settings = pf.PathFindingQuboGeneratorSettings( 45 | encoding_type=pf.EncodingType.ONE_HOT, 46 | n_paths=1, 47 | max_path_length=5, 48 | loops=True, 49 | ) 50 | 51 | manual_generator = pf.PathFindingQuboGenerator(objective_function=None, graph=graph, settings=settings) 52 | 53 | manual_generator.add_constraint(cf.PathIsValid([1])) 54 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 55 | check_equal(json_generator, manual_generator) 56 | 57 | 58 | def test_tsp() -> None: 59 | """Tests a tsplib input file that represents a TSP problem.""" 60 | json_generator = read_from_path("tsp-5.tsp") 61 | graph = json_generator.graph 62 | 63 | settings = pf.PathFindingQuboGeneratorSettings( 64 | encoding_type=pf.EncodingType.ONE_HOT, 65 | n_paths=1, 66 | max_path_length=5, 67 | loops=True, 68 | ) 69 | 70 | manual_generator = pf.PathFindingQuboGenerator( 71 | objective_function=cf.MinimizePathLength([1]), graph=graph, settings=settings 72 | ) 73 | 74 | manual_generator.add_constraint(cf.PathIsValid([1])) 75 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 76 | check_equal(json_generator, manual_generator) 77 | 78 | 79 | def test_atsp() -> None: 80 | """Tests a tsplib input file that represents an ATSP problem.""" 81 | json_generator = read_from_path("atsp-5.atsp") 82 | graph = json_generator.graph 83 | 84 | settings = pf.PathFindingQuboGeneratorSettings( 85 | encoding_type=pf.EncodingType.ONE_HOT, 86 | n_paths=1, 87 | max_path_length=5, 88 | loops=True, 89 | ) 90 | 91 | manual_generator = pf.PathFindingQuboGenerator( 92 | objective_function=cf.MinimizePathLength([1]), graph=graph, settings=settings 93 | ) 94 | 95 | manual_generator.add_constraint(cf.PathIsValid([1])) 96 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 97 | check_equal(json_generator, manual_generator) 98 | 99 | 100 | def test_sop() -> None: 101 | """Tests a tsplib input file that represents a SOP problem.""" 102 | json_generator = read_from_path("sop-5.sop") 103 | graph = json_generator.graph 104 | 105 | settings = pf.PathFindingQuboGeneratorSettings( 106 | encoding_type=pf.EncodingType.ONE_HOT, 107 | n_paths=1, 108 | max_path_length=5, 109 | loops=False, 110 | ) 111 | 112 | manual_generator = pf.PathFindingQuboGenerator( 113 | objective_function=cf.MinimizePathLength([1]), graph=graph, settings=settings 114 | ) 115 | 116 | manual_generator.add_constraint(cf.PathIsValid([1])) 117 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 118 | manual_generator.add_constraint(cf.PrecedenceConstraint(1, 2, [1])) 119 | manual_generator.add_constraint(cf.PrecedenceConstraint(1, 5, [1])) 120 | manual_generator.add_constraint(cf.PrecedenceConstraint(2, 4, [1])) 121 | manual_generator.add_constraint(cf.PrecedenceConstraint(2, 5, [1])) 122 | check_equal(json_generator, manual_generator) 123 | 124 | 125 | def test_fail_cvrp() -> None: 126 | """Tests a tsplib input file that represents a CVRP problem. This should fail.""" 127 | with pytest.raises(ValueError, match="CVRP"): 128 | read_from_path("fail/cvrp-7.vrp") 129 | 130 | 131 | def test_with_forced_edges() -> None: 132 | """Tests a tsplib input file that includes forced edges.""" 133 | json_generator = read_from_path("forced-edges.tsp") 134 | graph = json_generator.graph 135 | 136 | settings = pf.PathFindingQuboGeneratorSettings( 137 | encoding_type=pf.EncodingType.ONE_HOT, 138 | n_paths=1, 139 | max_path_length=5, 140 | loops=True, 141 | ) 142 | 143 | manual_generator = pf.PathFindingQuboGenerator( 144 | objective_function=cf.MinimizePathLength([1]), graph=graph, settings=settings 145 | ) 146 | 147 | manual_generator.add_constraint(cf.PathIsValid([1])) 148 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1])) 149 | manual_generator.add_constraint(cf.PathContainsEdgesExactlyOnce([(1, 5), (5, 3)], [1])) 150 | check_equal(json_generator, manual_generator) 151 | -------------------------------------------------------------------------------- /tests/pathfinder/test_pathfinder.py: -------------------------------------------------------------------------------- 1 | """Tests the end-to-end performance of the pathfinder module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | 7 | import mqt.qubomaker as qm 8 | import mqt.qubomaker.pathfinder as pf 9 | import mqt.qubomaker.pathfinder.cost_functions as cf 10 | 11 | from .utils_test import get_test_graph_small, paths_equal_with_loops, paths_to_assignment_list 12 | 13 | TEST_GRAPH = get_test_graph_small() 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "encoding_type", 18 | [ 19 | cf.EncodingType.ONE_HOT, 20 | cf.EncodingType.DOMAIN_WALL, 21 | cf.EncodingType.BINARY, 22 | ], 23 | ) 24 | def test_decoding(encoding_type: pf.EncodingType) -> None: 25 | """Test the decoding of a solution.""" 26 | generator = setup_tsp() 27 | generator.settings.encoding_type = encoding_type 28 | generator2 = setup_tsp() 29 | generator2.settings.n_paths = 2 30 | generator2.settings.encoding_type = encoding_type 31 | s1 = [[1, 2, 4, 3]] 32 | s2 = [[1, 3, 3, 2]] 33 | s3 = [[1, 4]] 34 | s4 = [[1, 2], [4, 3]] 35 | 36 | d1 = generator.decode_bit_array(paths_to_assignment_list(s1, 4, 4, encoding_type)) 37 | d2 = generator.decode_bit_array(paths_to_assignment_list(s2, 4, 4, encoding_type)) 38 | d3 = generator.decode_bit_array(paths_to_assignment_list(s3, 4, 4, encoding_type)) 39 | d4 = generator2.decode_bit_array(paths_to_assignment_list(s4, 4, 4, encoding_type)) 40 | 41 | assert s1 == d1 42 | assert s2 == d2 43 | assert s3 == d3 44 | assert s4 == d4 45 | assert s1 != d2 46 | assert s2 != d3 47 | assert s3 != d4 48 | assert s4 != d1 49 | 50 | 51 | @pytest.mark.parametrize( 52 | "encoding_type", 53 | [ 54 | cf.EncodingType.ONE_HOT, 55 | cf.EncodingType.DOMAIN_WALL, 56 | cf.EncodingType.BINARY, 57 | ], 58 | ) 59 | def test_tsp(encoding_type: pf.EncodingType) -> None: 60 | """Test the module with a TSP problem.""" 61 | settings = pf.PathFindingQuboGeneratorSettings( 62 | encoding_type=encoding_type, 63 | n_paths=1, 64 | max_path_length=4, 65 | loops=True, 66 | ) 67 | generator = pf.PathFindingQuboGenerator( 68 | objective_function=cf.MinimizePathLength([1]), 69 | graph=TEST_GRAPH, 70 | settings=settings, 71 | ) 72 | generator.add_constraint(cf.PathIsValid([1])) 73 | generator.add_constraint(cf.PathContainsVerticesExactlyOnce(TEST_GRAPH.all_vertices, [1])) 74 | 75 | if encoding_type != cf.EncodingType.BINARY: 76 | # Binary encoding is too complex for evaluation in this test. 77 | qubo_matrix = generator.construct_qubo_matrix() 78 | optimal_solution, _ = qm.optimize_classically(qubo_matrix) 79 | 80 | path_representation = generator.decode_bit_array(optimal_solution) 81 | 82 | assert paths_equal_with_loops(path_representation[0], [4, 1, 2, 3]) 83 | assert generator.get_cost(optimal_solution) == 20.0 84 | 85 | # Test if constructing the Quantum Circuit works without a problem. 86 | generator.construct_qaoa_circuit() 87 | 88 | solution = paths_to_assignment_list([[4, 1, 2, 3]], 4, 4, encoding_type) 89 | assert generator.get_cost(solution) == 20.0 90 | 91 | 92 | @pytest.mark.parametrize( 93 | "encoding_type", 94 | [ 95 | cf.EncodingType.ONE_HOT, 96 | cf.EncodingType.DOMAIN_WALL, 97 | cf.EncodingType.BINARY, 98 | ], 99 | ) 100 | def test_2dpp(encoding_type: pf.EncodingType) -> None: 101 | """Test the module with a 2DPP problem.""" 102 | settings = pf.PathFindingQuboGeneratorSettings( 103 | encoding_type=encoding_type, 104 | n_paths=2, 105 | max_path_length=2, 106 | loops=False, 107 | ) 108 | generator = pf.PathFindingQuboGenerator( 109 | objective_function=cf.MinimizePathLength([1, 2]), 110 | graph=TEST_GRAPH, 111 | settings=settings, 112 | ) 113 | generator.add_constraint(cf.PathIsValid([1, 2])) 114 | generator.add_constraint(cf.PathsShareNoVertices(1, 2)) 115 | generator.add_constraint(cf.PathStartsAt([1], 1)) 116 | generator.add_constraint(cf.PathStartsAt([2], 2)) 117 | generator.add_constraint(cf.PathEndsAt([3], 1)) 118 | generator.add_constraint(cf.PathEndsAt([4], 2)) 119 | 120 | solution = paths_to_assignment_list([[1, 3], [2, 4]], 4, 2, encoding_type) 121 | assert generator.get_cost(solution) == 9.0 122 | 123 | 124 | def setup_tsp() -> pf.PathFindingQuboGenerator: 125 | """Set up a `PathFindingQuboGenerator` for a TSP problem.""" 126 | settings = pf.PathFindingQuboGeneratorSettings( 127 | encoding_type=pf.EncodingType.ONE_HOT, 128 | n_paths=1, 129 | max_path_length=4, 130 | loops=True, 131 | ) 132 | generator = pf.PathFindingQuboGenerator( 133 | objective_function=cf.MinimizePathLength([1]), 134 | graph=TEST_GRAPH, 135 | settings=settings, 136 | ) 137 | generator.add_constraint(cf.PathIsValid([1])) 138 | generator.add_constraint(cf.PathContainsVerticesExactlyOnce(TEST_GRAPH.all_vertices, [1])) 139 | return generator 140 | 141 | 142 | def test_fail_get_cost_variable_count() -> None: 143 | """Tests whether passing an incorrect number of variables to `get_cost()` fails.""" 144 | generator = setup_tsp() 145 | with pytest.raises(ValueError, match="Invalid assignment length"): 146 | generator.get_cost([1, 0]) 147 | 148 | 149 | def test_fail_get_cost_variable_values() -> None: 150 | """Tests whether passing non-binary variables to `get_cost()` fails.""" 151 | generator = setup_tsp() 152 | with pytest.raises(ValueError, match="Provided values are not binary \\(1/0\\)"): 153 | generator.get_cost([2, 0]) 154 | 155 | with pytest.raises(ValueError, match="Provided values are not binary \\(1/0\\)"): 156 | generator.get_cost([-1, 0]) 157 | -------------------------------------------------------------------------------- /tests/pathfinder/test_json_input.py: -------------------------------------------------------------------------------- 1 | """Tests the correctness of the JSON input format.""" 2 | 3 | from __future__ import annotations 4 | 5 | from pathlib import Path 6 | 7 | import pytest 8 | 9 | import mqt.qubomaker.pathfinder as pf 10 | import mqt.qubomaker.pathfinder.cost_functions as cf 11 | 12 | from .utils_test import check_equal, get_test_graph 13 | 14 | TEST_GRAPH = get_test_graph() 15 | 16 | 17 | def read_from_path(path: str) -> pf.PathFindingQuboGenerator: 18 | """Reads a JSON input file and returns the corresponding `PathFindingQuboGenerator`. 19 | 20 | Args: 21 | path (str): The path to the JSON input file. 22 | 23 | Returns: 24 | pf.PathFindingQuboGenerator: The corresponding `PathFindingQuboGenerator`. 25 | """ 26 | with Path.open(Path("tests") / "pathfinder" / "resources" / "json" / path) as file: 27 | return pf.PathFindingQuboGenerator.from_json(file.read(), TEST_GRAPH) 28 | 29 | 30 | def test_all_constraints() -> None: 31 | """Tests a JSON input file that includes all constraints.""" 32 | json_generator = read_from_path("all.json") 33 | 34 | settings = pf.PathFindingQuboGeneratorSettings( 35 | encoding_type=pf.EncodingType.ONE_HOT, 36 | n_paths=3, 37 | max_path_length=4, 38 | loops=True, 39 | ) 40 | manual_generator = pf.PathFindingQuboGenerator( 41 | objective_function=cf.MinimizePathLength([1]), graph=TEST_GRAPH, settings=settings 42 | ) 43 | 44 | manual_generator.add_constraint(cf.PathContainsEdgesAtLeastOnce([(1, 2)], [1])) 45 | manual_generator.add_constraint(cf.PathContainsEdgesAtMostOnce([(1, 2)], [1])) 46 | manual_generator.add_constraint(cf.PathContainsEdgesExactlyOnce([(1, 2)], [1])) 47 | manual_generator.add_constraint(cf.PathContainsVerticesAtLeastOnce([1, 2, 3], [1])) 48 | manual_generator.add_constraint(cf.PathContainsVerticesAtMostOnce([1, 2, 3], [1])) 49 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce([1, 2, 3], [1])) 50 | manual_generator.add_constraint(cf.PathEndsAt([1, 2, 3], 1)) 51 | manual_generator.add_constraint(cf.PathStartsAt([1, 2, 3], 1)) 52 | manual_generator.add_constraint(cf.PathPositionIs(2, [1, 2, 3], 1)) 53 | manual_generator.add_constraint(cf.PrecedenceConstraint(1, 2, [1])) 54 | manual_generator.add_constraint(cf.PathsShareNoEdges(1, 2)) 55 | manual_generator.add_constraint(cf.PathsShareNoEdges(2, 3)) 56 | manual_generator.add_constraint(cf.PathsShareNoEdges(1, 3)) 57 | manual_generator.add_constraint(cf.PathsShareNoVertices(1, 2)) 58 | manual_generator.add_constraint(cf.PathsShareNoVertices(2, 3)) 59 | manual_generator.add_constraint(cf.PathsShareNoVertices(1, 3)) 60 | manual_generator.add_constraint(cf.PathIsValid([1, 2, 3])) 61 | 62 | check_equal(json_generator, manual_generator) 63 | 64 | 65 | def test_alternative_options() -> None: 66 | """Tests a JSON input file that includes alternative (non-default) options.""" 67 | json_generator = read_from_path("alternative_options.json") 68 | 69 | settings = pf.PathFindingQuboGeneratorSettings( 70 | encoding_type=pf.EncodingType.BINARY, 71 | n_paths=2, 72 | max_path_length=5, 73 | loops=False, 74 | ) 75 | manual_generator = pf.PathFindingQuboGenerator(objective_function=None, graph=TEST_GRAPH, settings=settings) 76 | 77 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(TEST_GRAPH.all_vertices, [1])) 78 | manual_generator.add_constraint(cf.PathContainsVerticesAtLeastOnce([1, 2], [1])) 79 | manual_generator.add_constraint(cf.PathContainsVerticesAtMostOnce(TEST_GRAPH.all_vertices, [1, 2])) 80 | manual_generator.add_constraint(cf.PrecedenceConstraint(1, 2, [1])) 81 | manual_generator.add_constraint(cf.PrecedenceConstraint(2, 3, [1])) 82 | 83 | check_equal(json_generator, manual_generator) 84 | 85 | 86 | def test_suggest_encoding() -> None: 87 | """Tests the encoding suggestion feature for a JSON input file.""" 88 | with Path.open(Path("tests") / "pathfinder" / "resources" / "json" / "with_weight.json") as file: 89 | j = file.read() 90 | assert pf.PathFindingQuboGenerator.suggest_encoding(j, TEST_GRAPH) == pf.EncodingType.ONE_HOT 91 | 92 | 93 | def test_with_weight() -> None: 94 | """Tests a JSON input file that includes weights for some constraints.""" 95 | json_generator = read_from_path("with_weight.json") 96 | 97 | settings = pf.PathFindingQuboGeneratorSettings( 98 | encoding_type=pf.EncodingType.DOMAIN_WALL, 99 | n_paths=1, 100 | max_path_length=5, 101 | loops=False, 102 | ) 103 | manual_generator = pf.PathFindingQuboGenerator( 104 | objective_function=cf.MaximizePathLength([1]), graph=TEST_GRAPH, settings=settings 105 | ) 106 | 107 | manual_generator.add_constraint(cf.PathContainsVerticesExactlyOnce(TEST_GRAPH.all_vertices, [1]), weight=500) 108 | 109 | check_equal(json_generator, manual_generator) 110 | 111 | 112 | def test_fail_excess_field() -> None: 113 | """Tests a JSON input file that should fail because it includes an excess field.""" 114 | with pytest.raises(ValueError, match="JSON"): 115 | read_from_path("fail/excess_field.json") 116 | 117 | 118 | def test_fail_missing_field() -> None: 119 | """Tests a JSON input file that should fail because it is missing a field.""" 120 | with pytest.raises(ValueError, match="JSON"): 121 | read_from_path("fail/missing_field.json") 122 | 123 | 124 | def test_fail_too_few_elements() -> None: 125 | """Tests a JSON input file that should fail because some options have too few elements.""" 126 | with pytest.raises(ValueError, match="JSON"): 127 | read_from_path("fail/too_few_elements.json") 128 | 129 | 130 | def test_fail_unknown_type() -> None: 131 | """Tests a JSON input file that should fail because it includes an unknown type.""" 132 | with pytest.raises(ValueError, match="JSON"): 133 | read_from_path("fail/unknown_type.json") 134 | -------------------------------------------------------------------------------- /.github/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Thank you for your interest in contributing to this project. 5 | We value contributions from people with all levels of experience. 6 | In particular if this is your first pull request not everything has to be perfect. 7 | We will guide you through the process. 8 | 9 | We use GitHub to `host code <https://github.com/cda-tum/mqt-qubomaker>`_, to `track issues and feature requests <https://github.com/cda-tum/mqt-qubomaker/issues>`_, as well as accept `pull requests <https://github.com/cda-tum/mqt-qubomaker/pulls>`_. 10 | See https://docs.github.com/en/get-started/quickstart for a general introduction to working with GitHub and contributing to projects. 11 | 12 | Types of Contributions 13 | ###################### 14 | 15 | You can contribute in several ways: 16 | 17 | - 🐛 Report Bugs 18 | Report bugs at https://github.com/cda-tum/mqt-qubomaker/issues using the *🐛 Bug report* issue template. Please make sure to fill out all relevant information in the respective issue form. 19 | 20 | - 🐛 Fix Bugs 21 | Look through the `GitHub Issues <https://github.com/cda-tum/mqt-qubomaker/issues>`_ for bugs. Anything tagged with "bug" is open to whoever wants to try and fix it. 22 | 23 | - ✨ Propose New Features 24 | Propose new features at https://github.com/cda-tum/mqt-qubomaker/issues using the *✨ Feature request* issue template. Please make sure to fill out all relevant information in the respective issue form. 25 | 26 | - ✨ Implement New Features 27 | Look through the `GitHub Issues <https://github.com/cda-tum/mqt-qubomaker/issues>`_ for features. Anything tagged with "feature" is open to whoever wants to implement it. We highly appreciate external contributions to the project. 28 | 29 | - 📝 Write Documentation 30 | MQT QUBOMaker could always use some more `documentation <https://mqt.readthedocs.io/projects/qubomaker>`_, and we appreciate any help with that. 31 | 32 | 🎉 Get Started 33 | ############## 34 | 35 | Ready to contribute? Check out the :doc:`Development Guide <DevelopmentGuide>` to set up MQT QUBOMaker for local development and learn about the style guidelines and conventions used throughout the project. 36 | 37 | We value contributions from people with all levels of experience. 38 | In particular if this is your first PR not everything has to be perfect. 39 | We will guide you through the PR process. 40 | Nevertheless, please try to follow the guidelines below as well as you can to help make the PR process quick and smooth. 41 | 42 | Core Guidelines 43 | ############### 44 | 45 | - `"Commit early and push often" <https://www.worklytics.co/blog/commit-early-push-often>`_. 46 | - Write meaningful commit messages (preferably using `gitmoji <https://gitmoji.dev>`_ to give additional context to your commits). 47 | - Focus on a single feature/bug at a time and only touch relevant files. Split multiple features into multiple contributions. 48 | - If you added a new feature, you should add tests that ensure it works as intended. Furthermore, the new feature should be documented appropriately. 49 | - If you fixed a bug, you should add tests that demonstrate that the bug has been fixed. 50 | - Document your code thoroughly and write readable code. 51 | - Keep your code clean. Remove any debug statements, left-over comments, or code unrelated to your contribution. 52 | - Run :code:`pre-commit run -a` to check your code for style and linting errors before committing. 53 | 54 | Pull Request Workflow 55 | ##################### 56 | 57 | - Create PRs early. It is ok to create work-in-progress PRs. You may mark these as draft PRs on GitHub. 58 | - Describe your PR. Start with a descriptive title, reference any related issues by including the issue number in the PR description, and add a comprehensive description of the changes. We provide a PR template that you can (and should) follow to create a PR. 59 | - Whenever a PR is created or updated, several workflows on all supported platforms and versions of Python are executed. Make sure your PR passes *all* these continuous integration (CI) checks. Here are some tips for finding the cause of certain failures: 60 | - If any of the :code:`Python Packaging/\*` checks fail, this indicates an error in the creation of the Python wheels and/or source distribution. Look through the respective logs on GitHub for any error or failure messages. 61 | - If any of the :code:`Python/\*` checks fail, this indicates an error in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. 62 | - If any of the :code:`codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. 63 | - If the :code:`docs/readthedocs.org:mqt-qubomaker` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. 64 | - If the :code:`pre-commit.ci` check fails, some of the :code:`pre-commit` checks failed and could not be fixed automatically by the *pre-commit.ci* bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. 65 | 66 | - Once your PR is ready, change it from a draft PR to a regular PR and request a review from one of the project maintainers. 67 | - If your PR gets a "Changes requested" review, you will need to address the feedback and update your PR by pushing to the same branch. You don't need to close the PR and open a new one. Respond to review comments on the PR (e.g., with "done 👍"). Be sure to re-request review once you have made changes after a code review so that maintainers know that the requests have been addressed. 68 | 69 | .. raw:: html 70 | 71 | <hr> 72 | 73 | This document was inspired by and partially adapted from 74 | 75 | - https://matplotlib.org/stable/devel/coding_guide.html 76 | - https://opensource.creativecommons.org/contributing-code/pr-guidelines/ 77 | - https://yeoman.io/contributing/pull-request.html 78 | - https://github.com/scikit-build/scikit-build 79 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/pathfinder/tsplib.py: -------------------------------------------------------------------------------- 1 | """Provides support for the TSPLib format as input for the pathfinding QuboMaker.""" 2 | 3 | from __future__ import annotations 4 | 5 | import importlib.util 6 | from typing import TYPE_CHECKING 7 | 8 | import mqt.qubomaker 9 | import mqt.qubomaker.pathfinder 10 | from mqt.qubomaker.pathfinder import cost_functions 11 | 12 | if TYPE_CHECKING: 13 | import contextlib 14 | 15 | import networkx as nx 16 | 17 | with contextlib.suppress(ImportError): 18 | from tsplib95.models import StandardProblem 19 | 20 | 21 | def __check_forced_edges(problem: StandardProblem) -> cost_functions.CostFunction | None: 22 | if not problem.fixed_edges: 23 | return None 24 | forced_edges: list[tuple[int, int]] = [] 25 | for i, j in problem.fixed_edges: 26 | forced_edges.append((i + 1, j + 1)) 27 | return cost_functions.PathContainsEdgesExactlyOnce(forced_edges, [1]) 28 | 29 | 30 | def to_graph(g: nx.Graph) -> mqt.qubomaker.Graph: 31 | """Transforms a networkx graph into a Graph object. 32 | 33 | Args: 34 | g (nx.Graph): The networkx graph to be transformed. 35 | 36 | Returns: 37 | Graph: The transformed graph. 38 | """ 39 | return mqt.qubomaker.Graph(g.number_of_nodes(), g.edges.data("weight")) 40 | 41 | 42 | def __tsp( 43 | problem: StandardProblem, encoding_type: cost_functions.EncodingType 44 | ) -> mqt.qubomaker.pathfinder.PathFindingQuboGenerator: 45 | """Constructs a QUBO generator for a TSP problem. 46 | 47 | Args: 48 | problem (StandardProblem): The TSP problem. 49 | encoding_type (cost_functions.EncodingType): The desired encoding type. 50 | 51 | Returns: 52 | PathFindingQuboGenerator: The constructed QUBO generator. 53 | """ 54 | g = to_graph(problem.get_graph()) 55 | settings = mqt.qubomaker.pathfinder.PathFindingQuboGeneratorSettings(encoding_type, 1, g.n_vertices, True) 56 | generator = mqt.qubomaker.pathfinder.PathFindingQuboGenerator(cost_functions.MinimizePathLength([1]), g, settings) 57 | 58 | generator.add_constraint(cost_functions.PathIsValid([1])) 59 | generator.add_constraint(cost_functions.PathContainsVerticesExactlyOnce(g.all_vertices, [1])) 60 | 61 | generator.add_constraint_if_exists(__check_forced_edges(problem)) 62 | 63 | return generator 64 | 65 | 66 | def __hcp( 67 | problem: StandardProblem, encoding_type: cost_functions.EncodingType 68 | ) -> mqt.qubomaker.pathfinder.PathFindingQuboGenerator: 69 | """Constructs a QUBO generator for a HCP problem. 70 | 71 | Args: 72 | problem (StandardProblem): The HCP problem. 73 | encoding_type (cost_functions.EncodingType): The desired encoding type. 74 | 75 | Returns: 76 | PathFindingQuboGenerator: The constructed QUBO generator. 77 | """ 78 | g = to_graph(problem.get_graph()) 79 | settings = mqt.qubomaker.pathfinder.PathFindingQuboGeneratorSettings(encoding_type, 1, g.n_vertices, True) 80 | generator = mqt.qubomaker.pathfinder.PathFindingQuboGenerator(None, g, settings) 81 | 82 | generator.add_constraint(cost_functions.PathIsValid([1])) 83 | generator.add_constraint(cost_functions.PathContainsVerticesExactlyOnce(g.all_vertices, [1])) 84 | 85 | generator.add_constraint_if_exists(__check_forced_edges(problem)) 86 | 87 | return generator 88 | 89 | 90 | def __sop( 91 | problem: StandardProblem, encoding_type: cost_functions.EncodingType 92 | ) -> mqt.qubomaker.pathfinder.PathFindingQuboGenerator: 93 | """Constructs a QUBO generator for a SOP problem. 94 | 95 | Args: 96 | problem (StandardProblem): The SOP problem. 97 | encoding_type (cost_functions.EncodingType): The desired encoding type. 98 | 99 | Returns: 100 | PathFindingQuboGenerator: The constructed QUBO generator. 101 | """ 102 | g = to_graph(problem.get_graph()) 103 | settings = mqt.qubomaker.pathfinder.PathFindingQuboGeneratorSettings(encoding_type, 1, g.n_vertices, False) 104 | generator = mqt.qubomaker.pathfinder.PathFindingQuboGenerator(cost_functions.MinimizePathLength([1]), g, settings) 105 | generator.add_constraint(cost_functions.PathIsValid([1])) 106 | generator.add_constraint(cost_functions.PathContainsVerticesExactlyOnce(g.all_vertices, [1])) 107 | sop_pairs = [] 108 | for u, v, weight in problem.get_graph().edges.data("weight"): 109 | if weight == -1: 110 | sop_pairs.append((v + 1, u + 1)) 111 | for u, v in sop_pairs: 112 | generator.add_constraint(cost_functions.PrecedenceConstraint(u, v, [1])) 113 | 114 | generator.add_constraint_if_exists(__check_forced_edges(problem)) 115 | 116 | return generator 117 | 118 | 119 | def from_tsplib_problem( 120 | problem: StandardProblem, encoding_type: cost_functions.EncodingType 121 | ) -> mqt.qubomaker.pathfinder.PathFindingQuboGenerator: 122 | """Constructs a QUBO generator for a given problem in TSPLib format. 123 | 124 | Args: 125 | problem (StandardProblem): The TSPLib problem. 126 | encoding_type (cost_functions.EncodingType): The desired encoding type. 127 | 128 | Raises: 129 | NotImplementedError: If a CVRP problem is given, as this problem type cannot be solved by the pathfinder. 130 | ValueError: If an unknown problem type is given. 131 | 132 | Returns: 133 | PathFindingQuboGenerator: The constructed QUBO generator. 134 | """ 135 | try: 136 | importlib.util.find_spec("tsplib95") 137 | except ImportError: 138 | msg = "The 'tsplib95' package is required to use this function." 139 | raise RuntimeError(msg) from None 140 | 141 | if problem.type in {"TSP", "ATSP"}: 142 | return __tsp(problem, encoding_type) 143 | if problem.type == "HCP": 144 | return __hcp(problem, encoding_type) 145 | if problem.type == "SOP": 146 | return __sop(problem, encoding_type) 147 | if problem.type == "CVRP": 148 | msg = "CVRP is not supported as it is not a pure path-finding problem." 149 | raise ValueError(msg) 150 | msg = "Problem type not supported." 151 | raise ValueError(msg) 152 | -------------------------------------------------------------------------------- /tests/test_device.py: -------------------------------------------------------------------------------- 1 | """Tests the features of the device representation module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | 7 | from mqt.qubomaker import Calibration 8 | 9 | 10 | @pytest.fixture 11 | def sample_device_heavy_hex() -> Calibration: 12 | """Provides a test device calibration with 500 qubits and nearest-neighbor connectivity.""" 13 | heavy_hex_coupling = [] 14 | 15 | def get_qubit_index(row: int, cell: int, off: bool) -> int: 16 | """Converts a (row, cell, off) tuple to a qubit index. 17 | 18 | Args: 19 | row (int): The row of the qubit. 20 | cell (int): The cell of the qubit. 21 | off (bool): Whether the qubit is in a main row or an off row. 22 | 23 | Returns: 24 | int: The qubit index. 25 | """ 26 | if not off: 27 | return 19 * row + cell 28 | return 19 * row + 15 + cell 29 | 30 | for row in range(7): 31 | for cell in range(15): 32 | current = get_qubit_index(row, cell, False) 33 | if cell != 14: 34 | neighbor = get_qubit_index(row, cell + 1, False) 35 | heavy_hex_coupling.append((current, neighbor)) 36 | if cell % 4 == 0: 37 | if row % 2 == 0 and row != 6: 38 | below = get_qubit_index(row, cell // 4, True) 39 | heavy_hex_coupling.append((current, below)) 40 | elif row % 2 == 1: 41 | above = get_qubit_index(row - 1, cell // 4, True) 42 | heavy_hex_coupling.append((current, above)) 43 | if cell % 4 == 2: 44 | if row % 2 == 0 and row != 0: 45 | above = get_qubit_index(row - 1, cell // 4, True) 46 | heavy_hex_coupling.append((current, above)) 47 | elif row % 2 == 1: 48 | below = get_qubit_index(row, cell // 4, True) 49 | heavy_hex_coupling.append((current, below)) 50 | 51 | longest_hamiltonian_path = [] 52 | for row in range(7): 53 | for cell in range(15): 54 | if row == 0 and cell == 0: 55 | continue # we skip the first qubit. 56 | current = get_qubit_index(row, cell if row % 2 == 1 else (14 - cell), False) 57 | longest_hamiltonian_path.append(current) 58 | if row != 6: 59 | longest_hamiltonian_path.append(get_qubit_index(row, 3 if row % 2 == 1 else 0, True)) 60 | 61 | num_qubits = max(max(pair) for pair in heavy_hex_coupling) + 1 62 | 63 | cal = Calibration( 64 | num_qubits=num_qubits, 65 | one_qubit=dict.fromkeys(range(num_qubits), 0.99), 66 | two_qubit=dict.fromkeys(heavy_hex_coupling, 0.99), 67 | measurement_confidences=dict.fromkeys(range(num_qubits), 0.99), 68 | basis_gates=["cz", "id", "rx", "rz", "rzz", "sx", "x"], 69 | t1=dict.fromkeys(range(num_qubits), 0.0001), 70 | t2=dict.fromkeys(range(num_qubits), 0.0002), 71 | ) 72 | cal.test_tags = {} # type: ignore[attr-defined] 73 | cal.test_tags["longest_hamiltonian_path"] = longest_hamiltonian_path # type: ignore[attr-defined] 74 | cal.test_tags["longest_heavy_chain"] = [ # type: ignore[attr-defined] 75 | 12, 76 | 31, 77 | 29, 78 | 27, 79 | 25, 80 | 23, 81 | 21, 82 | 40, 83 | 42, 84 | 44, 85 | 46, 86 | 48, 87 | 50, 88 | 69, 89 | 67, 90 | 65, 91 | 63, 92 | 61, 93 | 59, 94 | 78, 95 | 80, 96 | 82, 97 | 84, 98 | 86, 99 | 88, 100 | 107, 101 | 105, 102 | 103, 103 | 101, 104 | 99, 105 | 97, 106 | 116, 107 | ] 108 | return cal 109 | 110 | 111 | def test_connected_qubit_chain(sample_device_heavy_hex: Calibration) -> None: 112 | """Tests the computation of the longest hamiltonian path through the device topology. 113 | 114 | Args: 115 | sample_device_heavy_hex (Calibration): The testing device calibration. 116 | """ 117 | chain = sample_device_heavy_hex.get_connected_qubit_chain() 118 | assert chain == sample_device_heavy_hex.test_tags["longest_hamiltonian_path"] # type: ignore[attr-defined] 119 | 120 | 121 | def test_heavy_chain(sample_device_heavy_hex: Calibration) -> None: 122 | """Tests the computation of the longest heavy chain through the device topology. 123 | 124 | Args: 125 | sample_device_heavy_hex (Calibration): The testing device calibration. 126 | """ 127 | chain = sample_device_heavy_hex.get_heavy_chain() 128 | assert chain == sample_device_heavy_hex.test_tags["longest_heavy_chain"] # type: ignore[attr-defined] 129 | 130 | 131 | def test_shared_neighbor(sample_device_heavy_hex: Calibration) -> None: 132 | """Tests the computation of a shared neighbor between two qubits. 133 | 134 | Args: 135 | sample_device_heavy_hex (Calibration): The testing device calibration. 136 | """ 137 | assert sample_device_heavy_hex.get_shared_neighbor(0, 2) == 1 138 | assert sample_device_heavy_hex.get_shared_neighbor(0, 19) == 15 139 | assert sample_device_heavy_hex.get_shared_neighbor(1, 3) == 2 140 | assert sample_device_heavy_hex.get_shared_neighbor(124, 105) == 112 141 | 142 | 143 | def test_from_dict(sample_device_heavy_hex: Calibration) -> None: 144 | """Tests the deserialization of a Calibration object through the `from_dict` method. 145 | 146 | Args: 147 | sample_device_heavy_hex (Calibration): The testing device calibration. 148 | """ 149 | obj = { 150 | "num_qubits": len(sample_device_heavy_hex.one_qubit), 151 | "one_qubit": sample_device_heavy_hex.one_qubit, 152 | "two_qubit": sample_device_heavy_hex.two_qubit, 153 | "measurement_confidences": sample_device_heavy_hex.measurement_confidences, 154 | "t1": sample_device_heavy_hex.t1, 155 | "t2": sample_device_heavy_hex.t2, 156 | } 157 | 158 | c = Calibration.from_dict(obj, ["cz", "id", "rx", "rz", "rzz", "sx", "x"]) 159 | assert c == sample_device_heavy_hex 160 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 - 2025 Chair for Design Automation, TUM 2 | # Copyright (c) 2025 Munich Quantum Software Company GmbH 3 | # All rights reserved. 4 | # 5 | # SPDX-License-Identifier: MIT 6 | # 7 | # Licensed under the MIT License 8 | 9 | """Nox sessions.""" 10 | 11 | from __future__ import annotations 12 | 13 | import argparse 14 | import contextlib 15 | import os 16 | import shutil 17 | import tempfile 18 | from typing import TYPE_CHECKING 19 | 20 | import nox 21 | 22 | if TYPE_CHECKING: 23 | from collections.abc import Generator, Sequence 24 | 25 | nox.needs_version = ">=2024.3.2" 26 | nox.options.default_venv_backend = "uv" 27 | 28 | nox.options.sessions = ["lint", "tests", "minimums"] 29 | 30 | PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] 31 | 32 | if os.environ.get("CI", None): 33 | nox.options.error_on_missing_interpreters = True 34 | 35 | 36 | @contextlib.contextmanager 37 | def preserve_lockfile() -> Generator[None]: 38 | """Preserve the lockfile by moving it to a temporary directory.""" 39 | with tempfile.TemporaryDirectory() as temp_dir_name: 40 | shutil.move("uv.lock", f"{temp_dir_name}/uv.lock") 41 | try: 42 | yield 43 | finally: 44 | shutil.move(f"{temp_dir_name}/uv.lock", "uv.lock") 45 | 46 | 47 | @nox.session(reuse_venv=True) 48 | def lint(session: nox.Session) -> None: 49 | """Run the linter.""" 50 | if shutil.which("pre-commit") is None: 51 | session.install("pre-commit") 52 | 53 | session.run("pre-commit", "run", "--all-files", *session.posargs, external=True) 54 | 55 | 56 | def _run_tests( 57 | session: nox.Session, 58 | *, 59 | install_args: Sequence[str] = (), 60 | extra_command: Sequence[str] = (), 61 | pytest_run_args: Sequence[str] = (), 62 | ) -> None: 63 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 64 | if shutil.which("cmake") is None and shutil.which("cmake3") is None: 65 | session.install("cmake") 66 | if shutil.which("ninja") is None: 67 | session.install("ninja") 68 | 69 | # install build and test dependencies on top of the existing environment 70 | session.run( 71 | "uv", 72 | "sync", 73 | "--inexact", 74 | "--only-group", 75 | "build", 76 | "--only-group", 77 | "test", 78 | *install_args, 79 | env=env, 80 | ) 81 | print(session.python) 82 | add_tsplib = [] if session.python == "3.13" else ["--extra", "tsplib"] 83 | 84 | session.run( 85 | "uv", 86 | "sync", 87 | "--inexact", 88 | "--no-dev", # do not auto-install dev dependencies 89 | "--no-build-isolation-package", 90 | "mqt-qubomaker", # build the project without isolation 91 | "--extra", 92 | "check", 93 | *add_tsplib, 94 | *install_args, 95 | env=env, 96 | ) 97 | if extra_command: 98 | session.run(*extra_command, env=env) 99 | if "--cov" in session.posargs: 100 | # try to use the lighter-weight `sys.monitoring` coverage core 101 | env["COVERAGE_CORE"] = "sysmon" 102 | session.run( 103 | "uv", 104 | "run", 105 | "--no-sync", # do not sync as everything is already installed 106 | *install_args, 107 | "pytest", 108 | *pytest_run_args, 109 | *session.posargs, 110 | "--cov-config=pyproject.toml", 111 | env=env, 112 | ) 113 | 114 | 115 | @nox.session(reuse_venv=True, python=PYTHON_ALL_VERSIONS) 116 | def tests(session: nox.Session) -> None: 117 | """Run the test suite.""" 118 | _run_tests(session) 119 | 120 | 121 | @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) 122 | def minimums(session: nox.Session) -> None: 123 | """Test the minimum versions of dependencies.""" 124 | with preserve_lockfile(): 125 | _run_tests( 126 | session, 127 | install_args=["--resolution=lowest-direct"], 128 | pytest_run_args=["-Wdefault"], 129 | ) 130 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 131 | session.run("uv", "tree", "--frozen", env=env) 132 | 133 | 134 | @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) 135 | def qiskit(session: nox.Session) -> None: 136 | """Tests against the latest version of Qiskit.""" 137 | with preserve_lockfile(): 138 | _run_tests( 139 | session, 140 | extra_command=["uv", "pip", "install", "qiskit[qasm3-import] @ git+https://github.com/Qiskit/qiskit.git"], 141 | ) 142 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 143 | session.run("uv", "pip", "show", "qiskit", env=env) 144 | 145 | 146 | @nox.session(reuse_venv=True) 147 | def docs(session: nox.Session) -> None: 148 | """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" 149 | parser = argparse.ArgumentParser() 150 | parser.add_argument("-b", dest="builder", default="html", help="Build target (default: html)") 151 | args, posargs = parser.parse_known_args(session.posargs) 152 | 153 | serve = args.builder == "html" and session.interactive 154 | if serve: 155 | session.install("sphinx-autobuild") 156 | 157 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 158 | # install build and docs dependencies on top of the existing environment 159 | session.run( 160 | "uv", 161 | "sync", 162 | "--inexact", 163 | "--only-group", 164 | "build", 165 | "--only-group", 166 | "docs", 167 | env=env, 168 | ) 169 | 170 | shared_args = [ 171 | "-n", # nitpicky mode 172 | "-T", # full tracebacks 173 | f"-b={args.builder}", 174 | "docs", 175 | f"docs/_build/{args.builder}", 176 | *posargs, 177 | ] 178 | 179 | session.run( 180 | "uv", 181 | "run", 182 | "--no-dev", # do not auto-install dev dependencies 183 | "--no-build-isolation-package", 184 | "mqt-qubomaker", # build the project without isolation 185 | "sphinx-autobuild" if serve else "sphinx-build", 186 | *shared_args, 187 | env=env, 188 | ) 189 | -------------------------------------------------------------------------------- /tests/test_circuit_generation.py: -------------------------------------------------------------------------------- 1 | """This module tests the generation of QAOA circuits (both the standard and heavy-hex optimized variants).""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import sympy as sp 7 | 8 | from mqt.qubomaker import Calibration, QuboGenerator 9 | 10 | 11 | @pytest.fixture 12 | def simple_generator() -> QuboGenerator: 13 | """Provides a simple QuboGenerator instance for testing. 14 | 15 | Returns: 16 | QuboGenerator: An instance of QuboGenerator with the objective function x1 * x2 * x3 * x4 * x5. 17 | """ 18 | symbols = [sp.Symbol(f"x_{i + 1}") for i in range(5)] 19 | obj = sp.Mul(*symbols) 20 | generator = QuboGenerator(obj) 21 | generator.disable_caching = True 22 | return generator 23 | 24 | 25 | @pytest.fixture 26 | def simple_device() -> Calibration: 27 | """Provides a test device calibration with 500 qubits and nearest-neighbor connectivity.""" 28 | heavy_hex_coupling = [] 29 | 30 | def get_qubit_index(row: int, cell: int, off: bool) -> int: 31 | """Converts a (row, cell, off) tuple to a qubit index. 32 | 33 | Args: 34 | row (int): The row of the qubit. 35 | cell (int): The cell of the qubit. 36 | off (bool): Whether the qubit is in a main row or an off row. 37 | 38 | Returns: 39 | int: The qubit index. 40 | """ 41 | if not off: 42 | return 19 * row + cell 43 | return 19 * row + 15 + cell 44 | 45 | for row in range(7): 46 | for cell in range(15): 47 | current = get_qubit_index(row, cell, False) 48 | if cell != 14: 49 | neighbor = get_qubit_index(row, cell + 1, False) 50 | heavy_hex_coupling.append((current, neighbor)) 51 | if cell % 4 == 0: 52 | if row % 2 == 0 and row != 6: 53 | below = get_qubit_index(row, cell // 4, True) 54 | heavy_hex_coupling.append((current, below)) 55 | elif row % 2 == 1: 56 | above = get_qubit_index(row - 1, cell // 4, True) 57 | heavy_hex_coupling.append((current, above)) 58 | if cell % 4 == 2: 59 | if row % 2 == 0 and row != 0: 60 | above = get_qubit_index(row - 1, cell // 4, True) 61 | heavy_hex_coupling.append((current, above)) 62 | elif row % 2 == 1: 63 | below = get_qubit_index(row, cell // 4, True) 64 | heavy_hex_coupling.append((current, below)) 65 | 66 | longest_hamiltonian_path = [] 67 | for row in range(7): 68 | for cell in range(15): 69 | if row == 0 and cell == 0: 70 | continue # we skip the first qubit. 71 | current = get_qubit_index(row, cell if row % 2 == 1 else (14 - cell), False) 72 | longest_hamiltonian_path.append(current) 73 | if row != 6: 74 | longest_hamiltonian_path.append(get_qubit_index(row, 3 if row % 2 == 1 else 0, True)) 75 | 76 | num_qubits = max(max(pair) for pair in heavy_hex_coupling) + 1 77 | 78 | return Calibration( 79 | num_qubits=num_qubits, 80 | one_qubit=dict.fromkeys(range(num_qubits), 0.99), 81 | two_qubit=dict.fromkeys(heavy_hex_coupling, 0.99), 82 | measurement_confidences=dict.fromkeys(range(num_qubits), 0.99), 83 | basis_gates=["cz", "id", "rx", "rz", "rzz", "sx", "x"], 84 | t1=dict.fromkeys(range(num_qubits), 0.0001), 85 | t2=dict.fromkeys(range(num_qubits), 0.0002), 86 | ) 87 | 88 | 89 | def test_simple_qaoa(simple_generator: QuboGenerator) -> None: 90 | """Tests the construction of a simple QAOA circuit without qubit reuse and with barriers. 91 | 92 | Args: 93 | simple_generator (QuboGenerator): A simple QuboGenerator fixture. 94 | """ 95 | circuit = simple_generator.construct_qaoa_circuit(do_reuse=False, include_barriers=True) 96 | expected_qubits = 8 97 | assert circuit.num_qubits == expected_qubits 98 | ops = circuit.count_ops() 99 | assert ops["barrier"] == 3 100 | assert ops["h"] == expected_qubits 101 | assert ops["rx"] == expected_qubits 102 | assert ops["rzz"] == 10 103 | 104 | 105 | def test_simple_qaoa_no_barriers(simple_generator: QuboGenerator) -> None: 106 | """Tests the construction of a simple QAOA circuit without qubit reuse and without barriers. 107 | 108 | Args: 109 | simple_generator (QuboGenerator): A simple QuboGenerator fixture. 110 | """ 111 | circuit = simple_generator.construct_qaoa_circuit(do_reuse=False, include_barriers=False) 112 | expected_qubits = 8 113 | assert circuit.num_qubits == expected_qubits 114 | ops = circuit.count_ops() 115 | assert "barrier" not in ops 116 | assert ops["h"] == expected_qubits 117 | assert ops["rx"] == expected_qubits 118 | assert ops["rzz"] == 10 119 | 120 | 121 | def test_qaoa_with_reuse(simple_generator: QuboGenerator) -> None: 122 | """Tests the construction of a QAOA circuit with qubit reuse and without barriers. 123 | 124 | Args: 125 | simple_generator (QuboGenerator): A simple QuboGenerator fixture. 126 | """ 127 | circuit = simple_generator.construct_qaoa_circuit(do_reuse=True, include_barriers=False) 128 | expected_qubits = 3 129 | expected_variables = 8 130 | assert circuit.num_qubits == expected_qubits 131 | ops = circuit.count_ops() 132 | assert "barrier" not in ops 133 | assert ops["h"] == expected_variables 134 | assert ops["rx"] == expected_variables 135 | assert ops["reset"] == expected_variables - expected_qubits 136 | assert ops["rzz"] == 10 137 | 138 | 139 | def test_heavy_hex_qaoa(simple_generator: QuboGenerator, simple_device: Calibration) -> None: 140 | """Tests the construction of a heavy-hex optimized QAOA circuit. 141 | 142 | Args: 143 | simple_generator (QuboGenerator): A simple QuboGenerator fixture. 144 | simple_device (Calibration): A simple device (heavy-hex) calibration fixture. 145 | """ 146 | circuit = simple_generator.construct_embedded_qaoa_circuit(simple_device) 147 | assert circuit.num_qubits == simple_device.num_qubits 148 | ops = circuit.count_ops() 149 | expected_variables = 8 150 | assert ops["h"] == expected_variables 151 | assert ops["rx"] == expected_variables 152 | assert ops["rzz"] == 10 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > As of October 2025, this repository is no longer actively maintained. All code has been directly integrated into [MQT ProblemSolver](https://github.com/munich-quantum-toolkit/problemsolver). 3 | > Development is expected to continue there. No new contributions will be accepted here. 4 | 5 | [![PyPI](https://img.shields.io/pypi/v/mqt.qubomaker?logo=pypi&style=flat-square)](https://pypi.org/project/mqt.qubomaker/) 6 | ![OS](https://img.shields.io/badge/os-linux%20%7C%20macos%20%7C%20windows-blue?style=flat-square) 7 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) 8 | [![CI](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-qubomaker/ci.yml?branch=main&style=flat-square&logo=github&label=ci)](https://github.com/cda-tum/mqt-qubomaker/actions/workflows/ci.yml) 9 | [![CD](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-qubomaker/cd.yml?style=flat-square&logo=github&label=cd)](https://github.com/cda-tum/mqt-qubomaker/actions/workflows/cd.yml) 10 | [![Documentation](https://img.shields.io/readthedocs/mqt-qubomaker?logo=readthedocs&style=flat-square)](https://mqt.readthedocs.io/projects/qubomaker) 11 | [![codecov](https://img.shields.io/codecov/c/github/cda-tum/mqt-qubomaker?style=flat-square&logo=codecov)](https://codecov.io/gh/cda-tum/mqt-qubomaker) 12 | 13 | <p align="center"> 14 | <a href="https://mqt.readthedocs.io"> 15 | <picture> 16 | <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/mqt_light.png" width="60%"> 17 | <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/mqt_dark.png" width="60%"> 18 | </picture> 19 | </a> 20 | </p> 21 | 22 | # MQT QUBOMaker: Automatic Generation of QUBO Formulations from Optimization Problem Specifications 23 | 24 | MQT QUBOMaker is a framework that can be used to automatically generate QUBO formulations for various optimization problems based on a selection of constraints that define the problem. 25 | It is developed by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/) as part of the _[Munich Quantum Toolkit](https://mqt.readthedocs.io/) (MQT)_. 26 | 27 | The tool allows users to create QUBO formulations, and, thus, interact with quantum algorithms, without requiring any background knowledge in the field of quantum computing. End-users can stay entirely within their domain of expertise while being shielded from the complex and error-prone mathematical tasks of QUBO reformulation. 28 | 29 | Furthermore, MQT QUBOMaker supports a variety of different encodings. End users can easily switch between the encodings for evaluation purposes without any additional effort, a task that would otherwise require a large amount of tedious mathematical reformulation. 30 | 31 | Currently, MQT QUBOMaker provides the following submodule: 32 | 33 | - [_Pathfinder_](./src/mqt/qubomaker/pathfinder/README.md): This submodule provides a specialization of the QUBOMaker class for the solution of optimization problems involving the search for paths in a directed graph. It provides a large set of pathfinding-related constraints that are used to define individual problem instances. 34 | 35 | The _Pathfinder_ submodule also has a supporting [GUI](https://cda-tum.github.io/mqt-qubomaker/) to further facilitate its use. 36 | 37 | For more details, please refer to: 38 | 39 | <p align="center"> 40 | <a href="https://mqt-qubomaker.readthedocs.io/en/latest/"> 41 | <img width=30% src="https://img.shields.io/badge/documentation-blue?style=for-the-badge&logo=read%20the%20docs" alt="Documentation" /> 42 | </a> 43 | </p> 44 | 45 | If you have any questions, feel free to create a [discussion](https://github.com/cda-tum/mqt-qubomaker/discussions) or an [issue](https://github.com/cda-tum/mqt-qubomaker/issues) on [GitHub](https://github.com/cda-tum/mqt-qubomaker). 46 | 47 | ## Getting Started 48 | 49 | `mqt-qubomaker` is available via [PyPI](https://pypi.org/project/mqt.qubomaker/). 50 | 51 | ```console 52 | (venv) $ pip install mqt.qubomaker 53 | ``` 54 | 55 | The following code gives an example of the usage with the `pathfinder` submodule: 56 | 57 | ```python3 58 | import mqt.qubomaker as qm 59 | import mqt.qubomaker.pathfinder as pf 60 | 61 | # define an example graph to investigate. 62 | graph = qm.Graph.from_adjacency_matrix( 63 | [ 64 | [0, 1, 3, 4], 65 | [2, 0, 4, 2], 66 | [1, 5, 0, 3], 67 | [3, 8, 1, 0], 68 | ] 69 | ) 70 | 71 | # select the settings for the QUBO formulation. 72 | settings = pf.PathFindingQuboGeneratorSettings( 73 | encoding_type=pf.EncodingType.ONE_HOT, n_paths=1, max_path_length=4, loops=True 74 | ) 75 | 76 | # define the generator to be used for the QUBO formulation. 77 | generator = pf.PathFindingQuboGenerator( 78 | objective_function=pf.MinimizePathLength(path_ids=[1]), 79 | graph=graph, 80 | settings=settings, 81 | ) 82 | 83 | # add the constraints that define the problem instance. 84 | generator.add_constraint(pf.PathIsValid(path_ids=[1])) 85 | generator.add_constraint( 86 | pf.PathContainsVerticesExactlyOnce(vertex_ids=graph.all_vertices, path_ids=[1]) 87 | ) 88 | 89 | # generate and view the QUBO formulation as a QUBO matrix. 90 | print(generator.construct_qubo_matrix()) 91 | ``` 92 | 93 | **Detailed documentation and examples are available at [ReadTheDocs](https://mqt-qubomaker.readthedocs.io/en/latest/).** 94 | 95 | ## References 96 | 97 | MQT QUBOMaker has been developed based on methods proposed in the following paper: 98 | 99 | - D. Rovara, N. Quetschlich, and R. Wille "[A Framework to Formulate 100 | Pathfinding Problems for Quantum Computing](https://arxiv.org/abs/2404.10820)", arXiv, 2024 101 | 102 | ## Acknowledgements 103 | 104 | The Munich Quantum Toolkit has been supported by the European 105 | Research Council (ERC) under the European Union's Horizon 2020 research and innovation program (grant agreement 106 | No. 101001318), the Bavarian State Ministry for Science and Arts through the Distinguished Professorship Program, as well as the 107 | Munich Quantum Valley, which is supported by the Bavarian state government with funds from the Hightech Agenda Bayern Plus. 108 | 109 | <p align="center"> 110 | <picture> 111 | <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_dark.svg" width="28%"> 112 | <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_light.svg" width="28%" alt="TUM Logo"> 113 | </picture> 114 | <picture> 115 | <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-bavaria.svg" width="16%" alt="Coat of Arms of Bavaria"> 116 | </picture> 117 | <picture> 118 | <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_dark.svg" width="24%"> 119 | <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_light.svg" width="24%" alt="ERC Logo"> 120 | <picture> 121 | <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-mqv.svg" width="28%" alt="MQV Logo"> 122 | </picture> 123 | </p> 124 | -------------------------------------------------------------------------------- /docs/pathfinder/Constraints.rst: -------------------------------------------------------------------------------- 1 | Constraints 2 | =========== 3 | 4 | The *Pathfinder* submodule supports a total of 13 constraints. These constraints are translated 5 | into their QUBO formulation automatically during the QUBO generation process, based on the selected encoding. 6 | 7 | The following lists all supported constraints together with their properties and their QUBO formulations. 8 | In the formula representation, :math:`\delta(x, v, i, \pi^{(i)})` is a function that returns 1 if vertex :math:`v` is at position :math:`i` of path :math:`\pi^{(i)}` and 0 otherwise. 9 | The specific function depends on the selected :doc:`encoding <Encodings>`. 10 | 11 | In each formula, :math:`x` is the binary variable assignment, :math:`N` is the maximum path length, :math:`V` is the set of vertices, :math:`E` is the set of edges, and :math:`A_{uv}` is the adjacency matrix of the graph. 12 | 13 | PathIsValid 14 | ----------- 15 | Enforces that a path is valid. A path is valid if: 16 | 17 | #. For any two consecutive vertices, there exists an edge between them. 18 | #. If the used encoding is :code:`ONE_HOT`, no two or more vertices are selected as the same position of the same path. 19 | #. If the used encoding is :code:`DOMAIN_WALL`, the bitstring indicating the vertex occupying position :math:`j` of path :math:`\pi^{(i)}` is of the form :math:`111..10..0`, i.e. after the first 0, no more bits have the value 1. 20 | 21 | Properties: 22 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 23 | 24 | $$\\sum_{(u \\rightarrow v) \\not \\in E} \\sum_{i = 1}^{N}\\delta(x, \\pi, u, i)\\delta(x, \\pi, v, i+1)$$ 25 | 26 | *Encoding-specific penalties are further added to this expression if necessary.* 27 | 28 | PathPositionIs 29 | -------------- 30 | Enforces that a given position of a provided path is occupied by one of a set of vertices. 31 | 32 | Properties: 33 | - :code:`position: int`: The position of the path subject to this constraint. 34 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that can occupy the position. 35 | - :code:`path: int`: The ID of the path this constraint should be applied to. 36 | 37 | $$1 - \\sum_{v \\in V'} \\delta(x, \\pi, v, i)$$ 38 | 39 | PathStartsAt 40 | ------------- 41 | Enforces that a given path starts at one of a given set of vertices. 42 | 43 | Properties: 44 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that the path can start at. 45 | - :code:`path: int`: The ID of the path this constraint should be applied to. 46 | 47 | $$1 - \\sum_{v \\in V'} \\delta(x, \\pi, v, 1)$$ 48 | 49 | PathEndsAt 50 | ----------- 51 | Enforces that a given path ends at one of a given set of vertices. 52 | 53 | Properties: 54 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that the path can end at. 55 | - :code:`path: int`: The ID of the path this constraint should be applied to. 56 | 57 | $$\\sum_{i=2}^N \\left[ \\left(1 - \\sum_{v \\in V} \\delta(x, \\pi, v, i) \\right)^2 \\left(\\sum_{v \\not \\in V'} \\delta(x, \\pi, v, i - 1) \\right) \\right] + \\sum_{v \\not \\in V'} \\delta(x, \\pi, v, N)$$ 58 | 59 | PathContainsVerticesExactlyOnce 60 | ------------------------------- 61 | Enforces that a given set of paths each contain each given vertex exactly once. 62 | 63 | Properties: 64 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that should be contained exactly once in each path. 65 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 66 | 67 | $$\\left(1 - \\sum_{i = 1}^N \\delta(x, \\pi, v, i) \\right) ^2$$ 68 | 69 | PathContainsVerticesAtLeastOnce 70 | ------------------------------- 71 | Enforces that a given set of paths each contain each given vertex at least once. 72 | 73 | Properties: 74 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that should be contained at least once in each path. 75 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 76 | 77 | $$\\prod_{i=1}^N(1 - \\delta(x, v, i))$$ 78 | 79 | PathContainsVerticesAtMostOnce 80 | ------------------------------- 81 | Enforces that a given set of paths each contain each given vertex at most once. 82 | 83 | Properties: 84 | - :code:`vertex_ids: list[int]`: A list of IDs of the vertices that should be contained at most once in each path. 85 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 86 | 87 | $$\\sum_{1 \\leq i \\lt j \\lt N}\\delta(x, v,i)\\delta(x, v,j)$$ 88 | 89 | PathContainsEdgesExactlyOnce 90 | ------------------------------- 91 | Enforces that a given set of paths each contain each given edge exactly once. 92 | 93 | Properties: 94 | - :code:`edges: list[tuple[int, int]]`: A list of the edges that should be contained exactly once in each path. 95 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 96 | 97 | $$\\left( 1 - \\sum_{i=1}^{N}\\delta(x, \\pi, u, i)\\delta(x, \\pi, v, i + 1) \\right)^2$$ 98 | 99 | PathContainsEdgesAtLeastOnce 100 | ------------------------------- 101 | Enforces that a given set of paths each contain each given edge at least once. 102 | 103 | Properties: 104 | - :code:`edges: list[tuple[int, int]]`: A list of the edges that should be contained at least once in each path. 105 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 106 | 107 | $$\\prod_{i=1}^N(1 - \\delta(x, \\pi, u, i)\\delta(x, \\pi, v, i +1))$$ 108 | 109 | PathContainsEdgesAtMostOnce 110 | ------------------------------- 111 | Enforces that a given set of paths each contain each given edge at most once. 112 | 113 | Properties: 114 | - :code:`edges: list[tuple[int, int]]`: A list of the edges that should be contained at most once in each path. 115 | - :code:`path_ids: list[int]`: A list of IDs of the paths this constraint should be applied to. 116 | 117 | $$\\sum_{1 \\leq i \\lt j \\leq N}(\\delta(x, \\pi, u, i)\\delta(x, \\pi, v,i+1))(\\delta(x, \\pi, u,j)\\delta(x, \\pi, v,j+1))$$ 118 | 119 | PrecedenceConstraint 120 | -------------------- 121 | For the given vertices :math:`u` and :math:`v`, enforces that :math:`u` is visited before :math:`v` in the path. 122 | 123 | Properties: 124 | - :code:`pre: int`: The ID of the preceding vertex. 125 | - :code:`post: int`: The ID of the preceded vertex. 126 | 127 | $$\\sum_{i=1}^N\\delta(x, \\pi, v,i)\\prod_{j=1}^{i-1}(1-\\delta(x, \\pi, u,j))$$ 128 | 129 | PathsShareNoVertices 130 | -------------------- 131 | Enforces that two given paths share no vertices. 132 | 133 | Properties 134 | - :code:`path_one: int`: The ID of the first path. 135 | - :code:`path_two: int`: The ID of the second path. 136 | 137 | $$\\sum_{v \\in V} \\left[ \\left(\\sum_{i=1}^N \\delta(x, \\pi^{(1)}, v, i) \\right) \\left(\\sum_{i=1}^N \\delta(x, \\pi^{(2)}, v, i) \\right) \\right]$$ 138 | 139 | PathsShareNoEdges 140 | ----------------- 141 | Enforces that two given paths share no edges. 142 | 143 | Properties 144 | - :code:`path_one: int`: The ID of the first path. 145 | - :code:`path_two: int`: The ID of the second path. 146 | 147 | $$\\sum_{(u \\rightarrow v) \\in E} \\left[ \\left( \\sum_{i=1}^{N} \\delta(x, \\pi^{(1)}, u, i) \\delta(x, \\pi^{(1)}, v, i + 1) \\right) \\left(\\sum_{i=1}^{N} \\delta(x, \\pi^{(2)}, u, i) \\delta(x, \\pi^{(2)}, v, i + 1) \\right) \\right]$$ 148 | 149 | MinimizePathLength 150 | ------------------ 151 | Enforces that the length of a given path is minimized. 152 | 153 | Properties 154 | - :code:`path_ids: int`: The ID of the paths this constraint should be applied to. 155 | 156 | $$\\sum_{(u \\rightarrow v) \\in E} \\sum_{i = 1}^{N} A_{uv}\\delta(x, \\pi, u, i)\\delta(x, \\pi, v, i+1)$$ 157 | 158 | MaximizePathLength 159 | ------------------ 160 | Enforces that the length of a given path is maximized. 161 | 162 | Properties 163 | - :code:`path_ids: int`: The ID of the paths this constraint should be applied to. 164 | 165 | $$-\\sum_{(u \\rightarrow v) \\in E} \\sum_{i = 1}^{N} A_{uv}\\delta(x, \\pi, u, i)\\delta(x, \\pi, v, i+1)$$ 166 | -------------------------------------------------------------------------------- /tests/pathfinder/utils_test.py: -------------------------------------------------------------------------------- 1 | """This module provides utility functions for testing the pathfinder module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import itertools 6 | from typing import TYPE_CHECKING 7 | 8 | import numpy as np 9 | 10 | import mqt.qubomaker.pathfinder as pf 11 | import mqt.qubomaker.pathfinder.cost_functions as cf 12 | from mqt.qubomaker import Graph 13 | 14 | if TYPE_CHECKING: 15 | import sympy as sp 16 | 17 | 18 | def get_test_graph() -> Graph: 19 | """Generate a |V| = 5 graph for testing. 20 | 21 | Returns: 22 | Graph: The generated graph. 23 | """ 24 | return Graph( 25 | 5, 26 | [ 27 | (1, 2, 5), 28 | (1, 3, 4), 29 | (1, 5, 4), 30 | (2, 1, 3), 31 | (2, 3, 2), 32 | (2, 4, 3), 33 | (2, 5, 5), 34 | (3, 4, 6), 35 | (3, 5, 2), 36 | (4, 1, 2), 37 | (4, 2, 3), 38 | (4, 5, 4), 39 | (5, 1, 2), 40 | (5, 2, 2), 41 | (5, 3, 3), 42 | (5, 4, 1), 43 | ], 44 | ) 45 | 46 | 47 | def get_test_graph_small() -> Graph: 48 | """Generate a |V| = 4 graph for testing. 49 | 50 | Returns: 51 | Graph: The generated graph. 52 | """ 53 | return Graph( 54 | 4, 55 | [ 56 | (1, 2, 9), 57 | (1, 3, 8), 58 | (2, 4, 1), 59 | (2, 3, 1), 60 | (3, 4, 6), 61 | (3, 1, 2), 62 | (4, 2, 3), 63 | (4, 1, 4), 64 | ], 65 | ) 66 | 67 | 68 | def get_test_graph_tiny() -> Graph: 69 | """Generate a |V| = 3 graph for testing. 70 | 71 | Returns: 72 | Graph: The generated graph. 73 | """ 74 | return Graph( 75 | 3, 76 | [ 77 | (1, 2, 8), 78 | (1, 3, 4), 79 | (2, 1, 2), 80 | (3, 2, 3), 81 | ], 82 | ) 83 | 84 | 85 | def paths_equal_with_loops(a: list[int], b: list[int]) -> bool: 86 | """Check if two paths are equal, in the presence of loops. 87 | 88 | E.g., [1, 2, 3, 4] and [2, 3, 4, 1] are considered equal, 89 | but [1, 2, 3, 4] and [1, 3, 2, 4] are not. 90 | 91 | Args: 92 | a (list[int]): The first path to be compared. 93 | b (list[int]): The second path to be compared. 94 | 95 | Returns: 96 | bool: True if the paths are equal, False otherwise. 97 | """ 98 | if len(a) != len(b): 99 | return False 100 | edges_a = [*list(itertools.pairwise(a)), (a[-1], a[0])] 101 | edges_b = [*list(itertools.pairwise(b)), (b[-1], b[0])] 102 | edges_a = sorted(edges_a) 103 | edges_b = sorted(edges_b) 104 | return edges_a == edges_b 105 | 106 | 107 | def paths_to_assignment_list( 108 | paths: list[list[int]], n_vertices: int, max_path_length: int, encoding: pf.EncodingType 109 | ) -> list[int]: 110 | """Converts a list of paths to a list of binary variables for the given encoding type. 111 | 112 | Args: 113 | paths (list[list[int]]): The path(s) to be converted. 114 | n_vertices (int): The number of vertices in the graph. 115 | max_path_length (int): The maximum length of the path(s). 116 | encoding (pf.EncodingType): The encoding type to be used. 117 | 118 | Returns: 119 | list[int]: The binary variable assignment. 120 | """ 121 | assignment = paths_to_assignment(paths, n_vertices, max_path_length, encoding) 122 | return [assignment[key] for key in sorted(assignment.keys(), key=lambda x: (x.args[0], x.args[2], x.args[1]))] 123 | 124 | 125 | def paths_to_assignment( 126 | paths: list[list[int]], n_vertices: int, max_path_length: int, encoding: pf.EncodingType 127 | ) -> dict[sp.Expr, int]: 128 | """Converts a list of paths to a substitution from encoding variables to binary values for the given encoding type. 129 | 130 | Args: 131 | paths (list[list[int]]): The path(s) to be converted. 132 | n_vertices (int): The number of vertices in the graph. 133 | max_path_length (int): The maximum length of the path(s). 134 | encoding (pf.EncodingType): The encoding type to be used. 135 | 136 | Returns: 137 | dict[sp.Expr, int]: The substitution dictionary mapping each encoding variable to its value. 138 | """ 139 | if encoding == pf.EncodingType.ONE_HOT: 140 | return __paths_to_assignment_one_hot(paths, n_vertices, max_path_length) 141 | if encoding == pf.EncodingType.DOMAIN_WALL: 142 | return __paths_to_assignment_domain_wall(paths, n_vertices, max_path_length) 143 | if encoding == pf.EncodingType.BINARY: 144 | return __paths_to_assignment_binary(paths, n_vertices, max_path_length) 145 | msg = f"Unknown encoding type: {encoding}" # type: ignore[unreachable] 146 | raise ValueError(msg) 147 | 148 | 149 | def __paths_to_assignment_one_hot(paths: list[list[int]], n_vertices: int, max_path_length: int) -> dict[sp.Expr, int]: 150 | """Converts a list of paths to a substitution from one-hot encoding variables to binary values. 151 | 152 | Args: 153 | paths (list[list[int]]): The path(s) to be converted. 154 | n_vertices (int): The number of vertices in the graph. 155 | max_path_length (int): The maximum length of the path(s). 156 | 157 | Returns: 158 | dict[sp.Expr, int]: The substitution dictionary mapping each one-hot encoding variable to its value. 159 | """ 160 | result = [ 161 | (cf.X(p + 1, v + 1, i + 1), 1 if len(path) > i and (path[i] == v + 1) else 0) 162 | for p, path in enumerate(paths) 163 | for i in range(max_path_length) 164 | for v in range(n_vertices) 165 | ] 166 | return dict(result) 167 | 168 | 169 | def __paths_to_assignment_domain_wall( 170 | paths: list[list[int]], n_vertices: int, max_path_length: int 171 | ) -> dict[sp.Expr, int]: 172 | """Converts a list of paths to a substitution from domain_wall encoding variables to binary values. 173 | 174 | Args: 175 | paths (list[list[int]]): The path(s) to be converted. 176 | n_vertices (int): The number of vertices in the graph. 177 | max_path_length (int): The maximum length of the path(s). 178 | 179 | Returns: 180 | dict[sp.Expr, int]: The substitution dictionary mapping each domain_wall encoding variable to its value. 181 | """ 182 | result = [ 183 | (cf.X(p + 1, v + 1, i + 1), 1 if len(path) > i and (path[i] >= v + 1) else 0) 184 | for p, path in enumerate(paths) 185 | for i in range(max_path_length) 186 | for v in range(n_vertices) 187 | ] 188 | return dict(result) 189 | 190 | 191 | def __paths_to_assignment_binary(paths: list[list[int]], n_vertices: int, max_path_length: int) -> dict[sp.Expr, int]: 192 | """Converts a list of paths to a substitution from binary encoding variables to binary values. 193 | 194 | Args: 195 | paths (list[list[int]]): The path(s) to be converted. 196 | n_vertices (int): The number of vertices in the graph. 197 | max_path_length (int): The maximum length of the path(s). 198 | 199 | Returns: 200 | dict[sp.Expr, int]: The substitution dictionary mapping each binary encoding variable to its value. 201 | """ 202 | max_index = int(np.ceil(np.log2(n_vertices + 1))) 203 | result = [ 204 | (cf.X(p + 1, v + 1, i + 1), 1 if len(path) > i and (((path[i]) >> v) & 1) else 0) 205 | for p, path in enumerate(paths) 206 | for i in range(max_path_length) 207 | for v in range(max_index) 208 | ] 209 | return dict(result) 210 | 211 | 212 | def check_equal(a: pf.PathFindingQuboGenerator, b: pf.PathFindingQuboGenerator) -> None: 213 | """Check if two PathFindingQuboGenerators are equal. 214 | 215 | Args: 216 | a (pf.PathFindingQuboGenerator): The first generator to be compared. 217 | b (pf.PathFindingQuboGenerator): The second generator to be compared. 218 | """ 219 | assert a.objective_function == b.objective_function 220 | assert a.graph == b.graph 221 | assert a.settings == b.settings 222 | 223 | for expr, weight in a.penalties: 224 | assert len([w for (e, w) in b.penalties if e == expr and w == weight]) == 1 225 | 226 | for expr, weight in b.penalties: 227 | assert len([w for (e, w) in a.penalties if e == expr and w == weight]) == 1 228 | -------------------------------------------------------------------------------- /src/mqt/qubomaker/device.py: -------------------------------------------------------------------------------- 1 | """Represents device information and utility methods for quantum devices.""" 2 | 3 | from __future__ import annotations 4 | 5 | from dataclasses import dataclass, field 6 | from typing import Any 7 | 8 | 9 | @dataclass 10 | class Calibration: 11 | """Represents a device calibration including its qubit connectivities and gate fidelities.""" 12 | 13 | num_qubits: int 14 | 15 | one_qubit: dict[int, float] 16 | two_qubit: dict[tuple[int, int], float] 17 | measurement_confidences: dict[int, float] 18 | basis_gates: list[str] 19 | 20 | connections_dict: dict[int, list[int]] = field(init=False) 21 | heavy: dict[int, list[int]] = field(init=False) 22 | heavy_children: dict[int, list[int]] = field(init=False) 23 | 24 | t1: dict[int, float] 25 | t2: dict[int, float] 26 | 27 | def __post_init__(self) -> None: 28 | """Post-initialization to set up connections and heavy nodes.""" 29 | self.connections_dict = {} 30 | for i, j in self.two_qubit: 31 | if i not in self.connections_dict: 32 | self.connections_dict[i] = [] 33 | if j not in self.connections_dict: 34 | self.connections_dict[j] = [] 35 | self.connections_dict[i].append(j) 36 | self.connections_dict[j].append(i) 37 | self.heavy = {} 38 | self.heavy_children = {} 39 | for qubit, connections in self.connections_dict.items(): 40 | if len(connections) > 2: 41 | self.heavy[qubit] = connections 42 | for connection in connections: 43 | if connection not in self.heavy_children: 44 | self.heavy_children[connection] = [] 45 | self.heavy_children[connection].append(qubit) 46 | 47 | def __eq__(self, value: object) -> bool: 48 | """Check equality between two Calibration objects. 49 | 50 | Args: 51 | value (object): The other object to compare against. 52 | 53 | Returns: 54 | bool: True if equal, False otherwise. 55 | """ 56 | if not isinstance(value, Calibration): 57 | return False 58 | return ( 59 | self.num_qubits == value.num_qubits 60 | and self.one_qubit == value.one_qubit 61 | and self.two_qubit == value.two_qubit 62 | and self.measurement_confidences == value.measurement_confidences 63 | and self.basis_gates == value.basis_gates 64 | and self.t1 == value.t1 65 | and self.t2 == value.t2 66 | ) 67 | 68 | def __hash__(self) -> int: 69 | """Compute a hash for the Calibration object. 70 | 71 | Returns: 72 | int: The hash value. 73 | """ 74 | return hash(( 75 | self.num_qubits, 76 | frozenset(self.one_qubit.items()), 77 | frozenset(self.two_qubit.items()), 78 | frozenset(self.measurement_confidences.items()), 79 | tuple(self.basis_gates), 80 | frozenset(self.t1.items()), 81 | frozenset(self.t2.items()), 82 | )) 83 | 84 | @classmethod 85 | def from_dict(cls, data: dict[str, Any], basis_gates: list[str]) -> Calibration: 86 | """Create a Calibration object from a dictionary.""" 87 | return cls( 88 | num_qubits=data["num_qubits"], 89 | one_qubit=data["one_qubit"], 90 | two_qubit=data["two_qubit"], 91 | measurement_confidences=data["measurement_confidences"], 92 | basis_gates=basis_gates, 93 | t1=data["t1"], 94 | t2=data["t2"], 95 | ) 96 | 97 | def get_shared_neighbor(self, q1: int, q2: int) -> int: 98 | """Given two qubits in the heavy-hex topology, find a qubit that is connected to both. 99 | 100 | Args: 101 | q1 (int): The first qubit. 102 | q2 (int): The second qubit. 103 | 104 | Returns: 105 | int: The shared neighbor qubit, or -1 if none exists. 106 | """ 107 | for x in self.connections_dict[q1]: 108 | if x in self.connections_dict[q2]: 109 | return x 110 | return -1 111 | 112 | def get_connected_qubit_chain(self) -> list[int]: 113 | """Compute the longest possible hamiltonian path through the device topology. 114 | 115 | Returns: 116 | list[int]: The longest Hamiltonian path through the qubit connectivity graph. 117 | """ 118 | # To compute the start that leasts to the longest Hamiltonian path, we look for qubits with only one connection. 119 | # Sometimes, these might not be directly connected to a heavy node, so we keep traversing until we find one that is. 120 | # That qubit is then the start of the longest Hamiltonian path. 121 | potential_starts = [x for x in self.connections_dict if len(self.connections_dict[x]) == 1] 122 | assert len(potential_starts) >= 2, ( 123 | f"There should be exactly two potential starts for the connected qubit chain. ({potential_starts})" 124 | ) 125 | start = min(potential_starts) 126 | 127 | # To check if `start` is connected to a heavy node, we check whether its successsor has more than two connections. 128 | # Otherwise, we proceed to the successor. 129 | def get_next(current: int, previous: int) -> int: 130 | """Compute the next qubit in the chain. 131 | 132 | Args: 133 | current (int): The current qubit to find the successor for. 134 | previous (int): The previous qubit in the chain. 135 | 136 | Returns: 137 | int: The next qubit in the chain, or -1 if none exists. 138 | """ 139 | for x in self.connections_dict[current]: 140 | if x != previous: 141 | return x 142 | return -1 143 | 144 | previous = -1 145 | while len(self.connections_dict[get_next(start, previous)]) == 2: 146 | p = start 147 | start = get_next(start, previous) 148 | previous = p 149 | 150 | # We traverse through the heavy-hex topology to get the longest Hamiltonian path. 151 | path = [start] 152 | current = start 153 | while True: 154 | possible_successors = [x for x in self.connections_dict[current] if (len(path) == 1 or x != path[-2])] 155 | successor_distances = sorted([(abs(s - current), s) for s in possible_successors]) 156 | if len(successor_distances) == 0: 157 | break 158 | current = successor_distances[0][1] 159 | path.append(current) 160 | return path 161 | 162 | def get_heavy_chain(self) -> list[int]: 163 | """Find a chain of heavy nodes in the heavy-hex topology that are connected through shared children. 164 | 165 | Returns: 166 | list[int]: A chain of heavy nodes connected through shared children. 167 | """ 168 | heavy_graph: dict[int, list[int]] = {} 169 | for x, children in self.heavy.items(): 170 | heavy_graph[x] = [] 171 | for child in children: 172 | for other in self.heavy_children[child]: 173 | if other != x: 174 | heavy_graph[x].append(other) 175 | potential_starts = [x for x in heavy_graph if len(heavy_graph[x]) == 1] 176 | potential_starts = [x for x in potential_starts if len(heavy_graph[heavy_graph[x][0]]) == 2] 177 | 178 | heavy_chain = [potential_starts[0]] 179 | while True: 180 | current = heavy_chain[-1] 181 | possible_successors = [x for x in heavy_graph[current] if x not in heavy_chain] 182 | if len(possible_successors) == 0: 183 | break 184 | if len(possible_successors) == 1: 185 | heavy_chain.append(possible_successors[0]) 186 | continue 187 | assert len(possible_successors) == 2, "Heavy chain must have exactly two successors." 188 | [s_a, s_b] = possible_successors 189 | d_a = abs(s_a - current) 190 | d_b = abs(s_b - current) 191 | if d_a < d_b: 192 | heavy_chain.append(s_a) 193 | else: 194 | heavy_chain.append(s_b) 195 | return heavy_chain 196 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=66.1", 4 | "setuptools_scm>=8.1" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [project] 9 | name = "mqt.qubomaker" 10 | description = "A tool for the automatic generation and combination of QUBO formulations for specific problem classes." 11 | readme = "README.md" 12 | authors = [ 13 | { name = "Damian Rovara", email = "damian.rovara@tum.de" }, 14 | { name = "Nils Quetschlich", email = "nils.quetschlich@tum.de" }, 15 | { name = "Lukas Burgholzer", email = "lukas.burgholzer@tum.de" }, 16 | ] 17 | keywords = ["MQT", "quantum-computing", "qubo", "shortest-path", "design-automation"] 18 | license = { file = "LICENSE" } 19 | requires-python = ">=3.10" 20 | dynamic = ["version"] 21 | dependencies = [ 22 | "importlib_resources>=5.0; python_version < '3.10'", 23 | "qiskit[qasm3-import,visualization]>=1.0.0; python_version < '3.13'", 24 | "qiskit[qasm3-import,visualization]>=2.0.0; python_version >= '3.13'", 25 | "joblib>=1.3.2", 26 | "numpy>=2.0.0; python_version >= '3.13'", 27 | "numpy>=1.26; python_version >= '3.12'", 28 | "numpy>=1.24; python_version >= '3.11'", 29 | "numpy>=1.22", 30 | "sympy>=1.13.1", 31 | "jsonschema>=4.20.0", 32 | "IPython>=8.12.1", 33 | "ipywidgets>=8.1.1", 34 | "typing_extensions>=4.15.0", 35 | ] 36 | 37 | classifiers = [ 38 | "Development Status :: 4 - Beta", 39 | "Intended Audience :: Developers", 40 | "License :: OSI Approved :: MIT License", 41 | "Operating System :: Microsoft :: Windows", 42 | "Operating System :: MacOS", 43 | "Operating System :: POSIX :: Linux", 44 | "Programming Language :: Python", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3 :: Only", 47 | "Programming Language :: Python :: 3.10", 48 | "Programming Language :: Python :: 3.11", 49 | "Programming Language :: Python :: 3.12", 50 | "Programming Language :: Python :: 3.13", 51 | "Intended Audience :: Science/Research", 52 | "Natural Language :: English", 53 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", 54 | "Typing :: Typed", 55 | ] 56 | 57 | [project.optional-dependencies] 58 | test = ["pytest>=7.2"] 59 | coverage = ["mqt.qubomaker[test]", "pytest-cov>=4"] 60 | docs = [ 61 | "furo>=2023.9.10", 62 | "setuptools-scm>=8.1", 63 | "sphinxcontrib-bibtex>=2.4.2", 64 | "sphinx-copybutton>=0.5", 65 | "sphinx>=7.4.7", 66 | "sphinx>=8.1.3; python_version >= '3.10'", 67 | "sphinx>=8.2.3; python_version >= '3.11'", 68 | "sphinxext-opengraph>=0.9", 69 | ] 70 | dev = ["mqt.qubomaker[coverage, docs]"] 71 | check = [ 72 | "qiskit>=1.2.4", 73 | "scipy>=1.15; python_version >= '3.13'", 74 | "scipy>=1.13.1; python_version < '3.13'", 75 | ] 76 | tsplib = [ 77 | "tsplib95>=0.7.1; python_version < '3.13'", 78 | "numpy>=1.22,<2.0; python_version < '3.13'", 79 | "pandas[output-formatting]==2.1.2; python_version < '3.13'", 80 | "networkx==2.8.8", 81 | ] 82 | 83 | [project.urls] 84 | Homepage = "https://github.com/cda-tum/mqt-qubomaker" 85 | Documentation = "https://mqt.readthedocs.io/projects/qubomaker" 86 | Issues = "https://github.com/cda-tum/mqt-qubomaker/issues" 87 | Discussions = "https://github.com/cda-tum/mqt-qubomaker/discussions" 88 | 89 | [tool.setuptools_scm] 90 | 91 | [tool.setuptools.package-data] 92 | "mqt.qubomaker.pathfinder" = ["resources/*", "resources/constraints/*"] 93 | 94 | [tool.pytest.ini_options] 95 | minversion = "7.2" 96 | testpaths = ["tests"] 97 | addopts = ["-ra", "--strict-markers", "--strict-config", "--showlocals"] 98 | log_cli_level = "INFO" 99 | xfail_strict = true 100 | filterwarnings = [ 101 | "error", 102 | "ignore:.*invalid escape sequence.*::.*docplex.*", 103 | "ignore::matplotlib._api.deprecation.MatplotlibDeprecationWarning:pydev", 104 | ] 105 | 106 | [tool.coverage] 107 | run.source = ["mqt.qubomaker"] 108 | report.exclude_also = [ 109 | '\.\.\.', 110 | 'if TYPE_CHECKING:', 111 | 'raise AssertionError', 112 | 'raise NotImplementedError', 113 | ] 114 | 115 | [tool.mypy] 116 | mypy_path = "$MYPY_CONFIG_FILE_DIR/src,$MYPY_CONFIG_FILE_DIR/stubs" 117 | files = ["src", "tests"] 118 | python_version = "3.10" 119 | strict = true 120 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] 121 | warn_unreachable = true 122 | explicit_package_bases = true 123 | pretty = true 124 | 125 | [[tool.mypy.overrides]] 126 | module = ["types-Pillow", "jsonschema", "tsplib95.*", "ipywidgets.*", "IPython.*", "qiskit.*", "qiskit_algorithms.*", "matplotlib.*", "networkx.*", "mqt.ddsim.*", "joblib.*", "qiskit_optimization.*", "docplex.*", "typing_extensions.*", "numpy.*"] 127 | ignore_missing_imports = true 128 | 129 | 130 | [tool.check-wheel-contents] 131 | ignore = ["W002"] # Triggers on __init__.py's 132 | 133 | 134 | [tool.ruff] 135 | line-length = 120 136 | preview = true 137 | unsafe-fixes = true 138 | 139 | [tool.ruff.lint] 140 | extend-select = [ 141 | "A", # flake8-builtins 142 | "ANN", # flake8-annotations 143 | "ARG", # flake8-unused-arguments 144 | "ASYNC", # flake8-async 145 | "B", "B904", # flake8-bugbear 146 | "C4", # flake8-comprehensions 147 | "D", # pydocstyle 148 | "EM", # flake8-errmsg 149 | "EXE", # flake8-executable 150 | "FA", # flake8-future-annotations 151 | "FLY", # flynt 152 | "FURB", # refurb 153 | "I", # isort 154 | "ICN", # flake8-import-conventions 155 | "ISC", # flake8-implicit-str-concat 156 | "N", # flake8-naming 157 | "NPY", # numpy 158 | "PERF", # perflint 159 | "PGH", # pygrep-hooks 160 | "PIE", # flake8-pie 161 | "PL", # pylint 162 | "PT", # flake8-pytest-style 163 | "PTH", # flake8-use-pathlib 164 | "PYI", # flake8-pyi 165 | "Q", # flake8-quotes 166 | "RET", # flake8-return 167 | "RSE", # flake8-raise 168 | "RUF", # Ruff-specific 169 | "SLF", # flake8-self 170 | "SLOT", # flake8-slots 171 | "SIM", # flake8-simplify 172 | "TCH", # flake8-type-checking 173 | "TID251", # flake8-tidy-imports.banned-api 174 | "TRY", # tryceratops 175 | "UP", # pyupgrade 176 | "YTT", # flake8-2020 177 | ] 178 | ignore = [ 179 | "ISC001", # Conflicts with formatter 180 | "PLR09", # Too many <...> 181 | "PLR2004", # Magic value used in comparison 182 | "PLC0415", # Import should be at top of file 183 | "S101", # Use of assert detected 184 | ] 185 | isort.required-imports = ["from __future__ import annotations"] 186 | 187 | [tool.ruff.lint.flake8-tidy-imports.banned-api] 188 | "typing.Callable".msg = "Use collections.abc.Callable instead." 189 | "typing.Iterator".msg = "Use collections.abc.Iterator instead." 190 | "typing.Mapping".msg = "Use collections.abc.Mapping instead." 191 | "typing.Sequence".msg = "Use collections.abc.Sequence instead." 192 | "typing.Set".msg = "Use collections.abc.Set instead." 193 | 194 | [tool.ruff.lint.per-file-ignores] 195 | "src/mqt/qubomaker/pathfinder/cost_functions.py" = ["ANN401"] 196 | "test/python/**" = ["T20", "ANN"] 197 | "docs/**" = ["T20"] 198 | "noxfile.py" = ["T20", "TID251"] 199 | "*.pyi" = ["D"] # pydocstyle 200 | "*.ipynb" = [ 201 | "D", # pydocstyle 202 | "E402", # Allow imports to appear anywhere in Jupyter notebooks 203 | "I002", # Allow missing `from __future__ import annotations` import 204 | ] 205 | "src/mqt/qubomaker/_compat/**.py" = ["TID251"] 206 | 207 | [tool.ruff.lint.pydocstyle] 208 | convention = "google" 209 | 210 | 211 | [tool.typos] 212 | default.extend-ignore-re = [ 213 | '"id": ".*",', 214 | "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", # ignore line 215 | "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", # ignore block 216 | "NDArray", 217 | ] 218 | [tool.typos.default.extend-words] 219 | wille = "wille" 220 | 221 | 222 | [tool.repo-review] 223 | ignore = ["GH200"] 224 | 225 | [dependency-groups] 226 | build = [ 227 | "pybind11>=3.0.0", 228 | "scikit-build-core>=0.11.1", 229 | "setuptools-scm>=8.2", 230 | ] 231 | test = [ 232 | "pytest>=7.0", 233 | "pytest-console-scripts>=1.4", 234 | "pytest-cov>=6.1.1; python_version >= '3.12'", 235 | "pytest-cov>=5.0,<6.0; python_version < '3.12'", 236 | "pytest-sugar>=1.0.0", 237 | "pytest-xdist>=3.7.0", 238 | "coverage==7.2; python_version < '3.12'", 239 | "coverage>=7.6; python_version >= '3.12'", 240 | ] 241 | docs = [ 242 | "furo>=2024.8.6", 243 | "myst-nb>=1.2.0", 244 | "setuptools-scm>=8.2", 245 | "sphinx-copybutton>=0.5.2", 246 | "sphinx-design>=0.6.1", 247 | "sphinxcontrib-bibtex>=2.6.3", 248 | "sphinxcontrib-svg2pdfconverter>=1.3.0", 249 | "sphinxext-opengraph>=0.10.0", 250 | "sphinx-hoverxref>=1.4.2", 251 | "qiskit[qasm3-import,visualization]>=1.0.0; python_version < '3.13'", 252 | "qiskit[qasm3-import,visualization]>=2.0.0; python_version >= '3.13'", 253 | "openqasm-pygments>=0.1.2", 254 | "breathe>=4.36.0", 255 | "graphviz>=0.20.3", 256 | "sphinx>=8.1.3", 257 | "sphinx>=8.2.3; python_version >= '3.11'", 258 | ] 259 | dev = [ 260 | {include-group = "build"}, 261 | {include-group = "docs"}, 262 | {include-group = "test"}, 263 | ] 264 | -------------------------------------------------------------------------------- /notebooks/tsp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Initialisation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from __future__ import annotations\n", 17 | "\n", 18 | "import locale\n", 19 | "import random\n", 20 | "from pathlib import Path\n", 21 | "\n", 22 | "import mqt.qubomaker as qm\n", 23 | "from mqt.qubomaker import Graph\n", 24 | "from mqt.qubomaker import pathfinder as pf" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "with Path(\"input/graph\").open(encoding=locale.getpreferredencoding(False)) as file:\n", 34 | " graph = Graph.read(file)\n", 35 | "graph.plot()" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "# QUBO Construction" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "### Parameters\n", 50 | "\n", 51 | "We begin by setting up our parameters for the QuboGenerator. The `PathFindingQuboGenerator` supports the following parameters:\n", 52 | "\n", 53 | "- `encoding_type`: One of `ONE_HOT`, `DOMAIN_WALL`, or `BINARY`. the encoding for the binary variables in the QUBO formulation.\n", 54 | "- `n_paths`: The number of paths to be searched.\n", 55 | "- `max_path_length`: The maximum length of a path to be searched.\n", 56 | "- `loops`: Indicates, whether the searched path should be a loop." 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "encoding_type = pf.EncodingType.DOMAIN_WALL\n", 66 | "n_paths = 1\n", 67 | "max_path_length = graph.n_vertices\n", 68 | "loops = True\n", 69 | "\n", 70 | "settings = pf.PathFindingQuboGeneratorSettings(encoding_type, n_paths, max_path_length, loops)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "Using these settings, we can now create a new `PathFindingQuboGenerator`:" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "generator = pf.PathFindingQuboGenerator(\n", 87 | " objective_function=pf.MinimizePathLength(path_ids=[1]),\n", 88 | " graph=graph,\n", 89 | " settings=settings,\n", 90 | ")" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### Constraints\n", 98 | "\n", 99 | "We can add constraints to the QUBO generator with the `add_constraint` method. For this example, we add two constraints:\n", 100 | "\n", 101 | "1) `PathIsValid`: Enforces that the found path is actually valid (i.e. all edges in it exist).\n", 102 | "2) `PathContainsVerticesExactlyOnce`: Enforces that the given vertices appear exactly once in the path." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "generator.add_constraint(pf.PathIsValid(path_ids=[1]))\n", 112 | "generator.add_constraint(pf.PathContainsVerticesExactlyOnce(vertex_ids=graph.all_vertices, path_ids=[1]))" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "### Generate QUBO Formulation\n", 120 | "\n", 121 | "There are several ways to generate the problem's QUBO formulation.\n", 122 | "\n", 123 | "`QuboGenerator.construct()` generates a simplified mathematical expression for the problem: " 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "generator.construct()" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "`QuboGenerator.construct_expansion()` generates an expanded formula of the form\n", 140 | "\n", 141 | "$C_{1,1}x_1 + C_{1,2}x_1x_2 + C_{1,3}x_1x_3 + ... + C_{n-1,n}x_{n-1}x_n$" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "generator.construct_expansion()" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "`QuboGenerator.construct_qubo_matrix()` generates the QUBO formulation as a QUBO matrix $Q$ such that the QUBO problem can be formulated as\n", 158 | "\n", 159 | "$$\\mathbf{x}^* = \\argmin_\\mathbf{x} \\mathbf{x}^T Q \\mathbf{x}$$" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "A = generator.construct_qubo_matrix()\n", 169 | "qm.print_matrix(A)" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "# Test Results" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "### Brute Force Optimization\n", 184 | "\n", 185 | "We offer a naive brute-force optimization method to test simple QUBO formulations. It will generate the optimal assignment vector $\\mathbf{x}^*$ which can be passed to the method `QuboGenerator.decode_bit_array(...)` to translate it into a readable solution of the problem (in this case a list of paths)." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "import numpy as np\n", 195 | "\n", 196 | "(best_test, best_score) = qm.optimize_classically(A)\n", 197 | "\n", 198 | "x = np.array(best_test)\n", 199 | "pth = generator.decode_bit_array(best_test)\n", 200 | "print(pth)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "### Operator: Classical Eigensolver\n", 208 | "\n", 209 | "The method `QuboGenerator.construct_operator()` generates the QUBO formulation as a quantum operator that can be used for optimuzation. Using qiskit, we can compute its minimal eigenvalue using classical methods or quantum algorithms." 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "from typing import TYPE_CHECKING\n", 219 | "\n", 220 | "import numpy as np\n", 221 | "import numpy.typing as npt\n", 222 | "from qiskit.result import QuasiDistribution\n", 223 | "from qiskit_algorithms.minimum_eigensolvers import NumPyMinimumEigensolver\n", 224 | "\n", 225 | "if TYPE_CHECKING:\n", 226 | " from qiskit.quantum_info import Statevector\n", 227 | "\n", 228 | "\n", 229 | "def bitfield(n: int, width: int) -> list[int]:\n", 230 | " result = np.binary_repr(n, width)\n", 231 | " return [int(digit) for digit in result]\n", 232 | "\n", 233 | "\n", 234 | "def sample_most_likely(\n", 235 | " state_vector: QuasiDistribution | Statevector | dict[str, float],\n", 236 | ") -> npt.NDArray[np.int_ | np.float64]:\n", 237 | " \"\"\"Compute the most likely binary string from state vector.\n", 238 | " Args:\n", 239 | " state_vector: State vector or quasi-distribution.\n", 240 | "\n", 241 | " Returns:\n", 242 | " Binary string as an array of ints.\n", 243 | " \"\"\"\n", 244 | " values = (\n", 245 | " list(state_vector.values())\n", 246 | " if isinstance(state_vector, QuasiDistribution)\n", 247 | " else [state_vector[key] for key in state_vector]\n", 248 | " if isinstance(state_vector, dict)\n", 249 | " else state_vector\n", 250 | " )\n", 251 | " n = int(np.log2(len(values)))\n", 252 | " k = np.argmax(np.abs(values))\n", 253 | " x = bitfield(k, n)\n", 254 | " x.reverse()\n", 255 | " return np.asarray(x)\n", 256 | "\n", 257 | "\n", 258 | "op = generator.construct_operator()\n", 259 | "\n", 260 | "npme = NumPyMinimumEigensolver()\n", 261 | "result = npme.compute_minimum_eigenvalue(op)\n", 262 | "x = sample_most_likely(result.eigenstate)\n", 263 | "print(x)\n", 264 | "print(generator.decode_bit_array(x))\n", 265 | "print(result.eigenvalue)" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "## Quantum Circuits" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "The constructed operator can be used to create quantum circuits that solve the optimization problem.\n", 280 | "\n", 281 | "### QAOA" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": null, 287 | "metadata": {}, 288 | "outputs": [], 289 | "source": [ 290 | "seed = random.randint(10000, 20000)\n", 291 | "(qaoa, op) = generator.construct_qaoa(seed=seed)\n", 292 | "\n", 293 | "result = qaoa.compute_minimum_eigenvalue(op)\n", 294 | "x = sample_most_likely(result.eigenstate)\n", 295 | "print(generator.decode_bit_array(x))\n", 296 | "print(result.eigenvalue)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "# Other Features" 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "metadata": {}, 309 | "source": [ 310 | "_Use the JSON input format_" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": null, 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "with Path.open(\"input/tsp.json\") as file:\n", 320 | " generator_new = pf.PathFindingQuboGenerator.from_json(file.read(), graph)\n", 321 | "\n", 322 | "A = generator_new.construct_qubo_matrix()\n", 323 | "(best_test, best_score) = qm.optimize_classically(A)\n", 324 | "\n", 325 | "pth = generator_new.decode_bit_array(best_test)\n", 326 | "print(pth)" 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "metadata": {}, 332 | "source": [ 333 | "_Use Encoding suggestion_\n", 334 | "\n", 335 | "The `PathFindingQuboGenerator` supports the suggestion of the optimal encoding for a given problem instance, based on the number of required binary variables. " 336 | ] 337 | }, 338 | { 339 | "cell_type": "code", 340 | "execution_count": null, 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "with Path.open(\"input/tsp.json\") as file:\n", 345 | " print(pf.PathFindingQuboGenerator.suggest_encoding(file.read(), graph))" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": {}, 351 | "source": [ 352 | "## Different constraints\n", 353 | "\n", 354 | "_Also define the starting vertex of the path_" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": null, 360 | "metadata": {}, 361 | "outputs": [], 362 | "source": [ 363 | "generator_new = pf.PathFindingQuboGenerator(pf.MinimizePathLength([1]), graph, settings)\n", 364 | "generator_new.add_constraint(pf.PathIsValid([1]))\n", 365 | "generator_new.add_constraint(pf.PathContainsVerticesExactlyOnce(graph.all_vertices, [1]))\n", 366 | "\n", 367 | "\n", 368 | "generator_new.add_constraint(pf.PathStartsAt([3], 1))\n", 369 | "\n", 370 | "\n", 371 | "A = generator_new.construct_qubo_matrix()\n", 372 | "(best_test, best_score) = qm.optimize_classically(A)\n", 373 | "\n", 374 | "pth = generator_new.decode_bit_array(best_test)\n", 375 | "print(pth)" 376 | ] 377 | }, 378 | { 379 | "cell_type": "markdown", 380 | "metadata": {}, 381 | "source": [ 382 | "_Find the shortest paths $\\pi_1$ and $\\pi_2$ from $s_p$ to $t_p$ respectively that don't intersect_" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": null, 388 | "metadata": {}, 389 | "outputs": [], 390 | "source": [ 391 | "(s1, t1) = 1, 5\n", 392 | "(s2, t2) = 2, 6\n", 393 | "\n", 394 | "settings = pf.PathFindingQuboGeneratorSettings(encoding_type, n_paths=2, max_path_length=max_path_length, loops=True)\n", 395 | "generator_new = pf.PathFindingQuboGenerator(pf.MinimizePathLength([1, 2]), graph, settings)\n", 396 | "generator_new.add_constraint(pf.PathIsValid([1, 2]))\n", 397 | "generator_new.add_constraint(pf.PathStartsAt([s1], 1))\n", 398 | "generator_new.add_constraint(pf.PathStartsAt([s2], 2))\n", 399 | "generator_new.add_constraint(pf.PathEndsAt([t1], 1))\n", 400 | "generator_new.add_constraint(pf.PathEndsAt([t2], 2))\n", 401 | "generator_new.add_constraint(pf.PathsShareNoVertices(1, 2))" 402 | ] 403 | } 404 | ], 405 | "metadata": { 406 | "kernelspec": { 407 | "display_name": "mqt-test", 408 | "language": "python", 409 | "name": "python3" 410 | }, 411 | "language_info": { 412 | "codemirror_mode": { 413 | "name": "ipython", 414 | "version": 3 415 | }, 416 | "file_extension": ".py", 417 | "mimetype": "text/x-python", 418 | "name": "python", 419 | "nbconvert_exporter": "python", 420 | "pygments_lexer": "ipython3" 421 | } 422 | }, 423 | "nbformat": 4, 424 | "nbformat_minor": 2 425 | } 426 | -------------------------------------------------------------------------------- /notebooks/input/u574.tsp: -------------------------------------------------------------------------------- 1 | NAME : u574 2 | COMMENT : Drilling problem (Reinelt) 3 | TYPE : TSP 4 | DIMENSION : 574 5 | EDGE_WEIGHT_TYPE : EUC_2D 6 | NODE_COORD_SECTION 7 | 1 6.29570e+02 6.80860e+02 8 | 2 7.56500e+02 7.72700e+02 9 | 3 7.81890e+02 7.72700e+02 10 | 4 8.07300e+02 7.72700e+02 11 | 5 1.01051e+03 8.99710e+02 12 | 6 1.03590e+03 8.74300e+02 13 | 7 1.09061e+03 9.21190e+02 14 | 8 1.09061e+03 9.71990e+02 15 | 9 1.09061e+03 9.97400e+02 16 | 10 1.11211e+03 1.00131e+03 17 | 11 1.23910e+03 1.10289e+03 18 | 12 1.36609e+03 1.00131e+03 19 | 13 1.46770e+03 9.75900e+02 20 | 14 1.59859e+03 9.97400e+02 21 | 15 1.70020e+03 1.14980e+03 22 | 16 1.70020e+03 1.17520e+03 23 | 17 1.63670e+03 1.22600e+03 24 | 18 1.59859e+03 1.25141e+03 25 | 19 1.54391e+03 1.30609e+03 26 | 20 1.53119e+03 1.22990e+03 27 | 21 1.64551e+03 1.30609e+03 28 | 22 1.64939e+03 1.37840e+03 29 | 23 1.62400e+03 1.42920e+03 30 | 24 1.62400e+03 1.45461e+03 31 | 25 1.59859e+03 1.50539e+03 32 | 26 1.54391e+03 1.50930e+03 33 | 27 1.51850e+03 1.43311e+03 34 | 28 1.37000e+03 1.50539e+03 35 | 29 1.34070e+03 1.50930e+03 36 | 30 1.39150e+03 1.43311e+03 37 | 31 1.41689e+03 1.30609e+03 38 | 32 1.45500e+03 1.28070e+03 39 | 33 1.40811e+03 1.22600e+03 40 | 34 1.37000e+03 1.25141e+03 41 | 35 1.34461e+03 1.25141e+03 42 | 36 1.21760e+03 1.35301e+03 43 | 37 1.16680e+03 1.35301e+03 44 | 38 1.15410e+03 1.27680e+03 45 | 39 1.09061e+03 1.31490e+03 46 | 40 1.06520e+03 1.32760e+03 47 | 41 1.03980e+03 1.35301e+03 48 | 42 1.03590e+03 1.30609e+03 49 | 43 1.02320e+03 1.28070e+03 50 | 44 8.83500e+02 1.10289e+03 51 | 45 8.58110e+02 1.06480e+03 52 | 46 8.11190e+02 1.04820e+03 53 | 47 7.05700e+02 1.00131e+03 54 | 48 6.33400e+02 9.21190e+02 55 | 49 6.33400e+02 9.97400e+02 56 | 50 6.54900e+02 1.02670e+03 57 | 51 6.58810e+02 1.04820e+03 58 | 52 6.58810e+02 1.07359e+03 59 | 53 6.54900e+02 1.10289e+03 60 | 54 7.56500e+02 1.15369e+03 61 | 55 7.60390e+02 1.17520e+03 62 | 56 8.61990e+02 1.30221e+03 63 | 57 7.60390e+02 1.27680e+03 64 | 58 6.58810e+02 1.35301e+03 65 | 59 7.09610e+02 1.53080e+03 66 | 60 7.35000e+02 1.58160e+03 67 | 61 6.20700e+02 1.68320e+03 68 | 62 6.58810e+02 1.73400e+03 69 | 63 6.58810e+02 1.78480e+03 70 | 64 6.08010e+02 1.79750e+03 71 | 65 6.54900e+02 1.89029e+03 72 | 66 7.05700e+02 1.91570e+03 73 | 67 7.56500e+02 1.91570e+03 74 | 68 7.56500e+02 1.89029e+03 75 | 69 7.81890e+02 1.89029e+03 76 | 70 8.36600e+02 1.88641e+03 77 | 71 7.81890e+02 1.83949e+03 78 | 72 7.56500e+02 1.78869e+03 79 | 73 7.81890e+02 1.76330e+03 80 | 74 8.07300e+02 1.76330e+03 81 | 75 7.81890e+02 1.71250e+03 82 | 76 7.56500e+02 1.66170e+03 83 | 77 8.07300e+02 1.66170e+03 84 | 78 8.32700e+02 1.58551e+03 85 | 79 8.96190e+02 1.42039e+03 86 | 80 9.59710e+02 1.34420e+03 87 | 81 9.63590e+02 1.40381e+03 88 | 82 9.63590e+02 1.45461e+03 89 | 83 1.01051e+03 1.48391e+03 90 | 84 1.03590e+03 1.48391e+03 91 | 85 1.13750e+03 1.53471e+03 92 | 86 1.13750e+03 1.48391e+03 93 | 87 1.21369e+03 1.45850e+03 94 | 88 1.26840e+03 1.50539e+03 95 | 89 1.26840e+03 1.55619e+03 96 | 90 1.31529e+03 1.58551e+03 97 | 91 1.36609e+03 1.58551e+03 98 | 92 1.37000e+03 1.63240e+03 99 | 93 1.34461e+03 1.63240e+03 100 | 94 1.34461e+03 1.68320e+03 101 | 95 1.31920e+03 1.78480e+03 102 | 96 1.14141e+03 1.93721e+03 103 | 97 1.03980e+03 1.91180e+03 104 | 98 1.01051e+03 1.90301e+03 105 | 99 1.06131e+03 1.83949e+03 106 | 100 1.11211e+03 1.86490e+03 107 | 101 1.11211e+03 1.81410e+03 108 | 102 1.18830e+03 1.78869e+03 109 | 103 1.21369e+03 1.78869e+03 110 | 104 1.23910e+03 1.76330e+03 111 | 105 1.21369e+03 1.71250e+03 112 | 106 1.23910e+03 1.71250e+03 113 | 107 1.21369e+03 1.66170e+03 114 | 108 1.16680e+03 1.63240e+03 115 | 109 1.19221e+03 1.58160e+03 116 | 110 1.16680e+03 1.56891e+03 117 | 111 1.06131e+03 1.66170e+03 118 | 112 1.03980e+03 1.63240e+03 119 | 113 9.63590e+02 1.70859e+03 120 | 114 9.38200e+02 1.75939e+03 121 | 115 8.74710e+02 1.78480e+03 122 | 116 8.83500e+02 1.81410e+03 123 | 117 9.08910e+02 1.81410e+03 124 | 118 9.59710e+02 1.78869e+03 125 | 119 9.85100e+02 1.78869e+03 126 | 120 9.85100e+02 1.76330e+03 127 | 121 1.01439e+03 1.75939e+03 128 | 122 1.01439e+03 1.78480e+03 129 | 123 9.89000e+02 1.83561e+03 130 | 124 9.38200e+02 1.96260e+03 131 | 125 8.87400e+02 1.94990e+03 132 | 126 8.61990e+02 1.93721e+03 133 | 127 7.09610e+02 2.06420e+03 134 | 128 5.95290e+02 2.16580e+03 135 | 129 4.91990e+02 2.21480e+03 136 | 130 5.14490e+02 2.29730e+03 137 | 131 8.70800e+02 2.04270e+03 138 | 132 9.12790e+02 2.01340e+03 139 | 133 9.12790e+02 2.06420e+03 140 | 134 9.97790e+02 2.18240e+03 141 | 135 1.03590e+03 2.11891e+03 142 | 136 1.03590e+03 2.09350e+03 143 | 137 1.08670e+03 2.08080e+03 144 | 138 1.09939e+03 2.05539e+03 145 | 139 1.11211e+03 2.03000e+03 146 | 140 1.14141e+03 2.08961e+03 147 | 141 1.18830e+03 2.14430e+03 148 | 142 1.26449e+03 2.11891e+03 149 | 143 1.28990e+03 2.14430e+03 150 | 144 1.35340e+03 2.10619e+03 151 | 145 1.30260e+03 2.00461e+03 152 | 146 1.39150e+03 2.08080e+03 153 | 147 1.44230e+03 2.04270e+03 154 | 148 1.46770e+03 2.04270e+03 155 | 149 1.49311e+03 2.04270e+03 156 | 150 1.52240e+03 2.02609e+03 157 | 151 1.51850e+03 2.11891e+03 158 | 152 1.56930e+03 2.04270e+03 159 | 153 1.56930e+03 1.89029e+03 160 | 154 1.59471e+03 1.81410e+03 161 | 155 1.57320e+03 1.78480e+03 162 | 156 1.52240e+03 1.83561e+03 163 | 157 1.49699e+03 1.88641e+03 164 | 158 1.47160e+03 1.88641e+03 165 | 159 1.44619e+03 1.88641e+03 166 | 160 1.49311e+03 1.81410e+03 167 | 161 1.46770e+03 1.78869e+03 168 | 162 1.49311e+03 1.78869e+03 169 | 163 1.51850e+03 1.78869e+03 170 | 164 1.54391e+03 1.73789e+03 171 | 165 1.59471e+03 1.61090e+03 172 | 166 1.76369e+03 1.78480e+03 173 | 167 1.70020e+03 1.78480e+03 174 | 168 1.64551e+03 1.94109e+03 175 | 169 1.72170e+03 1.99189e+03 176 | 170 1.77250e+03 1.96650e+03 177 | 171 1.79789e+03 1.96650e+03 178 | 172 1.83600e+03 1.96650e+03 179 | 173 1.86141e+03 1.89029e+03 180 | 174 1.88680e+03 1.89029e+03 181 | 175 1.84869e+03 1.81410e+03 182 | 176 1.87410e+03 1.81410e+03 183 | 177 1.89949e+03 1.81410e+03 184 | 178 1.95029e+03 1.78869e+03 185 | 179 1.97570e+03 1.78869e+03 186 | 180 1.97570e+03 1.83949e+03 187 | 181 2.00109e+03 1.83949e+03 188 | 182 2.05189e+03 1.83949e+03 189 | 183 2.05189e+03 1.78869e+03 190 | 184 2.02650e+03 1.73789e+03 191 | 185 2.07730e+03 1.71250e+03 192 | 186 2.12811e+03 1.68711e+03 193 | 187 2.20430e+03 1.73789e+03 194 | 188 2.20430e+03 1.78869e+03 195 | 189 2.38211e+03 1.63631e+03 196 | 190 2.44561e+03 1.58551e+03 197 | 191 2.48369e+03 1.58551e+03 198 | 192 2.52570e+03 1.58160e+03 199 | 193 2.47490e+03 1.55619e+03 200 | 194 2.44949e+03 1.55619e+03 201 | 195 2.38600e+03 1.53080e+03 202 | 196 2.36061e+03 1.53080e+03 203 | 197 2.38211e+03 1.47119e+03 204 | 198 2.36061e+03 1.40381e+03 205 | 199 2.33520e+03 1.40381e+03 206 | 200 2.29320e+03 1.35689e+03 207 | 201 2.33131e+03 1.33150e+03 208 | 202 2.29711e+03 1.27680e+03 209 | 203 2.24631e+03 1.27680e+03 210 | 204 2.19551e+03 1.35301e+03 211 | 205 2.15740e+03 1.40381e+03 212 | 206 2.15740e+03 1.42920e+03 213 | 207 2.20820e+03 1.48000e+03 214 | 208 2.08119e+03 1.60699e+03 215 | 209 2.03039e+03 1.65779e+03 216 | 210 1.95420e+03 1.73400e+03 217 | 211 1.90340e+03 1.70859e+03 218 | 212 1.82721e+03 1.63240e+03 219 | 213 1.77641e+03 1.73400e+03 220 | 214 1.77250e+03 1.71250e+03 221 | 215 1.74711e+03 1.63631e+03 222 | 216 1.77250e+03 1.53471e+03 223 | 217 1.77250e+03 1.50930e+03 224 | 218 1.74711e+03 1.43311e+03 225 | 219 1.69631e+03 1.38230e+03 226 | 220 1.77250e+03 1.30609e+03 227 | 221 1.77250e+03 1.25529e+03 228 | 222 1.75980e+03 1.22990e+03 229 | 223 1.89949e+03 1.28070e+03 230 | 224 1.92490e+03 1.28070e+03 231 | 225 1.95029e+03 1.28070e+03 232 | 226 1.96689e+03 1.32760e+03 233 | 227 1.94150e+03 1.34029e+03 234 | 228 1.92881e+03 1.40381e+03 235 | 229 1.90340e+03 1.40381e+03 236 | 230 1.87801e+03 1.40381e+03 237 | 231 1.90340e+03 1.48000e+03 238 | 232 1.96689e+03 1.60699e+03 239 | 233 1.93760e+03 1.61090e+03 240 | 234 1.97570e+03 1.53471e+03 241 | 235 2.02650e+03 1.48391e+03 242 | 236 2.00500e+03 1.45461e+03 243 | 237 2.05189e+03 1.45850e+03 244 | 238 2.12811e+03 1.50930e+03 245 | 239 2.12811e+03 1.45850e+03 246 | 240 2.08119e+03 1.40381e+03 247 | 241 2.00500e+03 1.35301e+03 248 | 242 2.07730e+03 1.28070e+03 249 | 243 2.14080e+03 1.28070e+03 250 | 244 2.19160e+03 1.22990e+03 251 | 245 2.22971e+03 1.22990e+03 252 | 246 2.25510e+03 1.22990e+03 253 | 247 2.29320e+03 1.22990e+03 254 | 248 2.35670e+03 1.28070e+03 255 | 249 2.35670e+03 1.33150e+03 256 | 250 2.40750e+03 1.36961e+03 257 | 251 2.50910e+03 1.38230e+03 258 | 252 2.52180e+03 1.35689e+03 259 | 253 2.53449e+03 1.38230e+03 260 | 254 2.58529e+03 1.33150e+03 261 | 255 2.81391e+03 1.48391e+03 262 | 256 2.85199e+03 1.45850e+03 263 | 257 2.79240e+03 1.53080e+03 264 | 258 2.76699e+03 1.53080e+03 265 | 259 2.73770e+03 1.57279e+03 266 | 260 2.76311e+03 1.58551e+03 267 | 261 2.79240e+03 1.58160e+03 268 | 262 2.67811e+03 1.69590e+03 269 | 263 2.66541e+03 1.75939e+03 270 | 264 2.73770e+03 1.82680e+03 271 | 265 2.78850e+03 1.78869e+03 272 | 266 2.86859e+03 1.86100e+03 273 | 267 2.91551e+03 1.86490e+03 274 | 268 2.94480e+03 1.88641e+03 275 | 269 2.99561e+03 1.97529e+03 276 | 270 2.89400e+03 1.97529e+03 277 | 271 2.81779e+03 1.97529e+03 278 | 272 2.72891e+03 1.93721e+03 279 | 273 2.72891e+03 1.88641e+03 280 | 274 2.65270e+03 1.87369e+03 281 | 275 2.62340e+03 1.87760e+03 282 | 276 2.61461e+03 1.83561e+03 283 | 277 2.58529e+03 1.81410e+03 284 | 278 2.58920e+03 1.78480e+03 285 | 279 2.53840e+03 1.81020e+03 286 | 280 2.51301e+03 1.78480e+03 287 | 281 2.38600e+03 1.86100e+03 288 | 282 2.25900e+03 1.96260e+03 289 | 283 2.22090e+03 1.93721e+03 290 | 284 2.18279e+03 1.92449e+03 291 | 285 2.00500e+03 1.93721e+03 292 | 286 1.97961e+03 2.03881e+03 293 | 287 1.95420e+03 2.03881e+03 294 | 288 1.91609e+03 2.07689e+03 295 | 289 1.85260e+03 2.14039e+03 296 | 290 1.82721e+03 2.16580e+03 297 | 291 1.81449e+03 2.22930e+03 298 | 292 1.87410e+03 2.15699e+03 299 | 293 1.95029e+03 2.09350e+03 300 | 294 2.00109e+03 2.09350e+03 301 | 295 2.05189e+03 2.11891e+03 302 | 296 2.08119e+03 2.11500e+03 303 | 297 2.08119e+03 2.19119e+03 304 | 298 2.15350e+03 2.19510e+03 305 | 299 2.22971e+03 2.25859e+03 306 | 300 2.33131e+03 2.14430e+03 307 | 301 2.30980e+03 2.14039e+03 308 | 302 2.25900e+03 2.14039e+03 309 | 303 2.23359e+03 2.14039e+03 310 | 304 2.20820e+03 2.14039e+03 311 | 305 2.14471e+03 2.08961e+03 312 | 306 2.16619e+03 2.08080e+03 313 | 307 2.22971e+03 2.04270e+03 314 | 308 2.25510e+03 2.04270e+03 315 | 309 2.35670e+03 2.06811e+03 316 | 310 2.43680e+03 2.14039e+03 317 | 311 2.38211e+03 2.14430e+03 318 | 312 2.40750e+03 2.06811e+03 319 | 313 2.43289e+03 2.05539e+03 320 | 314 2.43289e+03 2.03000e+03 321 | 315 2.43680e+03 2.02609e+03 322 | 316 2.48369e+03 2.04270e+03 323 | 317 2.50910e+03 2.04270e+03 324 | 318 2.56381e+03 2.10230e+03 325 | 319 2.53840e+03 2.16580e+03 326 | 320 2.58529e+03 2.10619e+03 327 | 321 2.66150e+03 2.06811e+03 328 | 322 2.66150e+03 2.04270e+03 329 | 323 2.70350e+03 2.07689e+03 330 | 324 2.66150e+03 2.14430e+03 331 | 325 2.68689e+03 2.14430e+03 332 | 326 2.71619e+03 2.14039e+03 333 | 327 2.81391e+03 2.24590e+03 334 | 328 2.81391e+03 2.11891e+03 335 | 329 2.84320e+03 2.11500e+03 336 | 330 2.90279e+03 2.19510e+03 337 | 331 3.01711e+03 2.19510e+03 338 | 332 2.97900e+03 2.10619e+03 339 | 333 3.01711e+03 2.04270e+03 340 | 334 3.04250e+03 2.04270e+03 341 | 335 3.05520e+03 2.10619e+03 342 | 336 3.08061e+03 2.10619e+03 343 | 337 3.13141e+03 2.11891e+03 344 | 338 3.19490e+03 2.04270e+03 345 | 339 3.22029e+03 2.04270e+03 346 | 340 3.22029e+03 2.06811e+03 347 | 341 3.25840e+03 2.09350e+03 348 | 342 3.22420e+03 2.11500e+03 349 | 343 3.43350e+03 2.34529e+03 350 | 344 3.51600e+03 2.32279e+03 351 | 345 3.36000e+03 1.99189e+03 352 | 346 3.41080e+03 1.97920e+03 353 | 347 3.39811e+03 1.91570e+03 354 | 348 3.43619e+03 1.85221e+03 355 | 349 3.40199e+03 1.83561e+03 356 | 350 3.36000e+03 1.78869e+03 357 | 351 3.39811e+03 1.78869e+03 358 | 352 3.39811e+03 1.71250e+03 359 | 353 3.37270e+03 1.59820e+03 360 | 354 3.39811e+03 1.59820e+03 361 | 355 3.42350e+03 1.58551e+03 362 | 356 3.42350e+03 1.48391e+03 363 | 357 3.37660e+03 1.40381e+03 364 | 358 3.35119e+03 1.45461e+03 365 | 359 3.30041e+03 1.53080e+03 366 | 360 3.30041e+03 1.58160e+03 367 | 361 3.24961e+03 1.63240e+03 368 | 362 3.27109e+03 1.66170e+03 369 | 363 3.29650e+03 1.66170e+03 370 | 364 3.32580e+03 1.70859e+03 371 | 365 3.27500e+03 1.73400e+03 372 | 366 3.30041e+03 1.78480e+03 373 | 367 3.33850e+03 1.83561e+03 374 | 368 3.32580e+03 1.86100e+03 375 | 369 3.33850e+03 1.91180e+03 376 | 370 3.30041e+03 1.98801e+03 377 | 371 3.30041e+03 2.03881e+03 378 | 372 3.23301e+03 2.01730e+03 379 | 373 3.20760e+03 1.95381e+03 380 | 374 3.23301e+03 1.94109e+03 381 | 375 3.23301e+03 1.87760e+03 382 | 376 3.23301e+03 1.80141e+03 383 | 377 3.17340e+03 1.81020e+03 384 | 378 3.17340e+03 1.88641e+03 385 | 379 3.09330e+03 1.96650e+03 386 | 380 3.13141e+03 1.91570e+03 387 | 381 3.10600e+03 1.86490e+03 388 | 382 3.09330e+03 1.78869e+03 389 | 383 3.04641e+03 1.72131e+03 390 | 384 3.00830e+03 1.73400e+03 391 | 385 3.02100e+03 1.78480e+03 392 | 386 2.94480e+03 1.75939e+03 393 | 387 2.89400e+03 1.78480e+03 394 | 388 2.86859e+03 1.78480e+03 395 | 389 2.84320e+03 1.78480e+03 396 | 390 2.86471e+03 1.73789e+03 397 | 391 2.89010e+03 1.61090e+03 398 | 392 2.94090e+03 1.56010e+03 399 | 393 2.99170e+03 1.53471e+03 400 | 394 3.04641e+03 1.48000e+03 401 | 395 2.91551e+03 1.40770e+03 402 | 396 2.86471e+03 1.34420e+03 403 | 397 2.89010e+03 1.34420e+03 404 | 398 2.94090e+03 1.30609e+03 405 | 399 2.94090e+03 1.26801e+03 406 | 400 3.00439e+03 1.22990e+03 407 | 401 3.04250e+03 1.20449e+03 408 | 402 3.03369e+03 1.27680e+03 409 | 403 3.05520e+03 1.35689e+03 410 | 404 3.10600e+03 1.35689e+03 411 | 405 3.14801e+03 1.45461e+03 412 | 406 3.10990e+03 1.58160e+03 413 | 407 3.16949e+03 1.59820e+03 414 | 408 3.16949e+03 1.56010e+03 415 | 409 3.16949e+03 1.53471e+03 416 | 410 3.19490e+03 1.50930e+03 417 | 411 3.23301e+03 1.50930e+03 418 | 412 3.27500e+03 1.40381e+03 419 | 413 3.22420e+03 1.37840e+03 420 | 414 3.19881e+03 1.37840e+03 421 | 415 3.19490e+03 1.35689e+03 422 | 416 3.18221e+03 1.33150e+03 423 | 417 3.19490e+03 1.26801e+03 424 | 418 3.17340e+03 1.25141e+03 425 | 419 3.12260e+03 1.22600e+03 426 | 420 3.14410e+03 1.20449e+03 427 | 421 3.16949e+03 1.17910e+03 428 | 422 3.20760e+03 1.16641e+03 429 | 423 3.24570e+03 1.15369e+03 430 | 424 3.27109e+03 1.15369e+03 431 | 425 3.24961e+03 1.22600e+03 432 | 426 3.28381e+03 1.28070e+03 433 | 427 3.30920e+03 1.21721e+03 434 | 428 3.34730e+03 1.20449e+03 435 | 429 3.45279e+03 1.11170e+03 436 | 430 3.32580e+03 9.97400e+02 437 | 431 3.30041e+03 9.97400e+02 438 | 432 3.27500e+03 1.02279e+03 439 | 433 3.22420e+03 9.97400e+02 440 | 434 3.24570e+03 9.50510e+02 441 | 435 3.30920e+03 7.72700e+02 442 | 436 3.34730e+03 7.72700e+02 443 | 437 3.49199e+03 6.83010e+02 444 | 438 3.53699e+03 6.83010e+02 445 | 439 3.56400e+03 7.10000e+02 446 | 440 3.46344e+03 6.83010e+02 447 | 441 3.12260e+03 9.21190e+02 448 | 442 3.02100e+03 9.71990e+02 449 | 443 3.06789e+03 1.00131e+03 450 | 444 3.09330e+03 1.00131e+03 451 | 445 3.14410e+03 1.00131e+03 452 | 446 3.17340e+03 1.04820e+03 453 | 447 3.09721e+03 1.02279e+03 454 | 448 3.02100e+03 1.07359e+03 455 | 449 2.99561e+03 1.14980e+03 456 | 450 2.94090e+03 1.09020e+03 457 | 451 2.90279e+03 1.02670e+03 458 | 452 2.92820e+03 9.63200e+02 459 | 453 2.96631e+03 8.74300e+02 460 | 454 2.85590e+03 9.71990e+02 461 | 455 2.86859e+03 9.97400e+02 462 | 456 2.89400e+03 1.09900e+03 463 | 457 2.79240e+03 1.14980e+03 464 | 458 2.83051e+03 1.21330e+03 465 | 459 2.80510e+03 1.21330e+03 466 | 460 2.84320e+03 1.27680e+03 467 | 461 2.83051e+03 1.32760e+03 468 | 462 2.80510e+03 1.32760e+03 469 | 463 2.79240e+03 1.27680e+03 470 | 464 2.69961e+03 1.29340e+03 471 | 465 2.69961e+03 1.21721e+03 472 | 466 2.64000e+03 1.17520e+03 473 | 467 2.66150e+03 1.09020e+03 474 | 468 2.71619e+03 1.07359e+03 475 | 469 2.66541e+03 1.04820e+03 476 | 470 2.58920e+03 1.09900e+03 477 | 471 2.57260e+03 1.02670e+03 478 | 472 2.57650e+03 9.46600e+02 479 | 473 2.55109e+03 1.02279e+03 480 | 474 2.50029e+03 1.09900e+03 481 | 475 2.53840e+03 1.20061e+03 482 | 476 2.51301e+03 1.20061e+03 483 | 477 2.38211e+03 1.05211e+03 484 | 478 2.39480e+03 1.02670e+03 485 | 479 2.39480e+03 1.00131e+03 486 | 480 2.42020e+03 9.75900e+02 487 | 481 2.43289e+03 8.99710e+02 488 | 482 2.49641e+03 8.36190e+02 489 | 483 2.53449e+03 8.23500e+02 490 | 484 2.55990e+03 8.23500e+02 491 | 485 2.61070e+03 8.36190e+02 492 | 486 2.73770e+03 8.23500e+02 493 | 487 2.76311e+03 8.23500e+02 494 | 488 2.78850e+03 8.23500e+02 495 | 489 2.83930e+03 8.23500e+02 496 | 490 2.88461e+03 6.76600e+02 497 | 491 2.88961e+03 6.71600e+02 498 | 492 2.88668e+03 6.74510e+02 499 | 493 2.63061e+03 6.76600e+02 500 | 494 2.63270e+03 6.74510e+02 501 | 495 2.63561e+03 6.71600e+02 502 | 496 2.65912e+03 6.06800e+02 503 | 497 2.40971e+03 6.83010e+02 504 | 498 2.29326e+03 6.80860e+02 505 | 499 2.29035e+03 6.77950e+02 506 | 500 2.22342e+03 6.80860e+02 507 | 501 2.22051e+03 6.77950e+02 508 | 502 2.17262e+03 6.80860e+02 509 | 503 2.16971e+03 6.77950e+02 510 | 504 2.09350e+03 6.77950e+02 511 | 505 2.09641e+03 6.80860e+02 512 | 506 2.31859e+03 8.74300e+02 513 | 507 2.34789e+03 9.08500e+02 514 | 508 2.28439e+03 8.70390e+02 515 | 509 2.25900e+03 8.70390e+02 516 | 510 2.24631e+03 8.95800e+02 517 | 511 2.17010e+03 8.83110e+02 518 | 512 2.23359e+03 9.21190e+02 519 | 513 2.33131e+03 1.02670e+03 520 | 514 2.36061e+03 1.02279e+03 521 | 515 2.39869e+03 1.07359e+03 522 | 516 2.37330e+03 1.09900e+03 523 | 517 2.38600e+03 1.12439e+03 524 | 518 2.34789e+03 1.14980e+03 525 | 519 2.30980e+03 1.14980e+03 526 | 520 2.18279e+03 1.07359e+03 527 | 521 2.19551e+03 1.14980e+03 528 | 522 2.13199e+03 1.14980e+03 529 | 523 2.06850e+03 1.14980e+03 530 | 524 2.03039e+03 1.14980e+03 531 | 525 1.91221e+03 1.15369e+03 532 | 526 1.92881e+03 1.02279e+03 533 | 527 1.90340e+03 1.02279e+03 534 | 528 1.85260e+03 1.07359e+03 535 | 529 1.72170e+03 1.00131e+03 536 | 530 1.77250e+03 1.00131e+03 537 | 531 1.82330e+03 1.01400e+03 538 | 532 1.82330e+03 9.88590e+02 539 | 533 1.86141e+03 9.50510e+02 540 | 534 1.92881e+03 8.95800e+02 541 | 535 1.86529e+03 9.21190e+02 542 | 536 1.74711e+03 8.99710e+02 543 | 537 1.74711e+03 8.74300e+02 544 | 538 1.74711e+03 8.48910e+02 545 | 539 1.74711e+03 8.23500e+02 546 | 540 1.74711e+03 7.98110e+02 547 | 541 1.70020e+03 7.94200e+02 548 | 542 1.70020e+03 8.19610e+02 549 | 543 1.64939e+03 7.94200e+02 550 | 544 1.64939e+03 8.70390e+02 551 | 545 1.62400e+03 8.95800e+02 552 | 546 1.51850e+03 8.99710e+02 553 | 547 1.53119e+03 8.74300e+02 554 | 548 1.54391e+03 8.48910e+02 555 | 549 1.52240e+03 8.19610e+02 556 | 550 1.47160e+03 8.70390e+02 557 | 551 1.42080e+03 8.57700e+02 558 | 552 1.39539e+03 8.45000e+02 559 | 553 1.39539e+03 8.95800e+02 560 | 554 1.34461e+03 8.95800e+02 561 | 555 1.21760e+03 9.08500e+02 562 | 556 1.16680e+03 8.70390e+02 563 | 557 1.14141e+03 8.57700e+02 564 | 558 1.13750e+03 8.99710e+02 565 | 559 1.21760e+03 8.19610e+02 566 | 560 1.15410e+03 7.94200e+02 567 | 561 1.06520e+03 7.68810e+02 568 | 562 1.03980e+03 7.81500e+02 569 | 563 1.01439e+03 7.94200e+02 570 | 564 8.26410e+02 6.80860e+02 571 | 565 8.23500e+02 6.77950e+02 572 | 566 7.50210e+02 6.80860e+02 573 | 567 7.47300e+02 6.77950e+02 574 | 568 6.67660e+02 6.80860e+02 575 | 569 6.64750e+02 6.77950e+02 576 | 570 6.52700e+02 6.06800e+02 577 | 571 4.91990e+02 7.55000e+02 578 | 572 4.91990e+02 7.10000e+02 579 | 573 5.19000e+02 6.83010e+02 580 | 574 6.26640e+02 6.77950e+02 581 | EOF 582 | --------------------------------------------------------------------------------