├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── .git_archival.txt ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml ├── codecov.yml ├── contributing.md ├── pull_request_template.md ├── release-drafter.yml ├── renovate.json5 ├── support.md └── workflows │ ├── cd.yml │ ├── ci.yml │ ├── release-drafter.yml │ └── update-mqt-core.yml ├── .gitignore ├── .license-tools-config.json ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── apps ├── CMakeLists.txt ├── noise_aware.cpp └── simple.cpp ├── cmake ├── ExternalDependencies.cmake └── cmake_uninstall.cmake.in ├── docs ├── CHANGELOG.md ├── Doxyfile ├── UPGRADING.md ├── _static │ ├── custom.css │ ├── mqt_dark.png │ └── mqt_light.png ├── _templates │ └── page.html ├── conf.py ├── contributing.md ├── development_guide.md ├── index.md ├── installation.md ├── lit_header.bib ├── primitives.md ├── quickstart.md ├── references.md ├── refs.bib ├── simulators.md ├── simulators │ ├── CircuitSimulator.md │ ├── HybridSchrodingerFeynman.md │ ├── NoiseAwareSimulator.md │ ├── SimulationPathFramework.md │ └── UnitarySimulator.md └── support.md ├── include ├── CircuitSimulator.hpp ├── DeterministicNoiseSimulator.hpp ├── GroverSimulator.hpp ├── HybridSchrodingerFeynmanSimulator.hpp ├── PathSimulator.hpp ├── ShorFastSimulator.hpp ├── ShorSimulator.hpp ├── Simulator.hpp ├── StochasticNoiseSimulator.hpp └── UnitarySimulator.hpp ├── noxfile.py ├── pyproject.toml ├── scripts ├── README.md ├── approximation.sh ├── hybrid_simulation.sh └── primebases_timing_shor.sh ├── src ├── CMakeLists.txt ├── CircuitSimulator.cpp ├── DeterministicNoiseSimulator.cpp ├── GroverSimulator.cpp ├── HybridSchrodingerFeynmanSimulator.cpp ├── PathSimulator.cpp ├── ShorFastSimulator.cpp ├── ShorSimulator.cpp ├── Simulator.cpp ├── StochasticNoiseSimulator.cpp ├── UnitarySimulator.cpp ├── mqt │ └── ddsim │ │ ├── __init__.py │ │ ├── _version.pyi │ │ ├── deterministicnoisesimulator.py │ │ ├── header.py │ │ ├── hybridqasmsimulator.py │ │ ├── hybridstatevectorsimulator.py │ │ ├── job.py │ │ ├── pathqasmsimulator.py │ │ ├── pathstatevectorsimulator.py │ │ ├── primitives │ │ ├── __init__.py │ │ ├── estimator.py │ │ └── sampler.py │ │ ├── provider.py │ │ ├── py.typed │ │ ├── pyddsim.pyi │ │ ├── qasmsimulator.py │ │ ├── statevectorsimulator.py │ │ ├── stochasticnoisesimulator.py │ │ ├── target.py │ │ └── unitarysimulator.py └── python │ ├── CMakeLists.txt │ └── bindings.cpp ├── test ├── CMakeLists.txt ├── python │ ├── hybridsimulator │ │ ├── test_hybrid_qasm_simulator.py │ │ ├── test_hybrid_standalone_simulator.py │ │ └── test_hybrid_statevector_simulator.py │ ├── noiseawaresimulator │ │ ├── test_noiseaware_deterministic_simulator.py │ │ └── test_noiseaware_stochastic_simulator.py │ ├── primitives │ │ ├── test_estimator.py │ │ └── test_sampler.py │ ├── simulator │ │ ├── ghz_03.qasm │ │ ├── grover.py │ │ ├── test_multi_registers_convention.py │ │ ├── test_qasm_simulator.py │ │ ├── test_standalone_simulator.py │ │ └── test_statevector_simulator.py │ ├── taskbasedsimulator │ │ ├── test_path_sim_qasm_simulator.py │ │ ├── test_path_sim_standalone_simulator.py │ │ └── test_path_sim_statevector_simulator.py │ ├── test_provider.py │ ├── test_target.py │ └── unitarysimulator │ │ ├── test_standalone_unitary_simulator.py │ │ └── test_unitary_simulator.py ├── test_circuit_sim.cpp ├── test_det_noise_sim.cpp ├── test_fast_shor_sim.cpp ├── test_grover_sim.cpp ├── test_hybridsim.cpp ├── test_path_sim.cpp ├── test_shor_sim.cpp ├── test_stoch_noise_sim.cpp └── test_unitary_sim.cpp └── uv.lock /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IncludeBlocks: Regroup 3 | PointerAlignment: Left 4 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | FormatStyle: file 2 | 3 | Checks: | 4 | bugprone-*, 5 | -bugprone-easily-swappable-parameters, 6 | -bugprone-unchecked-optional-access, 7 | clang-analyzer-*, 8 | -clang-analyzer-core.NullDereference, 9 | clang-diagnostic-*, 10 | cppcoreguidelines-*, 11 | -cppcoreguidelines-non-private-member-variables-in-classes, 12 | -cppcoreguidelines-special-member-functions, 13 | -cppcoreguidelines-avoid-magic-numbers, 14 | -cppcoreguidelines-macro-usage, 15 | -cppcoreguidelines-pro-type-reinterpret-cast, 16 | -cppcoreguidelines-pro-bounds-constant-array-index, 17 | -cppcoreguidelines-avoid-do-while, 18 | google-*, 19 | -google-readability-todo, 20 | -google-build-using-namespace, 21 | misc-*, 22 | -misc-no-recursion, 23 | -misc-non-private-member-variables-in-classes, 24 | modernize-*, 25 | -modernize-use-trailing-return-type, 26 | performance-*, 27 | -performance-no-int-to-ptr, 28 | portability-*, 29 | readability-*, 30 | -readability-identifier-length, 31 | -readability-magic-numbers, 32 | -readability-function-cognitive-complexity 33 | 34 | CheckOptions: 35 | - key: readability-identifier-naming.ClassCase 36 | value: CamelCase 37 | - key: readability-identifier-naming.ClassIgnoredRegexp 38 | value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" 39 | - key: readability-identifier-naming.ConstantParameterCase 40 | value: camelBack 41 | - key: readability-identifier-naming.EnumCase 42 | value: CamelCase 43 | - key: readability-identifier-naming.EnumConstantCase 44 | value: CamelCase 45 | - key: readability-identifier-naming.FunctionCase 46 | value: camelBack 47 | - key: readability-identifier-naming.FunctionIgnoredRegexp 48 | value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" 49 | - key: readability-identifier-naming.GlobalConstantCase 50 | value: UPPER_CASE 51 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 52 | value: "true" 53 | - key: readability-identifier-naming.LocalConstantCase 54 | value: camelBack 55 | - key: readability-identifier-naming.LocalVariableCase 56 | value: camelBack 57 | - key: readability-identifier-naming.MemberCase 58 | value: camelBack 59 | - key: readability-identifier-naming.MemberIgnoredRegexp 60 | value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" 61 | - key: readability-identifier-naming.MethodCase 62 | value: camelBack 63 | - key: readability-identifier-naming.ParameterCase 64 | value: camelBack 65 | - key: readability-identifier-naming.ParameterIgnoredRegexp 66 | value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*|.*_" 67 | - key: readability-identifier-naming.ConstantParameterIgnoredRegexp 68 | value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*|.*_" 69 | - key: readability-identifier-naming.NamespaceCase 70 | value: lower_case 71 | - key: readability-identifier-naming.StaticConstantCase 72 | value: UPPER_CASE 73 | - key: readability-identifier-naming.StructCase 74 | value: CamelCase 75 | - key: readability-identifier-naming.VariableCase 76 | value: camelBack 77 | - key: misc-include-cleaner.IgnoreHeaders 78 | value: pybind11/detail/.* 79 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | format: 2 | line_width: 100 3 | keyword_case: "upper" 4 | autosort: true 5 | 6 | markup: 7 | first_comment_is_literal: true 8 | -------------------------------------------------------------------------------- /.git_archival.txt: -------------------------------------------------------------------------------- 1 | node: 3121b5775d4c0f6c204f5926d1c235f757eae082 2 | node-date: 2025-06-07T04:21:36Z 3 | describe-name: v2.0.0b2-36-g3121b577 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .git_archival.txt export-subst 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | name: 🐛 Bug Report 10 | description: Report a problem or unexpected behavior 11 | title: "🐛 " 12 | body: 13 | - type: markdown 14 | attributes: 15 | value: >- 16 | **Thanks for taking the time to report an issue!** 17 | 18 | ⚠ Before submitting: 19 | 1. Search [existing issues](https://github.com/munich-quantum-toolkit/ddsim/search?q=is%3Aissue&type=issues) to avoid duplicates 20 | 2. For questions or discussions, please use our [discussions forum](https://github.com/munich-quantum-toolkit/ddsim/discussions) 21 | - type: textarea 22 | attributes: 23 | label: System Information 24 | description: Please provide details about your environment 25 | placeholder: | 26 | - Operating System (including version): 27 | - MQT DDSIM version: 28 | - Python/C++ compiler version (if applicable): 29 | - Any relevant dependencies and their versions: 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Bug Description 35 | description: Provide a clear and detailed explanation of the issue you're experiencing. 36 | placeholder: | 37 | What happened? 38 | What did you expect to happen instead? 39 | Include any error messages or unexpected behavior you observed. 40 | validations: 41 | required: true 42 | - type: textarea 43 | attributes: 44 | label: Steps to Reproduce 45 | description: Provide specific steps to reproduce this issue 46 | placeholder: | 47 | 1. Set up environment with '...' 48 | 2. Configure workflow using '...' 49 | 3. Execute command '...' 50 | 4. Observe error/issue: '...' 51 | 52 | Include any relevant code snippets, workflow configurations, or input files that help demonstrate the problem. 53 | validations: 54 | required: true 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | name: ✨ Feature request 10 | description: Suggest a new feature or improvement 11 | title: "✨ " 12 | body: 13 | - type: markdown 14 | attributes: 15 | value: > 16 | **Thanks for helping improve this project by suggesting a feature!** 17 | 18 | ⚠ Before submitting: 19 | - Search [existing feature requests](https://github.com/munich-quantum-toolkit/ddsim/search?q=is%3Aissue&type=issues) to avoid duplicates 20 | - One feature per issue helps us track and implement ideas effectively 21 | 22 | - type: textarea 23 | attributes: 24 | label: Problem Statement 25 | description: >- 26 | Describe the problem you're facing and why it needs to be solved. What limitations are you encountering? 27 | placeholder: >- 28 | Currently, when I try to [action/task], I'm unable to [blockers/limitations]. 29 | This creates problems because [impact/consequences]. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: Proposed Solution 36 | description: > 37 | Describe your ideal solution. What would the feature look like? How would it work? 38 | placeholder: >- 39 | I'd like to see [feature/change] that would: 40 | - [benefit 1] 41 | - [benefit 2] 42 | - [example usage/scenario] 43 | validations: 44 | required: true 45 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "apps/*" 3 | - "extern/**/*" 4 | - "include/python/**/*" 5 | - "src/python/*.cpp" 6 | - "test/*.cpp" 7 | 8 | coverage: 9 | range: 60..90 10 | precision: 1 11 | status: 12 | project: off 13 | patch: off 14 | 15 | flag_management: 16 | default_rules: 17 | carryforward: true 18 | statuses: 19 | - type: project 20 | target: auto 21 | threshold: 0.5% 22 | removed_code_behavior: adjust_base 23 | - type: patch 24 | target: 90% 25 | threshold: 1% 26 | individual_flags: 27 | - name: cpp 28 | paths: 29 | - "include" 30 | - "src" 31 | - "!src/python/*.cpp" 32 | - name: python 33 | paths: 34 | - "src/mqt/**/*.py" 35 | statuses: 36 | - type: project 37 | threshold: 0.5% 38 | removed_code_behavior: adjust_base 39 | - type: patch 40 | target: 95% 41 | threshold: 1% 42 | 43 | parsers: 44 | gcov: 45 | branch_detection: 46 | conditional: no 47 | loop: no 48 | 49 | comment: 50 | layout: "reach, diff, flags, files" 51 | require_changes: true 52 | show_carryforward_flags: true 53 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and, if applicable, which issue is fixed. 4 | Please also include relevant motivation and context. 5 | List any dependencies that are required for this change. 6 | 7 | Fixes #(issue) <!--- Replace `(issue)` with the issue number fixed by this pull request. If this PR does not fix an issue, please remove this line. --> 8 | 9 | ## Checklist: 10 | 11 | <!--- 12 | This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. 13 | --> 14 | 15 | - [ ] The pull request only contains commits that are focused and relevant to this change. 16 | - [ ] I have added appropriate tests that cover the new/changed functionality. 17 | - [ ] I have updated the documentation to reflect these changes. 18 | - [ ] I have added entries to the changelog for any noteworthy additions, changes, fixes or removals. 19 | - [ ] I have added migration instructions to the upgrade guide (if needed). 20 | - [ ] The changes follow the project's style guidelines and introduce no new warnings. 21 | - [ ] The changes are fully tested and pass the CI checks. 22 | - [ ] I have reviewed my own code changes. 23 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "MQT DDSIM $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 | _Please refer to the [changelog](https://github.com/$OWNER/$REPOSITORY/blob/main/CHANGELOG.md) and the [upgrade guide](https://github.com/$OWNER/$REPOSITORY/blob/main/UPGRADING.md) for a structured overview of the changes._ 51 | 52 | $CHANGES 53 | 54 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 55 | -------------------------------------------------------------------------------- /.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, 12 | }, 13 | configMigration: true, 14 | labels: ["dependencies"], 15 | schedule: ["every weekend"], 16 | packageRules: [ 17 | { 18 | matchManagers: ["github-actions"], 19 | addLabels: ["github-actions"], 20 | commitMessagePrefix: "⬆\uFE0F\uD83D\uDC68\u200D\uD83D\uDCBB" 21 | }, 22 | { 23 | matchManagers: ["pep621"], 24 | addLabels: ["python"], 25 | commitMessagePrefix: "⬆\uFE0F\uD83D\uDC0D" 26 | }, 27 | { 28 | matchManagers: ["pre-commit"], 29 | addLabels: ["pre-commit"], 30 | commitMessagePrefix: "⬆\uFE0F\uD83E\uDE9D", 31 | }, 32 | { 33 | description: "Automerge patch updates", 34 | matchUpdateTypes: ["patch"], 35 | automerge: true 36 | }, 37 | { 38 | description: "Automerge minor updates for stable dependencies", 39 | matchManagers: ["pep621", "pre-commit"], 40 | matchUpdateTypes: ["minor", "patch"], 41 | matchCurrentVersion: "!/^0/", 42 | automerge: true 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /.github/support.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | If you are stuck with a problem using MQT DDSIM or have questions, please get in touch at our [Issues] or [Discussions]. 4 | 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. 9 | - Search through past [Issues] and [Discussions] 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. 11 | It is much easier to identify the cause of the issue if a handful of lines suffice to show that something is not working. 12 | 13 | You can also always reach us at [quantum.cda@xcit.tum.de](mailto:quantum.cda@xcit.tum.de). 14 | 15 | [Issues]: https://github.com/munich-quantum-toolkit/ddsim/issues 16 | [Discussions]: https://github.com/munich-quantum-toolkit/ddsim/discussions 17 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 🚀 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | 7 | permissions: 8 | attestations: write 9 | contents: read 10 | id-token: write 11 | 12 | jobs: 13 | # Builds the sdist and wheels on all supported platforms and uploads the resulting 14 | # wheels as GitHub artifacts `dev-cibw-*` or `cibw-*`, depending on whether the 15 | # workflow is triggered from a PR or a release, respectively. 16 | python-packaging: 17 | name: 🐍 Packaging 18 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging.yml@v1.9 19 | with: 20 | # Runs to enable 21 | enable-ubuntu2404: true 22 | enable-ubuntu2404-arm: true 23 | enable-macos13: true 24 | enable-macos14: true 25 | enable-windows2022: true 26 | enable-windows11-arm: true 27 | # Runs to disable 28 | enable-ubuntu2204: false 29 | enable-ubuntu2204-arm: false 30 | enable-macos15: false 31 | enable-windows2025: false 32 | 33 | # Downloads the previously generated artifacts and deploys to PyPI on published releases. 34 | deploy: 35 | if: github.event_name == 'release' && github.event.action == 'published' 36 | name: 🚀 Deploy to PyPI 37 | runs-on: ubuntu-latest 38 | environment: 39 | name: pypi 40 | url: https://pypi.org/p/mqt.ddsim 41 | needs: [python-packaging] 42 | steps: 43 | - uses: actions/download-artifact@v4 44 | with: 45 | pattern: cibw-* 46 | path: dist 47 | merge-multiple: true 48 | - name: Generate artifact attestation for sdist and wheel(s) 49 | uses: actions/attest-build-provenance@v2 50 | with: 51 | subject-path: "dist/*" 52 | - uses: pypa/gh-action-pypi-publish@release/v1 53 | -------------------------------------------------------------------------------- /.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 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | update_release_draft: 15 | name: Run 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: release-drafter/release-drafter@v6 22 | env: 23 | GITHUB_TOKEN: ${{ github.token }} 24 | -------------------------------------------------------------------------------- /.github/workflows/update-mqt-core.yml: -------------------------------------------------------------------------------- 1 | name: Update MQT Core 2 | on: 3 | schedule: 4 | # run once a month on the first day of the month at 00:00 UTC 5 | - cron: "0 0 1 * *" 6 | workflow_dispatch: 7 | inputs: 8 | update-to-head: 9 | description: "Update to the latest commit on the default branch" 10 | type: boolean 11 | required: false 12 | default: false 13 | pull_request: 14 | paths: 15 | - .github/workflows/update-mqt-core.yml 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | update-mqt-core: 23 | name: ⬆️ Update MQT Core 24 | uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-mqt-core-update.yml@v1.9 25 | with: 26 | update-to-head: ${{ github.event.inputs.update-to-head == 'true' }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | .ccache/ 9 | cmake-build-* 10 | 11 | # Distribution / packaging 12 | .Python 13 | /build/ 14 | /test/*/build 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | *.profraw 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | docs/api/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 91 | __pypackages__/ 92 | 93 | # Celery stuff 94 | celerybeat-schedule 95 | celerybeat.pid 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env* 102 | .venv* 103 | env*/ 104 | venv*/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | # pytype static type analyzer 128 | .pytype/ 129 | 130 | # Cython debug symbols 131 | cython_debug/ 132 | 133 | # setuptools_scm 134 | src/*/_version.py 135 | 136 | # SKBuild cache dir 137 | _skbuild/ 138 | 139 | # Any build dirs in the tests 140 | test/**/build/ 141 | /src/mqt/**/_version.py 142 | 143 | # Common editor files 144 | *~ 145 | *.swp 146 | 147 | # RPM spec file 148 | !/distro/*.spec 149 | /distro/*.tar.gz 150 | *.rpm 151 | 152 | # ruff 153 | .ruff_cache/ 154 | 155 | # OS specific stuff 156 | .DS_Store 157 | .DS_Store? 158 | ._* 159 | .Spotlight-V100 160 | .Trashes 161 | ehthumbs.db 162 | Thumbs.db 163 | 164 | .idea/ 165 | .vscode/ 166 | # tmt setup 167 | /distro/main.fmf 168 | /distro/plans/main.fmf 169 | /distro/tests/main.fmf 170 | 171 | /docs/**/build 172 | .vs 173 | out/build 174 | 175 | node_modules/ 176 | wheelhouse/ 177 | -------------------------------------------------------------------------------- /.license-tools-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Chair for Design Automation, TUM\nCopyright (c) 2025 Munich Quantum Software Company GmbH", 4 | "years": [2023, 2025] 5 | }, 6 | "force_author": true, 7 | "license": "MIT", 8 | "title": false, 9 | "include": ["**/*"], 10 | "style_override_for_suffix": { 11 | ".pyi": "DOCSTRING_STYLE", 12 | ".in": "POUND_STYLE", 13 | ".mlir": "SLASH_STYLE", 14 | ".td": "SLASH_STYLE", 15 | ".yaml": "POUND_STYLE", 16 | ".toml": "POUND_STYLE", 17 | ".yml": "POUND_STYLE" 18 | }, 19 | "exclude": [ 20 | "^\\.[^/]+", 21 | "/\\.[^/]+", 22 | ".*\\.qasm", 23 | ".*\\.md", 24 | ".*\\.bib", 25 | ".*\\.cff", 26 | ".*\\.css", 27 | ".*\\.ipynb", 28 | ".*\\.json", 29 | ".*\\.html", 30 | ".*\\.tfc", 31 | ".*\\.qc", 32 | ".*\\.real", 33 | ".*\\.rst", 34 | ".*\\.tex", 35 | ".*\\.profile", 36 | "uv\\.lock", 37 | "py\\.typed", 38 | ".*build.*" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.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 | autoupdate_schedule: quarterly 14 | skip: [mypy] 15 | 16 | repos: 17 | # Standard hooks 18 | - repo: https://github.com/pre-commit/pre-commit-hooks 19 | rev: v5.0.0 20 | hooks: 21 | - id: check-added-large-files 22 | - id: check-case-conflict 23 | - id: check-docstring-first 24 | - id: check-merge-conflict 25 | - id: check-toml 26 | - id: check-yaml 27 | - id: debug-statements 28 | - id: end-of-file-fixer 29 | - id: mixed-line-ending 30 | - id: trailing-whitespace 31 | 32 | # Clean jupyter notebooks 33 | - repo: https://github.com/srstevenson/nb-clean 34 | rev: 4.0.1 35 | hooks: 36 | - id: nb-clean 37 | args: 38 | - --remove-empty-cells 39 | - --preserve-cell-metadata 40 | - raw_mimetype 41 | - -- 42 | 43 | # Handling unwanted unicode characters 44 | - repo: https://github.com/sirosen/texthooks 45 | rev: 0.6.8 46 | hooks: 47 | - id: fix-ligatures 48 | - id: fix-smartquotes 49 | 50 | # Check for common mistakes 51 | - repo: https://github.com/pre-commit/pygrep-hooks 52 | rev: v1.10.0 53 | hooks: 54 | - id: rst-backticks 55 | - id: rst-directive-colons 56 | - id: rst-inline-touching-normal 57 | 58 | # Check for license headers 59 | - repo: https://github.com/emzeat/mz-lictools 60 | rev: v2.7.0 61 | hooks: 62 | - id: license-tools 63 | 64 | # Python linting using ruff 65 | - repo: https://github.com/astral-sh/ruff-pre-commit 66 | rev: v0.11.13 67 | hooks: 68 | - id: ruff 69 | args: ["--fix", "--show-fixes"] 70 | - id: ruff-format 71 | 72 | # Also run Black on examples in the documentation 73 | - repo: https://github.com/adamchainz/blacken-docs 74 | rev: 1.19.1 75 | hooks: 76 | - id: blacken-docs 77 | additional_dependencies: [black==25.*] 78 | 79 | # CMake format and lint the CMakeLists.txt files 80 | - repo: https://github.com/cheshirekow/cmake-format-precommit 81 | rev: v0.6.13 82 | hooks: 83 | - id: cmake-format 84 | additional_dependencies: [pyyaml] 85 | types: [file] 86 | files: (\.cmake|CMakeLists.txt)(.in)?$ 87 | 88 | # Clang-format the C++ part of the code base automatically 89 | - repo: https://github.com/pre-commit/mirrors-clang-format 90 | rev: v20.1.5 91 | hooks: 92 | - id: clang-format 93 | types_or: [c++, c, cuda] 94 | 95 | # Format configuration files with prettier 96 | - repo: https://github.com/rbubley/mirrors-prettier 97 | rev: v3.5.3 98 | hooks: 99 | - id: prettier 100 | types_or: [yaml, markdown, html, css, scss, javascript, json] 101 | 102 | # Check static types with mypy 103 | - repo: https://github.com/pre-commit/mirrors-mypy 104 | rev: v1.16.0 105 | hooks: 106 | - id: mypy 107 | files: ^(src/mqt|test/python|noxfile.py) 108 | additional_dependencies: 109 | - nox 110 | - numpy 111 | - pytest 112 | - mqt.core>=3 113 | 114 | # Check for spelling 115 | - repo: https://github.com/crate-ci/typos 116 | rev: v1.33.1 117 | hooks: 118 | - id: typos 119 | 120 | # Catch common capitalization mistakes 121 | - repo: local 122 | hooks: 123 | - id: disallow-caps 124 | name: Disallow improper capitalization 125 | language: pygrep 126 | entry: PyBind|Numpy|Cmake|CCache|Github|PyTest|Mqt|Tum 127 | exclude: .pre-commit-config.yaml 128 | 129 | # Check best practices for scientific Python code 130 | - repo: https://github.com/scientific-python/cookie 131 | rev: 2025.05.02 132 | hooks: 133 | - id: sp-repo-review 134 | additional_dependencies: ["repo-review[cli]"] 135 | 136 | # Check JSON schemata 137 | - repo: https://github.com/python-jsonschema/check-jsonschema 138 | rev: 0.33.0 139 | hooks: 140 | - id: check-dependabot 141 | - id: check-github-workflows 142 | - id: check-readthedocs 143 | 144 | # Check the pyproject.toml file 145 | - repo: https://github.com/henryiii/validate-pyproject-schema-store 146 | rev: 2025.05.12 147 | hooks: 148 | - id: validate-pyproject 149 | 150 | # Tidy up BibTeX files 151 | - repo: https://github.com/FlamingTempura/bibtex-tidy 152 | rev: v1.14.0 153 | hooks: 154 | - id: bibtex-tidy 155 | args: 156 | [ 157 | "--align=20", 158 | "--curly", 159 | "--months", 160 | "--blank-lines", 161 | "--sort", 162 | "--strip-enclosing-braces", 163 | "--sort-fields", 164 | "--trailing-commas", 165 | "--remove-empty-fields", 166 | ] 167 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | formats: 4 | # - pdf 5 | - htmlzip 6 | 7 | sphinx: 8 | configuration: docs/conf.py 9 | 10 | build: 11 | os: ubuntu-24.04 12 | tools: 13 | python: "3.12" 14 | apt_packages: 15 | - graphviz 16 | - inkscape 17 | jobs: 18 | post_checkout: 19 | # Skip docs build if the commit message contains "skip ci" 20 | - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 21 | # Skip docs build if there are no changes related to docs 22 | - | 23 | if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ .readthedocs.yaml src/mqt/ src/python include/*/python .github/contributing* .github/support*; 24 | then 25 | exit 183; 26 | fi 27 | # Unshallow the git clone and fetch tags to get proper version information 28 | - git fetch --unshallow --tags 29 | pre_build: 30 | # Set up uv 31 | - asdf plugin add uv 32 | - asdf install uv latest 33 | - asdf global uv latest 34 | # Set up build time dependencies including a source distribution of mqt-core. 35 | - uv sync --only-group build --only-group docs --no-binary-package mqt-core 36 | # The default version of CMake on Ubuntu 24.04 is too old, so we need to install a newer version. 37 | - uv pip install cmake 38 | build: 39 | html: 40 | - uv run --frozen --no-dev --no-build-isolation-package mqt-ddsim -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html 41 | htmlzip: 42 | - uv run --frozen --no-dev --no-build-isolation-package mqt-ddsim -m sphinx -T -b dirhtml -d docs/_build/doctrees -D language=en docs docs/_build/dirhtml 43 | - mkdir -p $READTHEDOCS_OUTPUT/htmlzip 44 | - zip -r $READTHEDOCS_OUTPUT/htmlzip/html.zip docs/_build/dirhtml/* 45 | # pdf: 46 | # - uv run --frozen --no-dev --no-build-isolation-package mqt-ddsim -m sphinx -T -b latex -d docs/_build/doctrees -D language=en docs docs/_build/latex 47 | # - cd docs/_build/latex && latexmk -pdf -f -dvi- -ps- -interaction=nonstopmode -jobname=$READTHEDOCS_PROJECT 48 | # - mkdir -p $READTHEDOCS_OUTPUT/pdf 49 | # - cp docs/_build/latex/$READTHEDOCS_PROJECT.pdf $READTHEDOCS_OUTPUT/pdf/$READTHEDOCS_PROJECT.pdf 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on a mixture of [Keep a Changelog] and [Common Changelog]. 6 | This project adheres to [Semantic Versioning], with the exception that minor releases may include breaking changes. 7 | 8 | ## [Unreleased] 9 | 10 | _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#Unreleased)._ 11 | 12 | ### Added 13 | 14 | - ✨ Add Windows 11 ARM64 wheelsa and C++ testing ([#556]) ([**@burgholzer**]) 15 | 16 | ### Changed 17 | 18 | - **Breaking**: 🚚 Move MQT DDSIM to the [munich-quantum-toolkit] GitHub organization 19 | - **Breaking**: ♻️ Use the `mqt-core` Python package for handling circuits ([#336]) ([**@burgholzer**]) 20 | - **Breaking**: ⬆️ Bump minimum required CMake version to `3.24.0` ([#538]) ([**@burgholzer**]) 21 | - 📝 Rework existing project documentation ([#556]) ([**@burgholzer**]) 22 | 23 | ### Removed 24 | 25 | - **Breaking**: 🔥 Remove the TN flow from the path simulator ([#336]) ([**@burgholzer**]) 26 | - **Breaking**: 🔥 Remove some superfluous C++ executables ([#336]) ([**@burgholzer**]) 27 | - **Breaking**: 🔥 Remove support for `.real`, `.qc`, `.tfc`, and `GRCS` files ([#538]) ([**@burgholzer**]) 28 | 29 | ## [1.24.0] - 2024-10-10 30 | 31 | _📚 Refer to the [GitHub Release Notes] for previous changelogs._ 32 | 33 | <!-- Version links --> 34 | 35 | [unreleased]: https://github.com/munich-quantum-toolkit/ddsim/compare/v1.24.0...HEAD 36 | [1.24.0]: https://github.com/munich-quantum-toolkit/ddsim/releases/tag/v1.24.0 37 | 38 | <!-- PR links --> 39 | 40 | [#556]: https://github.com/munich-quantum-toolkit/ddsim/pulls/556 41 | [#538]: https://github.com/munich-quantum-toolkit/ddsim/pulls/538 42 | [#336]: https://github.com/munich-quantum-toolkit/ddsim/pulls/336 43 | 44 | <!-- Contributor --> 45 | 46 | [**@burgholzer**]: https://github.com/burgholzer 47 | 48 | <!-- General links --> 49 | 50 | [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ 51 | [Common Changelog]: https://common-changelog.org 52 | [Semantic Versioning]: https://semver.org/spec/v2.0.0.html 53 | [GitHub Release Notes]: https://github.com/munich-quantum-toolkit/ddsim/releases 54 | [munich-quantum-toolkit]: https://github.com/munich-quantum-toolkit 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | # set required cmake version 10 | cmake_minimum_required(VERSION 3.24...4.0) 11 | 12 | project( 13 | mqt-ddsim 14 | LANGUAGES C CXX 15 | DESCRIPTION "MQT DDSIM - A quantum circuit simulator based on decision diagrams") 16 | 17 | option(BUILD_MQT_DDSIM_BINDINGS "Build the MQT DDSIM Python bindings" OFF) 18 | if(BUILD_MQT_DDSIM_BINDINGS) 19 | # ensure that the BINDINGS option is set 20 | set(BINDINGS 21 | ON 22 | CACHE INTERNAL "Enable settings related to Python bindings") 23 | # Some common settings for finding Python 24 | set(Python_FIND_VIRTUALENV 25 | FIRST 26 | CACHE STRING "Give precedence to virtualenvs when searching for Python") 27 | set(Python_FIND_FRAMEWORK 28 | LAST 29 | CACHE STRING "Prefer Brew/Conda to Apple framework Python") 30 | set(Python_ARTIFACTS_INTERACTIVE 31 | ON 32 | CACHE BOOL "Prevent multiple searches for Python and instead cache the results.") 33 | 34 | if(DISABLE_GIL) 35 | message(STATUS "Disabling Python GIL") 36 | add_compile_definitions(Py_GIL_DISABLED) 37 | endif() 38 | 39 | # top-level call to find Python 40 | find_package( 41 | Python 3.9 REQUIRED 42 | COMPONENTS Interpreter Development.Module 43 | OPTIONAL_COMPONENTS Development.SABIModule) 44 | endif() 45 | 46 | # check if this is the master project or used via add_subdirectory 47 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 48 | set(MQT_DDSIM_MASTER_PROJECT ON) 49 | else() 50 | set(MQT_DDSIM_MASTER_PROJECT OFF) 51 | endif() 52 | 53 | option(BUILD_MQT_DDSIM_TESTS "Also build tests for the MQT DDSIM project" 54 | ${MQT_DDSIM_MASTER_PROJECT}) 55 | option(BUILD_MQT_DDSIM_CLI "Build the MQT DDSIM command line interface" ${MQT_DDSIM_MASTER_PROJECT}) 56 | 57 | include(cmake/ExternalDependencies.cmake) 58 | 59 | # set the include directory for the build tree 60 | set(MQT_DDSIM_INCLUDE_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") 61 | 62 | add_subdirectory(src) 63 | 64 | if(BUILD_MQT_DDSIM_TESTS) 65 | enable_testing() 66 | include(GoogleTest) 67 | add_subdirectory(test) 68 | endif() 69 | 70 | if(BUILD_MQT_DDSIM_CLI) 71 | add_subdirectory(apps) 72 | endif() 73 | 74 | if(MQT_DDSIM_MASTER_PROJECT) 75 | if(NOT TARGET mqt-ddsim-uninstall) 76 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in 77 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake IMMEDIATE @ONLY) 78 | add_custom_target(mqt-ddsim-uninstall COMMAND ${CMAKE_COMMAND} -P 79 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) 80 | endif() 81 | else() 82 | set(mqt-ddsim_FOUND 83 | TRUE 84 | CACHE INTERNAL "True if mqt-ddsim is found on the system") 85 | endif() 86 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 4 | Copyright (c) 2025 Munich Quantum Software Company GmbH 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [changelog](CHANGELOG.md). 4 | 5 | ## [Unreleased] 6 | 7 | This major release introduces several breaking changes, including the removal of deprecated features. 8 | The following paragraphs describe the most important changes and how to adapt your code accordingly. 9 | We intend to provide a more comprehensive migration guide for future releases. 10 | 11 | The major change in this major release is the move to the MQT Core Python package. 12 | This move allows us to make `qiskit` a fully optional dependency and entirely rely on the MQT Core IR for representing circuits. 13 | Additionally, the `mqt-core` Python package now ships all its C++ libraries as shared libraries so that these need not be fetched or built as part of the build process. 14 | This was tricky to achieve cross-platform, and you can find some more backstory in the corresponding [PR](https://github.com/munich-quantum-toolkit/ddsim/pulls/336). 15 | We expect this integration to mature over the next few releases. 16 | If you encounter any issues, please let us know. 17 | 18 | Support for the tensor network strategy in the path simulator has been removed. 19 | If you still depend on that method, please use the last version of MQT DDSIM that supports them, which is `1.24.0`. 20 | 21 | MQT Core itself dropped support for several parsers in `v3.0.0`, including the `.real`, `.qc`, `.tfc`, and `GRCS` parsers. 22 | The `.real` parser lives on as part of the [MQT SyReC] project. All others have been removed without replacement. 23 | Consequently, these input formats are no longer supported in MQT QMAP. 24 | 25 | MQT DDSIM has moved to the [munich-quantum-toolkit](https://github.com/munich-quantum-toolkit) GitHub organization under https://github.com/munich-quantum-toolkit/ddsim. 26 | While most links should be automatically redirected, please update any links in your code to point to the new location. 27 | All links in the documentation have been updated accordingly. 28 | 29 | MQT DDSIM now requires CMake 3.24 or higher. 30 | Most modern operating systems should have this version available in their package manager. 31 | Alternatively, CMake can be conveniently installed from PyPI using the [`cmake`](https://pypi.org/project/cmake/) package. 32 | 33 | [MQT SyReC]: https://github.com/cda-tum/mqt-syrec 34 | [unreleased]: https://github.com/munich-quantum-toolkit/qmap/compare/v1.24.0...HEAD 35 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | # macro to add a executable with the given libraries 10 | macro(ADD_SIM_EXECUTABLE appname) 11 | add_executable(mqt-ddsim-${appname} ${appname}.cpp) 12 | target_link_libraries(mqt-ddsim-${appname} PRIVATE MQT::DDSim MQT::CoreQASM ${ARGN}) 13 | endmacro() 14 | 15 | add_sim_executable(simple cxxopts::cxxopts) 16 | target_link_libraries(mqt-ddsim-simple PRIVATE MQT::CoreAlgorithms) 17 | 18 | if(Threads_FOUND) 19 | add_sim_executable(noise_aware cxxopts::cxxopts) 20 | target_link_libraries(mqt-ddsim-noise_aware PRIVATE Threads::Threads) 21 | endif() 22 | -------------------------------------------------------------------------------- /cmake/ExternalDependencies.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | # Declare all external dependencies and make sure that they are available. 10 | 11 | include(FetchContent) 12 | set(FETCH_PACKAGES "") 13 | 14 | if(BUILD_MQT_DDSIM_BINDINGS) 15 | # Manually detect the installed mqt-core package. 16 | execute_process( 17 | COMMAND "${Python_EXECUTABLE}" -m mqt.core --cmake_dir 18 | OUTPUT_STRIP_TRAILING_WHITESPACE 19 | OUTPUT_VARIABLE mqt-core_DIR 20 | ERROR_QUIET) 21 | 22 | # Add the detected directory to the CMake prefix path. 23 | if(mqt-core_DIR) 24 | list(APPEND CMAKE_PREFIX_PATH "${mqt-core_DIR}") 25 | message(STATUS "Found mqt-core package: ${mqt-core_DIR}") 26 | endif() 27 | 28 | if(NOT SKBUILD) 29 | # Manually detect the installed pybind11 package. 30 | execute_process( 31 | COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir 32 | OUTPUT_STRIP_TRAILING_WHITESPACE 33 | OUTPUT_VARIABLE pybind11_DIR) 34 | 35 | # Add the detected directory to the CMake prefix path. 36 | list(APPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") 37 | endif() 38 | 39 | # add pybind11 library 40 | find_package(pybind11 2.13.6 CONFIG REQUIRED) 41 | endif() 42 | 43 | # cmake-format: off 44 | set(MQT_CORE_MINIMUM_VERSION 3.0.0 45 | CACHE STRING "MQT Core minimum version") 46 | set(MQT_CORE_VERSION 3.0.2 47 | CACHE STRING "MQT Core version") 48 | set(MQT_CORE_REV "9b6e01482cc77f48c828d988407ee4f8e4e93b56" 49 | CACHE STRING "MQT Core identifier (tag, branch or commit hash)") 50 | set(MQT_CORE_REPO_OWNER "munich-quantum-toolkit" 51 | CACHE STRING "MQT Core repository owner (change when using a fork)") 52 | # cmake-format: on 53 | FetchContent_Declare( 54 | mqt-core 55 | GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git 56 | GIT_TAG ${MQT_CORE_REV} 57 | FIND_PACKAGE_ARGS ${MQT_CORE_MINIMUM_VERSION}) 58 | list(APPEND FETCH_PACKAGES mqt-core) 59 | 60 | if(BUILD_MQT_DDSIM_TESTS) 61 | set(gtest_force_shared_crt 62 | ON 63 | CACHE BOOL "" FORCE) 64 | set(GTEST_VERSION 65 | 1.16.0 66 | CACHE STRING "Google Test version") 67 | set(GTEST_URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.tar.gz) 68 | FetchContent_Declare(googletest URL ${GTEST_URL} FIND_PACKAGE_ARGS ${GTEST_VERSION} NAMES GTest) 69 | list(APPEND FETCH_PACKAGES googletest) 70 | endif() 71 | 72 | set(TF_BUILD_TESTS 73 | OFF 74 | CACHE INTERNAL "") 75 | set(TF_BUILD_EXAMPLES 76 | OFF 77 | CACHE INTERNAL "") 78 | set(TF_BUILD_PROFILER 79 | OFF 80 | CACHE INTERNAL "") 81 | set(TF_VERSION 82 | 3.7.0 83 | CACHE STRING "Taskflow version") 84 | set(TF_URL https://github.com/taskflow/taskflow/archive/refs/tags/v${TF_VERSION}.tar.gz) 85 | FetchContent_Declare(taskflow URL ${TF_URL} FIND_PACKAGE_ARGS) 86 | list(APPEND FETCH_PACKAGES taskflow) 87 | 88 | if(BUILD_MQT_DDSIM_CLI) 89 | set(THREADS_PREFER_PTHREAD_FLAG ON) 90 | find_package(Threads) 91 | link_libraries(Threads::Threads) 92 | 93 | set(CXXOPTS_VERSION 94 | 3.1.1 95 | CACHE STRING "cxxopts version") 96 | set(CXXOPTS_URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v${CXXOPTS_VERSION}.tar.gz) 97 | FetchContent_Declare(cxxopts URL ${CXXOPTS_URL} FIND_PACKAGE_ARGS ${CXXOPTS_VERSION}) 98 | list(APPEND FETCH_PACKAGES cxxopts) 99 | endif() 100 | 101 | if(BUILD_MQT_DDSIM_BINDINGS) 102 | # add pybind11_json library 103 | FetchContent_Declare( 104 | pybind11_json 105 | GIT_REPOSITORY https://github.com/pybind/pybind11_json 106 | FIND_PACKAGE_ARGS) 107 | list(APPEND FETCH_PACKAGES pybind11_json) 108 | endif() 109 | 110 | # Make all declared dependencies available. 111 | FetchContent_MakeAvailable(${FETCH_PACKAGES}) 112 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | # Source: https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#can-i-do-make-uninstall-with-cmake 10 | 11 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 12 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 13 | endif() 14 | 15 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 16 | string(REGEX REPLACE "\n" ";" files "${files}") 17 | foreach(file ${files}) 18 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 19 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 20 | exec_program( 21 | "@CMAKE_COMMAND@" ARGS 22 | "-E remove \"$ENV{DESTDIR}${file}\"" 23 | OUTPUT_VARIABLE rm_out 24 | RETURN_VALUE rm_retval) 25 | if(NOT "${rm_retval}" STREQUAL 0) 26 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 27 | endif() 28 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 29 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 30 | endif() 31 | endforeach() 32 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CHANGELOG.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/UPGRADING.md: -------------------------------------------------------------------------------- 1 | ```{include} ../UPGRADING.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /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 | .highlight .s { 40 | background-color: #00000001; 41 | } 42 | 43 | /* provide dark mode overrides for mystnb variables */ 44 | body:not([data-theme="light"]) { 45 | --mystnb-source-bg-color: #131416; 46 | --mystnb-stdout-bg-color: #1a1c1e; 47 | --mystnb-stderr-bg-color: #442222; 48 | --mystnb-traceback-bg-color: #202020; 49 | } 50 | 51 | body:not([data-theme="light"]) .highlight .gp { 52 | color: #c65d09; 53 | } 54 | -------------------------------------------------------------------------------- /docs/_static/mqt_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/munich-quantum-toolkit/ddsim/3121b5775d4c0f6c204f5926d1c235f757eae082/docs/_static/mqt_dark.png -------------------------------------------------------------------------------- /docs/_static/mqt_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/munich-quantum-toolkit/ddsim/3121b5775d4c0f6c204f5926d1c235f757eae082/docs/_static/mqt_light.png -------------------------------------------------------------------------------- /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 | 37 | <div style="margin-top: 0.5em"> 38 | <div class="only-light" align="center"> 39 | <img 40 | src="https://raw.githubusercontent.com/munich-quantum-toolkit/.github/refs/heads/main/docs/_static/mqt-funding-footer-light.svg" 41 | width="90%" 42 | alt="MQT Funding Footer" 43 | /> 44 | </div> 45 | <div class="only-dark" align="center"> 46 | <img 47 | src="https://raw.githubusercontent.com/munich-quantum-toolkit/.github/refs/heads/main/docs/_static/mqt-funding-footer-dark.svg" 48 | width="90%" 49 | alt="MQT Funding Footer" 50 | /> 51 | </div> 52 | </div> 53 | </div> 54 | <div class="bottom-of-page"> 55 | <div class="left-details"> 56 | {%- if show_copyright %} 57 | <div class="copyright"> 58 | {%- if hasdoc('copyright') %} {% trans path=pathto('copyright'), 59 | copyright=copyright|e -%} 60 | <a href="{{ path }}">Copyright</a> © {{ copyright }} {%- endtrans %} 61 | {%- else %} {% trans copyright=copyright|e -%} Copyright © {{ 62 | copyright }} {%- endtrans %} {%- endif %} 63 | </div> 64 | {%- endif %} {%- if last_updated -%} 65 | <div class="last-updated"> 66 | {% trans last_updated=last_updated|e -%} Last updated on {{ last_updated 67 | }} {%- endtrans -%} 68 | </div> 69 | {%- endif %} 70 | </div> 71 | <div class="right-details"> 72 | {% if theme_footer_icons or READTHEDOCS -%} 73 | <div class="icons"> 74 | {% if theme_footer_icons -%} {% for icon_dict in theme_footer_icons -%} 75 | <a 76 | class="muted-link {{ icon_dict.class }}" 77 | href="{{ icon_dict.url }}" 78 | aria-label="{{ icon_dict.name }}" 79 | > 80 | {{- icon_dict.html -}} 81 | </a> 82 | {% endfor %} {%- endif %} {%- endif %} 83 | </div> 84 | </div> 85 | {% endblock footer %} 86 | </div> 87 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../.github/contributing.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # MQT DDSIM - A quantum circuit simulator based on Decision Diagrams 2 | 3 | ```{raw} latex 4 | \begin{abstract} 5 | ``` 6 | 7 | MQT DDSIM is an open-source C++17 and Python library for classical quantum circuit simulation developed as part of the _{doc}`Munich Quantum Toolkit (MQT) <mqt:index>`_ [^1]. 8 | 9 | This documentation provides a comprehensive guide to the MQT DDSIM library, including {doc}`installation instructions <installation>`, a {doc}`quickstart guide <quickstart>`, and detailed {doc}`API documentation <api/mqt/ddsim/index>`. 10 | The source code of MQT DDSIM is publicly available on GitHub at [munich-quantum-toolkit/ddsim](https://github.com/munich-quantum-toolkit/ddsim), while pre-built binaries are available via [PyPI](https://pypi.org/project/mqt.ddsim/) for all major operating systems and all modern Python versions. 11 | MQT DDSIM is fully compatible with Qiskit 1.0 and above. 12 | 13 | [^1]: 14 | The _[Munich Quantum Toolkit (MQT)](https://mqt.readthedocs.io)_ is a collection of software tools for quantum computing developed by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/) as well as the [Munich Quantum Software Company (MQSC)](https://munichquantum.software). 15 | Among others, it is part of the [Munich Quantum Software Stack (MQSS)](https://www.munich-quantum-valley.de/research/research-areas/mqss) ecosystem, which is being developed as part of the [Munich Quantum Valley (MQV)](https://www.munich-quantum-valley.de) initiative. 16 | 17 | ````{only} latex 18 | ```{note} 19 | A live version of this document is available at [mqt.readthedocs.io/projects/ddsim](https://mqt.readthedocs.io/projects/ddsim). 20 | ``` 21 | ```` 22 | 23 | ```{raw} latex 24 | \end{abstract} 25 | 26 | \sphinxtableofcontents 27 | ``` 28 | 29 | ```{toctree} 30 | :hidden: 31 | 32 | self 33 | ``` 34 | 35 | ```{toctree} 36 | :maxdepth: 1 37 | :caption: User Guide 38 | 39 | installation 40 | quickstart 41 | simulators 42 | primitives 43 | references 44 | CHANGELOG 45 | UPGRADING 46 | ``` 47 | 48 | ````{only} not latex 49 | ```{toctree} 50 | :maxdepth: 2 51 | :titlesonly: 52 | :caption: Developers 53 | :glob: 54 | 55 | contributing 56 | support 57 | development_guide 58 | ``` 59 | ```` 60 | 61 | ```{toctree} 62 | :caption: Python API Reference 63 | :maxdepth: 1 64 | 65 | api/mqt/ddsim/index 66 | ``` 67 | 68 | ```{toctree} 69 | :glob: 70 | :caption: C++ API Reference 71 | :maxdepth: 1 72 | 73 | api/cpp/filelist 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Quickstart 15 | 16 | Assume you want to simulate the following quantum program: 17 | 18 | ```{code-cell} ipython3 19 | from qiskit.circuit import QuantumCircuit 20 | 21 | circ = QuantumCircuit(3) 22 | circ.h(0) 23 | circ.cx(0, 1) 24 | circ.cx(0, 2) 25 | 26 | circ.draw(output="mpl", style="iqp") 27 | ``` 28 | 29 | Then, using DDSIM to simulate the circuit is as easy as 30 | 31 | ```{code-cell} ipython3 32 | from mqt import ddsim 33 | 34 | provider = ddsim.DDSIMProvider() 35 | backend = provider.get_backend("qasm_simulator") 36 | 37 | job = backend.run(circ, shots=10000) 38 | counts = job.result().get_counts(circ) 39 | print(counts) 40 | ``` 41 | 42 | Check out the {doc}`reference documentation <api/mqt/ddsim/index>` for more information. 43 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | ```{raw} latex 2 | \begingroup 3 | \renewcommand\section[1]{\endgroup} 4 | \phantomsection 5 | ``` 6 | 7 | ```{only} html 8 | # References 9 | 10 | *DDSIM* is academic software. Thus, many of its built-in algorithms have been published as scientific papers. 11 | 12 | If you use *DDSIM* in your work, we would appreciate if you cited the respective papers. 13 | A full list of related papers is given below. 14 | ``` 15 | 16 | ```{bibliography} 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/refs.bib: -------------------------------------------------------------------------------- 1 | @article{10.1145/3530776, 2 | title = {Approximating Decision Diagrams for Quantum Circuit Simulation}, 3 | author = {Hillmich, Stefan and Zulehner, Alwin and Kueng, Richard and Markov, Igor L. and Wille, Robert}, 4 | year = {2022}, 5 | journal = tqc, 6 | volume = {3}, 7 | number = {4}, 8 | doi = {10.1145/3530776}, 9 | articleno = {22}, 10 | numpages = {21}, 11 | } 12 | 13 | @inproceedings{burgholzer2021efficient, 14 | title = {Efficient Construction of Functional Representations for Quantum Algorithms}, 15 | author = {L. Burgholzer and R. Raymond and I. Sengupta and R. Wille}, 16 | year = {2021}, 17 | booktitle = rc_conf, 18 | url = {https://www.cda.cit.tum.de/files/eda/2021_rc_efficient_construction_quantum_functionality.pdf}, 19 | } 20 | 21 | @article{burgholzer2022simulation, 22 | title = {Simulation Paths for Quantum Circuit Simulation with Decision Diagrams}, 23 | author = {Burgholzer, Lukas and Ploier, Alexander and Wille, Robert}, 24 | year = {2022}, 25 | journal = tcad, 26 | doi = {10.1109/TCAD.2022.3197969}, 27 | url = {https://www.cda.cit.tum.de/files/eda/2022_tcad_simulation_paths_for_decision_diagrams.pdf}, 28 | } 29 | 30 | @inproceedings{DBLP:conf/dac/HillmichMW20, 31 | title = {Just Like the Real Thing: {F}ast Weak Simulation of Quantum Computation}, 32 | author = {Stefan Hillmich and Igor L. Markov and Robert Wille}, 33 | year = {2020}, 34 | booktitle = dac, 35 | url = {https://www.cda.cit.tum.de/files/eda/2020_dac_weak_simulation_quantum_computation.pdf}, 36 | } 37 | 38 | @inproceedings{DBLP:conf/date/GrurlKFW21, 39 | title = {Stochastic Quantum Circuit Simulation Using Decision Diagrams}, 40 | author = {Thomas Grurl and Richard Kueng and J{\"{u}}rgen Fu{\ss} and Robert Wille}, 41 | year = {2021}, 42 | booktitle = date, 43 | pages = {194--199}, 44 | doi = {10.23919/DATE51398.2021.9474135}, 45 | url = {https://www.cda.cit.tum.de/files/eda/2021_stochastic_quantum_circuit_simulation_using_decision_diagrams.pdf}, 46 | } 47 | 48 | @inproceedings{DBLP:conf/date/HillmichKMW21, 49 | title = {As Accurate as Needed, as Efficient as Possible: {A}pproximations in {DD}-based Quantum Circuit Simulation}, 50 | author = {Stefan Hillmich and Richard Kueng and Igor L. Markov and Robert Wille}, 51 | year = {2021}, 52 | booktitle = date, 53 | url = {https://www.cda.cit.tum.de/files/eda/2021_date_approximations_dd_baed_quantum_circuit_simulation.pdf}, 54 | } 55 | 56 | @inproceedings{DBLP:conf/iccad/ZulehnerHW19, 57 | title = {How to Efficiently Handle Complex Values? Implementing Decision Diagrams for Quantum Computing}, 58 | author = {Alwin Zulehner and Stefan Hillmich and Robert Wille}, 59 | year = {2019}, 60 | booktitle = iccad, 61 | pages = {1--7}, 62 | doi = {10.1109/ICCAD45719.2019.8942057}, 63 | url = {https://www.cda.cit.tum.de/files/eda/2019_iccad_implementing_decision_diagrams_for_quantum_computing.pdf}, 64 | } 65 | 66 | @inproceedings{DBLP:conf/qce/BurgholzerBW21, 67 | title = {Hybrid {S}chr{\"{o}}dinger-{F}eynman Simulation of Quantum Circuits With Decision Diagrams}, 68 | author = {Lukas Burgholzer and Hartwig Bauer and Robert Wille}, 69 | year = {2021}, 70 | booktitle = qce, 71 | pages = {199--206}, 72 | doi = {10.1109/QCE52317.2021.00037}, 73 | url = {https://www.cda.cit.tum.de/files/eda/2021_qce_hybrid_schrodinger_feynman_simulation_with_decision_diagrams.pdf}, 74 | } 75 | 76 | @article{grurl2022, 77 | title = {Noise-aware Quantum Circuit Simulation With Decision Diagrams}, 78 | author = {T. Grurl and J. Fu{\ss} and R. Wille}, 79 | year = {2022}, 80 | journal = tcad, 81 | url = {https://www.cda.cit.tum.de/files/eda/2022_tcad_noise-aware_quantum_circuit_simulation_with_decision_diagrams.pdf}, 82 | } 83 | 84 | @inproceedings{wille2020JKQtools, 85 | title = {{JKQ}: {JKU} Tools for Quantum Computing}, 86 | author = {Wille, Robert and Hillmich, Stefan and Burgholzer, Lukas}, 87 | year = {2020}, 88 | booktitle = iccad, 89 | url = {https://www.cda.cit.tum.de/files/eda/2020_iccad_jku_tools_for_quantum_computing.pdf}, 90 | } 91 | 92 | @article{zulehner2019advanced, 93 | title = {Advanced Simulation of Quantum Computations}, 94 | author = {Zulehner, Alwin and Wille, Robert}, 95 | year = {2019}, 96 | journal = tcad, 97 | volume = {38}, 98 | number = {5}, 99 | pages = {848--859}, 100 | doi = {10.1109/TCAD.2018.2834427}, 101 | url = {https://www.cda.cit.tum.de/files/eda/2018_tcad_advanced_simulation_quantum_computation.pdf}, 102 | } 103 | -------------------------------------------------------------------------------- /docs/simulators.md: -------------------------------------------------------------------------------- 1 | # Simulators 2 | 3 | DDSIM offers different kinds of simulators to use. The following documentation gives an overview of the different simulator 4 | followed by brief demonstrations of the usage in Python and as standalone executable. 5 | 6 | All simulators immediately, or with steps in between, inherit from the abstract {cpp:class}`Simulator` class that provides 7 | basic functionality used by multiple actual simulators. 8 | 9 | ```{toctree} 10 | :maxdepth: 4 11 | 12 | simulators/CircuitSimulator 13 | simulators/NoiseAwareSimulator 14 | simulators/SimulationPathFramework 15 | simulators/HybridSchrodingerFeynman 16 | simulators/UnitarySimulator 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/simulators/HybridSchrodingerFeynman.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Hybrid Schrödinger-Feynman Simulator 15 | 16 | Hybrid Schrödinger-Feynman approaches strive to use all the available memory and processing units in order to efficiently simulate quantum circuits which would 17 | 18 | 1. run into memory bottlenecks using Schrödinger-style simulation, or 19 | 2. take exceedingly long using Feynman-style path summation, 20 | 21 | eventually trading-off the respective memory and runtime requirements. 22 | 23 | DDSIM offers such a simulator based on decision diagrams as proposed in {cite:p}`DBLP:conf/qce/BurgholzerBW21`. 24 | 25 | It currently assumes that no non-unitary operations (besides measurements at the end of the circuit) are present in the circuit. 26 | Furthermore it always measures all qubits at the end of the circuit in the order they were defined. 27 | 28 | The backend provides two different modes that can be set using the `mode` option: 29 | 30 | - `dd`: all computations are conducted on decision diagrams and the requested number of shots are sampled from the final decision diagram 31 | - `amplitude`: all individual paths in the hybrid simulation scheme are simulated using decision diagrams, while subsequent computations (addition of all results) is conducted using arrays. This requires more memory but can lead to significantly better runtime performance in many cases. The requested shots are sampled from the final statevector array. 32 | 33 | The number of threads to use can be set using the `nthreads` option. Note that the number of threads may be reduced when using the `amplitude` mode in order to fit the computation in the available memory. 34 | 35 | ```{code-cell} ipython3 36 | from qiskit import QuantumCircuit 37 | 38 | qc = QuantumCircuit(4) 39 | 40 | # create a simple graph state 41 | qc.h(range(4)) 42 | qc.cz(0, 2) 43 | qc.cz(1, 3) 44 | 45 | qc.draw(output="mpl", style="iqp") 46 | ``` 47 | 48 | ```{code-cell} ipython3 49 | from mqt.ddsim import DDSIMProvider 50 | 51 | provider = DDSIMProvider() 52 | 53 | # get the backend 54 | backend = provider.get_backend("hybrid_qasm_simulator") 55 | 56 | # submit the job 57 | job = backend.run(qc, shots=1000, mode="dd", nthreads=2) 58 | 59 | # get the result 60 | result = job.result() 61 | print(result.get_counts()) 62 | ``` 63 | 64 | Similar to the circuit simulator, there is also a statevector simulator available. The statevector simulator can be used to obtain the full statevector of the quantum circuit at the end of the simulation. 65 | 66 | Note that `shots` has to be set to `0` when using the `amplitude` mode as the statevector array is modified in-place for sampling and, hence, the state vector is no longer available afterwards. 67 | 68 | ```{code-cell} ipython3 69 | from mqt.ddsim import DDSIMProvider 70 | 71 | provider = DDSIMProvider() 72 | 73 | # get the backend 74 | backend = provider.get_backend("hybrid_statevector_simulator") 75 | 76 | # submit the job 77 | job = backend.run(qc, shots=0, mode="amplitude", nthreads=2) 78 | 79 | # get the result 80 | result = job.result() 81 | print(result.get_statevector()) 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/simulators/NoiseAwareSimulator.md: -------------------------------------------------------------------------------- 1 | # Noise-aware Simulator 2 | 3 | The tool also supports noise-aware quantum circuit simulation, based on a stochastic approach. It currently supports 4 | global decoherence and gate error noise effects. A detailed summary of the simulator is presented 5 | in {cite:p}`grurl2022`. Note that the simulator currently does not support simulating the integrated 6 | algorithms. 7 | 8 | ## Usage in Python 9 | 10 | :::{note} 11 | The noise-aware simulators are exposed to Python, but are currently lacking documentation. Any contributions are welcome! 12 | ::: 13 | 14 | ## Usage as Standalone Executable 15 | 16 | Building the simulator requires {code}`Threads::Threads`. It can be built by executing 17 | 18 | ```console 19 | $ cmake -DCMAKE_BUILD_TYPE=Release -S . -B build 20 | $ cmake --build build --config Release --target ddsim_noise_aware 21 | ``` 22 | 23 | The simulator provides a help function which is called in the following way: 24 | 25 | ```console 26 | $ ./build/apps/ddsim_noise_aware --help 27 | see for more information https://www.cda.cit.tum.de/ 28 | Usage: 29 | MQT DDSIM [OPTION...] 30 | 31 | -h, --help produce help message 32 | --seed arg seed for random number generator (default zero is possibly directly used as seed!)(default: 0) 33 | --pm print measurements 34 | --ps print simulation stats (applied gates, sim. time, and maximal size of the DD) 35 | --verbose Causes some simulators to print additional information to STDERR 36 | --simulate_file arg simulate a quantum circuit given by file (detection by the file extension) 37 | --step_fidelity arg target fidelity for each approximation run (>=1 = disable approximation) (default: 1.0) 38 | --steps arg number of approximation steps (default: 1) 39 | --noise_effects arg Noise effects (A (=amplitude damping),D (=depolarization),P (=phase flip)) in the form of a character string describing the noise effects (default: APD) 40 | --noise_prob arg Probability for applying noise. (default: 0.001) 41 | --noise_prob_t1 arg Probability for applying amplitude damping noise (default:2 x noise_prob) 42 | --noise_prob_multi arg Noise factor for multi qubit operations (default: 2) 43 | --use_density_matrix_simulator Set this flag to use the density matrix simulator. Per default the stochastic simulator is used 44 | --shots arg Specify the number of shots that shall be generated (default: 0) 45 | ``` 46 | 47 | An example run of the stochastic simulator, with 1000 samples, amplitude damping, phase flip, and depolarization error enabled (each with a probability of 0.1% whenever a gate is applied) looks like this: 48 | 49 | ```console 50 | $./build/apps/ddsim_noise_aware ./build/apps/ddsim_noise_aware --noise_effects APD --noise_prob 0.001 --shots 1000 --simulate_file adder_n4.qasm --pm --ps 51 | { 52 | "measurement_results": { 53 | "0000": 15, 54 | "0001": 36, 55 | "0010": 3, 56 | "0011": 3, 57 | "0100": 3, 58 | "0110": 4, 59 | "0111": 4, 60 | "1000": 9, 61 | "1001": 915, 62 | "1010": 2, 63 | "1011": 1, 64 | "1101": 4, 65 | "1111": 1 66 | }, 67 | "statistics": { 68 | "applied_gates": 27, 69 | "approximation_runs": "0.000000", 70 | "benchmark": "stoch_adder_n4", 71 | "max_matrix_nodes": 0, 72 | "max_nodes": 0, 73 | "mean_stoch_run_time": "0.000000", 74 | "n_qubits": 4, 75 | "parallel_instances": "8", 76 | "perfect_run_time": "0.000000", 77 | "seed": "0", 78 | "simulation_time": 0.2757418751716614, 79 | "step_fidelity": "1.000000", 80 | "stoch_runs": "1000", 81 | "stoch_wall_time": "0.275722", 82 | "threads": "8" 83 | } 84 | } 85 | ``` 86 | 87 | The deterministic simulator is run when the flag "--use_density_matrix_simulator" is set. The same run from above, using the deterministic simulator would look like this: 88 | 89 | ```console 90 | $ ./build/apps/ddsim_noise_aware --noise_effects APD --noise_prob 0.001 --shots 1000 --simulate_file adder_n4.qasm --pm --ps --use_density_matrix_simulator 91 | { 92 | "measurement_results": { 93 | "0000": 12, 94 | "0001": 40, 95 | "0010": 1, 96 | "0011": 5, 97 | "0100": 3, 98 | "0101": 1, 99 | "0110": 1, 100 | "0111": 7, 101 | "1000": 12, 102 | "1001": 912, 103 | "1010": 3, 104 | "1011": 1, 105 | "1101": 2 106 | }, 107 | "statistics": { 108 | "active_matrix_nodes": 0, 109 | "active_nodes": 22, 110 | "applied_gates": 27, 111 | "benchmark": "adder_n4", 112 | "max_matrix_nodes": 0, 113 | "n_qubits": 4, 114 | "seed": "0", 115 | "simulation_time": 0.0007002829806879163 116 | } 117 | } 118 | ``` 119 | -------------------------------------------------------------------------------- /docs/simulators/SimulationPathFramework.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Simulation Path Framework 15 | 16 | DDSIM also provides a framework for exploiting arbitrary simulation paths (using the [taskflow](https://github.com/taskflow/taskflow) library) based on the methods proposed in {cite:p}`burgholzer2022simulation`. 17 | 18 | ```{note} 19 | As of mqt-ddsim v2.0.0, the Cotengra-based workflow for translating circuits to tensor networks and deriving contraction strategies has been removed. If you want to check out the respective code, please checkout prior versions of mqt-ddsim. 20 | ``` 21 | 22 | A _simulation path_ describes the order in which the individual MxV or MxM multiplications are conducted during the simulation of a quantum circuit. 23 | It can be described as a list of tuples that identify the individual states/operations to be multiplied. 24 | The initial state is always assigned `ID 0`, while the i-th operation is assigned `ID i`. 25 | The result of a multiplication is assigned the next highest, unused ID, e.g., in a circuit with `|G|` gates the result of the first multiplication is assigned `ID |G|+1`. 26 | 27 | The framework comes with several pre-defined simulation path strategies: 28 | 29 | - `sequential` (_default_): simulate the circuit sequentially from left to right using only MxV multiplications 30 | - `pairwise_recursive`: recursively group pairs of states and operations to form a binary tree of MxV/MxM multiplications 31 | - `bracket`: group certain number of operations according to a given `bracket_size` 32 | - `alternating`: start the simulation in the middle of the circuit and alternate between applications of gates "from the left" and "from the right" (useful for equivalence checking). 33 | 34 | ## Simulating a simple circuit 35 | 36 | This example shall serve as a showcase on how to use the simulation path framework (via Python). 37 | First, create the circuit to be simulated using qiskit, e.g., in this case a three-qubit GHZ state: 38 | 39 | ```{code-cell} ipython3 40 | from qiskit import QuantumCircuit 41 | 42 | circ = QuantumCircuit(3) 43 | # the initial state corresponds to ID 0 44 | circ.h(0) # corresponds to ID 1 45 | circ.cx(0, 1) # corresponds to ID 2 46 | circ.cx(0, 2) # corresponds to ID 3 47 | 48 | circ.draw(output="mpl", style="iqp") 49 | ``` 50 | 51 | Then, obtain the simulation path framework qiskit backend. You can choose between the `path_sim_qasm_simulator` and the `path_sim_statevector_simulator`. 52 | The first just yields a dictionary with the counts of the measurements, while the latter also provides the complete statevector (which, depending on the amount of qubits, might not fit in the available memory). 53 | 54 | ```{code-cell} ipython3 55 | from mqt.ddsim import DDSIMProvider 56 | 57 | provider = DDSIMProvider() 58 | backend = provider.get_backend("path_sim_qasm_simulator") 59 | ``` 60 | 61 | Per default, the backend uses the `sequential` strategy. For this particular example, this means: 62 | 63 | - first, the Hadamard operation is applied to the initial state (`[0, 1] -> 4`) 64 | - then, the first CNOT is applied (`[4, 2] -> 5`) 65 | - finally, the last CNOT is applied (`[5, 3] -> 6`) 66 | 67 | The corresponding simulation path is thus described by `[[0, 1], [4, 2], [5, 3]]` and the final state is the one with `ID 6`. 68 | 69 | The simulation is started by calling the `run` method on the backend, which takes several optional configuration parameters (such as the simulation path strategy). 70 | For a complete list of configuration options see [here](#configuration). 71 | 72 | ```{code-cell} ipython3 73 | job = backend.run(circ) 74 | result = job.result() 75 | print(result.get_counts()) 76 | ``` 77 | 78 | ## Configuration 79 | 80 | The framework can be configured using multiple options (which can be passed to the `backend.run()` method): 81 | 82 | - `mode`: the simulation path mode to use (`sequential`, `pairwise_recursive`, `bracket`, `alternating`)) 83 | - `bracket_size`: the bracket size used for the `bracket` mode (default: `2`) 84 | - `alternating_start`: the id of the operation to start with in the `alternating` mode (default: `0`) 85 | - `seed`: the random seed used for the simulator (default `0`, i.e., no particular seed). 86 | -------------------------------------------------------------------------------- /docs/support.md: -------------------------------------------------------------------------------- 1 | ```{include} ../.github/support.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /include/CircuitSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "Simulator.hpp" 14 | #include "dd/DDDefinitions.hpp" 15 | #include "dd/DDpackageConfig.hpp" 16 | #include "ir/Definitions.hpp" 17 | #include "ir/QuantumComputation.hpp" 18 | #include "ir/operations/NonUnitaryOperation.hpp" 19 | #include "ir/operations/Operation.hpp" 20 | 21 | #include <cstddef> 22 | #include <cstdint> 23 | #include <istream> 24 | #include <map> 25 | #include <memory> 26 | #include <stdexcept> 27 | #include <string> 28 | #include <utility> 29 | 30 | struct ApproximationInfo { 31 | enum ApproximationStrategy : std::uint8_t { FidelityDriven, MemoryDriven }; 32 | 33 | /* Default to no approximation */ 34 | ApproximationInfo() = default; 35 | ApproximationInfo(const double stepFidelity_, const std::size_t stepNumber_, 36 | const ApproximationStrategy strategy_) 37 | : stepFidelity(stepFidelity_), stepNumber(stepNumber_), 38 | strategy(strategy_) {} 39 | 40 | static ApproximationStrategy fromString(const std::string& str) { 41 | if (str == "fidelity") { 42 | return FidelityDriven; 43 | } 44 | if (str == "memory") { 45 | return MemoryDriven; 46 | } 47 | 48 | throw std::runtime_error("Unknown approximation strategy '" + str + "'."); 49 | } 50 | 51 | friend std::istream& operator>>(std::istream& in, 52 | ApproximationStrategy& strategy_) { 53 | std::string token; 54 | in >> token; 55 | strategy_ = fromString(token); 56 | return in; 57 | } 58 | 59 | double stepFidelity = 1.; 60 | std::size_t stepNumber = 1; 61 | ApproximationStrategy strategy = FidelityDriven; 62 | }; 63 | 64 | class CircuitSimulator : public Simulator { 65 | public: 66 | explicit CircuitSimulator( 67 | std::unique_ptr<qc::QuantumComputation>&& qc_, 68 | const dd::DDPackageConfig& config = dd::DDPackageConfig()) 69 | : Simulator(config), qc(std::move(qc_)) { 70 | dd->resize(qc->getNqubits()); 71 | } 72 | 73 | CircuitSimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 74 | const std::uint64_t seed_, 75 | const dd::DDPackageConfig& config = dd::DDPackageConfig()) 76 | : Simulator(seed_, config), qc(std::move(qc_)) { 77 | dd->resize(qc->getNqubits()); 78 | } 79 | 80 | CircuitSimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 81 | const ApproximationInfo& approximationInfo_, 82 | const dd::DDPackageConfig& config = dd::DDPackageConfig()) 83 | : Simulator(config), qc(std::move(qc_)), 84 | approximationInfo(approximationInfo_) { 85 | dd->resize(qc->getNqubits()); 86 | } 87 | 88 | CircuitSimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 89 | const ApproximationInfo& approximationInfo_, 90 | const std::uint64_t seed_, 91 | const dd::DDPackageConfig& config = dd::DDPackageConfig()) 92 | : Simulator(seed_, config), qc(std::move(qc_)), 93 | approximationInfo(approximationInfo_) { 94 | dd->resize(qc->getNqubits()); 95 | } 96 | 97 | std::map<std::string, std::size_t> 98 | measureAllNonCollapsing(std::size_t shots) override { 99 | return Simulator::measureAllNonCollapsing(shots); 100 | } 101 | 102 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 103 | 104 | virtual dd::fp expectationValue(const qc::QuantumComputation& observable); 105 | 106 | std::map<std::string, std::string> additionalStatistics() override { 107 | return { 108 | {"step_fidelity", std::to_string(approximationInfo.stepFidelity)}, 109 | {"approximation_runs", std::to_string(approximationRuns)}, 110 | {"final_fidelity", std::to_string(finalFidelity)}, 111 | {"single_shots", std::to_string(singleShots)}, 112 | }; 113 | }; 114 | 115 | [[nodiscard]] std::size_t getNumberOfQubits() const override { 116 | return qc->getNqubits(); 117 | }; 118 | 119 | [[nodiscard]] std::size_t getNumberOfOps() const override { 120 | return qc->getNops(); 121 | }; 122 | 123 | [[nodiscard]] std::string getName() const override { return qc->getName(); }; 124 | 125 | protected: 126 | std::unique_ptr<qc::QuantumComputation> qc; 127 | std::size_t singleShots{0}; 128 | 129 | ApproximationInfo approximationInfo; 130 | std::size_t approximationRuns{0}; 131 | long double finalFidelity{1.0L}; 132 | 133 | struct CircuitAnalysis { 134 | bool isDynamic = false; 135 | bool hasMeasurements = false; 136 | std::map<qc::Qubit, size_t> measurementMap; 137 | }; 138 | 139 | CircuitAnalysis analyseCircuit(); 140 | 141 | virtual std::map<std::size_t, bool> singleShot(bool ignoreNonUnitaries); 142 | virtual void initializeSimulation(std::size_t nQubits); 143 | virtual char measure(dd::Qubit i); 144 | 145 | virtual void reset(qc::NonUnitaryOperation* nonUnitaryOp); 146 | virtual void applyOperationToState(std::unique_ptr<qc::Operation>& op); 147 | }; 148 | -------------------------------------------------------------------------------- /include/GroverSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "Simulator.hpp" 14 | #include "ir/Definitions.hpp" 15 | 16 | #include <cmath> 17 | #include <cstddef> 18 | #include <cstdint> 19 | #include <map> 20 | #include <random> 21 | #include <string> 22 | 23 | class GroverSimulator final : public Simulator { 24 | public: 25 | explicit GroverSimulator(const std::string& oracle_, 26 | const std::uint64_t seed_) 27 | : Simulator(seed_), oracle{oracle_.rbegin(), oracle_.rend()}, 28 | nQubits(static_cast<qc::Qubit>(oracle_.length())), 29 | iterations(calculateIterations(nQubits)) {} 30 | 31 | explicit GroverSimulator(const std::string& oracle_) 32 | : oracle{oracle_.rbegin(), oracle_.rend()}, 33 | nQubits(static_cast<qc::Qubit>(oracle_.length())), 34 | iterations(calculateIterations(nQubits)) {} 35 | 36 | explicit GroverSimulator(const qc::Qubit nQubits_, const std::uint64_t seed_) 37 | : Simulator(seed_), nQubits{nQubits_}, 38 | iterations(calculateIterations(nQubits_)) { 39 | // NOLINTNEXTLINE(misc-const-correctness): Using dist is not const 40 | std::uniform_int_distribution<int> dist(0, 1); // range is inclusive 41 | oracle = std::string(nQubits_, '0'); 42 | for (qc::Qubit i = 0; i < nQubits_; i++) { 43 | if (dist(Simulator::mt) == 1) { 44 | oracle[i] = '1'; 45 | } 46 | } 47 | } 48 | 49 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 50 | 51 | std::map<std::string, std::string> additionalStatistics() override { 52 | return { 53 | {"oracle", std::string(oracle.rbegin(), oracle.rend())}, 54 | {"iterations", std::to_string(iterations)}, 55 | }; 56 | } 57 | 58 | static std::uint64_t calculateIterations(const qc::Qubit nQubits) { 59 | // NOLINTNEXTLINE(readability-identifier-naming) 60 | constexpr long double PI_4 = 61 | 0.785398163397448309615660845819875721049292349843776455243L; // dd::PI_4 62 | // is of 63 | // type fp 64 | // and 65 | // hence 66 | // possibly 67 | // smaller 68 | // than 69 | // long 70 | // double 71 | if (nQubits <= 3) { 72 | return 1; 73 | } 74 | return static_cast<std::uint64_t>( 75 | std::floor(PI_4 * std::pow(2.L, nQubits / 2.0L))); 76 | } 77 | 78 | [[nodiscard]] std::size_t getNumberOfQubits() const override { 79 | return nQubits + 1; 80 | }; 81 | 82 | [[nodiscard]] std::size_t getNumberOfOps() const override { return 0; }; 83 | 84 | [[nodiscard]] std::string getName() const override { 85 | return "emulated_grover_" + std::to_string(nQubits); 86 | }; 87 | 88 | [[nodiscard]] std::string getOracle() const { 89 | return {oracle.rbegin(), oracle.rend()}; 90 | } 91 | 92 | protected: 93 | std::string oracle; // due to how qubits and std::string are indexed, this is 94 | // stored in *reversed* order 95 | qc::Qubit nQubits; 96 | qc::Qubit nAnciallae = 1; 97 | std::size_t iterations; 98 | }; 99 | -------------------------------------------------------------------------------- /include/HybridSchrodingerFeynmanSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "CircuitSimulator.hpp" 14 | #include "circuit_optimizer/CircuitOptimizer.hpp" 15 | #include "dd/DDDefinitions.hpp" 16 | #include "dd/Node.hpp" 17 | #include "dd/Package.hpp" 18 | #include "ir/Definitions.hpp" 19 | #include "ir/QuantumComputation.hpp" 20 | #include "ir/operations/Operation.hpp" 21 | 22 | #include <cstddef> 23 | #include <cstdint> 24 | #include <map> 25 | #include <memory> 26 | #include <stdexcept> 27 | #include <string> 28 | #include <utility> 29 | 30 | class HybridSchrodingerFeynmanSimulator final : public CircuitSimulator { 31 | public: 32 | enum class Mode : std::uint8_t { DD, Amplitude }; 33 | 34 | HybridSchrodingerFeynmanSimulator( 35 | std::unique_ptr<qc::QuantumComputation>&& qc_, 36 | const ApproximationInfo& approxInfo_, Mode mode_ = Mode::Amplitude, 37 | const std::size_t nthreads_ = 2) 38 | : CircuitSimulator(std::move(qc_), approxInfo_), mode(mode_), 39 | nthreads(nthreads_) { 40 | qc::CircuitOptimizer::flattenOperations(*qc); 41 | // remove final measurements 42 | qc::CircuitOptimizer::removeFinalMeasurements(*qc); 43 | } 44 | 45 | explicit HybridSchrodingerFeynmanSimulator( 46 | std::unique_ptr<qc::QuantumComputation>&& qc_, 47 | Mode mode_ = Mode::Amplitude, const std::size_t nthreads_ = 2) 48 | : HybridSchrodingerFeynmanSimulator(std::move(qc_), {}, mode_, 49 | nthreads_) {} 50 | 51 | HybridSchrodingerFeynmanSimulator( 52 | std::unique_ptr<qc::QuantumComputation>&& qc_, 53 | const ApproximationInfo& approxInfo_, const std::uint64_t seed_, 54 | Mode mode_ = Mode::Amplitude, const std::size_t nthreads_ = 2) 55 | : CircuitSimulator(std::move(qc_), approxInfo_, seed_), mode(mode_), 56 | nthreads(nthreads_) { 57 | // remove final measurements 58 | qc::CircuitOptimizer::flattenOperations(*qc); 59 | qc::CircuitOptimizer::removeFinalMeasurements(*qc); 60 | } 61 | 62 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 63 | 64 | Mode mode = Mode::Amplitude; 65 | 66 | [[nodiscard]] dd::CVec getVectorFromHybridSimulation() const { 67 | if (CircuitSimulator::getNumberOfQubits() >= 60) { 68 | // On 64bit system the vector can hold up to (2^60)-1 elements, if memory 69 | // permits 70 | throw std::range_error("getVector only supports less than 60 qubits."); 71 | } 72 | if (getMode() == Mode::Amplitude) { 73 | return finalAmplitudes; 74 | } 75 | return CircuitSimulator::rootEdge.getVector(); 76 | } 77 | 78 | // Get # of decisions for given split_qubit, so that lower slice: q0 < i < 79 | // qubit; upper slice: qubit <= i < nqubits 80 | std::size_t getNDecisions(qc::Qubit splitQubit); 81 | 82 | [[nodiscard]] Mode getMode() const { return mode; } 83 | 84 | private: 85 | std::size_t nthreads = 2; 86 | dd::CVec finalAmplitudes; 87 | 88 | void simulateHybridTaskflow(qc::Qubit splitQubit); 89 | void simulateHybridAmplitudes(qc::Qubit splitQubit); 90 | 91 | dd::VectorDD simulateSlicing(std::unique_ptr<dd::Package>& sliceDD, 92 | qc::Qubit splitQubit, std::size_t controls); 93 | 94 | class Slice { 95 | protected: 96 | qc::Qubit nextControlIdx = 0; 97 | 98 | std::size_t getNextControl() { 99 | std::size_t idx = 1UL << nextControlIdx; 100 | nextControlIdx++; 101 | return controls & idx; 102 | } 103 | 104 | public: 105 | qc::Qubit start; 106 | qc::Qubit end; 107 | std::size_t controls; 108 | qc::Qubit nqubits; 109 | std::size_t nDecisionsExecuted = 0; 110 | dd::VectorDD edge{}; 111 | 112 | explicit Slice(std::unique_ptr<dd::Package>& dd, const qc::Qubit start_, 113 | const qc::Qubit end_, const std::size_t controls_) 114 | : start(start_), end(end_), controls(controls_), 115 | nqubits(end - start + 1), 116 | edge(dd->makeZeroState(static_cast<dd::Qubit>(nqubits), start_)) { 117 | dd->incRef(edge); 118 | } 119 | 120 | explicit Slice(std::unique_ptr<dd::Package>& dd, dd::VectorDD edge_, 121 | const qc::Qubit start_, const qc::Qubit end_, 122 | const std::size_t controls_) 123 | : start(start_), end(end_), controls(controls_), 124 | nqubits(end - start + 1), edge(edge_) { 125 | dd->incRef(edge); 126 | } 127 | 128 | // returns true if this operation was a split operation 129 | bool apply(std::unique_ptr<dd::Package>& sliceDD, 130 | const std::unique_ptr<qc::Operation>& op); 131 | }; 132 | }; 133 | -------------------------------------------------------------------------------- /include/ShorFastSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "Simulator.hpp" 14 | #include "dd/DDDefinitions.hpp" 15 | #include "dd/Node.hpp" 16 | 17 | #include <cmath> 18 | #include <cstddef> 19 | #include <cstdint> 20 | #include <map> 21 | #include <string> 22 | #include <utility> 23 | #include <vector> 24 | 25 | class ShorFastSimulator final : public Simulator { 26 | static std::uint64_t modpow(std::uint64_t base, std::uint64_t exp, 27 | std::uint64_t modulus) { 28 | base %= modulus; 29 | std::uint64_t result = 1ULL; 30 | while (exp > 0) { 31 | if ((exp & 1ULL) > 0) { 32 | result = (result * base) % modulus; 33 | } 34 | base = (base * base) % modulus; 35 | exp >>= 1ULL; 36 | } 37 | return result; 38 | } 39 | 40 | static std::uint64_t gcd(std::uint64_t a, std::uint64_t b) { 41 | while (a != 0) { 42 | const std::uint64_t c = a; 43 | a = b % a; 44 | b = c; 45 | } 46 | return b; 47 | } 48 | 49 | static double cosine(double fac, double div) { 50 | return std::cos((dd::PI * fac) / div); 51 | } 52 | 53 | static double sine(double fac, double div) { 54 | return std::sin((dd::PI * fac) / div); 55 | } 56 | 57 | void uAEmulate2(std::uint64_t a); 58 | 59 | void uAEmulate2Rec(dd::vEdge e); 60 | 61 | [[nodiscard]] std::pair<std::uint32_t, std::uint32_t> 62 | postProcessing(const std::string& sample) const; 63 | 64 | void applyGate(dd::GateMatrix matrix, dd::Qubit target); 65 | 66 | std::vector<std::map<dd::vNode*, dd::vCachedEdge>> nodesOnLevel; 67 | 68 | dd::mEdge addConst(std::uint64_t a); 69 | 70 | dd::mEdge addConstMod(std::uint64_t a); 71 | 72 | dd::mEdge limitTo(std::uint64_t a); 73 | 74 | /// composite number to be factored 75 | std::uint32_t compositeN; 76 | /// coprime number to `compositeN`. Setting this to zero will randomly 77 | /// generate a suitable number 78 | std::uint32_t coprimeA; 79 | std::size_t requiredBits; 80 | std::size_t nQubits; 81 | 82 | std::string simResult = "did not start"; 83 | std::pair<std::uint32_t, std::uint32_t> simFactors{0, 0}; 84 | 85 | std::size_t numberOfOperations{}; 86 | 87 | bool verbose; 88 | 89 | std::map<dd::vNode*, dd::vEdge> dagEdges; 90 | 91 | public: 92 | ShorFastSimulator(const std::uint32_t compositeNumber, 93 | const std::uint32_t coprimeA_, const bool verbose_ = false) 94 | : compositeN(compositeNumber), coprimeA(coprimeA_), 95 | requiredBits( 96 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 97 | nQubits(static_cast<std::size_t>(std::ceil(std::log2(compositeN))) + 1), 98 | verbose(verbose_) { 99 | nodesOnLevel.resize(nQubits); 100 | }; 101 | 102 | ShorFastSimulator(const std::uint32_t compositeNumber, 103 | const std::uint32_t coprimeA_, const std::uint64_t seed_, 104 | const bool verbose_ = false) 105 | : Simulator(seed_), compositeN(compositeNumber), coprimeA(coprimeA_), 106 | requiredBits( 107 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 108 | nQubits(static_cast<std::size_t>(std::ceil(std::log2(compositeN))) + 1), 109 | verbose(verbose_) { 110 | nodesOnLevel.resize(nQubits); 111 | }; 112 | 113 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 114 | 115 | [[nodiscard]] std::string getName() const override { 116 | return "fast_shor_" + std::to_string(compositeN) + "_" + 117 | std::to_string(coprimeA); 118 | } 119 | 120 | [[nodiscard]] std::size_t getNumberOfQubits() const override { 121 | return nQubits; 122 | } 123 | 124 | [[nodiscard]] std::size_t getNumberOfOps() const override { 125 | return numberOfOperations; 126 | } 127 | 128 | std::pair<std::uint32_t, std::uint32_t> getFactors() { return simFactors; } 129 | 130 | std::map<std::string, std::string> additionalStatistics() override { 131 | return {{"composite_number", std::to_string(compositeN)}, 132 | {"coprime_a", std::to_string(coprimeA)}, 133 | {"sim_result", simResult}, 134 | {"sim_factor1", std::to_string(simFactors.first)}, 135 | {"sim_factor2", std::to_string(simFactors.second)}}; 136 | } 137 | }; 138 | -------------------------------------------------------------------------------- /include/ShorSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "Simulator.hpp" 14 | #include "dd/DDDefinitions.hpp" 15 | #include "dd/Node.hpp" 16 | 17 | #include <cmath> 18 | #include <cstddef> 19 | #include <cstdint> 20 | #include <map> 21 | #include <string> 22 | #include <utility> 23 | #include <vector> 24 | 25 | class ShorSimulator final : public Simulator { 26 | static std::uint64_t modpow(std::uint64_t base, std::uint64_t exp, 27 | std::uint64_t modulus) { 28 | base %= modulus; 29 | std::uint64_t result = 1ULL; 30 | while (exp > 0) { 31 | if ((exp & 1ULL) > 0) { 32 | result = (result * base) % modulus; 33 | } 34 | base = (base * base) % modulus; 35 | exp >>= 1ULL; 36 | } 37 | return result; 38 | } 39 | 40 | static std::uint64_t gcd(std::uint64_t a, std::uint64_t b) { 41 | while (a != 0) { 42 | const std::uint64_t c = a; 43 | a = b % a; 44 | b = c; 45 | } 46 | return b; 47 | } 48 | 49 | static double cosine(double fac, double div) { 50 | return std::cos((dd::PI * fac) / div); 51 | } 52 | 53 | static double sine(double fac, double div) { 54 | return std::sin((dd::PI * fac) / div); 55 | } 56 | 57 | void uAEmulate(std::uint64_t a, std::int32_t q); 58 | 59 | std::vector<std::uint64_t> ts; 60 | 61 | dd::mEdge addConst(std::uint64_t a); 62 | 63 | dd::mEdge addConstMod(std::uint64_t a); 64 | 65 | dd::mEdge limitTo(std::uint64_t a); 66 | 67 | [[nodiscard]] std::pair<std::uint32_t, std::uint32_t> 68 | postProcessing(const std::string& sample) const; 69 | 70 | /// composite number to be factored 71 | std::size_t compositeN; 72 | /// coprime number to `coprimeN`. Setting this to zero will randomly generate 73 | /// a suitable number 74 | std::size_t coprimeA; 75 | std::size_t requiredBits; 76 | std::size_t nQubits{}; 77 | 78 | std::string simResult = "did not start"; 79 | std::pair<std::uint32_t, std::uint32_t> simFactors{0, 0}; 80 | 81 | std::string polrResult = "did not start"; 82 | std::pair<std::uint32_t, std::uint32_t> polrFactors{0, 0}; 83 | 84 | bool verbose; 85 | bool approximate; 86 | std::uint64_t approximationRuns{0}; 87 | long double finalFidelity{1.0L}; 88 | double stepFidelity{0.9}; 89 | 90 | std::map<dd::vNode*, dd::mEdge> dagEdges; 91 | 92 | public: 93 | ShorSimulator(const std::size_t compositeNumber, 94 | const std::size_t coprimeNumber) 95 | : compositeN(compositeNumber), coprimeA(coprimeNumber), 96 | requiredBits( 97 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 98 | verbose(false), approximate(false) { 99 | ts.resize(nQubits); 100 | }; 101 | 102 | ShorSimulator(const std::size_t compositeNumber, 103 | const std::size_t coprimeNumber, const std::uint64_t seed_) 104 | : Simulator(seed_), compositeN(compositeNumber), coprimeA(coprimeNumber), 105 | requiredBits( 106 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 107 | verbose(false), approximate(false) { 108 | ts.resize(nQubits); 109 | }; 110 | 111 | ShorSimulator(const std::size_t compositeNumber, 112 | const std::size_t coprimeNumber, const bool verbose_, 113 | const bool approximate_) 114 | : compositeN(compositeNumber), coprimeA(coprimeNumber), 115 | requiredBits( 116 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 117 | verbose(verbose_), approximate(approximate_) { 118 | ts.resize(nQubits); 119 | }; 120 | 121 | ShorSimulator(const std::size_t compositeNumber, 122 | const std::size_t coprimeNumber, const std::uint64_t seed_, 123 | const bool verbose_, const bool approximate_) 124 | : Simulator(seed_), compositeN(compositeNumber), coprimeA(coprimeNumber), 125 | requiredBits( 126 | static_cast<std::size_t>(std::ceil(std::log2(compositeNumber)))), 127 | verbose(verbose_), approximate(approximate_) { 128 | ts.resize(nQubits); 129 | }; 130 | 131 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 132 | 133 | [[nodiscard]] std::string getName() const override { 134 | return "shor_" + std::to_string(compositeN) + "_" + 135 | std::to_string(coprimeA); 136 | } 137 | 138 | [[nodiscard]] std::size_t getNumberOfQubits() const override { 139 | return nQubits; 140 | } 141 | 142 | [[nodiscard]] std::size_t getNumberOfOps() const override { return 0; } 143 | 144 | std::pair<std::uint32_t, std::uint32_t> getFactors() { return simFactors; } 145 | 146 | std::map<std::string, std::string> additionalStatistics() override { 147 | return { 148 | {"composite_number", std::to_string(compositeN)}, 149 | {"coprime_a", std::to_string(coprimeA)}, 150 | {"emulation", "true"}, 151 | {"sim_result", simResult}, 152 | {"sim_factor1", std::to_string(simFactors.first)}, 153 | {"sim_factor2", std::to_string(simFactors.second)}, 154 | {"polr_result", polrResult}, 155 | {"polr_factor1", std::to_string(polrFactors.first)}, 156 | {"polr_factor2", std::to_string(polrFactors.second)}, 157 | {"approximation_runs", std::to_string(approximationRuns)}, 158 | {"step_fidelity", std::to_string(stepFidelity)}, 159 | {"final_fidelity", std::to_string(finalFidelity)}, 160 | }; 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /include/StochasticNoiseSimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "CircuitSimulator.hpp" 14 | #include "dd/DDpackageConfig.hpp" 15 | #include "dd/NoiseFunctionality.hpp" 16 | #include "ir/Definitions.hpp" 17 | #include "ir/QuantumComputation.hpp" 18 | 19 | #include <cstddef> 20 | #include <cstdint> 21 | #include <map> 22 | #include <memory> 23 | #include <optional> 24 | #include <string> 25 | #include <thread> 26 | #include <utility> 27 | #include <vector> 28 | 29 | class StochasticNoiseSimulator final : public CircuitSimulator { 30 | public: 31 | StochasticNoiseSimulator( 32 | std::unique_ptr<qc::QuantumComputation>&& qc_, 33 | const ApproximationInfo& approximationInfo_, 34 | std::string noiseEffects_ = "APD", double noiseProbability_ = 0.001, 35 | std::optional<double> ampDampingProbability_ = std::nullopt, 36 | double multiQubitGateFactor_ = 2) 37 | : CircuitSimulator(std::move(qc_), approximationInfo_, 38 | dd::STOCHASTIC_NOISE_SIMULATOR_DD_PACKAGE_CONFIG), 39 | noiseProbability(noiseProbability_), 40 | amplitudeDampingProb((ampDampingProbability_) 41 | ? ampDampingProbability_.value() 42 | : noiseProbability_ * 2), 43 | multiQubitGateFactor(multiQubitGateFactor_), 44 | maxInstances(std::thread::hardware_concurrency() > 4 45 | ? std::thread::hardware_concurrency() - 4 46 | : 1), 47 | noiseEffects(std::move(noiseEffects_)) { 48 | dd::sanityCheckOfNoiseProbabilities(noiseProbability, amplitudeDampingProb, 49 | multiQubitGateFactor); 50 | } 51 | 52 | explicit StochasticNoiseSimulator( 53 | std::unique_ptr<qc::QuantumComputation>&& qc_, 54 | std::string noiseEffects_ = "APD", double noiseProbability_ = 0.001, 55 | std::optional<double> ampDampingProbability_ = std::nullopt, 56 | double multiQubitGateFactor_ = 2) 57 | : StochasticNoiseSimulator(std::move(qc_), {}, std::move(noiseEffects_), 58 | noiseProbability_, ampDampingProbability_, 59 | multiQubitGateFactor_) {} 60 | 61 | StochasticNoiseSimulator( 62 | std::unique_ptr<qc::QuantumComputation>&& qc_, 63 | const ApproximationInfo& approximationInfo_, const std::size_t seed_, 64 | std::string noiseEffects_ = "APD", double noiseProbability_ = 0.001, 65 | std::optional<double> ampDampingProbability_ = std::nullopt, 66 | double multiQubitGateFactor_ = 2) 67 | : CircuitSimulator(std::move(qc_), approximationInfo_, seed_, 68 | dd::STOCHASTIC_NOISE_SIMULATOR_DD_PACKAGE_CONFIG), 69 | noiseProbability(noiseProbability_), 70 | amplitudeDampingProb((ampDampingProbability_) 71 | ? ampDampingProbability_.value() 72 | : noiseProbability_ * 2), 73 | multiQubitGateFactor(multiQubitGateFactor_), 74 | maxInstances(std::thread::hardware_concurrency() > 4 75 | ? std::thread::hardware_concurrency() - 4 76 | : 1), 77 | noiseEffects(std::move(noiseEffects_)) { 78 | dd::sanityCheckOfNoiseProbabilities(noiseProbability, amplitudeDampingProb, 79 | multiQubitGateFactor); 80 | } 81 | 82 | std::vector<std::map<std::string, size_t>> classicalMeasurementsMaps; 83 | std::map<std::string, size_t> finalClassicalMeasurementsMap; 84 | 85 | std::map<std::string, std::size_t> simulate(std::size_t shots) override; 86 | 87 | [[nodiscard]] std::size_t getMaxMatrixNodeCount() const override { 88 | return 0U; 89 | } // Not available for stochastic simulation 90 | [[nodiscard]] std::size_t getMatrixActiveNodeCount() const override { 91 | return 0U; 92 | } // Not available for stochastic simulation 93 | [[nodiscard]] std::size_t countNodesFromRoot() override { 94 | return 0U; 95 | } // Not available for stochastic simulation 96 | 97 | std::map<std::string, std::string> additionalStatistics() override; 98 | 99 | private: 100 | double noiseProbability{}; 101 | double amplitudeDampingProb{}; 102 | double multiQubitGateFactor{}; 103 | std::size_t stochasticRuns{}; 104 | std::size_t maxInstances{}; 105 | 106 | std::string noiseEffects; 107 | 108 | double stochRunTime{}; 109 | 110 | void runStochSimulationForId( 111 | std::size_t stochRun, qc::Qubit nQubits, 112 | std::map<std::string, size_t>& classicalMeasurementsMap, 113 | std::uint64_t localSeed); 114 | }; 115 | -------------------------------------------------------------------------------- /include/UnitarySimulator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "CircuitSimulator.hpp" 14 | #include "dd/Node.hpp" 15 | #include "ir/QuantumComputation.hpp" 16 | 17 | #include <cstddef> 18 | #include <cstdint> 19 | #include <memory> 20 | 21 | class UnitarySimulator final : public CircuitSimulator { 22 | public: 23 | enum class Mode : std::uint8_t { Sequential, Recursive }; 24 | 25 | UnitarySimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 26 | const ApproximationInfo& approximationInfo_, 27 | Mode simMode = Mode::Recursive); 28 | 29 | explicit UnitarySimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 30 | Mode simMode = Mode::Recursive); 31 | 32 | UnitarySimulator(std::unique_ptr<qc::QuantumComputation>&& qc_, 33 | const ApproximationInfo& approximationInfo_, 34 | std::uint64_t seed_, Mode simMode = Mode::Recursive); 35 | 36 | void construct(); 37 | 38 | [[nodiscard]] Mode getMode() const { return mode; } 39 | [[nodiscard]] dd::MatrixDD getConstructedDD() const { return e; } 40 | [[nodiscard]] double getConstructionTime() const { return constructionTime; } 41 | [[nodiscard]] std::size_t getFinalNodeCount() const { return e.size(); } 42 | [[nodiscard]] std::size_t getMaxNodeCount() const override; 43 | 44 | private: 45 | dd::MatrixDD e{}; 46 | 47 | Mode mode = Mode::Recursive; 48 | 49 | double constructionTime = 0.; 50 | }; 51 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts for MQT DDSIM 2 | 3 | The scripts in this folder should help to replicate the results we published in papers. 4 | However, please note that these scripts assume a certain behavior of the simulator that may (and in fact very likely) change in the future. 5 | 6 | A good idea to get started is to check out the last commit where a particular script has been changed and run it from there. 7 | -------------------------------------------------------------------------------- /scripts/hybrid_simulation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | # Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | # All rights reserved. 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Licensed under the MIT License 9 | 10 | BUILD_DIR='/tmp/cmake-build-hybrid-sim' 11 | BENCH_DIR="hybrid-sim-$(date "+%F-%H-%M-%S")" 12 | TIMEOUT='86400' 13 | 14 | benchmark() { 15 | local OUTFILE="$1" 16 | shift 17 | printf "Simulation category %s 'ddsim_simple %s' (%s) ... " "${OUTFILE}" "$*" "$(date "+%F %T")" 1>&2 18 | { 19 | timeout --foreground ${TIMEOUT} "${BUILD_DIR}/ddsim_simple" "$@" 20 | ret=$? 21 | if [ $ret -ne 0 ]; then 22 | printf "TIMEOUT (%s) %s\n" "$ret" "$(date "+%F-%H-%M-%S")" 23 | printf "TIMEOUT (%s) %s\n" "$ret" "$(date "+%F-%H-%M-%S")" 1>&2; 24 | else 25 | printf "done (%s)\n" "$(date "+%F %T")" 1>&2 26 | fi 27 | } >> "${BENCH_DIR}/${OUTFILE}.log" 28 | 29 | } 30 | 31 | export BUILD_DIR 32 | export BENCH_DIR 33 | export TIMEOUT 34 | export -f benchmark 35 | 36 | mkdir -p "${BENCH_DIR}" 37 | cmake -DCMAKE_BUILD_TYPE=Release -S ~/Code/ddsim/ -B "${BUILD_DIR}" -DBINDINGS=ON || exit 1 38 | cmake --build "${BUILD_DIR}" --config Release --target ddsim_simple || exit 1 39 | 40 | START_TIME="$(date "+%F %T")" 41 | printf "Build to %s\n" "${BUILD_DIR}" 42 | printf "Start running all simulations (%s) timeout set to %s\n" "${START_TIME}" "${TIMEOUT}" 43 | 44 | for file in "${BUILD_DIR}"/circuits/inst_4x4*.txt; do 45 | base="$(basename -s .txt "$file")" 46 | sem -j 4 benchmark hybrid_sim_base_"${base}" --ps --simulate_file "${file}" 47 | sem -j 1 benchmark hybrid_sim_dd_"${base}" --ps --nthreads 32 --hybrid_mode dd --simulate_file_hybrid "${file}" 48 | sem -j 1 benchmark hybrid_sim_amp_"${base}" --ps --nthreads 32 --hybrid_mode amplitude --simulate_file_hybrid "${file}" 49 | done 50 | 51 | for file in "${BUILD_DIR}"/circuits/inst_4x5*.txt; do 52 | base="$(basename -s .txt "$file")" 53 | sem -j 4 benchmark hybrid_sim_base_"${base}" --ps --simulate_file "${file}" 54 | sem -j 1 benchmark hybrid_sim_dd_"${base}" --ps --nthreads 32 --hybrid_mode dd --simulate_file_hybrid "${file}" 55 | sem -j 1 benchmark hybrid_sim_amp_"${base}" --ps --nthreads 32 --hybrid_mode amplitude --simulate_file_hybrid "${file}" 56 | done 57 | 58 | for file in "${BUILD_DIR}"/circuits/inst_5x5*.txt; do 59 | base="$(basename -s .txt "$file")" 60 | # sem -j 2 benchmark hybrid_sim_base_"${base}" --ps --simulate_file "${file}" 61 | # sem -j 1 benchmark hybrid_sim_dd_"${base}" --ps --nthreads 32 --hybrid_mode dd --simulate_file_hybrid "${file}" 62 | sem -j 1 benchmark hybrid_sim_amp_"${base}" --ps --nthreads 32 --hybrid_mode amplitude --simulate_file_hybrid "${file}" 63 | done 64 | 65 | for file in "${BUILD_DIR}"/circuits/inst_5x6*.txt; do 66 | base="$(basename -s .txt "$file")" 67 | # sem -j 2 benchmark hybrid_sim_base_"${base}" --ps --simulate_file "${file}" 68 | # sem -j 1 benchmark hybrid_sim_dd_"${base}" --ps --nthreads 32 --hybrid_mode dd --simulate_file_hybrid "${file}" 69 | sem -j 1 benchmark hybrid_sim_amp_"${base}" --ps --nthreads 8 --hybrid_mode amplitude --simulate_file_hybrid "${file}" 70 | done 71 | 72 | sem --wait 73 | 74 | printf "Finished running all simulations (%s)\n" "$(date "+%F %T")" 75 | printf "(Started running all simulations (%s))\n" "${START_TIME}" 76 | -------------------------------------------------------------------------------- /scripts/primebases_timing_shor.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | # Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | # All rights reserved. 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Licensed under the MIT License 9 | 10 | BUILD_DIR='/tmp/cmake-build-ddsim-primebases' 11 | 12 | echo "(Re)Building in ${BUILD_DIR} as necessary" 13 | cmake -DCMAKE_BUILD_TYPE=Release -S .. -B "${BUILD_DIR}" > /dev/null || exit 1 14 | cmake --build "${BUILD_DIR}" --config Release --target ddsim_primebases ddsim_simple > /dev/null || exit 1 15 | 16 | 17 | echo "Start time: $(date +"%Y-%m-%d %H:%M:%S")" 18 | for base in $("${BUILD_DIR}/apps/ddsim_primebases" -N 749 -S primes -L 25); do 19 | echo "Base $base" 20 | "${BUILD_DIR}/apps/ddsim_simple" --simulate_shor 749 --simulate_shor_coprime "$base" --benchmark --verbose 21 | done 22 | echo "End time: $(date +"%Y-%m-%d %H:%M:%S")" 23 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | set(MQT_DDSIM_TARGET_NAME mqt-ddsim) 10 | 11 | if(NOT TARGET MQT::DDSim) 12 | # collect headers and source files 13 | file(GLOB_RECURSE MQT_DDSIM_HEADERS ${MQT_DDSIM_INCLUDE_BUILD_DIR}/*.hpp) 14 | file(GLOB_RECURSE MQT_DDSIM_SOURCES **.cpp) 15 | list(FILTER MQT_DDSIM_SOURCES EXCLUDE REGEX ".*/python/.*$") 16 | 17 | # add DDSim Package library 18 | add_library(${MQT_DDSIM_TARGET_NAME} ${MQT_DDSIM_HEADERS} ${MQT_DDSIM_SOURCES}) 19 | 20 | # set include directories 21 | target_include_directories(${MQT_DDSIM_TARGET_NAME} 22 | PUBLIC $<BUILD_INTERFACE:${MQT_DDSIM_INCLUDE_BUILD_DIR}>) 23 | 24 | # link to the MQT::Core and Taskflow libraries 25 | target_link_libraries( 26 | ${MQT_DDSIM_TARGET_NAME} 27 | PUBLIC MQT::CoreDD MQT::CoreCircuitOptimizer Taskflow 28 | PRIVATE MQT::ProjectWarnings MQT::ProjectOptions) 29 | 30 | # the following sets the SYSTEM flag for the include dirs of the taskflow libs 31 | set_target_properties( 32 | Taskflow PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES 33 | $<TARGET_PROPERTY:Taskflow,INTERFACE_INCLUDE_DIRECTORIES>) 34 | 35 | # add MQT alias 36 | add_library(MQT::DDSim ALIAS ${MQT_DDSIM_TARGET_NAME}) 37 | endif() 38 | 39 | if(BUILD_MQT_DDSIM_BINDINGS) 40 | add_subdirectory(python) 41 | endif() 42 | -------------------------------------------------------------------------------- /src/DeterministicNoiseSimulator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "DeterministicNoiseSimulator.hpp" 12 | 13 | #include "Simulator.hpp" 14 | #include "dd/ComplexNumbers.hpp" 15 | #include "dd/DDDefinitions.hpp" 16 | #include "dd/Operations.hpp" 17 | #include "ir/operations/NonUnitaryOperation.hpp" 18 | #include "ir/operations/OpType.hpp" 19 | #include "ir/operations/Operation.hpp" 20 | 21 | #include <cstddef> 22 | #include <cstdint> 23 | #include <iterator> 24 | #include <map> 25 | #include <memory> 26 | #include <random> 27 | #include <string> 28 | #include <vector> 29 | 30 | using CN = dd::ComplexNumbers; 31 | 32 | void DeterministicNoiseSimulator::initializeSimulation( 33 | const std::size_t nQubits) { 34 | rootEdge = dd->makeZeroDensityOperator(static_cast<dd::Qubit>(nQubits)); 35 | } 36 | 37 | void DeterministicNoiseSimulator::applyOperationToState( 38 | std::unique_ptr<qc::Operation>& op) { 39 | auto operation = dd::getDD(*op, *Simulator::dd); 40 | dd->applyOperationToDensity(DeterministicNoiseSimulator::rootEdge, operation); 41 | deterministicNoiseFunctionality.applyNoiseEffects( 42 | DeterministicNoiseSimulator::rootEdge, op); 43 | } 44 | 45 | char DeterministicNoiseSimulator::measure(const dd::Qubit i) { 46 | return Simulator::dd->measureOneCollapsing( 47 | rootEdge, static_cast<dd::Qubit>(i), Simulator::mt); 48 | } 49 | 50 | void DeterministicNoiseSimulator::reset(qc::NonUnitaryOperation* nonUnitaryOp) { 51 | for (const auto& qubit : nonUnitaryOp->getTargets()) { 52 | auto const result = 53 | dd->measureOneCollapsing(rootEdge, static_cast<dd::Qubit>(qubit), mt); 54 | if (result == '1') { 55 | const auto x = qc::StandardOperation(qubit, qc::X); 56 | const auto operation = dd::getDD(x, *dd); 57 | rootEdge = dd->applyOperationToDensity(rootEdge, operation); 58 | } 59 | } 60 | } 61 | 62 | std::map<std::string, std::size_t> 63 | DeterministicNoiseSimulator::sampleFromProbabilityMap( 64 | const dd::SparsePVecStrKeys& resultProbabilityMap, 65 | const std::size_t shots) { 66 | std::vector<dd::fp> weights; 67 | weights.reserve(resultProbabilityMap.size()); 68 | for (const auto& [state, prob] : resultProbabilityMap) { 69 | weights.emplace_back(prob); 70 | } 71 | std::discrete_distribution<std::size_t> d( 72 | weights.begin(), 73 | weights.end()); // NOLINT(misc-const-correctness) false-positive 74 | 75 | // Create the final map containing the measurement results and the 76 | // corresponding shots 77 | std::map<std::string, std::size_t> results; 78 | for (size_t n = 0; n < shots; ++n) { 79 | const auto sampleIdx = d(mt); 80 | const auto state = (std::next(resultProbabilityMap.begin(), 81 | static_cast<std::int64_t>(sampleIdx))) 82 | ->first; 83 | results[state] += 1; 84 | } 85 | 86 | return results; 87 | } 88 | -------------------------------------------------------------------------------- /src/GroverSimulator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "GroverSimulator.hpp" 12 | 13 | #include "dd/DDDefinitions.hpp" 14 | #include "dd/Edge.hpp" 15 | #include "dd/FunctionalityConstruction.hpp" 16 | #include "ir/Definitions.hpp" 17 | #include "ir/QuantumComputation.hpp" 18 | #include "ir/operations/Control.hpp" 19 | 20 | #include <cassert> 21 | #include <cstddef> 22 | #include <limits> 23 | #include <map> 24 | #include <string> 25 | 26 | std::map<std::string, std::size_t> 27 | GroverSimulator::simulate(std::size_t shots) { 28 | // Setup X on the last, Hadamard on all qubits 29 | qc::QuantumComputation qcSetup(nQubits + nAnciallae); 30 | qcSetup.x(nQubits); 31 | for (qc::Qubit i = 0; i < nQubits; ++i) { 32 | qcSetup.h(i); 33 | } 34 | 35 | const dd::Edge setupOp{dd::buildFunctionality(qcSetup, *dd)}; 36 | 37 | // Build the oracle 38 | qc::QuantumComputation qcOracle(nQubits + nAnciallae); 39 | qc::Controls controls{}; 40 | for (qc::Qubit i = 0; i < nQubits; i++) { 41 | controls.emplace(i, oracle.at(i) == '1' ? qc::Control::Type::Pos 42 | : qc::Control::Type::Neg); 43 | } 44 | qcOracle.mcz(controls, nQubits); 45 | 46 | const dd::Edge oracleOp{dd::buildFunctionality(qcOracle, *dd)}; 47 | 48 | // Build the diffusion stage. 49 | qc::QuantumComputation qcDiffusion(nQubits + nAnciallae); 50 | 51 | for (qc::Qubit i = 0; i < nQubits; ++i) { 52 | qcDiffusion.h(i); 53 | } 54 | for (qc::Qubit i = 0; i < nQubits; ++i) { 55 | qcDiffusion.x(i); 56 | } 57 | 58 | qcDiffusion.h(nQubits - 1); 59 | 60 | qc::Controls diffControls{}; 61 | for (qc::Qubit j = 0; j < nQubits - 1; ++j) { 62 | diffControls.emplace(j); 63 | } 64 | qcDiffusion.mcx(diffControls, nQubits - 1); 65 | 66 | qcDiffusion.h(nQubits - 1); 67 | 68 | for (qc::Qubit i = 0; i < nQubits; ++i) { 69 | qcDiffusion.x(i); 70 | } 71 | for (qc::Qubit i = 0; i < nQubits; ++i) { 72 | qcDiffusion.h(i); 73 | } 74 | 75 | const dd::Edge diffusionOp{dd::buildFunctionality(qcDiffusion, *dd)}; 76 | 77 | const dd::Edge fullIteration{dd->multiply(oracleOp, diffusionOp)}; 78 | dd->incRef(fullIteration); 79 | 80 | assert(nQubits + nAnciallae <= std::numeric_limits<dd::Qubit>::max()); 81 | rootEdge = dd->makeZeroState(static_cast<dd::Qubit>(nQubits + nAnciallae)); 82 | rootEdge = dd->multiply(setupOp, rootEdge); 83 | dd->incRef(rootEdge); 84 | 85 | std::size_t jPre = 0; 86 | 87 | while ((iterations - jPre) % 8 != 0) { 88 | auto tmp = dd->multiply(fullIteration, rootEdge); 89 | dd->incRef(tmp); 90 | dd->decRef(rootEdge); 91 | rootEdge = tmp; 92 | dd->garbageCollect(); 93 | jPre++; 94 | } 95 | 96 | for (std::size_t j = jPre; j < iterations; j += 8) { 97 | auto tmp = dd->multiply(fullIteration, rootEdge); 98 | for (std::size_t i = 0; i < 7; ++i) { 99 | tmp = dd->multiply(fullIteration, tmp); 100 | } 101 | dd->incRef(tmp); 102 | dd->decRef(rootEdge); 103 | rootEdge = tmp; 104 | dd->garbageCollect(); 105 | } 106 | 107 | return measureAllNonCollapsing(shots); 108 | } 109 | -------------------------------------------------------------------------------- /src/UnitarySimulator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "UnitarySimulator.hpp" 12 | 13 | #include "CircuitSimulator.hpp" 14 | #include "circuit_optimizer/CircuitOptimizer.hpp" 15 | #include "dd/DDpackageConfig.hpp" 16 | #include "dd/FunctionalityConstruction.hpp" 17 | #include "dd/Node.hpp" 18 | #include "ir/QuantumComputation.hpp" 19 | 20 | #include <chrono> 21 | #include <cstddef> 22 | #include <cstdint> 23 | #include <memory> 24 | #include <utility> 25 | 26 | void UnitarySimulator::construct() { 27 | // carry out actual computation 28 | auto start = std::chrono::steady_clock::now(); 29 | if (mode == Mode::Sequential) { 30 | e = dd::buildFunctionality(*qc, *dd); 31 | } else if (mode == Mode::Recursive) { 32 | e = dd::buildFunctionalityRecursive(*qc, *dd); 33 | } 34 | auto end = std::chrono::steady_clock::now(); 35 | constructionTime = std::chrono::duration<double>(end - start).count(); 36 | } 37 | 38 | UnitarySimulator::UnitarySimulator( 39 | std::unique_ptr<qc::QuantumComputation>&& qc_, 40 | const ApproximationInfo& approximationInfo_, Mode simMode) 41 | : CircuitSimulator(std::move(qc_), approximationInfo_, 42 | dd::UNITARY_SIMULATOR_DD_PACKAGE_CONFIG), 43 | mode(simMode) { 44 | // remove final measurements 45 | qc::CircuitOptimizer::removeFinalMeasurements(*qc); 46 | } 47 | 48 | UnitarySimulator::UnitarySimulator( 49 | std::unique_ptr<qc::QuantumComputation>&& qc_, Mode simMode) 50 | : UnitarySimulator(std::move(qc_), {}, simMode) {} 51 | 52 | UnitarySimulator::UnitarySimulator( 53 | std::unique_ptr<qc::QuantumComputation>&& qc_, 54 | const ApproximationInfo& approximationInfo_, const std::uint64_t seed_, 55 | const Mode simMode) 56 | : CircuitSimulator(std::move(qc_), approximationInfo_, seed_, 57 | dd::UNITARY_SIMULATOR_DD_PACKAGE_CONFIG), 58 | mode(simMode) { 59 | // remove final measurements 60 | qc::CircuitOptimizer::removeFinalMeasurements(*qc); 61 | } 62 | 63 | std::size_t UnitarySimulator::getMaxNodeCount() const { 64 | return dd->getUniqueTable<dd::mNode>().getPeakNumActiveEntries(); 65 | } 66 | -------------------------------------------------------------------------------- /src/mqt/ddsim/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """MQT DDSim Python Package.""" 10 | 11 | from __future__ import annotations 12 | 13 | import sys 14 | 15 | # under Windows, make sure to add the appropriate DLL directory to the PATH 16 | if sys.platform == "win32": 17 | 18 | def _dll_patch() -> None: 19 | """Add the DLL directory to the PATH.""" 20 | import os 21 | import sysconfig 22 | from pathlib import Path 23 | 24 | site_packages = Path(sysconfig.get_paths()["purelib"]) 25 | bin_dir = site_packages / "mqt" / "core" / "bin" 26 | os.add_dll_directory(str(bin_dir)) 27 | 28 | _dll_patch() 29 | del _dll_patch 30 | 31 | from ._version import version as __version__ 32 | from .provider import DDSIMProvider 33 | from .pyddsim import ( 34 | CircuitSimulator, 35 | ConstructionMode, 36 | DeterministicNoiseSimulator, 37 | HybridCircuitSimulator, 38 | HybridMode, 39 | PathCircuitSimulator, 40 | PathSimulatorConfiguration, 41 | PathSimulatorMode, 42 | StochasticNoiseSimulator, 43 | UnitarySimulator, 44 | ) 45 | 46 | __all__ = [ 47 | "CircuitSimulator", 48 | "ConstructionMode", 49 | "DDSIMProvider", 50 | "DeterministicNoiseSimulator", 51 | "HybridCircuitSimulator", 52 | "HybridMode", 53 | "PathCircuitSimulator", 54 | "PathSimulatorConfiguration", 55 | "PathSimulatorMode", 56 | "StochasticNoiseSimulator", 57 | "UnitarySimulator", 58 | "__version__", 59 | ] 60 | -------------------------------------------------------------------------------- /src/mqt/ddsim/_version.pyi: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | __version__: str 10 | version: str 11 | __version_tuple__: tuple[int, int, int, str, str] | tuple[int, int, int] 12 | version_tuple: tuple[int, int, int, str, str] | tuple[int, int, int] 13 | -------------------------------------------------------------------------------- /src/mqt/ddsim/deterministicnoisesimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Density Matrix Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | import time 14 | from typing import TYPE_CHECKING, Any, cast 15 | 16 | from mqt.core import load 17 | from qiskit.providers import Options 18 | from qiskit.result.models import ExperimentResult, ExperimentResultData 19 | 20 | from mqt import ddsim 21 | 22 | from .header import DDSIMHeader 23 | from .qasmsimulator import QasmSimulatorBackend 24 | 25 | if TYPE_CHECKING: 26 | from qiskit import QuantumCircuit 27 | 28 | 29 | class DeterministicNoiseSimulatorBackend(QasmSimulatorBackend): 30 | """Python interface to MQT DDSIM deterministic noise-aware simulator.""" 31 | 32 | def __init__( 33 | self, 34 | name: str = "dd_simulator_density_matrix", 35 | description: str = "MQT DDSIM noise-aware density matrix simulator based on decision diagrams", 36 | ) -> None: 37 | """Constructor for the DDSIM density matrix simulator backend. 38 | 39 | Args: 40 | name: The name of the backend. 41 | description: The description of the backend. 42 | """ 43 | super().__init__(name=name, description=description) 44 | 45 | @classmethod 46 | def _default_options(cls) -> Options: 47 | return Options( 48 | shots=None, 49 | parameter_binds=None, 50 | simulator_seed=None, 51 | noise_effects="APD", 52 | noise_probability=0.01, 53 | amp_damping_probability=0.02, 54 | multi_qubit_gate_factor=2, 55 | ) 56 | 57 | @staticmethod 58 | def _run_experiment(qc: QuantumCircuit, **options: dict[str, Any]) -> ExperimentResult: 59 | start_time = time.time() 60 | noise_effects = cast("str", options.get("noise_effects", "APD")) 61 | noise_probability = cast("float", options.get("noise_probability", 0.01)) 62 | amp_damping_probability = cast("float", options.get("amp_damping_probability", 0.02)) 63 | multi_qubit_gate_factor = cast("float", options.get("multi_qubit_gate_factor", 2)) 64 | seed = cast("int", options.get("simulator_seed", -1)) 65 | shots = cast("int", options.get("shots", 1024)) 66 | 67 | circ = load(qc) 68 | sim = ddsim.DeterministicNoiseSimulator( 69 | circ=circ, 70 | seed=seed, 71 | noise_effects=noise_effects, 72 | noise_probability=noise_probability, 73 | amp_damping_probability=amp_damping_probability, 74 | multi_qubit_gate_factor=multi_qubit_gate_factor, 75 | ) 76 | 77 | counts = sim.simulate(shots=shots) 78 | end_time = time.time() 79 | 80 | data = ExperimentResultData( 81 | counts={hex(int(result, 2)): count for result, count in counts.items()}, 82 | statevector=None, 83 | time_taken=end_time - start_time, 84 | ) 85 | 86 | return ExperimentResult( 87 | shots=shots, 88 | success=True, 89 | status="DONE", 90 | seed=seed, 91 | data=data, 92 | metadata=qc.metadata, 93 | header=DDSIMHeader(qc), 94 | ) 95 | -------------------------------------------------------------------------------- /src/mqt/ddsim/header.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Utilities for constructing a Qiskit experiment header for DDSIM backends.""" 10 | 11 | from __future__ import annotations 12 | 13 | from dataclasses import dataclass 14 | from typing import TYPE_CHECKING 15 | 16 | from qiskit.result.models import QobjExperimentHeader 17 | 18 | if TYPE_CHECKING: 19 | from qiskit import QuantumCircuit 20 | 21 | 22 | @dataclass 23 | class DDSIMHeader(QobjExperimentHeader): # type: ignore[misc] 24 | """Qiskit experiment header for DDSIM backends.""" 25 | 26 | name: str 27 | n_qubits: int 28 | memory_slots: int 29 | global_phase: float 30 | creg_sizes: list[tuple[str, int]] 31 | clbit_labels: list[tuple[str, int]] 32 | qreg_sizes: list[tuple[str, int]] 33 | qubit_labels: list[tuple[str, int]] 34 | 35 | def __init__(self, qc: QuantumCircuit) -> None: 36 | """Initialize a new DDSIM experiment header.""" 37 | # no call to super class constructor because this would trigger deprecation warnings 38 | self.name = qc.name 39 | self.n_qubits = qc.num_qubits 40 | self.memory_slots = qc.num_clbits 41 | self.global_phase = qc.global_phase 42 | self.creg_sizes = [(creg.name, creg.size) for creg in qc.cregs] 43 | self.clbit_labels = [(creg.name, j) for creg in qc.cregs for j in range(creg.size)] 44 | self.qreg_sizes = [(qreg.name, qreg.size) for qreg in qc.qregs] 45 | self.qubit_labels = [(qreg.name, j) for qreg in qc.qregs for j in range(qreg.size)] 46 | -------------------------------------------------------------------------------- /src/mqt/ddsim/hybridqasmsimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Hybrid Schrodinger-Feynman Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | import time 14 | from typing import TYPE_CHECKING, Any 15 | 16 | if TYPE_CHECKING: 17 | from qiskit import QuantumCircuit 18 | 19 | import numpy as np 20 | from mqt.core import load 21 | from qiskit import QiskitError 22 | from qiskit.providers import Options 23 | from qiskit.result.models import ExperimentResult, ExperimentResultData 24 | from qiskit.transpiler import Target 25 | from qiskit.utils.multiprocessing import local_hardware_info 26 | 27 | from .header import DDSIMHeader 28 | from .pyddsim import HybridCircuitSimulator, HybridMode 29 | from .qasmsimulator import QasmSimulatorBackend 30 | from .target import DDSIMTargetBuilder 31 | 32 | 33 | class HybridQasmSimulatorBackend(QasmSimulatorBackend): 34 | """Python interface to MQT DDSIM Hybrid Schrodinger-Feynman Simulator.""" 35 | 36 | _HSF_TARGET = Target(description="MQT DDSIM HSF Simulator Target", num_qubits=128) 37 | 38 | @staticmethod 39 | def _add_operations_to_target(target: Target) -> None: 40 | DDSIMTargetBuilder.add_0q_gates(target) 41 | DDSIMTargetBuilder.add_1q_gates(target) 42 | DDSIMTargetBuilder.add_2q_controlled_gates(target) 43 | DDSIMTargetBuilder.add_barrier(target) 44 | DDSIMTargetBuilder.add_measure(target) 45 | 46 | def __init__( 47 | self, 48 | name: str = "hybrid_qasm_simulator", 49 | description: str = "MQT DDSIM Hybrid Schrodinger-Feynman simulator", 50 | ) -> None: 51 | """Constructor for the DDSIM Hybrid Schrodinger-Feynman simulator backend. 52 | 53 | Args: 54 | name: The name of the backend. 55 | description: The description of the backend. 56 | """ 57 | super().__init__(name=name, description=description) 58 | 59 | @classmethod 60 | def _default_options(cls) -> Options: 61 | return Options( 62 | shots=None, 63 | parameter_binds=None, 64 | simulator_seed=None, 65 | mode="amplitude", 66 | nthreads=local_hardware_info()["cpus"], 67 | ) 68 | 69 | @property 70 | def target(self) -> Target: 71 | """Return the target of the backend.""" 72 | return self._HSF_TARGET 73 | 74 | def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResult: 75 | start_time = time.time() 76 | seed = options.get("seed", -1) 77 | mode = options.get("mode", "amplitude") 78 | nthreads = int(options.get("nthreads", local_hardware_info()["cpus"])) 79 | if mode == "amplitude": 80 | hybrid_mode = HybridMode.amplitude 81 | max_qubits = 30 # hard-coded 16GiB memory limit 82 | algorithm_qubits = qc.num_qubits 83 | if algorithm_qubits > max_qubits: 84 | msg = "Not enough memory available to simulate the circuit even on a single thread" 85 | raise QiskitError(msg) 86 | qubit_diff = max_qubits - algorithm_qubits 87 | nthreads = int(min(2**qubit_diff, nthreads)) 88 | elif mode == "dd": 89 | hybrid_mode = HybridMode.DD 90 | else: 91 | msg = f"Simulation mode{mode} not supported by hybrid simulator. Available modes are 'amplitude' and 'dd'." 92 | raise QiskitError(msg) 93 | 94 | circuit = load(qc) 95 | sim = HybridCircuitSimulator(circuit, seed=seed, mode=hybrid_mode, nthreads=nthreads) 96 | 97 | shots = options.get("shots", 1024) 98 | if self._SHOW_STATE_VECTOR and shots > 0: 99 | shots = 0 100 | 101 | counts = sim.simulate(shots) 102 | end_time = time.time() 103 | 104 | data = ExperimentResultData( 105 | counts={hex(int(result, 2)): count for result, count in counts.items()}, 106 | statevector=None 107 | if not self._SHOW_STATE_VECTOR 108 | else np.array(sim.get_constructed_dd().get_vector(), copy=False) 109 | if sim.get_mode() == HybridMode.DD 110 | else sim.get_final_amplitudes(), 111 | time_taken=end_time - start_time, 112 | mode=mode, 113 | nthreads=nthreads, 114 | ) 115 | 116 | return ExperimentResult( 117 | shots=shots, 118 | success=True, 119 | status="DONE", 120 | seed=seed, 121 | data=data, 122 | metadata=qc.metadata, 123 | header=DDSIMHeader(qc), 124 | ) 125 | -------------------------------------------------------------------------------- /src/mqt/ddsim/hybridstatevectorsimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Hybrid Schrodinger-Feynman Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | from qiskit.transpiler import Target 14 | 15 | from .hybridqasmsimulator import HybridQasmSimulatorBackend 16 | 17 | 18 | class HybridStatevectorSimulatorBackend(HybridQasmSimulatorBackend): 19 | """Python interface to MQT DDSIM Hybrid Schrodinger-Feynman Simulator.""" 20 | 21 | _SHOW_STATE_VECTOR = True 22 | _HSF_SV_TARGET = Target( 23 | description="MQT DDSIM HSF Statevector Simulator Target", 24 | num_qubits=30, # corresponds to 16GiB memory for storing the full statevector 25 | ) 26 | 27 | def __init__(self) -> None: 28 | """Constructor for the DDSIM Hybrid Schrodinger-Feynman Statevector simulator backend.""" 29 | super().__init__( 30 | name="hybrid_statevector_simulator", 31 | description="MQT DDSIM Hybrid Schrodinger-Feynman Statevector simulator", 32 | ) 33 | 34 | @property 35 | def target(self) -> Target: 36 | """Return the target of the backend.""" 37 | return self._HSF_SV_TARGET 38 | -------------------------------------------------------------------------------- /src/mqt/ddsim/job.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Job module for DDSIM backend.""" 10 | 11 | from __future__ import annotations 12 | 13 | import functools 14 | from concurrent import futures 15 | from typing import TYPE_CHECKING, Any, Union 16 | 17 | from qiskit.providers import JobError, JobStatus, JobV1 18 | 19 | if TYPE_CHECKING: 20 | from collections.abc import Callable, Mapping, Sequence 21 | 22 | from qiskit import QuantumCircuit 23 | from qiskit.circuit import Parameter 24 | from qiskit.circuit.parameterexpression import ParameterValueType 25 | from qiskit.providers import BackendV2 26 | from qiskit.result import Result 27 | 28 | Parameters = Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]] 29 | 30 | 31 | def requires_submit(func: Callable[..., Any]) -> Callable[..., Any]: 32 | """Decorator to ensure that a submit has been performed before calling the method. 33 | 34 | Args: 35 | func (callable): test function to be decorated. 36 | 37 | Returns: 38 | callable: the decorated function. 39 | """ 40 | 41 | @functools.wraps(func) 42 | def _wrapper(self: DDSIMJob, *args: Any, **kwargs: Any) -> Any: # noqa: ANN401 43 | if self._future is None: 44 | msg = "Job not submitted yet!. You have to .submit() first!" 45 | raise JobError(msg) 46 | return func(self, *args, **kwargs) 47 | 48 | return _wrapper 49 | 50 | 51 | class DDSIMJob(JobV1): # type: ignore[misc] 52 | """DDSIMJob class. 53 | 54 | Attributes: 55 | _executor (futures.Executor): executor to handle asynchronous jobs 56 | """ 57 | 58 | _executor = futures.ThreadPoolExecutor(max_workers=1) 59 | 60 | def __init__( 61 | self, 62 | backend: BackendV2, 63 | job_id: str, 64 | fn: Callable[..., Result], 65 | experiments: Sequence[QuantumCircuit], 66 | parameter_values: Sequence[Parameters] | None, 67 | **args: dict[str, Any], 68 | ) -> None: 69 | """Constructor. 70 | 71 | Args: 72 | backend: the backend instance used to run the job. 73 | job_id: job ID. 74 | fn: function to be run by the job. 75 | experiments: circuits to be executed. 76 | parameter_values: the parameters for each circuit. 77 | args: additional arguments for the function 78 | """ 79 | super().__init__(backend, job_id) 80 | self._fn = fn 81 | self._experiments = experiments 82 | self._parameter_values = parameter_values 83 | self._args = args 84 | self._future: futures.Future[Result] | None = None 85 | 86 | def submit(self) -> None: 87 | """Submit the job to the backend for execution. 88 | 89 | Raises: 90 | JobError: if trying to re-submit the job. 91 | """ 92 | if self._future is not None: 93 | msg = "Job was already submitted!" 94 | raise JobError(msg) 95 | 96 | self._future = self._executor.submit( 97 | self._fn, 98 | self._job_id, 99 | self._experiments, 100 | self._parameter_values, 101 | **self._args, 102 | ) 103 | 104 | @requires_submit 105 | def result(self, timeout: float | None = None) -> Result: 106 | # pylint: disable=arguments-differ 107 | """Get job result. 108 | 109 | The behavior is the same as the underlying 110 | concurrent Future objects, 111 | https://docs.python.org/3/library/concurrent.futures.html#future-objects. 112 | 113 | Args: 114 | timeout: number of seconds to wait for results. 115 | 116 | Returns: 117 | Result object 118 | Raises: 119 | concurrent.futures.TimeoutError: if timeout occurred. 120 | concurrent.futures.CancelledError: if job cancelled before completed. 121 | """ 122 | assert self._future is not None 123 | return self._future.result(timeout=timeout) 124 | 125 | @requires_submit 126 | def cancel(self) -> bool: 127 | """Attempt to cancel the job.""" 128 | assert self._future is not None 129 | return self._future.cancel() 130 | 131 | @requires_submit 132 | def status(self) -> JobStatus: 133 | """Gets the status of the job by querying the Python's future. 134 | 135 | Returns: 136 | JobStatus: The current JobStatus 137 | Raises: 138 | JobError: If the future is in unexpected state 139 | concurrent.futures.TimeoutError: if timeout occurred. 140 | """ 141 | # The order is important here 142 | assert self._future is not None 143 | if self._future.running(): 144 | return JobStatus.RUNNING 145 | if self._future.cancelled(): 146 | return JobStatus.CANCELLED 147 | if self._future.done(): 148 | return JobStatus.DONE if self._future.exception() is None else JobStatus.ERROR 149 | 150 | # Note: There is an undocumented Future state: PENDING, that seems to show up when 151 | # the job is enqueued, waiting for someone to pick it up. We need to deal with this 152 | # state but there's no public API for it, so we are assuming that if the job is not 153 | # in any of the previous states, is PENDING, ergo INITIALIZING for us. 154 | return JobStatus.INITIALIZING 155 | 156 | def backend(self) -> BackendV2 | None: 157 | """Return the instance of the backend used for this job.""" 158 | return self._backend 159 | -------------------------------------------------------------------------------- /src/mqt/ddsim/pathqasmsimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Task-Based Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | import time 14 | from typing import TYPE_CHECKING, Any 15 | 16 | if TYPE_CHECKING: 17 | from qiskit import QuantumCircuit 18 | 19 | 20 | import numpy as np 21 | from mqt.core import load 22 | from qiskit.providers import Options 23 | from qiskit.result.models import ExperimentResult, ExperimentResultData 24 | from qiskit.transpiler import Target 25 | 26 | from .header import DDSIMHeader 27 | from .pyddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode 28 | from .qasmsimulator import QasmSimulatorBackend 29 | from .target import DDSIMTargetBuilder 30 | 31 | 32 | class PathQasmSimulatorBackend(QasmSimulatorBackend): 33 | """Python interface to MQT DDSIM Simulation Path Framework.""" 34 | 35 | _PATH_TARGET = Target(description="MQT DDSIM Simulation Path Framework Target", num_qubits=128) 36 | 37 | @staticmethod 38 | def _add_operations_to_target(target: Target) -> None: 39 | DDSIMTargetBuilder.add_0q_gates(target) 40 | DDSIMTargetBuilder.add_1q_gates(target) 41 | DDSIMTargetBuilder.add_2q_gates(target) 42 | DDSIMTargetBuilder.add_3q_gates(target) 43 | DDSIMTargetBuilder.add_multi_qubit_gates(target) 44 | DDSIMTargetBuilder.add_barrier(target) 45 | DDSIMTargetBuilder.add_measure(target) 46 | 47 | def __init__( 48 | self, 49 | name: str = "path_sim_qasm_simulator", 50 | description: str = "MQT DDSIM Simulation Path Framework", 51 | ) -> None: 52 | """Constructor for the DDSIM Simulation Path Framework QASM Simulator backend. 53 | 54 | Args: 55 | name: The name of the backend. 56 | description: The description of the backend. 57 | """ 58 | super().__init__(name=name, description=description) 59 | 60 | @classmethod 61 | def _default_options(cls) -> Options: 62 | return Options( 63 | shots=None, 64 | parameter_binds=None, 65 | simulator_seed=None, 66 | pathsim_configuration=PathSimulatorConfiguration(), 67 | mode=None, 68 | bracket_size=None, 69 | alternating_start=None, 70 | gate_cost=None, 71 | seed=None, 72 | ) 73 | 74 | @property 75 | def target(self) -> Target: 76 | """Return the target of the backend.""" 77 | return self._PATH_TARGET 78 | 79 | def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResult: 80 | start_time = time.time() 81 | 82 | pathsim_configuration = options.get("pathsim_configuration", PathSimulatorConfiguration()) 83 | 84 | mode = options.get("mode") 85 | if mode is not None: 86 | pathsim_configuration.mode = PathSimulatorMode(mode) 87 | 88 | bracket_size = options.get("bracket_size") 89 | if bracket_size is not None: 90 | pathsim_configuration.bracket_size = bracket_size 91 | 92 | alternating_start = options.get("alternating_start") 93 | if alternating_start is not None: 94 | pathsim_configuration.alternating_start = alternating_start 95 | 96 | gate_cost = options.get("gate_cost") 97 | if gate_cost is not None: 98 | pathsim_configuration.gate_cost = gate_cost 99 | 100 | seed: int | None = options.get("seed") 101 | if seed is not None: 102 | pathsim_configuration.seed = seed 103 | 104 | circuit = load(qc) 105 | sim = PathCircuitSimulator(circuit, config=pathsim_configuration) 106 | 107 | shots = options.get("shots", 1024) 108 | setup_time = time.time() 109 | counts = sim.simulate(shots) 110 | end_time = time.time() 111 | 112 | data = ExperimentResultData( 113 | counts={hex(int(result, 2)): count for result, count in counts.items()}, 114 | statevector=None if not self._SHOW_STATE_VECTOR else np.array(sim.get_constructed_dd().get_vector()), 115 | time_taken=end_time - start_time, 116 | time_setup=setup_time - start_time, 117 | time_sim=end_time - setup_time, 118 | ) 119 | 120 | return ExperimentResult( 121 | shots=shots, 122 | success=True, 123 | status="DONE", 124 | config=pathsim_configuration, 125 | seed=seed, 126 | data=data, 127 | metadata=qc.metadata, 128 | header=DDSIMHeader(qc), 129 | ) 130 | -------------------------------------------------------------------------------- /src/mqt/ddsim/pathstatevectorsimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM.""" 10 | 11 | from __future__ import annotations 12 | 13 | from qiskit.transpiler import Target 14 | 15 | from .pathqasmsimulator import PathQasmSimulatorBackend 16 | 17 | 18 | class PathStatevectorSimulatorBackend(PathQasmSimulatorBackend): 19 | """Python interface to MQT DDSIM Simulation Path Framework.""" 20 | 21 | _SHOW_STATE_VECTOR = True 22 | _Path_SV_TARGET = Target( 23 | description="MQT DDSIM Simulation Path Framework Statevector Target", 24 | num_qubits=30, # corresponds to 16GiB memory for storing the full statevector 25 | ) 26 | 27 | def __init__(self) -> None: 28 | """Constructor for the DDSIM Simulation Path Framework Statevector Simulator backend.""" 29 | super().__init__( 30 | name="path_sim_statevector_simulator", 31 | description="MQT DDSIM Simulation Path Framework Statevector Simulator", 32 | ) 33 | 34 | @property 35 | def target(self) -> Target: 36 | """Return the target of the backend.""" 37 | return self._Path_SV_TARGET 38 | -------------------------------------------------------------------------------- /src/mqt/ddsim/primitives/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Module for Qiskit Primitives.""" 10 | 11 | from __future__ import annotations 12 | 13 | from .estimator import Estimator 14 | from .sampler import Sampler 15 | 16 | __all__ = ["Estimator", "Sampler"] 17 | -------------------------------------------------------------------------------- /src/mqt/ddsim/primitives/sampler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Sampler implementation using QasmSimulatorBackend.""" 10 | 11 | from __future__ import annotations 12 | 13 | import math 14 | from typing import TYPE_CHECKING, Any, Union 15 | 16 | from qiskit.primitives import SamplerResult 17 | from qiskit.primitives.sampler import Sampler as QiskitSampler 18 | from qiskit.result import QuasiDistribution, Result 19 | 20 | from mqt.ddsim.qasmsimulator import QasmSimulatorBackend 21 | 22 | if TYPE_CHECKING: 23 | from collections.abc import Mapping, Sequence 24 | 25 | from qiskit.circuit import Parameter 26 | from qiskit.circuit.parameterexpression import ParameterValueType 27 | 28 | Parameters = Union[Mapping[Parameter, ParameterValueType], Sequence[ParameterValueType]] 29 | 30 | 31 | class Sampler(QiskitSampler): # type: ignore[misc] 32 | """Sampler implementation using QasmSimulatorBackend.""" 33 | 34 | _BACKEND = QasmSimulatorBackend() 35 | 36 | def __init__( 37 | self, 38 | *, 39 | options: dict[str, Any] | None = None, 40 | ) -> None: 41 | """Initialize a new DDSIM Sampler. 42 | 43 | Args: 44 | options: Default options. 45 | """ 46 | super().__init__(options=options) 47 | 48 | @property 49 | def backend(self) -> QasmSimulatorBackend: 50 | """The backend used by the sampler.""" 51 | return self._BACKEND 52 | 53 | @property 54 | def num_circuits(self) -> int: 55 | """The number of circuits stored in the sampler.""" 56 | return len(self._circuits) 57 | 58 | def _call( 59 | self, 60 | circuits: Sequence[int], 61 | parameter_values: Sequence[Parameters], 62 | **run_options: Any, 63 | ) -> SamplerResult: 64 | """Runs DDSIM backend. 65 | 66 | Args: 67 | circuits: List of circuit indices to simulate 68 | parameter_values: List of parameters associated with those circuits 69 | run_options: Additional run options. 70 | 71 | Returns: 72 | The result of the sampling process. 73 | """ 74 | result = self.backend.run([self._circuits[i] for i in circuits], parameter_values, **run_options).result() 75 | 76 | return self._postprocessing(result, circuits) 77 | 78 | @staticmethod 79 | def _postprocessing(result: Result, circuits: Sequence[int]) -> SamplerResult: 80 | """Converts counts into quasi-probability distributions. 81 | 82 | Args: 83 | result: Result from DDSIM backend 84 | circuits: List of circuit indices 85 | 86 | Returns: 87 | The result of the sampling process. 88 | """ 89 | counts = result.get_counts() 90 | if not isinstance(counts, list): 91 | counts = [counts] 92 | 93 | shots = sum(counts[0].values()) 94 | metadata: list[dict[str, Any]] = [{"shots": shots} for _ in range(len(circuits))] 95 | probabilities = [ 96 | QuasiDistribution( 97 | {k: v / shots for k, v in count.items()}, 98 | shots=shots, 99 | stddev_upper_bound=1 / math.sqrt(shots), 100 | ) 101 | for count in counts 102 | ] 103 | 104 | return SamplerResult(probabilities, metadata) 105 | -------------------------------------------------------------------------------- /src/mqt/ddsim/provider.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Provider for DDSIM backends.""" 10 | 11 | from __future__ import annotations 12 | 13 | from typing import TYPE_CHECKING, Any, cast 14 | 15 | from qiskit.providers.exceptions import QiskitBackendNotFoundError 16 | from qiskit.providers.providerutils import filter_backends 17 | 18 | from .deterministicnoisesimulator import DeterministicNoiseSimulatorBackend 19 | from .hybridqasmsimulator import HybridQasmSimulatorBackend 20 | from .hybridstatevectorsimulator import HybridStatevectorSimulatorBackend 21 | from .pathqasmsimulator import PathQasmSimulatorBackend 22 | from .pathstatevectorsimulator import PathStatevectorSimulatorBackend 23 | from .qasmsimulator import QasmSimulatorBackend 24 | from .statevectorsimulator import StatevectorSimulatorBackend 25 | from .stochasticnoisesimulator import StochasticNoiseSimulatorBackend 26 | from .unitarysimulator import UnitarySimulatorBackend 27 | 28 | if TYPE_CHECKING: 29 | from collections.abc import Callable 30 | 31 | from qiskit.providers import BackendV2 32 | 33 | 34 | class DDSIMProvider: 35 | """Provider for DDSIM backends.""" 36 | 37 | _BACKENDS = ( 38 | ("qasm_simulator", QasmSimulatorBackend), 39 | ("statevector_simulator", StatevectorSimulatorBackend), 40 | ("hybrid_qasm_simulator", HybridQasmSimulatorBackend), 41 | ("hybrid_statevector_simulator", HybridStatevectorSimulatorBackend), 42 | ("path_sim_qasm_simulator", PathQasmSimulatorBackend), 43 | ("path_sim_statevector_simulator", PathStatevectorSimulatorBackend), 44 | ("unitary_simulator", UnitarySimulatorBackend), 45 | ("stochastic_dd_simulator", StochasticNoiseSimulatorBackend), 46 | ("density_matrix_dd_simulator", DeterministicNoiseSimulatorBackend), 47 | ) 48 | 49 | def get_backend(self, name: str | None = None, **kwargs: Any) -> BackendV2: 50 | """Return a backend matching the specified criteria. 51 | 52 | Args: 53 | name: Name of the backend. 54 | kwargs: Additional filtering criteria. 55 | """ 56 | backends = self.backends(name, **kwargs) 57 | if len(backends) > 1: 58 | msg = "More than one backend matches the criteria" 59 | raise QiskitBackendNotFoundError(msg) 60 | if not backends: 61 | msg = "No backend matches the criteria" 62 | raise QiskitBackendNotFoundError(msg) 63 | 64 | return backends[0] 65 | 66 | def backends( 67 | self, 68 | name: str | None = None, 69 | filters: Callable[[list[BackendV2]], list[BackendV2]] | None = None, 70 | **kwargs: dict[str, Any], 71 | ) -> list[BackendV2]: 72 | """Return a list of backends matching the specified criteria. 73 | 74 | Args: 75 | name: Name of the backend. 76 | filters: Additional filtering criteria. 77 | kwargs: Additional filtering criteria. 78 | """ 79 | backends = [ 80 | backend_cls() for backend_name, backend_cls in self._BACKENDS if name is None or backend_name == name 81 | ] 82 | return cast("list[BackendV2]", filter_backends(backends, filters=filters, **kwargs)) 83 | 84 | def __str__(self) -> str: 85 | """Return the provider name.""" 86 | return "DDSIMProvider" 87 | -------------------------------------------------------------------------------- /src/mqt/ddsim/py.typed: -------------------------------------------------------------------------------- 1 | # Instruct type checkers to look for inline type annotations in this package. 2 | # See PEP 561. 3 | -------------------------------------------------------------------------------- /src/mqt/ddsim/statevectorsimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM.""" 10 | 11 | from __future__ import annotations 12 | 13 | from qiskit.transpiler import Target 14 | 15 | from .qasmsimulator import QasmSimulatorBackend 16 | 17 | 18 | class StatevectorSimulatorBackend(QasmSimulatorBackend): 19 | """Python interface to MQT DDSIM.""" 20 | 21 | _SHOW_STATE_VECTOR = True 22 | _SV_TARGET = Target( 23 | description="MQT DDSIM Statevector Simulator Target", 24 | num_qubits=30, # corresponds to 16GiB memory for storing the full statevector 25 | ) 26 | 27 | def __init__(self) -> None: 28 | """Constructor for the DDSIM Statevector simulator backend.""" 29 | super().__init__(name="statevector_simulator", description="MQT DDSIM Statevector Simulator") 30 | 31 | @property 32 | def target(self) -> Target: 33 | """Return the target of the backend.""" 34 | return self._SV_TARGET 35 | -------------------------------------------------------------------------------- /src/mqt/ddsim/stochasticnoisesimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Stochastic Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | import time 14 | from typing import TYPE_CHECKING, Any, cast 15 | 16 | from mqt.core import load 17 | from qiskit.providers import Options 18 | from qiskit.result.models import ExperimentResult, ExperimentResultData 19 | 20 | from mqt import ddsim 21 | 22 | from .header import DDSIMHeader 23 | from .qasmsimulator import QasmSimulatorBackend 24 | 25 | if TYPE_CHECKING: 26 | from qiskit import QuantumCircuit 27 | 28 | 29 | class StochasticNoiseSimulatorBackend(QasmSimulatorBackend): 30 | """Python interface to MQT DDSIM stochastic noise-aware simulator.""" 31 | 32 | def __init__( 33 | self, 34 | name: str = "stochastic_dd_simulator", 35 | description: str = "MQT DDSIM noise-aware stochastic simulator based on decision diagrams", 36 | ) -> None: 37 | """Constructor for the DDSIM stochastic simulator backend.""" 38 | super().__init__(name=name, description=description) 39 | 40 | @classmethod 41 | def _default_options(cls) -> Options: 42 | return Options( 43 | shots=None, 44 | parameter_binds=None, 45 | simulator_seed=None, 46 | approximation_step_fidelity=1.0, 47 | approximation_steps=1, 48 | approximation_strategy="fidelity", 49 | noise_effects="APD", 50 | noise_probability=0.01, 51 | amp_damping_probability=0.02, 52 | multi_qubit_gate_factor=2, 53 | ) 54 | 55 | @staticmethod 56 | def _run_experiment(qc: QuantumCircuit, **options: dict[str, Any]) -> ExperimentResult: 57 | start_time = time.time() 58 | approximation_step_fidelity = cast("float", options.get("approximation_step_fidelity", 1.0)) 59 | approximation_steps = cast("int", options.get("approximation_steps", 1)) 60 | approximation_strategy = cast("str", options.get("approximation_strategy", "fidelity")) 61 | noise_effects = cast("str", options.get("noise_effects", "APD")) 62 | noise_probability = cast("float", options.get("noise_probability", 0.01)) 63 | amp_damping_probability = cast("float", options.get("amp_damping_probability", 0.02)) 64 | multi_qubit_gate_factor = cast("float", options.get("multi_qubit_gate_factor", 2)) 65 | seed = cast("int", options.get("seed_simulator", -1)) 66 | shots = cast("int", options.get("shots", 1024)) 67 | 68 | circ = load(qc) 69 | sim = ddsim.StochasticNoiseSimulator( 70 | circ=circ, 71 | approximation_step_fidelity=approximation_step_fidelity, 72 | approximation_steps=approximation_steps, 73 | approximation_strategy=approximation_strategy, 74 | seed=seed, 75 | noise_effects=noise_effects, 76 | noise_probability=noise_probability, 77 | amp_damping_probability=amp_damping_probability, 78 | multi_qubit_gate_factor=multi_qubit_gate_factor, 79 | ) 80 | 81 | counts = sim.simulate(shots=shots) 82 | end_time = time.time() 83 | 84 | data = ExperimentResultData( 85 | counts={hex(int(result, 2)): count for result, count in counts.items()}, 86 | statevector=None, 87 | time_taken=end_time - start_time, 88 | ) 89 | 90 | return ExperimentResult( 91 | shots=shots, 92 | success=True, 93 | status="DONE", 94 | seed=seed, 95 | data=data, 96 | metadata=qc.metadata, 97 | header=DDSIMHeader(qc), 98 | ) 99 | -------------------------------------------------------------------------------- /src/mqt/ddsim/unitarysimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | """Backend for DDSIM Unitary Simulator.""" 10 | 11 | from __future__ import annotations 12 | 13 | import time 14 | from typing import TYPE_CHECKING, Any 15 | 16 | import numpy as np 17 | from mqt.core import load 18 | from qiskit import QiskitError 19 | from qiskit.providers import Options 20 | from qiskit.result.models import ExperimentResult, ExperimentResultData 21 | from qiskit.transpiler import Target 22 | 23 | from .header import DDSIMHeader 24 | from .pyddsim import ConstructionMode, UnitarySimulator 25 | from .qasmsimulator import QasmSimulatorBackend 26 | from .target import DDSIMTargetBuilder 27 | 28 | if TYPE_CHECKING: 29 | from collections.abc import Sequence 30 | 31 | from qiskit import QuantumCircuit 32 | 33 | 34 | class UnitarySimulatorBackend(QasmSimulatorBackend): 35 | """Decision diagram-based unitary simulator.""" 36 | 37 | _US_TARGET = Target( 38 | description="MQT DDSIM Unitary Simulator Target", 39 | num_qubits=15, # corresponds to 16GiB memory for storing the full matrix 40 | ) 41 | 42 | @staticmethod 43 | def _add_operations_to_target(target: Target) -> None: 44 | DDSIMTargetBuilder.add_0q_gates(target) 45 | DDSIMTargetBuilder.add_1q_gates(target) 46 | DDSIMTargetBuilder.add_2q_gates(target) 47 | DDSIMTargetBuilder.add_3q_gates(target) 48 | DDSIMTargetBuilder.add_multi_qubit_gates(target) 49 | DDSIMTargetBuilder.add_barrier(target) 50 | 51 | def __init__(self) -> None: 52 | """Constructor for the DDSIM unitary simulator backend.""" 53 | super().__init__(name="unitary_simulator", description="MQT DDSIM Unitary Simulator") 54 | 55 | @classmethod 56 | def _default_options(cls) -> Options: 57 | return Options(shots=1, mode="recursive", parameter_binds=None) 58 | 59 | @property 60 | def target(self) -> Target: 61 | """Return the target of the backend.""" 62 | return self._US_TARGET 63 | 64 | @classmethod 65 | def _run_experiment(cls, qc: QuantumCircuit, **options: Any) -> ExperimentResult: 66 | start_time = time.time() 67 | seed = options.get("seed", -1) 68 | mode = options.get("mode", "recursive") 69 | 70 | if mode == "sequential": 71 | construction_mode = ConstructionMode.sequential 72 | elif mode == "recursive": 73 | construction_mode = ConstructionMode.recursive 74 | else: 75 | msg = ( 76 | f"Construction mode {mode} not supported by DDSIM unitary simulator. Available modes are " 77 | "'recursive' and 'sequential'" 78 | ) 79 | raise QiskitError(msg) 80 | 81 | circuit = load(qc) 82 | sim = UnitarySimulator(circuit, seed=seed, mode=construction_mode) 83 | sim.construct() 84 | # Extract resulting matrix from final DD and write data 85 | dd = sim.get_constructed_dd() 86 | mat = dd.get_matrix(sim.get_number_of_qubits()) 87 | unitary = np.array(mat, copy=False) 88 | end_time = time.time() 89 | 90 | data = ExperimentResultData( 91 | unitary=unitary, 92 | construction_time=sim.get_construction_time(), 93 | max_dd_nodes=sim.get_max_node_count(), 94 | dd_nodes=sim.get_final_node_count(), 95 | time_taken=end_time - start_time, 96 | ) 97 | 98 | return ExperimentResult( 99 | shots=1, 100 | success=True, 101 | status="DONE", 102 | seed=seed, 103 | data=data, 104 | metadata=qc.metadata, 105 | header=DDSIMHeader(qc), 106 | ) 107 | 108 | def _validate(self, quantum_circuits: Sequence[QuantumCircuit]) -> None: 109 | """Semantic validations of the quantum circuits which cannot be done via schemas. 110 | 111 | 1. No shots 112 | 2. No measurements in the middle. 113 | """ 114 | for qc in quantum_circuits: 115 | name = qc.name 116 | n_qubits = qc.num_qubits 117 | max_qubits = self.target.num_qubits 118 | 119 | if n_qubits > max_qubits: 120 | msg = f"Number of qubits {n_qubits} is greater than maximum ({max_qubits}) for '{self.name}'." 121 | raise QiskitError(msg) 122 | 123 | if qc.metadata is not None and "shots" in qc.metadata and qc.metadata["shots"] != 1: 124 | qc.metadata["shots"] = 1 125 | 126 | for instruction in qc.data: 127 | operation = instruction.operation 128 | if operation.name in {"measure", "reset"}: 129 | msg = f"Unsupported '{self.name}' instruction '{operation.name}' in circuit '{name}'." 130 | raise QiskitError(msg) 131 | -------------------------------------------------------------------------------- /src/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | if(APPLE) 10 | set(BASEPOINT @loader_path) 11 | else() 12 | set(BASEPOINT $ORIGIN) 13 | endif() 14 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 15 | set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) 16 | list( 17 | APPEND 18 | CMAKE_INSTALL_RPATH 19 | ${BASEPOINT} 20 | ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR} 21 | ${BASEPOINT}/../core/${CMAKE_INSTALL_LIBDIR} 22 | ${BASEPOINT}/../core/lib 23 | ${BASEPOINT}/../core/lib64 24 | ${BASEPOINT}/../../core/${CMAKE_INSTALL_LIBDIR} 25 | ${BASEPOINT}/../../core/lib 26 | ${BASEPOINT}/../../core/lib64) 27 | 28 | pybind11_add_module( 29 | pyddsim 30 | # Prefer thin LTO if available 31 | THIN_LTO 32 | # Optimize the bindings for size 33 | OPT_SIZE 34 | # Source code goes here 35 | bindings.cpp) 36 | target_link_libraries(pyddsim PRIVATE MQT::DDSim MQT::ProjectOptions MQT::ProjectWarnings 37 | pybind11_json) 38 | 39 | # Install directive for scikit-build-core 40 | install( 41 | TARGETS pyddsim 42 | DESTINATION . 43 | COMPONENT mqt-ddsim_Python) 44 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | package_add_test( 10 | mqt-ddsim-test 11 | MQT::DDSim 12 | test_circuit_sim.cpp 13 | test_shor_sim.cpp 14 | test_fast_shor_sim.cpp 15 | test_grover_sim.cpp 16 | test_hybridsim.cpp 17 | test_stoch_noise_sim.cpp 18 | test_det_noise_sim.cpp 19 | test_unitary_sim.cpp 20 | test_path_sim.cpp) 21 | 22 | target_link_libraries(mqt-ddsim-test PRIVATE MQT::CoreAlgorithms) 23 | -------------------------------------------------------------------------------- /test/python/hybridsimulator/test_hybrid_qasm_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | from qiskit import QuantumCircuit, QuantumRegister 14 | 15 | from mqt.ddsim.hybridqasmsimulator import HybridQasmSimulatorBackend 16 | 17 | 18 | class MQTHybridQasmSimulatorTest(unittest.TestCase): 19 | """Runs backend checks, the Basic qasm_simulator tests from Qiskit Terra, and some additional tests for the Hybrid DDSIM QasmSimulator.""" 20 | 21 | def setUp(self) -> None: 22 | self.backend = HybridQasmSimulatorBackend() 23 | self.circuit = QuantumCircuit.from_qasm_str("""OPENQASM 2.0; 24 | include "qelib1.inc"; 25 | qreg q[3]; 26 | qreg r[3]; 27 | h q; 28 | cx q, r; 29 | creg c[3]; 30 | creg d[3]; 31 | barrier q; 32 | measure q->c; 33 | measure r->d;""") 34 | self.circuit.name = "test" 35 | 36 | def test_qasm_simulator_single_shot(self) -> None: 37 | """Test single shot run.""" 38 | assert self.backend.run(self.circuit, shots=1).result().success 39 | 40 | def test_qasm_simulator(self) -> None: 41 | """Test data counts output for single circuit run against reference.""" 42 | shots = 1024 43 | result = self.backend.run(self.circuit, shots=shots).result() 44 | threshold = 0.04 * shots 45 | counts = result.get_counts() 46 | target = { 47 | "100 100": shots / 8, 48 | "011 011": shots / 8, 49 | "101 101": shots / 8, 50 | "111 111": shots / 8, 51 | "000 000": shots / 8, 52 | "010 010": shots / 8, 53 | "110 110": shots / 8, 54 | "001 001": shots / 8, 55 | } 56 | 57 | assert len(target) == len(counts) 58 | for key, value in target.items(): 59 | assert key in counts 60 | assert abs(value - counts[key]) < threshold 61 | 62 | def test_qasm_simulator_access(self) -> None: 63 | """Test data counts output for multiple quantum circuits in a single job.""" 64 | shots = 1024 65 | circuit_1 = QuantumCircuit(2, name="c1") 66 | circuit_2 = QuantumCircuit(2, name="c2") 67 | circuit_2.x(0) 68 | circuit_2.x(1) 69 | 70 | result = self.backend.run([circuit_1, circuit_2], shots=shots).result() 71 | assert result.success 72 | 73 | counts_1 = result.get_counts(circuit_1.name) 74 | counts_2 = result.get_counts(circuit_2.name) 75 | 76 | assert counts_1 == {"0": shots} 77 | assert counts_2 == {"11": shots} 78 | 79 | def test_dd_mode_simulation(self) -> None: 80 | """Test running a single circuit.""" 81 | q = QuantumRegister(4) 82 | circ = QuantumCircuit(q) 83 | circ.h(q) 84 | circ.cz(3, 1) 85 | circ.cz(2, 0) 86 | circ.measure_all(inplace=True) 87 | print(circ.draw(fold=-1)) 88 | self.circuit = circ 89 | result = self.backend.run(self.circuit, mode="dd").result() 90 | assert result.success 91 | 92 | def test_amplitude_mode_simulation(self) -> None: 93 | """Test running a single circuit.""" 94 | q = QuantumRegister(4) 95 | circ = QuantumCircuit(q) 96 | circ.h(q) 97 | circ.cz(3, 1) 98 | circ.cz(2, 0) 99 | circ.measure_all(inplace=True) 100 | print(circ.draw(fold=-1)) 101 | self.circuit = circ 102 | result = self.backend.run(self.circuit, mode="amplitude").result() 103 | assert result.success 104 | -------------------------------------------------------------------------------- /test/python/hybridsimulator/test_hybrid_standalone_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | from mqt.core.ir import QuantumComputation 14 | 15 | from mqt.ddsim import HybridCircuitSimulator, HybridMode 16 | 17 | 18 | class MQTStandaloneHybridSimulatorTest(unittest.TestCase): 19 | def setUp(self) -> None: 20 | circ = QuantumComputation(4) 21 | for i in range(4): 22 | circ.h(i) 23 | circ.cz(3, 1) 24 | circ.cz(2, 0) 25 | circ.measure_all() 26 | self.circuit = circ 27 | self.non_zeros_in_matrix = 16 28 | 29 | def test_standalone_amplitude_mode(self) -> None: 30 | sim = HybridCircuitSimulator(self.circuit, mode=HybridMode.amplitude) 31 | result = sim.simulate(2048) 32 | assert len(result.keys()) == self.non_zeros_in_matrix 33 | 34 | def test_standalone_amplitude_mode_with_seed(self) -> None: 35 | sim = HybridCircuitSimulator(self.circuit, seed=1337, mode=HybridMode.amplitude) 36 | result = sim.simulate(2048) 37 | assert len(result.keys()) == self.non_zeros_in_matrix 38 | 39 | def test_standalone_dd_mode(self) -> None: 40 | sim = HybridCircuitSimulator(self.circuit, mode=HybridMode.DD) 41 | result = sim.simulate(2048) 42 | assert len(result.keys()) == self.non_zeros_in_matrix 43 | 44 | def test_standalone_dd_mode_with_seed(self) -> None: 45 | sim = HybridCircuitSimulator(self.circuit, seed=1337, mode=HybridMode.DD) 46 | result = sim.simulate(2048) 47 | assert len(result.keys()) == self.non_zeros_in_matrix 48 | -------------------------------------------------------------------------------- /test/python/hybridsimulator/test_hybrid_statevector_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import math 12 | import unittest 13 | 14 | from qiskit import QuantumCircuit, QuantumRegister 15 | 16 | from mqt.ddsim.hybridstatevectorsimulator import HybridStatevectorSimulatorBackend 17 | 18 | 19 | class MQTHybridStatevectorSimulatorTest(unittest.TestCase): 20 | """Runs backend checks and some very basic functionality tests.""" 21 | 22 | def setUp(self) -> None: 23 | self.backend = HybridStatevectorSimulatorBackend() 24 | qr = QuantumRegister(2) 25 | self.q_circuit = QuantumCircuit(qr) 26 | self.q_circuit.h(qr[0]) 27 | self.q_circuit.cx(qr[0], qr[1]) 28 | 29 | def test_statevector_output(self) -> None: 30 | """Test final state vector for single circuit run.""" 31 | result = self.backend.run(self.q_circuit, shots=0).result() 32 | assert result.success 33 | actual = result.get_statevector() 34 | 35 | assert len(actual) == 2**2 # state vector has 2**(#qubits) length 36 | 37 | # state is 1/sqrt(2)|00> + 1/sqrt(2)|11>, up to a global phase 38 | assert math.isclose((abs(actual[0])) ** 2, 0.5, abs_tol=0.0001) 39 | assert actual[1] == 0 40 | assert actual[2] == 0 41 | assert math.isclose((abs(actual[3])) ** 2, 0.5, abs_tol=0.0001) 42 | -------------------------------------------------------------------------------- /test/python/noiseawaresimulator/test_noiseaware_deterministic_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import pytest 12 | from qiskit import QuantumCircuit, qasm2 13 | 14 | from mqt.ddsim.deterministicnoisesimulator import DeterministicNoiseSimulatorBackend 15 | 16 | 17 | @pytest.fixture 18 | def circuit() -> QuantumCircuit: 19 | """The circuit fixture for the tests in this file.""" 20 | circ = qasm2.loads( 21 | """OPENQASM 2.0; 22 | include "qelib1.inc"; 23 | qreg q[4]; 24 | creg c[4]; 25 | x q[0]; 26 | x q[1]; 27 | h q[3]; 28 | cx q[2], q[3]; 29 | t q[0]; 30 | t q[1]; 31 | t q[2]; 32 | tdg q[3]; 33 | cx q[0], q[1]; 34 | cx q[2], q[3]; 35 | cx q[3], q[0]; 36 | cx q[1], q[2]; 37 | cx q[0], q[1]; 38 | cx q[2], q[3]; 39 | tdg q[0]; 40 | tdg q[1]; 41 | tdg q[2]; 42 | t q[3]; 43 | cx q[0], q[1]; 44 | cx q[2], q[3]; 45 | s q[3]; 46 | cx q[3], q[0]; 47 | h q[3]; 48 | barrier q; 49 | measure q->c; 50 | """ 51 | ) 52 | circ.name = "adder_n4" 53 | return circ 54 | 55 | 56 | @pytest.fixture 57 | def backend() -> DeterministicNoiseSimulatorBackend: 58 | """The backend fixture for the tests in this file.""" 59 | return DeterministicNoiseSimulatorBackend() 60 | 61 | 62 | def test_no_noise(circuit: QuantumCircuit, backend: DeterministicNoiseSimulatorBackend) -> None: 63 | shots = 1024 64 | result = backend.run( 65 | circuit, 66 | shots=shots, 67 | noise_probability=0, 68 | noise_effects="", 69 | amp_damping_probability=0, 70 | multi_qubit_gate_factor=0, 71 | ).result() 72 | counts = result.get_counts() 73 | assert counts["1001"] == shots 74 | 75 | 76 | def test_default_config(circuit: QuantumCircuit, backend: DeterministicNoiseSimulatorBackend) -> None: 77 | tolerance = 100 78 | result = backend.run(circuit, shots=1000).result() 79 | counts = result.get_counts() 80 | assert abs(counts["0001"] - 173) < tolerance 81 | assert abs(counts["1001"] - 414) < tolerance 82 | 83 | 84 | def test_custom_config(circuit: QuantumCircuit) -> None: 85 | tolerance = 50 86 | for i in range(20): 87 | sim = DeterministicNoiseSimulatorBackend() 88 | result = sim.run( 89 | circuit, 90 | shots=1000, 91 | noise_effects="AP", 92 | noise_probability=0.001, 93 | amp_damping_probability=0.002, 94 | multi_qubit_gate_factor=2, 95 | simulator_seed=i, 96 | ).result() 97 | counts = result.get_counts() 98 | assert abs(counts["1001"] - 936) < tolerance 99 | -------------------------------------------------------------------------------- /test/python/noiseawaresimulator/test_noiseaware_stochastic_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import pytest 12 | from qiskit import QuantumCircuit, qasm2 13 | 14 | from mqt.ddsim.stochasticnoisesimulator import StochasticNoiseSimulatorBackend 15 | 16 | 17 | @pytest.fixture 18 | def circuit() -> QuantumCircuit: 19 | """The circuit fixture for the tests in this file.""" 20 | circ = qasm2.loads( 21 | """OPENQASM 2.0; 22 | include "qelib1.inc"; 23 | qreg q[4]; 24 | creg c[4]; 25 | x q[0]; 26 | x q[1]; 27 | h q[3]; 28 | cx q[2], q[3]; 29 | t q[0]; 30 | t q[1]; 31 | t q[2]; 32 | tdg q[3]; 33 | cx q[0], q[1]; 34 | cx q[2], q[3]; 35 | cx q[3], q[0]; 36 | cx q[1], q[2]; 37 | cx q[0], q[1]; 38 | cx q[2], q[3]; 39 | tdg q[0]; 40 | tdg q[1]; 41 | tdg q[2]; 42 | t q[3]; 43 | cx q[0], q[1]; 44 | cx q[2], q[3]; 45 | s q[3]; 46 | cx q[3], q[0]; 47 | h q[3]; 48 | barrier q; 49 | measure q->c; 50 | """ 51 | ) 52 | circ.name = "adder_n4" 53 | return circ 54 | 55 | 56 | @pytest.fixture 57 | def backend() -> StochasticNoiseSimulatorBackend: 58 | """The backend fixture for the tests in this file.""" 59 | return StochasticNoiseSimulatorBackend() 60 | 61 | 62 | def test_no_noise(circuit: QuantumCircuit, backend: StochasticNoiseSimulatorBackend) -> None: 63 | shots = 1024 64 | result = backend.run( 65 | circuit, 66 | shots=shots, 67 | noise_probability=0, 68 | noise_effects="", 69 | amp_damping_probability=0, 70 | multi_qubit_gate_factor=0, 71 | ).result() 72 | counts = result.get_counts() 73 | assert counts["1001"] == shots 74 | 75 | 76 | def test_def_config(circuit: QuantumCircuit, backend: StochasticNoiseSimulatorBackend) -> None: 77 | tolerance = 100 78 | result = backend.run( 79 | circuit, 80 | shots=1000, 81 | noise_probability=0.1, 82 | noise_effects="APD", 83 | amp_damping_probability=0.1, 84 | multi_qubit_gate_factor=2, 85 | ).result() 86 | counts = result.get_counts() 87 | assert abs(counts["0000"] - 211) < tolerance 88 | assert abs(counts["1000"] - 146) < tolerance 89 | -------------------------------------------------------------------------------- /test/python/simulator/ghz_03.qasm: -------------------------------------------------------------------------------- 1 | OPENQASM 2.0; 2 | include "qelib1.inc"; 3 | qreg q[3]; 4 | creg c[3]; 5 | h q[0]; 6 | cx q[0], q[1]; 7 | cx q[0], q[2]; 8 | barrier q; 9 | measure q->c; 10 | -------------------------------------------------------------------------------- /test/python/simulator/grover.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import locale 12 | import pathlib 13 | 14 | from qiskit import BasicAer 15 | from qiskit.algorithms import AmplificationProblem, Grover 16 | from qiskit.circuit.library import PhaseOracle 17 | 18 | from mqt.ddsim import DDSIMProvider 19 | 20 | input_3sat_instance = """ 21 | c example DIMACS-CNF 3-SAT 22 | p cnf 3 5 23 | -1 -2 -3 0 24 | 1 -2 3 0 25 | 1 2 -3 0 26 | 1 -2 -3 0 27 | -1 2 3 0 28 | """ 29 | with pathlib.Path("dimacs_file.txt").open("w", encoding=locale.getpreferredencoding(False)) as fd: 30 | fd.write(input_3sat_instance) 31 | 32 | print("pyddsim") 33 | backend = DDSIMProvider().get_backend("qasm_simulator") 34 | backend.set_options(shots=1024) 35 | 36 | oracle = PhaseOracle.from_dimacs_file("dimacs_file.txt") 37 | grover = Grover(quantum_instance=backend) 38 | 39 | problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) 40 | result = grover.amplify(problem) 41 | 42 | print(result.assignment) 43 | 44 | print("BasicAer") 45 | backend = BasicAer.get_backend("qasm_simulator") 46 | 47 | oracle = PhaseOracle.from_dimacs_file("dimacs_file.txt") 48 | grover = Grover(quantum_instance=backend) 49 | 50 | problem = AmplificationProblem(oracle, is_good_state=oracle.evaluate_bitstring) 51 | result = grover.amplify(problem) 52 | 53 | print(result.assignment) 54 | -------------------------------------------------------------------------------- /test/python/simulator/test_multi_registers_convention.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import math 12 | 13 | from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister 14 | from qiskit.quantum_info import Statevector, state_fidelity 15 | 16 | from mqt.ddsim.statevectorsimulator import StatevectorSimulatorBackend 17 | 18 | 19 | def test_circuit_multi() -> None: 20 | """Test circuit multi regs declared at start.""" 21 | qreg0 = QuantumRegister(2, "q0") 22 | creg0 = ClassicalRegister(2, "c0") 23 | 24 | qreg1 = QuantumRegister(2, "q1") 25 | creg1 = ClassicalRegister(2, "c1") 26 | 27 | circ = QuantumCircuit(qreg0, qreg1) 28 | circ.x(qreg0[1]) 29 | circ.x(qreg1[0]) 30 | 31 | meas = QuantumCircuit(qreg0, qreg1, creg0, creg1) 32 | meas.measure(qreg0, creg0) 33 | meas.measure(qreg1, creg1) 34 | 35 | qc = meas.compose(circ, front=True) 36 | 37 | backend_sim = StatevectorSimulatorBackend() 38 | 39 | result = backend_sim.run(qc).result() 40 | counts = result.get_counts(qc) 41 | 42 | target = {"01 10": 1024} 43 | 44 | result = backend_sim.run(circ).result() 45 | state = result.get_statevector() 46 | 47 | assert counts == target 48 | assert math.isclose(state_fidelity(Statevector.from_label("0110"), state), 1.0, abs_tol=0.000001) 49 | -------------------------------------------------------------------------------- /test/python/simulator/test_standalone_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import pathlib 12 | import unittest 13 | 14 | from mqt.core import load 15 | from mqt.core.ir import QuantumComputation 16 | 17 | from mqt.ddsim import CircuitSimulator 18 | 19 | 20 | class MQTStandaloneSimulatorTests(unittest.TestCase): 21 | def setUp(self) -> None: 22 | self.nonzero_states_ghz = 2 23 | 24 | def test_truly_standalone(self) -> None: 25 | filename = str(pathlib.Path(__file__).with_name("ghz_03.qasm").absolute()) 26 | circ = load(filename) 27 | sim = CircuitSimulator(circ) 28 | result = sim.simulate(1000) 29 | print(result) 30 | assert len(result.keys()) == self.nonzero_states_ghz 31 | assert "000" in result 32 | assert "111" in result 33 | 34 | def test_standalone(self) -> None: 35 | circ = QuantumComputation(3) 36 | circ.h(0) 37 | circ.cx(0, 1) 38 | circ.cx(0, 2) 39 | 40 | sim = CircuitSimulator(circ) 41 | result = sim.simulate(1000) 42 | assert len(result.keys()) == self.nonzero_states_ghz 43 | assert "000" in result 44 | assert "111" in result 45 | 46 | def test_standalone_with_seed(self) -> None: 47 | circ = QuantumComputation(3) 48 | circ.h(0) 49 | circ.cx(0, 1) 50 | circ.cx(0, 2) 51 | 52 | sim = CircuitSimulator(circ, seed=1337) 53 | result = sim.simulate(1000) 54 | assert len(result.keys()) == self.nonzero_states_ghz 55 | assert "000" in result 56 | assert "111" in result 57 | 58 | def test_standalone_simple_approximation(self) -> None: 59 | import numpy as np 60 | 61 | # creates a state with <2% probability of measuring |1x> 62 | circ = QuantumComputation(2) 63 | circ.h(0) 64 | circ.cry(np.pi / 8, 0, 1) 65 | circ.i(0) 66 | circ.i(0) 67 | 68 | # create a simulator that approximates once and by at most 2% 69 | sim = CircuitSimulator(circ, approximation_step_fidelity=0.98, approximation_steps=1) 70 | result = sim.simulate(4096) 71 | 72 | # the result should always be 0 73 | assert len(result.keys()) == self.nonzero_states_ghz 74 | assert "00" in result 75 | assert "01" in result 76 | 77 | @staticmethod 78 | def test_native_two_qubit_gates() -> None: 79 | qc = QuantumComputation(2) 80 | qc.dcx(0, 1) 81 | qc.ecr(0, 1) 82 | qc.rxx(0.5, 0, 1) 83 | qc.rzz(0.5, 0, 1) 84 | qc.ryy(0.5, 0, 1) 85 | qc.rzx(0.5, 0, 1) 86 | qc.xx_minus_yy(0.5, 0.25, 0, 1) 87 | qc.xx_plus_yy(0.5, 0.25, 0, 1) 88 | print(qc) 89 | print(qc.global_phase) 90 | sim = CircuitSimulator(qc) 91 | result = sim.simulate(1000) 92 | print(result) 93 | 94 | @staticmethod 95 | def test_expectation_value_local_operators() -> None: 96 | import numpy as np 97 | 98 | max_qubits = 3 99 | for qubits in range(1, max_qubits + 1): 100 | qc = QuantumComputation(qubits) 101 | sim = CircuitSimulator(qc) 102 | for i in range(qubits): 103 | x_observable = QuantumComputation(qubits) 104 | x_observable.x(i) 105 | assert sim.expectation_value(x_observable) == 0 106 | z_observable = QuantumComputation(qubits) 107 | z_observable.z(i) 108 | assert sim.expectation_value(z_observable) == 1 109 | h_observable = QuantumComputation(qubits) 110 | h_observable.h(i) 111 | assert np.allclose(sim.expectation_value(h_observable), 1 / np.sqrt(2)) 112 | 113 | @staticmethod 114 | def test_expectation_value_global_operators() -> None: 115 | import numpy as np 116 | 117 | max_qubits = 3 118 | for qubits in range(1, max_qubits + 1): 119 | qc = QuantumComputation(qubits) 120 | sim = CircuitSimulator(qc) 121 | x_observable = QuantumComputation(qubits) 122 | z_observable = QuantumComputation(qubits) 123 | h_observable = QuantumComputation(qubits) 124 | for i in range(qubits): 125 | x_observable.x(i) 126 | z_observable.z(i) 127 | h_observable.h(i) 128 | assert sim.expectation_value(x_observable) == 0 129 | assert sim.expectation_value(z_observable) == 1 130 | assert np.allclose(sim.expectation_value(h_observable), (1 / np.sqrt(2)) ** qubits) 131 | -------------------------------------------------------------------------------- /test/python/simulator/test_statevector_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import math 12 | import unittest 13 | 14 | from qiskit import QuantumCircuit, QuantumRegister 15 | 16 | from mqt.ddsim.statevectorsimulator import StatevectorSimulatorBackend 17 | 18 | 19 | class MQTStatevectorSimulatorTest(unittest.TestCase): 20 | def setUp(self) -> None: 21 | self.backend = StatevectorSimulatorBackend() 22 | qr = QuantumRegister(2) 23 | self.q_circuit = QuantumCircuit(qr) 24 | self.q_circuit.h(qr[0]) 25 | self.q_circuit.cx(qr[0], qr[1]) 26 | 27 | def test_statevector_output(self) -> None: 28 | """Test final state vector for single circuit run.""" 29 | result = self.backend.run(self.q_circuit).result() 30 | assert result.success 31 | actual = result.get_statevector() 32 | 33 | # state is 1/sqrt(2)|00> + 1/sqrt(2)|11>, up to a global phase 34 | assert math.isclose((abs(actual[0])) ** 2, 0.5, abs_tol=0.0001) 35 | assert actual[1] == 0 36 | assert actual[2] == 0 37 | assert math.isclose((abs(actual[3])) ** 2, 0.5, abs_tol=0.0001) 38 | -------------------------------------------------------------------------------- /test/python/taskbasedsimulator/test_path_sim_qasm_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | from qiskit import QuantumCircuit 14 | 15 | from mqt.ddsim.pathqasmsimulator import PathQasmSimulatorBackend 16 | 17 | 18 | class MQTQasmSimulatorTest(unittest.TestCase): 19 | """Runs backend checks and the Basic qasm_simulator tests from Qiskit Terra.""" 20 | 21 | def setUp(self) -> None: 22 | self.backend = PathQasmSimulatorBackend() 23 | self.circuit = QuantumCircuit.from_qasm_str("""OPENQASM 2.0; 24 | include "qelib1.inc"; 25 | qreg q[3]; 26 | qreg r[3]; 27 | h q; 28 | cx q, r; 29 | creg c[3]; 30 | creg d[3]; 31 | barrier q; 32 | measure q->c; 33 | measure r->d;""") 34 | self.circuit.name = "test" 35 | 36 | def test_qasm_simulator_single_shot(self) -> None: 37 | """Test single shot run.""" 38 | assert self.backend.run(self.circuit, shots=1).result().success 39 | 40 | def test_qasm_simulator(self) -> None: 41 | """Test data counts output for single circuit run against reference.""" 42 | shots = 8192 43 | result = self.backend.run(self.circuit, shots=shots).result() 44 | threshold = 0.04 * shots 45 | counts = result.get_counts() 46 | target = { 47 | "100 100": shots / 8, 48 | "011 011": shots / 8, 49 | "101 101": shots / 8, 50 | "111 111": shots / 8, 51 | "000 000": shots / 8, 52 | "010 010": shots / 8, 53 | "110 110": shots / 8, 54 | "001 001": shots / 8, 55 | } 56 | 57 | assert len(target) == len(counts) 58 | for key, value in target.items(): 59 | assert key in counts 60 | assert abs(value - counts[key]) < threshold 61 | 62 | def test_qasm_simulator_access(self) -> None: 63 | """Test data counts output for multiple quantum circuits in a single job.""" 64 | shots = 8192 65 | circuit_1 = QuantumCircuit(2, name="c1") 66 | circuit_2 = QuantumCircuit(2, name="c2") 67 | circuit_2.x(0) 68 | circuit_2.x(1) 69 | 70 | result = self.backend.run([circuit_1, circuit_2], shots=shots).result() 71 | assert result.success 72 | 73 | counts_1 = result.get_counts(circuit_1.name) 74 | counts_2 = result.get_counts(circuit_2.name) 75 | 76 | assert counts_1 == {"0": shots} 77 | assert counts_2 == {"11": shots} 78 | 79 | def test_qasm_simulator_pairwise(self) -> None: 80 | """Test data counts output for single circuit run against reference.""" 81 | shots = 8192 82 | result = self.backend.run(self.circuit, shots=shots, mode="pairwise_recursive").result() 83 | threshold = 0.04 * shots 84 | counts = result.get_counts() 85 | target = { 86 | "100 100": shots / 8, 87 | "011 011": shots / 8, 88 | "101 101": shots / 8, 89 | "111 111": shots / 8, 90 | "000 000": shots / 8, 91 | "010 010": shots / 8, 92 | "110 110": shots / 8, 93 | "001 001": shots / 8, 94 | } 95 | 96 | assert len(target) == len(counts) 97 | for key, value in target.items(): 98 | assert key in counts 99 | assert abs(value - counts[key]) < threshold 100 | 101 | def test_qasm_simulator_bracket(self) -> None: 102 | """Test data counts output for single circuit run against reference.""" 103 | shots = 8192 104 | result = self.backend.run(self.circuit, shots=shots, mode="bracket").result() 105 | 106 | print(result) 107 | threshold = 0.04 * shots 108 | counts = result.get_counts() 109 | target = { 110 | "100 100": shots / 8, 111 | "011 011": shots / 8, 112 | "101 101": shots / 8, 113 | "111 111": shots / 8, 114 | "000 000": shots / 8, 115 | "010 010": shots / 8, 116 | "110 110": shots / 8, 117 | "001 001": shots / 8, 118 | } 119 | 120 | assert len(target) == len(counts) 121 | for key, value in target.items(): 122 | assert key in counts 123 | assert abs(value - counts[key]) < threshold 124 | 125 | def test_qasm_simulator_alternating(self) -> None: 126 | """Test data counts output for single circuit run against reference.""" 127 | shots = 8192 128 | result = self.backend.run(self.circuit, shots=shots, mode="alternating").result() 129 | 130 | print(result) 131 | threshold = 0.04 * shots 132 | counts = result.get_counts() 133 | target = { 134 | "100 100": shots / 8, 135 | "011 011": shots / 8, 136 | "101 101": shots / 8, 137 | "111 111": shots / 8, 138 | "000 000": shots / 8, 139 | "010 010": shots / 8, 140 | "110 110": shots / 8, 141 | "001 001": shots / 8, 142 | } 143 | 144 | assert len(target) == len(counts) 145 | for key, value in target.items(): 146 | assert key in counts 147 | assert abs(value - counts[key]) < threshold 148 | -------------------------------------------------------------------------------- /test/python/taskbasedsimulator/test_path_sim_standalone_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | from mqt.core.ir import QuantumComputation 14 | 15 | from mqt.ddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode 16 | 17 | 18 | class MQTStandaloneSimulatorTests(unittest.TestCase): 19 | def setUp(self) -> None: 20 | self.nonzero_states_ghz = 2 21 | 22 | def test_standalone(self) -> None: 23 | circ = QuantumComputation(3) 24 | circ.h(0) 25 | circ.cx(0, 1) 26 | circ.cx(0, 2) 27 | 28 | sim = PathCircuitSimulator(circ) 29 | result = sim.simulate(1000) 30 | assert len(result.keys()) == self.nonzero_states_ghz 31 | assert "000" in result 32 | assert "111" in result 33 | 34 | def test_standalone_with_config(self) -> None: 35 | circ = QuantumComputation(3) 36 | circ.h(0) 37 | circ.cx(0, 1) 38 | circ.cx(0, 2) 39 | 40 | sim = PathCircuitSimulator(circ, PathSimulatorConfiguration()) 41 | result = sim.simulate(1000) 42 | assert len(result.keys()) == self.nonzero_states_ghz 43 | assert "000" in result 44 | assert "111" in result 45 | 46 | def test_standalone_with_seed(self) -> None: 47 | circ = QuantumComputation(3) 48 | circ.h(0) 49 | circ.cx(0, 1) 50 | circ.cx(0, 2) 51 | 52 | sim = PathCircuitSimulator(circ, seed=1337) 53 | result = sim.simulate(1000) 54 | assert len(result.keys()) == self.nonzero_states_ghz 55 | assert "000" in result 56 | assert "111" in result 57 | 58 | def test_standalone_individual_objects(self) -> None: 59 | circ = QuantumComputation(3) 60 | circ.h(0) 61 | circ.cx(0, 1) 62 | circ.cx(0, 2) 63 | 64 | sim = PathCircuitSimulator(circ, seed=0, mode=PathSimulatorMode.bracket, bracket_size=2) 65 | result = sim.simulate(1000) 66 | assert len(result.keys()) == self.nonzero_states_ghz 67 | assert "000" in result 68 | assert "111" in result 69 | 70 | def test_standalone_pairwise_only(self) -> None: 71 | circ = QuantumComputation(3) 72 | circ.h(0) 73 | circ.cx(0, 1) 74 | circ.cx(0, 2) 75 | 76 | sim = PathCircuitSimulator( 77 | circ, 78 | seed=1, 79 | mode=PathSimulatorMode.pairwise_recursive, 80 | bracket_size=2, 81 | ) 82 | result = sim.simulate(1000) 83 | assert len(result.keys()) == self.nonzero_states_ghz 84 | assert "000" in result 85 | assert "111" in result 86 | 87 | def test_standalone_gatecost_only(self) -> None: 88 | circ = QuantumComputation(3) 89 | circ.h(0) 90 | circ.cx(0, 1) 91 | circ.cx(0, 2) 92 | 93 | sim = PathCircuitSimulator( 94 | circ, 95 | mode=PathSimulatorMode.gate_cost, 96 | starting_point=2, 97 | gate_cost=[1, 1], 98 | ) 99 | result = sim.simulate(1000) 100 | assert len(result.keys()) == self.nonzero_states_ghz 101 | assert "000" in result 102 | assert "111" in result 103 | -------------------------------------------------------------------------------- /test/python/taskbasedsimulator/test_path_sim_statevector_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import math 12 | import unittest 13 | 14 | from qiskit import QuantumCircuit, QuantumRegister 15 | 16 | from mqt.ddsim.pathstatevectorsimulator import PathStatevectorSimulatorBackend 17 | 18 | 19 | class MQTStatevectorSimulatorTest(unittest.TestCase): 20 | def setUp(self) -> None: 21 | self.backend = PathStatevectorSimulatorBackend() 22 | qr = QuantumRegister(2) 23 | self.q_circuit = QuantumCircuit(qr) 24 | self.q_circuit.h(qr[0]) 25 | self.q_circuit.cx(qr[0], qr[1]) 26 | 27 | def test_statevector_output(self) -> None: 28 | """Test final state vector for single circuit run.""" 29 | result = self.backend.run(self.q_circuit).result() 30 | assert result.success 31 | actual = result.get_statevector() 32 | 33 | # state is 1/sqrt(2)|00> + 1/sqrt(2)|11>, up to a global phase 34 | assert math.isclose((abs(actual[0])) ** 2, 0.5, abs_tol=0.0001) 35 | assert actual[1] == 0 36 | assert actual[2] == 0 37 | assert math.isclose((abs(actual[3])) ** 2, 0.5, abs_tol=0.0001) 38 | 39 | def test_statevector_output_pairwise(self) -> None: 40 | """Test final state vector for single circuit run.""" 41 | mode = "pairwise_recursive" 42 | result = self.backend.run(self.q_circuit, mode=mode).result() 43 | assert result.success 44 | actual = result.get_statevector() 45 | 46 | # state is 1/sqrt(2)|00> + 1/sqrt(2)|11>, up to a global phase 47 | assert math.isclose((abs(actual[0])) ** 2, 0.5, abs_tol=0.0001) 48 | assert actual[1] == 0 49 | assert actual[2] == 0 50 | assert math.isclose((abs(actual[0])) ** 2, 0.5, abs_tol=0.0001) 51 | -------------------------------------------------------------------------------- /test/python/test_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | from mqt.ddsim import DDSIMProvider 14 | 15 | 16 | class DDSIMProviderTestCase(unittest.TestCase): 17 | def setUp(self) -> None: 18 | self.provider = DDSIMProvider() 19 | self.backend_name = "qasm_simulator" 20 | 21 | def test_backends(self) -> None: 22 | """Test the provider has backends.""" 23 | backends = self.provider.backends() 24 | assert len(backends) > 0 25 | 26 | def test_get_backend(self) -> None: 27 | """Test getting a backend from the provider.""" 28 | backend = self.provider.get_backend(name=self.backend_name) 29 | assert backend.name == self.backend_name 30 | -------------------------------------------------------------------------------- /test/python/test_target.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import contextlib 12 | 13 | import numpy as np 14 | import pytest 15 | from qiskit import QuantumCircuit, transpile 16 | from qiskit.transpiler import Target 17 | 18 | from mqt.ddsim.target import DDSIMTargetBuilder 19 | 20 | 21 | @pytest.fixture 22 | def target() -> Target: 23 | """The target fixture for the tests in this file.""" 24 | target = Target(num_qubits=8) 25 | DDSIMTargetBuilder.add_0q_gates(target) 26 | DDSIMTargetBuilder.add_1q_gates(target) 27 | DDSIMTargetBuilder.add_2q_gates(target) 28 | DDSIMTargetBuilder.add_3q_gates(target) 29 | DDSIMTargetBuilder.add_multi_qubit_gates(target) 30 | DDSIMTargetBuilder.add_non_unitary_operations(target) 31 | DDSIMTargetBuilder.add_barrier(target) 32 | return target 33 | 34 | 35 | @pytest.mark.parametrize("gate", ["x", "y", "z", "h", "s", "sdg", "t", "tdg"]) 36 | def test_transpilation_preserves_1q_0p_target_gates(target: Target, gate: str) -> None: 37 | """Test that transpilation does not change single-qubit gates without parameters that are already in the target.""" 38 | qc = QuantumCircuit(1) 39 | getattr(qc, gate)(0) 40 | qc_transpiled = transpile(qc, target=target) 41 | assert len(qc_transpiled.data) == 1 42 | assert qc_transpiled.data[0].operation.name == gate 43 | 44 | 45 | @pytest.mark.parametrize("gate", ["rx", "ry", "rz", "p"]) 46 | def test_transpile_preserves_1q_1p_target_gates(target: Target, gate: str) -> None: 47 | """Test that transpilation does not change single-qubit gates with one parameter that are already in the target.""" 48 | qc = QuantumCircuit(1) 49 | getattr(qc, gate)(np.pi, 0) 50 | qc_transpiled = transpile(qc, target=target) 51 | assert len(qc_transpiled.data) == 1 52 | assert qc_transpiled.data[0].operation.name == gate 53 | 54 | 55 | @pytest.mark.parametrize("gate", ["cx", "cy", "cz", "ch", "cs", "csdg", "csx", "swap", "iswap", "dcx", "ecr"]) 56 | def test_transpilation_preserves_2q_0p_target_gates(target: Target, gate: str) -> None: 57 | """Test that transpilation does not change two-qubit gates without parameters that are already in the target.""" 58 | qc = QuantumCircuit(2) 59 | with contextlib.suppress(AttributeError): 60 | getattr(qc, gate)(0, 1) 61 | qc_transpiled = transpile(qc, target=target) 62 | print(qc_transpiled) 63 | num_gates = len(qc_transpiled.data) 64 | assert num_gates <= 1 65 | assert num_gates == 0 or qc_transpiled.data[0].operation.name == gate 66 | 67 | 68 | @pytest.mark.parametrize("gate", ["rxx", "ryy", "rzz", "rzx", "cp", "crx", "cry", "crz"]) 69 | def test_transpilation_preserves_2q_1p_target_gates(target: Target, gate: str) -> None: 70 | """Test that transpilation does not change two-qubit gates with one parameter that are already in the target.""" 71 | qc = QuantumCircuit(2) 72 | getattr(qc, gate)(np.pi / 2, 0, 1) 73 | qc_transpiled = transpile(qc, target=target) 74 | assert len(qc_transpiled.data) == 1 75 | assert qc_transpiled.data[0].operation.name == gate 76 | 77 | 78 | @pytest.mark.parametrize("gate", ["ccx", "ccz", "cswap"]) 79 | def test_transpilation_preserves_3q_target_gates(target: Target, gate: str) -> None: 80 | """Test that transpilation does not change three-qubit gates that are already in the target.""" 81 | qc = QuantumCircuit(3) 82 | with contextlib.suppress(AttributeError): 83 | getattr(qc, gate)(0, 1, 2) 84 | qc_transpiled = transpile(qc, target=target) 85 | assert len(qc_transpiled.data) == 1 86 | assert qc_transpiled.data[0].operation.name == gate 87 | 88 | 89 | @pytest.mark.parametrize("num_controls", list(range(3, 6))) 90 | @pytest.mark.parametrize("mode", ["noancilla", "recursion", "v-chain"]) 91 | def test_transpilation_preserves_mcx_target_gates(target: Target, num_controls: int, mode: str) -> None: 92 | """Test that transpilation does not change MCX gates that are already in the target.""" 93 | nqubits = num_controls + 1 94 | nancillas = 0 95 | if mode == "recursion": 96 | nancillas = 1 97 | elif mode == "v-chain": 98 | nancillas = max(0, nqubits - 2) 99 | qc = QuantumCircuit(nqubits + nancillas) 100 | controls = list(range(1, nqubits)) 101 | qc.mcx(controls, 0, ancilla_qubits=list(range(nqubits, nqubits + nancillas)), mode=mode) 102 | qc_transpiled = transpile(qc, target=target) 103 | assert len(qc_transpiled.data) == 1 104 | if mode == "noancilla": 105 | assert qc_transpiled.data[0].operation.name in {"mcx_gray", "mcx"} 106 | elif mode == "recursion": 107 | assert qc_transpiled.data[0].operation.name == "mcx_recursive" 108 | elif mode == "v-chain": 109 | assert qc_transpiled.data[0].operation.name == "mcx_vchain" 110 | 111 | 112 | @pytest.mark.parametrize("num_controls", list(range(3, 6))) 113 | def test_transpilation_preserves_mcp_target_gates(target: Target, num_controls: int) -> None: 114 | """Test that transpilation does not change MCP gates that are already in the target.""" 115 | nqubits = num_controls + 1 116 | qc = QuantumCircuit(nqubits) 117 | controls = list(range(1, nqubits)) 118 | qc.mcp(np.pi, controls, 0) 119 | qc_transpiled = transpile(qc, target=target) 120 | assert len(qc_transpiled.data) == 1 121 | assert qc_transpiled.data[0].operation.name == "mcphase" 122 | -------------------------------------------------------------------------------- /test/python/unitarysimulator/test_standalone_unitary_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | import numpy as np 14 | from mqt.core.ir import QuantumComputation 15 | 16 | from mqt.ddsim import ConstructionMode, UnitarySimulator 17 | 18 | 19 | class MQTStandaloneUnitarySimulatorTests(unittest.TestCase): 20 | def setUp(self) -> None: 21 | circ = QuantumComputation(3) 22 | circ.h(0) 23 | circ.cx(0, 1) 24 | circ.cx(0, 2) 25 | self.circuit = circ 26 | self.non_zeros_in_bell_circuit = 16 27 | 28 | def test_standalone_sequential_mode(self) -> None: 29 | sim = UnitarySimulator(self.circuit, mode=ConstructionMode.sequential) 30 | sim.construct() 31 | 32 | unitary = np.array(sim.get_constructed_dd().get_matrix(sim.get_number_of_qubits()), copy=False) 33 | print(unitary) 34 | assert np.count_nonzero(unitary) == self.non_zeros_in_bell_circuit 35 | 36 | def test_standalone_sequential_mode_with_seed(self) -> None: 37 | sim = UnitarySimulator(self.circuit, seed=1337, mode=ConstructionMode.sequential) 38 | sim.construct() 39 | 40 | unitary = np.array(sim.get_constructed_dd().get_matrix(sim.get_number_of_qubits()), copy=False) 41 | print(unitary) 42 | assert np.count_nonzero(unitary) == self.non_zeros_in_bell_circuit 43 | 44 | def test_standalone_recursive_mode(self) -> None: 45 | sim = UnitarySimulator(self.circuit, mode=ConstructionMode.recursive) 46 | sim.construct() 47 | 48 | unitary = np.array(sim.get_constructed_dd().get_matrix(sim.get_number_of_qubits()), copy=False) 49 | print(unitary) 50 | assert np.count_nonzero(unitary) == self.non_zeros_in_bell_circuit 51 | 52 | def test_standalone_recursive_mode_with_seed(self) -> None: 53 | sim = UnitarySimulator(self.circuit, seed=1337, mode=ConstructionMode.recursive) 54 | sim.construct() 55 | 56 | unitary = np.array(sim.get_constructed_dd().get_matrix(sim.get_number_of_qubits()), copy=False) 57 | print(unitary) 58 | assert np.count_nonzero(unitary) == self.non_zeros_in_bell_circuit 59 | -------------------------------------------------------------------------------- /test/python/unitarysimulator/test_unitary_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 - 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 | from __future__ import annotations 10 | 11 | import unittest 12 | 13 | import numpy as np 14 | from qiskit import QuantumCircuit 15 | 16 | from mqt.ddsim.unitarysimulator import UnitarySimulatorBackend 17 | 18 | 19 | class MQTUnitarySimulatorTest(unittest.TestCase): 20 | def setUp(self) -> None: 21 | self.backend = UnitarySimulatorBackend() 22 | circ = QuantumCircuit(3) 23 | circ.h(0) 24 | circ.cx(0, 1) 25 | circ.cx(0, 2) 26 | self.circuit = circ 27 | self.circuit.name = "test" 28 | self.non_zeros_in_bell_circuit = 16 29 | 30 | def test_unitary_simulator_sequential_mode(self) -> None: 31 | result = self.backend.run(self.circuit, mode="sequential").result() 32 | assert result.success 33 | print(result.get_unitary()) 34 | assert np.count_nonzero(result.get_unitary()) == self.non_zeros_in_bell_circuit 35 | 36 | def test_unitary_simulator_recursive_mode(self) -> None: 37 | result = self.backend.run(self.circuit, mode="recursive").result() 38 | assert result.success 39 | assert np.count_nonzero(result.get_unitary()) == self.non_zeros_in_bell_circuit 40 | -------------------------------------------------------------------------------- /test/test_fast_shor_sim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "ShorFastSimulator.hpp" 12 | 13 | #include <cstdint> 14 | #include <gtest/gtest.h> 15 | 16 | /** 17 | * These tests may have to be adjusted if something about the random-number 18 | * generation changes. 19 | */ 20 | 21 | TEST(FastShorSimTest, Factorize15BaseTest) { 22 | ShorFastSimulator ddsim(15, 2, 5ULL, true); 23 | 24 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 25 | ASSERT_EQ(ddsim.getName(), "fast_shor_15_2"); 26 | ASSERT_EQ(ddsim.getFactors().first, 0); 27 | ASSERT_EQ(ddsim.getFactors().second, 0); 28 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 29 | ddsim.simulate(1); 30 | 31 | ASSERT_EQ(ddsim.getFactors().first, 3); 32 | ASSERT_EQ(ddsim.getFactors().second, 5); 33 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "SUCCESS(3*5)"); 34 | } 35 | 36 | TEST(FastShorSimTest, Factorize15NoSeedBaseTest) { 37 | ShorFastSimulator ddsim(15, 2, true); 38 | 39 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 40 | ASSERT_EQ(ddsim.getName(), "fast_shor_15_2"); 41 | ASSERT_EQ(ddsim.getFactors().first, 0); 42 | ASSERT_EQ(ddsim.getFactors().second, 0); 43 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 44 | ddsim.simulate(1); 45 | 46 | ASSERT_EQ(ddsim.getNumberOfQubits(), 5); 47 | } 48 | 49 | TEST(FastShorSimTest, Factorize15BaseTestNoFixedCoPrime) { 50 | ShorFastSimulator ddsim(15, 0, static_cast<std::uint64_t>(1)); 51 | ddsim.simulate(1); 52 | 53 | SUCCEED() 54 | << "Successfully executed this path. Testing for values is flaky..."; 55 | } 56 | 57 | TEST(FastShorSimTest, Factorize15NegTest) { 58 | ShorFastSimulator ddsim(15, 2, static_cast<std::uint64_t>(1)); 59 | ddsim.simulate(1); 60 | 61 | ASSERT_EQ(ddsim.getFactors().first, 0); 62 | ASSERT_EQ(ddsim.getFactors().second, 0); 63 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "FAILURE"); 64 | } 65 | 66 | TEST(FastShorSimTest, Factorize55Test) { 67 | ShorFastSimulator ddsim(55, 2, static_cast<std::uint64_t>(3)); 68 | ddsim.simulate(1); 69 | 70 | ASSERT_EQ(ddsim.getFactors().first, 11); 71 | ASSERT_EQ(ddsim.getFactors().second, 5); 72 | } 73 | 74 | TEST(FastShorSimTest, Factorize221Test) { 75 | ShorFastSimulator ddsim(221, 2, static_cast<std::uint64_t>(4)); 76 | ddsim.simulate(1); 77 | 78 | ASSERT_EQ(ddsim.getFactors().first, 13); 79 | ASSERT_EQ(ddsim.getFactors().second, 17); 80 | } 81 | -------------------------------------------------------------------------------- /test/test_grover_sim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "GroverSimulator.hpp" 12 | 13 | #include <gtest/gtest.h> 14 | 15 | /** 16 | * These tests may have to be adjusted if something about the random-number 17 | * generation changes. 18 | */ 19 | 20 | TEST(GroverSimTest, EmulatedOracle14Test) { 21 | GroverSimulator ddsim(14, 0); 22 | ddsim.simulate(1); 23 | 24 | EXPECT_EQ(ddsim.getNumberOfOps(), 0); 25 | EXPECT_EQ(ddsim.getName(), "emulated_grover_14"); 26 | EXPECT_EQ(ddsim.getOracle(), ddsim.additionalStatistics().at("oracle")); 27 | ASSERT_EQ(ddsim.getPathOfLeastResistance().second.substr(1), 28 | ddsim.additionalStatistics().at("oracle")); 29 | } 30 | 31 | TEST(GroverSimTest, EmulatedFixedOracleFixedSeedTest) { 32 | GroverSimulator ddsim("0110011", 0); 33 | ddsim.simulate(1); 34 | 35 | EXPECT_EQ(ddsim.getNumberOfOps(), 0); 36 | EXPECT_EQ(ddsim.getName(), "emulated_grover_7"); 37 | EXPECT_EQ(ddsim.getOracle(), ddsim.additionalStatistics().at("oracle")); 38 | ASSERT_EQ(ddsim.getPathOfLeastResistance().second.substr(1), 39 | ddsim.additionalStatistics().at("oracle")); 40 | } 41 | 42 | TEST(GroverSimTest, EmulatedFixedOracleTest) { 43 | GroverSimulator ddsim("0110001"); 44 | ddsim.simulate(1); 45 | 46 | EXPECT_EQ(ddsim.getNumberOfOps(), 0); 47 | EXPECT_EQ(ddsim.getName(), "emulated_grover_7"); 48 | EXPECT_EQ(ddsim.getOracle(), ddsim.additionalStatistics().at("oracle")); 49 | ASSERT_EQ(ddsim.getPathOfLeastResistance().second.substr(1), 50 | ddsim.additionalStatistics().at("oracle")); 51 | } 52 | -------------------------------------------------------------------------------- /test/test_shor_sim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "ShorSimulator.hpp" 12 | 13 | #include <gtest/gtest.h> 14 | 15 | /** 16 | * These tests may have to be adjusted if something about the random-number 17 | * generation changes. 18 | */ 19 | 20 | TEST(ShorSimTest, Factorize15EmulationTest) { 21 | // add verbose = true for coverage 22 | ShorSimulator ddsim(15, 2, 3ULL, true, false); 23 | 24 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 25 | ASSERT_EQ(ddsim.getName(), "shor_15_2"); 26 | ASSERT_EQ(ddsim.getFactors().first, 0); 27 | ASSERT_EQ(ddsim.getFactors().second, 0); 28 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 29 | ddsim.simulate(1); 30 | 31 | ASSERT_EQ(ddsim.getFactors().first, 3); 32 | ASSERT_EQ(ddsim.getFactors().second, 5); 33 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "SUCCESS(3*5)"); 34 | 35 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor1"), "3"); 36 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor2"), "5"); 37 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_result"), "SUCCESS(3*5)"); 38 | } 39 | 40 | TEST(ShorSimTest, Factorize15EmulationNoSeedTest) { 41 | // add verbose = true for coverage 42 | ShorSimulator ddsim(15, 2, true, false); 43 | 44 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 45 | ASSERT_EQ(ddsim.getName(), "shor_15_2"); 46 | ASSERT_EQ(ddsim.getFactors().first, 0); 47 | ASSERT_EQ(ddsim.getFactors().second, 0); 48 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 49 | ddsim.simulate(1); 50 | 51 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor1"), "3"); 52 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor2"), "5"); 53 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_result"), "SUCCESS(3*5)"); 54 | } 55 | 56 | TEST(ShorSimTest, Factorize15EmulationNoOptionalParametersTest) { 57 | // add verbose = true for coverage 58 | ShorSimulator ddsim(15, 2); 59 | 60 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 61 | ASSERT_EQ(ddsim.getName(), "shor_15_2"); 62 | ASSERT_EQ(ddsim.getFactors().first, 0); 63 | ASSERT_EQ(ddsim.getFactors().second, 0); 64 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 65 | ddsim.simulate(1); 66 | 67 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor1"), "3"); 68 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor2"), "5"); 69 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_result"), "SUCCESS(3*5)"); 70 | } 71 | 72 | TEST(ShorSimTest, Factorize15EmulationNoCoprimeParametersTest) { 73 | // add verbose = true for coverage 74 | ShorSimulator ddsim(15, 0); 75 | 76 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 77 | ASSERT_EQ(ddsim.getFactors().first, 0); 78 | ASSERT_EQ(ddsim.getFactors().second, 0); 79 | ASSERT_EQ(ddsim.additionalStatistics().at("coprime_a"), "0"); 80 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 81 | ddsim.simulate(1); 82 | 83 | ASSERT_NE(ddsim.additionalStatistics().at("coprime_a"), "0"); 84 | } 85 | 86 | TEST(ShorSimTest, Factorize15EmulationInvalidCoprimeParametersTest) { 87 | // add verbose = true for coverage 88 | ShorSimulator ddsim(15, 3); 89 | 90 | ASSERT_EQ(ddsim.getNumberOfOps(), 0); 91 | ASSERT_EQ(ddsim.getFactors().first, 0); 92 | ASSERT_EQ(ddsim.getFactors().second, 0); 93 | ASSERT_EQ(ddsim.additionalStatistics().at("coprime_a"), "3"); 94 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "did not start"); 95 | ddsim.simulate(1); 96 | 97 | ASSERT_NE(ddsim.additionalStatistics().at("coprime_a"), "3"); 98 | } 99 | 100 | TEST(ShorSimTest, Factorize15NegTest) { 101 | ShorSimulator ddsim(15, 2, 1ULL); 102 | ddsim.simulate(1); 103 | 104 | ASSERT_EQ(ddsim.getFactors().first, 0); 105 | ASSERT_EQ(ddsim.getFactors().second, 0); 106 | ASSERT_EQ(ddsim.additionalStatistics().at("sim_result"), "FAILURE"); 107 | 108 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor1"), "3"); 109 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_factor2"), "5"); 110 | ASSERT_EQ(ddsim.additionalStatistics().at("polr_result"), "SUCCESS(3*5)"); 111 | } 112 | 113 | TEST(ShorSimTest, Factorize55Test) { 114 | ShorSimulator ddsim(55, 2, 3ULL); 115 | ddsim.simulate(1); 116 | 117 | ASSERT_EQ(ddsim.getFactors().first, 11); 118 | ASSERT_EQ(ddsim.getFactors().second, 5); 119 | } 120 | 121 | TEST(ShorSimTest, Factorize55ApproximateTest) { 122 | ShorSimulator ddsim(55, 2, 3ULL, true, true); 123 | ddsim.simulate(1); 124 | 125 | ASSERT_EQ(ddsim.getFactors().first, 11); 126 | ASSERT_EQ(ddsim.getFactors().second, 5); 127 | } 128 | -------------------------------------------------------------------------------- /test/test_unitary_sim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM 3 | * Copyright (c) 2025 Munich Quantum Software Company GmbH 4 | * All rights reserved. 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Licensed under the MIT License 9 | */ 10 | 11 | #include "UnitarySimulator.hpp" 12 | #include "ir/QuantumComputation.hpp" 13 | 14 | #include <gtest/gtest.h> 15 | #include <iostream> 16 | #include <memory> 17 | #include <stdexcept> 18 | #include <utility> 19 | 20 | using namespace qc::literals; 21 | 22 | TEST(UnitarySimTest, ConstructSimpleCircuitSequential) { 23 | auto quantumComputation = std::make_unique<qc::QuantumComputation>(3); 24 | quantumComputation->h(2); 25 | quantumComputation->ch(2, 1); 26 | quantumComputation->ch(2, 0); 27 | UnitarySimulator ddsim(std::move(quantumComputation), 28 | UnitarySimulator::Mode::Sequential); 29 | ASSERT_NO_THROW(ddsim.construct()); 30 | const auto& e = ddsim.getConstructedDD(); 31 | EXPECT_TRUE(e.p->e[0].isIdentity()); 32 | EXPECT_TRUE(e.p->e[1].isIdentity()); 33 | auto finalNodes = ddsim.getFinalNodeCount(); 34 | EXPECT_EQ(finalNodes, 4); 35 | auto maxNodes = ddsim.getMaxNodeCount(); 36 | auto constructionTime = ddsim.getConstructionTime(); 37 | std::cout << "Construction took " << constructionTime 38 | << "s, requiring a maximum of " << maxNodes << " nodes\n"; 39 | } 40 | 41 | TEST(UnitarySimTest, ConstructSimpleCircuitRecursive) { 42 | auto quantumComputation = std::make_unique<qc::QuantumComputation>(3); 43 | quantumComputation->h(2); 44 | quantumComputation->ch(2, 1); 45 | quantumComputation->ch(2, 0); 46 | UnitarySimulator ddsim(std::move(quantumComputation), 47 | UnitarySimulator::Mode::Recursive); 48 | ASSERT_NO_THROW(ddsim.construct()); 49 | const auto& e = ddsim.getConstructedDD(); 50 | EXPECT_TRUE(e.p->e[0].isIdentity()); 51 | EXPECT_TRUE(e.p->e[1].isIdentity()); 52 | auto finalNodes = ddsim.getFinalNodeCount(); 53 | EXPECT_EQ(finalNodes, 4); 54 | auto maxNodes = ddsim.getMaxNodeCount(); 55 | auto constructionTime = ddsim.getConstructionTime(); 56 | std::cout << "Construction took " << constructionTime 57 | << "s, requiring a maximum of " << maxNodes << " nodes\n"; 58 | } 59 | 60 | TEST(UnitarySimTest, ConstructSimpleCircuitRecursiveWithSeed) { 61 | auto quantumComputation = std::make_unique<qc::QuantumComputation>(3); 62 | quantumComputation->h(2); 63 | quantumComputation->ch(2, 1); 64 | quantumComputation->ch(2, 0); 65 | UnitarySimulator ddsim(std::move(quantumComputation), ApproximationInfo{}, 66 | 1337, UnitarySimulator::Mode::Recursive); 67 | ASSERT_NO_THROW(ddsim.construct()); 68 | const auto& e = ddsim.getConstructedDD(); 69 | EXPECT_TRUE(e.p->e[0].isIdentity()); 70 | EXPECT_TRUE(e.p->e[1].isIdentity()); 71 | } 72 | 73 | TEST(UnitarySimTest, NonStandardOperation) { 74 | auto quantumComputation = std::make_unique<qc::QuantumComputation>(1, 1); 75 | quantumComputation->h(0); 76 | quantumComputation->measure(0, 0); 77 | quantumComputation->barrier(0); 78 | quantumComputation->h(0); 79 | quantumComputation->measure(0, 0); 80 | 81 | UnitarySimulator ddsim(std::move(quantumComputation)); 82 | EXPECT_TRUE(ddsim.getMode() == UnitarySimulator::Mode::Recursive); 83 | EXPECT_THROW(ddsim.construct(), std::invalid_argument); 84 | } 85 | --------------------------------------------------------------------------------