From ee498497c71e057fde2a81497bb1e6642d1fdd4a Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 1 Sep 2026 18:40:07 +0000 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=92=A5=20Remove=20the=20legacy=20ci?= =?UTF-8?q?rcuit=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove CoreIR, CoreQASM, classic bindings, converters, and circuit DD adapters. Keep CoreDD primitives independent and execute DDSIM through QC/QCO and QIR when MLIR is enabled. Skip the DDSIM target entirely in no-MLIR builds. Provide no compatibility or density layer. Print local C++ lint diagnostics so failures remain actionable. Assisted-by: GPT-5.6 via Codex --- AGENTS.md | 4 +- CHANGELOG.md | 17 +- CMakeLists.txt | 21 +- README.md | 22 +- UPGRADING.md | 122 +- bindings/CMakeLists.txt | 1 - bindings/dd/register_dd.cpp | 165 +- bindings/dd/register_dd_package.cpp | 143 +- bindings/ir/CMakeLists.txt | 34 - .../register_compound_operation.cpp | 227 -- bindings/ir/operations/register_control.cpp | 58 - .../operations/register_if_else_operation.cpp | 142 - .../register_non_unitary_operation.cpp | 83 - bindings/ir/operations/register_operation.cpp | 171 -- bindings/ir/operations/register_optype.cpp | 237 -- .../register_standard_operation.cpp | 109 - .../register_symbolic_operation.cpp | 110 - bindings/ir/register_ir.cpp | 42 - bindings/ir/register_operations.cpp | 38 - bindings/ir/register_permutation.cpp | 216 -- bindings/ir/register_quantum_computation.cpp | 2291 -------------- bindings/ir/register_registers.cpp | 194 -- bindings/ir/register_symbolic.cpp | 28 - bindings/ir/symbolic/register_expression.cpp | 165 - bindings/ir/symbolic/register_term.cpp | 85 - bindings/ir/symbolic/register_variable.cpp | 48 - cmake/mqt-core-config.cmake.in | 2 +- docs/dd_package.md | 200 +- docs/index.md | 11 +- docs/mlir/python_compiler_collection.md | 12 +- docs/mqt_core_ir.md | 412 --- docs/qdmi/ddsim_device.md | 40 +- docs/qdmi/driver.md | 10 +- doxygen/namespaces.md | 18 +- include/mqt-core/dd/Complex.hpp | 3 +- include/mqt-core/dd/ComputeTable.hpp | 3 +- include/mqt-core/dd/DDDefinitions.hpp | 87 +- .../mqt-core/dd/FunctionalityConstruction.hpp | 59 - include/mqt-core/dd/GateMatrixDefinitions.hpp | 90 +- include/mqt-core/dd/Operations.hpp | 262 +- include/mqt-core/dd/Package.hpp | 44 +- include/mqt-core/dd/Simulation.hpp | 97 - include/mqt-core/dd/UniqueTable.hpp | 3 +- include/mqt-core/ir/Definitions.hpp | 106 - include/mqt-core/ir/OpenQASMSerializer.hpp | 80 - include/mqt-core/ir/Permutation.hpp | 217 -- include/mqt-core/ir/QuantumComputation.hpp | 604 ---- include/mqt-core/ir/Register.hpp | 125 - .../ir/operations/CompoundOperation.hpp | 214 -- include/mqt-core/ir/operations/Control.hpp | 142 - include/mqt-core/ir/operations/Expression.hpp | 857 ------ .../ir/operations/IfElseOperation.hpp | 163 - .../ir/operations/NonUnitaryOperation.hpp | 113 - include/mqt-core/ir/operations/OpType.hpp | 133 - include/mqt-core/ir/operations/OpType.inc | 65 - include/mqt-core/ir/operations/Operation.hpp | 219 -- .../ir/operations/StandardOperation.hpp | 118 - .../ir/operations/SymbolicOperation.hpp | 136 - include/mqt-core/qasm3/Exception.hpp | 89 - include/mqt-core/qasm3/Gate.hpp | 69 - include/mqt-core/qasm3/Importer.hpp | 196 -- include/mqt-core/qasm3/InstVisitor.hpp | 149 - include/mqt-core/qasm3/NestedEnvironment.hpp | 45 - include/mqt-core/qasm3/Parser.hpp | 174 -- include/mqt-core/qasm3/Scanner.hpp | 77 - include/mqt-core/qasm3/Statement.hpp | 493 --- include/mqt-core/qasm3/Statement_fwd.hpp | 43 - include/mqt-core/qasm3/StdGates.hpp | 235 -- include/mqt-core/qasm3/Token.hpp | 202 -- include/mqt-core/qasm3/Types.hpp | 247 -- include/mqt-core/qasm3/Types_fwd.hpp | 26 - .../mqt-core/qasm3/passes/CompilerPass.hpp | 26 - .../mqt-core/qasm3/passes/ConstEvalPass.hpp | 106 - .../mqt-core/qasm3/passes/TypeCheckPass.hpp | 128 - include/mqt-core/qdmi/Client.hpp | 26 + include/mqt-core/qdmi/devices/dd/Device.hpp | 2 +- mlir/include/mlir/Conversion/GateTable.def | 2 +- .../Dialect/QIR/Execution/Runtime/Runtime.h | 101 +- .../lib/Dialect/QCO/Utils/DDFunctionality.cpp | 114 +- .../QIR/Execution/Runtime/CMakeLists.txt | 2 +- .../lib/Dialect/QIR/Execution/Runtime/QIR.cpp | 106 +- .../Dialect/QIR/Execution/Runtime/Runtime.cpp | 58 +- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 119 +- .../test_multi_controlled_decomposition.cpp | 147 +- .../QCO/Utils/test_dd_functionality.cpp | 239 +- .../Execution/Runtime/test_qir_runtime.cpp | 75 +- noxfile.py | 4 +- pyproject.toml | 2 +- python/mqt/core/__init__.py | 3 +- python/mqt/core/dd.pyi | 236 +- python/mqt/core/ir/__init__.pyi | 2111 ------------- python/mqt/core/ir/operations.pyi | 891 ------ python/mqt/core/ir/registers.pyi | 85 - python/mqt/core/ir/symbolic.pyi | 155 - python/mqt/core/load.py | 56 - python/mqt/core/plugins/qiskit/__init__.py | 4 - python/mqt/core/plugins/qiskit/backend.py | 14 + .../mqt/core/plugins/qiskit/mqt_to_qiskit.py | 422 --- .../mqt/core/plugins/qiskit/qiskit_to_mqt.py | 568 ---- src/CMakeLists.txt | 10 +- src/dd/CMakeLists.txt | 6 +- src/dd/CachedEdge.cpp | 3 +- src/dd/ComplexValue.cpp | 3 +- src/dd/Edge.cpp | 3 +- src/dd/FunctionalityConstruction.cpp | 47 - src/dd/GateMatrixDefinitions.cpp | 81 +- src/dd/Operations.cpp | 302 +- src/dd/Package.cpp | 83 +- src/dd/Simulation.cpp | 239 -- src/ir/CMakeLists.txt | 40 - src/ir/CircuitOptimization.cpp | 218 -- src/ir/OpenQASMSerializer.cpp | 588 ---- src/ir/Permutation.cpp | 64 - src/ir/QuantumComputation.cpp | 1535 ---------- src/ir/operations/CompoundOperation.cpp | 287 -- src/ir/operations/Expression.cpp | 40 - src/ir/operations/IfElseOperation.cpp | 269 -- src/ir/operations/NonUnitaryOperation.cpp | 176 -- src/ir/operations/OpType.cpp | 168 -- src/ir/operations/Operation.cpp | 191 -- src/ir/operations/StandardOperation.cpp | 419 --- src/ir/operations/SymbolicOperation.cpp | 438 --- src/qasm3/CMakeLists.txt | 42 - src/qasm3/Importer.cpp | 1044 ------- src/qasm3/Parser.cpp | 937 ------ src/qasm3/Scanner.cpp | 688 ----- src/qasm3/Statement.cpp | 88 - src/qasm3/Token.cpp | 309 -- src/qasm3/Types.cpp | 81 - src/qasm3/passes/ConstEvalPass.cpp | 632 ---- src/qasm3/passes/TypeCheckPass.cpp | 424 --- src/qdmi/Client.cpp | 39 +- src/qdmi/devices/CMakeLists.txt | 2 +- src/qdmi/devices/dd/CMakeLists.txt | 18 +- src/qdmi/devices/dd/Device.cpp | 141 +- test/CMakeLists.txt | 1 - test/dd/CMakeLists.txt | 2 +- test/dd/test_dd_functionality.cpp | 515 +--- test/dd/test_package.cpp | 717 ++--- test/ir/CMakeLists.txt | 14 - test/ir/test_flatten_operations.cpp | 106 - test/ir/test_if_else_operation.cpp | 206 -- test/ir/test_io.cpp | 1064 ------- test/ir/test_operation.cpp | 273 -- test/ir/test_qasm3_parser.cpp | 2680 ----------------- test/ir/test_qfr_functionality.cpp | 1525 ---------- test/ir/test_remove_final_measurements.cpp | 180 -- test/ir/test_symbolic.cpp | 351 --- test/python/dd/test_dd_package.py | 84 - test/python/dd/test_matrix_dds.py | 24 +- test/python/ir/test_if_else_operation.py | 136 - test/python/ir/test_ir.py | 70 - .../plugins/qiskit/test_mock_backend.py | 19 + test/python/plugins/qiskit/test_sampler.py | 14 +- test/python/plugins/test_qiskit.py | 865 ------ test/python/qdmi/test_qdmi.py | 18 +- test/python/test_load.py | 138 - test/python/test_mlir.py | 7 - test/python/test_mlir_qiskit_translation.py | 9 +- test/python/test_python_surface.py | 44 + test/qdmi/CMakeLists.txt | 3 + test/qdmi/devices/dd/CMakeLists.txt | 7 +- test/qdmi/devices/dd/concurrency_test.cpp | 2 - .../devices/dd/device_properties_test.cpp | 2 - test/qdmi/devices/dd/error_handling_test.cpp | 17 + test/qdmi/devices/dd/helpers/circuits.hpp | 18 + test/qdmi/devices/dd/job_parameters_test.cpp | 8 - .../qdmi/devices/dd/results_sampling_test.cpp | 42 +- .../devices/dd/results_statevector_test.cpp | 74 +- test/qdmi/driver/CMakeLists.txt | 20 +- test/qdmi/driver/test_driver.cpp | 22 +- test/qdmi/registry/CMakeLists.txt | 3 + test/qdmi/registry/test_device_registry.cpp | 7 + test/qdmi/test_client.cpp | 18 + 174 files changed, 1848 insertions(+), 34377 deletions(-) delete mode 100644 bindings/ir/CMakeLists.txt delete mode 100644 bindings/ir/operations/register_compound_operation.cpp delete mode 100644 bindings/ir/operations/register_control.cpp delete mode 100644 bindings/ir/operations/register_if_else_operation.cpp delete mode 100644 bindings/ir/operations/register_non_unitary_operation.cpp delete mode 100644 bindings/ir/operations/register_operation.cpp delete mode 100644 bindings/ir/operations/register_optype.cpp delete mode 100644 bindings/ir/operations/register_standard_operation.cpp delete mode 100644 bindings/ir/operations/register_symbolic_operation.cpp delete mode 100644 bindings/ir/register_ir.cpp delete mode 100644 bindings/ir/register_operations.cpp delete mode 100644 bindings/ir/register_permutation.cpp delete mode 100644 bindings/ir/register_quantum_computation.cpp delete mode 100644 bindings/ir/register_registers.cpp delete mode 100644 bindings/ir/register_symbolic.cpp delete mode 100644 bindings/ir/symbolic/register_expression.cpp delete mode 100644 bindings/ir/symbolic/register_term.cpp delete mode 100644 bindings/ir/symbolic/register_variable.cpp delete mode 100644 docs/mqt_core_ir.md delete mode 100644 include/mqt-core/dd/FunctionalityConstruction.hpp delete mode 100644 include/mqt-core/dd/Simulation.hpp delete mode 100644 include/mqt-core/ir/Definitions.hpp delete mode 100644 include/mqt-core/ir/OpenQASMSerializer.hpp delete mode 100644 include/mqt-core/ir/Permutation.hpp delete mode 100644 include/mqt-core/ir/QuantumComputation.hpp delete mode 100644 include/mqt-core/ir/Register.hpp delete mode 100644 include/mqt-core/ir/operations/CompoundOperation.hpp delete mode 100644 include/mqt-core/ir/operations/Control.hpp delete mode 100644 include/mqt-core/ir/operations/Expression.hpp delete mode 100644 include/mqt-core/ir/operations/IfElseOperation.hpp delete mode 100644 include/mqt-core/ir/operations/NonUnitaryOperation.hpp delete mode 100644 include/mqt-core/ir/operations/OpType.hpp delete mode 100644 include/mqt-core/ir/operations/OpType.inc delete mode 100644 include/mqt-core/ir/operations/Operation.hpp delete mode 100644 include/mqt-core/ir/operations/StandardOperation.hpp delete mode 100644 include/mqt-core/ir/operations/SymbolicOperation.hpp delete mode 100644 include/mqt-core/qasm3/Exception.hpp delete mode 100644 include/mqt-core/qasm3/Gate.hpp delete mode 100644 include/mqt-core/qasm3/Importer.hpp delete mode 100644 include/mqt-core/qasm3/InstVisitor.hpp delete mode 100644 include/mqt-core/qasm3/NestedEnvironment.hpp delete mode 100644 include/mqt-core/qasm3/Parser.hpp delete mode 100644 include/mqt-core/qasm3/Scanner.hpp delete mode 100644 include/mqt-core/qasm3/Statement.hpp delete mode 100644 include/mqt-core/qasm3/Statement_fwd.hpp delete mode 100644 include/mqt-core/qasm3/StdGates.hpp delete mode 100644 include/mqt-core/qasm3/Token.hpp delete mode 100644 include/mqt-core/qasm3/Types.hpp delete mode 100644 include/mqt-core/qasm3/Types_fwd.hpp delete mode 100644 include/mqt-core/qasm3/passes/CompilerPass.hpp delete mode 100644 include/mqt-core/qasm3/passes/ConstEvalPass.hpp delete mode 100644 include/mqt-core/qasm3/passes/TypeCheckPass.hpp delete mode 100644 python/mqt/core/ir/__init__.pyi delete mode 100644 python/mqt/core/ir/operations.pyi delete mode 100644 python/mqt/core/ir/registers.pyi delete mode 100644 python/mqt/core/ir/symbolic.pyi delete mode 100644 python/mqt/core/load.py delete mode 100644 python/mqt/core/plugins/qiskit/mqt_to_qiskit.py delete mode 100644 python/mqt/core/plugins/qiskit/qiskit_to_mqt.py delete mode 100644 src/dd/FunctionalityConstruction.cpp delete mode 100644 src/dd/Simulation.cpp delete mode 100644 src/ir/CMakeLists.txt delete mode 100644 src/ir/CircuitOptimization.cpp delete mode 100644 src/ir/OpenQASMSerializer.cpp delete mode 100644 src/ir/Permutation.cpp delete mode 100644 src/ir/QuantumComputation.cpp delete mode 100644 src/ir/operations/CompoundOperation.cpp delete mode 100644 src/ir/operations/Expression.cpp delete mode 100644 src/ir/operations/IfElseOperation.cpp delete mode 100644 src/ir/operations/NonUnitaryOperation.cpp delete mode 100644 src/ir/operations/OpType.cpp delete mode 100644 src/ir/operations/Operation.cpp delete mode 100644 src/ir/operations/StandardOperation.cpp delete mode 100644 src/ir/operations/SymbolicOperation.cpp delete mode 100644 src/qasm3/CMakeLists.txt delete mode 100644 src/qasm3/Importer.cpp delete mode 100644 src/qasm3/Parser.cpp delete mode 100644 src/qasm3/Scanner.cpp delete mode 100644 src/qasm3/Statement.cpp delete mode 100644 src/qasm3/Token.cpp delete mode 100644 src/qasm3/Types.cpp delete mode 100644 src/qasm3/passes/ConstEvalPass.cpp delete mode 100644 src/qasm3/passes/TypeCheckPass.cpp delete mode 100644 test/ir/CMakeLists.txt delete mode 100644 test/ir/test_flatten_operations.cpp delete mode 100644 test/ir/test_if_else_operation.cpp delete mode 100644 test/ir/test_io.cpp delete mode 100644 test/ir/test_operation.cpp delete mode 100644 test/ir/test_qasm3_parser.cpp delete mode 100644 test/ir/test_qfr_functionality.cpp delete mode 100644 test/ir/test_remove_final_measurements.cpp delete mode 100644 test/ir/test_symbolic.cpp delete mode 100644 test/python/dd/test_dd_package.py delete mode 100644 test/python/ir/test_if_else_operation.py delete mode 100644 test/python/ir/test_ir.py delete mode 100644 test/python/plugins/test_qiskit.py delete mode 100644 test/python/test_load.py create mode 100644 test/python/test_python_surface.py diff --git a/AGENTS.md b/AGENTS.md index ace2d53b67..9e173715ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -109,10 +109,8 @@ MQT Core. The project-wide policy for AI-assisted contributions is `cpp-linter` check on every line of each changed C++ file. A changed-line `clang-tidy` run is useful while iterating but is not sufficient validation. - Run a component binary directly when iterating, for example - `./build/release/test/ir/mqt-core-ir-test` or `./build/release/test/qdmi/driver/mqt-core-qdmi-driver-test`. -- Use GoogleTest filters to narrow a binary further, for example - `./build/release/test/ir/mqt-core-ir-test --gtest_filter='StandardOperation.*'`. +- Use GoogleTest filters to narrow a binary further. - Replace `release` with `debug` for a debug build. Consult `CMakePresets.json` for other supported configurations. diff --git a/CHANGELOG.md b/CHANGELOG.md index f073f6d73a..bbf8764a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,8 +95,6 @@ releases may include breaking changes. ([#2259]) ([**@denialhaag**]) - ⬆️ Raise the macOS deployment target to 13.3 to enable `std::format` in libc++ ([#2259]) ([**@denialhaag**]) -- 💥 Move circuit IR OpenQASM serialization from operation subclasses to - `OpenQASMSerializer` ([#2249]) ([**@simon1hofmann**]) - 💥 Require Python 3.11 or newer ([#2209]) ([**@denialhaag**], [**@burgholzer**]) - ⬆️ Update `nanobind` to version 3.0.1 ([#2209], [#2283]) ([**@denialhaag**], @@ -109,8 +107,6 @@ releases may include breaking changes. ([**@burgholzer**]) - 🐛 Protect process-wide DD, IR, and QDMI state for free-threaded Python ([#2209]) ([**@burgholzer**]) -- 💥 Prune dead and misleading CoreIR APIs and remove random-number generator - state from `QuantumComputation` ([#2111], [#2112]) ([**@simon1hofmann**]) - 💥 Update QIR execution for QIR 2.1, isolated runtimes, deterministic QDMI sampling, and safe statevector extraction ([#2035], [#2036], [#2246]) ([**@burgholzer**], [**@denialhaag**]) @@ -121,13 +117,14 @@ releases may include breaking changes. ### Removed +- 💥 Remove the classic circuit APIs and adapters. Use compiler-backed QC/QCO + APIs in v4 or MQT Core 3.9.x for legacy code ([#2288]) ([**@simon1hofmann**]) - 💥 Remove the `spdlog` dependency from MQT Core source builds, installed CMake packages, and Python wheels. QDMI diagnostics continue to be written to standard error ([#2270]) ([**@denialhaag**]) -- 💥 Remove `CircuitOptimizer`. Move circuit flattening and final-measurement - removal to `QuantumComputation`, equivalence-checking transformations to MQT - QCEC, and mapping transformations to MQT QMAP. Move single-qubit gate fusion - to both downstream packages. Remove the public circuit dependency graph and +- 💥 Remove `CircuitOptimizer`. Move equivalence-checking transformations to MQT + QCEC and mapping transformations to MQT QMAP. Move single-qubit gate fusion to + both downstream packages. Remove the public circuit dependency graph and transformations without production consumers ([#2262]) ([**@simon1hofmann**]) - 💥 Remove test-only DD state generators and recursive functionality construction from MQT Core ([#2257]) ([**@simon1hofmann**]) @@ -871,13 +868,13 @@ for previous changelogs._ [#2298]: https://github.com/munich-quantum-toolkit/core/pull/2298 [#2284]: https://github.com/munich-quantum-toolkit/core/pull/2284 [#2283]: https://github.com/munich-quantum-toolkit/core/pull/2283 +[#2288]: https://github.com/munich-quantum-toolkit/core/pull/2288 [#2278]: https://github.com/munich-quantum-toolkit/core/pull/2278 [#2270]: https://github.com/munich-quantum-toolkit/core/pull/2270 [#2262]: https://github.com/munich-quantum-toolkit/core/pull/2262 [#2259]: https://github.com/munich-quantum-toolkit/core/pull/2259 [#2258]: https://github.com/munich-quantum-toolkit/core/pull/2258 [#2257]: https://github.com/munich-quantum-toolkit/core/pull/2257 -[#2249]: https://github.com/munich-quantum-toolkit/core/pull/2249 [#2246]: https://github.com/munich-quantum-toolkit/core/pull/2246 [#2220]: https://github.com/munich-quantum-toolkit/core/pull/2220 [#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232 @@ -919,8 +916,6 @@ for previous changelogs._ [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 -[#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 -[#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 [#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 diff --git a/CMakeLists.txt b/CMakeLists.txt index a555e57fe3..72c9cf6c27 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,9 +67,9 @@ option(BUILD_MQT_CORE_DOCUMENTATION "Generate documentation artifacts as part of if(BUILD_MQT_CORE_DOCUMENTATION AND NOT BUILD_MQT_CORE_MLIR) message(FATAL_ERROR "BUILD_MQT_CORE_DOCUMENTATION requires BUILD_MQT_CORE_MLIR") endif() -if(BUILD_MQT_CORE_TESTS AND (NOT BUILD_MQT_CORE_QDMI_DDSIM_DEVICE OR NOT - BUILD_MQT_CORE_QDMI_SC_DEVICE)) - message(FATAL_ERROR "BUILD_MQT_CORE_TESTS requires all bundled QDMI devices") +if(BUILD_MQT_CORE_TESTS AND (NOT BUILD_MQT_CORE_QDMI_SC_DEVICE + OR (BUILD_MQT_CORE_MLIR AND NOT BUILD_MQT_CORE_QDMI_DDSIM_DEVICE))) + message(FATAL_ERROR "BUILD_MQT_CORE_TESTS requires all available bundled QDMI devices") endif() if(BUILD_MQT_CORE_MLIR) if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -149,7 +149,6 @@ if(BUILD_MQT_CORE_MLIR) COMMENT "Copying and cleaning up generated MLIR documentation" VERBATIM) endif() - if(BUILD_MQT_CORE_DOCUMENTATION) if(PROJECT_IS_TOP_LEVEL) add_custom_target(mqt-core-docs DEPENDS mlir-doc) @@ -164,25 +163,19 @@ if(BUILD_MQT_CORE_MLIR) COMMENT "Copying and cleaning up generated MLIR documentation" VERBATIM) endif() - foreach(binding ir dd qdmi) + foreach(binding dd qdmi) add_dependencies(${MQT_CORE_TARGET_NAME}-${binding}-bindings mqt-core-docs) endforeach() endif() endif() if(BUILD_MQT_CORE_BINDINGS) - set(MQT_CORE_WHEEL_TARGETS - mqt-core-bench-bindings - mqt-core-bench-library - mqt-core-ir - mqt-core-dd - mqt-core-ir-bindings - mqt-core-dd-bindings - mqt-core-qdmi-bindings) + set(MQT_CORE_WHEEL_TARGETS mqt-core-bench-bindings mqt-core-bench-library mqt-core-dd + mqt-core-dd-bindings mqt-core-qdmi-bindings) if(BUILD_MQT_CORE_MLIR) list(APPEND MQT_CORE_WHEEL_TARGETS mqt-core-bench mqt-core-mlir-bindings) endif() - if(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) + if(BUILD_MQT_CORE_MLIR AND BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) list(APPEND MQT_CORE_WHEEL_TARGETS mqt-core-qdmi-ddsim-device) endif() if(BUILD_MQT_CORE_QDMI_SC_DEVICE) diff --git a/README.md b/README.md index 8ae7958541..62164d1cb8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ forms the backbone of the quantum software tools developed as part of the ## Key Features -- Fully fledged intermediate representation (IR) for quantum computations. +- An MLIR-based compiler collection for quantum programs. - A state-of-the-art decision diagram (DD) package for quantum computing. - A QIR runtime based on the decision diagram package. @@ -97,14 +97,18 @@ uv pip install mqt.core The following code gives an example on the usage: ```python3 -from mqt.core.ir import QuantumComputation - -qc = QuantumComputation(2, 2) -qc.h(0) -qc.cx(0, 1) -qc.measure(range(2), range(2)) - -print(qc) +from mqt.core.mlir import compile_program + +program = compile_program("""OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] result; +h q[0]; +cx q[0], q[1]; +result = measure q; +""") + +print(program.ir) ``` **Detailed documentation and examples are available at diff --git a/UPGRADING.md b/UPGRADING.md index af0bd551cf..706c48d860 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,6 +6,37 @@ of changes including minor and patch releases, please refer to the ## [Unreleased] +### Removal of the classic circuit representation + +MQT Core 4 removes the complete classic circuit surface. This includes the C++ +`qc::QuantumComputation` hierarchy, the `MQT::CoreIR` and `MQT::CoreQASM` CMake +targets, and the installed `ir/` and `qasm3/` headers. Python no longer provides +`mqt.core.ir`, the top-level `mqt.core.load` helper, or the classic +`mqt.core.plugins.qiskit.mqt_to_qiskit` and `qiskit_to_mqt` converters. MQT Core +4 deliberately provides no compatibility alias or adapter for these APIs. + +Use the compiler-backed Python interface in new code: + +```python +from mqt.core.mlir import OutputFormat, QCProgram, compile_program + +qc_program = QCProgram.from_qasm_file("circuit.qasm") +qco_program = compile_program(qc_program, output=OutputFormat.QCO_OPTIMIZED) +``` + +Use `QCProgram.from_qasm_str` for source text, `QCProgram.from_qiskit` for a +Qiskit `QuantumCircuit`, and `QCProgram.to_qiskit` for conversion back to +Qiskit. For decision-diagram simulation, lower to a `QCOProgram` and use its +`sample`, `simulate`, or `build_functionality` methods. The circuit-taking +functions in `mqt.core.dd` and the operation-taking `DDPackage` methods have +been removed; the raw vector and matrix DD constructors remain available. + +MQT Core 3.9.x continues to provide the classic circuit interfaces for +repositories that have not migrated. Pin `mqt-core~=3.9.0` and stay on the 3.9 +release line for those consumers. C++ consumers should likewise use the 3.9 +release branch or a matching 3.9 version constraint. MQT Core 3.9 and 4 cannot +provide their Python or CMake packages in the same environment. + ### Removal of the `spdlog` dependency MQT Core no longer discovers, downloads, builds, installs, or exports `spdlog`. @@ -20,17 +51,11 @@ dependency instead. ### CircuitOptimizer removal -MQT Core no longer provides `qc::CircuitOptimizer`. Replace the two generic -transformations with `QuantumComputation` member calls: - -- Replace `qc::CircuitOptimizer::flattenOperations(qc, customGatesOnly)` with - `qc.flattenOperations(customGatesOnly)`. -- Replace `qc::CircuitOptimizer::removeFinalMeasurements(qc)` with - `qc.removeFinalMeasurements()`. - -Include `ir/QuantumComputation.hpp` and link `MQT::CoreIR`. MQT QCEC and MQT -QMAP each own their single-qubit gate-fusion implementation. MQT Core provides -no replacement for `singleQubitGateFusion` outside those packages. +MQT Core no longer provides `qc::CircuitOptimizer`. MQT QCEC and MQT QMAP each +own their single-qubit gate-fusion implementation. MQT Core provides no +replacement for `flattenOperations`, `removeFinalMeasurements`, or +`singleQubitGateFusion` outside packages that still use the classic circuit +representation. MQT QCEC now owns the equivalence-checking transformations `swapReconstruction`, `removeDiagonalGatesBeforeMeasure`, `eliminateResets`, `deferMeasurements`, @@ -50,8 +75,8 @@ Core replacement. Build the small traversal structure in the package that consumes it. MQT QMAP and MQT QuSAT demonstrate this migration. The public `removeIdentities`, `removeOperation`, `collectBlocks`, and -`collectCliffordBlocks` functions have no replacement. Erase operations through -`QuantumComputation` where needed. +`collectCliffordBlocks` functions have no replacement. Keep a package-specific +implementation with a consumer that still needs one. The `MQT::CoreCircuitOptimizer` CMake target and the `circuit_optimizer/CircuitOptimizer.hpp` header are removed. The @@ -66,13 +91,14 @@ generated decision diagrams with selected shapes for tests and have no direct replacement. MQT Core also removed `dd::buildFunctionalityRecursive`. The Python -`mqt.core.dd.build_unitary` and `mqt.core.dd.build_functionality` functions no -longer accept the `recursive` argument and always use sequential construction. -Use MQT DDSIM's unitary simulator when recursive pairwise construction is -required. +`mqt.core.dd.sample`, `simulate_statevector`, `build_unitary`, `simulate`, and +`build_functionality` functions were removed together with the classic circuit +representation. Compile to a `mqt.core.mlir.QCOProgram` and call its `sample`, +`simulate`, or `build_functionality` method instead. Use MQT DDSIM's unitary +simulator when recursive pairwise construction is required. -The zero, basis, GHZ, W, dense-vector, and sequential circuit constructors -remain available. +The zero, basis, GHZ, W, dense-vector, dense-matrix, and raw gate-matrix DD +constructors remain available. ### macOS support @@ -193,58 +219,6 @@ The class and function names do not change. For example: auto device = qdmi::Session::openDevice("mqt.ddsim.default"); ``` -### CoreIR API cleanup - -The CoreIR API cleanup requires the following migrations: - -- Replace `getNmeasuredQubits()` and `num_measured_qubits` with - `getNoutputQubits()` and `num_output_qubits`, respectively. -- Replace permutation-aware `Operation::equals()` and `getUsedQubitsPermuted()` - calls by applying the permutation to cloned operations before comparing them. -- Replace `getHighestLogicalQubitIndex()`, `printStatistics()`, and - `printPermutation()` with `initialLayout.maxValue()`, the individual count - accessors, and direct `Permutation` iteration, respectively. -- Construct output-permutation measurements explicitly instead of calling - `appendMeasurementsAccordingToOutputPermutation()`. -- Replace direct `Operation::dumpOpenQASM2()`, `dumpOpenQASM3()`, or - `dumpOpenQASM()` calls with `OpenQASMSerializer`. The register-map aliases - moved from `ir/Register.hpp` to `ir/OpenQASMSerializer.hpp`: - - ```cpp - #include "ir/OpenQASMSerializer.hpp" - - qc::OpenQASMSerializer(stream, qc::Format::OpenQASM2) - .serialize(operation, qubitMap, bitMap); - ``` - - Use `qc::Format::OpenQASM3` for OpenQASM 3 output. The relocated maps own - their register metadata instead of retaining references to the registers used - to construct them. Packages that define custom `Operation` subclasses must own - serialization for their extended syntax; in particular, MQT QMAP owns - neutral-atom OpenQASM serialization. - -The register lookup helpers `getQubitRegister()`, `getPhysicalQubitIndex()`, and -`physicalQubitIsAncillary()` are now private implementation details. - -`QuantumComputation` no longer stores a random-number generator or seed. Remove -the third `seed` argument from C++ and Python constructor calls. C++ callers -that used `QuantumComputation::getGenerator()` must create and own a -random-number generator instead. Randomized circuit generators continue to -accept a seed and now own a separate generator for each call. - -### Removal of the legacy circuit-to-MLIR translator - -The compiler no longer accepts `qc::QuantumComputation` or -`mqt.core.ir.QuantumComputation` objects. The -`mlir::QCProgram::fromQuantumComputation` and Python -`QCProgram.from_quantum_computation` functions have been removed. Pass OpenQASM, -a Qiskit circuit, or a typed MLIR program to the compiler instead. Existing -Python code can convert a legacy circuit to OpenQASM 3 before compilation: - -```python -program = compile_program(computation.qasm3_str()) -``` - ### Removal of the ZX-calculus library MQT Core no longer provides the `mqt-core-zx` library, the `MQT::CoreZX` CMake @@ -276,9 +250,9 @@ return `QDMI_ERROR_NOTSUPPORTED`. MQT Core now builds its MLIR-based compiler infrastructure by default. This configuration requires LLVM 23.1+ with MLIR and includes QIR support in the -DDSIM QDMI device. Set `BUILD_MQT_CORE_MLIR=OFF` to build the core IR, decision -diagram, OpenQASM, and QDMI libraries without LLVM/MLIR or DDSIM QIR support. -The DDSIM device continues to support OpenQASM 2 and OpenQASM 3 programs. +DDSIM QDMI device. Set `BUILD_MQT_CORE_MLIR=OFF` to build the decision-diagram +and QDMI libraries without LLVM/MLIR. The compiler-backed DDSIM device requires +MLIR and is skipped in this configuration. We offer pre-built distributions for all supported platforms as part of the `setup-mlir` project at diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 33784543be..53df01f98f 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -7,7 +7,6 @@ # Licensed under the MIT License add_subdirectory(bench) -add_subdirectory(ir) add_subdirectory(dd) add_subdirectory(qdmi) if(BUILD_MQT_CORE_MLIR) diff --git a/bindings/dd/register_dd.cpp b/bindings/dd/register_dd.cpp index 1dbea8fbe2..9daab813d6 100644 --- a/bindings/dd/register_dd.cpp +++ b/bindings/dd/register_dd.cpp @@ -9,22 +9,11 @@ */ #include "dd/DDDefinitions.hpp" -#include "dd/FunctionalityConstruction.hpp" -#include "dd/Node.hpp" -#include "dd/Package.hpp" -#include "dd/Simulation.hpp" -#include "dd/StateGeneration.hpp" -#include "ir/QuantumComputation.hpp" #include -#include -#include // NOLINT(misc-include-cleaner) +#include #include // NOLINT(misc-include-cleaner) -#include -#include -#include - namespace mqt { namespace nb = nanobind; @@ -35,16 +24,42 @@ void registerVectorDDs(const nb::module_& m); void registerMatrixDDs(const nb::module_& m); void registerDDPackage(const nb::module_& m); -using Vector = nb::ndarray, nb::ndim<1>>; -Vector getVector(const dd::vEdge& v, dd::fp threshold = 0.); +namespace { +void registerControl(const nb::module_& m) { + auto control = nb::class_( + m, "Control", + R"pb(Control a raw matrix DD operation with one qubit. -using Matrix = nb::ndarray, nb::ndim<2>>; -Matrix getMatrix(const dd::mEdge& m, size_t numQubits, dd::fp threshold = 0.); +Args: + qubit: Control qubit index. + type_: Control polarity.)pb"); + + nb::enum_(control, "Type", "Control polarity.") + .value("Pos", dd::Control::Type::Pos) + .value("Neg", dd::Control::Type::Neg); + + control.def(nb::init(), "qubit"_a, + "type_"_a.sig("...") = dd::Control::Type::Pos); + control.def_ro("qubit", &dd::Control::qubit, "Control qubit index."); + control.def_ro("type_", &dd::Control::type, "Control polarity."); + control.def("__str__", &dd::Control::toString); + control.def("__repr__", &dd::Control::toString); + control.def(nb::self == nb::self, + nb::sig("def __eq__(self, arg: object, /) -> bool")); + control.def(nb::self != nb::self, + nb::sig("def __ne__(self, arg: object, /) -> bool")); + control.def(nb::hash(nb::self)); + + nb::implicitly_convertible(); +} +} // namespace +/// NOLINTNEXTLINE(performance-unnecessary-value-param) NB_MODULE(MQT_CORE_MODULE_NAME, m) { - m.doc() = R"pb(MQT Core DD - The MQT Core Decision Diagram (DD) module.)pb"; + m.doc() = "MQT Core decision diagram module."; - nb::module_::import_("mqt.core.ir"); + /// Controls for raw matrix DD construction. + registerControl(m); // Vector Decision Diagrams registerVectorDDs(m); @@ -54,120 +69,6 @@ NB_MODULE(MQT_CORE_MODULE_NAME, m) { // DD Package registerDDPackage(m); - - m.def( - "sample", - [](const qc::QuantumComputation& qc, const size_t shots = 1024U, - const size_t seed = 0U) { return dd::sample(qc, shots, seed); }, - "qc"_a, "shots"_a = 1024U, "seed"_a = 0U, - R"pb(Sample from the output distribution of a quantum computation. - -This function classically simulates the quantum computation and repeatedly samples from the output distribution. -It supports mid-circuit measurements, resets, and classical control. - -Args: - qc: The quantum computation. - shots: The number of samples to take. - If the quantum computation contains no mid-circuit measurements or resets, the circuit is simulated once and the samples are drawn from the final state. - Otherwise, the circuit is simulated once for each sample. - Defaults to 1024. - seed: The seed for the random number generator. - If set to a specific non-zero value, the simulation is deterministic. - If set to 0, the RNG is randomly seeded. - Defaults to 0. - -Returns: - A histogram of the samples. - Each sample is a bitstring representing the measurement outcomes of the qubits in the quantum computation. - The leftmost bit corresponds to the most significant qubit, that is, the qubit with the highest index (big-endian). - If the circuit contains measurements, only the qubits that are actively measured are included in the output distribution. - Otherwise, all qubits in the circuit are measured.)pb"); - - m.def( - "simulate_statevector", - [](const qc::QuantumComputation& qc) { - const auto dd = std::make_unique(qc.getNqubits()); - const auto in = makeZeroState(qc.getNqubits(), *dd); - const auto sim = simulate(qc, in, *dd); - return getVector(sim); - }, - "qc"_a, - R"pb(Simulate the quantum computation and return the final state vector. - -This function classically simulates the quantum computation and returns the state vector of the final state. -It does not support measurements, resets, or classical control. - -Since the state vector is guaranteed to be exponentially large in the number of qubits, this function is only suitable for small quantum computations. -Consider using the :func:`~mqt.core.dd.simulate` or the :func:`~mqt.core.dd.sample` functions, which never explicitly construct the state vector, for larger quantum computations. - -Notes: - This function internally constructs a :class:`~mqt.core.dd.DDPackage`, creates the zero state, and simulates the quantum computation via the :func:`simulate` function. - The state vector is then extracted from the resulting DD via the :meth:`~mqt.core.dd.VectorDD.get_vector` method. - -Args: - qc: The quantum computation. Must only contain unitary operations. - -Returns: - The state vector of the final state.)pb"); - - m.def( - "build_unitary", - [](const qc::QuantumComputation& qc) { - const auto dd = std::make_unique(qc.getNqubits()); - const auto u = buildFunctionality(qc, *dd); - return getMatrix(u, qc.getNqubits()); - }, - "qc"_a, - R"pb(Build a unitary matrix representation of a quantum computation. - -This function builds a matrix representation of the unitary representing the functionality of a quantum computation. -This function does not support measurements, resets, or classical control, as the corresponding operations are non-unitary. - -Since the unitary matrix is guaranteed to be exponentially large in the number of qubits, this function is only suitable for small quantum computations. -Consider using the :func:`~mqt.core.dd.build_functionality` function, which never explicitly constructs the unitary matrix, for larger quantum computations. - -Notes: - This function internally constructs a :class:`~mqt.core.dd.DDPackage`, creates the identity matrix, and builds the unitary matrix via the :func:`~mqt.core.dd.build_functionality` function. - The unitary matrix is then extracted from the resulting DD via the :meth:`~mqt.core.dd.MatrixDD.get_matrix` method. - -Args: - qc: The quantum computation. Must only contain unitary operations. - -Returns: - The unitary matrix representing the functionality of the quantum computation.)pb"); - - m.def("simulate", &dd::simulate, "qc"_a, "initial_state"_a, "dd_package"_a, - R"pb(Simulate a quantum computation. - -This function classically simulates a quantum computation for a given initial state and returns the final state (represented as a DD). -Compared to the `sample` function, this function does not support measurements, resets, or classical control. -It only supports unitary operations. - -The simulation is effectively computed by sequentially applying the operations of the quantum computation to the initial state. - -Args: - qc: The quantum computation. Must only contain unitary operations. - initial_state: The initial state as a DD. Must have the same number of qubits as the quantum computation. - The reference count of the initial state is decremented during the simulation, so the caller must ensure that the initial state has a non-zero reference count. - dd_package: The DD package. Must be configured with a sufficient number of qubits to accommodate the quantum computation. - -Returns: - The final state as a DD. The reference count of the final state is non-zero and must be manually decremented by the caller if it is no longer needed.)pb"); - - m.def("build_functionality", &dd::buildFunctionality, "qc"_a, "dd_package"_a, - nb::keep_alive<0, 2>(), - R"pb(Build a functional representation of a quantum computation. - -This function builds a matrix DD representation of the unitary representing the functionality of a quantum computation. -This function does not support measurements, resets, or classical control, as the corresponding operations are non-unitary. - -Args: - qc: The quantum computation. - Must only contain unitary operations. - dd_package: The DD package. Must be configured with a sufficient number of qubits to accommodate the quantum computation. - -Returns: - The functionality as a DD. The reference count of the result is non-zero and must be manually decremented by the caller if it is no longer needed.)pb"); } } // namespace mqt diff --git a/bindings/dd/register_dd_package.cpp b/bindings/dd/register_dd_package.cpp index 60f5f7ce79..0dc2f2e6bb 100644 --- a/bindings/dd/register_dd_package.cpp +++ b/bindings/dd/register_dd_package.cpp @@ -11,19 +11,12 @@ #include "dd/CachedEdge.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Node.hpp" -#include "dd/Operations.hpp" #include "dd/Package.hpp" #include "dd/StateGeneration.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/Operation.hpp" #include #include #include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) @@ -34,7 +27,6 @@ #include #include #include -#include #include namespace mqt { @@ -296,106 +288,6 @@ Specifically, it The DD for the vector. The resulting state is guaranteed to have its reference count increased.)pb"); - dd.def( - "apply_unitary_operation", - [](dd::Package& p, const dd::vEdge& v, const qc::Operation& op, - const qc::Permutation& perm = {}) { - return applyUnitaryOperation(op, v, p, perm); - }, - "vec"_a, "operation"_a, "permutation"_a = qc::Permutation{}, - // keep the DD package alive while the returned vector DD is alive. - nb::keep_alive<0, 1>(), R"pb(Apply a unitary operation to the DD. - -Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - -Args: - vec: The input DD. - operation: The operation. Must be unitary. - permutation: The permutation of the qubits. Defaults to the identity permutation. - -Returns: - The resulting DD.)pb"); - - dd.def( - "apply_measurement", - [](dd::Package& p, const dd::vEdge& v, const qc::NonUnitaryOperation& op, - const std::vector& measurements, - const qc::Permutation& perm = {}) { - static thread_local std::mt19937_64 rng(std::random_device{}()); - auto measurementsCopy = measurements; - return std::pair{ - applyMeasurement(op, v, p, rng, measurementsCopy, perm), - measurementsCopy}; - }, - "vec"_a, "operation"_a, "measurements"_a, - "permutation"_a = qc::Permutation{}, - // keep the DD package alive while the returned vector DD is alive. - nb::keep_alive<0, 1>(), R"pb(Apply a measurement to the DD. - -Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count - -Args: - vec: The input DD. - operation: The measurement operation. - measurements: A list of bits with existing measurement outcomes. - permutation: The permutation of the qubits. Defaults to the identity permutation. - -Returns: - The resulting DD after the measurement as well as the updated measurement outcomes.)pb"); - - dd.def( - "apply_reset", - [](dd::Package& p, const dd::vEdge& v, const qc::NonUnitaryOperation& op, - const qc::Permutation& perm = {}) { - static thread_local std::mt19937_64 rng(std::random_device{}()); - return applyReset(op, v, p, rng, perm); - }, - "vec"_a, "operation"_a, "permutation"_a = qc::Permutation{}, - // keep the DD package alive while the returned vector DD is alive. - nb::keep_alive<0, 1>(), R"pb(Apply a reset to the DD. - -Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - -Args: - vec: The input DD. - operation: The reset operation. - permutation: The permutation of the qubits. Defaults to the identity permutation. - -Returns: - The resulting DD after the reset.)pb"); - - dd.def( - "apply_if_else_operation", - [](dd::Package& p, const dd::vEdge& v, const qc::IfElseOperation& op, - const std::vector& measurements, - const qc::Permutation& perm = {}) { - return applyIfElseOperation(op, v, p, measurements, perm); - }, - "vec"_a, "operation"_a, "measurements"_a, - "permutation"_a = qc::Permutation{}, - // keep the DD package alive while the returned vector DD is alive. - nb::keep_alive<0, 1>(), - R"pb(Apply a classically controlled operation to the DD. - -Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - -Args: - vec: The input DD. - operation: The classically controlled operation. - measurements: A list of bits with stored measurement outcomes. - permutation: The permutation of the qubits. Defaults to the identity permutation. - -Returns: - The resulting DD after the operation.)pb"); - dd.def( "measure_collapsing", [](dd::Package& p, dd::vEdge& v, const dd::Qubit q) { @@ -463,7 +355,7 @@ Specifically, it dd.def( "controlled_single_qubit_gate", [](dd::Package& p, const SingleQubitMatrix& mat, - const qc::Control& control, const dd::Qubit target) { + const dd::Control& control, const dd::Qubit target) { return p.makeGateDD({mat(0, 0), mat(0, 1), mat(1, 0), mat(1, 1)}, control, target); }, @@ -473,7 +365,7 @@ Specifically, it nb::sig( "def controlled_single_qubit_gate(self, " "matrix: Annotated[NDArray[numpy.complex128], {\"shape\": (2, 2)}]," - "control: mqt.core.ir.operations.Control | int," + "control: mqt.core.dd.Control | int," "target: int) -> mqt.core.dd.MatrixDD"), R"pb(Create the DD for a controlled single-qubit gate. @@ -488,7 +380,7 @@ Specifically, it dd.def( "multi_controlled_single_qubit_gate", [](dd::Package& p, const SingleQubitMatrix& mat, - const qc::Controls& controls, const dd::Qubit target) { + const dd::Controls& controls, const dd::Qubit target) { return p.makeGateDD({mat(0, 0), mat(0, 1), mat(1, 0), mat(1, 1)}, controls, target); }, @@ -498,7 +390,7 @@ Specifically, it nb::sig( "def multi_controlled_single_qubit_gate(self, " "matrix: Annotated[NDArray[numpy.complex128], {\"shape\": (2, 2)}]," - "controls: collections.abc.Set[mqt.core.ir.operations.Control | int]," + "controls: collections.abc.Set[mqt.core.dd.Control | int]," "target: int) -> mqt.core.dd.MatrixDD"), R"pb(Create the DD for a multi-controlled single-qubit gate. @@ -535,7 +427,7 @@ Specifically, it dd.def( "controlled_two_qubit_gate", - [](dd::Package& p, const TwoQubitMatrix& mat, const qc::Control& control, + [](dd::Package& p, const TwoQubitMatrix& mat, const dd::Control& control, const dd::Qubit target0, const dd::Qubit target1) { return p.makeTwoQubitGateDD( {std::array{mat(0, 0), mat(0, 1), mat(0, 2), mat(0, 3)}, @@ -550,7 +442,7 @@ Specifically, it nb::sig( "def controlled_two_qubit_gate(self, " "matrix: Annotated[NDArray[numpy.complex128], {\"shape\": (4, 4)}]," - "control: mqt.core.ir.operations.Control | int," + "control: mqt.core.dd.Control | int," "target0: int, target1: int) -> mqt.core.dd.MatrixDD"), R"pb(Create the DD for a controlled two-qubit gate. @@ -566,7 +458,7 @@ Specifically, it dd.def( "multi_controlled_two_qubit_gate", [](dd::Package& p, const TwoQubitMatrix& mat, - const qc::Controls& controls, const dd::Qubit target0, + const dd::Controls& controls, const dd::Qubit target0, const dd::Qubit target1) { return p.makeTwoQubitGateDD( {std::array{mat(0, 0), mat(0, 1), mat(0, 2), mat(0, 3)}, @@ -581,7 +473,7 @@ Specifically, it nb::sig( "def multi_controlled_two_qubit_gate(self, " "matrix: Annotated[NDArray[numpy.complex128], {\"shape\": (4, 4)}]," - "controls: collections.abc.Set[mqt.core.ir.operations.Control | int]," + "controls: collections.abc.Set[mqt.core.dd.Control | int]," "target0: int, target1: int) -> mqt.core.dd.MatrixDD"), R"pb(Create the DD for a multi-controlled two-qubit gate. @@ -626,25 +518,6 @@ Specifically, it Returns: The DD for the matrix.)pb"); - dd.def( - "from_operation", - [](dd::Package& p, const qc::Operation& op, const bool invert = false) { - if (invert) { - return getInverseDD(op, p); - } - return getDD(op, p); - }, - "operation"_a, "invert"_a = false, - // keep the DD package alive while the returned matrix DD is alive. - nb::keep_alive<0, 1>(), R"pb(Create a DD from an operation. - -Args: - operation: The operation. Must be unitary. - invert: Whether to get the inverse of the operation. - -Returns: - The DD for the operation.)pb"); - // Reference counting and garbage collection dd.def("inc_ref_vec", &dd::Package::incRef, "vec"_a, "Increment the reference count of a vector."); diff --git a/bindings/ir/CMakeLists.txt b/bindings/ir/CMakeLists.txt deleted file mode 100644 index 695639ef2b..0000000000 --- a/bindings/ir/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -if(NOT TARGET ${MQT_CORE_TARGET_NAME}-ir-bindings) - # collect source files - file(GLOB_RECURSE IR_SOURCES **.cpp) - - # declare the Python module - add_mqt_python_binding( - CORE - ${MQT_CORE_TARGET_NAME}-ir-bindings - ${IR_SOURCES} - MODULE_NAME - ir - INSTALL_DIR - . - LINK_LIBS - MQT::CoreIR - MQT::CoreQASM) - - # install the Python stub files in editable mode for better IDE support - if(SKBUILD_STATE STREQUAL "editable") - file(GLOB_RECURSE IR_PYI_FILES ${PROJECT_SOURCE_DIR}/python/mqt/core/ir/*.pyi) - install( - FILES ${IR_PYI_FILES} - DESTINATION ./ir - COMPONENT ${MQT_CORE_TARGET_NAME}_Python) - endif() -endif() diff --git a/bindings/ir/operations/register_compound_operation.cpp b/bindings/ir/operations/register_compound_operation.cpp deleted file mode 100644 index 99b1437c3e..0000000000 --- a/bindings/ir/operations/register_compound_operation.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -using DiffType = std::vector>::difference_type; -using SizeType = std::vector>::size_type; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerCompoundOperation(const nb::module_& m) { - auto wrap = [](DiffType i, const SizeType size) { - if (i < 0) { - i += static_cast(size); - } - if (i < 0 || std::cmp_greater_equal(i, size)) { - throw nb::index_error(); - } - return i; - }; - - nb::class_( - m, "CompoundOperation", - nb::sig( - "class CompoundOperation(mqt.core.ir.operations.Operation, " - "collections.abc.MutableSequence[mqt.core.ir.operations.Operation])"), - R"pb(Compound quantum operation. - -This class is used to aggregate and group multiple operations into a single object. -This is useful for optimizations and for representing complex quantum functionality. -A :class:`CompoundOperation` can contain any number of operations, including other :class:`CompoundOperation`'s. - -Args: - ops: The operations that are part of the compound operation.)pb") - - .def(nb::init<>()) - .def( - "__init__", - [](qc::CompoundOperation* self, - const std::vector& ops) { - std::vector> uniqueOps; - uniqueOps.reserve(ops.size()); - for (const auto& op : ops) { - assert(op != nullptr && "ops must not contain nullptr"); - uniqueOps.emplace_back(op->clone()); - } - new (self) qc::CompoundOperation(std::move(uniqueOps)); - }, - "ops"_a) - - .def("__len__", &qc::CompoundOperation::size, - "The number of operations in the compound operation.") - - .def( - "__getitem__", - [wrap](const qc::CompoundOperation& op, DiffType i) { - i = wrap(i, op.size()); - return op.at(static_cast(i)).get(); - }, - nb::rv_policy::reference_internal, "index"_a, - R"pb(Get the operation at the given index. - -Note: - This gives direct access to the operations in the compound operation - -Args: - index: The index of the operation to get. - -Returns: - The operation at the given index.)pb") - - .def( - "__getitem__", - [](const qc::CompoundOperation& op, const nb::slice& slice) { - auto [start, stop, step, sliceLength] = slice.compute(op.size()); - auto ops = std::vector(); - ops.reserve(sliceLength); - for (std::size_t i = 0; i < sliceLength; ++i) { - auto idx = static_cast(start) + - (static_cast(i) * step); - ops.emplace_back(op.at(static_cast(idx)).get()); - } - return ops; - }, - nb::rv_policy::reference_internal, "index"_a, - R"pb(Get the operations in the given slice. - -Note: - This gives direct access to the operations in the compound operation. - -Args: - index: The slice of the operations to get. - -Returns: - The operations in the given slice.)pb") - - .def( - "__setitem__", - [wrap](qc::CompoundOperation& compOp, DiffType i, - const qc::Operation& op) { - i = wrap(i, compOp.size()); - compOp[static_cast(i)] = op.clone(); - }, - "index"_a, "value"_a, R"pb(Set the operation at the given index. - -Args: - index: The index of the operation to set. - value: The operation to set at the given index.)pb") - - .def( - "__setitem__", - [](qc::CompoundOperation& compOp, const nb::slice& slice, - const std::vector& ops) { - auto [start, stop, step, sliceLength] = - slice.compute(compOp.size()); - if (sliceLength != ops.size()) { - throw std::runtime_error( - "Length of slice and number of operations do not match."); - } - for (std::size_t i = 0; i < sliceLength; ++i) { - assert(ops[i] != nullptr && "ops must not contain nullptr"); - compOp[static_cast(start)] = ops[i]->clone(); - start += step; - } - }, - nb::sig("def __setitem__(self, index: slice, value: " - "collections.abc.Iterable[mqt.core.ir.operations.Operation]) " - "-> None"), - R"pb(Set the operations in the given slice. - -Args: - index: The slice of operations to set. - value: The operations to set in the given slice.)pb") - - .def( - "__delitem__", - [wrap](qc::CompoundOperation& op, DiffType i) { - i = wrap(i, op.size()); - op.erase(op.begin() + i); - }, - "index"_a, R"pb(Delete the operation at the given index. - -Args: - index: The index of the operation to delete.)pb") - - .def( - "__delitem__", - [](qc::CompoundOperation& op, const nb::slice& slice) { - auto [start, stop, step, sliceLength] = slice.compute(op.size()); - // Delete in reverse order to not invalidate indices - std::vector indices; - indices.reserve(sliceLength); - for (std::size_t i = 0; i < sliceLength; ++i) { - indices.emplace_back(static_cast(start) + - (static_cast(i) * step)); - } - std::ranges::sort(indices, std::greater<>()); - for (const auto idx : indices) { - op.erase(op.begin() + idx); - } - }, - "index"_a, R"pb(Delete the operations in the given slice. - -Args: - index: The slice of operations to delete.)pb") - - .def( - "append", - [](qc::CompoundOperation& compOp, const qc::Operation& op) { - compOp.emplace_back(op.clone()); - }, - "value"_a, "Append an operation to the compound operation.") - - .def( - "insert", - [](qc::CompoundOperation& compOp, const std::size_t idx, - const qc::Operation& op) { - compOp.insert(compOp.begin() + static_cast(idx), - op.clone()); - }, - "index"_a, "value"_a, R"pb(Insert an operation at the given index. - -Args: - index: The index to insert the operation at. - value: The operation to insert.)pb") - - .def("empty", &qc::CompoundOperation::empty, - "Check if the compound operation is empty.") - - .def("clear", &qc::CompoundOperation::clear, - "Clear all operations in the compound operation.") - - .def("__repr__", [](const qc::CompoundOperation& op) { - std::stringstream ss; - ss << "CompoundOperation([..." << op.size() << " ops...])"; - return ss.str(); - }); -} -} // namespace mqt diff --git a/bindings/ir/operations/register_control.cpp b/bindings/ir/operations/register_control.cpp deleted file mode 100644 index fa6da5a54d..0000000000 --- a/bindings/ir/operations/register_control.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" - -#include -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerControl(const nb::module_& m) { - auto control = nb::class_( - m, "Control", - R"pb(A control is a pair of a qubit and a type. The type can be either positive or negative. - -Args: - qubit: The qubit that is the control. - type_: The type of the control.)pb"); - - nb::enum_(control, "Type", "Enumeration of control types.") - .value("Pos", qc::Control::Type::Pos) - .value("Neg", qc::Control::Type::Neg); - - control.def(nb::init(), "qubit"_a, - "type_"_a.sig("...") = qc::Control::Type::Pos); - - control.def_ro("qubit", &qc::Control::qubit, - "The qubit that is the control."); - - control.def_ro("type_", &qc::Control::type, "The type of the control."); - - control.def("__str__", [](const qc::Control& c) { return c.toString(); }); - control.def("__repr__", [](const qc::Control& c) { return c.toString(); }); - - control.def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")); - control.def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")); - control.def(nb::hash(nb::self)); - - nb::implicitly_convertible(); -} - -} // namespace mqt diff --git a/bindings/ir/operations/register_if_else_operation.cpp b/bindings/ir/operations/register_if_else_operation.cpp deleted file mode 100644 index 811207cbaa..0000000000 --- a/bindings/ir/operations/register_if_else_operation.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/Register.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerIfElseOperation(const nb::module_& m) { - nb::enum_( - m, "ComparisonKind", - "Enumeration of comparison types for classic-controlled operations.") - .value("eq", qc::ComparisonKind::Eq, "Equality comparison.") - .value("neq", qc::ComparisonKind::Neq, "Inequality comparison.") - .value("lt", qc::ComparisonKind::Lt, "Less-than comparison.") - .value("leq", qc::ComparisonKind::Leq, "Less-than-or-equal comparison.") - .value("gt", qc::ComparisonKind::Gt, "Greater-than comparison.") - .value("geq", qc::ComparisonKind::Geq, - "Greater-than-or-equal comparison."); - - auto ifElse = nb::class_( - m, "IfElseOperation", R"pb(If-else quantum operation. - -This class is used to represent an if-else operation. -The then operation is executed if the value of the classical register matches the expected value. -Otherwise, the else operation is executed. - -Args: - then_operation: The operation that is executed if the condition is met. - else_operation: The operation that is executed if the condition is not met. - control_register: The classical register that controls the operation. - expected_value: The expected value of the classical register. - comparison_kind: The kind of comparison (default is equality).)pb"); - - ifElse.def( - "__init__", - [](qc::IfElseOperation* self, qc::Operation* thenOp, - qc::Operation* elseOp, qc::ClassicalRegister& controlReg, - const std::uint64_t expectedVal, const qc::ComparisonKind kind) { - std::unique_ptr thenPtr = - thenOp ? thenOp->clone() : nullptr; - std::unique_ptr elsePtr = - elseOp ? elseOp->clone() : nullptr; - new (self) qc::IfElseOperation(std::move(thenPtr), std::move(elsePtr), - controlReg, expectedVal, kind); - }, - "then_operation"_a, nb::arg("else_operation").none(), - "control_register"_a, "expected_value"_a = 1U, - "comparison_kind"_a = qc::ComparisonKind::Eq); - ifElse.def( - "__init__", - [](qc::IfElseOperation* self, qc::Operation* thenOp, - qc::Operation* elseOp, qc::Bit controlBit, bool expectedVal, - qc::ComparisonKind kind) { - std::unique_ptr thenPtr = - thenOp ? thenOp->clone() : nullptr; - std::unique_ptr elsePtr = - elseOp ? elseOp->clone() : nullptr; - new (self) qc::IfElseOperation(std::move(thenPtr), std::move(elsePtr), - controlBit, expectedVal, kind); - }, - "then_operation"_a, nb::arg("else_operation").none(), "control_bit"_a, - "expected_value"_a = true, "comparison_kind"_a = qc::ComparisonKind::Eq); - - ifElse.def_prop_ro("then_operation", &qc::IfElseOperation::getThenOp, - nb::rv_policy::reference_internal, - "The operation that is executed if the condition is met."); - - ifElse.def_prop_ro( - "else_operation", &qc::IfElseOperation::getElseOp, - nb::rv_policy::reference_internal, - nb::sig("def else_operation(self) -> " - "mqt.core.ir.operations.Operation | None"), - "The operation that is executed if the condition is not met."); - - ifElse.def_prop_ro("control_register", - &qc::IfElseOperation::getControlRegister, - "The classical register that controls the operation."); - - ifElse.def_prop_ro("control_bit", &qc::IfElseOperation::getControlBit, - "The classical bit that controls the operation."); - - ifElse.def_prop_ro("expected_value_register", - &qc::IfElseOperation::getExpectedValueRegister, - R"pb(The expected value of the classical register. - -The then-operation is executed if the value of the classical register matches the expected value based on the kind of comparison. -The expected value is an integer that is interpreted as a binary number, where the least significant bit is at the start index of the classical register.)pb"); - - ifElse.def_prop_ro("expected_value_bit", - &qc::IfElseOperation::getExpectedValueBit, - R"pb(The expected value of the classical bit. - -The then-operation is executed if the value of the classical bit matches the expected value based on the kind of comparison.)pb"); - - ifElse.def_prop_ro("comparison_kind", &qc::IfElseOperation::getComparisonKind, - R"pb(The kind of comparison. - -The then-operation is executed if the value of the control matches the expected value based on the kind of comparison.)pb"); - - ifElse.def("__repr__", [](const qc::IfElseOperation& op) { - std::stringstream ss; - ss << "IfElseOperation(<...then-op...>, <...else-op...>, "; - if (const auto& controlReg = op.getControlRegister(); - controlReg.has_value()) { - ss << "control_register=ClassicalRegister(" << controlReg->getSize() - << ", " << controlReg->getStartIndex() << ", " << controlReg->getName() - << "), " - << "expected_value=" << op.getExpectedValueRegister() << ", "; - } - if (const auto& controlBit = op.getControlBit(); controlBit.has_value()) { - ss << "control_bit=" << controlBit.value() << ", " - << "expected_value=" << op.getExpectedValueBit() << ", "; - } - ss << "comparison_kind='" << op.getComparisonKind() << "')"; - return ss.str(); - }); -} - -} // namespace mqt diff --git a/bindings/ir/operations/register_non_unitary_operation.cpp b/bindings/ir/operations/register_non_unitary_operation.cpp deleted file mode 100644 index f37db12c90..0000000000 --- a/bindings/ir/operations/register_non_unitary_operation.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerNonUnitaryOperation(const nb::module_& m) { - nb::class_(m, "NonUnitaryOperation", - R"pb(Non-unitary operation. - -This class is used to represent all non-unitary operations, i.e., operations that are not reversible. -This includes measurements and resets. - -Args: - targets: The target qubit(s) of the operation. - classics: The classical bit(s) that are associated with the operation (only relevant for measurements). - op_type: The type of the operation.)pb") - - .def(nb::init, std::vector>(), - "targets"_a, "classics"_a) - .def(nb::init(), "target"_a, "classic"_a) - .def(nb::init, qc::OpType>(), "targets"_a, - "op_type"_a = qc::OpType::Reset) - - .def_prop_ro("classics", - nb::overload_cast<>(&qc::NonUnitaryOperation::getClassics, - nb::const_), - "The classical bits that are associated with the operation.") - - .def("__repr__", [](const qc::NonUnitaryOperation& op) { - std::stringstream ss; - ss << "NonUnitaryOperation("; - const auto& targets = op.getTargets(); - if (targets.size() == 1U) { - ss << "target=" << targets[0]; - } else { - ss << "targets=["; - for (const auto& target : targets) { - ss << target << ", "; - } - ss << "]"; - } - const auto& classics = op.getClassics(); - if (!classics.empty()) { - ss << ", "; - if (classics.size() == 1U) { - ss << "classic=" << classics[0]; - } else { - ss << "classics=["; - for (const auto& classic : classics) { - ss << classic << ", "; - } - ss << "]"; - } - } - ss << ")"; - return ss.str(); - }); -} - -} // namespace mqt diff --git a/bindings/ir/operations/register_operation.cpp b/bindings/ir/operations/register_operation.cpp deleted file mode 100644 index 0220e1de78..0000000000 --- a/bindings/ir/operations/register_operation.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Control.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerOperation(const nb::module_& m) { - nb::class_(m, "Operation") - .def_prop_ro("name", &qc::Operation::getName, - "The name of the operation.") - - .def_prop_rw("type_", &qc::Operation::getType, &qc::Operation::setGate, - "The type of the operation.") - - .def_prop_rw( - "targets", [](const qc::Operation& op) { return op.getTargets(); }, - &qc::Operation::setTargets, R"pb(The targets of the operation. - -Note: - The notion of a target might not make sense for all types of operations.)pb") - - .def_prop_ro("num_targets", &qc::Operation::getNtargets, - "The number of targets of the operation.") - - .def_prop_rw( - "controls", [](const qc::Operation& op) { return op.getControls(); }, - &qc::Operation::setControls, R"pb(The controls of the operation. - -Note: - The notion of a control might not make sense for all types of operations.)pb") - - .def_prop_ro("num_controls", &qc::Operation::getNcontrols, - "The number of controls of the operation.") - - .def("add_control", &qc::Operation::addControl, "control"_a, - R"pb(Add a control to the operation. - -Args: - control: The control to add.)pb") - - .def("add_controls", &qc::Operation::addControls, "controls"_a, - R"pb(Add multiple controls to the operation. - -Args: - controls: The controls to add.)pb") - - .def("clear_controls", &qc::Operation::clearControls, - "Clear all controls of the operation.") - - .def( - "remove_control", - [](qc::Operation& op, const qc::Control& c) { op.removeControl(c); }, - "control"_a, R"pb(Remove a control from the operation. - -Args: - control: The control to remove.)pb") - - .def("remove_controls", &qc::Operation::removeControls, "controls"_a, - R"pb(Remove multiple controls from the operation. - -Args: - controls: The controls to remove.)pb") - - .def("get_used_qubits", &qc::Operation::getUsedQubits, - R"pb(Get the qubits that are used by the operation. - -Returns: - The set of qubits that are used by the operation.)pb") - - .def("acts_on", &qc::Operation::actsOn, "qubit"_a, - R"pb(Check if the operation acts on a specific qubit. - -Args: - qubit: The qubit to check. - -Returns: - True if the operation acts on the qubit, False otherwise.)pb") - - .def_prop_rw( - "parameter", - [](const qc::Operation& op) { return op.getParameter(); }, - &qc::Operation::setParameter, R"pb(The parameters of the operation. - -Note: - The notion of a parameter might not make sense for all types of operations.)pb") - - .def("is_unitary", &qc::Operation::isUnitary, - R"pb(Check if the operation is unitary. - -Returns: - True if the operation is unitary, False otherwise.)pb") - - .def("is_standard_operation", &qc::Operation::isStandardOperation, - R"pb(Check if the operation is a :class:`StandardOperation`. - -Returns: - True if the operation is a :class:`StandardOperation`, False otherwise.)pb") - - .def("is_compound_operation", &qc::Operation::isCompoundOperation, - R"pb(Check if the operation is a :class:`CompoundOperation`. - -Returns: - True if the operation is a :class:`CompoundOperation`, False otherwise.)pb") - - .def("is_non_unitary_operation", &qc::Operation::isNonUnitaryOperation, - R"pb(Check if the operation is a :class:`NonUnitaryOperation`. - -Returns: - True if the operation is a :class:`NonUnitaryOperation`, False otherwise.)pb") - - .def("is_if_else_operation", &qc::Operation::isIfElseOperation, - R"pb(Check if the operation is a :class:`IfElseOperation`. - -Returns: - True if the operation is a :class:`IfElseOperation`, False otherwise.)pb") - - .def("is_symbolic_operation", &qc::Operation::isSymbolicOperation, - R"pb(Check if the operation is a :class:`SymbolicOperation`. - -Returns: - True if the operation is a :class:`SymbolicOperation`, False otherwise.)pb") - - .def("is_controlled", &qc::Operation::isControlled, - R"pb(Check if the operation is controlled. - -Returns: - True if the operation is controlled, False otherwise.)pb") - - .def("get_inverted", &qc::Operation::getInverted, - R"pb(Get the inverse of the operation. - -Returns: - The inverse of the operation.)pb") - - .def("invert", &qc::Operation::invert, "Invert the operation (in-place).") - - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - - .def("__repr__", [](const qc::Operation& op) { - std::ostringstream oss; - oss << "Operation(type=" << op.getType() << ", ...)"; - return oss.str(); - }); -} -} // namespace mqt diff --git a/bindings/ir/operations/register_optype.cpp b/bindings/ir/operations/register_optype.cpp deleted file mode 100644 index 339807551d..0000000000 --- a/bindings/ir/operations/register_optype.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/OpType.hpp" - -#include - -namespace mqt { - -namespace nb = nanobind; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerOptype(const nb::module_& m) { - nb::enum_(m, "OpType", "Enumeration of operation types.") - - .value("none", qc::OpType::None, R"pb(A placeholder operation. - -It is used to represent an operation that is not yet defined.)pb") - - .value("gphase", qc::OpType::GPhase, R"pb(A global phase operation. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.gphase`)pb") - - .value("i", qc::OpType::I, R"pb(An identity operation. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.i`)pb") - - .value("h", qc::OpType::H, R"pb(A Hadamard gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.h`)pb") - - .value("x", qc::OpType::X, R"pb(An X gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.x`)pb") - - .value("y", qc::OpType::Y, R"pb(A Y gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.y`)pb") - - .value("z", qc::OpType::Z, R"pb(A Z gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.z`)pb") - - .value("s", qc::OpType::S, R"pb(An S gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.s`)pb") - - .value("sdg", qc::OpType::Sdg, R"pb(An :math:`S^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.sdg`)pb") - - .value("t", qc::OpType::T, R"pb(A T gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.t`)pb") - - .value("tdg", qc::OpType::Tdg, R"pb(A :math:`T^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.tdg`)pb") - - .value("v", qc::OpType::V, R"pb(A V gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.v`)pb") - - .value("vdg", qc::OpType::Vdg, R"pb(A :math:`V^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.vdg`)pb") - - .value("u", qc::OpType::U, R"pb(A U gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.u`)pb") - - .value("u2", qc::OpType::U2, R"pb(A U2 gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.u2`)pb") - - .value("p", qc::OpType::P, R"pb(A phase gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.p`)pb") - - .value("sx", qc::OpType::SX, R"pb(A :math:`\sqrt{X}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.sx`)pb") - - .value("sxdg", qc::OpType::SXdg, R"pb(A :math:`\sqrt{X}^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.sxdg`)pb") - - .value("rx", qc::OpType::RX, R"pb(A :math:`R_x` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rx`)pb") - - .value("ry", qc::OpType::RY, R"pb(A :math:`R_y` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.ry`)pb") - - .value("rz", qc::OpType::RZ, R"pb(A :math:`R_z` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rz`)pb") - - .value("r", qc::OpType::R, R"pb(An :math:`R` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.r`)pb") - - .value("swap", qc::OpType::SWAP, R"pb(A SWAP gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.swap`)pb") - - .value("iswap", qc::OpType::iSWAP, R"pb(A iSWAP gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.iswap`)pb") - - .value("iswapdg", qc::OpType::iSWAPdg, - R"pb(A :math:`i\text{SWAP}^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.iswapdg`)pb") - - .value("peres", qc::OpType::Peres, R"pb(A Peres gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.peres`)pb") - - .value("peresdg", qc::OpType::Peresdg, - R"pb(A :math:`\text{Peres}^\dagger` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.peresdg`)pb") - - .value("dcx", qc::OpType::DCX, R"pb(A DCX gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.dcx`)pb") - - .value("ecr", qc::OpType::ECR, R"pb(An ECR gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.ecr`)pb") - - .value("rxx", qc::OpType::RXX, R"pb(A :math:`R_{xx}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rxx`)pb") - - .value("ryy", qc::OpType::RYY, R"pb(A :math:`R_{yy}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.ryy`)pb") - - .value("rzz", qc::OpType::RZZ, R"pb(A :math:`R_{zz}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rzz`)pb") - - .value("rzx", qc::OpType::RZX, R"pb(A :math:`R_{zx}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rzx`)pb") - - .value("xx_minus_yy", qc::OpType::XXminusYY, - R"pb(A :math:`R_{XX - YY}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.xx_minus_yy`)pb") - - .value("xx_plus_yy", qc::OpType::XXplusYY, - R"pb(A :math:`R_{XX + YY}` gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.xx_plus_yy`)pb") - - .value("rccx", qc::OpType::RCCX, R"pb(A relative-phase CCX gate. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.rccx`)pb") - - .value("compound", qc::OpType::Compound, R"pb(A compound operation. - -It is used to group multiple operations into a single operation. - -See also :class:`.CompoundOperation`)pb") - - .value("measure", qc::OpType::Measure, R"pb(A measurement operation. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.measure`)pb") - - .value("reset", qc::OpType::Reset, R"pb(A reset operation. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.reset`)pb") - - .value("barrier", qc::OpType::Barrier, R"pb(A barrier operation. - -It is used to separate operations in the circuit. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.barrier`)pb") - - .value("if_else", qc::OpType::IfElse, R"pb(An if-else operation. - -It is used to control the execution of an operation based on the value of a classical register. - -See Also: - :meth:`mqt.core.ir.QuantumComputation.if_else`)pb"); -} - -} // namespace mqt diff --git a/bindings/ir/operations/register_standard_operation.cpp b/bindings/ir/operations/register_standard_operation.cpp deleted file mode 100644 index a3a69d9919..0000000000 --- a/bindings/ir/operations/register_standard_operation.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerStandardOperation(const nb::module_& m) { - nb::class_( - m, "StandardOperation", R"pb(Standard quantum operation. - -This class is used to represent all standard quantum operations, i.e., operations that are unitary. -This includes all possible quantum gates. -Such Operations are defined by their :class:`OpType`, the qubits (controls and targets) they act on, and their parameters. - -Args: - control: The control qubit(s) of the operation (if any). - target: The target qubit(s) of the operation. - op_type: The type of the operation. - params: The parameters of the operation (if any).)pb") - - .def(nb::init<>()) - .def(nb::init>(), "target"_a, - "op_type"_a, "params"_a.sig("...") = std::vector{}) - .def(nb::init>(), - "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "control"_a, "target"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "control"_a, "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "controls"_a, "target"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init>(), - "controls"_a, "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init>(), - "controls"_a, "target0"_a, "target1"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def("__repr__", [](const qc::StandardOperation& op) { - std::stringstream ss; - ss << "StandardOperation("; - const auto& controls = op.getControls(); - if (controls.size() == 1U) { - ss << "control="; - const auto& control = *controls.begin(); - ss << control.toString() << ", "; - } else if (!controls.empty()) { - ss << "controls={"; - for (const auto& control : controls) { - ss << control.toString() << ", "; - } - ss << "}, "; - } - const auto& targets = op.getTargets(); - if (targets.size() == 1U) { - ss << "target=" << targets.front() << ", "; - } else if (!targets.empty()) { - ss << "targets=["; - for (const auto& target : targets) { - ss << target << ", "; - } - ss << "], "; - } - ss << "op_type=" << toString(op.getType()); - const auto& params = op.getParameter(); - if (!params.empty()) { - ss << ", params=["; - for (const auto& param : params) { - ss << param << ", "; - } - ss << "]"; - } - ss << ")"; - return ss.str(); - }); -} -} // namespace mqt diff --git a/bindings/ir/operations/register_symbolic_operation.cpp b/bindings/ir/operations/register_symbolic_operation.cpp deleted file mode 100644 index acc62eee5d..0000000000 --- a/bindings/ir/operations/register_symbolic_operation.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerSymbolicOperation(const nb::module_& m) { - nb::class_( - m, "SymbolicOperation", - R"pb(Symbolic quantum operation. - -This class is used to represent quantum operations that are not yet fully defined. -This can be useful for representing operations that depend on parameters that are not yet known. -A :class:`SymbolicOperation` is defined by its :class:`OpType`, the qubits (controls and targets) it acts on, and its parameters. -The parameters can be either fixed values or symbolic expressions. - -Args: - controls: The control qubit(s) of the operation (if any). - targets: The target qubit(s) of the operation. - op_type: The type of the operation. - params: The parameters of the operation (if any).)pb") - - .def(nb::init<>()) - .def(nb::init&>(), - "target"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "control"_a, "target"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "control"_a, "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "controls"_a, "target"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "controls"_a, "targets"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def(nb::init&>(), - "controls"_a, "target0"_a, "target1"_a, "op_type"_a, - "params"_a.sig("...") = std::vector{}) - .def("get_parameter", &qc::SymbolicOperation::getParameter, "index"_a, - R"pb(Get the parameter at the given index. - -Args: - index: The index of the parameter to get. - -Returns: - The parameter at the given index.)pb") - - .def("get_parameters", &qc::SymbolicOperation::getParameters, - R"pb(Get all parameters of the operation. - -Returns: - The parameters of the operation.)pb") - - .def("get_instantiated_operation", - &qc::SymbolicOperation::getInstantiatedOperation, "assignment"_a, - R"pb(Get the instantiated operation. - -Args: - assignment: The assignment of the symbolic parameters. - -Returns: - The instantiated operation.)pb") - - .def("instantiate", &qc::SymbolicOperation::instantiate, "assignment"_a, - R"pb(Instantiate the operation (in-place). - -Args: - assignment: The assignment of the symbolic parameters.)pb"); -} - -} // namespace mqt diff --git a/bindings/ir/register_ir.cpp b/bindings/ir/register_ir.cpp deleted file mode 100644 index 817a30e243..0000000000 --- a/bindings/ir/register_ir.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include - -namespace mqt { - -namespace nb = nanobind; - -// forward declarations -void registerRegisters(const nb::module_& m); -void registerPermutation(const nb::module_& m); -void registerOperations(const nb::module_& m); -void registerSymbolic(const nb::module_& m); -void registerQuantumComputation(const nb::module_& m); - -NB_MODULE(MQT_CORE_MODULE_NAME, m) { - m.doc() = - R"pb(MQT Core IR - The MQT Core Intermediate Representation (IR) module.)pb"; - - registerPermutation(m); - - const nb::module_ symbolic = m.def_submodule("symbolic"); - registerSymbolic(symbolic); - - const nb::module_ registers = m.def_submodule("registers"); - registerRegisters(registers); - - const nb::module_ operations = m.def_submodule("operations"); - registerOperations(operations); - - registerQuantumComputation(m); -} - -} // namespace mqt diff --git a/bindings/ir/register_operations.cpp b/bindings/ir/register_operations.cpp deleted file mode 100644 index fb6954b2de..0000000000 --- a/bindings/ir/register_operations.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include - -namespace mqt { - -namespace nb = nanobind; - -// forward declarations -void registerOptype(const nb::module_& m); -void registerControl(const nb::module_& m); -void registerOperation(const nb::module_& m); -void registerStandardOperation(const nb::module_& m); -void registerCompoundOperation(const nb::module_& m); -void registerNonUnitaryOperation(const nb::module_& m); -void registerSymbolicOperation(const nb::module_& m); -void registerIfElseOperation(const nb::module_& m); - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerOperations(const nb::module_& m) { - registerOptype(m); - registerControl(m); - registerOperation(m); - registerStandardOperation(m); - registerCompoundOperation(m); - registerNonUnitaryOperation(m); - registerSymbolicOperation(m); - registerIfElseOperation(m); -} -} // namespace mqt diff --git a/bindings/ir/register_permutation.cpp b/bindings/ir/register_permutation.cpp deleted file mode 100644 index b0e52279bd..0000000000 --- a/bindings/ir/register_permutation.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" - -#include -#include -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -namespace { - -qc::Qubit nbIntToQubit(const nb::int_& value) { - const auto valueInt = static_cast(value); - if (valueInt < 0) { - throw nb::value_error("Qubit index cannot be negative"); - } - const auto valueUint = static_cast(valueInt); - if (valueUint > std::numeric_limits::max()) { - throw nb::value_error("Qubit index exceeds maximum value"); - } - return static_cast(valueUint); -} - -} // namespace - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerPermutation(const nb::module_& m) { - nb::class_( - m, "Permutation", - nb::sig("class Permutation(collections.abc.MutableMapping[int, int])"), - R"pb(A class to represent a permutation of the qubits in a quantum circuit. - -Args: - permutation: The permutation to initialize the object with.)pb") - - .def(nb::init<>()) - - .def( - "__init__", - [](qc::Permutation* self, - const nb::typed& p) { - qc::Permutation perm; - for (const auto& [key, value] : p) { - const auto keyQubit = nbIntToQubit(static_cast(key)); - const auto valueQubit = - nbIntToQubit(static_cast(value)); - perm[keyQubit] = valueQubit; - } - new (self) qc::Permutation(std::move(perm)); - }, - "permutation"_a, "Create a permutation from a dictionary.") - - .def("apply", - nb::overload_cast(&qc::Permutation::apply, - nb::const_), - "controls"_a, R"pb(Apply the permutation to a set of controls. - -Args: - controls: The set of controls to apply the permutation to. - -Returns: - The set of controls with the permutation applied.)pb") - - .def("apply", - nb::overload_cast(&qc::Permutation::apply, - nb::const_), - "targets"_a, R"pb(Apply the permutation to a list of targets. - -Args: - targets: The list of targets to apply the permutation to. - -Returns: - The list of targets with the permutation applied.)pb") - - .def( - "clear", [](qc::Permutation& p) { p.clear(); }, - "Clear the permutation of all indices and values.") - - .def( - "__getitem__", - [](const qc::Permutation& p, const nb::int_& index) { - const auto q = nbIntToQubit(index); - const auto it = p.find(q); - if (it == p.end()) { - const auto msg = - std::string("Permutation does not contain index ") + - std::to_string(q); - throw nb::key_error(msg.c_str()); - } - return it->second; - }, - "index"_a, R"pb(Get the value of the permutation at the given index. - -Args: - index: The index to get the value of the permutation at. - -Returns: - The value of the permutation at the given index.)pb") - - .def( - "__setitem__", - [](qc::Permutation& p, const nb::int_& index, const nb::int_& value) { - const auto q = nbIntToQubit(index); - const auto r = nbIntToQubit(value); - p[q] = r; - }, - "index"_a, "value"_a, - R"pb(Set the value of the permutation at the given index. - -Args: - index: The index to set the value of the permutation at. - value: The value to set the permutation at the given index to.)pb") - - .def( - "__delitem__", - [](qc::Permutation& p, const nb::int_& index) { - const auto q = nbIntToQubit(index); - const auto it = p.find(q); - if (it == p.end()) { - // Match Python's KeyError semantics for missing keys. - const auto msg = - std::string("Permutation does not contain index ") + - std::to_string(q); - throw nb::key_error(msg.c_str()); - } - p.erase(it); - }, - "index"_a, - R"pb(Delete the value of the permutation at the given index. - -Args: - index: The index to delete the value of the permutation at.)pb") - - .def("__len__", &qc::Permutation::size, - "Return the number of indices in the permutation.") - - .def( - "__iter__", - [](const qc::Permutation& p) { - return make_key_iterator( - nb::type(), "key_iterator", p.begin(), p.end(), - "Return an iterator over the indices of the permutation."); - }, - nb::keep_alive<0, 1>()) - - .def( - "items", - [](const qc::Permutation& p) { - return make_iterator( - nb::type(), "item_iterator", p.begin(), - p.end(), - "Return an iterable over the items of the permutation."); - }, - nb::sig("def items(self) -> collections.abc.ItemsView[int, int]"), - nb::keep_alive<0, 1>()) - - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - - .def("__str__", - [](const qc::Permutation& p) { - std::stringstream ss; - ss << "{"; - for (auto it = p.cbegin(); it != p.cend(); ++it) { - ss << it->first << ": " << it->second; - if (std::next(it) != p.cend()) { - ss << ", "; - } - } - ss << "}"; - return ss.str(); - }) - .def("__repr__", [](const qc::Permutation& p) { - std::stringstream ss; - ss << "Permutation({"; - for (auto it = p.cbegin(); it != p.cend(); ++it) { - ss << it->first << ": " << it->second; - if (std::next(it) != p.cend()) { - ss << ", "; - } - } - ss << "})"; - return ss.str(); - }); - - nb::implicitly_convertible(); -} - -} // namespace mqt diff --git a/bindings/ir/register_quantum_computation.cpp b/bindings/ir/register_quantum_computation.cpp deleted file mode 100644 index 2ba9893852..0000000000 --- a/bindings/ir/register_quantum_computation.cpp +++ /dev/null @@ -1,2291 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "qasm3/Importer.hpp" - -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -using DiffType = std::vector>::difference_type; -using SizeType = std::vector>::size_type; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerQuantumComputation(const nb::module_& m) { - auto wrap = [](DiffType i, const SizeType size) { - if (i < 0) { - i += static_cast(size); - } - if (i < 0 || std::cmp_greater_equal(i, size)) { - throw nb::index_error(); - } - return i; - }; - - auto qc = nb::class_( - m, "QuantumComputation", - nb::sig("class " - "QuantumComputation(collections.abc.MutableSequence[mqt.core.ir." - "operations.Operation])"), - R"pb(The main class for representing quantum computations within the MQT. - -Acts as mutable sequence of :class:`~mqt.core.ir.operations.Operation` objects, which represent the individual operations in the quantum computation. - -Args: - nq: The number of qubits in the quantum computation. - nc: The number of classical bits in the quantum computation.)pb"); - - ///--------------------------------------------------------------------------- - /// \n Constructors \n - ///--------------------------------------------------------------------------- - qc.def(nb::init(), "nq"_a = 0U, "nc"_a = 0U); - - // expose the static constructor from qasm strings or files - qc.def_static("from_qasm_str", &qasm3::Importer::imports, "qasm"_a, - R"pb(Create a QuantumComputation object from an OpenQASM string. - -Args: - qasm: The OpenQASM string to create the QuantumComputation object from. - -Returns: - The QuantumComputation object created from the OpenQASM string.)pb"); - - qc.def_static("from_qasm", &qasm3::Importer::importf, "filename"_a, - R"pb(Create a QuantumComputation object from an OpenQASM file. - -Args: - filename: The filename of the OpenQASM file to create the QuantumComputation object from. - -Returns: - The QuantumComputation object created from the OpenQASM file.)pb"); - - ///--------------------------------------------------------------------------- - /// \n General Properties \n - ///--------------------------------------------------------------------------- - - qc.def_prop_rw("name", &qc::QuantumComputation::getName, - &qc::QuantumComputation::setName, - "The name of the quantum computation."); - - qc.def_prop_ro("num_qubits", &qc::QuantumComputation::getNqubits, - "The total number of qubits in the quantum computation."); - - qc.def_prop_ro("num_ancilla_qubits", &qc::QuantumComputation::getNancillae, - R"pb(The number of ancilla qubits in the quantum computation. - -Note: - Ancilla qubits are qubits that always start in a fixed state (usually :math:`|0\rangle`).)pb"); - - qc.def_prop_ro("num_garbage_qubits", - &qc::QuantumComputation::getNgarbageQubits, - R"pb(The number of garbage qubits in the quantum computation. - -Note: - Garbage qubits are qubits whose final state is not relevant for the computation.)pb"); - - qc.def_prop_ro( - "num_output_qubits", &qc::QuantumComputation::getNoutputQubits, - R"pb(The number of logical output qubits in the quantum computation. - -Output qubits are the qubits that are not marked as garbage.)pb"); - - qc.def_prop_ro("num_data_qubits", - &qc::QuantumComputation::getNqubitsWithoutAncillae, - R"pb(The number of data qubits in the quantum computation. - -Computed as :math:`| \text{qubits} | - | \text{ancilla} |`.)pb"); - - qc.def_prop_ro("num_classical_bits", &qc::QuantumComputation::getNcbits, - "The number of classical bits in the quantum computation."); - - qc.def_prop_ro("num_ops", &qc::QuantumComputation::getNops, - "The number of operations in the quantum computation."); - - qc.def("num_single_qubit_ops", &qc::QuantumComputation::getNsingleQubitOps, - "Return the number of single-qubit operations in the quantum " - "computation."); - - qc.def("num_total_ops", &qc::QuantumComputation::getNindividualOps, - R"pb(Return the total number of operations in the quantum computation. - -Recursively counts sub-operations (e.g., from :class:`~mqt.core.ir.operations.CompoundOperation` objects).)pb"); - - qc.def("depth", &qc::QuantumComputation::getDepth, - "Return the depth of the quantum computation."); - - qc.def_prop_rw("global_phase", &qc::QuantumComputation::getGlobalPhase, - &qc::QuantumComputation::gphase, - "The global phase of the quantum computation."); - - qc.def("invert", &qc::QuantumComputation::invert, - "Invert the quantum computation in-place by inverting each operation " - "and reversing the order of operations."); - - qc.def("to_operation", &qc::QuantumComputation::asOperation, - R"pb(Convert the quantum computation to a single operation. - -This gives ownership of the operations to the resulting operation, so the quantum computation will be empty after this operation. - -When the quantum computation contains more than one operation, the resulting operation is a :class:`~mqt.core.ir.operations.CompoundOperation`. - -Returns: - The operation representing the quantum computation.)pb"); - - ///--------------------------------------------------------------------------- - /// \n Mutable Sequence Interface \n - ///--------------------------------------------------------------------------- - - qc.def( - "__getitem__", - [wrap](const qc::QuantumComputation& circ, DiffType i) { - i = wrap(i, circ.getNops()); - return circ.at(static_cast(i)).get(); - }, - nb::rv_policy::reference_internal, "index"_a, - R"pb(Get the operation at the given index. - -Note: - This gives write access to the operation at the given index. - -Args: - index: The index of the operation to get. - -Returns: - The operation at the given index.)pb"); - - qc.def( - "__getitem__", - [](qc::QuantumComputation& circ, const nb::slice& slice) { - auto [start, stop, step, sliceLength] = slice.compute(circ.getNops()); - auto ops = std::vector(); - ops.reserve(sliceLength); - for (std::size_t i = 0; i < sliceLength; ++i) { - auto idx = - static_cast(start) + (static_cast(i) * step); - ops.emplace_back(circ.at(static_cast(idx)).get()); - } - return ops; - }, - nb::rv_policy::reference_internal, "index"_a, - R"pb(Get a slice of operations from the quantum computation. - -Note: - This gives write access to the operations in the given slice. - -Args: - index: The slice of operations to get. - -Returns: - The operations in the given slice.)pb"); - - qc.def( - "__setitem__", - [wrap](qc::QuantumComputation& circ, DiffType i, - const qc::Operation& op) { - i = wrap(i, circ.getNops()); - circ.at(static_cast(i)) = op.clone(); - }, - "index"_a, "value"_a, R"pb(Set the operation at the given index. - -Args: - index: The index of the operation to set. - value: The operation to set at the given index.)pb"); - - qc.def( - "__setitem__", - [](qc::QuantumComputation& circ, const nb::slice& slice, - const std::vector& ops) { - auto [start, stop, step, sliceLength] = slice.compute(circ.getNops()); - if (sliceLength != ops.size()) { - throw std::runtime_error( - "Length of slice and number of operations do not match."); - } - for (std::size_t i = 0; i < sliceLength; ++i) { - assert(ops[i] != nullptr && "ops must not contain nullptr"); - circ.at(static_cast(start)) = ops[i]->clone(); - start += step; - } - }, - nb::sig("def __setitem__(self, index: slice, value: " - "collections.abc.Iterable[mqt.core.ir.operations.Operation]) -> " - "None"), - R"pb(Set the operations in the given slice. - -Args: - index: The slice of operations to set. - value: The operations to set in the given slice.)pb"); - - qc.def( - "__delitem__", - [wrap](qc::QuantumComputation& circ, DiffType i) { - i = wrap(i, circ.getNops()); - circ.erase(circ.begin() + i); - }, - "index"_a, R"pb(Delete the operation at the given index. - -Args: - index: The index of the operation to delete.)pb"); - - qc.def( - "__delitem__", - [](qc::QuantumComputation& circ, const nb::slice& slice) { - auto [start, stop, step, sliceLength] = slice.compute(circ.getNops()); - // Delete in reverse order to not invalidate indices - std::vector indices; - indices.reserve(sliceLength); - for (std::size_t i = 0; i < sliceLength; ++i) { - indices.emplace_back(static_cast(start) + - (static_cast(i) * step)); - } - std::ranges::sort(indices, std::greater<>()); - for (const auto idx : indices) { - circ.erase(circ.begin() + idx); - } - }, - "index"_a, R"pb(Delete the operations in the given slice. - -Args: - index: The slice of operations to delete.)pb"); - - qc.def("__len__", &qc::QuantumComputation::getNops, - "Return the number of operations in the quantum computation."); - - qc.def( - "insert", - [](qc::QuantumComputation& circ, std::size_t idx, - const qc::Operation& op) { - circ.insert(circ.begin() + static_cast(idx), op.clone()); - }, - "index"_a, "value"_a, R"pb(Insert an operation at the given index. - -Args: - index: The index to insert the operation at. - value: The operation to insert.)pb"); - - qc.def( - "append", - [](qc::QuantumComputation& circ, const qc::Operation& op) { - circ.emplace_back(op.clone()); - }, - "value"_a, R"pb(Append an operation to the end of the quantum computation. - -Args: - value: The operation to append.)pb"); - - qc.def("reverse", &qc::QuantumComputation::reverse, - "Reverse the order of the operations in the quantum computation " - "(in-place)."); - - qc.def("clear", nb::overload_cast<>(&qc::QuantumComputation::reset), - "Clear the quantum computation of all operations."); - - ///--------------------------------------------------------------------------- - /// \n (Qu)Bit Registers \n - ///--------------------------------------------------------------------------- - - qc.def("add_qubit_register", &qc::QuantumComputation::addQubitRegister, "n"_a, - "name"_a = "q", R"pb(Add a qubit register to the quantum computation. - -Args: - n: The number of qubits in the qubit register. - name: The name of the qubit register. - -Returns: - The qubit register added to the quantum computation.)pb"); - - qc.def("add_classical_register", - &qc::QuantumComputation::addClassicalRegister, "n"_a, "name"_a = "c", - R"pb(Add a classical register to the quantum computation. - -Args: - n: The number of bits in the classical register. - name: The name of the classical register. - -Returns: - The classical register added to the quantum computation.)pb"); - - qc.def("add_ancillary_register", - &qc::QuantumComputation::addAncillaryRegister, "n"_a, "name"_a = "anc", - R"pb(Add an ancillary register to the quantum computation. - -Args: - n: The number of qubits in the ancillary register. - name: The name of the ancillary register. - -Returns: - The ancillary register added to the quantum computation.)pb"); - - qc.def("unify_quantum_registers", - &qc::QuantumComputation::unifyQuantumRegisters, "name"_a = "q", - R"pb(Unify all quantum registers in the quantum computation. - -Args: - name: The name of the unified quantum register. - -Returns: - The unified quantum register.)pb"); - - qc.def_prop_ro("qregs", &qc::QuantumComputation::getQuantumRegisters, - "The quantum registers in the quantum computation."); - - qc.def_prop_ro("cregs", &qc::QuantumComputation::getClassicalRegisters, - "The classical registers in the quantum computation."); - - qc.def_prop_ro("ancregs", &qc::QuantumComputation::getAncillaRegisters, - "The ancillary registers in the quantum computation."); - - ///--------------------------------------------------------------------------- - /// \n Input Layout and Output Permutation \n - ///--------------------------------------------------------------------------- - - qc.def_rw("initial_layout", &qc::QuantumComputation::initialLayout, - R"pb(The initial layout of the qubits in the quantum computation. - -This is a permutation of the qubits in the quantum computation. -It is mainly used to track the mapping of circuit qubits to device qubits during quantum circuit compilation. -The keys are the device qubits (in which a compiled circuit is expressed in), and the values are the circuit qubits (in which the original quantum circuit is expressed in). - -Any operations in the quantum circuit are expected to be expressed in terms of the keys of the initial layout. - -Examples: - - If no initial layout is explicitly specified (which is the default), the initial layout is assumed to be the identity permutation. - - Assume a three-qubit circuit has been compiled to a four qubit device and circuit qubit 0 is mapped to device qubit 1, circuit qubit 1 is mapped to device qubit 2, and circuit qubit 2 is mapped to device qubit 3. - Then the initial layout is {1: 0, 2: 1, 3: 2}.)pb"); - - qc.def_rw( - "output_permutation", &qc::QuantumComputation::outputPermutation, - R"pb(The output permutation of the qubits in the quantum computation. - -This is a permutation of the qubits in the quantum computation. -It is mainly used to track where individual qubits end up at the end of the quantum computation, for example after a circuit has been compiled to a specific device and SWAP gates have been inserted, which permute the qubits. -Similar to the initial layout, the keys are the qubits in the circuit and the values are the qubits in the "original" circuit. - -Examples: - - If no output permutation is explicitly specified and the circuit does not contain measurements at the end, the output permutation is assumed to be the identity permutation. - - If the circuit contains measurements at the end, these measurements are used to infer the output permutation. - Assume a three-qubit circuit has been compiled to a four qubit device and, at the end of the circuit, circuit qubit 0 is measured into classical bit 2, circuit qubit 1 is measured into classical bit 1, and circuit qubit 3 is measured into classical bit 0. - Then the output permutation is {0: 2, 1: 1, 3: 0}.)pb"); - - qc.def("initialize_io_mapping", &qc::QuantumComputation::initializeIOMapping, - R"pb(Initialize the I/O mapping of the quantum computation. - -If no initial layout is explicitly specified, the initial layout is assumed to be the identity permutation. -If the circuit contains measurements at the end, these measurements are used to infer the output permutation. -If the output permutation is not empty, it must contain every measured device qubit as a key. -Clear the output permutation before calling this method to infer it only from measurements. - -Raises: - ValueError: If a measured device qubit is missing from a non-empty output permutation.)pb"); - - ///--------------------------------------------------------------------------- - /// \n Ancillary and Garbage Handling \n - ///--------------------------------------------------------------------------- - - qc.def_prop_ro( - "ancillary", nb::overload_cast<>(&qc::QuantumComputation::getAncillary), - "A list of booleans indicating whether each qubit is ancillary."); - - qc.def("set_circuit_qubit_ancillary", - &qc::QuantumComputation::setLogicalQubitAncillary, "q"_a, - R"pb(Set a circuit (i.e., logical) qubit to be ancillary. - -Args: - q: The index of the circuit qubit to set as ancillary.)pb"); - - qc.def("set_circuit_qubits_ancillary", - &qc::QuantumComputation::setLogicalQubitsAncillary, "q_min"_a, - "q_max"_a, - R"pb(Set a range of circuit (i.e., logical) qubits to be ancillary. - -Args: - q_min: The minimum index of the circuit qubits to set as ancillary. - q_max: The maximum index of the circuit qubits to set as ancillary.)pb"); - - qc.def("is_circuit_qubit_ancillary", - &qc::QuantumComputation::logicalQubitIsAncillary, "q"_a, - R"pb(Check if a circuit (i.e., logical) qubit is ancillary. - -Args: - q: The index of the circuit qubit to check. - -Returns: - True if the circuit qubit is ancillary, False otherwise.)pb"); - - qc.def_prop_ro( - "garbage", nb::overload_cast<>(&qc::QuantumComputation::getGarbage), - "A list of booleans indicating whether each qubit is garbage."); - - qc.def("set_circuit_qubit_garbage", - &qc::QuantumComputation::setLogicalQubitGarbage, "q"_a, - R"pb(Set a circuit (i.e., logical) qubit to be garbage. - -Args: - q: The index of the circuit qubit to set as garbage.)pb"); - - qc.def("set_circuit_qubits_garbage", - &qc::QuantumComputation::setLogicalQubitsGarbage, "q_min"_a, "q_max"_a, - R"pb(Set a range of circuit (i.e., logical) qubits to be garbage. - -Args: - q_min: The minimum index of the circuit qubits to set as garbage. - q_max: The maximum index of the circuit qubits to set as garbage.)pb"); - - qc.def("is_circuit_qubit_garbage", - &qc::QuantumComputation::logicalQubitIsGarbage, "q"_a, - R"pb(Check if a circuit (i.e., logical) qubit is garbage. - -Args: - q: The index of the circuit qubit to check. - -Returns: - True if the circuit qubit is garbage, False otherwise.)pb"); - - ///--------------------------------------------------------------------------- - /// \n Symbolic Circuit Handling \n - ///--------------------------------------------------------------------------- - - qc.def_prop_ro("variables", &qc::QuantumComputation::getVariables, - "The set of variables in the quantum computation."); - - qc.def("add_variable", &qc::QuantumComputation::addVariable, "var"_a, - R"pb(Add a variable to the quantum computation. - -Args: - var: The variable to add.)pb"); - - qc.def( - "add_variables", - [](qc::QuantumComputation& circ, - const std::vector& vars) { - for (const auto& var : vars) { - circ.addVariable(var); - } - }, - "vars_"_a, R"pb(Add multiple variables to the quantum computation. - -Args: - vars_: The variables to add.)pb"); - - qc.def("is_variable_free", &qc::QuantumComputation::isVariableFree, - R"pb(Check if the quantum computation is free of variables. - -Returns: - True if the quantum computation is free of variables, False otherwise.)pb"); - - qc.def( - "instantiate", &qc::QuantumComputation::instantiate, "assignment"_a, - R"pb(Instantiate the quantum computation with the given variable assignment. - -Args: - assignment: The variable assignment to instantiate the quantum computation with. - -Returns: - The instantiated quantum computation.)pb"); - - qc.def( - "instantiate_inplace", &qc::QuantumComputation::instantiateInplace, - "assignment"_a, - R"pb(Instantiate the quantum computation with the given variable assignment in-place. - -Args: - assignment: The variable assignment to instantiate the quantum computation with.)pb"); - - ///--------------------------------------------------------------------------- - /// \n Output Handling \n - ///--------------------------------------------------------------------------- - - qc.def( - "qasm2_str", - [](const qc::QuantumComputation& circ) { return circ.toQASM(false); }, - R"pb(Return the OpenQASM2 representation of the quantum computation as a string. - -Note: - This uses some custom extensions to OpenQASM 2.0 that allow for easier definition of multi-controlled gates. - These extensions might not be supported by all OpenQASM 2.0 parsers. - Consider using the :meth:`qasm3_str` method instead, which uses OpenQASM 3.0 that natively supports multi-controlled gates. - The export also assumes the bigger, non-standard `qelib1.inc` from Qiskit is available. - -Returns: - The OpenQASM2 representation of the quantum computation as a string.)pb"); - - qc.def( - "qasm2", - [](const qc::QuantumComputation& circ, const std::string& filename) { - circ.dump(filename, qc::Format::OpenQASM2); - }, - "filename"_a, - nb::sig("def qasm2(self, filename: os.PathLike[str] | str) -> None"), - R"pb(Write the OpenQASM2 representation of the quantum computation to a file. - -See Also: - :meth:`qasm2_str` - -Args: - filename: The filename of the file to write the OpenQASM2 representation to.)pb"); - - qc.def( - "qasm3_str", - [](const qc::QuantumComputation& circ) { return circ.toQASM(true); }, - R"pb(Return the OpenQASM3 representation of the quantum computation as a string. - -Returns: - The OpenQASM3 representation of the quantum computation as a string.)pb"); - - qc.def( - "qasm3", - [](const qc::QuantumComputation& circ, const std::string& filename) { - circ.dump(filename, qc::Format::OpenQASM3); - }, - "filename"_a, - nb::sig("def qasm3(self, filename: os.PathLike[str] | str) -> None"), - R"pb(Write the OpenQASM3 representation of the quantum computation to a file. - -See Also: - :meth:`qasm3_str` - -Args: - filename: The filename of the file to write the OpenQASM3 representation to.)pb"); - - qc.def("__str__", [](const qc::QuantumComputation& circ) { - auto ss = std::stringstream(); - circ.print(ss); - return ss.str(); - }); - - qc.def("__repr__", [](const qc::QuantumComputation& circ) { - auto ss = std::stringstream(); - ss << "QuantumComputation(num_qubits=" << circ.getNqubits() - << ", num_bits=" << circ.getNcbits() << ", num_ops=" << circ.getNops() - << ")"; - circ.print(ss); - return ss.str(); - }); - - ///--------------------------------------------------------------------------- - /// \n Operations \n - ///--------------------------------------------------------------------------- - - // I - - qc.def("i", &qc::QuantumComputation::i, "q"_a, - R"pb(Apply an identity operation. - -.. math:: - I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("ci", &qc::QuantumComputation::ci, "control"_a, "target"_a, - nb::sig("def ci(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled identity operation. - -See Also: - :meth:`i` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mci", &qc::QuantumComputation::mci, "controls"_a, "target"_a, - nb::sig("def mci(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled identity operation. - -See Also: - :meth:`i` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // X - - qc.def("x", &qc::QuantumComputation::x, "q"_a, - R"pb(Apply a Pauli-X gate. - -.. math:: - X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cx", &qc::QuantumComputation::cx, "control"_a, "target"_a, - nb::sig("def cx(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled Pauli-X (i.e., CNOT or CX) gate. - -See Also: - :meth:`x` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcx", &qc::QuantumComputation::mcx, "controls"_a, "target"_a, - nb::sig("def mcx(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled Pauli-X (i.e., Toffoli or MCX) gate. - -See Also: - :meth:`x` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // Y - - qc.def("y", &qc::QuantumComputation::y, "q"_a, - R"pb(Apply a Pauli-Y gate. - -.. math:: - Y = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cy", &qc::QuantumComputation::cy, "control"_a, "target"_a, - nb::sig("def cy(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled Pauli-Y gate. - -See Also: - :meth:`y` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcy", &qc::QuantumComputation::mcy, "controls"_a, "target"_a, - nb::sig("def mcy(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled Pauli-Y gate. - -See Also: - :meth:`y` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // Z - - qc.def("z", &qc::QuantumComputation::z, "q"_a, - R"pb(Apply a Pauli-Z gate. - -.. math:: - Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cz", &qc::QuantumComputation::cz, "control"_a, "target"_a, - nb::sig("def cz(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled Pauli-Z gate. - -See Also: - :meth:`z` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcz", &qc::QuantumComputation::mcz, "controls"_a, "target"_a, - nb::sig("def mcz(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled Pauli-Z gate. - -See Also: - :meth:`z` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // H - - qc.def("h", &qc::QuantumComputation::h, "q"_a, - R"pb(Apply a Hadamard gate. - -.. math:: - H = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("ch", &qc::QuantumComputation::ch, "control"_a, "target"_a, - nb::sig("def ch(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled Hadamard gate. - -See Also: - :meth:`h` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mch", &qc::QuantumComputation::mch, "controls"_a, "target"_a, - nb::sig("def mch(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled Hadamard gate. - -See Also: - :meth:`h` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // S - - qc.def("s", &qc::QuantumComputation::s, "q"_a, - R"pb(Apply an S (i.e., phase) gate. - -.. math:: - S = \begin{pmatrix} 1 & 0 \\ 0 & i \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cs", &qc::QuantumComputation::cs, "control"_a, "target"_a, - nb::sig("def cs(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled S gate. - -See Also: - :meth:`s` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcs", &qc::QuantumComputation::mcs, "controls"_a, "target"_a, - nb::sig("def mcs(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled S gate. - -See Also: - :meth:`s` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // Sdg - - qc.def("sdg", &qc::QuantumComputation::sdg, "q"_a, - R"pb(Apply an :math:`S^\dagger` gate. - -.. math:: - S^\dagger = \begin{pmatrix} 1 & 0 \\ 0 & -i \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("csdg", &qc::QuantumComputation::csdg, "control"_a, "target"_a, - nb::sig("def csdg(self, control: mqt.core.ir.operations.Control | " - "int, target: " - "int) -> None"), - R"pb(Apply a controlled :math:`S^\dagger` gate. - -See Also: - :meth:`sdg` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcsdg", &qc::QuantumComputation::mcsdg, "controls"_a, "target"_a, - nb::sig("def mcsdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`S^\dagger` gate. - -See Also: - :meth:`sdg` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // T - - qc.def("t", &qc::QuantumComputation::t, "q"_a, - R"pb(Apply a T gate. - -.. math:: - T = \begin{pmatrix} 1 & 0 \\ 0 & e^{i \pi / 4} \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("ct", &qc::QuantumComputation::ct, "control"_a, "target"_a, - nb::sig("def ct(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled T gate. - -See Also: - :meth:`t` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mct", &qc::QuantumComputation::mct, "controls"_a, "target"_a, - nb::sig("def mct(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled T gate. - -See Also: - :meth:`t` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // Tdg - - qc.def("tdg", &qc::QuantumComputation::tdg, "q"_a, - R"pb(Apply a :math:`T^\dagger` gate. - -.. math:: - T^\dagger = \begin{pmatrix} 1 & 0 \\ 0 & e^{-i \pi / 4} \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("ctdg", &qc::QuantumComputation::ctdg, "control"_a, "target"_a, - nb::sig("def ctdg(self, control: mqt.core.ir.operations.Control | " - "int, target: " - "int) -> None"), - R"pb(Apply a controlled :math:`T^\dagger` gate. - -See Also: - :meth:`tdg` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mctdg", &qc::QuantumComputation::mctdg, "controls"_a, "target"_a, - nb::sig("def mctdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`T^\dagger` gate. - -See Also: - :meth:`tdg` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // V - - qc.def("v", &qc::QuantumComputation::v, "q"_a, - R"pb(Apply a V gate. - -.. math:: - V = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & -i \\ -i & 1 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cv", &qc::QuantumComputation::cv, "control"_a, "target"_a, - nb::sig("def cv(self, control: mqt.core.ir.operations.Control | int, " - "target: int) " - "-> None"), - R"pb(Apply a controlled V gate. - -See Also: - :meth:`v` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcv", &qc::QuantumComputation::mcv, "controls"_a, "target"_a, - nb::sig("def mcv(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled V gate. - -See Also: - :meth:`v` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // Vdg - - qc.def("vdg", &qc::QuantumComputation::vdg, "q"_a, - R"pb(Apply a :math:`V^\dagger` gate. - -.. math:: - V^\dagger = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & i \\ i & 1 \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("cvdg", &qc::QuantumComputation::cvdg, "control"_a, "target"_a, - nb::sig("def cvdg(self, control: mqt.core.ir.operations.Control | " - "int, target: " - "int) -> None"), - R"pb(Apply a controlled :math:`V^\dagger` gate. - -See Also: - :meth:`vdg` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcvdg", &qc::QuantumComputation::mcvdg, "controls"_a, "target"_a, - nb::sig("def mcvdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`V^\dagger` gate. - -See Also: - :meth:`vdg` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // SX - - qc.def("sx", &qc::QuantumComputation::sx, "q"_a, - R"pb(Apply a :math:`\sqrt{X}` gate. - -.. math:: - \sqrt{X} = \frac{1}{2} \begin{pmatrix} 1 + i & 1 - i \\ 1 - i & 1 + i \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("csx", &qc::QuantumComputation::csx, "control"_a, "target"_a, - nb::sig("def csx(self, control: mqt.core.ir.operations.Control | int, " - "target: " - "int) -> None"), - R"pb(Apply a controlled :math:`\sqrt{X}` gate. - -See Also: - :meth:`sx` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcsx", &qc::QuantumComputation::mcsx, "controls"_a, "target"_a, - nb::sig("def mcsx(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`\sqrt{X}` gate. - -See Also: - :meth:`sx` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // SXdg - - qc.def("sxdg", &qc::QuantumComputation::sxdg, "q"_a, - R"pb(Apply a :math:`\sqrt{X}^\dagger` gate. - -.. math:: - \sqrt{X}^{\dagger} = \frac{1}{2} \begin{pmatrix} 1 - i & 1 + i \\ 1 + i & 1 - i \end{pmatrix} - -Args: - q: The target qubit)pb"); - qc.def("csxdg", &qc::QuantumComputation::csxdg, "control"_a, "target"_a, - nb::sig("def csxdg(self, control: mqt.core.ir.operations.Control | " - "int, target: " - "int) -> None"), - R"pb(Apply a controlled :math:`\sqrt{X}^\dagger` gate. - -See Also: - :meth:`sxdg` - -Args: - control: The control qubit - target: The target qubit)pb"); - qc.def("mcsxdg", &qc::QuantumComputation::mcsxdg, "controls"_a, "target"_a, - nb::sig("def mcsxdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`\sqrt{X}^\dagger` gate. - -See Also: - :meth:`sxdg` - -Args: - controls: The control qubits - target: The target qubit)pb"); - - // RX - - qc.def("rx", &qc::QuantumComputation::rx, "theta"_a, "q"_a, - R"pb(Apply an :math:`R_x(\theta)` gate. - -.. math:: - R_x(\theta) = e^{-i \theta X / 2} = \cos(\theta / 2) I - i \sin(\theta / 2) X - = \begin{pmatrix} \cos(\theta / 2) & -i \sin(\theta / 2) \\ -i \sin(\theta / 2) & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - q: The target qubit)pb"); - qc.def("crx", &qc::QuantumComputation::crx, "theta"_a, "control"_a, - "target"_a, - nb::sig("def crx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target: int) -> None"), - R"pb(Apply a controlled :math:`R_x(\theta)` gate. - -See Also: - :meth:`rx` - -Args: - theta: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def("mcrx", &qc::QuantumComputation::mcrx, "theta"_a, "controls"_a, - "target"_a, - nb::sig("def mcrx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target: int) " - "-> None"), - R"pb(Apply a multi-controlled :math:`R_x(\theta)` gate. - -See Also: - :meth:`rx` - -Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // RY - - qc.def("ry", &qc::QuantumComputation::ry, "theta"_a, "q"_a, - R"pb(Apply an :math:`R_y(\theta)` gate. - -.. math:: - R_y(\theta) = e^{-i \theta Y / 2} = \cos(\theta / 2) I - i \sin(\theta / 2) Y - = \begin{pmatrix} \cos(\theta / 2) & -\sin(\theta / 2) \\ \sin(\theta / 2) & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - q: The target qubit)pb"); - qc.def("cry", &qc::QuantumComputation::cry, "theta"_a, "control"_a, - "target"_a, - nb::sig("def cry(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target: int) -> None"), - R"pb(Apply a controlled :math:`R_y(\theta)` gate. - -See Also: - :meth:`ry` - -Args: - theta: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def("mcry", &qc::QuantumComputation::mcry, "theta"_a, "controls"_a, - "target"_a, - nb::sig("def mcry(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target: int) " - "-> None"), - R"pb(Apply a multi-controlled :math:`R_y(\theta)` gate. - -See Also: - :meth:`ry` - -Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // RZ - - qc.def("rz", &qc::QuantumComputation::rz, "theta"_a, "q"_a, - R"pb(Apply an :math:`R_z(\theta)` gate. - -.. math:: - R_z(\theta) = e^{-i \theta Z / 2} = \begin{pmatrix} e^{-i \theta / 2} & 0 \\ 0 & e^{i \theta / 2} \end{pmatrix} - -Args: - theta: The rotation angle - q: The target qubit)pb"); - qc.def("crz", &qc::QuantumComputation::crz, "theta"_a, "control"_a, - "target"_a, - nb::sig("def crz(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target: int) -> None"), - R"pb(Apply a controlled :math:`R_z(\theta)` gate. - -See Also: - :meth:`rz` - -Args: - theta: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def("mcrz", &qc::QuantumComputation::mcrz, "theta"_a, "controls"_a, - "target"_a, - nb::sig("def mcrz(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target: int) " - "-> None"), - R"pb(Apply a multi-controlled :math:`R_z(\theta)` gate. - -See Also: - :meth:`rz` - -Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // P - - qc.def("p", &qc::QuantumComputation::p, "theta"_a, "q"_a, - R"pb(Apply a phase gate. - -.. math:: - P(\theta) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i \theta} \end{pmatrix} - -Args: - theta: The rotation angle - q: The target qubit)pb"); - qc.def("cp", &qc::QuantumComputation::cp, "theta"_a, "control"_a, "target"_a, - nb::sig("def cp(self, theta: mqt.core.ir.symbolic.Expression | float, " - "control: " - "mqt.core.ir.operations.Control | int, target: int) -> None"), - R"pb(Apply a controlled phase gate. - -See Also: - :meth:`p` - -Args: - theta: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def("mcp", &qc::QuantumComputation::mcp, "theta"_a, "controls"_a, - "target"_a, - nb::sig("def mcp(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target: int) " - "-> None"), - R"pb(Apply a multi-controlled phase gate. - -See Also: - :meth:`p` - -Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // U2 - - qc.def("u2", &qc::QuantumComputation::u2, "phi"_a, "lambda_"_a, "q"_a, - R"pb(Apply a :math:`U_2(\phi, \lambda)` gate. - -.. math:: - U_2(\phi, \lambda) = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & -e^{i \lambda} \\ e^{i \phi} & e^{i (\phi + \lambda)} \end{pmatrix} - -Args: - phi: The rotation angle - lambda_: The rotation angle - q: The target qubit)pb"); - qc.def("cu2", &qc::QuantumComputation::cu2, "phi"_a, "lambda_"_a, "control"_a, - "target"_a, - nb::sig("def cu2(self, phi: mqt.core.ir.symbolic.Expression | float, " - "lambda_: " - "mqt.core.ir.symbolic.Expression | float, control: " - "mqt.core.ir.operations.Control | " - "int, target: int) -> None"), - R"pb(Apply a controlled :math:`U_2(\phi, \lambda)` gate. - -See Also: - :meth:`u2` - -Args: - phi: The rotation angle - lambda_: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def("mcu2", &qc::QuantumComputation::mcu2, "phi"_a, "lambda_"_a, - "controls"_a, "target"_a, - nb::sig("def mcu2(self, phi: mqt.core.ir.symbolic.Expression | float, " - "lambda_: " - "mqt.core.ir.symbolic.Expression | float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target: int) " - "-> None"), - R"pb(Apply a multi-controlled :math:`U_2(\phi, \lambda)` gate. - -See Also: - :meth:`u2` - -Args: - phi: The rotation angle - lambda_: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // R - - qc.def("r", &qc::QuantumComputation::r, "theta"_a, "phi"_a, "q"_a, - R"pb(Apply an :math:`R(\theta, \phi)` gate. - -.. math:: - R(\theta, \phi) = e^{-i \frac{\theta}{2} (\cos(\phi) X + \sin(\phi) Y)} - = \begin{pmatrix} \cos(\theta / 2) & -i e^{-i \phi} \sin(\theta / 2) \\ -i e^{i \phi} \sin(\theta / 2) & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - phi: The rotation angle - q: The target qubit)pb"); - qc.def( - "cr", &qc::QuantumComputation::cr, "theta"_a, "phi"_a, "control"_a, - "target"_a, - nb::sig( - "def cr(self, theta: mqt.core.ir.symbolic.Expression | float, phi: " - "mqt.core.ir.symbolic.Expression " - "| float, control: mqt.core.ir.operations.Control | int, target: " - "int) -> None"), - R"pb(Apply a controlled :math:`R(\theta, \phi)` gate. - -See Also: - :meth:`r` - -Args: - theta: The rotation angle - phi: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def( - "mcr", &qc::QuantumComputation::mcr, "theta"_a, "phi"_a, "controls"_a, - "target"_a, - nb::sig( - "def mcr(self, theta: mqt.core.ir.symbolic.Expression | float, phi: " - "mqt.core.ir.symbolic.Expression | float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], target: " - "int) " - "-> None"), - R"pb(Apply a multi-controlled :math:`R(\theta, \phi)` gate. - -See Also: - :meth:`r` - -Args: - theta: The rotation angle - phi: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // U - - qc.def("u", &qc::QuantumComputation::u, "theta"_a, "phi"_a, "lambda_"_a, - "q"_a, - R"pb(Apply a :math:`U(\theta, \phi, \lambda)` gate. - -.. math:: - U(\theta, \phi, \lambda) = \begin{pmatrix} \cos(\theta / 2) & -e^{i \lambda} \sin(\theta / 2) \\ e^{i \phi} \sin(\theta / 2) & e^{i (\phi + \lambda)}\cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - q: The target qubit)pb"); - qc.def( - "cu", &qc::QuantumComputation::cu, "theta"_a, "phi"_a, "lambda_"_a, - "control"_a, "target"_a, - nb::sig( - "def cu(self, theta: mqt.core.ir.symbolic.Expression | float, phi: " - "mqt.core.ir.symbolic.Expression | float, lambda_: " - "mqt.core.ir.symbolic.Expression | " - "float, control: mqt.core.ir.operations.Control | int, target: int) " - "-> None"), - R"pb(Apply a controlled :math:`U(\theta, \phi, \lambda)` gate. - -See Also: - :meth:`u` - -Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - control: The control qubit - target: The target qubit)pb"); - qc.def( - "mcu", &qc::QuantumComputation::mcu, "theta"_a, "phi"_a, "lambda_"_a, - "controls"_a, "target"_a, - nb::sig( - "def mcu(self, theta: mqt.core.ir.symbolic.Expression | float, phi: " - "mqt.core.ir.symbolic.Expression | float, lambda_: " - "mqt.core.ir.symbolic.Expression | " - "float, controls: collections.abc.Set[mqt.core.ir.operations.Control " - "| " - "int], target: int) -> None"), - R"pb(Apply a multi-controlled :math:`U(\theta, \phi, \lambda)` gate. - -See Also: - :meth:`u` - -Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - controls: The control qubits - target: The target qubit)pb"); - - // SWAP - - qc.def("swap", &qc::QuantumComputation::swap, "target1"_a, "target2"_a, - R"pb(Apply a SWAP gate. - -.. math:: - \text{SWAP} = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cswap", &qc::QuantumComputation::cswap, "control"_a, "target1"_a, - "target2"_a, - nb::sig("def cswap(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, " - "target2: int) -> None"), - R"pb(Apply a controlled SWAP gate. - -See Also: - :meth:`swap` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcswap", &qc::QuantumComputation::mcswap, "controls"_a, "target1"_a, - "target2"_a, - nb::sig("def mcswap(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled SWAP gate. - -See Also: - :meth:`swap` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // DCX - - qc.def("dcx", &qc::QuantumComputation::dcx, "target1"_a, "target2"_a, - R"pb(Apply a DCX (i.e., double CNOT) gate. - -.. math:: - DCX = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cdcx", &qc::QuantumComputation::cdcx, "control"_a, "target1"_a, - "target2"_a, - nb::sig("def cdcx(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, " - "target2: int) -> None"), - R"pb(Apply a controlled DCX gate. - -See Also: - :meth:`dcx` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcdcx", &qc::QuantumComputation::mcdcx, "controls"_a, "target1"_a, - "target2"_a, - nb::sig("def mcdcx(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled DCX gate. - -See Also: - :meth:`dcx` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // ECR - - qc.def("ecr", &qc::QuantumComputation::ecr, "target1"_a, "target2"_a, - R"pb(Apply a ECR (echoed cross-resonance) gate. - -.. math:: - ECR = \frac{1}{\sqrt{2}} \begin{pmatrix} 0 & 0 & 1 & i \\ 0 & 0 & i & 1 \\ 1 & -i & 0 & 0 \\ -i & 1 & 0 & 0 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cecr", &qc::QuantumComputation::cecr, "control"_a, "target1"_a, - "target2"_a, - nb::sig("def cecr(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, " - "target2: int) -> None"), - R"pb(Apply a controlled ECR gate. - -See Also: - :meth:`ecr` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcecr", &qc::QuantumComputation::mcecr, "controls"_a, "target1"_a, - "target2"_a, - nb::sig("def mcecr(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled ECR gate. - -See Also: - :meth:`ecr` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // iSWAP - - qc.def("iswap", &qc::QuantumComputation::iswap, "target1"_a, "target2"_a, - R"pb(Apply a :math:`i\text{SWAP}` gate. - -.. math:: - i\text{SWAP} = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & i & 0 \\ 0 & i & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("ciswap", &qc::QuantumComputation::ciswap, "control"_a, "target1"_a, - "target2"_a, - nb::sig("def ciswap(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, " - "target2: int) -> None"), - R"pb(Apply a controlled :math:`i\text{SWAP}` gate. - -See Also: - :meth:`iswap` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mciswap", &qc::QuantumComputation::mciswap, "controls"_a, "target1"_a, - "target2"_a, - nb::sig("def mciswap(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`i\text{SWAP}` gate. - -See Also: - :meth:`iswap` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // iSWAPdg - - qc.def("iswapdg", &qc::QuantumComputation::iswapdg, "target1"_a, "target2"_a, - R"pb(Apply a :math:`i\text{SWAP}^\dagger` gate. - -.. math:: - i\text{SWAP}^\dagger = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & -i & 0 \\ 0 & -i & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("ciswapdg", &qc::QuantumComputation::ciswapdg, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def ciswapdg(self, control: mqt.core.ir.operations.Control | " - "int, target1: " - "int, target2: int) -> None"), - R"pb(Apply a controlled :math:`i\text{SWAP}^\dagger` gate. - -See Also: - :meth:`iswapdg` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mciswapdg", &qc::QuantumComputation::mciswapdg, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mciswapdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`i\text{SWAP}^\dagger` gate. - -See Also: - :meth:`iswapdg` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // Peres - - qc.def("peres", &qc::QuantumComputation::peres, "target1"_a, "target2"_a, - R"pb(Apply a Peres gate. - -.. math:: - \text{Peres} = \begin{pmatrix} 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix} - -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cperes", &qc::QuantumComputation::cperes, "control"_a, "target1"_a, - "target2"_a, - nb::sig("def cperes(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, " - "target2: int) -> None"), - R"pb(Apply a controlled Peres gate. - -See Also: - :meth:`peres` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcperes", &qc::QuantumComputation::mcperes, "controls"_a, "target1"_a, - "target2"_a, - nb::sig("def mcperes(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled Peres gate. - -See Also: - :meth:`peres` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // Peresdg - - qc.def("peresdg", &qc::QuantumComputation::peresdg, "target1"_a, "target2"_a, - R"pb(Apply a :math:`\text{Peres}^\dagger` gate. - -.. math:: - \text{Peres}^\dagger = \begin{pmatrix} 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix} -Args: - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cperesdg", &qc::QuantumComputation::cperesdg, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def cperesdg(self, control: mqt.core.ir.operations.Control | " - "int, target1: " - "int, target2: int) -> None"), - R"pb(Apply a controlled :math:`\text{Peres}^\dagger` gate. - -See Also: - :meth:`peresdg` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcperesdg", &qc::QuantumComputation::mcperesdg, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mcperesdg(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control " - "| int], target1: int, target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`\text{Peres}^\dagger` gate. - -See Also: - :meth:`peresdg` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // RXX - - qc.def("rxx", &qc::QuantumComputation::rxx, "theta"_a, "target1"_a, - "target2"_a, - R"pb(Apply an :math:`R_{xx}(\theta)` gate. - -.. math:: - R_{xx}(\theta) = e^{-i \theta XX / 2} = \cos(\theta / 2) I \otimes I - i \sin(\theta / 2) X \otimes X - = \begin{pmatrix} \cos(\theta / 2) & 0 & 0 & -i \sin(\theta / 2) \\ - 0 & \cos(\theta / 2) & -i \sin(\theta / 2) & 0 \\ - 0 & -i \sin(\theta / 2) & \cos(\theta / 2) & 0 \\ - -i \sin(\theta / 2) & 0 & 0 & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("crxx", &qc::QuantumComputation::crxx, "theta"_a, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def crxx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target1: int, target2: " - "int) -> None"), - R"pb(Apply a controlled :math:`R_{xx}(\theta)` gate. - -See Also: - :meth:`rxx` - -Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcrxx", &qc::QuantumComputation::mcrxx, "theta"_a, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mcrxx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{xx}(\theta)` gate. - -See Also: - :meth:`rxx` - -Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // RYY - - qc.def("ryy", &qc::QuantumComputation::ryy, "theta"_a, "target1"_a, - "target2"_a, - R"pb(Apply an :math:`R_{yy}(\theta)` gate. - -.. math:: - R_{yy}(\theta) = e^{-i \theta YY / 2} = \cos(\theta / 2) I \otimes I - i \sin(\theta / 2) Y \otimes Y - = \begin{pmatrix} \cos(\theta / 2) & 0 & 0 & i \sin(\theta / 2) \\ - 0 & \cos(\theta / 2) & -i \sin(\theta / 2) & 0 \\ - 0 & -i \sin(\theta / 2) & \cos(\theta / 2) & 0 \\ - i \sin(\theta / 2) & 0 & 0 & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cryy", &qc::QuantumComputation::cryy, "theta"_a, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def cryy(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target1: int, target2: " - "int) -> None"), - R"pb(Apply a controlled :math:`R_{yy}(\theta)` gate. - -See Also: - :meth:`ryy` - -Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcryy", &qc::QuantumComputation::mcryy, "theta"_a, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mcryy(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{yy}(\theta)` gate. - -See Also: - :meth:`ryy` - -Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // RZX - - qc.def("rzx", &qc::QuantumComputation::rzx, "theta"_a, "target1"_a, - "target2"_a, - R"pb(Apply an :math:`R_{zx}(\theta)` gate. - -.. math:: - R_{zx}(\theta) = e^{-i \theta ZX / 2} = \cos(\theta / 2) I \otimes I - i \sin(\theta / 2) Z \otimes X - = \begin{pmatrix} \cos(\theta/2) & -i \sin(\theta/2) & 0 & 0 \\ - -i \sin(\theta/2) & \cos(\theta/2) & 0 & 0 \\ - 0 & 0 & \cos(\theta/2) & i \sin(\theta/2) \\ - 0 & 0 & i \sin(\theta/2) & \cos(\theta/2) \end{pmatrix} - -Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("crzx", &qc::QuantumComputation::crzx, "theta"_a, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def crzx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target1: int, target2: " - "int) -> None"), - R"pb(Apply a controlled :math:`R_{zx}(\theta)` gate. - -See Also: - :meth:`rzx` - -Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcrzx", &qc::QuantumComputation::mcrzx, "theta"_a, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mcrzx(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{zx}(\theta)` gate. - -See Also: - :meth:`rzx` - -Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // RZZ - - qc.def("rzz", &qc::QuantumComputation::rzz, "theta"_a, "target1"_a, - "target2"_a, - R"pb(Apply an :math:`R_{zz}(\theta)` gate. - -.. math:: - R_{zz}(\theta) = e^{-i \theta ZZ / 2} - = \begin{pmatrix} e^{-i \theta / 2} & 0 & 0 & 0 \\ - 0 & e^{i \theta / 2} & 0 & 0 \\ - 0 & 0 & e^{i \theta / 2} & 0 \\ - 0 & 0 & 0 & e^{-i \theta / 2} \end{pmatrix} - -Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("crzz", &qc::QuantumComputation::crzz, "theta"_a, "control"_a, - "target1"_a, "target2"_a, - nb::sig("def crzz(self, theta: mqt.core.ir.symbolic.Expression | " - "float, control: " - "mqt.core.ir.operations.Control | int, target1: int, target2: " - "int) -> None"), - R"pb(Apply a controlled :math:`R_{zz}(\theta)` gate. - -See Also: - :meth:`rzz` - -Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcrzz", &qc::QuantumComputation::mcrzz, "theta"_a, "controls"_a, - "target1"_a, "target2"_a, - nb::sig("def mcrzz(self, theta: mqt.core.ir.symbolic.Expression | " - "float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{zz}(\theta)` gate. - -See Also: - :meth:`rzz` - -Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // XXMinusYY - - qc.def("xx_minus_yy", &qc::QuantumComputation::xx_minus_yy, "theta"_a, - "beta"_a, "target1"_a, "target2"_a, - R"pb(Apply an :math:`R_{XX - YY}(\theta, \beta)` gate. - -.. math:: - R_{XX - YY}(\theta, \beta) = R_{z_2}(\beta) \cdot e^{-i \frac{\theta}{2} \frac{XX - YY}{2}} \cdot R_{z_2}(-\beta) - = \begin{pmatrix} \cos(\theta / 2) & 0 & 0 & -i \sin(\theta / 2) e^{-i \beta} \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - -i \sin(\theta / 2) e^{i \beta} & 0 & 0 & \cos(\theta / 2) \end{pmatrix} - -Args: - theta: The rotation angle - beta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cxx_minus_yy", &qc::QuantumComputation::cxx_minus_yy, "theta"_a, - "beta"_a, "control"_a, "target1"_a, "target2"_a, - nb::sig("def cxx_minus_yy(self, theta: " - "mqt.core.ir.symbolic.Expression | float, beta: " - "mqt.core.ir.symbolic.Expression | float, control: " - "mqt.core.ir.operations.Control | " - "int, target1: int, target2: int) -> None"), - R"pb(Apply a controlled :math:`R_{XX - YY}(\theta, \beta)` gate. - -See Also: - :meth:`xx_minus_yy` - -Args: - theta: The rotation angle - beta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcxx_minus_yy", &qc::QuantumComputation::mcxx_minus_yy, "theta"_a, - "beta"_a, "controls"_a, "target1"_a, "target2"_a, - nb::sig("def mcxx_minus_yy(self, theta: " - "mqt.core.ir.symbolic.Expression | float, beta: " - "mqt.core.ir.symbolic.Expression | float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{XX - YY}(\theta, \beta)` gate. - -See Also: - :meth:`xx_minus_yy` - -Args: - theta: The rotation angle - beta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // XXPlusYY - - qc.def("xx_plus_yy", &qc::QuantumComputation::xx_plus_yy, "theta"_a, "beta"_a, - "target1"_a, "target2"_a, - R"pb(Apply an :math:`R_{XX + YY}(\theta, \beta)` gate. - -.. math:: - R_{XX + YY}(\theta, \beta) = R_{z_1}(\beta) \cdot e^{-i \frac{\theta}{2} \frac{XX + YY}{2}} \cdot R_{z_1}(-\beta) - = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos(\theta / 2) & -i \sin(\theta / 2) e^{-i \beta} & 0 \\ - 0 & -i \sin(\theta / 2) e^{i \beta} & \cos(\theta / 2) & 0 \\ - 0 & 0 & 0 & 1 \end{pmatrix} - -Args: - theta: The rotation angle - beta: The rotation angle - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("cxx_plus_yy", &qc::QuantumComputation::cxx_plus_yy, "theta"_a, - "beta"_a, "control"_a, "target1"_a, "target2"_a, - nb::sig("def cxx_plus_yy(self, theta: mqt.core.ir.symbolic.Expression " - "| float, beta: " - "mqt.core.ir.symbolic.Expression | float, control: " - "mqt.core.ir.operations.Control | " - "int, target1: int, target2: int) -> None"), - R"pb(Apply a controlled :math:`R_{XX + YY}(\theta, \beta)` gate. - -See Also: - :meth:`xx_plus_yy` - -Args: - theta: The rotation angle - beta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit)pb"); - qc.def("mcxx_plus_yy", &qc::QuantumComputation::mcxx_plus_yy, "theta"_a, - "beta"_a, "controls"_a, "target1"_a, "target2"_a, - nb::sig("def mcxx_plus_yy(self, theta: " - "mqt.core.ir.symbolic.Expression | float, beta: " - "mqt.core.ir.symbolic.Expression | float, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | int], " - "target1: int, " - "target2: int) -> None"), - R"pb(Apply a multi-controlled :math:`R_{XX + YY}(\theta, \beta)` gate. - -See Also: - :meth:`xx_plus_yy` - -Args: - theta: The rotation angle - beta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit)pb"); - - // RCCX - - qc.def("rccx", &qc::QuantumComputation::rccx, "target1"_a, "target2"_a, - "target3"_a, - R"pb(Apply a relative-phase CCX (RCCX) gate. - -Args: - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit)pb"); - qc.def("crccx", &qc::QuantumComputation::crccx, "control"_a, "target1"_a, - "target2"_a, "target3"_a, - nb::sig("def crccx(self, control: mqt.core.ir.operations.Control | " - "int, target1: int, target2: int, target3: int) -> None"), - R"pb(Apply a controlled RCCX gate. - -See Also: - :meth:`rccx` - -Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit)pb"); - qc.def("mcrccx", &qc::QuantumComputation::mcrccx, "controls"_a, "target1"_a, - "target2"_a, "target3"_a, - nb::sig("def mcrccx(self, controls: " - "collections.abc.Set[mqt.core.ir.operations.Control | " - "int], target1: int, target2: int, target3: int) -> None"), - R"pb(Apply a multi-controlled RCCX gate. - -See Also: - :meth:`rccx` - -Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit)pb"); - - qc.def("gphase", &qc::QuantumComputation::gphase, "phase"_a, - R"pb(Apply a global phase gate. - -.. math:: - GPhase(\theta) = (e^{i \theta}) - -Args: - phase: The rotation angle)pb"); - - qc.def("measure", - nb::overload_cast( - &qc::QuantumComputation::measure), - "qubit"_a, "cbit"_a, - R"pb(Measure a qubit and store the result in a classical bit. - -Args: - qubit: The qubit to measure - cbit: The classical bit to store the result)pb"); - - qc.def("measure", - nb::overload_cast&, - const std::vector&>( - &qc::QuantumComputation::measure), - "qubits"_a, "cbits"_a, - R"pb(Measure multiple qubits and store the results in classical bits. - -This method is equivalent to calling :meth:`measure` multiple times. - -Args: - qubits: The qubits to measure - cbits: The classical bits to store the results)pb"); - qc.def("measure_all", &qc::QuantumComputation::measureAll, nb::kw_only(), - "add_bits"_a = true, "add_barrier"_a = true, - R"pb(Measure all qubits and store the results in classical bits. - -Details: - If `add_bits` is `True`, a new classical register (named "`meas`") with the same size as the number of qubits will be added to the circuit and the results will be stored in it. - If `add_bits` is `False`, the classical register must already exist and have a sufficient number of bits to store the results. - If `add_barrier` is `True`, a barrier is added before the measurements. - -Args: - add_bits: Whether to explicitly add a classical register - add_barrier: Whether to add a barrier before the measurements)pb"); - - qc.def("reset", nb::overload_cast(&qc::QuantumComputation::reset), - "q"_a, R"pb(Add a reset operation to the circuit. - -Args: - q: The qubit to reset)pb"); - - qc.def("reset", - nb::overload_cast&>( - &qc::QuantumComputation::reset), - "qubits"_a, R"pb(Add a reset operation to the circuit. - -Args: - qubits: The qubits to reset)pb"); - - qc.def("barrier", nb::overload_cast<>(&qc::QuantumComputation::barrier), - "Add a barrier to the circuit."); - - qc.def("barrier", - nb::overload_cast(&qc::QuantumComputation::barrier), "q"_a, - R"pb(Add a barrier to the circuit. - -Args: - q: The qubit to add the barrier to)pb"); - - qc.def("barrier", - nb::overload_cast&>( - &qc::QuantumComputation::barrier), - "qubits"_a, R"pb(Add a barrier to the circuit. - -Args: - qubits: The qubits to add the barrier to)pb"); - - qc.def( - "if_else", - [](qc::QuantumComputation& self, qc::Operation* thenOp, - qc::Operation* elseOp, const qc::ClassicalRegister& controlRegister, - const std::uint64_t expectedValue = 1U, - const qc::ComparisonKind kind = qc::ComparisonKind::Eq) { - std::unique_ptr thenPtr = - thenOp ? thenOp->clone() : nullptr; - std::unique_ptr elsePtr = - elseOp ? elseOp->clone() : nullptr; - self.ifElse(std::move(thenPtr), std::move(elsePtr), controlRegister, - expectedValue, kind); - }, - "then_operation"_a, "else_operation"_a, "control_register"_a, - "expected_value"_a = 1U, "comparison_kind"_a = qc::ComparisonKind::Eq, - R"pb(Add an if-else operation to the circuit. - -Args: - then_operation: The operation to apply if the condition is met - else_operation: The operation to apply if the condition is not met - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform)pb"); - - qc.def( - "if_else", - [](qc::QuantumComputation& self, qc::Operation* thenOp, - qc::Operation* elseOp, const qc::Bit controlBit, - const std::uint64_t expectedValue = 1U, - const qc::ComparisonKind kind = qc::ComparisonKind::Eq) { - std::unique_ptr thenPtr = - thenOp ? thenOp->clone() : nullptr; - std::unique_ptr elsePtr = - elseOp ? elseOp->clone() : nullptr; - self.ifElse(std::move(thenPtr), std::move(elsePtr), controlBit, - expectedValue, kind); - }, - "then_operation"_a, "else_operation"_a, "control_bit"_a, - "expected_value"_a = 1U, "comparison_kind"_a = qc::ComparisonKind::Eq, - R"pb(Add an if-else operation to the circuit. - -Args: - then_operation: The operation to apply if the condition is met - else_operation: The operation to apply if the condition is not met - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform)pb"); - - qc.def( - "if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "control_register"_a, "expected_value"_a = 1U, - "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a.sig("...") = std::vector{}, - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation)pb"); - - qc.def( - "if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "control"_a, "control_register"_a, - "expected_value"_a = 1U, "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a = std::vector{}, - nb::sig( - "def if_(self, op_type: mqt.core.ir.operations.OpType, target: " - "int, control: mqt.core.ir.operations.Control | int, " - "control_register: mqt.core.ir.registers.ClassicalRegister, " - "expected_value: int = 1, comparison_kind: " - "mqt.core.ir.operations.ComparisonKind = ..., params: " - "collections.abc.Sequence[mqt.core.ir.symbolic.Expression | float] " - "= ...) -> None"), - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - control: The control qubit - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation.)pb"); - - qc.def( - "if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "controls"_a, "control_register"_a, - "expected_value"_a = 1U, "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a = std::vector{}, - nb::sig( - "def if_(self, op_type: mqt.core.ir.operations.OpType, target: " - "int, controls: collections.abc.Set[mqt.core.ir.operations.Control | " - "int], " - "control_register: mqt.core.ir.registers.ClassicalRegister, " - "expected_value: int = 1, comparison_kind: " - "mqt.core.ir.operations.ComparisonKind = ..., params: " - "collections.abc.Sequence[mqt.core.ir.symbolic.Expression | float] " - "= ...) -> None"), - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - controls: The control qubits - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation.)pb"); - - qc.def("if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "control_bit"_a, "expected_value"_a = true, - "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a.sig("...") = std::vector{}, - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation.)pb"); - - qc.def( - "if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "control"_a, "control_bit"_a, - "expected_value"_a = true, "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a = std::vector{}, - nb::sig("def if_(self, op_type: mqt.core.ir.operations.OpType, target: " - "int, control: mqt.core.ir.operations.Control | int, " - "control_bit: int, expected_value: bool = True, " - "comparison_kind: " - "mqt.core.ir.operations.ComparisonKind = ..., params: " - "collections.abc.Sequence[mqt.core.ir.symbolic.Expression | " - "float] = ...) -> None"), - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - control: The control qubit - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation.)pb"); - - qc.def( - "if_", - nb::overload_cast&>( - &qc::QuantumComputation::if_), - "op_type"_a, "target"_a, "controls"_a, "control_bit"_a, - "expected_value"_a = true, "comparison_kind"_a = qc::ComparisonKind::Eq, - "params"_a = std::vector{}, - nb::sig( - "def if_(self, op_type: mqt.core.ir.operations.OpType, target: " - "int, controls: collections.abc.Set[mqt.core.ir.operations.Control " - "| int], control_bit: int, expected_value: bool = True, " - "comparison_kind: " - "mqt.core.ir.operations.ComparisonKind = ..., params: " - "collections.abc.Sequence[mqt.core.ir.symbolic.Expression | " - "float] = ...) -> None"), - R"pb(Add an if operation to the circuit. - -Args: - op_type: The operation to apply - target: The target qubit - controls: The control qubits - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation.)pb"); -} - -} // namespace mqt diff --git a/bindings/ir/register_registers.cpp b/bindings/ir/register_registers.cpp deleted file mode 100644 index 6cc65f253a..0000000000 --- a/bindings/ir/register_registers.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/Register.hpp" - -#include -#include -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerRegisters(const nb::module_& m) { - nb::class_( - m, "QuantumRegister", R"pb(A class to represent a collection of qubits. - -Args: - start: The starting index of the quantum register. - size: The number of qubits in the quantum register. - name: The name of the quantum register. A name will be generated if not provided.)pb") - - .def(nb::init(), - "start"_a, "size"_a, "name"_a = "") - - .def_prop_ro( - "name", [](const qc::QuantumRegister& reg) { return reg.getName(); }, - "The name of the quantum register.") - - .def_prop_rw( - "start", - [](const qc::QuantumRegister& reg) { return reg.getStartIndex(); }, - [](qc::QuantumRegister& reg, const nb::int_& start) { - const auto startInt = static_cast(start); - if (startInt < 0) { - throw nb::value_error("Start index cannot be negative"); - } - const auto startUint = static_cast(startInt); - if (startUint > std::numeric_limits::max()) { - throw nb::value_error("Start index exceeds maximum value"); - } - reg.getStartIndex() = static_cast(startUint); - }, - "The index of the first qubit in the quantum register.") - .def_prop_rw( - "size", [](const qc::QuantumRegister& reg) { return reg.getSize(); }, - [](qc::QuantumRegister& reg, const nb::int_& size) { - const auto sizeInt = static_cast(size); - if (sizeInt < 0) { - throw nb::value_error("Size cannot be negative"); - } - const auto sizeUint = static_cast(sizeInt); - if (sizeUint > std::numeric_limits::max()) { - throw nb::value_error("Size exceeds maximum value"); - } - reg.getSize() = static_cast(sizeUint); - }, - "The number of qubits in the quantum register.") - .def_prop_ro( - "end", - [](const qc::QuantumRegister& reg) { return reg.getEndIndex(); }, - "Index of the last qubit in the quantum register.") - - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - - .def( - "__getitem__", - [](const qc::QuantumRegister& reg, nb::ssize_t idx) { - const auto n = static_cast(reg.getSize()); - if (idx < 0) { - idx += n; - } - if (idx < 0 || idx >= n) { - throw nb::index_error(); - } - return reg.getGlobalIndex(static_cast(idx)); - }, - "key"_a, "Get the qubit at the specified index.") - - .def("__contains__", &qc::QuantumRegister::contains, "item"_a, - "Check if the quantum register contains a qubit.") - - .def("__repr__", [](const qc::QuantumRegister& reg) { - return "QuantumRegister(name=" + reg.getName() + - ", start=" + std::to_string(reg.getStartIndex()) + - ", size=" + std::to_string(reg.getSize()) + ")"; - }); - - nb::class_( - m, "ClassicalRegister", - R"pb(A class to represent a collection of classical bits. - -Args: - start: The starting index of the classical register. - size: The number of bits in the classical register. - name: The name of the classical register. A name will be generated if not provided.)pb") - - .def(nb::init(), - "start"_a, "size"_a, "name"_a = "") - - .def_prop_ro( - "name", - [](const qc::ClassicalRegister& reg) { return reg.getName(); }, - "The name of the classical register.") - - .def_prop_rw( - "start", - [](const qc::ClassicalRegister& reg) { return reg.getStartIndex(); }, - [](qc::ClassicalRegister& reg, const nb::int_& start) { - const auto startInt = static_cast(start); - if (startInt < 0) { - throw nb::value_error("Start index cannot be negative"); - } - const auto startUint = static_cast(startInt); - if (startUint > std::numeric_limits::max()) { - throw nb::value_error("Start index exceeds maximum value"); - } - reg.getStartIndex() = static_cast(startUint); - }, - "The index of the first bit in the classical register.") - - .def_prop_rw( - "size", - [](const qc::ClassicalRegister& reg) { return reg.getSize(); }, - [](qc::ClassicalRegister& reg, const nb::int_& size) { - const auto sizeInt = static_cast(size); - if (sizeInt < 0) { - throw nb::value_error("Size cannot be negative"); - } - const auto sizeUint = static_cast(sizeInt); - if (sizeUint > std::numeric_limits::max()) { - throw nb::value_error("Size exceeds maximum value"); - } - reg.getSize() = static_cast(sizeUint); - }, - "The number of bits in the classical register.") - - .def_prop_ro( - "end", - [](const qc::ClassicalRegister& reg) { return reg.getEndIndex(); }, - "Index of the last bit in the classical register.") - - // NOLINTNEXTLINE(misc-redundant-expression) - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - // NOLINTNEXTLINE(misc-redundant-expression) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - - .def( - "__getitem__", - [](const qc::ClassicalRegister& reg, nb::ssize_t idx) { - const auto n = static_cast(reg.getSize()); - if (idx < 0) { - idx += n; - } - if (idx < 0 || idx >= n) { - throw nb::index_error(); - } - return reg.getGlobalIndex(static_cast(idx)); - }, - "key"_a, "Get the bit at the specified index.") - - .def("__contains__", &qc::ClassicalRegister::contains, "item"_a, - "Check if the classical register contains a bit.") - - .def("__repr__", [](const qc::ClassicalRegister& reg) { - return "ClassicalRegister(name=" + reg.getName() + - ", start=" + std::to_string(reg.getStartIndex()) + - ", size=" + std::to_string(reg.getSize()) + ")"; - }); -} - -} // namespace mqt diff --git a/bindings/ir/register_symbolic.cpp b/bindings/ir/register_symbolic.cpp deleted file mode 100644 index 359e22ad19..0000000000 --- a/bindings/ir/register_symbolic.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include - -namespace mqt { - -namespace nb = nanobind; - -// forward declarations -void registerVariable(const nb::module_& m); -void registerTerm(const nb::module_& m); -void registerExpression(const nb::module_& m); - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerSymbolic(const nb::module_& m) { - registerVariable(m); - registerTerm(m); - registerExpression(m); -} -} // namespace mqt diff --git a/bindings/ir/symbolic/register_expression.cpp b/bindings/ir/symbolic/register_expression.cpp deleted file mode 100644 index 0814c15c71..0000000000 --- a/bindings/ir/symbolic/register_expression.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Expression.hpp" - -#include -#include -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include -#include -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerExpression(const nb::module_& m) { - nb::class_>( - m, "Expression", - R"pb(A symbolic expression which consists of a sum of terms and a constant. - -The expression is of the form :math:`constant + term_1 + term_2 + \dots + term_n`. -Alternatively, an expression can be created with a single term and a constant or just a constant. - -Args: - terms: The list of terms. - constant: The constant.)pb") - - .def(nb::init(), "constant"_a = 0.0) - .def(nb::init>&, double>(), "terms"_a, - "constant"_a = 0.0) - .def( - "__init__", - [](sym::Expression* self, - const sym::Term& term, double constant) { - new (self) sym::Expression( - std::vector>{term}, constant); - }, - "term"_a, "constant"_a = 0.0) - - .def_prop_rw("constant", &sym::Expression::getConst, - &sym::Expression::setConst, - "The constant of the expression.") - .def( - "__iter__", - [](const sym::Expression& expr) { - return make_iterator(nb::type>(), - "iterator", expr.begin(), expr.end()); - }, - nb::keep_alive<0, 1>()) - - .def( - "__getitem__", - [](const sym::Expression& expr, nb::ssize_t idx) { - const auto n = static_cast(expr.numTerms()); - if (idx < 0) { - idx += n; - } - if (idx < 0 || idx >= n) { - throw nb::index_error(); - } - // NOLINTNEXTLINE(*-pro-bounds-avoid-unchecked-container-access) - return expr.getTerms()[static_cast(idx)]; - }, - "index"_a) - - .def("is_zero", &sym::Expression::isZero, - "Check if the expression is zero.") - - .def("is_constant", &sym::Expression::isConstant, - "Check if the expression is a constant.") - - .def("num_terms", &sym::Expression::numTerms, - "The number of terms in the expression.") - - .def("__len__", &sym::Expression::numTerms) - - .def_prop_ro("terms", &sym::Expression::getTerms, - "The terms of the expression.") - - .def_prop_ro("variables", &sym::Expression::getVariables, - "The variables in the expression.") - - .def("evaluate", &sym::Expression::evaluate, - "assignment"_a, - R"pb(Evaluate the expression with a given variable assignment. - -Args: - assignment: The variable assignment. - -Returns: - The evaluated value of the expression.)pb") - - // addition operators - .def(nb::self + nb::self, nb::is_operator()) - .def(nb::self + double(), nb::is_operator()) - .def( - "__add__", - [](const sym::Expression& lhs, - const sym::Term& rhs) { return lhs + rhs; }, - nb::is_operator()) - .def( - "__radd__", - [](const sym::Expression& rhs, - const sym::Term& lhs) { return lhs + rhs; }, - nb::is_operator()) - .def( - "__radd__", - [](const sym::Expression& rhs, const double lhs) { - return rhs + lhs; - }, - nb::is_operator()) - // subtraction operators - // NOLINTNEXTLINE(misc-redundant-expression) - .def(nb::self - nb::self, nb::is_operator()) - .def(nb::self - double(), nb::is_operator()) - .def(double() - nb::self, nb::is_operator()) - .def( - "__sub__", - [](const sym::Expression& lhs, - const sym::Term& rhs) { return lhs - rhs; }, - nb::is_operator()) - .def( - "__rsub__", - [](const sym::Expression& rhs, - const sym::Term& lhs) { return lhs - rhs; }, - nb::is_operator()) - // multiplication operators - .def(nb::self * double(), nb::is_operator()) - .def(double() * nb::self, nb::is_operator()) - // division operators - .def(nb::self / double(), nb::is_operator()) - // comparison operators - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - .def("__str__", - [](const sym::Expression& expr) { - std::stringstream ss; - ss << expr; - return ss.str(); - }) - .def("__repr__", [](const sym::Expression& expr) { - std::stringstream ss; - ss << expr; - return ss.str(); - }); -} -} // namespace mqt diff --git a/bindings/ir/symbolic/register_term.cpp b/bindings/ir/symbolic/register_term.cpp deleted file mode 100644 index ec32e34021..0000000000 --- a/bindings/ir/symbolic/register_term.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Expression.hpp" - -#include -#include -#include // NOLINT(misc-include-cleaner) -#include // NOLINT(misc-include-cleaner) - -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerTerm(const nb::module_& m) { - nb::class_>( - m, "Term", - R"pb(A symbolic term which consists of a variable with a given coefficient. - -Args: - variable: The variable of the term. - coefficient: The coefficient of the term.)pb") - - .def(nb::init(), "variable"_a, - "coefficient"_a = 1.0) - - .def_prop_ro("variable", &sym::Term::getVar, - "The variable of the term.") - - .def_prop_ro("coefficient", &sym::Term::getCoeff, - "The coefficient of the term.") - - .def("has_zero_coefficient", &sym::Term::hasZeroCoeff, - "Check if the coefficient of the term is zero.") - - .def("add_coefficient", &sym::Term::addCoeff, "coeff"_a, - R"pb(Add a coefficient to the coefficient of this term. - -Args: - coeff: The coefficient to add.)pb") - - .def("evaluate", &sym::Term::evaluate, "assignment"_a, - R"pb(Evaluate the term with a given variable assignment. - -Args: - assignment: The variable assignment. - -Returns: - The evaluated value of the term.)pb") - - .def(nb::self * double(), nb::is_operator()) - .def(double() * nb::self, nb::is_operator()) - .def(nb::self / double(), nb::is_operator()) - - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - - .def("__str__", - [](const sym::Term& term) { - std::stringstream ss; - ss << term; - return ss.str(); - }) - - .def("__repr__", [](const sym::Term& term) { - std::stringstream ss; - ss << term; - return ss.str(); - }); -} -} // namespace mqt diff --git a/bindings/ir/symbolic/register_variable.cpp b/bindings/ir/symbolic/register_variable.cpp deleted file mode 100644 index 450c33218b..0000000000 --- a/bindings/ir/symbolic/register_variable.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Expression.hpp" - -#include -#include -#include // NOLINT(misc-include-cleaner) - -#include - -namespace mqt { - -namespace nb = nanobind; -using namespace nb::literals; - -// NOLINTNEXTLINE(misc-use-internal-linkage) -void registerVariable(const nb::module_& m) { - nb::class_(m, "Variable", R"pb(A symbolic variable. -Note: - Variables are uniquely identified by their name, so if a variable with the same name already exists, the existing variable will be returned. - -Args: - name: The name of the variable.)pb") - - .def(nb::init(), "name"_a = "") - - .def_prop_ro("name", &sym::Variable::getName, "The name of the variable.") - - .def("__str__", &sym::Variable::getName) - .def("__repr__", &sym::Variable::getName) - - .def(nb::self == nb::self, - nb::sig("def __eq__(self, arg: object, /) -> bool")) - .def(nb::self != nb::self, - nb::sig("def __ne__(self, arg: object, /) -> bool")) - .def(nb::hash(nb::self)) - .def(nb::self < nb::self) - .def(nb::self > nb::self); -} -} // namespace mqt diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index 7717b49b75..ccd482f46b 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -15,7 +15,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") include(CMakeFindDependencyMacro) find_dependency(qdmi) -if(TARGET MQT::Core) +if(TARGET MQT::CoreDD) return() endif() diff --git a/docs/dd_package.md b/docs/dd_package.md index 83186f5c8d..87c8a8038a 100644 --- a/docs/dd_package.md +++ b/docs/dd_package.md @@ -28,166 +28,74 @@ to work with decision diagrams in MQT Core from Python. ## Quickstart -In its simplest use case, the MQT Core DD package can be used as a classical -circuit simulator using the {py:func}`~mqt.core.dd.sample` function. The -underlying simulation approach supports mid-circuit measurements, reset -operations, as well as classically-controlled operations. For example, the -following code snippet demonstrates how to simulate the iterative quantum phase -estimation algorithm shown in -[the MQT Core IR Quickstart guide](mqt_core_ir). +The MQT Compiler Collection uses the DD package to simulate optimized +{py:class}`~mqt.core.mlir.QCOProgram` objects. The simulator supports +mid-circuit measurements, resets, and classically controlled operations. This +example compiles and samples a Bell-state program: ```{code-cell} ipython3 -from mqt.core.dd import sample -from mqt.core.ir import QuantumComputation -from mqt.core.ir.operations import OpType - -from math import pi - -theta = 3 * pi / 8 -precision = 3 - -# Create an empty quantum computation -qc = QuantumComputation() - -# Counting register -q = qc.add_qubit_register(1, "q") - -# Eigenstate register -psi = qc.add_qubit_register(1, "psi") - -# Classical register for the result, the estimated phase is `0.c_2 c_1 c_0 * pi` -c = qc.add_classical_register(precision, "c") - -# Prepare psi in the eigenstate |1> -qc.x(psi[0]) - -for i in range(precision): - # Hadamard on the working qubit - qc.h(q[0]) - - # Controlled phase gate - qc.cp(2**(precision - i - 1) * theta, q[0], psi[0]) - - # Iterative inverse QFT - for j in range(i): - qc.if_(op_type=OpType.p, target=q[0], control_bit=c[j], params=[-pi / 2**(i - j)]) - qc.h(q[0]) - - # Measure the result - qc.measure(q[0], c[i]) - - # Reset the qubit if not finished - if i < precision - 1: - qc.reset(q[0]) - -# Run the simulation -counts = sample(qc, 1024) -``` +from mqt.core.dd import DDPackage +from mqt.core.mlir import OutputFormat, compile_program + +bell_qasm = """OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] result; +h q[0]; +cx q[0], q[1]; +result = measure q; +""" + +sample_program = compile_program(bell_qasm, output=OutputFormat.QCO_OPTIMIZED) +sample_package = DDPackage(2) +counts = sample_program.sample(sample_package, shots=1024, seed=1) +print(counts) +``` + +Use {py:meth}`~mqt.core.mlir.QCOProgram.simulate` to obtain a state DD and +{py:meth}`~mqt.core.mlir.QCOProgram.build_functionality` to obtain a matrix DD. +These methods avoid constructing an exponentially large dense array unless the +result is explicitly converted with {py:meth}`~mqt.core.dd.VectorDD.get_vector` +or {py:meth}`~mqt.core.dd.MatrixDD.get_matrix`. ```{code-cell} ipython3 ---- -tags: [remove-cell] ---- -from pathlib import Path -from matplotlib import pyplot as plt - -def generate_plot(counts: dict[str, int], name: str, light: bool) -> None: - if light: - plt.style.use('default') - else: - plt.style.use('dark_background') - - # Create the bar plot - fig, ax = plt.subplots() - bars = ax.bar(counts.keys(), counts.values(), color='#0065bd') - - # Annotate counts above the bars - for bar in bars: - height = bar.get_height() - ax.annotate(f'{height}', - xy=(bar.get_x() + bar.get_width() / 2, height), - xytext=(0, 3), # 3 points vertical offset - textcoords="offset points", - ha='center', va='bottom') - - # Set background to transparent - fig.patch.set_alpha(0.0) - ax.patch.set_alpha(0.0) - - # Remove top and right borders - ax.spines['top'].set_visible(False) - ax.spines['right'].set_visible(False) - - plt.xlabel("Measurement Outcome") - plt.ylabel("Counts") - - # export to SVG (ensure the directory exists) - Path("_build/html/_images").mkdir(parents=True, exist_ok=True) - filename = "_build/html/_images/fig-" + name + ("-light" if light else "-dark") + ".svg" - plt.savefig(filename, format="svg") - -name = 'qpe' -generate_plot(counts, name, light=True) -generate_plot(counts, name, light=False) -``` - -```{raw} html -QPE measurement counts -QPE measurement counts -``` - -The {py:func}`~mqt.core.dd.sample` function is a high-level interface to the -decision diagram package that does not require any knowledge of the underlying -data structure. In a similar fashion, the -{py:func}`~mqt.core.dd.simulate_statevector` and -{py:func}`~mqt.core.dd.build_unitary` functions can be used to perform -statevector simulation or to construct the unitary matrix representation of a -quantum circuit, respectively. - -```{code-cell} ipython3 -from mqt.core.dd import simulate_statevector - import numpy as np - -qc = QuantumComputation(2) -qc.h(0) -qc.cx(0, 1) - -vec = np.array(simulate_statevector(qc), copy=False) +from mqt.core.mlir import QCOProgram + +unitary_program = QCOProgram.from_mlir_str(""" +module { + func.func @main() attributes {mqt.entry_point} { + %q0 = qco.static 0 : !qco.qubit + %q1 = qco.static 1 : !qco.qubit + %q0_h = qco.h %q0 : !qco.qubit -> !qco.qubit + %q0_out, %q1_out = qco.ctrl(%q0_h) targets(%target = %q1) { + %target_out = qco.x %target : !qco.qubit -> !qco.qubit + qco.yield %target_out : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + qco.sink %q0_out : !qco.qubit + qco.sink %q1_out : !qco.qubit + return + } +} +""") + +dd = DDPackage(2) +zero_state_dd = dd.zero_state(2) +out_state_dd = unitary_program.simulate(zero_state_dd, dd) +vec = np.array(out_state_dd.get_vector(), copy=False) with np.printoptions(precision=3, suppress=True): print(vec) -``` -```{code-cell} ipython3 -from mqt.core.dd import build_unitary - -unitary = np.array(build_unitary(qc), copy=False) +functionality_dd = unitary_program.build_functionality(dd) +unitary = np.array(functionality_dd.get_matrix(2), copy=False) with np.printoptions(precision=3, suppress=True): print(unitary) ``` -Both of these functions are inherently limited in their scalability due to the -exponential growth of the resulting data structures. MQT Core also allows one to -work with decision diagrams directly, which is particularly useful for larger -quantum circuits. To this end, the {py:class}`~mqt.core.dd.DDPackage` class -provides a low-level interface to the decision diagram package. An instance of -this class can be used to simulate quantum circuits (see -{py:func}`~mqt.core.dd.simulate`), construct unitary matrices (see -{py:func}`~mqt.core.dd.build_functionality`), or perform other operations on -decision diagrams. - -```{code-cell} ipython3 -from mqt.core.dd import DDPackage, simulate - -dd = DDPackage(qc.num_qubits) -zero_state_dd = dd.zero_state(qc.num_qubits) -out_state_dd = simulate(qc, zero_state_dd, dd) -``` - If the [Graphviz](https://www.graphviz.org/) library is installed, the `graphviz` Python package can be used to visualize resulting decision diagram -via the {py:meth}`~mqt.core.dd.VectorDD.to_dot` method. To directly, generate -SVG files, the {py:meth}`~mqt.core.dd.VectorDD.to_svg` method can be used. +with the {py:meth}`~mqt.core.dd.VectorDD.to_dot` method. Use +{py:meth}`~mqt.core.dd.VectorDD.to_svg` to generate an SVG file directly. ```{code-cell} ipython3 --- diff --git a/docs/index.md b/docs/index.md index f529d95c7b..e114644293 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,14 +4,14 @@ MQT Core is an open-source C++20 and Python library for quantum computing that forms the backbone of the quantum software tools developed as part of the _{doc}`Munich Quantum Toolkit (MQT) `_. To this end, MQT Core consists of multiple components that are used throughout the MQT, including a -fully fledged intermediate representation (IR) for quantum computations, a -state-of-the-art decision diagram (DD) package for quantum computing, and a QIR -runtime based on the decision diagram package. +compiler collection built on MLIR, a state-of-the-art decision diagram (DD) +package for quantum computing, and a QIR runtime based on the decision diagram +package. This documentation provides a comprehensive guide to the MQT Core library, including {doc}`installation instructions `, a -{doc}`quickstart guide for the MQT Core IR `, its -{doc}`decision diagram (DD) package `, as well as detailed +{doc}`guide to the MQT Compiler Collection `, +its {doc}`decision diagram (DD) package `, as well as detailed {doc}`API documentation ` and the C++ API reference. The source code of MQT Core is publicly available on GitHub at @@ -34,7 +34,6 @@ self installation benchmarks -mqt_core_ir dd_package mlir/index qdmi/index diff --git a/docs/mlir/python_compiler_collection.md b/docs/mlir/python_compiler_collection.md index 9d5a0efb05..a62cebe4dd 100644 --- a/docs/mlir/python_compiler_collection.md +++ b/docs/mlir/python_compiler_collection.md @@ -14,10 +14,7 @@ Collection. It accepts source strings, {code}`.qasm`, {code}`.mlir`, and and typed compiler programs. The requested output format determines where compilation stops and which program type is returned. -The compiler does not accept legacy {py:class}`~mqt.core.ir.QuantumComputation` -objects. Convert such an object to OpenQASM 3 with -{py:meth}`~mqt.core.ir.QuantumComputation.qasm3_str` before passing it to the -compiler. +The compiler collection is the circuit and program interface in MQT Core v4. Install {doc}`MQT Core <../installation>` and import the compiler interface: @@ -167,12 +164,7 @@ assert restored.count_ops() == qiskit_bell.count_ops() assert compiled_qiskit.is_valid ``` -This compiler route does not construct an intermediate -{py:class}`~mqt.core.ir.QuantumComputation`. The existing -{py:func}`~mqt.core.plugins.qiskit.qiskit_to_mqt`, -{py:func}`~mqt.core.plugins.qiskit.mqt_to_qiskit`, and {py:func}`mqt.core.load` -interfaces remain independent and retain their existing version range and -behavior. +This compiler route is the Qiskit circuit interface in MQT Core v4. Qiskit 2.5's C API cannot construct classical expressions or structured control flow, so export uses Qiskit's public Python classes for these operations. diff --git a/docs/mqt_core_ir.md b/docs/mqt_core_ir.md deleted file mode 100644 index a5a472efda..0000000000 --- a/docs/mqt_core_ir.md +++ /dev/null @@ -1,412 +0,0 @@ ---- -file_format: mystnb -kernelspec: - name: python3 -mystnb: - number_source_lines: true ---- - -```{code-cell} ipython3 -:tags: [remove-cell] -%config InlineBackend.figure_formats = ['svg'] -``` - -# MQT Core IR - -The central interface for working with quantum computations throughout the -Munich Quantum Toolkit is the {py:class}`~mqt.core.ir.QuantumComputation` class. -It effectively represents quantum computations as sequential lists of operation, -similar to Qiskit's {py:class}`~qiskit.circuit.QuantumCircuit` class. - -The following will demonstrate how to work with the -{py:class}`~mqt.core.ir.QuantumComputation` class in Python. - -:::{note} -MQT Core is primarily designed in C++ with a thin Python wrapper. Historically, -the C++ part of MQT Core was the focus and the Python interface was added later. -As the standards we hold ourselves to have evolved, the Python interface is much -better documented than the C++ interface. Contributions to the C++ documentation -are welcome. See the [contribution guidelines](contributing.md) for more -information. -::: - -## Quickstart - -The following code snippet demonstrates how to construct a quantum computation -for an instance of the Iterative Quantum Phase Estimation algorithm that aims to -estimate the phase of a unitary operator $U=p(3\pi/8)$ using 3 bits of -precision. - -```{code-cell} ipython3 ---- -mystnb: - text_lexer: 'qasm3' ---- -from mqt.core.ir import QuantumComputation -from mqt.core.ir.operations import OpType - -from math import pi - -theta = 3 * pi / 8 -precision = 3 - -# Create an empty quantum computation -qc = QuantumComputation() - -# Counting register -q = qc.add_qubit_register(1, "q") - -# Eigenstate register -psi = qc.add_qubit_register(1, "psi") - -# Classical register for the result, the estimated phase is `0.c_2 c_1 c_0 * pi` -c = qc.add_classical_register(precision, "c") - -# Prepare psi in the eigenstate |1> -qc.x(psi[0]) - -for i in range(precision): - # Hadamard on the working qubit - qc.h(q[0]) - - # Controlled phase gate - qc.cp(2**(precision - i - 1) * theta, q[0], psi[0]) - - # Iterative inverse QFT - for j in range(i): - qc.if_(op_type=OpType.p, target=q[0], control_bit=c[j], params=[-pi / 2**(i - j)]) - qc.h(q[0]) - - # Measure the result - qc.measure(q[0], c[i]) - - # Reset the qubit if not finished - if i < precision - 1: - qc.reset(q[0]) -``` - -The circuit class provides lots of flexibility when it comes to the kind of -gates that can be applied. Check out the full API documentation of the -{py:class}`~mqt.core.ir.QuantumComputation` class for more details. - -## Visualizing Circuits - -Circuits can be printed in a human-readable, text-based format. The output is to -be read from top to bottom and left to right. Each line represents a single -operation in the circuit. - -:::{note} -The first and last lines have a special meaning: the first line contains the -initial layout information, while the last line contains the output permutation. -This is explained in more detail in the -[Layout Information](#layout-information) section. -::: - -```{code-cell} ipython3 -print(qc) -``` - -Circuits can also easily be exported to OpenQASM 3 using the -{py:meth}`~mqt.core.ir.QuantumComputation.qasm3_str` method. - -```{code-cell} ipython3 ---- -mystnb: - text_lexer: 'qasm3' ---- -print(qc.qasm3_str()) -``` - -## Layout Information - -When compiling a quantum circuit for a specific quantum device, it is necessary -to map the qubits of the circuit to the qubits of the device. In addition, SWAP -operations might be necessary to ensure that gates are only applied to qubits -connected on the device. These SWAP operations permute the assignment of circuit -qubits to device qubits. At the end of the computation, the values of the -circuit qubits are measured at specific device qubits. This kind of -_layout information_ is important for reasoning about the functionality of the -compiled circuit. As such, preserving this information is essential for -verification and debugging purposes. - -:::{note} -In the literature, the qubits used in the circuit are often referred to as -_logical qubits_ or _virtual qubits_, while the qubits of the device are also -called _physical qubits_. Within the MQT, we try to avoid the terms _logical_ -and _physical_ qubits, as they can be misleading due to the connection to error -correction. Instead, we use the terms _circuit qubits_ and _device qubits_. -::: - -To this end, the {py:class}`~mqt.core.ir.QuantumComputation` class contains two -members, {py:attr}`~mqt.core.ir.QuantumComputation.initial_layout` and -{py:attr}`~mqt.core.ir.QuantumComputation.output_permutation`, which are -instances of the {py:class}`~mqt.core.ir.Permutation` class. The initial layout -tracks the mapping of circuit qubits to device qubits at the beginning of the -computation, while the output permutation tracks where a particular circuit -qubit is measured at the end of the computation. While the output permutation -can generally be inferred from the measurements in the circuit (using -{py:meth}`~mqt.core.ir.QuantumComputation.initialize_io_mapping`), the initial -layout is not always clear. OpenQASM, for example, lacks a way to express the -initial layout of a circuit and preserve this information. Therefore, MQT Core -will output the layout information as comments in the first two lines of the -QASM string using the following format: - -- `// i Q_0, Q_1, ..., Q_n`, meaning circuit qubit $i$ is mapped to device qubit - $Q_i$. -- `// o Q_0, Q_1, ..., Q_n` meaning the value of circuit qubit $i$ (assumed to - be stored in classical bit $c[i]$) is measured at device qubit $Q_i$. - -An example illustrates the idea: - -```{code-cell} ipython3 ---- -mystnb: - text_lexer: 'qasm3' ---- -# 3 qubits, 3 classical bits -qc = QuantumComputation(3, 3) - -qc.h(0) -qc.x(1) -qc.s(2) - -# c[0] is measured at device qubit 1 -qc.measure(1, 0) -# c[1] is measured at device qubit 2 -qc.measure(2, 1) -# c[2] is measured at device qubit 0 -qc.measure(0, 2) - -# determine permutation from measurement -qc.initialize_io_mapping() - -print(qc.qasm3_str()) -``` - -If {py:attr}`~mqt.core.ir.QuantumComputation.output_permutation` is not empty, -{py:meth}`~mqt.core.ir.QuantumComputation.initialize_io_mapping` uses it as an -existing mapping. The permutation must contain every measured device qubit as a -key. Clear {py:attr}`~mqt.core.ir.QuantumComputation.output_permutation` before -the call to infer it only from the measurements. - -In the example above, the initial layout is not explicitly specified. A trivial -layout is thus assumed, where the circuit qubits are mapped to the device qubits -in order. The output permutation is determined from the measurements and is -printed as comments in the QASM string. - -:::{note} -This layout information is not part of the OpenQASM 3 standard. It is a feature -of MQT Core to help with debugging and verification. MQT Core's QASM export will -always include this layout information in the first two lines of the QASM -string. MQT Core's QASM import will parse these lines and set the initial layout -and output permutation accordingly. -::: - -## Operations - -The operations in a {py:class}`~mqt.core.ir.QuantumComputation` object are of -type {py:class}`~mqt.core.ir.operations.Operation`. Every type of operation in -`mqt-core` is derived from this class. Operations can also be explicitly -constructed. Each {py:class}`~mqt.core.ir.operations.Operation` has a type in -the form of an {py:class}`~mqt.core.ir.operations.OpType`. - -### `StandardOperation` - -A {py:class}`~mqt.core.ir.operations.StandardOperation` is used to represent -basic unitary gates. These can also be declared with arbitrarily many controls. - -```{code-cell} ipython3 -from mqt.core.ir.operations import OpType, StandardOperation, Control - -# u3 gate on qubit 0 -u_gate = StandardOperation(target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u) - -# controlled x-rotation -crx = StandardOperation(control=Control(0), target=1, params=[pi], op_type=OpType.rx) - -# multi-controlled x-gate -mcx = StandardOperation(controls={Control(0), Control(1)}, target=2, op_type=OpType.x) - -# add operations to a quantum computation -qc = QuantumComputation(3) -qc.append(u_gate) -qc.append(crx) -qc.append(mcx) - -print(qc) -``` - -### `NonUnitaryOperation` - -A {py:class}`~mqt.core.ir.operations.NonUnitaryOperation` is used to represent -operations involving measurements or resets. - -```{code-cell} ipython3 -from mqt.core.ir.operations import NonUnitaryOperation - -nqubits = 2 -qc = QuantumComputation(nqubits, nqubits) -qc.h(0) - -# measure qubit 0 on classical bit 0 -meas_0 = NonUnitaryOperation(target=0, classic=0) - -# reset all qubits -reset = NonUnitaryOperation(targets=[0, 1], op_type=OpType.reset) - -qc.append(meas_0) -qc.append(reset) - -print(qc) -``` - -### `SymbolicOperation` - -A {py:class}`~mqt.core.ir.operations.SymbolicOperation` can represent all gates -of a {py:class}`~mqt.core.ir.operations.StandardOperation` but the gate -parameters can be symbolic. Symbolic expressions are represented in MQT using -the {py:class}`~mqt.core.ir.symbolic.Expression` type, which represent linear -combinations of symbolic {py:class}`~mqt.core.ir.symbolic.Term` objects over -some set of {py:class}`~mqt.core.ir.symbolic.Variable` objects. - -```{code-cell} ipython3 -from mqt.core.ir.operations import SymbolicOperation -from mqt.core.ir.symbolic import Expression, Term, Variable - -x = Variable("x") -y = Variable("y") -sym = Expression([Term(x, 2), Term(y, 3)]) -print(sym) - -sym += 1 -print(sym) - -# Create symbolic gate -u1_symb = SymbolicOperation(target=0, params=[sym], op_type=OpType.p) - -# Mixed symbolic and instantiated parameters -u2_symb = SymbolicOperation(target=0, params=[sym, 2.0], op_type=OpType.u2) -``` - -### `CompoundOperation` - -A {py:class}`~mqt.core.ir.operations.CompoundOperation` bundles multiple -{py:class}`~mqt.core.ir.operations.Operation` objects together. - -```{code-cell} ipython3 -from mqt.core.ir.operations import CompoundOperation - -comp_op = CompoundOperation() - -# create bell pair circuit -comp_op.append(StandardOperation(0, op_type=OpType.h)) -comp_op.append(StandardOperation(target=0, control=Control(1), op_type=OpType.x)) - -qc = QuantumComputation(2) -qc.append(comp_op) - -print(qc) -``` - -Circuits can be conveniently turned into operations which allows to create -nested circuits: - -```{code-cell} ipython3 -nqubits = 2 -comp = QuantumComputation(nqubits) -comp.h(0) -comp.cx(0, 1) - -qc = QuantumComputation(nqubits) -qc.append(comp.to_operation()) - -print(qc) -``` - -### `IfElseOperation` - -A {py:class}`~mqt.core.ir.operations.IfElseOperation` is an operation controlled -by a classical bit or a classical register. If a given condition is met, the -{py:attr}`~mqt.core.ir.operations.IfElseOperation.then_operation` is applied. If -the condition is not met, the -{py:attr}`~mqt.core.ir.operations.IfElseOperation.else_operation` is applied. - -```{code-cell} ipython3 -qc = QuantumComputation(1, 1) - -qc.h(0) -qc.measure(0, 0) -qc.if_else( - then_operation=StandardOperation(target=0, op_type=OpType.x), - else_operation=StandardOperation(target=0, op_type=OpType.y), - control_bit=0, -) - -print(qc) -``` - -If you do not need an `else_operation`, the -{py:class}`~mqt.core.ir.QuantumComputation` class provides a shortcut for -creating an {py:meth}`~mqt.core.ir.QuantumComputation.if_` operation. - -```{code-cell} ipython3 -qc = QuantumComputation(1, 1) - -qc.h(0) -qc.measure(0, 0) -qc.if_(op_type=OpType.x, target=0, control_bit=0) - -print(qc) -``` - -## Interfacing with other SDKs and Formats - -### OpenQASM - -OpenQASM is a widely used format for representing quantum circuits. Its latest -version, [OpenQASM 3](https://openqasm.com/index.html), is a powerful language -that can express a wide range of quantum circuits. MQT Core supports the full -functionality of OpenQASM 2.0 (including classically controlled operations) and -a growing subset of OpenQASM 3. - -```{code-cell} ipython3 -from mqt.core.ir import QuantumComputation - -qasm_str = """ -OPENQASM 3.0; -include "stdgates.inc"; -qubit[3] q; -h q[0]; -cx q[0], q[1]; -cx q[0], q[2]; -""" - -qc = QuantumComputation.from_qasm_str(qasm_str) - -print(qc) -``` - -### Qiskit - -In addition to OpenQASM, `mqt-core` can natively import -[Qiskit](https://qiskit.org/) {py:class}`~qiskit.circuit.QuantumCircuit` -objects. - -```{code-cell} ipython3 -from qiskit import QuantumCircuit - -# GHZ circuit in qiskit -qiskit_qc = QuantumCircuit(3) -qiskit_qc.h(0) -qiskit_qc.cx(0, 1) -qiskit_qc.cx(0, 2) - -qiskit_qc.draw(output="mpl", style="iqp") -``` - -```{code-cell} ipython3 -from mqt.core.plugins.qiskit import qiskit_to_mqt - -mqt_qc = qiskit_to_mqt(qiskit_qc) -print(mqt_qc) -``` diff --git a/docs/qdmi/ddsim_device.md b/docs/qdmi/ddsim_device.md index 2817165a97..7397d70635 100644 --- a/docs/qdmi/ddsim_device.md +++ b/docs/qdmi/ddsim_device.md @@ -10,11 +10,11 @@ simulate quantum programs. ## Capabilities -The simulator device supports all operations that our -[MQT Core IR](../mqt_core_ir.md) supports. It accepts OpenQASM 2, OpenQASM 3, -and textual or binary QIR programs using the Base or Adaptive Profile. See -[QIR Support in the MQT](../qir/index.md) for the exact QDMI program formats and -payload contracts. +The simulator device accepts OpenQASM 2, OpenQASM 3, and textual or binary QIR +programs using the Base or Adaptive Profile. See the +{doc}`OpenQASM support table <../mlir/OpenQASM>` and +[QIR Support in the MQT](../qir/index.md) for the supported operations, exact +QDMI program formats, and payload contracts. The device can perform weak simulation for every supported format, i.e., sample from the distribution produced by the program. It can also perform strong @@ -25,18 +25,26 @@ for weak simulation or to `0` for strong simulation. QIR Adaptive Profile programs require at least one shot because their measurement-dependent control flow cannot be represented by state extraction. -For reproducible sampling, set `QDMI_DEVICE_JOB_PARAMETER_CUSTOM1` to a positive -`int` seed. The Python API exposes the same parameter as `custom1`. If `custom1` -is absent, the device seeds the random-number generator from the system. The -seed applies to OpenQASM and QIR sampling jobs. State extraction does not use -it. +For OpenQASM state extraction, terminal output measurements are deferred. They +do not collapse the returned state. Mid-circuit measurements still execute and +may therefore collapse the state before subsequent operations. -Under the hood, the QDMI device uses the MQT Core OpenQASM parser (see -{cpp-api:func}`qasm3::Importer::imports`) to parse the program into a -{cpp-api:class}`qc::QuantumComputation` object. That circuit is then passed -either to the {cpp-api:func}`dd::sample` or {cpp-api:func}`dd::simulate` -function, depending on the mode. Consult the respective documentation for more -details and limitations. +For reproducible stochastic execution, set `QDMI_DEVICE_JOB_PARAMETER_CUSTOM1` +to a positive `int` seed. The Python API exposes the same parameter as +`custom1`. If `custom1` is absent, the device seeds the random-number generator +from the system. The seed controls OpenQASM and QIR sampling. During OpenQASM +state extraction, it also controls mid-circuit measurements and resets; QIR Base +state extraction does not use it. + +Under the hood, the QDMI device imports OpenQASM into the compiler's QC +representation, lowers it to QCO, and executes it with the QCO DD utilities. +This is the same compiler-backed simulation path exposed by +{py:class}`~mqt.core.mlir.QCOProgram`. + +OpenQASM 3 output bits are undefined until written, so direct QDMI jobs with a +partially initialized output register fail during import. The Qiskit backend +preserves Qiskit's zero-initialized classical-bit semantics by writing every +classical bit before submitting its generated OpenQASM 3 program. The device implements the full QDMI job interface (except for the `QDMI_JOB_RESULT_SHOTS` result format not supported by the simulator). diff --git a/docs/qdmi/driver.md b/docs/qdmi/driver.md index 00da77a1c5..7b15418d9b 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -34,9 +34,9 @@ use. They can be selected independently before making MQT Core available: - {code}`BUILD_MQT_CORE_QDMI_DDSIM_DEVICE` - {code}`BUILD_MQT_CORE_QDMI_SC_DEVICE` -The {code}`BUILD_MQT_CORE_MLIR` option controls the MLIR compiler infrastructure -and QIR support in the DDSIM device. A build with this option set to {code}`OFF` -retains the DDSIM device's OpenQASM 2 and OpenQASM 3 support. +The DDSIM device uses the MLIR compiler infrastructure for both OpenQASM and QIR +programs. Its target is skipped when {code}`BUILD_MQT_CORE_MLIR` is {code}`OFF`, +while the QDMI driver and superconducting device remain available. For example, an embedded simulator consumer can enable only the DDSIM device, while CUDA-Q can enable the DDSIM and superconducting devices used by its @@ -44,8 +44,8 @@ integration tests. The QDMI driver and QDMI libraries are available independently. Device-free builds can register external device libraries through -[QDMI device configuration](configuration.md). Building MQT Core's C++ tests -requires both bundled devices so that the complete device integration is tested. +[QDMI device configuration](configuration.md). C++ test builds require every +bundled device available in the selected build configuration. ## Python Bindings diff --git a/doxygen/namespaces.md b/doxygen/namespaces.md index f0bd962015..b02522fa7c 100644 --- a/doxygen/namespaces.md +++ b/doxygen/namespaces.md @@ -1,17 +1,9 @@ # Namespace documentation -@namespace qc - -Quantum-circuit representation and algorithms. - @namespace dd Decision-diagram data structures and simulation algorithms. -@namespace qasm3 - -OpenQASM 3 parsing, representation, and compilation support. - @namespace qdmi MQT Core's QDMI driver, owning client wrappers, and device integrations. @@ -20,18 +12,10 @@ MQT Core's QDMI driver, owning client wrappers, and device integrations. Decision-diagram-backed QDMI device implementation. -@namespace qdmi::sc - -Superconducting QDMI device implementation. - @namespace qir Quantum Intermediate Representation runtime and JIT support. @namespace sc -Superconducting-device helpers. - -@namespace sym - -Symbolic expression support for quantum operations. +Superconducting QDMI device configuration and helpers. diff --git a/include/mqt-core/dd/Complex.hpp b/include/mqt-core/dd/Complex.hpp index 252c3a7525..8249c1c187 100644 --- a/include/mqt-core/dd/Complex.hpp +++ b/include/mqt-core/dd/Complex.hpp @@ -16,7 +16,6 @@ #include "dd/DDDefinitions.hpp" #include "dd/RealNumber.hpp" -#include "ir/Definitions.hpp" #include #include @@ -164,6 +163,6 @@ template <> struct std::hash { std::size_t operator()(dd::Complex const& c) const noexcept { const auto h1 = dd::murmur64(reinterpret_cast(c.r)); const auto h2 = dd::murmur64(reinterpret_cast(c.i)); - return qc::combineHash(h1, h2); + return dd::combineHash(h1, h2); } }; diff --git a/include/mqt-core/dd/ComputeTable.hpp b/include/mqt-core/dd/ComputeTable.hpp index 4f8a46c459..915bdbf510 100644 --- a/include/mqt-core/dd/ComputeTable.hpp +++ b/include/mqt-core/dd/ComputeTable.hpp @@ -16,7 +16,6 @@ #pragma once #include "dd/statistics/TableStatistics.hpp" -#include "ir/Definitions.hpp" #include #include @@ -74,7 +73,7 @@ class ComputeTable { const RightOperandType& rightOperand) const { const auto h1 = std::hash{}(leftOperand); const auto h2 = std::hash{}(rightOperand); - const auto hash = qc::combineHash(h1, h2); + const auto hash = combineHash(h1, h2); const auto mask = stats.numBuckets - 1; return hash & mask; } diff --git a/include/mqt-core/dd/DDDefinitions.hpp b/include/mqt-core/dd/DDDefinitions.hpp index 36b96e880d..4aad640b05 100644 --- a/include/mqt-core/dd/DDDefinitions.hpp +++ b/include/mqt-core/dd/DDDefinitions.hpp @@ -14,14 +14,16 @@ #pragma once -#include "ir/Definitions.hpp" - #include #include #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -37,6 +39,67 @@ namespace dd { */ using Qubit = std::uint16_t; +/// Qubit indices targeted by an operation. +using Targets = std::vector; + +/// A control qubit and its polarity. +struct Control { + /// Control polarity. + enum class Type : bool { + /// Positive controls trigger on \f$\ket{1}\f$. + Pos = true, + /// Negative controls trigger on \f$\ket{0}\f$. + Neg = false + }; + + /// Control qubit index. + Qubit qubit{}; + /// Control polarity. + Type type = Type::Pos; + + /// Allow implicit conversion from a qubit index. + /// NOLINTBEGIN(google-explicit-constructor) + Control(const Qubit q = {}, const Type t = Type::Pos) : qubit(q), type(t) {} + /// NOLINTEND(google-explicit-constructor) + + [[nodiscard]] std::string toString() const { + std::ostringstream oss{}; + oss << "Control(qubit=" << qubit << ", type_=\"" + << (type == Type::Pos ? "Pos" : "Neg") << "\")"; + return oss.str(); + } +}; + +inline bool operator<(const Control& lhs, const Control& rhs) { + return lhs.qubit < rhs.qubit || + (lhs.qubit == rhs.qubit && lhs.type < rhs.type); +} + +inline bool operator==(const Control& lhs, const Control& rhs) { + return lhs.qubit == rhs.qubit && lhs.type == rhs.type; +} + +/// Compare controls by qubit index. +struct CompareControl { + using is_transparent [[maybe_unused]] = void; + + bool operator()(const Control& lhs, const Control& rhs) const { + return lhs < rhs; + } + bool operator()(const Qubit lhs, const Control& rhs) const { + return lhs < rhs.qubit; + } + bool operator()(const Control& lhs, const Qubit rhs) const { + return lhs.qubit < rhs; + } +}; + +/// Controls sorted by qubit index and polarity. +using Controls = std::set; + +/// Map logical qubit indices to physical qubit indices. +using Permutation = std::map; + /** * @brief Floating point type to use for computations * @note Adjusting the precision might lead to unexpected results. @@ -65,12 +128,23 @@ static constexpr fp PI = std::numbers::pi; static constexpr auto PI_2 = PI / 2; static constexpr fp PI_4 = PI / 4; +/// Combine two hashes with the Boost hash-combine formula. +[[nodiscard]] constexpr std::size_t +combineHash(const std::size_t lhs, const std::size_t rhs) noexcept { + return lhs ^ (rhs + 0x9e3779b97f4a7c15ULL + (lhs << 6) + (lhs >> 2)); +} + +/// Add an integer to a hash. +constexpr void hashCombine(std::size_t& hash, const std::size_t with) noexcept { + hash = combineHash(hash, with); +} + static constexpr std::uint64_t SERIALIZATION_VERSION = 1; struct PairHash { std::size_t operator()(const std::pair& p) const noexcept { - return qc::combineHash(p.first, p.second); + return combineHash(p.first, p.second); } }; @@ -151,3 +225,10 @@ template concept IsMatrix = std::is_same_v; } // namespace dd + +template <> struct std::hash { + std::size_t operator()(const dd::Control& control) const noexcept { + return std::hash{}(control.qubit) ^ + std::hash{}(control.type); + } +}; diff --git a/include/mqt-core/dd/FunctionalityConstruction.hpp b/include/mqt-core/dd/FunctionalityConstruction.hpp deleted file mode 100644 index 170573c42e..0000000000 --- a/include/mqt-core/dd/FunctionalityConstruction.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file FunctionalityConstruction.hpp - * @brief Construct decision-diagram functionality from quantum circuits. - */ - -#pragma once - -#include "dd/Package_fwd.hpp" - -namespace qc { -class QuantumComputation; -} - -namespace dd { -/** - * @brief Sequentially build a decision diagram representation for the - * functionality of a purely-quantum @ref qc::QuantumComputation. - * - * @details For a circuit \f$G\f$ with \f$|G|\f$ gates - * \f$g_0, g_1, \ldots, g_{|G|-1}\f$, the functionality of \f$G\f$ is defined as - * the unitary matrix \f$U\f$ such that - * \f[ - * U = U_{|G|-1}) \cdot U_{|G|-2} \cdot \ldots \cdot U_1 \cdot U_0, - * \f] - * where \f$U_i\f$ is the unitary matrix corresponding to gate \f$g_i\f$. - * For an \f$n\f$-qubit quantum computation, \f$U\f$ is a \f$2^n \times 2^n\f$ - * matrix. - * - * By representing every single operation in the circuit as a decision diagram - * instead of a unitary matrix and performing the matrix multiplication directly - * using decision diagrams, a representation of the functionality of a quantum - * computation can oftentimes be computed more efficiently in terms of memory - * and runtime. - * - * This function effectively computes - * \f[ - * DD(U) = DD(g_{|G|-1}) \otimes DD(g_{|G|-2}) \otimes \ldots \otimes DD(g_0) - * \f] - * by sequentially applying the decision diagrams of the gates in the circuit to - * the current decision diagram representing the functionality of the quantum - * computation. - * - * @param qc The quantum computation to construct the functionality for - * @param dd The DD package to use for the construction - * @return The matrix diagram representing the functionality of the quantum - * computation - */ -MatrixDD buildFunctionality(const qc::QuantumComputation& qc, Package& dd); - -} // namespace dd diff --git a/include/mqt-core/dd/GateMatrixDefinitions.hpp b/include/mqt-core/dd/GateMatrixDefinitions.hpp index 293b9ab50a..df50efad21 100644 --- a/include/mqt-core/dd/GateMatrixDefinitions.hpp +++ b/include/mqt-core/dd/GateMatrixDefinitions.hpp @@ -15,12 +15,94 @@ #pragma once #include "dd/DDDefinitions.hpp" -#include "ir/operations/OpType.hpp" +#include #include namespace dd { +/// Gates supported by the DD package. +enum class GateType : std::uint8_t { + None, + I, + H, + X, + Y, + Z, + S, + Sdg, + T, + Tdg, + U, + U2, + P, + SX, + SXdg, + RX, + RY, + RZ, + R, + SWAP, + iSWAP, + DCX, + ECR, + RXX, + RYY, + RZZ, + RZX, + XXminusYY, + XXplusYY, + RCCX +}; + +[[nodiscard]] constexpr bool isSingleQubitGate(const GateType type) { + switch (type) { + case GateType::I: + case GateType::H: + case GateType::X: + case GateType::Y: + case GateType::Z: + case GateType::S: + case GateType::Sdg: + case GateType::T: + case GateType::Tdg: + case GateType::U: + case GateType::U2: + case GateType::P: + case GateType::SX: + case GateType::SXdg: + case GateType::RX: + case GateType::RY: + case GateType::RZ: + case GateType::R: + return true; + default: + return false; + } +} + +[[nodiscard]] constexpr bool isTwoQubitGate(const GateType type) { + switch (type) { + case GateType::SWAP: + case GateType::iSWAP: + case GateType::DCX: + case GateType::ECR: + case GateType::RXX: + case GateType::RYY: + case GateType::RZZ: + case GateType::RZX: + case GateType::XXminusYY: + case GateType::XXplusYY: + return true; + default: + return false; + } +} + +[[nodiscard]] constexpr bool isThreeQubitGate(const GateType type) { + return type == GateType::RCCX; +} + /// Single-qubit gate matrix for collapsing a qubit to the |0> state constexpr GateMatrix MEAS_ZERO_MAT{1, 0, 0, 0}; /// Single-qubit gate matrix for collapsing a qubit to the |1> state @@ -32,7 +114,7 @@ constexpr GateMatrix MEAS_ONE_MAT{0, 0, 0, 1}; * @param params The parameters of the quantum operation * @return The single-qubit gate matrix representation of the quantum operation */ -GateMatrix opToSingleQubitGateMatrix(qc::OpType t, +GateMatrix opToSingleQubitGateMatrix(GateType t, const std::vector& params = {}); /** @@ -41,7 +123,7 @@ GateMatrix opToSingleQubitGateMatrix(qc::OpType t, * @param params The parameters of the quantum operation * @return The two-qubit gate matrix representation of the quantum operation */ -TwoQubitGateMatrix opToTwoQubitGateMatrix(qc::OpType t, +TwoQubitGateMatrix opToTwoQubitGateMatrix(GateType t, const std::vector& params = {}); /** @@ -51,6 +133,6 @@ TwoQubitGateMatrix opToTwoQubitGateMatrix(qc::OpType t, * @return The three-qubit gate matrix representation of the quantum operation */ ThreeQubitGateMatrix -opToThreeQubitGateMatrix(qc::OpType t, const std::vector& params = {}); +opToThreeQubitGateMatrix(GateType t, const std::vector& params = {}); } // namespace dd diff --git a/include/mqt-core/dd/Operations.hpp b/include/mqt-core/dd/Operations.hpp index 2bb22d9707..cc3b2c2c67 100644 --- a/include/mqt-core/dd/Operations.hpp +++ b/include/mqt-core/dd/Operations.hpp @@ -17,20 +17,7 @@ #include "dd/DDDefinitions.hpp" #include "dd/GateMatrixDefinitions.hpp" #include "dd/Package.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include -#include -#include -#include -#include #include namespace dd { @@ -49,179 +36,8 @@ namespace dd { * @param targets The operation targets * @return The decision diagram representation of the operation */ -MatrixDD getStandardOperationDD(Package& dd, qc::OpType type, - const std::vector& params, - const qc::Controls& controls, - const std::vector& targets); - -/** - * @brief Get the decision diagram representation of a @ref - * qc::StandardOperation. - * - * @note This function is only intended for internal use and should not be - * called directly. - * - * @param op The operation to get the DD for - * @param dd The DD package to use - * @param controls The operation controls - * @param targets The operation targets - * @param inverse Whether to get the inverse of the operation - * @return The decision diagram representation of the operation - */ -MatrixDD getStandardOperationDD(const qc::StandardOperation& op, Package& dd, - const qc::Controls& controls, - const std::vector& targets, - bool inverse); - -/** - * @brief Get the decision diagram representation of an operation. - * - * @param op The operation to get the DD for - * @param dd The DD package to use - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @param inverse Whether to get the inverse of the operation - * @return The decision diagram representation of the operation - */ -MatrixDD getDD(const qc::Operation& op, Package& dd, - const qc::Permutation& permutation = {}, bool inverse = false); - -/** - * @brief Get the decision diagram representation of the inverse of an - * operation. - * - * @see getDD - * - * @param op The operation to get the inverse DD for - * @param dd The DD package to use - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @return The decision diagram representation of the inverse of the operation - */ -MatrixDD getInverseDD(const qc::Operation& op, Package& dd, - const qc::Permutation& permutation = {}); - -/** - * @brief Apply a unitary operation to a given vector DD. - * - * @details This is a convenience function that realizes @p op times @p in and - * correctly accounts for the permutation of the operation's qubits as well as - * automatically handles reference counting. - * - * @param op The operation to apply - * @param in The input DD - * @param dd The DD package to use - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @return The output DD - */ -VectorDD applyUnitaryOperation(const qc::Operation& op, const VectorDD& in, - Package& dd, - const qc::Permutation& permutation = {}); - -/** - * @brief Apply a unitary operation to a given matrix DD. - * - * @details This is a convenience function that realizes @p op times @p in and - * correctly accounts for the permutation of the operation's qubits as well as - * automatically handles reference counting. - * - * @param op The operation to apply - * @param in The input DD - * @param dd The DD package to use - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @param applyFromLeft Whether to apply the operation from the left (true) - * or from the right (false). - * @return The output DD - */ -MatrixDD applyUnitaryOperation(const qc::Operation& op, const MatrixDD& in, - Package& dd, - const qc::Permutation& permutation = {}, - bool applyFromLeft = true); - -/** - * @brief Apply a measurement operation to a given DD. - * - * @details This is a convenience function that realizes the measurement @p op - * on @p in and stores the measurement results in @p measurements. The result is - * determined based on the RNG @p rng. The function correctly accounts for the - * permutation of the operation's qubits as well as automatically handles - * reference counting. - * - * @param op The measurement operation to apply - * @param in The input DD - * @param dd The DD package to use - * @param rng The random number generator to use - * @param measurements The vector to store the measurement results in - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @return The output DD - */ -VectorDD applyMeasurement(const qc::NonUnitaryOperation& op, VectorDD in, - Package& dd, std::mt19937_64& rng, - std::vector& measurements, - const qc::Permutation& permutation = {}); - -/** - * @brief Apply a reset operation to a given DD. - * - * @details This is a convenience function that realizes the reset @p op on @p - * in. To this end, it measures the qubit and applies an X operation if the - * measurement result is one. The result is determined based on the RNG @p rng. - * The function correctly accounts for the permutation of the operation's - * qubits as well as automatically handles reference counting. - * - * @param op The reset operation to apply - * @param in The input DD - * @param dd The DD package to use - * @param rng The random number generator to use - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @return The output DD - */ -VectorDD applyReset(const qc::NonUnitaryOperation& op, VectorDD in, Package& dd, - std::mt19937_64& rng, - const qc::Permutation& permutation = {}); - -/** - * @brief Apply an if-else operation to a given DD. - * - * @details This is a convenience function that realizes the if-else operation - * @p op on @p in. It applies the underlying operation if the actual value - * stored in the measurement results matches the expected value according to the - * comparison kind. The function correctly accounts for the permutation of the - * operation's qubits as well as automatically handles reference counting. - * - * @param op The if-else operation to apply - * @param in The input DD - * @param dd The DD package to use - * @param measurements The vector of measurement results - * @param permutation The permutation to apply to the operation's qubits. An - * empty permutation marks the identity permutation. - * @return The output DD - */ -VectorDD applyIfElseOperation(const qc::IfElseOperation& op, const VectorDD& in, - Package& dd, - const std::vector& measurements, - const qc::Permutation& permutation = {}); - -/** - * @brief Check whether @p op is virtually executable. - * - * @param op The operation in question. - * @return Whether @p op is virtually executable. - */ -bool isExecutableVirtually(const qc::Operation& op) noexcept; - -/** - * @brief Apply virtual operation @p op. - * - * @param op The virtual operation to apply. - * @param permutation If suitable, the to be updated permutation. - */ -void applyVirtualOperation(const qc::Operation& op, - qc::Permutation& permutation) noexcept; +MatrixDD getGateDD(Package& dd, GateType type, const std::vector& params, + const Controls& controls, const Targets& targets); /** * @brief Apply global phase to a given DD. @@ -233,78 +49,4 @@ void applyVirtualOperation(const qc::Operation& op, */ VectorDD applyGlobalPhase(VectorDD& in, const fp& phase, Package& dd); -/** - * @brief Change the permutation of a given DD. - * - * @details This function changes the permutation of the given DD @p on from - * @p from to @p to by applying SWAP gates. The @p from permutation must be at - * least as large as the @p to permutation. - * - * @tparam DDType The type of the DD - * @param on The DD to change the permutation of - * @param from The current permutation - * @param to The target permutation - * @param dd The DD package to use - * @param regular Whether to apply the permutation from the left (true) or from - * the right (false) - */ -template -void changePermutation(DDType& on, qc::Permutation& from, - const qc::Permutation& to, Package& dd, - const bool regular = true) { - assert(from.size() >= to.size()); - if (on.isZeroTerminal()) { - return; - } - - // iterate over (k,v) pairs of second permutation - for (const auto& [i, goal] : to) { - // search for key in the first map - auto it = from.find(i); - if (it == from.end()) { - throw std::runtime_error( - "[changePermutation] Key " + std::to_string(it->first) + - " was not found in first permutation. This should never happen."); - } - auto current = it->second; - - // permutations agree for this key value - if (current == goal) { - continue; - } - - // search for goal value in first permutation - qc::Qubit j = 0; - for (const auto& [key, value] : from) { - if (value == goal) { - j = key; - break; - } - } - - // swap i and j - auto saved = on; - const auto swapDD = dd.makeTwoQubitGateDD(opToTwoQubitGateMatrix(qc::SWAP), - from.at(i), from.at(j)); - if constexpr (std::is_same_v) { - on = dd.multiply(swapDD, on); - } else { - // the regular flag only has an effect on matrix DDs - if (regular) { - on = dd.multiply(swapDD, on); - } else { - on = dd.multiply(on, swapDD); - } - } - - dd.incRef(on); - dd.decRef(saved); - dd.garbageCollect(); - - // update permutation - from.at(i) = goal; - from.at(j) = current; - } -} - } // namespace dd diff --git a/include/mqt-core/dd/Package.hpp b/include/mqt-core/dd/Package.hpp index 1faa104bab..77cac0d9b6 100644 --- a/include/mqt-core/dd/Package.hpp +++ b/include/mqt-core/dd/Package.hpp @@ -29,9 +29,6 @@ #include "dd/RealNumberUniqueTable.hpp" #include "dd/UnaryComputeTable.hpp" #include "dd/UniqueTable.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" #include #include @@ -341,7 +338,7 @@ class Package { * @param target The target qubit * @return A decision diagram for the gate */ - mEdge makeGateDD(const GateMatrix& mat, qc::Qubit target); + mEdge makeGateDD(const GateMatrix& mat, Qubit target); /** * @brief Construct the DD for a single-qubit controlled gate @@ -350,8 +347,7 @@ class Package { * @param target The target qubit * @return A decision diagram for the gate */ - mEdge makeGateDD(const GateMatrix& mat, const qc::Control& control, - qc::Qubit target); + mEdge makeGateDD(const GateMatrix& mat, const Control& control, Qubit target); /** * @brief Construct the DD for a multi-controlled single-qubit gate @@ -360,8 +356,8 @@ class Package { * @param target The target qubit * @return A decision diagram for the gate */ - mEdge makeGateDD(const GateMatrix& mat, const qc::Controls& controls, - qc::Qubit target); + mEdge makeGateDD(const GateMatrix& mat, const Controls& controls, + Qubit target); /** * @brief Creates the DD for a two-qubit gate @@ -372,8 +368,8 @@ class Package { * @throws std::runtime_error if the number of qubits is larger than the * package configuration */ - mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, qc::Qubit target0, - qc::Qubit target1); + mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, Qubit target0, + Qubit target1); /** * @brief Creates the DD for a two-qubit gate @@ -386,8 +382,8 @@ class Package { * package configuration */ mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, - const qc::Control& control, qc::Qubit target0, - qc::Qubit target1); + const Control& control, Qubit target0, + Qubit target1); /** * @brief Creates the DD for a two-qubit gate @@ -400,8 +396,8 @@ class Package { * package configuration */ mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, - const qc::Controls& controls, qc::Qubit target0, - qc::Qubit target1); + const Controls& controls, Qubit target0, + Qubit target1); /** * @brief Creates the DD for a three-qubit gate @@ -413,8 +409,8 @@ class Package { * @throws std::runtime_error if the number of qubits is larger than the * package configuration */ - mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, qc::Qubit target0, - qc::Qubit target1, qc::Qubit target2); + mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, Qubit target0, + Qubit target1, Qubit target2); /** * @brief Creates the DD for a three-qubit gate @@ -428,8 +424,8 @@ class Package { * package configuration */ mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, - const qc::Control& control, qc::Qubit target0, - qc::Qubit target1, qc::Qubit target2); + const Control& control, Qubit target0, + Qubit target1, Qubit target2); /** * @brief Creates the DD for a three-qubit gate @@ -443,8 +439,8 @@ class Package { * package configuration */ mEdge makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, - const qc::Controls& controls, qc::Qubit target0, - qc::Qubit target1, qc::Qubit target2); + const Controls& controls, Qubit target0, + Qubit target1, Qubit target2); /** * @brief Converts a given matrix to a decision diagram @@ -1210,9 +1206,9 @@ class Package { * @param permutation An optional permutation of qubits. * @return The fidelity of the measurement outcomes. */ - static fp - fidelityOfMeasurementOutcomes(const vEdge& e, const SparsePVec& probs, - const qc::Permutation& permutation = {}); + static fp fidelityOfMeasurementOutcomes(const vEdge& e, + const SparsePVec& probs, + const Permutation& permutation = {}); private: /** @@ -1246,7 +1242,7 @@ class Package { */ static fp fidelityOfMeasurementOutcomesRecursive( const vEdge& e, const SparsePVec& probs, std::size_t i, - const qc::Permutation& permutation, std::size_t nQubits); + const Permutation& permutation, std::size_t nQubits); public: /** diff --git a/include/mqt-core/dd/Simulation.hpp b/include/mqt-core/dd/Simulation.hpp deleted file mode 100644 index f0ff531c7f..0000000000 --- a/include/mqt-core/dd/Simulation.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file - * @brief Defines functions for classically simulating quantum circuits. - */ - -#pragma once - -#include "dd/Package_fwd.hpp" - -#include -#include -#include - -namespace qc { -class QuantumComputation; -} - -namespace dd { - -/** - * @brief Simulate a purely-quantum @ref qc::QuantumComputation on a given input - * state using decision diagrams. - * - * @details This method classically simulates the quantum computation @p qc on - * the input state @p in by sequentially applying the operations in the circuit - * to the initial state via decision diagram multiplication. - * - * This simple simulation method can only handle circuits that do not contain - * any classical control operations or measurements. - * Its main purpose is to construct a representation of the statevector after - * simulating the quantum computation for the given input state. - * For more elaborate simulation methods that can handle classical control and - * mid-circuit measurements, see @ref dd::sample. - * - * @param qc The quantum computation to simulate - * @param in The input state to simulate. Represented as a vector DD. - * @param dd The DD package to use for the simulation - * @return A vector DD representing the output state of the simulation - */ -VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd); - -/** - * @brief Sample from the output distribution of a quantum computation - * - * @details This method classically simulates the quantum computation @p qc - * starting from the all-zero state and samples @p shots times from the output - * distribution. - * The seed for the random number generator can be set using @p seed. - * - * For a circuit without mid-circuit measurements, this function will construct - * a representation of the final statevector similar to @ref dd::simulate and - * then repeatedly sample from the resulting decision diagram, without actually - * collapsing the state. For a fixed number of qubits, each sample can be drawn - * in constant time, which is a significant of the decision diagram structure. - * - * For a circuit with mid-circuit measurements, this function will separately - * execute the circuit for each sample, probabilistically collapsing the state - * after each measurement. - * - * @param qc The quantum computation to simulate - * @param shots The number of shots to sample - * @param seed The seed for the random number generator - * @return A histogram of the measurement results - */ -std::map sample(const qc::QuantumComputation& qc, - std::size_t shots = 1024U, - std::size_t seed = 0U); - -/** - * @brief Sample from the output distribution of a quantum computation - * - * @details This is a more general version of @ref dd::sample that allows for - * choosing the input state to simulate as well as the DD package to use for the - * simulation. - * - * @param qc The quantum computation to simulate - * @param in The input state to simulate. Represented as a vector DD. - * @param dd The DD package to use for the simulation - * @param shots The number of shots to sample - * @param seed The seed for the random number generator - * @return A histogram of the measurement results - */ -std::map sample(const qc::QuantumComputation& qc, - const VectorDD& in, Package& dd, - std::size_t shots, - std::size_t seed = 0U); -} // namespace dd diff --git a/include/mqt-core/dd/UniqueTable.hpp b/include/mqt-core/dd/UniqueTable.hpp index fc9415522c..dba291b678 100644 --- a/include/mqt-core/dd/UniqueTable.hpp +++ b/include/mqt-core/dd/UniqueTable.hpp @@ -19,7 +19,6 @@ #include "dd/MemoryManager.hpp" #include "dd/Node.hpp" #include "dd/statistics/UniqueTableStatistics.hpp" -#include "ir/Definitions.hpp" #include #include @@ -81,7 +80,7 @@ class UniqueTable { const std::size_t mask = cfg.nBuckets - 1; std::size_t key = 0U; for (const auto& succ : p.e) { - qc::hashCombine(key, std::hash>{}(succ)); + hashCombine(key, std::hash>{}(succ)); } key &= mask; return key; diff --git a/include/mqt-core/ir/Definitions.hpp b/include/mqt-core/ir/Definitions.hpp deleted file mode 100644 index b79cd5a1a9..0000000000 --- a/include/mqt-core/ir/Definitions.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file - * @brief Common definitions used throughout the library. - */ - -#pragma once - -#include -#include -#include - -namespace qc { -/** - * @brief Type alias for qubit indices. - * @details This type (alias) is used to represent qubit indices in the library. - * It has been chosen to be an unsigned 32-bit integer to allow for up to - * 4,294,967,295 qubits, which should be enough for most use cases. - */ -using Qubit = std::uint32_t; -/** - * @brief Type alias for classical bit indices. - * @details This type (alias) is used to represent classical bit indices in the - * library. The choice of 64-bits is arbitrary and can be changed if necessary. - */ -using Bit = std::uint64_t; - -/// A type alias for a vector of qubits which are supposed to act as targets. -using Targets = std::vector; - -/// Floating-point type used throughout the library -using fp = double; - -/// A constant for the value of \f$\pi\f$. -static constexpr auto PI = static_cast( - 3.141592653589793238462643383279502884197169399375105820974L); -/// A constant for the value of \f$\frac{\pi}{2}\f$. -static constexpr auto PI_2 = static_cast( - 1.570796326794896619231321691639751442098584699687552910487L); -/// A constant for the value of \f$\frac{\pi}{4}\f$. -static constexpr auto PI_4 = static_cast( - 0.785398163397448309615660845819875721049292349843776455243L); -/// A constant for the value of \f$\tau\f$. -static constexpr auto TAU = static_cast( - 6.283185307179586476925286766559005768394338798750211641950L); -/// A constant for the value of \f$e\f$. -static constexpr auto E = static_cast( - 2.718281828459045235360287471352662497757247093699959574967L); - -/// Supported file formats -enum class Format : uint8_t { - /** - * @brief OpenQASM 2.0 format - * @see https://arxiv.org/abs/1707.03429 - */ - OpenQASM2, - /** - * @brief OpenQASM 3 format - * @see https://openqasm.com/index.html - */ - OpenQASM3 -}; - -/** - * @brief Combine two 64bit hashes into one 64bit hash - * @details Combines two 64bit hashes into one 64bit hash based on - * boost::hash_combine (https://www.boost.org/LICENSE_1_0.txt) - * @param lhs The first hash - * @param rhs The second hash - */ -[[nodiscard]] constexpr std::size_t -combineHash(const std::size_t lhs, const std::size_t rhs) noexcept { - return lhs ^ (rhs + 0x9e3779b97f4a7c15ULL + (lhs << 6) + (lhs >> 2)); -} - -/** - * @brief Extend a 64bit hash with a 64bit integer - * @param hash The hash to extend - * @param with The integer to extend the hash with - */ -constexpr void hashCombine(std::size_t& hash, const std::size_t with) noexcept { - hash = combineHash(hash, with); -} - -/** - * @brief Function used to mark unreachable code - * @details Uses compiler specific extensions if possible. Even if no extension - * is used, undefined behavior is still raised by an empty function body and the - * noreturn attribute. - */ -[[noreturn]] inline void unreachable() { -#ifdef __GNUC__ // GCC, Clang, ICC - __builtin_unreachable(); -#elif defined(_MSC_VER) // MSVC - __assume(false); -#endif -} -} // namespace qc diff --git a/include/mqt-core/ir/OpenQASMSerializer.hpp b/include/mqt-core/ir/OpenQASMSerializer.hpp deleted file mode 100644 index b766f9db55..0000000000 --- a/include/mqt-core/ir/OpenQASMSerializer.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file OpenQASMSerializer.hpp - * @brief OpenQASM serialization for the circuit intermediate representation. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Register.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { - -class Operation; -class QuantumComputation; - -using QubitIndexToRegisterMap = - std::unordered_map>; -using BitIndexToRegisterMap = - std::unordered_map>; - -/** - * @brief Serializes circuit IR computations and operations to OpenQASM. - */ -class OpenQASMSerializer final { -public: - /** - * @brief Callback for serializing otherwise unsupported leaf operations. - * @return Whether the operation was serialized. - */ - using CustomOperationSerializer = std::function; - - explicit OpenQASMSerializer(std::ostream& output, - Format format = Format::OpenQASM3, - CustomOperationSerializer customSerializer = {}) - : output(output), format(format), - customOperationSerializer(std::move(customSerializer)) {} - - /** - * @brief Serialize a complete quantum computation. - * @param computation The computation to serialize. - */ - void serialize(const QuantumComputation& computation) const; - - /** - * @brief Serialize a single operation without a circuit header. - * @param operation The operation to serialize. - * @param qubitMap Map from qubit indices to their register and operand name. - * @param bitMap Map from bit indices to their register and operand name. - * @param indent Nesting level, using two spaces per level. - */ - void serialize(const Operation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, - std::size_t indent = 0U) const; - -private: - std::ostream& output; - Format format; - CustomOperationSerializer customOperationSerializer; -}; - -} // namespace qc diff --git a/include/mqt-core/ir/Permutation.hpp b/include/mqt-core/ir/Permutation.hpp deleted file mode 100644 index 0da94d2494..0000000000 --- a/include/mqt-core/ir/Permutation.hpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Permutation.hpp - * @brief Qubit permutation mappings for quantum computations. - */ - -#pragma once - -#include "Definitions.hpp" -#include "operations/Control.hpp" - -#include -#include -#include -#include -#include - -namespace qc { -class Permutation { - std::map permutation; - -public: - [[nodiscard]] auto apply(const Controls& controls) const -> Controls; - [[nodiscard]] auto apply(const Targets& targets) const -> Targets; - [[nodiscard]] auto apply(Qubit qubit) const -> Qubit; - [[nodiscard]] auto maxKey() const -> Qubit; - [[nodiscard]] auto maxValue() const -> Qubit; - - /// Constructors - Permutation() = default; - template - Permutation(InputIt first, InputIt last) : permutation(first, last) {} - Permutation(const std::initializer_list> init) - : permutation(init) {} - - /// Returns an iterator to the beginning - [[nodiscard]] auto begin() noexcept -> decltype(permutation)::iterator { - return permutation.begin(); - } - [[nodiscard]] auto begin() const noexcept - -> decltype(permutation)::const_iterator { - return permutation.begin(); - } - [[nodiscard]] auto cbegin() const noexcept -> auto { - return permutation.cbegin(); - } - - /// Returns an iterator to the end - [[nodiscard]] auto end() noexcept -> decltype(permutation)::iterator { - return permutation.end(); - } - [[nodiscard]] auto end() const noexcept - -> decltype(permutation)::const_iterator { - return permutation.end(); - } - [[nodiscard]] auto cend() const noexcept -> auto { - return permutation.cend(); - } - - /// Returns a reverse iterator to the beginning - [[nodiscard]] auto rbegin() noexcept - -> decltype(permutation)::reverse_iterator { - return permutation.rbegin(); - } - [[nodiscard]] auto rbegin() const noexcept - -> decltype(permutation)::const_reverse_iterator { - return permutation.rbegin(); - } - [[nodiscard]] auto crbegin() const noexcept -> auto { - return permutation.crbegin(); - } - - /// Returns a reverse iterator to the end - [[nodiscard]] auto rend() noexcept - -> decltype(permutation)::reverse_iterator { - return permutation.rend(); - } - [[nodiscard]] auto rend() const noexcept - -> decltype(permutation)::const_reverse_iterator { - return permutation.rend(); - } - [[nodiscard]] auto crend() const noexcept -> auto { - return permutation.crend(); - } - - /// Checks whether the permutation is empty - [[nodiscard]] auto empty() const -> bool { return permutation.empty(); } - - /// Returns the number of elements - [[nodiscard]] auto size() const -> std::size_t { return permutation.size(); } - - /// Clears the permutation - void clear() { permutation.clear(); } - - /// Finds element with specific key - [[nodiscard]] auto find(const Qubit qubit) - -> decltype(permutation.find(qubit)) { - return permutation.find(qubit); - } - [[nodiscard]] auto find(const Qubit qubit) const - -> decltype(permutation.find(qubit)) { - return permutation.find(qubit); - } - - /// Returns the number of elements with specific key - [[nodiscard]] auto count(const Qubit qubit) const -> std::size_t { - return permutation.count(qubit); - } - - /// Access specified element with bounds checking - [[nodiscard]] auto at(const Qubit qubit) const -> Qubit { - return permutation.at(qubit); - } - - /// Access specified element with bounds checking - [[nodiscard]] auto at(const Qubit qubit) -> Qubit& { - return permutation.at(qubit); - } - - /// Access or insert specified element - [[nodiscard]] auto operator[](const Qubit qubit) -> Qubit& { - return permutation[qubit]; - } - - /// Inserts elements or nodes - auto insert(const std::pair& value) -> auto { - return permutation.insert(value); - } - template auto insert(InputIt first, InputIt last) -> void { - permutation.insert(first, last); - } - auto insert(const std::initializer_list> init) - -> void { - permutation.insert(init); - } - - /// Constructs element in-place - template auto emplace(Args&&... args) -> auto { - return permutation.emplace(std::forward(args)...); - } - - // NOLINTBEGIN(readability-identifier-naming) - - /// Inserts in-place if the key does not exist, does nothing otherwise - template - auto try_emplace(const Qubit key, Args&&... args) -> auto { - return permutation.try_emplace(key, std::forward(args)...); - } - - /// Inserts an element or assigns to the current element if the key already - /// exists - auto insert_or_assign(const Qubit key, const Qubit value) -> auto { - return permutation.insert_or_assign(key, value); - } - - // NOLINTEND(readability-identifier-naming) - - /// Erases elements - auto erase(const Qubit qubit) -> std::size_t { - return permutation.erase(qubit); - } - auto erase(const decltype(permutation)::const_iterator pos) - -> decltype(permutation)::iterator { - return permutation.erase(pos); - } - auto erase(const decltype(permutation)::const_iterator first, - const decltype(permutation)::const_iterator last) - -> decltype(permutation)::iterator { - return permutation.erase(first, last); - } - - /// Swaps the contents - void swap(Permutation& other) noexcept { - permutation.swap(other.permutation); - } - - /// Lexicographically compares the values in the map - [[nodiscard]] auto operator<(const Permutation& other) const -> bool { - return permutation < other.permutation; - } - [[nodiscard]] auto operator<=(const Permutation& other) const -> bool { - return permutation <= other.permutation; - } - [[nodiscard]] auto operator>(const Permutation& other) const -> bool { - return permutation > other.permutation; - } - [[nodiscard]] auto operator>=(const Permutation& other) const -> bool { - return permutation >= other.permutation; - } - [[nodiscard]] auto operator==(const Permutation& other) const -> bool { - return permutation == other.permutation; - } - [[nodiscard]] auto operator!=(const Permutation& other) const -> bool { - return permutation != other.permutation; - } -}; -} // namespace qc - -// define hash function for Permutation -template <> struct std::hash { - std::size_t operator()(const qc::Permutation& p) const noexcept { - std::size_t seed = 0; - for (const auto& [k, v] : p) { - qc::hashCombine(seed, k); - qc::hashCombine(seed, v); - } - return seed; - } -}; diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp deleted file mode 100644 index 3e677431a3..0000000000 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ /dev/null @@ -1,604 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file QuantumComputation.hpp - * @brief Quantum-computation intermediate representation. - */ - -#pragma once - -#include "Definitions.hpp" -#include "Permutation.hpp" -#include "Register.hpp" -#include "operations/CompoundOperation.hpp" -#include "operations/Control.hpp" -#include "operations/Expression.hpp" -#include "operations/IfElseOperation.hpp" -#include "operations/OpType.hpp" -#include "operations/Operation.hpp" -#include "operations/StandardOperation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -using QuantumRegisterMap = std::unordered_map; -using ClassicalRegisterMap = std::unordered_map; - -class QuantumComputation { -public: - using iterator = std::vector>::iterator; - using const_iterator = - std::vector>::const_iterator; - using reverse_iterator = - std::vector>::reverse_iterator; - using const_reverse_iterator = - std::vector>::const_reverse_iterator; - -protected: - std::vector> ops; - std::size_t nqubits = 0; - std::size_t nclassics = 0; - std::size_t nancillae = 0; - std::string name; - - QuantumRegisterMap quantumRegisters; - ClassicalRegisterMap classicalRegisters; - QuantumRegisterMap ancillaRegisters; - - std::vector ancillary; - std::vector garbage; - - fp globalPhase = 0.; - - std::unordered_set occurringVariables; - -public: - explicit QuantumComputation(std::size_t nq = 0, std::size_t nc = 0U); - QuantumComputation(QuantumComputation&& qc) noexcept = default; - QuantumComputation& operator=(QuantumComputation&& qc) noexcept = default; - QuantumComputation(const QuantumComputation& qc); - QuantumComputation& operator=(const QuantumComputation& qc); - ~QuantumComputation() = default; - - // physical qubits are used as keys, logical qubits as values - Permutation initialLayout; - Permutation outputPermutation; - - /** - * @brief Construct a QuantumComputation from CompoundOperation object - * @details The function creates a copy of each operation in the compound - * operation. It uses the largest qubit index in the CompoundOperation for - * determining the number of qubits. It adds a single quantum register with - * all qubits from 0 to the largest qubit index and a corresponding classical - * register with the same size. The initial layout as well as the output - * permutation are set to the identity permutation. - * @param op The CompoundOperation to convert to a quantum circuit - * @return The constructed QuantumComputation - */ - [[nodiscard]] static QuantumComputation - fromCompoundOperation(const CompoundOperation& op); - - [[nodiscard]] std::size_t getNops() const noexcept { return ops.size(); } - [[nodiscard]] std::size_t getNqubits() const noexcept { - return nqubits + nancillae; - } - [[nodiscard]] std::size_t getNancillae() const noexcept { return nancillae; } - [[nodiscard]] std::size_t getNqubitsWithoutAncillae() const noexcept { - return nqubits; - } - [[nodiscard]] const std::vector& getAncillary() const noexcept { - return ancillary; - } - [[nodiscard]] std::vector& getAncillary() noexcept { return ancillary; } - [[nodiscard]] const std::vector& getGarbage() const noexcept { - return garbage; - } - [[nodiscard]] std::vector& getGarbage() noexcept { return garbage; } - [[nodiscard]] std::size_t getNcbits() const noexcept { return nclassics; } - [[nodiscard]] std::string getName() const noexcept { return name; } - [[nodiscard]] const auto& getQuantumRegisters() const noexcept { - return quantumRegisters; - } - [[nodiscard]] const auto& getClassicalRegisters() const noexcept { - return classicalRegisters; - } - [[nodiscard]] const auto& getAncillaRegisters() const noexcept { - return ancillaRegisters; - } - [[nodiscard]] fp getGlobalPhase() const noexcept { return globalPhase; } - [[nodiscard]] bool hasGlobalPhase() const noexcept { - return std::abs(getGlobalPhase()) > 0; - } - - [[nodiscard]] const std::unordered_set& - getVariables() const noexcept { - return occurringVariables; - } - - /// Returns the number of logical output qubits that are not marked garbage. - [[nodiscard]] std::size_t getNoutputQubits() const noexcept; - [[nodiscard]] std::size_t getNgarbageQubits() const; - - void setName(const std::string& n) noexcept { name = n; } - - [[nodiscard]] std::size_t getNindividualOps() const; - [[nodiscard]] std::size_t getNsingleQubitOps() const; - [[nodiscard]] std::size_t getDepth() const; - - /// Returns the highest qubit index used as a key in the initial layout - [[nodiscard]] Qubit getHighestPhysicalQubitIndex() const; - [[nodiscard]] bool isIdleQubit(Qubit physicalQubit) const; - [[nodiscard]] bool - logicalQubitIsAncillary(const Qubit logicalQubitIndex) const { - return ancillary[logicalQubitIndex]; - } - /** - * @brief Sets the given logical qubit to be ancillary - * @details Removes the qubit from the qubit register and adds it to the - * ancillary register, if such a register exists. Otherwise a new ancillary - * register is created. - * @param logicalQubitIndex - */ - void setLogicalQubitAncillary(Qubit logicalQubitIndex); - /** - * @brief Sets all logical qubits in the range [minLogicalQubitIndex, - * maxLogicalQubitIndex] to be ancillary - * @details Removes the qubits from the qubit register and adds it to the - * ancillary register, if such a register exists. Otherwise a new ancillary - * register is created. - * @param minLogicalQubitIndex first qubit that is set to be ancillary - * @param maxLogicalQubitIndex last qubit that is set to be ancillary - */ - void setLogicalQubitsAncillary(Qubit minLogicalQubitIndex, - Qubit maxLogicalQubitIndex); - [[nodiscard]] bool - logicalQubitIsGarbage(const Qubit logicalQubitIndex) const { - return garbage[logicalQubitIndex]; - } - void setLogicalQubitGarbage(Qubit logicalQubitIndex); - /** - * @brief Sets all logical qubits in the range [minLogicalQubitIndex, - * maxLogicalQubitIndex] to be garbage - * @param minLogicalQubitIndex first qubit that is set to be garbage - * @param maxLogicalQubitIndex last qubit that is set to be garbage - */ - void setLogicalQubitsGarbage(Qubit minLogicalQubitIndex, - Qubit maxLogicalQubitIndex); - - /// checks whether the given logical qubit exists in the initial layout. - /// \param logicalQubitIndex the logical qubit index to check - /// \return whether the given logical qubit exists in the initial layout and - /// to which physical qubit it is mapped - [[nodiscard]] std::pair> - containsLogicalQubit(Qubit logicalQubitIndex) const; - - /// Adds a global phase to the quantum circuit. - /// \param angle the angle to add - void gphase(fp angle); - -#define DECLARE_SINGLE_TARGET_OPERATION(op) \ - void op(Qubit target); \ - void c##op(const Control& control, Qubit target); \ - void mc##op(const Controls& controls, const Qubit target); - - DECLARE_SINGLE_TARGET_OPERATION(i) - DECLARE_SINGLE_TARGET_OPERATION(x) - DECLARE_SINGLE_TARGET_OPERATION(y) - DECLARE_SINGLE_TARGET_OPERATION(z) - DECLARE_SINGLE_TARGET_OPERATION(h) - DECLARE_SINGLE_TARGET_OPERATION(s) - DECLARE_SINGLE_TARGET_OPERATION(sdg) - DECLARE_SINGLE_TARGET_OPERATION(t) - DECLARE_SINGLE_TARGET_OPERATION(tdg) - DECLARE_SINGLE_TARGET_OPERATION(v) - DECLARE_SINGLE_TARGET_OPERATION(vdg) - DECLARE_SINGLE_TARGET_OPERATION(sx) - DECLARE_SINGLE_TARGET_OPERATION(sxdg) - -#undef DECLARE_SINGLE_TARGET_OPERATION - -#define DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void op(const SymbolOrNumber&(param), Qubit target); \ - void c##op(const SymbolOrNumber&(param), const Control& control, \ - Qubit target); \ - void mc##op(const SymbolOrNumber&(param), const Controls& controls, \ - Qubit target); - - DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rx, theta) - DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(ry, theta) - DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rz, theta) - DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) - -#undef DECLARE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION - -#define DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ - void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - Qubit target); \ - void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Control& control, const Qubit target); \ - void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, const Qubit target); - - DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) - DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(r, theta, phi) - -#undef DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION - -#define DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ - param2) \ - void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), Qubit target); \ - void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), const Control& control, \ - Qubit target); \ - void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), const Controls& controls, \ - Qubit target); - - DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(u, theta, phi, lambda) - -#undef DECLARE_SINGLE_TARGET_THREE_PARAMETER_OPERATION - -#define DECLARE_TWO_TARGET_OPERATION(op) \ - void op(const Qubit target0, const Qubit target1); \ - void c##op(const Control& control, Qubit target0, Qubit target1); \ - void mc##op(const Controls& controls, Qubit target0, Qubit target1); - - DECLARE_TWO_TARGET_OPERATION(swap) // NOLINT: bugprone-exception-escape - DECLARE_TWO_TARGET_OPERATION(dcx) - DECLARE_TWO_TARGET_OPERATION(ecr) - DECLARE_TWO_TARGET_OPERATION(iswap) - DECLARE_TWO_TARGET_OPERATION(iswapdg) - DECLARE_TWO_TARGET_OPERATION(peres) - DECLARE_TWO_TARGET_OPERATION(peresdg) - DECLARE_TWO_TARGET_OPERATION(move) - -#undef DECLARE_TWO_TARGET_OPERATION - -#define DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void op(const SymbolOrNumber&(param), Qubit target0, Qubit target1); \ - void c##op(const SymbolOrNumber&(param), const Control& control, \ - Qubit target0, Qubit target1); \ - void mc##op(const SymbolOrNumber&(param), const Controls& controls, \ - Qubit target0, Qubit target1); - - DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rxx, theta) - DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(ryy, theta) - DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzz, theta) - DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzx, theta) - -#undef DECLARE_TWO_TARGET_SINGLE_PARAMETER_OPERATION - -#define DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ - void op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - Qubit target0, Qubit target1); \ - void c##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Control& control, Qubit target0, Qubit target1); \ - void mc##op(const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, Qubit target0, Qubit target1); - - // NOLINTNEXTLINE(readability-identifier-naming) - DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_minus_yy, theta, beta) - // NOLINTNEXTLINE(readability-identifier-naming) - DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) - -#undef DECLARE_TWO_TARGET_TWO_PARAMETER_OPERATION - -#define DECLARE_THREE_TARGET_OPERATION(op) \ - void op(const Qubit target0, const Qubit target1, const Qubit target2); \ - void c##op(const Control& control, Qubit target0, Qubit target1, \ - Qubit target2); \ - void mc##op(const Controls& controls, Qubit target0, Qubit target1, \ - Qubit target2); - - DECLARE_THREE_TARGET_OPERATION(rccx) - -#undef DECLARE_THREE_TARGET_OPERATION - - void measure(Qubit qubit, std::size_t bit); - void measure(const Targets& qubits, const std::vector& bits); - - /** - * @brief Add measurements to all qubits - * @param addBits Whether to add new classical bits to the circuit - * @param addBarrier Whether to add a barrier before the measurements - * @details This function adds measurements to all qubits in the circuit and - * appends a new classical register (named "meas") to the circuit if addBits - * is true. Otherwise, qubit q is measured into classical bit q. - */ - void measureAll(bool addBits = true, bool addBarrier = true); - - void reset(Qubit target); - void reset(const Targets& targets); - - void barrier(); - void barrier(Qubit target); - void barrier(const Targets& targets); - - void ifElse(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const ClassicalRegister& controlRegister, - std::uint64_t expectedValue = 1U, ComparisonKind cmp = Eq); - void ifElse(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, Bit controlBit, - bool expectedValue = true, ComparisonKind cmp = Eq); - - void if_(OpType op, Qubit target, const ClassicalRegister& controlRegister, - std::uint64_t expectedValue = 1U, ComparisonKind cmp = Eq, - const std::vector& params = {}); - void if_(OpType op, Qubit target, Control control, - const ClassicalRegister& controlRegister, - std::uint64_t expectedValue = 1U, ComparisonKind cmp = Eq, - const std::vector& params = {}); - void if_(OpType op, Qubit target, const Controls& controls, - const ClassicalRegister& controlRegister, - std::uint64_t expectedValue = 1U, ComparisonKind cmp = Eq, - const std::vector& params = {}); - void if_(OpType op, Qubit target, Bit controlBit, bool expectedValue = true, - ComparisonKind cmp = Eq, const std::vector& params = {}); - void if_(OpType op, Qubit target, Control control, Bit controlBit, - bool expectedValue = true, ComparisonKind cmp = Eq, - const std::vector& params = {}); - void if_(OpType op, Qubit target, const Controls& controls, Bit controlBit, - bool expectedValue = true, ComparisonKind cmp = Eq, - const std::vector& params = {}); - - /// strip away qubits with no operations applied to them and which do not pop - /// up in the output permutation \param force if true, also strip away idle - /// qubits occurring in the output permutation - void stripIdleQubits(bool force = false); - - /** - * @brief Initializes the input layout and output permutation. - * @details If the output permutation is not empty, it must contain every - * measured device qubit as a key. Clear the output permutation before this - * call to infer it only from measurements. - * @throws std::invalid_argument If a measured device qubit is missing from a - * non-empty output permutation. - */ - void initializeIOMapping(); - - // this function augments a given circuit by additional registers - const QuantumRegister& addQubitRegister(std::size_t nq, - const std::string& regName = "q"); - const ClassicalRegister& - addClassicalRegister(std::size_t nc, const std::string& regName = "c"); - const QuantumRegister& - addAncillaryRegister(std::size_t nq, const std::string& regName = "anc"); - // a function to combine all quantum registers (qregs and ancregs) into a - // single register (useful for circuits mapped to a device) - const QuantumRegister& - unifyQuantumRegisters(const std::string& regName = "q"); - - /** - * @brief Removes a logical qubit - * @param logicalQubitIndex The qubit to remove - * @return The physical qubit index that the logical qubit was mapped to in - * the initial layout and the output qubit index that the logical qubit was - * mapped to in the output permutation. - */ - std::pair> removeQubit(Qubit logicalQubitIndex); - - // adds physical qubit as ancillary qubit and gives it the appropriate output - // mapping - void addAncillaryQubit(Qubit physicalQubitIndex, - std::optional outputQubitIndex); - // try to add logical qubit to circuit and assign it to physical qubit with - // certain output permutation value - void addQubit(Qubit logicalQubitIndex, Qubit physicalQubitIndex, - std::optional outputQubitIndex); - - [[nodiscard]] QuantumComputation - instantiate(const VariableAssignment& assignment) const; - void instantiateInplace(const VariableAssignment& assignment); - - void addVariable(const SymbolOrNumber& expr); - - template void addVariables(const Vars&... vars) { - (addVariable(vars), ...); - } - - [[nodiscard]] bool isVariableFree() const; - - /** - * @brief Invert the circuit - * @details Inverts the circuit by inverting all operations and reversing the - * order of the operations. Additionally, the initial layout and output - * permutation are swapped. If the circuit has different initial - * layout and output permutation sizes, the initial layout and output - * permutation will not be swapped. - */ - void invert(); - - [[nodiscard]] bool operator==(const QuantumComputation& rhs) const; - [[nodiscard]] bool operator!=(const QuantumComputation& rhs) const { - return !(*this == rhs); - } - - /** - * printing - */ - std::ostream& print(std::ostream& os) const; - - friend std::ostream& operator<<(std::ostream& os, - const QuantumComputation& qc) { - return qc.print(os); - } - - void dump(const std::string& filename, - Format format = Format::OpenQASM3) const; - - /** - * @brief Dumps the circuit in OpenQASM format to the given output stream - * @param of The output stream to write the OpenQASM representation to - * @param openQasm3 Whether to use OpenQASM 3.0 or 2.0 - */ - void dumpOpenQASM(std::ostream& of, bool openQasm3 = true) const; - - /** - * @brief Returns the OpenQASM representation of the circuit - * @param qasm3 Whether to use OpenQASM 3.0 or 2.0 - * @return The OpenQASM representation of the circuit - */ - [[nodiscard]] std::string toQASM(bool qasm3 = true) const; - - // this convenience method allows to turn a circuit into a compound operation. - std::unique_ptr asCompoundOperation() { - return std::make_unique(std::move(ops)); - } - - // this convenience method allows to turn a circuit into an operation. - std::unique_ptr asOperation(); - - void reset(); - - /// Flattens compound operations. - /// - /// \param customGatesOnly Whether to flatten only custom gates. - void flattenOperations(bool customGatesOnly = false); - - /// Removes measurements and barriers from the end of the circuit. - void removeFinalMeasurements(); - - /** - * @brief Reorders the operations in the quantum computation to establish a - * canonical order - * @details Uses iterative breadth-first search starting from the topmost - * qubit. - */ - void reorderOperations(); - - /** - * @brief Check whether the quantum computation contains dynamic circuit - * primitives - * @details Dynamic circuit primitives are mid-circuit measurements, resets, - * or classical control flow operations. This method traverses the whole - * circuit once until it finds a dynamic operation. - * @return Whether the quantum computation contains dynamic circuit primitives - */ - [[nodiscard]] bool isDynamic() const; - -protected: - [[nodiscard]] std::size_t getSmallestAncillary() const { - for (std::size_t i = 0; i < ancillary.size(); ++i) { - if (ancillary[i]) { - return i; - } - } - return ancillary.size(); - } - - [[nodiscard]] std::size_t getSmallestGarbage() const { - for (std::size_t i = 0; i < garbage.size(); ++i) { - if (garbage[i]) { - return i; - } - } - return garbage.size(); - } - -private: - [[nodiscard]] QuantumRegister& getQubitRegister(Qubit physicalQubitIndex); - [[nodiscard]] Qubit getPhysicalQubitIndex(Qubit logicalQubitIndex) const; - [[nodiscard]] bool physicalQubitIsAncillary(Qubit physicalQubitIndex) const; - -protected: - void checkQubitRange(Qubit qubit) const; - void checkQubitRange(Qubit qubit, const Controls& controls) const; - void checkQubitRange(Qubit qubit0, Qubit qubit1, - const Controls& controls) const; - void checkQubitRange(Qubit qubit0, Qubit qubit1, Qubit qubit2, - const Controls& controls) const; - void checkQubitRange(const std::vector& qubits) const; - void checkBitRange(Bit bit) const; - void checkBitRange(const std::vector& bits) const; - void checkClassicalRegister(const ClassicalRegister& creg) const; - - /** - * Pass-Through - */ -public: - // Iterators (pass-through) - auto begin() noexcept { return ops.begin(); } - [[nodiscard]] auto begin() const noexcept { return ops.begin(); } - [[nodiscard]] auto cbegin() const noexcept { return ops.cbegin(); } - auto end() noexcept { return ops.end(); } - [[nodiscard]] auto end() const noexcept { return ops.end(); } - [[nodiscard]] auto cend() const noexcept { return ops.cend(); } - auto rbegin() noexcept { return ops.rbegin(); } - [[nodiscard]] auto rbegin() const noexcept { return ops.rbegin(); } - [[nodiscard]] auto crbegin() const noexcept { return ops.crbegin(); } - auto rend() noexcept { return ops.rend(); } - [[nodiscard]] auto rend() const noexcept { return ops.rend(); } - [[nodiscard]] auto crend() const noexcept { return ops.crend(); } - - // Capacity (pass-through) - [[nodiscard]] bool empty() const noexcept { return ops.empty(); } - [[nodiscard]] std::size_t size() const noexcept { return ops.size(); } - // NOLINTNEXTLINE(readability-identifier-naming) - [[nodiscard]] std::size_t max_size() const noexcept { return ops.max_size(); } - [[nodiscard]] std::size_t capacity() const noexcept { return ops.capacity(); } - - void reserve(const std::size_t newCap) { ops.reserve(newCap); } - // NOLINTNEXTLINE(readability-identifier-naming) - void shrink_to_fit() { ops.shrink_to_fit(); } - - // Modifiers (pass-through) - void clear() noexcept { ops.clear(); } - // NOLINTNEXTLINE(readability-identifier-naming) - void pop_back() { ops.pop_back(); } - void resize(const std::size_t count) { ops.resize(count); } - iterator erase(const const_iterator pos) { return ops.erase(pos); } - iterator erase(const const_iterator first, const const_iterator last) { - return ops.erase(first, last); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void push_back(const T& op) { - ops.push_back(std::make_unique(op)); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(Args&&... args) { - ops.emplace_back(std::make_unique(std::forward(args)...)); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(std::unique_ptr& op) { - ops.emplace_back(std::move(op)); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(std::unique_ptr&& op) { - ops.emplace_back(std::move(op)); - } - - template iterator insert(const_iterator pos, T&& op) { - return ops.insert(pos, std::forward(op)); - } - - [[nodiscard]] const auto& at(const std::size_t i) const { return ops.at(i); } - [[nodiscard]] auto& at(const std::size_t i) { return ops.at(i); } - [[nodiscard]] const auto& front() const { return ops.front(); } - [[nodiscard]] const auto& back() const { return ops.back(); } - - // reverse - void reverse(); -}; -} // namespace qc diff --git a/include/mqt-core/ir/Register.hpp b/include/mqt-core/ir/Register.hpp deleted file mode 100644 index 3cea4c2d72..0000000000 --- a/include/mqt-core/ir/Register.hpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Register.hpp - * @brief Quantum and classical register representations. - */ - -#pragma once - -#include "Definitions.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { - -template class Register { -public: - Register(const BitType regStartIndex, const std::size_t regSize, - std::string regName) - : startIndex(regStartIndex), size(regSize), name(std::move(regName)) {} - virtual ~Register() = default; - - [[nodiscard]] const std::string& getName() const noexcept { return name; } - [[nodiscard]] std::size_t getSize() const noexcept { return size; } - [[nodiscard]] std::size_t& getSize() noexcept { return size; } - [[nodiscard]] BitType getStartIndex() const noexcept { return startIndex; } - [[nodiscard]] BitType& getStartIndex() noexcept { return startIndex; } - [[nodiscard]] BitType getEndIndex() const noexcept { - return static_cast(startIndex + size - 1); - } - - [[nodiscard]] bool operator==(const Register& other) const { - return name == other.name && size == other.size; - } - [[nodiscard]] bool operator!=(const Register& other) const { - return !(*this == other); - } - - [[nodiscard]] bool contains(const BitType index) const { - return startIndex <= index && index < startIndex + size; - } - - [[nodiscard]] BitType getLocalIndex(const BitType globalIndex) const { - if (!contains(globalIndex)) { - throw std::out_of_range("Index out of range"); - } - return globalIndex - startIndex; - } - - [[nodiscard]] BitType getGlobalIndex(const BitType localIndex) const { - if (localIndex >= size) { - throw std::out_of_range("Index out of range"); - } - return startIndex + localIndex; - } - - [[nodiscard]] std::string toString(const BitType globalIndex) const { - return name + "[" + std::to_string(getLocalIndex(globalIndex)) + "]"; - } - - [[nodiscard]] BitType operator[](const BitType localIndex) const { - return getGlobalIndex(localIndex); - } - -private: - BitType startIndex; - std::size_t size; - std::string name; -}; - -class QuantumRegister final : public Register { -public: - QuantumRegister(const Qubit regStartIndex, const std::size_t regSize, - const std::string& regName = "") - : Register(regStartIndex, regSize, - regName.empty() ? generateName() : regName) {} - -protected: - static std::string generateName() { - static std::atomic_size_t counter = 0; - return "q" + - std::to_string(counter.fetch_add(1, std::memory_order_relaxed)); - } -}; - -class ClassicalRegister final : public Register { -public: - ClassicalRegister(const Bit regStartIndex, const std::size_t regSize, - const std::string& regName = "") - : Register(regStartIndex, regSize, - regName.empty() ? generateName() : regName) {} - -protected: - static std::string generateName() { - static std::atomic_size_t counter = 0; - return "c" + - std::to_string(counter.fetch_add(1, std::memory_order_relaxed)); - } -}; - -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(const qc::QuantumRegister& reg) const noexcept { - return qc::combineHash(reg.getStartIndex(), reg.getSize()); - } -}; - -template <> struct std::hash { - std::size_t operator()(const qc::ClassicalRegister& reg) const noexcept { - return qc::combineHash(reg.getStartIndex(), reg.getSize()); - } -}; diff --git a/include/mqt-core/ir/operations/CompoundOperation.hpp b/include/mqt-core/ir/operations/CompoundOperation.hpp deleted file mode 100644 index 74915092b3..0000000000 --- a/include/mqt-core/ir/operations/CompoundOperation.hpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file CompoundOperation.hpp - * @brief Composite quantum operations. - */ - -#pragma once - -#include "Control.hpp" -#include "Operation.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { - -class CompoundOperation final : public Operation { -public: - using iterator = std::vector>::iterator; - using const_iterator = - std::vector>::const_iterator; - -private: - std::vector> ops; - bool customGate; - -public: - explicit CompoundOperation(bool isCustom = false); - - explicit CompoundOperation( - std::vector>&& operations, - bool isCustom = false); - - CompoundOperation(const CompoundOperation& co); - - CompoundOperation& operator=(const CompoundOperation& co); - - [[nodiscard]] std::unique_ptr clone() const override; - - [[nodiscard]] size_t getNqubits() const override; - - [[nodiscard]] bool isCompoundOperation() const noexcept override; - - [[nodiscard]] bool isNonUnitaryOperation() const override; - - [[nodiscard]] bool isSymbolicOperation() const override; - - [[nodiscard]] bool isCustomGate() const noexcept; - - [[nodiscard]] bool isGlobal(size_t nQubits) const noexcept override; - - [[nodiscard]] bool isClifford() const override; - - void addControl(Control c) override; - - void clearControls() override; - - void removeControl(Control c) override; - - Controls::iterator removeControl(Controls::iterator it) override; - - [[nodiscard]] bool equals(const Operation& operation) const override; - - std::ostream& print(std::ostream& os, const Permutation& permutation, - std::size_t prefixWidth, - std::size_t nqubits) const override; - - [[nodiscard]] bool actsOn(Qubit i) const override; - - void addDepthContribution(std::vector& depths) const override; - - [[nodiscard]] const std::vector>& - getOps() const noexcept { - return ops; - } - [[nodiscard]] std::vector>& getOps() noexcept { - return ops; - } - - [[nodiscard]] auto getUsedQubits() const -> std::set override; - - [[nodiscard]] auto commutesAtQubit(const Operation& other, - const Qubit& qubit) const -> bool override; - - /** - * This refines the inherited method because the inherited method leads to - * false negatives - */ - [[nodiscard]] auto isInverseOf(const Operation& other) const -> bool override; - - void invert() override; - - void apply(const Permutation& permutation) override; - - /** - * @brief Merge another compound operation into this one. - * @details This transfers ownership of the operations from the other compound - * operation to this one. The other compound operation will be empty after - * this operation. - * @param op the compound operation to merge into this one - */ - void merge(CompoundOperation& op); - - /** - * @brief Check whether this operation can be collapsed into a single - * operation. - * @return true if this operation can be collapsed into a single operation, - * false otherwise - */ - [[nodiscard]] bool isConvertibleToSingleOperation() const; - - /** - * @brief Collapse this operation into a single operation. - * @details This operation must be convertible to a single operation. - * @return the collapsed operation - */ - [[nodiscard]] std::unique_ptr collapseToSingleOperation(); - - /** - * Pass-Through - */ - - // Iterators (pass-through) - auto begin() noexcept { return ops.begin(); } - [[nodiscard]] auto begin() const noexcept { return ops.begin(); } - [[nodiscard]] auto cbegin() const noexcept { return ops.cbegin(); } - auto end() noexcept { return ops.end(); } - [[nodiscard]] auto end() const noexcept { return ops.end(); } - [[nodiscard]] auto cend() const noexcept { return ops.cend(); } - auto rbegin() noexcept { return ops.rbegin(); } - [[nodiscard]] auto rbegin() const noexcept { return ops.rbegin(); } - [[nodiscard]] auto crbegin() const noexcept { return ops.crbegin(); } - auto rend() noexcept { return ops.rend(); } - [[nodiscard]] auto rend() const noexcept { return ops.rend(); } - [[nodiscard]] auto crend() const noexcept { return ops.crend(); } - - // Capacity (pass-through) - [[nodiscard]] bool empty() const noexcept { return ops.empty(); } - [[nodiscard]] std::size_t size() const noexcept { return ops.size(); } - // NOLINTNEXTLINE(readability-identifier-naming) - [[nodiscard]] std::size_t max_size() const noexcept { return ops.max_size(); } - [[nodiscard]] std::size_t capacity() const noexcept { return ops.capacity(); } - - void reserve(const std::size_t newCap) { ops.reserve(newCap); } - // NOLINTNEXTLINE(readability-identifier-naming) - void shrink_to_fit() { ops.shrink_to_fit(); } - - // Modifiers (pass-through) - void clear() noexcept { ops.clear(); } - // NOLINTNEXTLINE(readability-identifier-naming) - void pop_back() { ops.pop_back(); } - void resize(const std::size_t count) { ops.resize(count); } - iterator erase(const const_iterator pos) { return ops.erase(pos); } - iterator erase(const const_iterator first, const const_iterator last) { - return ops.erase(first, last); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(Args&&... args) { - ops.emplace_back(std::make_unique(std::forward(args)...)); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(std::unique_ptr& op) { - ops.emplace_back(std::move(op)); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - template void emplace_back(std::unique_ptr&& op) { - ops.emplace_back(std::move(op)); - } - - template - iterator insert(const_iterator iter, Args&&... args) { - return ops.insert(iter, std::make_unique(std::forward(args)...)); - } - template - iterator insert(const_iterator iter, std::unique_ptr& op) { - return ops.insert(iter, std::move(op)); - } - template iterator insert(const_iterator iter, T&& op) { - return ops.insert(iter, std::forward(op)); - } - - // Element access (pass-through) - [[nodiscard]] const auto& at(const std::size_t i) const { return ops.at(i); } - [[nodiscard]] auto& operator[](const std::size_t i) { return ops[i]; } - [[nodiscard]] const auto& operator[](const std::size_t i) const { - return ops[i]; - } - [[nodiscard]] auto& front() { return ops.front(); } - [[nodiscard]] const auto& front() const { return ops.front(); } - [[nodiscard]] auto& back() { return ops.back(); } - [[nodiscard]] const auto& back() const { return ops.back(); } -}; -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(const qc::CompoundOperation& co) const noexcept; -}; // namespace std diff --git a/include/mqt-core/ir/operations/Control.hpp b/include/mqt-core/ir/operations/Control.hpp deleted file mode 100644 index ddde88f0ba..0000000000 --- a/include/mqt-core/ir/operations/Control.hpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file - * @brief Defines a type for control qubits and some related functionality. - */ - -#pragma once - -#include "ir/Definitions.hpp" - -#include -#include -#include -#include -#include - -namespace qc { -/// Represents a control qubit as a qubit index and a control type. -struct Control { - /// The polarity of the control. - enum class Type : bool { - /// Positive controls trigger on \f$\ket{1}\f$. - Pos = true, - /// Negative controls trigger on \f$\ket{0}\f$. - Neg = false - }; - - /// The qubit that acts as a control. - Qubit qubit{}; - /// The type of the control. - Type type = Type::Pos; - - /// Get a string representation of the control. - [[nodiscard]] std::string toString() const { - std::ostringstream oss{}; - oss << "Control(qubit=" << qubit << ", type_=\""; - if (type == Type::Pos) { - oss << "Pos"; - } else { - oss << "Neg"; - } - oss << "\")"; - return oss.str(); - } - - // Explicitly allow implicit conversion from `Qubit` to `Control` - // NOLINTBEGIN(google-explicit-constructor) - /** - * @brief Construct a control qubit. - * @param q The qubit that acts as a control. - * @param t The type of the control. Defaults to `Type::Pos`. - * @note This constructor is not `explicit` to allow implicit conversion from - * `Qubit` to `Control`. - */ - Control(const Qubit q = {}, const Type t = Type::Pos) : qubit(q), type(t) {} - // NOLINTEND(google-explicit-constructor) -}; - -/// Defines the order of controls based on their qubit index and type. -inline bool operator<(const Control& lhs, const Control& rhs) { - return lhs.qubit < rhs.qubit || - (lhs.qubit == rhs.qubit && lhs.type < rhs.type); -} - -/// operator== overload for `Control` -inline bool operator==(const Control& lhs, const Control& rhs) { - return lhs.qubit == rhs.qubit && lhs.type == rhs.type; -} - -/// operator!= overload for `Control` -inline bool operator!=(const Control& lhs, const Control& rhs) { - return !(lhs == rhs); -} - -/// Allows a set of @ref Control to be indexed by a `Qubit` -struct CompareControl { - using is_transparent [[maybe_unused]] = void; - - bool operator()(const Control& lhs, const Control& rhs) const { - return lhs < rhs; - } - - bool operator()(const Qubit lhs, const Control& rhs) const { - return lhs < rhs.qubit; - } - - bool operator()(const Control& lhs, const Qubit rhs) const { - return lhs.qubit < rhs; - } -}; - -/// Type alias for a set of control qubits. -using Controls = std::set; - -/** - * @brief Inline namespace for control literals. - * @details Use `using namespace qc::literals` to enable the literals. - */ -inline namespace literals { -// User-defined literals require unsigned long long int -// NOLINTBEGIN(google-runtime-int) - -/** - * @brief User-defined literal for positive control qubits. - * @details This literal allows to create a positive control qubit from an - * unsigned integer, for example as `0_pc`. - * @param q Index of the qubit that acts as a control. - * @return A positive control qubit. - */ -inline Control operator""_pc(const unsigned long long int q) { - return {static_cast(q)}; -} - -/** - * @brief User-defined literal for negative control qubits. - * @details This literal allows to create a negative control qubit from an - * unsigned integer, for example as `0_nc`. - * @param q Index of the qubit that acts as a control. - * @return A negative control qubit. - */ -inline Control operator""_nc(const unsigned long long int q) { - return {static_cast(q), Control::Type::Neg}; -} -// NOLINTEND(google-runtime-int) -} // namespace literals -} // namespace qc - -/// Hash function for `Control` -template <> struct std::hash { - std::size_t operator()(const qc::Control& c) const noexcept { - return std::hash{}(c.qubit) ^ - std::hash{}(c.type); - } -}; diff --git a/include/mqt-core/ir/operations/Expression.hpp b/include/mqt-core/ir/operations/Expression.hpp deleted file mode 100644 index dee8116216..0000000000 --- a/include/mqt-core/ir/operations/Expression.hpp +++ /dev/null @@ -1,857 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Expression.hpp - * @brief Symbolic expressions, variables, and terms for quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sym { -static constexpr double TOLERANCE = 1e-9; - -class SymbolicException final : public std::invalid_argument { - std::string msg; - -public: - explicit SymbolicException(std::string m) - : std::invalid_argument("Symbolic Exception"), msg(std::move(m)) {} - - [[nodiscard]] const char* what() const noexcept override { - return msg.c_str(); - } -}; - -/** - * @brief Struct representing a symbolic variable. - * @details Variable names are assigned ids during creation. These ids are - * statically stored in a map. The name of a variable can be retrieved by its - * id. Variables are lexicographically ordered by their ids. - */ -struct Variable { - // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) - static inline std::unordered_map registered{}; - static inline std::unordered_map names{}; - static inline std::size_t nextId{}; - static inline std::mutex registryMutex{}; - // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - - /** - * @brief Construct variable with given name. - * @param name Name of the variable. - */ - explicit Variable(const std::string& name); - - /** - * @brief Get the name of the variable. - * @return Name of the variable. - */ - [[nodiscard]] std::string getName() const; - - /** - * @brief Check whether this variable's id is equal to another variable's id. - * @param rhs Variable to compare with. - * @return True if the variables are equal, false otherwise. - */ - bool operator==(const Variable& rhs) const { return id == rhs.id; } - - /** - * @brief Check whether this variable's id is not equal to another variable's - * id. - * @param rhs Variable to compare with. - * @return True if the variables are not equal, false otherwise. - */ - bool operator!=(const Variable& rhs) const { return !((*this) == rhs); } - - /** - * @brief Check whether this variable's id is less than another variable's id - * with respect to the default lexicographic ordering. - * @param rhs Variable to compare with. - * @return True if this variable's id is less than the other variable's id, - * false otherwise. - */ - bool operator<(const Variable& rhs) const { return id < rhs.id; } - - /** - * @brief Check whether this variable's id is greater than another variable's - * id with respect to the default lexicographic ordering. - * @param rhs Variable to compare with. - * @return True if this variable's id is greater than the other variable's id, - * false otherwise. - */ - bool operator>(const Variable& rhs) const { return id > rhs.id; } - -private: - std::size_t id{}; -}; -} // namespace sym - -/** - * @brief Hash function for the Variable struct. - */ -template <> struct std::hash { - std::size_t operator()(const sym::Variable& var) const { - return std::hash()(var.getName()); - } -}; - -namespace sym { - -/** - * @brief Type alias for a variable assignment. Maps variables to their assigned - * values. - */ -using VariableAssignment = std::unordered_map; - -/** - * @brief Struct representing a symbolic term. A term is a variable multiplied - * by a coefficient. - * @tparam T Type of the coefficient. Must be constructible from an integer and - * a double. - */ -template && - std::is_constructible_v>> -class Term { -public: - /** - * @brief Get the variable of the term. - * @return Variable of the term. - */ - [[nodiscard]] Variable getVar() const noexcept { return var; } - - /** - * @brief Get the coefficient of the term. - * @return Coefficient of the term. - */ - [[nodiscard]] T getCoeff() const noexcept { return coeff; } - - /** - * @brief Check whether the term has a zero coefficient. - * @return True if the coefficient is zero, false otherwise. - */ - [[nodiscard]] bool hasZeroCoeff() const { - return std::abs(static_cast(coeff)) < TOLERANCE; - } - - /** - * @brief Construct a term with a given variable and coefficient. - * @param v Variable of the term. - * @param coef Coefficient of the term. - */ - explicit Term(const Variable v, T coef = 1.) : coeff(coef), var(v) {}; - - /** - * @brief Get the negative of the term. - * @return Negative of the term. - */ - Term operator-() const { return Term(var, -coeff); } - - /** - * @brief Add to the coefficient of the term. - * @param rhs Value to add to the coefficient. - */ - void addCoeff(const T& rhs) { coeff += rhs; } - - /** - * @brief Multiply the term by a scalar (add coefficients). - * @param rhs Scalar to multiply the term by. - * @return Reference to the term. - */ - Term& operator*=(const T& rhs) { - coeff *= rhs; - return *this; - } - - /** - * @brief Divide the term by a scalar (divide coefficients). - * @param rhs Scalar to divide the term by. - * @return Reference to the term. - */ - Term& operator/=(const T& rhs) { - coeff /= rhs; - return *this; - } - - /** - * @brief Multiply the term by a scalar (add coefficients). - * @param rhs Scalar to multiply the term by. - * @return Reference to the term. - */ - Term& operator/=(const std::int64_t rhs) { - coeff /= static_cast(rhs); - return *this; - } - - /** - * @brief Check whether the Term's variable is assigned in a given assignment. - * @param assignment Assignment to check. - * @return True if the variable is assigned, false otherwise. - */ - [[nodiscard]] bool - totalAssignment(const VariableAssignment& assignment) const { - return assignment.contains(getVar()); - } - - /** - * @brief Evaluate the term with respect to a given assignment. - * @param assignment Assignment to evaluate the term with. - * @return Result of the evaluation. - */ - [[nodiscard]] double evaluate(const VariableAssignment& assignment) const { - if (!totalAssignment(assignment)) { - throw SymbolicException("Cannot instantiate variable " + - getVar().getName() + ". No value given."); - } - return assignment.at(getVar()) * getCoeff(); - } - -private: - T coeff; - Variable var; -}; - -/** @name Term operators - * @brief Overloaded operators for the Term struct. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return Term obtained by applying the operation. - */ -///@{ -template >> -Term operator*(Term lhs, const double rhs) { - lhs *= rhs; - return lhs; -} -template >> -Term operator/(Term lhs, const double rhs) { - lhs /= rhs; - return lhs; -} -template >> -Term operator*(double lhs, const Term& rhs) { - return rhs * lhs; -} -///@} - -/** - * @brief Check whether two terms are equal. - * @details Two terms are equal if their variables are equal and their - * coefficients are equal within a small tolerance. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return True if the terms are equal, false otherwise. - */ -template bool operator==(const Term& lhs, const Term& rhs) { - return lhs.getVar() == rhs.getVar() && - std::abs(lhs.getCoeff() - rhs.getCoeff()) < TOLERANCE; -} - -/** - * @brief Check whether two terms are not equal. - * @details Two terms are not equal if their variables are not equal or their - * coefficients are not equal within a small tolerance. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return True if the terms are not equal, false otherwise. - */ -template bool operator!=(const Term& lhs, const Term& rhs) { - return !(lhs == rhs); -} -} // namespace sym - -/** - * @brief Hash function for the Term struct. - */ -template struct std::hash> { - std::size_t operator()(const sym::Term& term) const noexcept { - const auto h1 = std::hash{}(term.getVar()); - const auto h2 = std::hash{}(term.getCoeff()); - return qc::combineHash(h1, h2); - } -}; - -namespace sym { - -/** - * @brief Class representing a symbolic expression. An expression is a sum of - * terms and a constant. - * @tparam T Type of the coefficients of the terms. Must be constructible from - * an integer and a double. - * @tparam U Type of the constant. Must be constructible from a double. - */ -template < - typename T, typename U, - typename = std::enable_if_t< - std::is_constructible_v && std::is_constructible_v && - std::is_constructible_v && std::is_constructible_v && - std::is_constructible_v>> -class Expression { -public: - using iterator = typename std::vector>::iterator; - using const_iterator = typename std::vector>::const_iterator; - - /** - * @brief Construct an Expression from a varargs list of terms. Constant is - * set to 0. - * @tparam Args Variadic template parameter for the terms. - * @param t First term. - * @param ms Remaining terms. - */ - template explicit Expression(Term t, Args&&... ms) { - terms.emplace_back(t); - (terms.emplace_back(std::forward(ms)), ...); - sortTerms(); - aggregateEqualTerms(); - } - - /** - * @brief Construct an Expression from a varargs list of variables. Constant - * is set to 0 and coefficients of the variables are set to 1. - * @tparam Args Variadic template parameter for the variables. - * @param v First variable. - * @param ms Remaining variables. - */ - template explicit Expression(Variable v, Args&&... ms) { - terms.emplace_back(Term(v)); - (terms.emplace_back(std::forward(ms)), ...); - sortTerms(); - aggregateEqualTerms(); - } - - /** - * @brief Construct an Expression from a vector of terms and a constant. - * @param ts Vector of terms. - * @param con Constant of the expression. - */ - Expression(const std::vector>& ts, const U& con) - : terms(ts), constant(con) {}; - - /** - * @brief Default constructor. Expression has no Terms and a constant of 0. - */ - Expression() = default; - - /** - * @brief Construct an Expression from a constant. Expression has no Terms. - * @param r Constant of the expression. - */ - explicit Expression(const U& r) : constant(r) {}; - - iterator begin() { return terms.begin(); } - [[nodiscard]] const_iterator begin() const { return terms.cbegin(); } - iterator end() { return terms.end(); } - [[nodiscard]] const_iterator end() const { return terms.cend(); } - [[nodiscard]] const_iterator cbegin() const { return terms.cbegin(); } - [[nodiscard]] const_iterator cend() const { return terms.cend(); } - - /** - * @brief Check whether the expression is zero, i.e. has no Terms and 0 - * constant. - * @return True if the expression is zero, false otherwise. - */ - [[nodiscard]] bool isZero() const { - return terms.empty() && constant == U{T{0}}; - } - - /** - * @brief Check whether the expression is a constant, i.e. has no Terms. - * @return True if the expression is a constant, false otherwise. - */ - [[nodiscard]] bool isConstant() const { return terms.empty(); } - - /** - * @brief Add two expressions. Coefficients of like terms and constants are - * added. - * @details If a term for the same variable is already present in the - * expression, the coefficients are added. Otherwise, the term is inserted - * into the expression. - * @param rhs Expression to add. - * @return Reference to the expression. - */ - Expression& operator+=(const Expression& rhs) { - if (this->isZero()) { - *this = rhs; - return *this; - } - - if (rhs.isZero()) { - return *this; - } - - auto t = rhs.begin(); - - while (t != rhs.end()) { - auto insertPos = terms.begin(); - while (insertPos != terms.end() && insertPos->getVar() < t->getVar()) { - ++insertPos; - } - if (insertPos != terms.end() && insertPos->getVar() == t->getVar()) { - if (std::abs(insertPos->getCoeff() + t->getCoeff()) < TOLERANCE) { - terms.erase(insertPos); - } else { - insertPos->addCoeff(t->getCoeff()); - } - } else { - terms.insert(insertPos, *t); - } - ++t; - } - constant += rhs.constant; - return *this; - } - - /** - * @brief Add a term to the expression. Coefficients of like terms are added. - * @details If a term for the same variable is already present in the - * expression, the coefficients are added. Otherwise, the term is inserted - * into the expression. - * @param rhs Term to add. - * @return Reference to the expression. - */ - Expression& operator+=(const Term& rhs) { - return *this += Expression(rhs); - } - - /** - * @brief Add a constant to the expression. - * @param rhs Constant to add. - * @return Reference to the expression. - */ - Expression& operator+=(const U& rhs) { - constant += rhs; - return *this; - } - - /** - * @brief Subtract two expressions. Coefficients of like terms and constants - * are subtracted. - * @details If a term for the same variable is already present in the - * expression, the coefficients are subtracted. Otherwise, the term is - * inserted into the expression. - * @param rhs Expression to subtract. - * @return Reference to the expression. - */ - Expression& operator-=(const Expression& rhs) { - return *this += -rhs; - } - - /** - * @brief Subtract a term from the expression. Coefficients of like terms are - * subtracted. - * @details If a term for the same variable is already present in the - * expression, the coefficients are subtracted. Otherwise, the term is - * inserted into the expression. - * @param rhs Term to subtract. - * @return Reference to the expression. - */ - Expression& operator-=(const Term& rhs) { return *this += -rhs; } - - /** - * @brief Subtract a constant from the expression. - * @param rhs Constant to subtract. - * @return Reference to the expression. - */ - Expression& operator-=(const U& rhs) { return *this += -rhs; } - - /** @name Multiplication operators - * @brief Multiply the expression by a scalar. Multiplies the coefficients of - * the terms and the constant by the scalar. - * @param rhs Scalar to multiply the expression by. - * @return Reference to the expression. - */ - ///@{ - Expression& operator*=(const T& rhs) { - if (std::abs(static_cast(rhs)) < TOLERANCE) { - terms.clear(); - constant = U{T{0}}; - return *this; - } - for (auto& term : terms) { - term *= rhs; - } - constant = U{double{constant} * double{rhs}}; - return *this; - } - - template , int> = 0> - Expression& operator*=(const U& rhs) { - if (std::abs(static_cast(T{rhs})) < TOLERANCE) { - terms.clear(); - constant = U{T{0}}; - return *this; - } - for (auto& term : terms) { - term *= T{rhs}; - } - constant *= rhs; - return *this; - } - ///@} - - /** @name Division operators - * @brief Divide the expression by a scalar. Divides the coefficients of the - * terms and the constant by the scalar. - * @details Throws an exception if the scalar is zero. - * @param rhs Scalar to divide the expression by. - * @return Reference to the expression. - */ - ///@{ - Expression& operator/=(const T& rhs) { - if (std::abs(static_cast(T{rhs})) < TOLERANCE) { - throw std::runtime_error("Trying to divide expression by 0!"); - } - for (auto& term : terms) { - term /= rhs; - } - constant = U{double{constant} / double{rhs}}; - return *this; - } - - template , int> = 0> - Expression& operator/=(const U& rhs) { - if (std::abs(static_cast(T{rhs})) < TOLERANCE) { - throw std::runtime_error("Trying to divide expression by 0!"); - } - for (auto& term : terms) { - term /= T{rhs}; - } - constant /= rhs; - return *this; - } - - Expression& operator/=(int64_t rhs) { - if (rhs == 0) { - throw std::runtime_error("Trying to divide expression by 0!"); - } - for (auto& term : terms) { - term /= T{static_cast(rhs)}; - } - constant = U{double{constant} / static_cast(rhs)}; - return *this; - } - - ///@} - - /** - * @brief Get the negative of the expression. - * @details Negates the coefficients of the terms and the constant. - * @return Negative of the expression. - */ - [[nodiscard]] Expression operator-() const { - Expression e; - e.terms.reserve(terms.size()); - for (auto& t : terms) { - e.terms.push_back(-t); - } - e.constant = -constant; - return e; - } - - /** - * @brief Get the term at a given index. - * @details No bounds checking is performed. - * @param i Index of the term. - * @return Term at the given index. - */ - [[nodiscard]] const Term& operator[](const std::size_t i) const { - return terms[i]; - } - - /** - * @brief Get the constant of the expression. - * @return Constant of the expression. - */ - [[nodiscard]] U getConst() const noexcept { return constant; } - - /** - * @brief Set the constant of the expression. - * @param val Constant to set. - */ - void setConst(const U& val) { constant = val; } - [[nodiscard]] auto numTerms() const { return terms.size(); } - - /** - * @brief Get the terms of the expression. - * @return Terms of the expression. - */ - [[nodiscard]] const std::vector>& getTerms() const { return terms; } - - /** - * @brief Get the variables appearing in terms of the expression. - * @return Variables of the expression. - */ - [[nodiscard]] std::unordered_set getVariables() const { - auto vars = std::unordered_set{}; - for (const auto& term : terms) { - vars.insert(term.getVar()); - } - return vars; - } - - /** - * @brief Convert the expression to a different type. - * @details Converts the coefficients of the terms and the constant to a - * different type. - * @tparam V Type to convert to. - * @return Expression with coefficients and constant converted to type V. - */ - template >* = nullptr> - Expression convert() const { - return Expression(terms, V{constant}); - } - - /** - * @brief Evaluate the expression with respect to a given assignment. - * @param assignment Assignment to evaluate the expression with. - * @return Result of the evaluation. - */ - [[nodiscard]] double evaluate(const VariableAssignment& assignment) const { - auto initial = static_cast(constant); - return std::accumulate(terms.begin(), terms.end(), initial, - [&](const double sum, const auto& term) { - return term.evaluate(assignment) + sum; - }); - } - -private: - std::vector> terms; - U constant{T{0.0}}; - - void sortTerms() { - for (auto it = terms.begin(); it != terms.end(); ++it) { - for (auto jt = std::next(it); jt != terms.end(); ++jt) { - if (jt->getVar() < it->getVar()) { - std::swap(*it, *jt); - } - } - } - } - - void aggregateEqualTerms() { - for (auto t = terms.begin(); t != terms.end();) { - auto next = std::next(t); - while (next != terms.end() && t->getVar() == next->getVar()) { - t->addCoeff(next->getCoeff()); - next = terms.erase(next); - } - if (t->hasZeroCoeff()) { - t = terms.erase(t); - } else { - t = next; - } - } - } -}; - -/** @name Arithmetic Expression operators - * @brief Overloaded operators for the Expression struct. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return Expression obtained by applying the operation. - */ -///@{ -template -Expression operator+(Expression lhs, const Expression& rhs) { - lhs += rhs; - return lhs; -} - -template -Expression operator+(Expression lhs, const Term& rhs) { - lhs += rhs; - return lhs; -} - -template -Expression operator+(const Term& lhs, Expression rhs) { - rhs += lhs; - return rhs; -} - -template -Expression operator+(const U& lhs, Expression rhs) { - rhs += lhs; - return rhs; -} - -template -Expression operator+(Expression lhs, const U& rhs) { - lhs += rhs; - return lhs; -} - -template -Expression operator+([[maybe_unused]] const T& lhs, - Expression rhs) { - rhs += rhs; - return rhs; -} - -template -Expression operator-(Expression lhs, const Expression& rhs) { - lhs -= rhs; - return lhs; -} -template -Expression operator-(Expression lhs, const Term& rhs) { - lhs -= rhs; - return lhs; -} -template -Expression operator-(const Term& lhs, Expression rhs) { - rhs -= lhs; - return rhs; -} -template -Expression operator-(const U& lhs, Expression rhs) { - rhs -= lhs; - return rhs; -} - -template -Expression operator-(Expression lhs, const U& rhs) { - lhs -= rhs; - return lhs; -} - -template -Expression operator*(Expression lhs, const T& rhs) { - lhs *= rhs; - return lhs; -} - -template >* = nullptr> -Expression operator*(Expression lhs, const U& rhs) { - lhs *= rhs; - return lhs; -} - -template -Expression operator/(Expression lhs, const T& rhs) { - lhs /= rhs; - return lhs; -} - -template >* = nullptr> -Expression operator/(Expression lhs, const U& rhs) { - lhs /= rhs; - return lhs; -} - -template -Expression operator/(Expression lhs, int64_t rhs) { - lhs /= rhs; - return lhs; -} - -template -Expression operator*(const T& lhs, Expression rhs) { - return rhs * lhs; -} - -template >* = nullptr> -Expression operator*(const U& lhs, Expression rhs) { - return rhs * lhs; -} - -///@} - -/** - * @brief Check whether two expressions are equal. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return True if the expressions are equal, false otherwise. - */ -template -bool operator==(const Expression& lhs, const Expression& rhs) { - if (lhs.numTerms() != rhs.numTerms() || lhs.getConst() != rhs.getConst()) { - return false; - } - - const auto lhsTerms = lhs.numTerms(); - for (size_t i = 0; i < lhsTerms; ++i) { - if (std::abs(lhs[i].getCoeff() - rhs[i].getCoeff()) >= TOLERANCE) { - return false; - } - } - return true; -} - -/** - * @brief Check whether two expressions are not equal. - * @param lhs Left-hand side of the operator. - * @param rhs Right-hand side of the operator. - * @return True if the expressions are not equal, false otherwise. - */ -template -bool operator!=(const Expression& lhs, const Expression& rhs) { - return !(lhs == rhs); -} - -std::ostream& operator<<(std::ostream& os, const Variable& var); - -template -std::ostream& operator<<(std::ostream& os, const Term& term) { - os << term.getCoeff() << "*" << term.getVar().getName(); - return os; -} - -template -std::ostream& operator<<(std::ostream& os, const Expression& expr) { - for (const auto& term : expr) { - os << term << " + "; - } - os << expr.getConst(); - return os; -} -} // namespace sym - -template struct std::hash> { - std::size_t operator()(const sym::Expression& expr) const noexcept { - std::size_t seed = 0U; - for (const auto& term : expr) { - qc::hashCombine(seed, std::hash>{}(term)); - } - qc::hashCombine(seed, std::hash{}(expr.getConst())); - return seed; - } -}; - -namespace qc { -using Symbolic = sym::Expression; -using VariableAssignment = std::unordered_map; -using SymbolOrNumber = std::variant; -} // namespace qc diff --git a/include/mqt-core/ir/operations/IfElseOperation.hpp b/include/mqt-core/ir/operations/IfElseOperation.hpp deleted file mode 100644 index 3c8be5d9b8..0000000000 --- a/include/mqt-core/ir/operations/IfElseOperation.hpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file IfElseOperation.hpp - * @brief Classically controlled if-else quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/Register.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include - -namespace qc { - -enum ComparisonKind : std::uint8_t { - Eq, - Neq, - Lt, - Leq, - Gt, - Geq, -}; - -ComparisonKind getInvertedComparisonKind(ComparisonKind kind); - -std::string toString(const ComparisonKind& kind); - -std::ostream& operator<<(std::ostream& os, const ComparisonKind& kind); - -class IfElseOperation final : public Operation { -public: - IfElseOperation(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const ClassicalRegister& controlRegister, - std::uint64_t expectedValue = 1U, ComparisonKind kind = Eq); - - IfElseOperation(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, Bit controlBit, - bool expectedValue = true, ComparisonKind kind = Eq); - - IfElseOperation(const IfElseOperation& op); - - IfElseOperation& operator=(const IfElseOperation& op); - - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(*this); - } - - void apply(const Permutation& permutation) override; - - [[nodiscard]] bool isUnitary() const override { return false; } - - [[nodiscard]] bool isNonUnitaryOperation() const override { return true; } - - [[nodiscard]] bool isIfElseOperation() const noexcept override { - return true; - } - - [[nodiscard]] bool isControlled() const override { return false; } - - [[nodiscard]] auto getThenOp() const { return thenOp_.get(); } - - [[nodiscard]] auto getElseOp() const { return elseOp_.get(); } - - [[nodiscard]] const auto& getControlRegister() const noexcept { - return controlRegister_; - } - - [[nodiscard]] const auto& getControlBit() const noexcept { - return controlBit_; - } - - [[nodiscard]] auto getExpectedValueRegister() const noexcept { - return expectedValueRegister_; - } - - [[nodiscard]] bool getExpectedValueBit() const noexcept { - return expectedValueBit_; - } - - [[nodiscard]] auto getComparisonKind() const noexcept { - return comparisonKind_; - } - - [[nodiscard]] bool equals(const Operation& op) const override; - - virtual std::ostream& print(std::ostream& os, const Permutation& permutation, - std::size_t prefixWidth, - std::size_t nqubits) const override; - - void invert() override { - throw std::runtime_error("An IfElseOperation cannot be inverted."); - } - - // Override invalid Operation setters - void setTargets(const Targets& /*t*/) override { - throw std::runtime_error("An IfElseOperation does not have a target."); - } - - void setControls(const Controls& /*c*/) override { - throw std::runtime_error("An IfElseOperation cannot be controlled."); - } - void addControl(Control /*c*/) override { - throw std::runtime_error("An IfElseOperation cannot be controlled."); - } - void clearControls() override { - throw std::runtime_error("An IfElseOperation cannot be controlled."); - } - void removeControl(Control /*c*/) override { - throw std::runtime_error("An IfElseOperation cannot be controlled."); - } - Controls::iterator removeControl(Controls::iterator /*it*/) override { - throw std::runtime_error("An IfElseOperation cannot be controlled."); - } - - void setGate(const OpType /*g*/) override { - throw std::runtime_error( - "Cannot set operation type of an IfElseOperation."); - } - - void setParameter(const std::vector&) override { - throw std::runtime_error("An IfElseOperation cannot be parameterized."); - } - -private: - std::unique_ptr thenOp_; - std::unique_ptr elseOp_; - std::optional controlRegister_; - std::optional controlBit_; - std::uint64_t expectedValueRegister_ = 1U; - bool expectedValueBit_ = true; - ComparisonKind comparisonKind_ = Eq; - - /** - * @brief Canonicalizes the IfElseOperation - * @details Ensures if an else block is present, the then block is also - * present. If only the else block is present, it is moved to the then block - * and the else block is set to null with the condition inverted. - * Furthermore, non-equality constraints on single bits are converted to - * equality constraints with inverted expected value. - * Finally, if both then and else blocks are present, any equality constraint - * on a single bit with expected value false is converted to an equality - * constraint with expected value true with then and else blocks swapped. - */ - void canonicalize(); -}; -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(qc::IfElseOperation const& op) const noexcept; -}; diff --git a/include/mqt-core/ir/operations/NonUnitaryOperation.hpp b/include/mqt-core/ir/operations/NonUnitaryOperation.hpp deleted file mode 100644 index cef8e2e01d..0000000000 --- a/include/mqt-core/ir/operations/NonUnitaryOperation.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file NonUnitaryOperation.hpp - * @brief Measurement, reset, and other non-unitary quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -class NonUnitaryOperation final : public Operation { -public: - // Measurement constructor - NonUnitaryOperation(std::vector qubitRegister, - std::vector classicalRegister); - NonUnitaryOperation(Qubit qubit, Bit cbit); - - // General constructor - explicit NonUnitaryOperation(Targets qubits, OpType op = Reset); - - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(*this); - } - - [[nodiscard]] bool isUnitary() const override { return false; } - - [[nodiscard]] bool isNonUnitaryOperation() const override { return true; } - - [[nodiscard]] const std::vector& getClassics() const { return classics; } - std::vector& getClassics() { return classics; } - [[nodiscard]] std::size_t getNclassics() const { return classics.size(); } - - void addDepthContribution(std::vector& depths) const override; - - void addControl(const Control /*c*/) override { - throw std::runtime_error("Cannot add control to non-unitary operation."); - } - - void clearControls() override { - throw std::runtime_error( - "Cannot clear controls from non-unitary operation."); - } - - void removeControl(const Control /*c*/) override { - throw std::runtime_error( - "Cannot remove controls from non-unitary operation."); - } - - Controls::iterator removeControl(const Controls::iterator /*it*/) override { - throw std::runtime_error( - "Cannot remove controls from non-unitary operation."); - } - - [[nodiscard]] bool equals(const Operation& operation) const override; - - std::ostream& print(std::ostream& os, const Permutation& permutation, - std::size_t prefixWidth, - std::size_t nqubits) const override; - - void invert() override { - throw std::runtime_error( - "Inverting a non-unitary operation is not supported."); - } - - void apply(const Permutation& permutation) override; - -protected: - std::vector classics; // vector for the classical bits to measure into - - static void printMeasurement(std::ostream& os, const std::vector& q, - const std::vector& c, - const Permutation& permutation, - std::size_t nqubits); - void printReset(std::ostream& os, const std::vector& q, - const Permutation& permutation, std::size_t nqubits) const; -}; -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(qc::NonUnitaryOperation const& op) const noexcept { - std::size_t seed = 0U; - qc::hashCombine(seed, op.getType()); - for (const auto& q : op.getTargets()) { - qc::hashCombine(seed, q); - } - for (const auto& c : op.getClassics()) { - qc::hashCombine(seed, c); - } - return seed; - } -}; diff --git a/include/mqt-core/ir/operations/OpType.hpp b/include/mqt-core/ir/operations/OpType.hpp deleted file mode 100644 index d5a0c3f682..0000000000 --- a/include/mqt-core/ir/operations/OpType.hpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file OpType.hpp - * @brief Quantum operation type enumeration and helper functions. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace qc { - -enum OpTypeFlags : uint8_t { - OpTypeNone = 0b00, - OpTypeInv = 0b01, - OpTypeDiag = 0b10, -}; - -constexpr static unsigned NUM_OP_TYPE_FLAG_BITS = 2; - -// Natively supported operations of the MQT Core library -enum OpType : std::uint8_t { -#define HANDLE_OP_TYPE(N, id, flags, repr) \ - id = ((N) << (NUM_OP_TYPE_FLAG_BITS)) | (flags), -#define LAST_OP_TYPE(N) OpTypeEnd = (N) << (NUM_OP_TYPE_FLAG_BITS), -#include "OpType.inc" - -#undef HANDLE_OP_TYPE -#undef LAST_OP_TYPE -}; - -std::string toString(OpType opType); - -/** - * @brief Gives a short name for the given OpType (at most 3 characters) - * @param opType OpType to get the short name for - * @return Short name for the given OpType - */ -std::string shortName(OpType opType); - -[[nodiscard]] constexpr bool isTwoQubitGate(const OpType opType) { - switch (opType) { - case SWAP: - case iSWAP: - case iSWAPdg: - case Peres: - case Peresdg: - case DCX: - case ECR: - case RXX: - case RYY: - case RZZ: - case RZX: - case XXminusYY: - case XXplusYY: - return true; - default: - return false; - } -} - -[[nodiscard]] constexpr bool isThreeQubitGate(const OpType opType) { - switch (opType) { - case RCCX: - return true; - default: - return false; - } -} - -/** - * @brief Checks if given OpType is a single qubit gate - */ -[[nodiscard]] constexpr bool isSingleQubitGate(const OpType type) { - switch (type) { - case I: - case U: - case U2: - case P: - case X: - case Y: - case Z: - case H: - case S: - case Sdg: - case T: - case SX: - case SXdg: - case Tdg: - case V: - case Vdg: - case RX: - case RY: - case RZ: - case R: - return true; - default: - return false; - } -} - -inline std::ostream& operator<<(std::ostream& out, const OpType opType) { - return out << toString(opType); -} - -[[nodiscard]] OpType opTypeFromString(const std::string& opType); - -inline std::istream& operator>>(std::istream& in, OpType& opType) { - std::string opTypeStr; - in >> opTypeStr; - - if (opTypeStr.empty()) { - in.setstate(std::istream::failbit); - return in; - } - - opType = opTypeFromString(opTypeStr); - return in; -} - -} // namespace qc diff --git a/include/mqt-core/ir/operations/OpType.inc b/include/mqt-core/ir/operations/OpType.inc deleted file mode 100644 index e01207c51d..0000000000 --- a/include/mqt-core/ir/operations/OpType.inc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -HANDLE_OP_TYPE(0, None, 0, "none") - -// Standard Operations -HANDLE_OP_TYPE(1, GPhase, 0, "gphase") -HANDLE_OP_TYPE(2, I, OpTypeDiag, "i") -HANDLE_OP_TYPE(3, Barrier, OpTypeDiag, "barrier") -HANDLE_OP_TYPE(4, H, OpTypeNone, "h") -HANDLE_OP_TYPE(5, X, OpTypeNone, "x") -HANDLE_OP_TYPE(6, Y, OpTypeNone, "y") -HANDLE_OP_TYPE(7, Z, OpTypeDiag, "z") -HANDLE_OP_TYPE(8, S, OpTypeDiag, "s") -HANDLE_OP_TYPE(8, Sdg, OpTypeDiag | OpTypeInv, "sdg") -HANDLE_OP_TYPE(9, T, OpTypeDiag, "t") -HANDLE_OP_TYPE(9, Tdg, OpTypeDiag | OpTypeInv, "tdg") -HANDLE_OP_TYPE(10, V, OpTypeNone, "v") -HANDLE_OP_TYPE(10, Vdg, OpTypeInv, "vdg") -HANDLE_OP_TYPE(11, U, OpTypeNone, "u") -HANDLE_OP_TYPE(12, U2, OpTypeNone, "u2") -HANDLE_OP_TYPE(13, P, OpTypeDiag, "p") -HANDLE_OP_TYPE(14, SX, OpTypeNone, "sx") -HANDLE_OP_TYPE(14, SXdg, OpTypeInv, "sxdg") -HANDLE_OP_TYPE(15, RX, OpTypeNone, "rx") -HANDLE_OP_TYPE(16, RY, OpTypeNone, "ry") -HANDLE_OP_TYPE(17, RZ, OpTypeDiag, "rz") -HANDLE_OP_TYPE(41, R, OpTypeNone, "r") -HANDLE_OP_TYPE(18, SWAP, OpTypeNone, "swap") -HANDLE_OP_TYPE(19, iSWAP, OpTypeNone, "iswap") -HANDLE_OP_TYPE(19, iSWAPdg, OpTypeInv, "iswapdg") -HANDLE_OP_TYPE(20, Peres, OpTypeNone, "peres") -HANDLE_OP_TYPE(20, Peresdg, OpTypeInv, "peresdg") -HANDLE_OP_TYPE(21, DCX, OpTypeNone, "dcx") -HANDLE_OP_TYPE(22, ECR, OpTypeNone, "ecr") -HANDLE_OP_TYPE(23, RXX, OpTypeNone, "rxx") -HANDLE_OP_TYPE(24, RYY, OpTypeNone, "ryy") -HANDLE_OP_TYPE(25, RZZ, OpTypeDiag, "rzz") -HANDLE_OP_TYPE(26, RZX, OpTypeNone, "rzx") -HANDLE_OP_TYPE(27, XXminusYY, OpTypeNone, "xx_minus_yy") -HANDLE_OP_TYPE(28, XXplusYY, OpTypeNone, "xx_plus_yy") -HANDLE_OP_TYPE(33, RCCX, OpTypeNone, "rccx") - -// Compound Operation -HANDLE_OP_TYPE(29, Compound, OpTypeNone, "compound") - -// Non Unitary Operations -HANDLE_OP_TYPE(30, Measure, OpTypeNone, "measure") -HANDLE_OP_TYPE(31, Reset, OpTypeNone, "reset") - -// Control flow operations -HANDLE_OP_TYPE(32, IfElse, OpTypeNone, "if_else") - -LAST_OP_TYPE(43) - - -#undef OpTypeInv -#undef OpTypeDiag diff --git a/include/mqt-core/ir/operations/Operation.hpp b/include/mqt-core/ir/operations/Operation.hpp deleted file mode 100644 index 76bdac6aff..0000000000 --- a/include/mqt-core/ir/operations/Operation.hpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Operation.hpp - * @brief Base interface for quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -class Operation { -protected: - Controls controls; - Targets targets; - std::vector parameter; - - OpType type = None; - std::string name; - -public: - Operation() = default; - Operation(const Operation& op) = default; - Operation(Operation&& op) noexcept = default; - Operation& operator=(const Operation& op) = default; - Operation& operator=(Operation&& op) noexcept = default; - - // Virtual Destructor - virtual ~Operation() = default; - - [[nodiscard]] virtual std::unique_ptr clone() const = 0; - - // Getters - [[nodiscard]] virtual const Targets& getTargets() const { return targets; } - virtual Targets& getTargets() { return targets; } - [[nodiscard]] virtual std::size_t getNtargets() const { - return targets.size(); - } - - [[nodiscard]] virtual const Controls& getControls() const { return controls; } - virtual Controls& getControls() { return controls; } - [[nodiscard]] virtual std::size_t getNcontrols() const { - return controls.size(); - } - [[nodiscard]] virtual std::size_t getNqubits() const { - return getNcontrols() + getNtargets(); - } - - [[nodiscard]] const std::vector& getParameter() const { - return parameter; - } - std::vector& getParameter() { return parameter; } - - [[nodiscard]] const std::string& getName() const { return name; } - [[nodiscard]] virtual OpType getType() const { return type; } - - [[nodiscard]] virtual auto getUsedQubits() const -> std::set; - - [[nodiscard]] std::unique_ptr getInverted() const { - auto op = clone(); - op->invert(); - return op; - } - - // Setter - virtual void setTargets(const Targets& t) { targets = t; } - - virtual void setControls(const Controls& c) { - clearControls(); - addControls(c); - } - - virtual void addControl(Control c) = 0; - - void addControls(const Controls& c) { - for (const auto& control : c) { - addControl(control); - } - } - - virtual void clearControls() = 0; - - virtual void removeControl(Control c) = 0; - - virtual Controls::iterator removeControl(Controls::iterator it) = 0; - - void removeControls(const Controls& c) { - for (auto it = c.begin(); it != c.end();) { - it = removeControl(it); - } - } - - virtual void setGate(const OpType g) { - type = g; - name = toString(g); - } - - virtual void setParameter(const std::vector& p) { parameter = p; } - - virtual void apply(const Permutation& permutation); - - [[nodiscard]] virtual bool isUnitary() const { return true; } - - [[nodiscard]] virtual bool isStandardOperation() const { return false; } - - [[nodiscard]] virtual bool isCompoundOperation() const noexcept { - return false; - } - - [[nodiscard]] virtual bool isNonUnitaryOperation() const { return false; } - - [[nodiscard]] virtual bool isIfElseOperation() const noexcept { - return false; - } - - [[nodiscard]] virtual bool isSymbolicOperation() const { return false; } - - [[nodiscard]] virtual auto isDiagonalGate() const -> bool { - // the second bit in the type is a flag that is set for diagonal gates - return (+type & OpTypeDiag) != 0; - } - - [[nodiscard]] virtual auto isSingleQubitGate() const -> bool { - return !isControlled() && qc::isSingleQubitGate(type); - } - - [[nodiscard]] virtual bool isControlled() const { return !controls.empty(); } - - [[nodiscard]] virtual bool isClifford() const { return false; } - - /** - * @brief Checks whether a gate is global. - * @details A StandardOperation is global if it acts on all qubits. - * A CompoundOperation is global if all its sub-operations are - * StandardOperations of the same type with the same parameters acting on all - * qubits. The latter is what a QASM line like `ry(π) q;` is translated to in - * MQT Core. All other operations are not global. - * @return True if the operation is global, false otherwise. - */ - [[nodiscard]] virtual bool isGlobal(size_t /* unused */) const { - return false; - } - - [[nodiscard]] virtual bool actsOn(const Qubit i) const { - for (const auto& t : targets) { - if (t == i) { - return true; - } - } - return controls.count(i) > 0; - } - - virtual void addDepthContribution(std::vector& depths) const; - - [[nodiscard]] virtual bool equals(const Operation& op) const; - - virtual std::ostream& printParameters(std::ostream& os) const; - std::ostream& print(std::ostream& os, const std::size_t nqubits) const { - return print(os, {}, 0, nqubits); - } - virtual std::ostream& print(std::ostream& os, const Permutation& permutation, - std::size_t prefixWidth, - std::size_t nqubits) const; - - /// Checks whether operation commutes with other operation on a given qubit. - [[nodiscard]] virtual auto commutesAtQubit(const Operation& /*other*/, - const Qubit& /*qubit*/) const - -> bool { - return false; - } - - [[nodiscard]] virtual auto isInverseOf(const Operation& /*other*/) const - -> bool; - - virtual void invert() = 0; - - virtual bool operator==(const Operation& rhs) const { return equals(rhs); } - bool operator!=(const Operation& rhs) const { return !(*this == rhs); } -}; -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(const qc::Operation& op) const noexcept { - std::size_t seed = 0U; - qc::hashCombine(seed, hash{}(op.getType())); - for (const auto& control : op.getControls()) { - qc::hashCombine(seed, hash{}(control.qubit)); - if (control.type == qc::Control::Type::Neg) { - seed ^= 1ULL; - } - } - for (const auto& target : op.getTargets()) { - qc::hashCombine(seed, hash{}(target)); - } - for (const auto& param : op.getParameter()) { - qc::hashCombine(seed, hash{}(param)); - } - return seed; - } -}; diff --git a/include/mqt-core/ir/operations/StandardOperation.hpp b/include/mqt-core/ir/operations/StandardOperation.hpp deleted file mode 100644 index a8fdafda3a..0000000000 --- a/include/mqt-core/ir/operations/StandardOperation.hpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file StandardOperation.hpp - * @brief Standard unitary quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { -class StandardOperation : public Operation { -protected: - constexpr static fp PARAMETER_TOLERANCE = 1e-13; - - static void checkInteger(fp& ld) { - const fp nearest = std::nearbyint(ld); - if (std::abs(ld - nearest) < PARAMETER_TOLERANCE) { - ld = nearest; - } - } - - static void checkFractionPi(fp& ld) { - const fp div = PI / ld; - const fp nearest = std::nearbyint(div); - if (std::abs(div - nearest) < PARAMETER_TOLERANCE) { - ld = PI / nearest; - } - } - - OpType parseU3(fp& theta, fp& phi, fp& lambda); - OpType parseU2(fp& phi, fp& lambda); - OpType parseU1(fp& lambda); - - void checkUgate(); - void setup(); - -public: - StandardOperation() = default; - - // Standard Constructors - StandardOperation(Qubit target, OpType g, std::vector params = {}); - StandardOperation(const Targets& targ, OpType g, std::vector params = {}); - - StandardOperation(Control control, Qubit target, OpType g, - const std::vector& params = {}); - StandardOperation(Control control, const Targets& targ, OpType g, - const std::vector& params = {}); - - StandardOperation(const Controls& c, Qubit target, OpType g, - const std::vector& params = {}); - StandardOperation(const Controls& c, const Targets& targ, OpType g, - const std::vector& params = {}); - - // MCF (cSWAP), Peres, parameterized two target Constructor - StandardOperation(const Controls& c, Qubit target0, Qubit target1, OpType g, - const std::vector& params = {}); - - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(*this); - } - - [[nodiscard]] bool isStandardOperation() const override { return true; } - - [[nodiscard]] bool isGlobal(size_t nQubits) const override; - - [[nodiscard]] bool isClifford() const override; - - void addControl(const Control c) override { - if (actsOn(c.qubit)) { - throw std::runtime_error("Cannot add control on qubit " + - std::to_string(c.qubit) + - " to operation it already acts on the qubit."); - } - - controls.emplace(c); - } - - void clearControls() override { controls.clear(); } - - void removeControl(const Control c) override { - if (controls.erase(c) == 0) { - throw std::runtime_error("Cannot remove control on qubit " + - std::to_string(c.qubit) + - " from operation as it is not a control."); - } - } - - Controls::iterator removeControl(const Controls::iterator it) override { - return controls.erase(it); - } - - [[nodiscard]] auto commutesAtQubit(const Operation& other, - const Qubit& qubit) const -> bool override; - - void invert() override; -}; - -} // namespace qc diff --git a/include/mqt-core/ir/operations/SymbolicOperation.hpp b/include/mqt-core/ir/operations/SymbolicOperation.hpp deleted file mode 100644 index b6f4a3fdc9..0000000000 --- a/include/mqt-core/ir/operations/SymbolicOperation.hpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file SymbolicOperation.hpp - * @brief Symbolic-parameter quantum operations. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { - -class SymbolicOperation final : public StandardOperation { -public: - SymbolicOperation() = default; - - [[nodiscard]] SymbolOrNumber getParameter(std::size_t i) const; - - [[nodiscard]] std::vector getParameters() const; - - void setSymbolicParameter(const Symbolic& par, const std::size_t i) { - symbolicParameter.at(i) = par; - } - - // Standard Constructors - SymbolicOperation(Qubit target, OpType g, - const std::vector& params = {}); - SymbolicOperation(const Targets& targ, OpType g, - const std::vector& params = {}); - - SymbolicOperation(Control control, Qubit target, OpType g, - const std::vector& params = {}); - SymbolicOperation(Control control, const Targets& targ, OpType g, - const std::vector& params = {}); - - SymbolicOperation(const Controls& c, Qubit target, OpType g, - const std::vector& params = {}); - SymbolicOperation(const Controls& c, const Targets& targ, OpType g, - const std::vector& params = {}); - - // MCF (cSWAP), Peres, parameterized two target Constructor - SymbolicOperation(const Controls& c, Qubit target0, Qubit target1, OpType g, - const std::vector& params = {}); - - [[nodiscard]] std::unique_ptr clone() const override; - - [[nodiscard]] bool isSymbolicOperation() const override; - - [[nodiscard]] bool isStandardOperation() const override; - - [[nodiscard]] bool equals(const Operation& op) const override; - - [[nodiscard]] StandardOperation - getInstantiatedOperation(const VariableAssignment& assignment) const; - - // Instantiates this Operation - // Afterwards casting to StandardOperation can be done if assignment is total - void instantiate(const VariableAssignment& assignment); - - void invert() override; - -protected: - std::vector> symbolicParameter; - - static OpType parseU3(const Symbolic& theta, fp& phi, fp& lambda); - static OpType parseU3(fp& theta, const Symbolic& phi, fp& lambda); - static OpType parseU3(fp& theta, fp& phi, const Symbolic& lambda); - static OpType parseU3(const Symbolic& theta, const Symbolic& phi, fp& lambda); - static OpType parseU3(const Symbolic& theta, fp& phi, const Symbolic& lambda); - static OpType parseU3(fp& theta, const Symbolic& phi, const Symbolic& lambda); - - static OpType parseU2(const Symbolic& phi, const Symbolic& lambda); - static OpType parseU2(const Symbolic& phi, fp& lambda); - static OpType parseU2(fp& phi, const Symbolic& lambda); - - static OpType parseU1(const Symbolic& lambda); - - void checkSymbolicUgate(); - - void storeSymbolOrNumber(const SymbolOrNumber& param, std::size_t i); - - [[nodiscard]] bool isSymbolicParameter(std::size_t i) const; - - static bool isSymbol(const SymbolOrNumber& param); - - static Symbolic& getSymbol(SymbolOrNumber& param); - - static fp& getNumber(SymbolOrNumber& param); - - void setup(const std::vector& params); - - [[nodiscard]] static fp - getInstantiation(const SymbolOrNumber& symOrNum, - const VariableAssignment& assignment); - - void negateSymbolicParameter(std::size_t index); - - void addToSymbolicParameter(std::size_t index, fp value); -}; -} // namespace qc - -template <> struct std::hash { - std::size_t operator()(qc::SymbolicOperation const& op) const noexcept { - std::size_t seed = 0U; - qc::hashCombine(seed, std::hash{}(op)); - for (const auto& param : op.getParameters()) { - if (std::holds_alternative(param)) { - qc::hashCombine(seed, hash{}(get(param))); - } else { - qc::hashCombine(seed, hash{}(get(param))); - } - } - return seed; - } -}; diff --git a/include/mqt-core/qasm3/Exception.hpp b/include/mqt-core/qasm3/Exception.hpp deleted file mode 100644 index 0bf7021be1..0000000000 --- a/include/mqt-core/qasm3/Exception.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Exception.hpp - * @brief OpenQASM 3 compiler exception types. - */ - -#pragma once - -#include "Statement.hpp" - -#include -#include -#include -#include -#include - -namespace qasm3 { -class CompilerError final : public std::exception { -public: - std::string message; - std::shared_ptr debugInfo; - mutable std::string cachedMessage; - - CompilerError(std::string msg, std::shared_ptr debug) - : message(std::move(msg)), debugInfo(std::move(debug)) {} - - [[nodiscard]] std::string toString() const { - std::stringstream ss{}; - ss << debugInfo->toString(); - - auto parentDebugInfo = debugInfo->parent; - while (parentDebugInfo != nullptr) { - ss << "\n (included from " << parentDebugInfo->toString() << ")"; - parentDebugInfo = parentDebugInfo->parent; - } - - ss << ":\n" << message; - - return ss.str(); - } - - [[nodiscard]] const char* what() const noexcept override { - cachedMessage = toString(); - return cachedMessage.c_str(); - } -}; - -class ConstEvalError final : public std::exception { -public: - std::string message; - mutable std::string cachedMessage; - - explicit ConstEvalError(std::string msg) : message(std::move(msg)) {} - - [[nodiscard]] std::string toString() const { - return "Constant Evaluation: " + message; - } - - [[nodiscard]] const char* what() const noexcept override { - cachedMessage = toString(); - return cachedMessage.c_str(); - } -}; - -class TypeCheckError final : public std::exception { -public: - std::string message; - mutable std::string cachedMessage; - - explicit TypeCheckError(std::string msg) : message(std::move(msg)) {} - - [[nodiscard]] std::string toString() const { - return "Type Check Error: " + message; - } - - [[nodiscard]] const char* what() const noexcept override { - cachedMessage = toString(); - return cachedMessage.c_str(); - } -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Gate.hpp b/include/mqt-core/qasm3/Gate.hpp deleted file mode 100644 index 00585d6296..0000000000 --- a/include/mqt-core/qasm3/Gate.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Gate.hpp - * @brief OpenQASM 3 gate declarations and definitions. - */ - -#pragma once - -#include "Statement_fwd.hpp" -#include "ir/operations/OpType.hpp" - -#include -#include -#include -#include -#include - -namespace qasm3 { -struct GateInfo { - size_t nControls; - size_t nTargets; - size_t nParameters; - qc::OpType type; -}; - -struct Gate { - virtual ~Gate() = default; - - virtual size_t getNControls() = 0; - virtual size_t getNTargets() = 0; - virtual size_t getNParameters() = 0; -}; - -struct StandardGate final : Gate { - GateInfo info; - - explicit StandardGate(const GateInfo& gateInfo) : info(gateInfo) {} - - size_t getNControls() override { return info.nControls; } - - size_t getNTargets() override { return info.nTargets; } - size_t getNParameters() override { return info.nParameters; } -}; - -struct CompoundGate final : Gate { - std::vector parameterNames; - std::vector targetNames; - std::vector> body; - - explicit CompoundGate( - std::vector parameters, std::vector targets, - std::vector> bodyStatements) - : parameterNames(std::move(parameters)), targetNames(std::move(targets)), - body(std::move(bodyStatements)) {} - - size_t getNControls() override { return 0; } - - size_t getNTargets() override { return targetNames.size(); } - size_t getNParameters() override { return parameterNames.size(); } -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Importer.hpp b/include/mqt-core/qasm3/Importer.hpp deleted file mode 100644 index b6e86ec1f2..0000000000 --- a/include/mqt-core/qasm3/Importer.hpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Importer.hpp - * @brief Import OpenQASM 3 programs into MQT Core circuits. - */ - -#pragma once - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "qasm3/InstVisitor.hpp" -#include "qasm3/NestedEnvironment.hpp" -#include "qasm3/Statement_fwd.hpp" -#include "qasm3/passes/ConstEvalPass.hpp" -#include "qasm3/passes/TypeCheckPass.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declarations -namespace qc { -enum ComparisonKind : std::uint8_t; -class ClassicalRegister; -struct Control; -class QuantumRegister; -class Operation; -class QuantumComputation; -using QuantumRegisterMap = std::unordered_map; -} // namespace qc -namespace qasm3 { -class Statement; -struct Gate; - -class Importer final : public InstVisitor { -public: - /** - * Imports a QASM3 file into a @ref qc::QuantumComputation instance - * @param filename The path to the QASM3 file to import - * @return The imported @ref qc::QuantumComputation instance - */ - [[nodiscard]] static auto importf(const std::string& filename) - -> qc::QuantumComputation; - - /** - * Imports a QASM3 program from a string into a @ref qc::QuantumComputation - * @param qasm The QASM3 program to import - * @return The imported @ref qc::QuantumComputation instance - */ - [[nodiscard]] static auto imports(const std::string& qasm) - -> qc::QuantumComputation; - - /** - * Imports a QASM3 program from a stream into a @ref qc::QuantumComputation - * @param is The input stream to read the QASM3 program from - * @return The imported @ref qc::QuantumComputation instance - */ - [[nodiscard]] static auto import(std::istream& is) -> qc::QuantumComputation; - -private: - /** - * @brief Construct a new instance for importing QASM3 code - * @param quantumComputation The @ref qc::QuantumComputation to import the - * QASM3 code into. - */ - explicit Importer(qc::QuantumComputation& quantumComputation); - - /** - * @brief Import the given QASM3 program into the @ref qc::QuantumComputation - * @param program The parsed QASM3 program AST - */ - void visitProgram(const std::vector>& program); - - const_eval::ConstEvalPass constEvalPass; - type_checking::TypeCheckPass typeCheckPass; - - NestedEnvironment> declarations; - qc::QuantumComputation* qc{}; - - std::map> gates; - - bool openQASM2CompatMode{false}; - - qc::Permutation initialLayout; - qc::Permutation outputPermutation; - - static std::map> - initializeBuiltins(); - - void translateGateOperand(const std::shared_ptr& gateOperand, - std::vector& qubits, - const qc::QuantumRegisterMap& qregs, - const std::shared_ptr& debugInfo) const; - - void translateBitOperand( - const std::shared_ptr& indexedIdentifier, - std::vector& bits, - const std::shared_ptr& debugInfo) const; - - /** - * @brief Translates a condition expression - * @param condition The condition expression to translate. - * @param debugInfo The debug information of the condition expression. - * @return Either a pair of a bit and a boolean value, or a triple of a - * classical register, a comparison kind, and an integer value. - * @throws CompilerError If the condition is neither of the expected types. - */ - [[nodiscard]] std::variant< - std::pair, - std::tuple> - translateCondition(const std::shared_ptr& condition, - const std::shared_ptr& debugInfo) const; - - static uint64_t - evaluatePositiveConstant(const std::shared_ptr& expr, - const std::shared_ptr& debugInfo, - uint64_t defaultValue = 0); - - void visitVersionDeclaration( - std::shared_ptr versionDeclaration) override; - - void visitDeclarationStatement( - std::shared_ptr declarationStatement) override; - - void visitAssignmentStatement( - std::shared_ptr assignmentStatement) override; - - void visitInitialLayout(std::shared_ptr layout) override; - - void visitOutputPermutation( - std::shared_ptr permutation) override; - - void - visitGateStatement(std::shared_ptr gateStatement) override; - - void visitGateCallStatement( - std::shared_ptr gateCallStatement) override; - - auto - evaluateGateCall(const std::shared_ptr& gateCallStatement, - const std::string& identifier, - const std::vector>& parameters, - std::vector> targets, - const qc::QuantumRegisterMap& qregs) - -> std::unique_ptr; - - static std::shared_ptr - getMcGateDefinition(const std::string& identifier, size_t operandSize, - const std::shared_ptr& debugInfo); - - auto applyQuantumOperation(const std::shared_ptr& gate, - const qc::Targets& targetBits, - const std::vector& controlBits, - const std::vector& evaluatedParameters, - bool invertOperation, - const std::shared_ptr& debugInfo) - -> std::unique_ptr; - - void visitMeasureAssignment( - const std::shared_ptr& indexedIdentifier, - const std::shared_ptr& measureExpression, - const std::shared_ptr& debugInfo); - - void visitBarrierStatement( - std::shared_ptr barrierStatement) override; - - void - visitResetStatement(std::shared_ptr resetStatement) override; - - void visitIfStatement(std::shared_ptr ifStatement) override; - - [[nodiscard]] auto translateBlockOperations( - const std::vector>& statements) - -> std::unique_ptr; - - std::pair - parseGateIdentifierCompatMode(const std::string& identifier); -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/InstVisitor.hpp b/include/mqt-core/qasm3/InstVisitor.hpp deleted file mode 100644 index 545987736d..0000000000 --- a/include/mqt-core/qasm3/InstVisitor.hpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file InstVisitor.hpp - * @brief Visitors for OpenQASM 3 instructions and expressions. - */ - -#pragma once - -#include "Statement_fwd.hpp" - -#include -#include -#include - -namespace qasm3 { - -class InstVisitor { -public: - virtual void - visitGateStatement(std::shared_ptr gateStatement) = 0; - virtual void visitVersionDeclaration( - std::shared_ptr versionDeclaration) = 0; - virtual void visitDeclarationStatement( - std::shared_ptr declarationStatement) = 0; - virtual void - visitInitialLayout(std::shared_ptr initialLayout) = 0; - virtual void visitOutputPermutation( - std::shared_ptr outputPermutation) = 0; - virtual void visitGateCallStatement( - std::shared_ptr gateCallStatement) = 0; - virtual void visitAssignmentStatement( - std::shared_ptr assignmentStatement) = 0; - virtual void - visitBarrierStatement(std::shared_ptr barrierStatement) = 0; - virtual void - visitResetStatement(std::shared_ptr resetStatement) = 0; - virtual void visitIfStatement(std::shared_ptr ifStatement) = 0; - - virtual ~InstVisitor() = default; -}; - -class DefaultInstVisitor : public InstVisitor { -public: - void visitGateStatement( - std::shared_ptr /*gateStatement*/) override {} - void visitVersionDeclaration( - std::shared_ptr /*versionDeclaration*/) override {} - void visitDeclarationStatement( - std::shared_ptr /*declarationStatement*/) override { - } - void visitInitialLayout( - std::shared_ptr /*initialLayout*/) override {} - void visitOutputPermutation( - std::shared_ptr /*outputPermutation*/) override {} - void visitGateCallStatement( - std::shared_ptr /*gateCallStatement*/) override {} - void visitAssignmentStatement( - std::shared_ptr /*assignmentStatement*/) override {} - void visitBarrierStatement( - std::shared_ptr /*barrierStatement*/) override {} - void visitResetStatement( - std::shared_ptr /*resetStatement*/) override {} - void visitIfStatement(std::shared_ptr /*ifStatement*/) override { - } -}; - -template class ExpressionVisitor { -public: - virtual T - visitBinaryExpression(std::shared_ptr binaryExpression) = 0; - virtual T - visitUnaryExpression(std::shared_ptr unaryExpression) = 0; - virtual T visitConstantExpression(std::shared_ptr constant) = 0; - virtual T visitIdentifierExpression( - std::shared_ptr identifierExpression) = 0; - virtual T - visitIdentifierList(std::shared_ptr identifierList) = 0; - virtual T visitIndexedIdentifier( - std::shared_ptr indexedIdentifier) = 0; - virtual T visitMeasureExpression( - std::shared_ptr measureExpression) = 0; - - // A manually implemented visitor function with a templated return type. - // This is impossible as a virtual function in expression, which is why - // we define it manually. - T visit(const std::shared_ptr& expression) { - if (expression == nullptr) { - throw std::runtime_error("Expression is null"); - } - if (const auto binaryExpression = - std::dynamic_pointer_cast(expression)) { - return visitBinaryExpression(binaryExpression); - } - if (const auto unaryExpression = - std::dynamic_pointer_cast(expression)) { - return visitUnaryExpression(unaryExpression); - } - if (const auto constantInt = - std::dynamic_pointer_cast(expression)) { - return visitConstantExpression(constantInt); - } - if (const auto identifierExpression = - std::dynamic_pointer_cast(expression)) { - return visitIdentifierExpression(identifierExpression); - } - if (const auto identifierList = - std::dynamic_pointer_cast(expression)) { - return visitIdentifierList(identifierList); - } - if (const auto indexedIdentifier = - std::dynamic_pointer_cast(expression)) { - return visitIndexedIdentifier(indexedIdentifier); - } - if (const auto measureExpression = - std::dynamic_pointer_cast(expression)) { - return visitMeasureExpression(measureExpression); - } - throw std::runtime_error("Unhandled expression type."); - } - - virtual ~ExpressionVisitor() = default; -}; - -template class Type; -using ResolvedType = Type; -template class DesignatedType; -template class UnsizedType; -template class ArrayType; - -template class TypeVisitor { -public: - virtual ~TypeVisitor() = default; - - virtual std::shared_ptr - visitDesignatedType(DesignatedType* designatedType) = 0; - virtual std::shared_ptr - visitUnsizedType(UnsizedType* unsizedType) = 0; - virtual std::shared_ptr - visitArrayType(ArrayType* arrayType) = 0; -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/NestedEnvironment.hpp b/include/mqt-core/qasm3/NestedEnvironment.hpp deleted file mode 100644 index 161ab6f9b1..0000000000 --- a/include/mqt-core/qasm3/NestedEnvironment.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file NestedEnvironment.hpp - * @brief Nested symbol environments for OpenQASM 3 compilation. - */ - -#pragma once - -#include -#include -#include -#include - -namespace qasm3 { -template class NestedEnvironment { - std::vector> env{}; - -public: - NestedEnvironment() { env.emplace_back(); }; - - void push() { env.emplace_back(); } - - void pop() { env.pop_back(); } - - std::optional find(std::string key) const { - for (auto it = env.rbegin(); it != env.rend(); ++it) { - auto found = it->find(key); - if (found != it->end()) { - return found->second; - } - } - return std::nullopt; - } - - void emplace(std::string key, T value) { env.back().emplace(key, value); } -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Parser.hpp b/include/mqt-core/qasm3/Parser.hpp deleted file mode 100644 index 0e330bf0b3..0000000000 --- a/include/mqt-core/qasm3/Parser.hpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Parser.hpp - * @brief Parser for OpenQASM 3 source programs. - */ - -/* - * This file is part of MQT QFR library which is released under the MIT license. - * See file README.md or go to https://www.cda.cit.tum.de/research/quantum/ for - * more information. - */ - -#pragma once - -#include "Scanner.hpp" -#include "Statement_fwd.hpp" -#include "Token.hpp" -#include "Types_fwd.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -// forward declarations -class Permutation; -} // namespace qc - -namespace qasm3 { -class Parser final { - struct ScannerState { - private: - std::unique_ptr is; - - public: - Token last{0, 0}; - Token t{0, 0}; - Token next{0, 0}; - std::unique_ptr scanner; - std::optional filename; - bool isImplicitInclude; - - bool scan() { - last = t; - t = next; - next = scanner->next(); - - return t.kind != Token::Kind::Eof; - } - - explicit ScannerState( - std::istream* in, - std::optional debugFilename = std::nullopt, - const bool implicitInclude = false) - : scanner(std::make_unique(in)), - filename(std::move(debugFilename)), - isImplicitInclude(implicitInclude) { - scan(); - } - - explicit ScannerState( - std::unique_ptr in, - std::optional debugFilename = std::nullopt, - const bool implicitInclude = false) - : is(std::move(in)), scanner(std::make_unique(is.get())), - filename(std::move(debugFilename)), - isImplicitInclude(implicitInclude) { - scan(); - } - }; - - std::stack scanner; - std::shared_ptr includeDebugInfo{nullptr}; - - [[noreturn]] void error(const Token& token, const std::string& msg); - - [[nodiscard]] Token last() const; - - [[nodiscard]] Token current() const; - - [[nodiscard]] Token peek() const; - - Token expect(const Token::Kind& expected, - const std::optional& context = std::nullopt); - -public: - explicit Parser(std::istream& is, bool implicitlyIncludeStdgates = true); - - ~Parser() = default; - - std::shared_ptr parseVersionDeclaration(); - - std::vector> parseProgram(); - - std::shared_ptr parseStatement(); - - std::shared_ptr parseQuantumStatement(); - - void parseInclude(); - - std::shared_ptr parseAssignmentStatement(); - - std::shared_ptr parseMeasureStatement(); - - std::shared_ptr parseResetStatement(); - - std::shared_ptr parseBarrierStatement(); - - std::shared_ptr parseDeclaration(bool isConst, bool isOutput); - - std::shared_ptr parseGateDefinition(); - - std::shared_ptr parseOpaqueGateDefinition(); - - std::shared_ptr parseGateCallStatement(); - - std::shared_ptr parseGateModifier(); - - std::shared_ptr parseIndexOperator(); - - std::shared_ptr parseIndexedIdentifier(); - - std::shared_ptr parseGateOperand(); - - std::shared_ptr parseDeclarationExpression(); - - std::shared_ptr parseMeasureExpression(); - - std::shared_ptr exponentiation(); - - std::shared_ptr factor(); - - std::shared_ptr term(); - - std::shared_ptr comparison(); - - std::shared_ptr parseExpression(); - - std::shared_ptr parseIdentifierList(); - - std::pair>>, bool> - parseType(); - - std::shared_ptr parseTypeDesignator(); - - static qc::Permutation parsePermutation(std::string s); - - void scan(); - - std::shared_ptr makeDebugInfo(Token const& begin, - Token const& /*end*/); - - std::shared_ptr makeDebugInfo(Token const& token); - - [[nodiscard]] bool isAtEnd() const { - return current().kind == Token::Kind::Eof; - } - std::shared_ptr parseIfStatement(); - std::vector> parseBlockOrStatement(); -}; - -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Scanner.hpp b/include/mqt-core/qasm3/Scanner.hpp deleted file mode 100644 index 14dc3642dc..0000000000 --- a/include/mqt-core/qasm3/Scanner.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Scanner.hpp - * @brief Lexical scanner for OpenQASM 3 source programs. - */ - -#pragma once - -#include "Token.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qasm3 { -class Scanner { - std::istream* is; - std::unordered_map keywords; - char ch = 0; - size_t line = 1; - size_t col = 0; - - [[nodiscard]] static bool isSpace(char c); - - [[nodiscard]] static bool isFirstIdChar(char c); - - [[nodiscard]] static bool isNum(char c); - - [[nodiscard]] static bool isHex(char c); - - [[nodiscard]] static bool hasTimingSuffix(char first, char second); - - static char readUtf8Codepoint(std::istream* in); - - void nextCh(); - - [[nodiscard]] char peek() const; - - std::optional consumeWhitespaceAndComments(); - - static bool isValidDigit(uint8_t base, char c); - - std::string consumeNumberLiteral(uint8_t base); - - static uint64_t parseIntegerLiteral(const std::string& str, uint8_t base); - - Token consumeNumberLiteral(); - - Token consumeHardwareQubit(); - - Token consumeString(); - - Token consumeName(); - - void error(const std::string& msg) const; - - void expect(char expected); - -public: - explicit Scanner(std::istream* in); - - ~Scanner() = default; - - Token next(); -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Statement.hpp b/include/mqt-core/qasm3/Statement.hpp deleted file mode 100644 index 8beb2b070c..0000000000 --- a/include/mqt-core/qasm3/Statement.hpp +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Statement.hpp - * @brief OpenQASM 3 abstract-syntax-tree statements and expressions. - */ - -#pragma once - -#include "Statement_fwd.hpp" // IWYU pragma: export -#include "Types_fwd.hpp" -#include "ir/Permutation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -// forward declarations -enum ComparisonKind : std::uint8_t; -} // namespace qc - -namespace qasm3 { -class InstVisitor; - -struct DebugInfo { - size_t line; - size_t column; - std::string filename; - std::shared_ptr parent; - - DebugInfo(const size_t l, const size_t c, std::string file, - std::shared_ptr parentDebugInfo = nullptr) - : line(l), column(c), filename(std::move(std::move(file))), - parent(std::move(parentDebugInfo)) {} - - [[nodiscard]] std::string toString() const { - return filename + ":" + std::to_string(line) + ":" + std::to_string(column); - } -}; - -// Expressions -class Expression { -public: - virtual ~Expression() = default; - - [[nodiscard]] virtual std::string getName() const = 0; -}; - -class DeclarationExpression final { -public: - std::shared_ptr expression; - - explicit DeclarationExpression(std::shared_ptr expr) - : expression(std::move(expr)) {} - - ~DeclarationExpression() = default; -}; - -class Constant final : public Expression { - std::variant val; - bool isSigned; - bool isFp; - bool isBoolean; - -public: - Constant(int64_t value, const bool valueIsSigned) - : val(value), isSigned(valueIsSigned), isFp(false), isBoolean(false) {} - - explicit Constant(double value) - : val(value), isSigned(true), isFp(true), isBoolean(false) {} - explicit Constant(bool value) - : val(value), isSigned(false), isFp(false), isBoolean(true) {} - - [[nodiscard]] bool isInt() const { return !isFp; } - [[nodiscard]] bool isSInt() const { return !isFp && isSigned; } - [[nodiscard]] bool isUInt() const { return !isFp && !isSigned; } - [[nodiscard]] bool isFP() const { return isFp; } - [[nodiscard]] bool isBool() const { return isBoolean; } - [[nodiscard]] int64_t getSInt() const { return std::get<0>(val); } - [[nodiscard]] uint64_t getUInt() const { - return static_cast(std::get<0>(val)); - } - [[nodiscard]] double getFP() const { return std::get<1>(val); } - [[nodiscard]] double asFP() const { - if (isFp) { - return getFP(); - } - if (isSigned) { - return static_cast(getSInt()); - } - return static_cast(getUInt()); - } - [[nodiscard]] bool getBool() const { return std::get<2>(val); } - - [[nodiscard]] std::string getName() const override { return "Constant"; } -}; - -class BinaryExpression final - : public Expression, - public std::enable_shared_from_this { -public: - enum Op : uint8_t { - Power, - Add, - Subtract, - Multiply, - Divide, - Modulo, - LeftShift, - RightShift, - LessThan, - LessThanOrEqual, - GreaterThan, - GreaterThanOrEqual, - Equal, - NotEqual, - BitwiseAnd, - BitwiseXor, - BitwiseOr, - LogicalAnd, - LogicalOr, - }; - - Op op; - std::shared_ptr lhs; - std::shared_ptr rhs; - - BinaryExpression(const Op opcode, std::shared_ptr l, - std::shared_ptr r) - : op(opcode), lhs(std::move(l)), rhs(std::move(r)) {} - - [[nodiscard]] std::string getName() const override { return "BinaryExpr"; } -}; - -std::optional getComparisonKind(BinaryExpression::Op op); - -class UnaryExpression final - : public Expression, - public std::enable_shared_from_this { -public: - enum Op : uint8_t { - BitwiseNot, - LogicalNot, - Negate, - DurationOf, - Sin, - Cos, - Tan, - Exp, - Ln, - Sqrt, - }; - - std::shared_ptr operand; - Op op; - - UnaryExpression(const Op opcode, std::shared_ptr expr) - : operand(std::move(expr)), op(opcode) {} - - [[nodiscard]] std::string getName() const override { return "UnaryExpr"; } -}; - -class IdentifierExpression final - : public Expression, - public std::enable_shared_from_this { -public: - std::string identifier; - - explicit IdentifierExpression(std::string id) : identifier(std::move(id)) {} - - [[nodiscard]] std::string getName() const override { - return std::string{"IdentifierExpr ("} + identifier + ")"; - } -}; - -class IdentifierList final - : public Expression, - public std::enable_shared_from_this { -public: - std::vector> identifiers; - - explicit IdentifierList( - std::vector> ids) - : identifiers(std::move(ids)) {} - - explicit IdentifierList() = default; - - [[nodiscard]] std::string getName() const override { - return "IdentifierList"; - } -}; - -class IndexOperator { -public: - std::vector> indexExpressions; - - explicit IndexOperator(std::vector> indices) - : indexExpressions(std::move(indices)) {} -}; - -class IndexedIdentifier final - : public Expression, - public std::enable_shared_from_this { -public: - std::string identifier; - std::vector> indices; - - explicit IndexedIdentifier( - std::string id, std::vector> idxs = {}) - : identifier(std::move(id)), indices(std::move(idxs)) {} - - [[nodiscard]] std::string getName() const override { - return std::string{"IndexedIdentifier ("} + identifier + ")"; - } -}; - -class GateOperand final : public Expression, - public std::enable_shared_from_this { -public: - std::variant, uint64_t> operand; - - explicit GateOperand(std::shared_ptr id) - : operand(std::move(id)) {} - - explicit GateOperand(const uint64_t qubit) : operand(qubit) {} - - [[nodiscard]] bool isHardwareQubit() const { - return std::holds_alternative(operand); - } - - [[nodiscard]] uint64_t getHardwareQubit() const { - return std::get(operand); - } - - [[nodiscard]] const std::shared_ptr& - getIdentifier() const { - return std::get>(operand); - } - - [[nodiscard]] std::string getName() const override { - return isHardwareQubit() ? "$" + std::to_string(getHardwareQubit()) - : getIdentifier()->getName(); - } -}; - -class MeasureExpression final - : public Expression, - public std::enable_shared_from_this { -public: - std::shared_ptr gate; - - explicit MeasureExpression(std::shared_ptr gateOperand) - : gate(std::move(gateOperand)) {} - - [[nodiscard]] std::string getName() const override { - return "MeasureExpression"; - } -}; - -// Statements - -class Statement { -public: - std::shared_ptr debugInfo; - explicit Statement(std::shared_ptr debug) - : debugInfo(std::move(debug)) {} - virtual ~Statement() = default; - - virtual void accept(InstVisitor* visitor) = 0; -}; - -class QuantumStatement : public Statement { -protected: - explicit QuantumStatement(std::shared_ptr debug) - : Statement(std::move(debug)) {} -}; - -class GateDeclaration final - : public Statement, - public std::enable_shared_from_this { -public: - std::string identifier; - std::shared_ptr parameters; - std::shared_ptr qubits; - std::vector> statements; - bool isOpaque; - - explicit GateDeclaration(std::shared_ptr debug, std::string id, - std::shared_ptr params, - std::shared_ptr qbits, - std::vector> stmts, - bool opaque = false); - - void accept(InstVisitor* visitor) override; -}; - -class VersionDeclaration final - : public Statement, - public std::enable_shared_from_this { -public: - double version; - - explicit VersionDeclaration(std::shared_ptr debug, - const double versionNum) - : Statement(std::move(debug)), version(versionNum) {} - - void accept(InstVisitor* visitor) override; -}; - -class InitialLayout final : public Statement, - public std::enable_shared_from_this { -public: - qc::Permutation permutation; - - explicit InitialLayout(std::shared_ptr debug, qc::Permutation perm) - : Statement(std::move(debug)), permutation(std::move(perm)) {} - -private: - void accept(InstVisitor* visitor) override; -}; - -class OutputPermutation final - : public Statement, - public std::enable_shared_from_this { -public: - qc::Permutation permutation; - - explicit OutputPermutation(std::shared_ptr debug, - qc::Permutation perm) - : Statement(std::move(debug)), permutation(std::move(perm)) {} - -private: - void accept(InstVisitor* visitor) override; -}; - -class DeclarationStatement final - : public Statement, - public std::enable_shared_from_this { -public: - bool isConst; - bool isOutput; - std::variant, std::shared_ptr> type; - std::string identifier; - std::shared_ptr expression; - - DeclarationStatement(std::shared_ptr debug, const bool declIsConst, - bool declIsOutput, std::shared_ptr ty, - std::string id, - std::shared_ptr expr) - : Statement(std::move(debug)), isConst(declIsConst), - isOutput(declIsOutput), type(ty), identifier(std::move(id)), - expression(std::move(expr)) {} - - void accept(InstVisitor* visitor) override; -}; - -class GateModifier : public std::enable_shared_from_this { -public: - virtual ~GateModifier() = default; -}; - -class InvGateModifier final - : public GateModifier, - public std::enable_shared_from_this {}; - -class PowGateModifier final - : public GateModifier, - public std::enable_shared_from_this { -public: - std::shared_ptr expression; - - explicit PowGateModifier(std::shared_ptr expr) - : expression(std::move(expr)) {} -}; - -class CtrlGateModifier final - : public GateModifier, - public std::enable_shared_from_this { -public: - bool ctrlType; - std::shared_ptr expression; - - explicit CtrlGateModifier(const bool ty, std::shared_ptr expr) - : ctrlType(ty), expression(std::move(expr)) {} -}; - -class GateCallStatement final - : public QuantumStatement, - public std::enable_shared_from_this { -public: - std::string identifier; - std::vector> modifiers; - std::vector> arguments; - std::vector> operands; - - GateCallStatement(std::shared_ptr debug, std::string id, - std::vector> modifierList, - std::vector> argumentList, - std::vector> operandList) - : QuantumStatement(std::move(debug)), identifier(std::move(id)), - modifiers(std::move(modifierList)), arguments(std::move(argumentList)), - operands(std::move(operandList)) {} - - void accept(InstVisitor* visitor) override; -}; - -class AssignmentStatement final - : public Statement, - public std::enable_shared_from_this { -public: - enum Type : uint8_t { - Assignment, - PlusAssignment, - MinusAssignment, - TimesAssignment, - DivAssignment, - BitwiseAndAssignment, - BitwiseOrAssignment, - BitwiseNotAssignment, - BitwiseXorAssignment, - LeftShiftAssignment, - RightShiftAssignment, - ModuloAssignment, - PowerAssignment, - } type; - std::shared_ptr identifier; - std::shared_ptr expression; - - AssignmentStatement(std::shared_ptr debug, const Type ty, - std::shared_ptr id, - std::shared_ptr expr) - : Statement(std::move(debug)), type(ty), identifier(std::move(id)), - expression(std::move(expr)) {} - - void accept(InstVisitor* visitor) override; -}; - -class BarrierStatement final - : public QuantumStatement, - public std::enable_shared_from_this { -public: - std::vector> gates; - - explicit BarrierStatement(std::shared_ptr debug, - std::vector> gateList) - : QuantumStatement(std::move(debug)), gates(std::move(gateList)) {} - - void accept(InstVisitor* visitor) override; -}; - -class ResetStatement final - : public QuantumStatement, - public std::enable_shared_from_this { -public: - std::shared_ptr gate; - - explicit ResetStatement(std::shared_ptr debug, - std::shared_ptr g) - : QuantumStatement(std::move(debug)), gate(std::move(g)) {} - - void accept(InstVisitor* visitor) override; -}; - -class IfStatement final : public Statement, - public std::enable_shared_from_this { -public: - std::shared_ptr condition; - std::vector> thenStatements; - std::vector> elseStatements; - - IfStatement(const std::shared_ptr& cond, - const std::vector>& thenStmts, - const std::vector>& elseStmts, - std::shared_ptr debug) - : Statement(std::move(debug)), condition(cond), thenStatements(thenStmts), - elseStatements(elseStmts) {} - - void accept(InstVisitor* visitor) override; -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Statement_fwd.hpp b/include/mqt-core/qasm3/Statement_fwd.hpp deleted file mode 100644 index 347c1ecdd6..0000000000 --- a/include/mqt-core/qasm3/Statement_fwd.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Statement_fwd.hpp - * @brief Forward declarations for OpenQASM 3 syntax-tree nodes. - */ - -#pragma once - -namespace qasm3 { -struct DebugInfo; -class Statement; -class IfStatement; -class IdentifierList; -class MeasureExpression; -class GateCallStatement; -class GateDeclaration; -class BarrierStatement; -class ResetStatement; -class AssignmentStatement; -class VersionDeclaration; -class DeclarationExpression; -class DeclarationStatement; -class IndexOperator; -class IndexedIdentifier; -class GateOperand; -class GateModifier; -class QuantumStatement; -class InitialLayout; -class OutputPermutation; -class Expression; -class Constant; -class BinaryExpression; -class UnaryExpression; -class IdentifierExpression; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/StdGates.hpp b/include/mqt-core/qasm3/StdGates.hpp deleted file mode 100644 index 85747d91bd..0000000000 --- a/include/mqt-core/qasm3/StdGates.hpp +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file StdGates.hpp - * @brief Definitions of OpenQASM 3 standard gates. - */ - -#pragma once - -#include "Gate.hpp" -#include "ir/operations/OpType.hpp" - -#include -#include -#include - -namespace qasm3 { -// Non-natively supported gates from -// https://github.com/Qiskit/qiskit/blob/main/qiskit/qasm/libs/stdgates.inc -const std::string STDGATES = - "// four parameter controlled-U gate with relative phase\n" - "gate cu(theta, phi, lambda, gamma) c, t { p(gamma) c; ctrl @ U(theta, " - "phi, lambda) c, t; }\n"; - -// Non-natively supported gates from -// https://github.com/Qiskit/qiskit/blob/main/qiskit/qasm/libs/qelib1.inc -const std::string QE1LIB = "gate rc3x a,b,c,d {\n" - " u2(0,pi) d; u1(pi/4) d; \n" - " cx c,d; u1(-pi/4) d; u2(0,pi) d; \n" - " cx a,d; u1(pi/4) d; \n" - " cx b,d; u1(-pi/4) d; \n" - " cx a,d; u1(pi/4) d; \n" - " cx b,d; u1(-pi/4) d; \n" - " u2(0,pi) d; u1(pi/4) d; \n" - " cx c,d; u1(-pi/4) d; \n" - " u2(0,pi) d; \n" - "}\n"; - -const std::map> STANDARD_GATES = { - // gates from which all other gates can be constructed. - {"gphase", - std::make_shared(StandardGate({.nControls = 0, - .nTargets = 0, - .nParameters = 1, - .type = qc::GPhase}))}, - {"U", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, - - // natively supported gates - {"p", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - {"u1", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - {"cu1", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - {"phase", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - {"cphase", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - {"cp", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, - - {"id", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::I}))}, - {"u2", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::U2}))}, - {"u3", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, - {"u", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, - - {"x", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - {"cx", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - {"CX", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - {"ccx", - std::make_shared(StandardGate( - {.nControls = 2, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - {"c3x", - std::make_shared(StandardGate( - {.nControls = 3, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - {"c4x", - std::make_shared(StandardGate( - {.nControls = 4, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, - - {"rx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RX}))}, - {"crx", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RX}))}, - - {"y", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Y}))}, - {"cy", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::Y}))}, - - {"ry", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RY}))}, - {"cry", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RY}))}, - - {"z", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Z}))}, - {"cz", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::Z}))}, - - {"rz", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RZ}))}, - {"crz", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RZ}))}, - - {"r", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, - {"prx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, - {"cr", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, - - {"h", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::H}))}, - {"ch", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::H}))}, - - {"s", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::S}))}, - {"sdg", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Sdg}))}, - - {"t", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::T}))}, - {"tdg", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Tdg}))}, - - {"sx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::SX}))}, - {"sxdg", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::SXdg}))}, - {"c3sqrtx", - std::make_shared(StandardGate( - {.nControls = 3, .nTargets = 1, .nParameters = 0, .type = qc::SXdg}))}, - - {"swap", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::SWAP}))}, - {"cswap", - std::make_shared(StandardGate( - {.nControls = 1, .nTargets = 2, .nParameters = 0, .type = qc::SWAP}))}, - - {"iswap", - std::make_shared(StandardGate({.nControls = 0, - .nTargets = 2, - .nParameters = 0, - .type = qc::iSWAP}))}, - {"iswapdg", - std::make_shared(StandardGate({.nControls = 0, - .nTargets = 2, - .nParameters = 0, - .type = qc::iSWAPdg}))}, - - {"rxx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RXX}))}, - {"ryy", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RYY}))}, - {"rzz", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RZZ}))}, - {"rzx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RZX}))}, - {"dcx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::DCX}))}, - {"ecr", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::ECR}))}, - {"xx_minus_yy", - std::make_shared(StandardGate({.nControls = 0, - .nTargets = 2, - .nParameters = 2, - .type = qc::XXminusYY}))}, - {"xx_plus_yy", - std::make_shared(StandardGate({.nControls = 0, - .nTargets = 2, - .nParameters = 2, - .type = qc::XXplusYY}))}, - {"rccx", - std::make_shared(StandardGate( - {.nControls = 0, .nTargets = 3, .nParameters = 0, .type = qc::RCCX}))}, -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Token.hpp b/include/mqt-core/qasm3/Token.hpp deleted file mode 100644 index 564a0b45eb..0000000000 --- a/include/mqt-core/qasm3/Token.hpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Token.hpp - * @brief Tokens produced by the OpenQASM 3 scanner. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace qasm3 { - -struct Token { - enum class Kind : uint8_t { - None, - - OpenQasm, - Include, - DefCalGrammar, - Def, - Cal, - DefCal, - Gate, - Opaque, - Extern, - Box, - Let, - - Break, - Continue, - If, - Else, - End, - Return, - For, - While, - In, - - Pragma, - - // types - Input, - Output, - Const, - ReadOnly, - Mutable, - - Qreg, - Qubit, - - CReg, - Bool, - Bit, - Int, - Uint, - Float, - Angle, - Complex, - Array, - Void, - - Duration, - Stretch, - - // builtin identifiers - Gphase, - Inv, - Pow, - Ctrl, - NegCtrl, - - Dim, - - DurationOf, - - Delay, - Reset, - Measure, - Barrier, - - True, - False, - - LBracket, - RBracket, - LBrace, - RBrace, - LParen, - RParen, - - Colon, - Semicolon, - Eof, - - Dot, - Comma, - - Equals, - Arrow, - Plus, - DoublePlus, - Minus, - Asterisk, - DoubleAsterisk, - Slash, - Percent, - Pipe, - DoublePipe, - Ampersand, - DoubleAmpersand, - Caret, - At, - Tilde, - ExclamationPoint, - - DoubleEquals, - NotEquals, - PlusEquals, - MinusEquals, - AsteriskEquals, - SlashEquals, - AmpersandEquals, - PipeEquals, - TildeEquals, - CaretEquals, - LeftShitEquals, - RightShiftEquals, - PercentEquals, - DoubleAsteriskEquals, - - LessThan, - LessThanEquals, - GreaterThan, - GreaterThanEquals, - LeftShift, - RightShift, - - Imag, - - Underscore, - - DoubleQuote, - SingleQuote, - BackSlash, - - Identifier, - - HardwareQubit, - StringLiteral, - IntegerLiteral, - FloatLiteral, - TimingLiteral, - - Sin, - Cos, - Tan, - Exp, - Ln, - Sqrt, - - InitialLayout, - OutputPermutation, - }; - - Kind kind = Kind::None; - size_t line = 0; - size_t col = 0; - size_t endLine = 0; - size_t endCol = 0; - int64_t val{}; - bool isSigned{false}; - double valReal{}; - std::string str; - - Token(const size_t l, const size_t c) - : line(l), col(c), endLine(l), endCol(c) {} - Token(const Kind k, const size_t l, const size_t c) - : kind(k), line(l), col(c), endLine(l), endCol(c) {} - Token(const Kind k, const size_t l, const size_t c, std::string s) - : kind(k), line(l), col(c), endLine(l), endCol(c), str(std::move(s)) {} - - static std::string kindToString(Kind kind); - - [[nodiscard]] std::string toString() const; - - friend std::ostream& operator<<(std::ostream& os, const Kind& k); - - friend std::ostream& operator<<(std::ostream& os, const Token& t); -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Types.hpp b/include/mqt-core/qasm3/Types.hpp deleted file mode 100644 index 5b5549c6b1..0000000000 --- a/include/mqt-core/qasm3/Types.hpp +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Types.hpp - * @brief OpenQASM 3 type-system definitions. - */ - -#pragma once - -#include "InstVisitor.hpp" -#include "Types_fwd.hpp" // IWYU pragma: export - -#include -#include -#include -#include -#include - -namespace qasm3 { - -template class Type { -public: - virtual ~Type() = default; - - [[nodiscard]] virtual bool operator==(const Type& other) const = 0; - [[nodiscard]] bool operator!=(const Type& other) const { - return !(*this == other); - } - [[nodiscard]] virtual bool allowsDesignator() const = 0; - - virtual void setDesignator(T /*designator*/) { - throw std::runtime_error("Type does not allow designator"); - } - - [[nodiscard]] virtual T getDesignator() = 0; - - virtual std::shared_ptr accept(TypeVisitor* visitor) = 0; - - virtual bool isBool() { return false; } - virtual bool isNumber() { return false; } - virtual bool isFP() { return false; } - virtual bool isUint() { return false; } - virtual bool isBit() { return false; } - - virtual bool isConvertibleToBool() { return isBool(); } - virtual bool fits(const Type& other) { return *this == other; } - - virtual std::string toString() = 0; -}; - -enum DesignatedTy : uint8_t { - Qubit, - Bit, - Int, - Uint, - Float, - Angle, -}; - -template class DesignatedType final : public Type { -public: - ~DesignatedType() override = default; - - DesignatedTy type; - - T designator; - - DesignatedType(const DesignatedTy ty, T design) - : type(ty), designator(std::move(design)) {} - - explicit DesignatedType(DesignatedTy ty); - - bool operator==(const Type& other) const override { - if (const auto* o = dynamic_cast*>(&other)) { - return type == o->type && designator == o->designator; - } - return false; - } - - [[nodiscard]] bool allowsDesignator() const override { return true; } - - static std::shared_ptr getQubitTy(T designator) { - return std::make_shared(Qubit, designator); - } - static std::shared_ptr getBitTy(T designator) { - return std::make_shared(Bit, designator); - } - static std::shared_ptr getIntTy(T designator) { - return std::make_shared(Int, designator); - } - static std::shared_ptr getUintTy(T designator) { - return std::make_shared(Uint, designator); - } - static std::shared_ptr getFloatTy(T designator) { - return std::make_shared(Float, designator); - } - static std::shared_ptr getAngleTy(T designator) { - return std::make_shared(Angle, designator); - } - - void setDesignator(T d) override { this->designator = std::move(d); } - - T getDesignator() override { return designator; } - - std::shared_ptr accept(TypeVisitor* visitor) override { - return visitor->visitDesignatedType(this); - } - - bool isNumber() override { - return type == Int || type == Uint || type == Bit || type == Float; - } - - bool isUint() override { return type == Uint; } - - bool isBit() override { return type == Bit; } - - bool isFP() override { return type == Float; } - - bool isConvertibleToBool() override { - if constexpr (std::is_integral_v) { - return type == Bit && designator == 1; - } else { - return false; - } - } - - bool fits(const Type& other) override; - - std::string toString() override { - switch (type) { - case Qubit: - return "qubit[" + designatorToString() + "]"; - case Bit: - return "bit[" + designatorToString() + "]"; - case Int: - return "int[" + designatorToString() + "]"; - case Uint: - return "uint[" + designatorToString() + "]"; - case Float: - return "float[" + designatorToString() + "]"; - case Angle: - return "angle[" + designatorToString() + "]"; - } - throw std::runtime_error("Unhandled type"); - } - - std::string designatorToString(); -}; - -enum UnsizedTy : uint8_t { Bool, Duration, SingleQubit }; - -template class UnsizedType final : public Type { -public: - ~UnsizedType() override = default; - - UnsizedTy type; - - explicit UnsizedType(const UnsizedTy ty) : type(ty) {} - - bool operator==(const Type& other) const override { - if (const auto* o = dynamic_cast(&other)) { - return type == o->type; - } - return false; - } - [[nodiscard]] bool allowsDesignator() const override { return false; } - - static std::shared_ptr> getBoolTy() { - return std::make_shared(Bool); - } - static std::shared_ptr> getDurationTy() { - return std::make_shared(Duration); - } - static std::shared_ptr> getSingleQubitTy() { - return std::make_shared(SingleQubit); - } - - T getDesignator() override { - throw std::runtime_error("Unsized types do not have designators"); - } - - std::shared_ptr accept(TypeVisitor* visitor) override { - return visitor->visitUnsizedType(this); - } - - bool isBool() override { return type == Bool; } - - std::string toString() override { - switch (type) { - case Bool: - return "bool"; - case Duration: - return "duration"; - case SingleQubit: - return "qubit"; - } - throw std::runtime_error("Unhandled type"); - } -}; - -template class ArrayType final : public Type { -public: - std::shared_ptr> type; - T size; - - ArrayType(std::shared_ptr> ty, T sz) - : type(std::move(ty)), size(sz) {} - ~ArrayType() override = default; - - bool operator==(const Type& other) const override { - if (const auto* o = dynamic_cast(&other)) { - return *type == *o->type && size == o->size; - } - return false; - } - [[nodiscard]] bool allowsDesignator() const override { return true; } - - T getDesignator() override { return size; } - - std::shared_ptr accept(TypeVisitor* visitor) override { - return visitor->visitArrayType(this); - } - - bool fits(const Type& other) override { - if (const auto* o = dynamic_cast(&other)) { - return type->fits(*o->type) && size == o->size; - } - return false; - } - - std::string toString() override { - if constexpr (std::is_same_v) { - return type->toString() + "[" + std::to_string(size) + "]"; - } else { - return type->toString() + "[]"; - } - } -}; - -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Types_fwd.hpp b/include/mqt-core/qasm3/Types_fwd.hpp deleted file mode 100644 index 2b0ed7bb0d..0000000000 --- a/include/mqt-core/qasm3/Types_fwd.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file Types_fwd.hpp - * @brief Forward declarations for OpenQASM 3 type-system classes. - */ - -#pragma once - -#include -#include - -namespace qasm3 { -class Expression; - -template class Type; -using TypeExpr = Type>; -using ResolvedType = Type; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/passes/CompilerPass.hpp b/include/mqt-core/qasm3/passes/CompilerPass.hpp deleted file mode 100644 index 48bcfa0349..0000000000 --- a/include/mqt-core/qasm3/passes/CompilerPass.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file CompilerPass.hpp - * @brief Base interface for OpenQASM 3 compiler passes. - */ - -#pragma once - -namespace qasm3 { -class Statement; - -class CompilerPass { -public: - virtual ~CompilerPass() = default; - - virtual void processStatement(Statement& statement) = 0; -}; -} // namespace qasm3 diff --git a/include/mqt-core/qasm3/passes/ConstEvalPass.hpp b/include/mqt-core/qasm3/passes/ConstEvalPass.hpp deleted file mode 100644 index 1aa3c68157..0000000000 --- a/include/mqt-core/qasm3/passes/ConstEvalPass.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file ConstEvalPass.hpp - * @brief Compile-time evaluation pass for OpenQASM 3 expressions. - */ - -#pragma once - -#include "qasm3/InstVisitor.hpp" -#include "qasm3/NestedEnvironment.hpp" -#include "qasm3/Statement_fwd.hpp" -#include "qasm3/passes/CompilerPass.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qasm3::const_eval { -struct ConstEvalValue { - enum Type : uint8_t { - ConstInt, - ConstUint, - ConstFloat, - ConstBool, - } type; - std::variant value; - size_t width; - - explicit ConstEvalValue(double val, const size_t w = 64) - : type(ConstFloat), value(val), width(w) {} - explicit ConstEvalValue(int64_t val, const bool isSigned, const size_t w = 64) - : type(isSigned ? ConstInt : ConstUint), value(val), width(w) {} - explicit ConstEvalValue(bool val) : type(ConstBool), value(val), width(1) {} - - [[nodiscard]] std::shared_ptr toExpr() const; - - bool operator==(const ConstEvalValue& rhs) const; - - bool operator!=(const ConstEvalValue& rhs) const { return !(*this == rhs); } - - [[nodiscard]] std::string toString() const; -}; - -class ConstEvalPass final - : public CompilerPass, - public DefaultInstVisitor, - public ExpressionVisitor>, - public TypeVisitor> { - NestedEnvironment env; - -public: - ConstEvalPass() = default; - ~ConstEvalPass() override = default; - - void addConst(const std::string& identifier, const ConstEvalValue& val) { - env.emplace(identifier, val); - } - - void addConst(const std::string& identifier, const double val) { - env.emplace(identifier, ConstEvalValue(val)); - } - - void processStatement(Statement& statement) override; - - void pushEnv() { env.push(); } - void popEnv() { env.pop(); } - - void visitDeclarationStatement( - std::shared_ptr declarationStatement) override; - void visitGateCallStatement( - std::shared_ptr gateCallStatement) override; - - std::optional visitBinaryExpression( - std::shared_ptr binaryExpression) override; - std::optional visitUnaryExpression( - std::shared_ptr unaryExpression) override; - std::optional - visitConstantExpression(std::shared_ptr constant) override; - std::optional visitIdentifierExpression( - std::shared_ptr identifierExpression) override; - std::optional - visitIdentifierList(std::shared_ptr identifierList) override; - std::optional visitIndexedIdentifier( - std::shared_ptr indexedIdentifier) override; - std::optional visitMeasureExpression( - std::shared_ptr measureExpression) override; - - std::shared_ptr visitDesignatedType( - DesignatedType>* designatedType) override; - std::shared_ptr visitUnsizedType( - UnsizedType>* unsizedType) override; - std::shared_ptr - visitArrayType(ArrayType>* arrayType) override; -}; -} // namespace qasm3::const_eval diff --git a/include/mqt-core/qasm3/passes/TypeCheckPass.hpp b/include/mqt-core/qasm3/passes/TypeCheckPass.hpp deleted file mode 100644 index 336224279f..0000000000 --- a/include/mqt-core/qasm3/passes/TypeCheckPass.hpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** @file TypeCheckPass.hpp - * @brief Type-checking pass for OpenQASM 3 programs. - */ - -#pragma once - -#include "qasm3/InstVisitor.hpp" -#include "qasm3/Statement_fwd.hpp" -#include "qasm3/Types.hpp" -#include "qasm3/passes/CompilerPass.hpp" -#include "qasm3/passes/ConstEvalPass.hpp" - -#include -#include -#include -#include - -namespace qasm3 { -class GateOperand; -struct DebugInfo; -} // namespace qasm3 - -namespace qasm3::type_checking { -struct InferredType { - bool isError; - std::shared_ptr type; - - InferredType(const bool isErr, std::shared_ptr ty) - : isError(isErr), type(std::move(ty)) {} - - explicit InferredType(std::shared_ptr ty) - : isError(false), type(std::move(ty)) {} - - static InferredType error() { return InferredType{true, nullptr}; } - - [[nodiscard]] bool matches(const InferredType& other) const { - if (isError || other.isError) { - return true; - } - - return *type == *other.type; - } - - [[nodiscard]] std::string toString() const { - if (isError) { - return "error"; - } - return type->toString(); - } -}; - -class TypeCheckPass final : public CompilerPass, - public InstVisitor, - public ExpressionVisitor { - bool hasError = false; - std::string errMessage; - std::map env; - // We need a reference to a const eval pass to evaluate types before type - // checking. - const_eval::ConstEvalPass* constEvalPass; - - InferredType error(const std::string& msg, - const std::shared_ptr& debugInfo = nullptr); - -public: - explicit TypeCheckPass(const_eval::ConstEvalPass& pass) - : constEvalPass(&pass) {} - - ~TypeCheckPass() override = default; - - void addBuiltin(const std::string& identifier, const InferredType& ty) { - env.emplace(identifier, ty); - } - - void processStatement(Statement& statement) override; - - void checkIndexOperator(const IndexOperator& indexOperator); - void checkIndexedIdentifier(const IndexedIdentifier& id); - void checkGateOperand(const GateOperand& operand); - - // Types - void - visitGateStatement(std::shared_ptr gateStatement) override; - void visitVersionDeclaration( - std::shared_ptr versionDeclaration) override; - void visitDeclarationStatement( - std::shared_ptr declarationStatement) override; - void - visitInitialLayout(std::shared_ptr initialLayout) override; - void visitOutputPermutation( - std::shared_ptr outputPermutation) override; - void visitGateCallStatement( - std::shared_ptr gateCallStatement) override; - void visitAssignmentStatement( - std::shared_ptr assignmentStatement) override; - void visitBarrierStatement( - std::shared_ptr barrierStatement) override; - void - visitResetStatement(std::shared_ptr resetStatement) override; - void visitIfStatement(std::shared_ptr ifStatement) override; - - // Expressions - InferredType visitBinaryExpression( - std::shared_ptr binaryExpression) override; - InferredType visitUnaryExpression( - std::shared_ptr unaryExpression) override; - InferredType - visitConstantExpression(std::shared_ptr constantInt) override; - InferredType visitIdentifierExpression( - std::shared_ptr identifierExpression) override; - InferredType - visitIdentifierList(std::shared_ptr identifierList) override; - InferredType visitIndexedIdentifier( - std::shared_ptr indexedIdentifier) override; - InferredType visitMeasureExpression( - std::shared_ptr measureExpression) override; -}; -} // namespace qasm3::type_checking diff --git a/include/mqt-core/qdmi/Client.hpp b/include/mqt-core/qdmi/Client.hpp index 013f778578..fe9427edfa 100644 --- a/include/mqt-core/qdmi/Client.hpp +++ b/include/mqt-core/qdmi/Client.hpp @@ -78,6 +78,32 @@ queuePositionFromResult(const int result, const size_t queuePosition) { return queuePosition; } +[[nodiscard]] inline std::vector +parseShots(const std::string_view shots, const size_t numShots) { + if (numShots == 0) { + if (!shots.empty()) { + throw std::runtime_error("Number of shots mismatch"); + } + return {}; + } + + std::vector parsed; + parsed.reserve(numShots); + size_t start = 0; + while (true) { + const auto end = shots.find(',', start); + parsed.emplace_back(shots.substr(start, end - start)); + if (end == std::string_view::npos) { + break; + } + start = end + 1; + } + if (parsed.size() != numShots) { + throw std::runtime_error("Number of shots mismatch"); + } + return parsed; +} + template [[nodiscard]] std::optional queryCustomValue(Query query, const std::string_view description) { diff --git a/include/mqt-core/qdmi/devices/dd/Device.hpp b/include/mqt-core/qdmi/devices/dd/Device.hpp index 240aa4bfd5..74b81e03a7 100644 --- a/include/mqt-core/qdmi/devices/dd/Device.hpp +++ b/include/mqt-core/qdmi/devices/dd/Device.hpp @@ -257,7 +257,7 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d { /// @returns @c QDMI_SUCCESS once the worker has been spawned. /// Failures inside @p body are reported through the job status (FAILED), /// not through the return value. - auto submitProgramAsync(std::function body) -> QDMI_STATUS; + auto submitProgramAsync(std::function body) -> QDMI_STATUS; /// Submit a QASM 2 or QASM 3 program. /// Dispatches to the sampling or the state-extraction helper depending on diff --git a/mlir/include/mlir/Conversion/GateTable.def b/mlir/include/mlir/Conversion/GateTable.def index b3b2019108..a364e0064a 100644 --- a/mlir/include/mlir/Conversion/GateTable.def +++ b/mlir/include/mlir/Conversion/GateTable.def @@ -15,7 +15,7 @@ * Define * `MQT_GATE(KEY, NAME, OP, GETTER, TARGETS, PARAMS, SUFFIX, CTL_SUFFIX)` * before including this file. `KEY` names the MLIR QC/QCO operation, `NAME` is - * the QIR symbol stem, `OP` is the `qc::OpType` enumerator, `GETTER` identifies + * the QIR symbol stem, `OP` is the `dd::GateType` enumerator, `GETTER` identifies * the QIR function-name selector, `TARGETS` and `PARAMS` describe the body * signature, and `SUFFIX`/`CTL_SUFFIX` select the uncontrolled and controlled * QIR specializations. diff --git a/mlir/include/mlir/Dialect/QIR/Execution/Runtime/Runtime.h b/mlir/include/mlir/Dialect/QIR/Execution/Runtime/Runtime.h index 6b46fcad9d..dded4a4906 100644 --- a/mlir/include/mlir/Dialect/QIR/Execution/Runtime/Runtime.h +++ b/mlir/include/mlir/Dialect/QIR/Execution/Runtime/Runtime.h @@ -15,14 +15,10 @@ #pragma once #include "dd/DDDefinitions.hpp" +#include "dd/GateMatrixDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Operations.hpp" #include "dd/Package.hpp" -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" #include "mlir/Dialect/QIR/Execution/Runtime/QIR.h" #include @@ -199,7 +195,7 @@ class Runtime { struct QState { std::unique_ptr dd; dd::vEdge edge; - dd::Qubit numQubits; + size_t numQubits; QState() : dd(std::make_unique()), edge(dd::vEdge::one()), @@ -228,19 +224,20 @@ class Runtime { private: friend class JitSession; - static constexpr uintptr_t MIN_DYN_QUBIT_ADDRESS = 0x10000; + static constexpr uintptr_t MIN_DYN_QUBIT_ADDRESS = + dd::Package::MAX_POSSIBLE_QUBITS; enum class ResourceMode : uint8_t { UNKNOWN, DYNAMIC, STATIC }; ResourceMode qubitMode; - std::unordered_map qRegister; + std::unordered_map qRegister; // swap gates are not executed, they are tracked here - std::vector qubitPermutation; + std::vector qubitPermutation; static constexpr uintptr_t MIN_DYN_RESULT_ADDRESS = 0x10000; ResourceMode resultMode; std::unordered_map rRegister; std::string measurements; uintptr_t currentMaxQubitAddress; - qc::Qubit currentMaxQubitId; + size_t currentMaxQubitId; uintptr_t currentMaxResultAddress; QState qState; std::mt19937_64 mt; @@ -250,17 +247,25 @@ class Runtime { OutputSchema outputSchema = OutputSchema::Labeled; std::vector> metadata; - auto enlargeState(std::uint64_t maxQubit) -> void; + auto enlargeState(size_t maxQubit) -> void; + static auto staticQubitId(const Qubit* qubit) -> dd::Qubit { + const auto id = reinterpret_cast(qubit); + if (id >= dd::Package::MAX_POSSIBLE_QUBITS) { + throw std::out_of_range( + "Static QIR qubit ID exceeds the supported qubit range"); + } + return static_cast(id); + } static auto bind(Runtime* runtime) noexcept -> Runtime*; auto translateAddresses(std::span qubits) - -> std::vector; - - template - auto createOperation(Args&... args) -> qc::StandardOperation { - static_assert(qc::isSingleQubitGate(Op) || qc::isTwoQubitGate(Op) || - qc::isThreeQubitGate(Op), - "Op must be a one-, two-, or three-qubit gate."); - const auto& params = Utils::packOfType(args...); + -> std::vector; + + template + auto createGateDD(Args&... args) -> dd::MatrixDD { + static_assert(dd::isSingleQubitGate(Gate) || dd::isTwoQubitGate(Gate) || + dd::isThreeQubitGate(Gate), + "Gate must be a one-, two-, or three-qubit gate."); + const auto& params = Utils::packOfType(args...); const auto& qubits = Utils::packOfType(args...); static_assert( std::tuple_size_v> + @@ -273,29 +278,25 @@ class Runtime { for (std::size_t i = 0; i < addresses.size(); ++i) { addresses[i] = qubitPermutation[addresses[i]]; } - // store parameters into vector (without copying) - const std::vector paramVec(params.data(), - params.data() + params.size()); + const std::vector paramVec(params.begin(), params.end()); // split addresses into control and target; also see static_assert above - constexpr uint8_t t = qc::isSingleQubitGate(Op) ? 1 - : qc::isTwoQubitGate(Op) ? 2 - : qc::isThreeQubitGate(Op) ? 3 - : 0; + constexpr uint8_t t = dd::isSingleQubitGate(Gate) ? 1 + : dd::isTwoQubitGate(Gate) ? 2 + : dd::isThreeQubitGate(Gate) ? 3 + : 0; static_assert( std::tuple_size_v> >= t, "Not enough qubits provided for the operation."); if constexpr (std::tuple_size_v> > t) { // create controlled operation - const auto& controls = - qc::Controls(addresses.cbegin(), addresses.cend() - t); - const auto& targets = qc::Targets(addresses.data() + (qubits.size() - t), - addresses.data() + qubits.size()); - return {controls, targets, Op, paramVec}; + const dd::Controls controls(addresses.cbegin(), addresses.cend() - t); + const dd::Targets targets(addresses.cend() - t, addresses.cend()); + return dd::getGateDD(*qState.dd, Gate, paramVec, controls, targets); } // std::tuple_size_v> == t // // create uncontrolled operation - const auto targets = qc::Targets(addresses.data(), addresses.data() + t); - return {targets, Op, paramVec}; + const dd::Targets targets(addresses.cbegin(), addresses.cend()); + return dd::getGateDD(*qState.dd, Gate, paramVec, {}, targets); } // Helper function to output a type (bool, int...) to @c os, honoring the @@ -321,22 +322,21 @@ class Runtime { auto reset() -> void; auto seed(uint64_t randomSeed) -> void; - template + template auto apply(Args&&... args) -> void { - if constexpr (Op == qc::SWAP && sizeof...(Args) == 2) { + if constexpr (Gate == dd::GateType::SWAP && sizeof...(Args) == 2) { swap(std::forward(args)...); } else { - const qc::StandardOperation& operation = - createOperation(std::forward(args)...); - qState.edge = applyUnitaryOperation(operation, qState.edge, *qState.dd); + qState.edge = qState.dd->applyOperation( + createGateDD(std::forward(args)...), qState.edge); } } - auto applyGlobalPhase(qc::fp phase) -> void { + auto applyGlobalPhase(dd::fp phase) -> void { qState.edge = dd::applyGlobalPhase(qState.edge, phase, *qState.dd); } /// Apply a gate with a runtime-sized control set, as required by generic /// controlled QIS specializations. - auto apply(qc::OpType op, std::span params, + auto apply(dd::GateType gate, std::span params, std::span controls, std::span targets) -> void; template auto measure(Args... args) -> void { @@ -372,18 +372,22 @@ class Runtime { for (std::size_t i = 0; i < targets.size(); ++i) { targets[i] = qubitPermutation[targets[i]]; } - const qc::NonUnitaryOperation resetOp( - {targets.data(), targets.data() + SIZE}, qc::Reset); - qState.edge = applyReset(resetOp, qState.edge, *qState.dd, mt); + for (const auto target : targets) { + if (qState.dd->measureOneCollapsing(qState.edge, target, mt) == '1') { + qState.edge = qState.dd->applyOperation( + dd::getGateDD(*qState.dd, dd::GateType::X, {}, {}, {target}), + qState.edge); + } + } } auto swap(Qubit* qubit1, Qubit* qubit2) -> void; auto qAlloc() -> Qubit*; auto qFree(Qubit* qubit) -> void; template auto translateAddresses(std::array qubits) - -> std::array { + -> std::array { // extract addresses from opaque qubit pointers - std::array qubitIds{}; + std::array qubitIds{}; if (qubitMode != ResourceMode::STATIC) { // qubitMode == ResourceMode::DYNAMIC or ResourceMode::UNKNOWN try { @@ -409,11 +413,8 @@ class Runtime { } // qubitMode might have changed to STATIC if (qubitMode == ResourceMode::STATIC) { - Utils::transform( - [](const auto q) { - return static_cast(reinterpret_cast(q)); - }, - qubits, qubitIds); + Utils::transform([](const auto q) { return staticQubitId(q); }, qubits, + qubitIds); } const auto maxQubit = *std::max_element(qubitIds.cbegin(), qubitIds.cend()); enlargeState(maxQubit); diff --git a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp index d9c984f768..c3d689944e 100644 --- a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp +++ b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp @@ -17,9 +17,6 @@ #include "dd/Operations.hpp" #include "dd/Package.hpp" #include "dd/StateGeneration.hpp" -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" #include "mlir/Dialect/CBit/IR/CBitAttributes.h" #include "mlir/Dialect/CBit/IR/CBitDialect.h" #include "mlir/Dialect/CBit/IR/CBitOps.h" @@ -74,12 +71,12 @@ namespace { constexpr size_t MAX_CONTROL_FLOW_STEPS = 10'000; struct QubitMap { - DenseMap qubits; + DenseMap qubits; size_t numQubits = 0; - void bind(Value value, qc::Qubit q) { qubits[value] = q; } + void bind(Value value, dd::Qubit q) { qubits[value] = q; } - [[nodiscard]] std::optional lookup(Value value) const { + [[nodiscard]] std::optional lookup(Value value) const { const auto it = qubits.find(value); if (it == qubits.end()) { return std::nullopt; @@ -100,9 +97,9 @@ struct QubitMap { return success(); } - FailureOr> lookupRange(ValueRange values, + FailureOr> lookupRange(ValueRange values, Operation* op) const { - SmallVector out; + SmallVector out; out.reserve(values.size()); for (Value value : values) { const auto q = lookup(value); @@ -117,7 +114,7 @@ struct QubitMap { }; /// Physical wires stored at each tensor index; extracted positions are empty. -using TensorSlots = SmallVector>; +using TensorSlots = SmallVector>; using TensorState = std::shared_ptr; struct TensorMap { @@ -144,13 +141,13 @@ struct TensorMap { struct ClassicalEnv { struct RegisterBit { std::optional value; - std::optional deferredWire; + std::optional deferredWire; }; using RegisterState = std::vector; using MemRefState = SmallVector; DenseMap values; - DenseMap deferredMeasurements; + DenseMap deferredMeasurements; Operation** deferredMeasurementUse = nullptr; /// Shared storage preserves CBit register identity across `func.call`. DenseMap> registers; @@ -174,7 +171,7 @@ struct ClassicalEnv { }; struct DecodedGate { - qc::OpType type = qc::OpType::None; + dd::GateType type = dd::GateType::None; std::vector params; }; @@ -185,12 +182,12 @@ struct WalkState { dd::Package* dd; std::mt19937_64* rng = nullptr; const DenseSet* deferredMeasurements = nullptr; - DenseSet* deferredMeasuredWires = nullptr; + DenseSet* deferredMeasuredWires = nullptr; size_t remainingExecutionSteps = MAX_CONTROL_FLOW_STEPS; DenseSet activeCalls; }; -using RuntimeValue = std::variant, std::shared_ptr>; struct LoopRange { @@ -254,39 +251,12 @@ resolveDouble(Value value, const ClassicalEnv& classical, Operation* op) { static FailureOr> decodeStandardGate(UnitaryOpInterface unitary, const ClassicalEnv& classical) { Operation* op = unitary.getOperation(); - const auto type = - TypeSwitch(op) - .Case([](auto) { return qc::OpType::I; }) - .Case([](auto) { return qc::OpType::X; }) - .Case([](auto) { return qc::OpType::Y; }) - .Case([](auto) { return qc::OpType::Z; }) - .Case([](auto) { return qc::OpType::H; }) - .Case([](auto) { return qc::OpType::S; }) - .Case([](auto) { return qc::OpType::Sdg; }) - .Case([](auto) { return qc::OpType::T; }) - .Case([](auto) { return qc::OpType::Tdg; }) - .Case([](auto) { return qc::OpType::SX; }) - .Case([](auto) { return qc::OpType::SXdg; }) - .Case([](auto) { return qc::OpType::RX; }) - .Case([](auto) { return qc::OpType::RY; }) - .Case([](auto) { return qc::OpType::RZ; }) - .Case([](auto) { return qc::OpType::P; }) - .Case([](auto) { return qc::OpType::R; }) - .Case([](auto) { return qc::OpType::U2; }) - .Case([](auto) { return qc::OpType::U; }) - .Case([](auto) { return qc::OpType::SWAP; }) - .Case([](auto) { return qc::OpType::iSWAP; }) - .Case([](auto) { return qc::OpType::DCX; }) - .Case([](auto) { return qc::OpType::ECR; }) - .Case([](auto) { return qc::OpType::RCCX; }) - .Case([](auto) { return qc::OpType::RXX; }) - .Case([](auto) { return qc::OpType::RYY; }) - .Case([](auto) { return qc::OpType::RZZ; }) - .Case([](auto) { return qc::OpType::RZX; }) - .Case([](auto) { return qc::OpType::XXplusYY; }) - .Case([](auto) { return qc::OpType::XXminusYY; }) - .Default([](auto) { return qc::OpType::None; }); - if (type == qc::OpType::None) { + TypeSwitch typeSwitch(op); +#define MQT_GATE(KEY, NAME, OP, GETTER, TARGETS, PARAMS, SUFFIX, CTL_SUFFIX) \ + typeSwitch.Case([](auto) { return dd::GateType::OP; }); +#include "mlir/Conversion/GateTable.def" + const auto type = typeSwitch.Default(dd::GateType::None); + if (type == dd::GateType::None) { return std::optional{std::nullopt}; } DecodedGate decoded{.type = type, .params = {}}; @@ -306,14 +276,14 @@ decodeStandardGate(UnitaryOpInterface unitary, const ClassicalEnv& classical) { static dd::mCachedEdge buildEmbeddedLocalDD(dd::Package& dd, const DynamicMatrix& local, - const DenseMap& operandForWire, + const DenseMap& operandForWire, size_t numOperands, int64_t level, size_t row, size_t col) { if (level < 0) { return dd::mCachedEdge::terminal( local(static_cast(row), static_cast(col))); } - const auto wire = static_cast(level); + const auto wire = static_cast(level); const auto operand = operandForWire.find(wire); if (operand == operandForWire.end()) { const auto child = buildEmbeddedLocalDD(dd, local, operandForWire, @@ -341,8 +311,8 @@ buildEmbeddedLocalDD(dd::Package& dd, const DynamicMatrix& local, static dd::MatrixDD makeEmbeddedLocalDD(dd::Package& dd, const DynamicMatrix& local, size_t numQubits, - ArrayRef wires) { - DenseMap operandForWire; + ArrayRef wires) { + DenseMap operandForWire; for (auto [operand, wire] : llvm::enumerate(wires)) { operandForWire[wire] = operand; } @@ -388,7 +358,7 @@ static LogicalResult applyUnitaryMatrix(UnitaryOpInterface unitary, if (failed(wiresOr)) { return failure(); } - ArrayRef wires = *wiresOr; + ArrayRef wires = *wiresOr; if (wires.size() >= 63 || local.rows() != static_cast(size_t{1} << wires.size())) { return unitary.emitError() @@ -438,7 +408,7 @@ static LogicalResult applyUnitaryMatrix(UnitaryOpInterface unitary, template static LogicalResult applyDecodedStandard(UnitaryOpInterface unitary, const DecodedGate& gate, - const qc::Controls& controls, + const dd::Controls& controls, WalkState& walk, StateDD& state) { SmallVector targetVals; for (size_t i = 0; i < unitary.getNumTargets(); ++i) { @@ -449,8 +419,8 @@ static LogicalResult applyDecodedStandard(UnitaryOpInterface unitary, return failure(); } state = walk.dd->applyOperation( - getStandardOperationDD(*walk.dd, gate.type, gate.params, controls, - {targets->begin(), targets->end()}), + dd::getGateDD(*walk.dd, gate.type, gate.params, controls, + {targets->begin(), targets->end()}), state); return walk.qubits->remapUnitary(unitary); } @@ -458,7 +428,7 @@ static LogicalResult applyDecodedStandard(UnitaryOpInterface unitary, static LogicalResult validateReturn(func::ReturnOp returnOp, const QubitMap& qubits, const TensorMap& tensors) { - qc::Qubit expected = 0; + dd::Qubit expected = 0; for (Value value : returnOp.getOperands()) { if (isQTensorType(value.getType())) { const auto slots = tensors.lookup(value); @@ -1053,7 +1023,7 @@ static LogicalResult bindValuePairs(ValueRange sources, ValueRange dests, for (auto [value, dest] : llvm::zip_equal(values, dests)) { if (isa(dest.getType())) { - walk.qubits->bind(dest, std::get(value)); + walk.qubits->bind(dest, std::get(value)); } else if (isQTensorType(dest.getType())) { walk.tensors->bind(dest, std::get(value)); } else if (isa(dest.getType())) { @@ -1062,9 +1032,9 @@ static LogicalResult bindValuePairs(ValueRange sources, ValueRange dests, } else if (isa(dest.getType())) { walk.classical->memrefs[dest] = std::get>(value); - } else if (std::holds_alternative(value)) { + } else if (std::holds_alternative(value)) { walk.classical->values.erase(dest); - walk.classical->deferredMeasurements[dest] = std::get(value); + walk.classical->deferredMeasurements[dest] = std::get(value); } else { walk.classical->deferredMeasurements.erase(dest); walk.classical->values[dest] = std::get(value); @@ -1159,7 +1129,7 @@ static FailureOr allocateZeroQubits(size_t count, WalkState& walk, TensorSlots slots; slots.reserve(count); for (size_t i = 0; i < count; ++i) { - slots.emplace_back(static_cast(first + i)); + slots.emplace_back(static_cast(first + i)); } walk.qubits->numQubits += count; return slots; @@ -1176,7 +1146,7 @@ static LogicalResult checkDeferredMeasurementUse(UnitaryOpInterface unitary, if (failed(wires)) { return failure(); } - if (llvm::none_of(*wires, [&](qc::Qubit wire) { + if (llvm::none_of(*wires, [&](dd::Qubit wire) { return walk.deferredMeasuredWires->contains(wire); })) { return success(); @@ -1245,7 +1215,7 @@ static LogicalResult applyOp(Operation& op, WalkState& walk, StateDD& state) { } TensorSlots slots; slots.reserve(wires->size()); - for (const qc::Qubit wire : *wires) { + for (const dd::Qubit wire : *wires) { slots.emplace_back(wire); } walk.tensors->bind(fromElements.getResult(), @@ -1365,7 +1335,7 @@ static LogicalResult applyOp(Operation& op, WalkState& walk, StateDD& state) { if (bit == '1') { state = walk.dd->applyOperation( walk.dd->makeGateDD( - dd::opToSingleQubitGateMatrix(qc::OpType::X), *q), + dd::opToSingleQubitGateMatrix(dd::GateType::X), *q), state); } walk.qubits->bind(resetOp.getQubitOut(), *q); @@ -1553,8 +1523,8 @@ static LogicalResult applyOp(Operation& op, WalkState& walk, StateDD& state) { if (failed(controlQubits)) { return failure(); } - qc::Controls controls; - for (qc::Qubit q : *controlQubits) { + dd::Controls controls; + for (dd::Qubit q : *controlQubits) { controls.emplace(q); } return applyDecodedStandard(ctrlOp, **decoded, controls, walk, @@ -1641,7 +1611,7 @@ prepare(func::FuncOp func, dd::Package& dd, return staticOp.emitError() << "static qubit index exceeds the supported qubit range"; } - const auto q = static_cast(index); + const auto q = static_cast(index); qubits.bind(staticOp.getQubit(), q); qubits.numQubits = std::max(qubits.numQubits, static_cast(q) + 1); } @@ -1653,7 +1623,7 @@ prepare(func::FuncOp func, dd::Package& dd, return func.emitError() << "QCO function exceeds the supported qubit range"; } - qubits.bind(arg, static_cast(next++)); + qubits.bind(arg, static_cast(next++)); } else if (isQTensorType(arg.getType())) { const auto type = cast(arg.getType()); int64_t size = type.getDimSize(0); @@ -1677,7 +1647,7 @@ prepare(func::FuncOp func, dd::Package& dd, TensorSlots slots; slots.reserve(count); for (size_t i = 0; i < count; ++i) { - slots.emplace_back(static_cast(next++)); + slots.emplace_back(static_cast(next++)); } prepared.tensors.bind(arg, std::make_shared(std::move(slots))); @@ -1692,7 +1662,7 @@ prepare(func::FuncOp func, dd::Package& dd, << "QCO function exceeds the supported qubit range"; } qubits.bind(alloc.getResult(), - static_cast(qubits.numQubits++)); + static_cast(qubits.numQubits++)); } } if (dd.qubits() < qubits.numQubits) { @@ -1737,7 +1707,7 @@ simulateImpl(func::FuncOp func, const dd::VectorDD& in, dd::Package& dd, const PreparedState& prepared, std::mt19937_64* rng, const DenseSet* deferredMeasurements = nullptr, ClassicalEnv* finalClassical = nullptr, - DenseSet* deferredMeasuredWires = nullptr, + DenseSet* deferredMeasuredWires = nullptr, Operation** deferredMeasurementUse = nullptr, bool validateQuantumReturn = true) { const size_t inputQubits = @@ -1878,7 +1848,7 @@ simulateStatevector(func::FuncOp func, dd::Package& dd, << "statevector extraction supports only terminal measurements " "that assemble returned CBit registers"; } - DenseSet measuredWires; + DenseSet measuredWires; Operation* deferredMeasurementUse = nullptr; auto state = simulateImpl( func, dd::makeZeroState(prepared->qubits.numQubits, dd), dd, *prepared, @@ -1955,7 +1925,7 @@ sampleImpl(func::FuncOp func, const dd::VectorDD& in, dd::Package& dd, if (!plan->dynamic) { ClassicalEnv classical; - DenseSet measuredWires; + DenseSet measuredWires; Operation* deferredMeasurementUse = nullptr; dd.incRef(in); auto state = simulateImpl(func, in, dd, prepared, nullptr, diff --git a/mlir/lib/Dialect/QIR/Execution/Runtime/CMakeLists.txt b/mlir/lib/Dialect/QIR/Execution/Runtime/CMakeLists.txt index 729106255f..d8645e9b60 100644 --- a/mlir/lib/Dialect/QIR/Execution/Runtime/CMakeLists.txt +++ b/mlir/lib/Dialect/QIR/Execution/Runtime/CMakeLists.txt @@ -20,6 +20,6 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries( ${TARGET_NAME} - PUBLIC MQT::CoreIR MQT::CoreDD + PUBLIC MQT::CoreDD PRIVATE MQT::ProjectWarnings MQT::ProjectOptions) endif() diff --git a/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp b/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp index 43a508de77..cc7816d90a 100644 --- a/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp +++ b/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp @@ -10,8 +10,8 @@ #include "mlir/Dialect/QIR/Execution/Runtime/QIR.h" -#include "ir/Definitions.hpp" -#include "ir/operations/OpType.hpp" +#include "dd/DDDefinitions.hpp" +#include "dd/GateMatrixDefinitions.hpp" #include "mlir/Dialect/QIR/Execution/Runtime/Runtime.h" #include @@ -64,16 +64,16 @@ static auto controlsFromArray(Array* array) -> std::vector { return controls; } -static auto applyControlled(const qc::OpType op, Array* controlArray, - Qubit* target, const std::span params) +static auto applyControlled(const dd::GateType gate, Array* controlArray, + Qubit* target, const std::span params) -> void { const auto controls = controlsFromArray(controlArray); const std::array targets{target}; - qir::Runtime::getInstance().apply(op, params, controls, targets); + qir::Runtime::getInstance().apply(gate, params, controls, targets); } template -static auto applyControlledTuple(const qc::OpType op, Array* controls, +static auto applyControlledTuple(const dd::GateType gate, Array* controls, Tuple* tuple) -> void { if (tuple == nullptr) { throw std::invalid_argument( @@ -95,10 +95,10 @@ static auto applyControlledTuple(const qc::OpType op, Array* controls, Args args; std::memcpy(&args, tuple, sizeof(Args)); const auto controlList = controlsFromArray(controls); - qir::Runtime::getInstance().apply(op, {}, controlList, args.targets); + qir::Runtime::getInstance().apply(gate, {}, controlList, args.targets); } else { struct Args { - std::array parameters{}; + std::array parameters{}; std::array targets{}; }; static_assert(std::is_standard_layout_v); @@ -106,7 +106,7 @@ static auto applyControlledTuple(const qc::OpType op, Array* controls, Args args; std::memcpy(&args, tuple, sizeof(Args)); const auto controlList = controlsFromArray(controls); - qir::Runtime::getInstance().apply(op, args.parameters, controlList, + qir::Runtime::getInstance().apply(gate, args.parameters, controlList, args.targets); } } @@ -269,148 +269,156 @@ void __quantum__rt__qubit_release(Qubit* qubit) { // QUANTUM INSTRUCTION SET #define MQT_QIR_DEFINE_1_0(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(Qubit* target) { \ - qir::Runtime::getInstance().apply(target); \ + qir::Runtime::getInstance().apply(target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target) { \ - qir::Runtime::getInstance().apply(control, target); \ + qir::Runtime::getInstance().apply(control, target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ Qubit* target) { \ - qir::Runtime::getInstance().apply(control0, control1, target); \ + qir::Runtime::getInstance().apply(control0, control1, \ + target); \ } #define MQT_QIR_DEFINE_1_1(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, target); \ + qir::Runtime::getInstance().apply(p0, target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, Qubit* control, \ Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, control, target); \ + qir::Runtime::getInstance().apply(p0, control, target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, Qubit* control0, \ Qubit* control1, Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, control0, control1, target); \ + qir::Runtime::getInstance().apply(p0, control0, \ + control1, target); \ } #define MQT_QIR_DEFINE_1_2(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, \ Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, target); \ + qir::Runtime::getInstance().apply(p0, p1, target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, double p1, \ Qubit* control, Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, control, target); \ + qir::Runtime::getInstance().apply(p0, p1, control, \ + target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX( \ double p0, double p1, Qubit* control0, Qubit* control1, Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, control0, control1, \ - target); \ + qir::Runtime::getInstance().apply(p0, p1, control0, \ + control1, target); \ } #define MQT_QIR_DEFINE_1_3(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, double p2, \ Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, p2, target); \ + qir::Runtime::getInstance().apply(p0, p1, p2, target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, double p1, double p2, \ Qubit* control, Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, p2, control, target); \ + qir::Runtime::getInstance().apply(p0, p1, p2, control, \ + target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, double p1, double p2, \ Qubit* control0, Qubit* control1, \ Qubit* target) { \ - qir::Runtime::getInstance().apply(p0, p1, p2, control0, control1, \ - target); \ + qir::Runtime::getInstance().apply(p0, p1, p2, control0, \ + control1, target); \ } #define MQT_QIR_DEFINE_2_0(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(target0, target1); \ + qir::Runtime::getInstance().apply(target0, target1); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target0, \ Qubit* target1) { \ - qir::Runtime::getInstance().apply(control, target0, target1); \ + qir::Runtime::getInstance().apply(control, target0, \ + target1); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(control0, control1, target0, \ - target1); \ + qir::Runtime::getInstance().apply(control0, control1, \ + target0, target1); \ } #define MQT_QIR_DEFINE_2_1(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, Qubit* target0, \ Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, target0, target1); \ + qir::Runtime::getInstance().apply(p0, target0, target1); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, Qubit* control, \ Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, control, target0, target1); \ + qir::Runtime::getInstance().apply(p0, control, target0, \ + target1); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, Qubit* control0, \ Qubit* control1, Qubit* target0, \ Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, control0, control1, target0, \ - target1); \ + qir::Runtime::getInstance().apply( \ + p0, control0, control1, target0, target1); \ } #define MQT_QIR_DEFINE_2_2(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, \ Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, p1, target0, target1); \ + qir::Runtime::getInstance().apply(p0, p1, target0, \ + target1); \ } \ void __quantum__qis__c##NAME##__##SUFFIX( \ double p0, double p1, Qubit* control, Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, p1, control, target0, \ - target1); \ + qir::Runtime::getInstance().apply(p0, p1, control, \ + target0, target1); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, double p1, \ Qubit* control0, Qubit* control1, \ Qubit* target0, Qubit* target1) { \ - qir::Runtime::getInstance().apply(p0, p1, control0, control1, \ - target0, target1); \ + qir::Runtime::getInstance().apply( \ + p0, p1, control0, control1, target0, target1); \ } #define MQT_QIR_DEFINE_3_0(NAME, OP, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(Qubit* target0, Qubit* target1, \ Qubit* target2) { \ - qir::Runtime::getInstance().apply(target0, target1, target2); \ + qir::Runtime::getInstance().apply(target0, target1, \ + target2); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target0, \ Qubit* target1, Qubit* target2) { \ - qir::Runtime::getInstance().apply(control, target0, target1, \ - target2); \ + qir::Runtime::getInstance().apply(control, target0, \ + target1, target2); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ Qubit* target0, Qubit* target1, \ Qubit* target2) { \ - qir::Runtime::getInstance().apply(control0, control1, target0, \ - target1, target2); \ + qir::Runtime::getInstance().apply( \ + control0, control1, target0, target1, target2); \ } #define MQT_QIR_DEFINE_CTL_1_0(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ Qubit* target) { \ - applyControlled(qc::OP, controls, target, {}); \ + applyControlled(dd::GateType::OP, controls, target, {}); \ } #define MQT_QIR_DEFINE_CTL_1_1(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<1, 1>(qc::OP, controls, args); \ + applyControlledTuple<1, 1>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_1_2(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<2, 1>(qc::OP, controls, args); \ + applyControlledTuple<2, 1>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_1_3(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<3, 1>(qc::OP, controls, args); \ + applyControlledTuple<3, 1>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_0(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<0, 2>(qc::OP, controls, args); \ + applyControlledTuple<0, 2>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_1(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<1, 2>(qc::OP, controls, args); \ + applyControlledTuple<1, 2>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_2(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<2, 2>(qc::OP, controls, args); \ + applyControlledTuple<2, 2>(dd::GateType::OP, controls, args); \ } #define MQT_QIR_DEFINE_CTL_3_0(NAME, OP, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ - applyControlledTuple<0, 3>(qc::OP, controls, args); \ + applyControlledTuple<0, 3>(dd::GateType::OP, controls, args); \ } #define MQT_GATE(KEY, NAME, OP, GETTER, TARGETS, PARAMS, SUFFIX, CTL_SUFFIX) \ diff --git a/mlir/lib/Dialect/QIR/Execution/Runtime/Runtime.cpp b/mlir/lib/Dialect/QIR/Execution/Runtime/Runtime.cpp index 4891214122..5699539786 100644 --- a/mlir/lib/Dialect/QIR/Execution/Runtime/Runtime.cpp +++ b/mlir/lib/Dialect/QIR/Execution/Runtime/Runtime.cpp @@ -15,10 +15,6 @@ #include "dd/Operations.hpp" #include "dd/Package.hpp" #include "dd/StateGeneration.hpp" -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" #include "mlir/Dialect/QIR/Execution/Runtime/QIR.h" #include "mlir/Dialect/QIR/QIRDefinitions.h" @@ -89,17 +85,21 @@ Runtime::Runtime(const uint64_t randomSeed) : qubitMode(ResourceMode::UNKNOWN), resultMode(ResourceMode::UNKNOWN), currentMaxQubitAddress(MIN_DYN_QUBIT_ADDRESS), currentMaxQubitId(0), currentMaxResultAddress(MIN_DYN_RESULT_ADDRESS), mt(randomSeed) { - qRegister = std::unordered_map(); + qRegister = std::unordered_map(); rRegister = std::unordered_map(); } -auto Runtime::enlargeState(const std::uint64_t maxQubit) -> void { +auto Runtime::enlargeState(const size_t maxQubit) -> void { + if (maxQubit >= dd::Package::MAX_POSSIBLE_QUBITS) { + throw std::out_of_range("QIR qubit ID exceeds the supported qubit range"); + } if (maxQubit >= qState.numQubits) { const auto d = maxQubit - qState.numQubits + 1; qubitPermutation.resize(qState.numQubits + d); - std::iota(qubitPermutation.begin() + qState.numQubits, - qubitPermutation.end(), qState.numQubits); - qState.numQubits += static_cast(d); + std::iota(qubitPermutation.begin() + + static_cast(qState.numQubits), + qubitPermutation.end(), static_cast(qState.numQubits)); + qState.numQubits += d; // Resize the DD package only if necessary. if (qState.dd->qubits() < qState.numQubits) { @@ -115,10 +115,12 @@ auto Runtime::enlargeState(const std::uint64_t maxQubit) -> void { // Enlarge state. // Each iteration adds one level above the current root, raising root.v by // one. After the loop, root.v == numQubits - 1. - for (auto q = qState.edge.p->v; q + 1 < qState.numQubits; ++q) { + for (auto q = static_cast(qState.edge.p->v); + q + 1U < qState.numQubits; ++q) { auto old = qState.edge; - qState.edge = qState.dd->makeDDNode( - q + 1U, std::array{qState.edge, dd::vEdge::zero()}); + qState.edge = + qState.dd->makeDDNode(static_cast(q + 1U), + std::array{qState.edge, dd::vEdge::zero()}); qState.dd->incRef(qState.edge); qState.dd->decRef(old); } @@ -126,8 +128,8 @@ auto Runtime::enlargeState(const std::uint64_t maxQubit) -> void { } auto Runtime::translateAddresses(const std::span qubits) - -> std::vector { - std::vector qubitIds(qubits.size()); + -> std::vector { + std::vector qubitIds(qubits.size()); if (qubitMode != ResourceMode::STATIC) { try { std::ranges::transform(qubits, qubitIds.begin(), [&](const auto* q) { @@ -148,9 +150,8 @@ auto Runtime::translateAddresses(const std::span qubits) } } if (qubitMode == ResourceMode::STATIC) { - std::ranges::transform(qubits, qubitIds.begin(), [](const auto* q) { - return static_cast(reinterpret_cast(q)); - }); + std::ranges::transform(qubits, qubitIds.begin(), + [](const auto* q) { return staticQubitId(q); }); } if (!qubitIds.empty()) { enlargeState(*std::ranges::max_element(qubitIds)); @@ -158,7 +159,8 @@ auto Runtime::translateAddresses(const std::span qubits) return qubitIds; } -auto Runtime::apply(const qc::OpType op, const std::span params, +auto Runtime::apply(const dd::GateType gate, + const std::span params, const std::span controls, const std::span targets) -> void { std::vector qubits; @@ -170,19 +172,20 @@ auto Runtime::apply(const qc::OpType op, const std::span params, return qubitPermutation[address]; }); - if (op == qc::SWAP && controls.empty() && targets.size() == 2) { + if (gate == dd::GateType::SWAP && controls.empty() && targets.size() == 2) { swap(targets[0], targets[1]); return; } const auto controlEnd = addresses.cbegin() + static_cast(controls.size()); - const qc::Controls mappedControls(addresses.cbegin(), controlEnd); - const qc::Targets mappedTargets(controlEnd, addresses.cend()); - const qc::StandardOperation operation( - mappedControls, mappedTargets, op, - std::vector(params.begin(), params.end())); - qState.edge = applyUnitaryOperation(operation, qState.edge, *qState.dd); + const dd::Controls mappedControls(addresses.cbegin(), controlEnd); + const dd::Targets mappedTargets(controlEnd, addresses.cend()); + qState.edge = qState.dd->applyOperation( + dd::getGateDD(*qState.dd, gate, + std::vector(params.begin(), params.end()), + mappedControls, mappedTargets), + qState.edge); } // NOLINTNEXTLINE(bugprone-exception-escape) @@ -198,8 +201,11 @@ auto Runtime::qAlloc() -> Qubit* { "Cannot dynamically allocate qubits after using static qubit IDs"); } qubitMode = ResourceMode::DYNAMIC; + if (currentMaxQubitId >= dd::Package::MAX_POSSIBLE_QUBITS) { + throw std::out_of_range("QIR runtime exceeds the supported qubit range"); + } auto* qubit = reinterpret_cast(currentMaxQubitAddress++); - qRegister.emplace(qubit, currentMaxQubitId++); + qRegister.emplace(qubit, static_cast(currentMaxQubitId++)); return qubit; } diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp index 521d250a92..d19ec7ab5d 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -10,14 +10,7 @@ #include "ExactUnitaryTest.h" #include "TestCaseUtils.h" -#include "dd/DDDefinitions.hpp" -#include "dd/FunctionalityConstruction.hpp" #include "dd/GateMatrixDefinitions.hpp" -#include "dd/Package.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" #include "mlir/Dialect/MQT/Utils/GatePowering.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" @@ -75,14 +68,10 @@ matrix4FromDefinition(const Definition& definition) { definition[3][0], definition[3][1], definition[3][2], definition[3][3]); } -template -[[nodiscard]] static Matrix4x4 -expectedMatrixFromComputation(const Fn& build, const size_t numQubits = 2) { - qc::QuantumComputation comp; - build(comp); - const auto package = std::make_unique(numQubits); - return matrix4FromDefinition( - dd::buildFunctionality(comp, *package).getMatrix(numQubits)); +[[nodiscard]] static DynamicMatrix controlledMatrix(const Matrix2x2& body) { + DynamicMatrix result = DynamicMatrix::identity(4); + result.setBottomRightCorner(body); + return result; } [[nodiscard]] static InvOp firstInvOp(ModuleOp module) { @@ -398,11 +387,7 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); - const Matrix4x4 expected = - expectedMatrixFromComputation([](qc::QuantumComputation& comp) { - comp.addQubitRegister(2, "q"); - comp.cx(1, 0); - }); + const auto expected = controlledMatrix(XOp::getUnitaryMatrix()); ASSERT_TRUE(matrix->isApprox(expected)); } @@ -414,11 +399,7 @@ TEST_F(QCOMatrixTest, ControlledHOpMatrix) { const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); - const Matrix4x4 expected = - expectedMatrixFromComputation([](qc::QuantumComputation& comp) { - comp.addQubitRegister(2, "q"); - comp.ch(1, 0); - }); + const auto expected = controlledMatrix(HOp::getUnitaryMatrix()); ASSERT_TRUE(matrix->isApprox(expected)); } @@ -430,12 +411,8 @@ TEST_F(QCOMatrixTest, ControlledXHOpMatrix) { const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); - const Matrix4x4 expected = - expectedMatrixFromComputation([](qc::QuantumComputation& comp) { - comp.addQubitRegister(2, "q"); - comp.cx(1, 0); - comp.ch(1, 0); - }); + const auto expected = + controlledMatrix(HOp::getUnitaryMatrix() * XOp::getUnitaryMatrix()); ASSERT_TRUE(matrix->isApprox(expected)); } @@ -447,15 +424,8 @@ TEST_F(QCOMatrixTest, ControlledInverseHTOpMatrix) { const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); - const Matrix4x4 expected = - expectedMatrixFromComputation([](qc::QuantumComputation& comp) { - comp.addQubitRegister(2, "q"); - qc::CompoundOperation body; - body.emplace_back(1, 0, qc::OpType::H); - body.emplace_back(1, 0, qc::OpType::T); - body.invert(); - comp.push_back(body); - }); + const auto expected = controlledMatrix( + (TOp::getUnitaryMatrix() * HOp::getUnitaryMatrix()).adjoint()); ASSERT_TRUE(matrix->isApprox(expected)); } @@ -900,11 +870,7 @@ TEST_F(QCOMatrixTest, InverseIswapOpMatrix) { const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); - const Matrix4x4 expected = - expectedMatrixFromComputation([](qc::QuantumComputation& comp) { - comp.addQubitRegister(2, "q"); - comp.iswapdg(0, 1); - }); + const auto expected = iSWAPOp::getUnitaryMatrix().adjoint(); ASSERT_TRUE(matrix->isApprox(expected)); } @@ -1019,7 +985,7 @@ TEST_F(QCOMatrixTest, DCXOpMatrix) { const auto matrix = DCXOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::DCX); + const auto definition = dd::opToTwoQubitGateMatrix(dd::GateType::DCX); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1034,7 +1000,7 @@ TEST_F(QCOMatrixTest, ECROpMatrix) { const auto matrix = ECROp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::ECR); + const auto definition = dd::opToTwoQubitGateMatrix(dd::GateType::ECR); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1069,7 +1035,7 @@ TEST_F(QCOMatrixTest, HOpMatrix) { const auto matrix = HOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::H); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::H); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1084,7 +1050,7 @@ TEST_F(QCOMatrixTest, IdOpMatrix) { const auto matrix = IdOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::I); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::I); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1099,7 +1065,7 @@ TEST_F(QCOMatrixTest, iSWAPOpMatrix) { const auto matrix = iSWAPOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::iSWAP); + const auto definition = dd::opToTwoQubitGateMatrix(dd::GateType::iSWAP); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1119,7 +1085,8 @@ TEST_F(QCOMatrixTest, POpMatrix) { const auto matrix = *pOp.getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::P, {0.123}); + const auto definition = + dd::opToSingleQubitGateMatrix(dd::GateType::P, {0.123}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1134,7 +1101,7 @@ TEST_F(QCOMatrixTest, RCCXOpMatrix) { const auto matrix = RCCXOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToThreeQubitGateMatrix(qc::OpType::RCCX); + const auto definition = dd::opToThreeQubitGateMatrix(dd::GateType::RCCX); DynamicMatrix expected(static_cast(dd::THREE_QUBIT_GATE_DIM)); for (std::size_t row = 0; row < dd::THREE_QUBIT_GATE_DIM; ++row) { @@ -1161,7 +1128,7 @@ TEST_F(QCOMatrixTest, ROpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::R, {0.123, 0.456}); + dd::opToSingleQubitGateMatrix(dd::GateType::R, {0.123, 0.456}); const Matrix2x2 expected = matrix2FromFlat(definition); ASSERT_TRUE(matrix.isApprox(expected)); @@ -1181,7 +1148,7 @@ TEST_F(QCOMatrixTest, RXOpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::RX, {0.123}); + dd::opToSingleQubitGateMatrix(dd::GateType::RX, {0.123}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1201,7 +1168,8 @@ TEST_F(QCOMatrixTest, RXXOpMatrix) { const auto matrix = *rxxOp.getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::RXX, {0.123}); + const auto definition = + dd::opToTwoQubitGateMatrix(dd::GateType::RXX, {0.123}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1222,7 +1190,7 @@ TEST_F(QCOMatrixTest, RYOpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::RY, {0.456}); + dd::opToSingleQubitGateMatrix(dd::GateType::RY, {0.456}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1242,7 +1210,8 @@ TEST_F(QCOMatrixTest, RYYOpMatrix) { const auto matrix = *ryyOp.getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::RYY, {0.123}); + const auto definition = + dd::opToTwoQubitGateMatrix(dd::GateType::RYY, {0.123}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1263,7 +1232,7 @@ TEST_F(QCOMatrixTest, RZOpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::RZ, {0.789}); + dd::opToSingleQubitGateMatrix(dd::GateType::RZ, {0.789}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1283,7 +1252,8 @@ TEST_F(QCOMatrixTest, RZXOpMatrix) { const auto matrix = *rzxOp.getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::RZX, {0.123}); + const auto definition = + dd::opToTwoQubitGateMatrix(dd::GateType::RZX, {0.123}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1303,7 +1273,8 @@ TEST_F(QCOMatrixTest, RZZOpMatrix) { const auto matrix = *rzzOp.getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::RZZ, {0.123}); + const auto definition = + dd::opToTwoQubitGateMatrix(dd::GateType::RZZ, {0.123}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1318,7 +1289,7 @@ TEST_F(QCOMatrixTest, SOpMatrix) { const auto matrix = SOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::S); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::S); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1333,7 +1304,7 @@ TEST_F(QCOMatrixTest, SdgOpMatrix) { const auto matrix = SdgOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::Sdg); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::Sdg); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1348,7 +1319,7 @@ TEST_F(QCOMatrixTest, SWAPOpMatrix) { const auto matrix = SWAPOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::SWAP); + const auto definition = dd::opToTwoQubitGateMatrix(dd::GateType::SWAP); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1363,7 +1334,7 @@ TEST_F(QCOMatrixTest, SXOpMatrix) { const auto matrix = SXOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::SX); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::SX); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1378,7 +1349,7 @@ TEST_F(QCOMatrixTest, SXdgOpMatrix) { const auto matrix = SXdgOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::SXdg); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::SXdg); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1393,7 +1364,7 @@ TEST_F(QCOMatrixTest, TOpMatrix) { const auto matrix = TOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::T); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::T); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1408,7 +1379,7 @@ TEST_F(QCOMatrixTest, TdgOpMatrix) { const auto matrix = TdgOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::Tdg); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::Tdg); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1429,7 +1400,7 @@ TEST_F(QCOMatrixTest, U2OpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::U2, {0.234, 0.567}); + dd::opToSingleQubitGateMatrix(dd::GateType::U2, {0.234, 0.567}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1450,7 +1421,7 @@ TEST_F(QCOMatrixTest, UOpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToSingleQubitGateMatrix(qc::OpType::U, {0.1, 0.2, 0.3}); + dd::opToSingleQubitGateMatrix(dd::GateType::U, {0.1, 0.2, 0.3}); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1465,7 +1436,7 @@ TEST_F(QCOMatrixTest, XOpMatrix) { const auto matrix = XOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::X); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::X); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1486,7 +1457,7 @@ TEST_F(QCOMatrixTest, XXMinusYYOpMatrix) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToTwoQubitGateMatrix(qc::OpType::XXminusYY, {0.123, 0.456}); + dd::opToTwoQubitGateMatrix(dd::GateType::XXminusYY, {0.123, 0.456}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1507,7 +1478,7 @@ TEST_F(QCOMatrixTest, XXPlusYYOp) { // Get the definition of the matrix from the DD library const auto definition = - dd::opToTwoQubitGateMatrix(qc::OpType::XXplusYY, {0.123, 0.456}); + dd::opToTwoQubitGateMatrix(dd::GateType::XXplusYY, {0.123, 0.456}); const Matrix4x4 expected = matrix4FromDefinition(definition); @@ -1522,7 +1493,7 @@ TEST_F(QCOMatrixTest, YOpMatrix) { const auto matrix = YOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::Y); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::Y); const Matrix2x2 expected = matrix2FromFlat(definition); @@ -1537,7 +1508,7 @@ TEST_F(QCOMatrixTest, ZOpMatrix) { const auto matrix = ZOp::getUnitaryMatrix(); // Get the definition of the matrix from the DD library - const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::Z); + const auto definition = dd::opToSingleQubitGateMatrix(dd::GateType::Z); const Matrix2x2 expected = matrix2FromFlat(definition); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_multi_controlled_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_multi_controlled_decomposition.cpp index 5a4c11d15a..f4f83d6d57 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_multi_controlled_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_multi_controlled_decomposition.cpp @@ -8,14 +8,10 @@ * Licensed under the MIT License */ -#include "dd/FunctionalityConstruction.hpp" +#include "dd/GateMatrixDefinitions.hpp" #include "dd/Package.hpp" #include "dd/RealNumber.hpp" -#include "dd/Simulation.hpp" #include "dd/StateGeneration.hpp" -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" #include "mlir/Dialect/MQT/Utils/Modifiers.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" @@ -121,8 +117,30 @@ static constexpr std::array K_EXPECTED_MCX_CX = { } namespace { - enum class ControlledPauli : uint8_t { X, Z }; +} // namespace + +[[nodiscard]] static dd::Controls makeControls(size_t numControls) { + dd::Controls controls; + for (size_t i = 0; i < numControls; ++i) { + controls.emplace(static_cast(i)); + } + return controls; +} + +[[nodiscard]] static dd::GateMatrix pauliMatrix(ControlledPauli pauli) { + return dd::opToSingleQubitGateMatrix( + pauli == ControlledPauli::X ? dd::GateType::X : dd::GateType::Z); +} + +[[nodiscard]] static dd::MatrixDD +makeControlledGateDD(dd::Package& package, size_t numControls, + const dd::GateMatrix& matrix) { + return package.makeGateDD(matrix, makeControls(numControls), + static_cast(numControls)); +} + +namespace { class MultiControlledDecompositionTest : public testing::Test { protected: @@ -237,22 +255,10 @@ static void expectImplementsControlledPauli(func::FuncOp funcOp, const auto decomposedDD = buildFunctionality(funcOp, *dd); ASSERT_TRUE(succeeded(decomposedDD)); - qc::QuantumComputation referenceQc(numQubits); - qc::Controls controls; - for (size_t i = 0; i < numControls; ++i) { - controls.emplace(static_cast(i)); - } - const auto target = static_cast(numControls); - if (pauli == ControlledPauli::X) { - referenceQc.mcx(controls, target); - } else { - referenceQc.mcz(controls, target); - } - - const dd::MatrixDD referenceDD = dd::buildFunctionality(referenceQc, *dd); + const auto referenceDD = + makeControlledGateDD(*dd, numControls, pauliMatrix(pauli)); EXPECT_EQ(*decomposedDD, referenceDD); dd->decRef(*decomposedDD); - dd->decRef(referenceDD); } static void expectMatchesReferenceOnBasisStates(func::FuncOp funcOp, @@ -262,18 +268,6 @@ static void expectMatchesReferenceOnBasisStates(func::FuncOp funcOp, ASSERT_EQ(numQubits, numControls + 1); expectFullyDecomposed(funcOp); - qc::QuantumComputation referenceQc(numQubits); - qc::Controls controls; - for (size_t i = 0; i < numControls; ++i) { - controls.emplace(static_cast(i)); - } - const auto target = static_cast(numControls); - if (pauli == ControlledPauli::X) { - referenceQc.mcx(controls, target); - } else { - referenceQc.mcz(controls, target); - } - std::vector> basisStates; basisStates.emplace_back(numQubits, true); basisStates.back()[numControls] = false; @@ -286,14 +280,16 @@ static void expectMatchesReferenceOnBasisStates(func::FuncOp funcOp, } const auto dd = std::make_unique(numQubits); + const auto referenceGate = + makeControlledGateDD(*dd, numControls, pauliMatrix(pauli)); + dd->incRef(referenceGate); std::mt19937_64 rng(0); for (const auto& basisState : basisStates) { const auto decomposedOutput = simulate( funcOp, dd::makeBasisState(numQubits, basisState, *dd), *dd, rng); ASSERT_TRUE(succeeded(decomposedOutput)); - dd->incRef(*decomposedOutput); - const auto referenceOutput = dd::simulate( - referenceQc, dd::makeBasisState(numQubits, basisState, *dd), *dd); + const auto referenceOutput = dd->applyOperation( + referenceGate, dd::makeBasisState(numQubits, basisState, *dd)); EXPECT_EQ(decomposedOutput->p, referenceOutput.p); EXPECT_NEAR(dd::RealNumber::val(decomposedOutput->w.r), dd::RealNumber::val(referenceOutput.w.r), 1e-11); @@ -302,30 +298,24 @@ static void expectMatchesReferenceOnBasisStates(func::FuncOp funcOp, dd->decRef(*decomposedOutput); dd->decRef(referenceOutput); } + dd->decRef(referenceGate); } [[nodiscard]] static dd::VectorDD makeCoherentControlInput(size_t numControls, bool targetOne, dd::Package& dd) { const auto numQubits = numControls + 1; const auto coherentControl = numControls / 2; - qc::QuantumComputation preparationQc(numQubits); - for (size_t control = 0; control < numControls; ++control) { - if (control == coherentControl) { - preparationQc.h(static_cast(control)); - } else { - preparationQc.x(static_cast(control)); - } - } - if (targetOne) { - preparationQc.x(static_cast(numControls)); - } - return dd::simulate(preparationQc, dd::makeZeroState(numQubits, dd), dd); + std::vector basisState(numQubits, dd::BasisStates::one); + basisState[coherentControl] = dd::BasisStates::plus; + basisState[numControls] = + targetOne ? dd::BasisStates::one : dd::BasisStates::zero; + return dd::makeBasisState(numQubits, basisState, dd); } static void -expectMatchesReferenceOnCoherentState(func::FuncOp funcOp, - const qc::QuantumComputation& referenceQc, - size_t numControls, bool targetOne) { +expectMatchesReferenceOnCoherentState(func::FuncOp funcOp, size_t numControls, + bool targetOne, + const dd::GateMatrix& referenceMatrix) { const auto numQubits = countStaticQubits(funcOp); ASSERT_EQ(numQubits, numControls + 1); expectFullyDecomposed(funcOp); @@ -335,9 +325,9 @@ expectMatchesReferenceOnCoherentState(func::FuncOp funcOp, const auto decomposedOutput = simulate( funcOp, makeCoherentControlInput(numControls, targetOne, *dd), *dd, rng); ASSERT_TRUE(succeeded(decomposedOutput)); - dd->incRef(*decomposedOutput); - const auto referenceOutput = dd::simulate( - referenceQc, makeCoherentControlInput(numControls, targetOne, *dd), *dd); + const auto referenceOutput = dd->applyOperation( + makeControlledGateDD(*dd, numControls, referenceMatrix), + makeCoherentControlInput(numControls, targetOne, *dd)); EXPECT_EQ(decomposedOutput->p, referenceOutput.p); EXPECT_NEAR(dd::RealNumber::val(decomposedOutput->w.r), @@ -352,31 +342,15 @@ expectMatchesReferenceOnCoherentState(func::FuncOp funcOp, static void expectMatchesControlledPauliOnCoherentState(func::FuncOp funcOp, size_t numControls, ControlledPauli pauli) { - const auto numQubits = numControls + 1; - qc::QuantumComputation referenceQc(numQubits); - qc::Controls controls; - for (size_t i = 0; i < numControls; ++i) { - controls.emplace(static_cast(i)); - } - const auto target = static_cast(numControls); - if (pauli == ControlledPauli::X) { - referenceQc.mcx(controls, target); - } else { - referenceQc.mcz(controls, target); - } - expectMatchesReferenceOnCoherentState(funcOp, referenceQc, numControls, - pauli == ControlledPauli::Z); + expectMatchesReferenceOnCoherentState( + funcOp, numControls, pauli == ControlledPauli::Z, pauliMatrix(pauli)); } static void expectMatchesMcpOnCoherentState(func::FuncOp funcOp, size_t numControls, double theta) { - qc::QuantumComputation referenceQc(numControls + 1); - qc::Controls controls; - for (size_t i = 0; i < numControls; ++i) { - controls.emplace(static_cast(i)); - } - referenceQc.mcp(theta, controls, static_cast(numControls)); - expectMatchesReferenceOnCoherentState(funcOp, referenceQc, numControls, true); + expectMatchesReferenceOnCoherentState( + funcOp, numControls, true, + dd::opToSingleQubitGateMatrix(dd::GateType::P, {theta})); } static void expectImplementsMcp(func::FuncOp funcOp, size_t numControls, @@ -389,17 +363,11 @@ static void expectImplementsMcp(func::FuncOp funcOp, size_t numControls, const auto decomposedDD = buildFunctionality(funcOp, *dd); ASSERT_TRUE(succeeded(decomposedDD)); - qc::QuantumComputation referenceQc(numQubits); - qc::Controls controls; - for (size_t i = 0; i < numControls; ++i) { - controls.emplace(static_cast(i)); - } - referenceQc.mcp(theta, controls, static_cast(numControls)); - - const dd::MatrixDD referenceDD = dd::buildFunctionality(referenceQc, *dd); + const auto referenceDD = makeControlledGateDD( + *dd, numControls, + dd::opToSingleQubitGateMatrix(dd::GateType::P, {theta})); EXPECT_EQ(*decomposedDD, referenceDD); dd->decRef(*decomposedDD); - dd->decRef(referenceDD); } /// Count `CtrlOp`s whose control operand count is at least @p minControlCount. @@ -694,12 +662,10 @@ TEST_F(MultiControlledDecompositionTest, DecomposesSingleControlledSwap) { const auto decomposedDD = buildFunctionality(funcOp, *dd); ASSERT_TRUE(succeeded(decomposedDD)); - qc::QuantumComputation referenceQc(numQubits); - referenceQc.cswap(0, 1, 2); - const dd::MatrixDD referenceDD = dd::buildFunctionality(referenceQc, *dd); + const auto referenceDD = dd->makeTwoQubitGateDD( + dd::opToTwoQubitGateMatrix(dd::GateType::SWAP), dd::Control{0}, 1, 2); EXPECT_EQ(*decomposedDD, referenceDD); dd->decRef(*decomposedDD); - dd->decRef(referenceDD); } TEST_F(MultiControlledDecompositionTest, DecomposesMultipleControlledSwap) { @@ -723,12 +689,11 @@ TEST_F(MultiControlledDecompositionTest, DecomposesMultipleControlledSwap) { const auto decomposedDD = buildFunctionality(funcOp, *dd); ASSERT_TRUE(succeeded(decomposedDD)); - qc::QuantumComputation referenceQc(numQubits); - referenceQc.mcswap({0, 1}, 2, 3); - const dd::MatrixDD referenceDD = dd::buildFunctionality(referenceQc, *dd); + const auto referenceDD = + dd->makeTwoQubitGateDD(dd::opToTwoQubitGateMatrix(dd::GateType::SWAP), + dd::Controls{{0}, {1}}, 2, 3); EXPECT_EQ(*decomposedDD, referenceDD); dd->decRef(*decomposedDD); - dd->decRef(referenceDD); } TEST_F(MultiControlledDecompositionTest, diff --git a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp index 4f51b7d4f5..0e77f52a63 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp @@ -8,14 +8,12 @@ * Licensed under the MIT License */ -#include "dd/FunctionalityConstruction.hpp" +#include "dd/DDDefinitions.hpp" #include "dd/GateMatrixDefinitions.hpp" #include "dd/Node.hpp" +#include "dd/Operations.hpp" #include "dd/Package.hpp" -#include "dd/Simulation.hpp" #include "dd/StateGeneration.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/OpType.hpp" #include "mlir/Dialect/CBit/IR/CBitAttributes.h" #include "mlir/Dialect/CBit/IR/CBitDialect.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" @@ -45,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +59,13 @@ using namespace qco; namespace { +struct ReferenceGate { + dd::GateType type; + dd::Targets targets; + std::vector params; + dd::Controls controls; +}; + class QCODDFunctionalityTest : public testing::Test { protected: std::unique_ptr context; @@ -89,27 +95,33 @@ class QCODDFunctionalityTest : public testing::Test { std::forward(buildFn)); } - /// Compare `mlir::qco::{buildFunctionality,simulate}` to - /// `dd::{buildFunctionality,simulate}` on an equivalent circuit. - void expectEqualToQc(func::FuncOp func, const qc::QuantumComputation& qc) { - const auto numQubits = qc.getNqubits(); + static void + expectEqualToReference(func::FuncOp func, const size_t numQubits, + const std::initializer_list gates) { auto dd = std::make_unique(numQubits); - const auto fromQcFn = dd::buildFunctionality(qc, *dd); + auto referenceFn = dd::MatrixDD::one(); + auto referenceSim = dd::makeZeroState(numQubits, *dd); + for (const auto& gate : gates) { + const auto operation = dd::getGateDD(*dd, gate.type, gate.params, + gate.controls, gate.targets); + referenceFn = dd->applyOperation(operation, referenceFn); + referenceSim = dd->applyOperation(operation, referenceSim); + } + const auto fromQcoFn = buildFunctionality(func, *dd); ASSERT_TRUE(succeeded(fromQcoFn)); - EXPECT_TRUE(*fromQcoFn == fromQcFn); + EXPECT_TRUE(*fromQcoFn == referenceFn); dd->decRef(*fromQcoFn); - dd->decRef(fromQcFn); + dd->decRef(referenceFn); - const auto fromQcSim = - dd::simulate(qc, dd::makeZeroState(numQubits, *dd), *dd); + std::mt19937_64 rng(0); const auto fromQcoSim = simulate(func, dd::makeZeroState(numQubits, *dd), *dd, rng); ASSERT_TRUE(succeeded(fromQcoSim)); - EXPECT_EQ(fromQcoSim->getVector(), fromQcSim.getVector()); + EXPECT_EQ(fromQcoSim->getVector(), referenceSim.getVector()); dd->decRef(*fromQcoSim); - dd->decRef(fromQcSim); + dd->decRef(referenceSim); } void expectMlirFails(size_t numQubits, StringRef mlirCode) const { @@ -124,7 +136,7 @@ class QCODDFunctionalityTest : public testing::Test { auto expected = dd::makeZeroState(1, *dd); if (expectedOne) { expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), expected); } const auto out = simulate(func, dd::makeZeroState(1, *dd), *dd, rng); @@ -148,7 +160,7 @@ class QCODDFunctionalityTest : public testing::Test { } }; -TEST_F(QCODDFunctionalityTest, MatchesQuantumComputation) { +TEST_F(QCODDFunctionalityTest, ExercisesStandardGatePaths) { // Every `decodeStandardGate` branch once (distinct angles catch param-order // bugs), plus barrier / sparse ctrl / inv / sink. constexpr double theta = 0.31; @@ -203,40 +215,40 @@ TEST_F(QCODDFunctionalityTest, MatchesQuantumComputation) { }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(3); - qc.i(0); - qc.x(0); - qc.y(0); - qc.z(0); - qc.h(0); - qc.s(0); - qc.sdg(0); - qc.t(0); - qc.tdg(0); - qc.sx(0); - qc.sxdg(0); - qc.rx(theta, 0); - qc.ry(theta, 0); - qc.rz(theta, 0); - qc.p(theta, 0); - qc.r(theta, phi, 0); - qc.u2(phi, lambda, 0); - qc.u(theta, phi, lambda, 0); - qc.swap(0, 1); - qc.iswap(0, 1); - qc.dcx(0, 1); - qc.ecr(0, 1); - qc.rxx(theta, 0, 1); - qc.ryy(theta, 0, 1); - qc.rzz(theta, 0, 1); - qc.rzx(theta, 0, 1); - qc.xx_plus_yy(theta, beta, 0, 1); - qc.xx_minus_yy(theta, beta, 0, 1); - qc.cx(0, 1); - qc.cp(std::numbers::pi / 5.0, 1, 2); - qc.mcx({0, 1}, 2); - qc.sdg(2); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference( + mainFunc(*mod), 3, + {{dd::GateType::I, {0}}, + {dd::GateType::X, {0}}, + {dd::GateType::Y, {0}}, + {dd::GateType::Z, {0}}, + {dd::GateType::H, {0}}, + {dd::GateType::S, {0}}, + {dd::GateType::Sdg, {0}}, + {dd::GateType::T, {0}}, + {dd::GateType::Tdg, {0}}, + {dd::GateType::SX, {0}}, + {dd::GateType::SXdg, {0}}, + {dd::GateType::RX, {0}, {theta}}, + {dd::GateType::RY, {0}, {theta}}, + {dd::GateType::RZ, {0}, {theta}}, + {dd::GateType::P, {0}, {theta}}, + {dd::GateType::R, {0}, {theta, phi}}, + {dd::GateType::U2, {0}, {phi, lambda}}, + {dd::GateType::U, {0}, {theta, phi, lambda}}, + {dd::GateType::SWAP, {0, 1}}, + {dd::GateType::iSWAP, {0, 1}}, + {dd::GateType::DCX, {0, 1}}, + {dd::GateType::ECR, {0, 1}}, + {dd::GateType::RXX, {0, 1}, {theta}}, + {dd::GateType::RYY, {0, 1}, {theta}}, + {dd::GateType::RZZ, {0, 1}, {theta}}, + {dd::GateType::RZX, {0, 1}, {theta}}, + {dd::GateType::XXplusYY, {0, 1}, {theta, beta}}, + {dd::GateType::XXminusYY, {0, 1}, {theta, beta}}, + {dd::GateType::X, {1}, {}, {{0}}}, + {dd::GateType::P, {2}, {std::numbers::pi / 5.0}, {{1}}}, + {dd::GateType::X, {2}, {}, {{0}, {1}}}, + {dd::GateType::Sdg, {2}}}); } TEST_F(QCODDFunctionalityTest, Rccx) { @@ -260,10 +272,9 @@ TEST_F(QCODDFunctionalityTest, Rccx) { }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(4); - qc.rccx(2, 0, 3); - qc.crccx(1, 2, 0, 3); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 4, + {{dd::GateType::RCCX, {2, 0, 3}}, + {dd::GateType::RCCX, {2, 0, 3}, {}, {{1}}}}); } TEST_F(QCODDFunctionalityTest, DensePaths) { @@ -282,11 +293,10 @@ TEST_F(QCODDFunctionalityTest, DensePaths) { return b.intConstant(0); }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(3); - qc.x(1); - qc.ct(2, 0); - qc.ch(2, 0); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 3, + {{dd::GateType::X, {1}}, + {dd::GateType::T, {0}, {}, {{2}}}, + {dd::GateType::H, {0}, {}, {{2}}}}); } { auto mod = buildModule([](QCOProgramBuilder& b) { @@ -301,9 +311,7 @@ TEST_F(QCODDFunctionalityTest, DensePaths) { return b.intConstant(0); }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(2); - qc.swap(0, 1); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 2, {{dd::GateType::SWAP, {0, 1}}}); } { auto mod = buildModule([](QCOProgramBuilder& b) { @@ -319,11 +327,10 @@ TEST_F(QCODDFunctionalityTest, DensePaths) { return b.intConstant(0); }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(3); - qc.rx(-0.2, 0); - qc.ry(-0.3, 1); - qc.rz(-0.4, 2); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 3, + {{dd::GateType::RX, {0}, {-0.2}}, + {dd::GateType::RY, {1}, {-0.3}}, + {dd::GateType::RZ, {2}, {-0.4}}}); } { auto mod = buildModule([](QCOProgramBuilder& b) { @@ -341,11 +348,10 @@ TEST_F(QCODDFunctionalityTest, DensePaths) { return b.intConstant(0); }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(4); - qc.rx(-0.2, 0); - qc.ry(-0.3, 1); - qc.rz(-0.4, 2); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 4, + {{dd::GateType::RX, {0}, {-0.2}}, + {dd::GateType::RY, {1}, {-0.3}}, + {dd::GateType::RZ, {2}, {-0.4}}}); } { // Four-qubit dense `inv` on a non-contiguous wire subset (idle q3). @@ -368,12 +374,11 @@ TEST_F(QCODDFunctionalityTest, DensePaths) { return b.intConstant(0); }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(5); - qc.rx(-0.2, 0); - qc.ry(-0.3, 1); - qc.rz(-0.4, 2); - qc.h(4); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 5, + {{dd::GateType::RX, {0}, {-0.2}}, + {dd::GateType::RY, {1}, {-0.3}}, + {dd::GateType::RZ, {2}, {-0.4}}, + {dd::GateType::H, {4}}}); } } @@ -456,9 +461,7 @@ TEST_F(QCODDFunctionalityTest, FuncArgs) { context.get()); ASSERT_TRUE(mod); - qc::QuantumComputation qc(1); - qc.h(0); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 1, {{dd::GateType::H, {0}}}); } TEST_F(QCODDFunctionalityTest, ReturnedQubitsMustPreserveWireOrder) { @@ -601,16 +604,16 @@ TEST_F(QCODDFunctionalityTest, auto dd = std::make_unique(3); auto input = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 1), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 1), dd::makeZeroState(2, *dd)); const auto output = simulate(mainFunc(*mod), input, *dd, rng); ASSERT_TRUE(succeeded(output)); auto expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 1), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 1), dd::makeZeroState(3, *dd)); expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 2), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 2), expected); EXPECT_EQ(output->getVector(), expected.getVector()); dd->decRef(*output); @@ -632,7 +635,7 @@ TEST_F(QCODDFunctionalityTest, SimulateMeasureCollapsesLikePackage) { std::mt19937_64 refRng(seed); auto ref = dd::makeZeroState(1, *dd); ref = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::H), 0), ref); + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::H), 0), ref); static_cast(dd->measureOneCollapsing(ref, 0, refRng)); const auto expected = ref.getVector(); @@ -685,12 +688,8 @@ TEST_F(QCODDFunctionalityTest, SimulateIfConstantBranches) { ASSERT_TRUE(thenMod); ASSERT_TRUE(elseMod); - qc::QuantumComputation thenQc(1); - thenQc.x(0); - expectEqualToQc(mainFunc(*thenMod), thenQc); - - const qc::QuantumComputation elseQc(1); - expectEqualToQc(mainFunc(*elseMod), elseQc); + expectEqualToReference(mainFunc(*thenMod), 1, {{dd::GateType::X, {0}}}); + expectEqualToReference(mainFunc(*elseMod), 1, {}); } TEST_F(QCODDFunctionalityTest, SimulateIndexSwitchBranches) { @@ -717,12 +716,8 @@ TEST_F(QCODDFunctionalityTest, SimulateIndexSwitchBranches) { ASSERT_TRUE(caseMod); ASSERT_TRUE(defaultMod); - qc::QuantumComputation caseQc(1); - caseQc.x(0); - expectEqualToQc(mainFunc(*caseMod), caseQc); - - const qc::QuantumComputation defaultQc(1); - expectEqualToQc(mainFunc(*defaultMod), defaultQc); + expectEqualToReference(mainFunc(*caseMod), 1, {{dd::GateType::X, {0}}}); + expectEqualToReference(mainFunc(*defaultMod), 1, {}); } TEST_F(QCODDFunctionalityTest, SimulateMeasureFeedsIf) { @@ -742,7 +737,7 @@ TEST_F(QCODDFunctionalityTest, SimulateMeasureFeedsIf) { auto dd = std::make_unique(1); std::mt19937_64 rng(99); auto one = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), dd::makeZeroState(1, *dd)); const auto out = simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd, rng); @@ -782,7 +777,7 @@ TEST_F(QCODDFunctionalityTest, SimulateCBitConditionAndMeasurementUpdate) { std::mt19937_64 rng(99); auto zero = dd::makeZeroState(1, *dd); auto one = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), dd::makeZeroState(1, *dd)); const auto zeroOut = @@ -915,10 +910,10 @@ TEST_F(QCODDFunctionalityTest, SimulateAndiOriShliClassical) { // Final computational basis: |1>|0>|1> after measures and case-1 X on q2. auto expected = dd::makeZeroState(3, *dd); expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), expected); expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 2), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 2), expected); const auto out = @@ -952,7 +947,7 @@ TEST_F(QCODDFunctionalityTest, AcceptsLargestValidShift) { simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd, rng); ASSERT_TRUE(succeeded(out)); auto expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), dd::makeZeroState(1, *dd)); EXPECT_EQ(out->getVector(), expected.getVector()); dd->decRef(*out); @@ -1154,12 +1149,11 @@ TEST_F(QCODDFunctionalityTest, EmbedsWideLocalMatrixWithoutRegisterLimit) { }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(13); - qc.rx(-0.2, 0); - qc.ry(-0.3, 4); - qc.rz(-0.4, 8); - qc.h(12); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 13, + {{dd::GateType::RX, {0}, {-0.2}}, + {dd::GateType::RY, {4}, {-0.3}}, + {dd::GateType::RZ, {8}, {-0.4}}, + {dd::GateType::H, {12}}}); } TEST_F(QCODDFunctionalityTest, RejectsUnsupportedOrUnboundClassicalOperations) { @@ -1283,7 +1277,7 @@ TEST_F(QCODDFunctionalityTest, BindsClassicalIfResults) { simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd, rng); ASSERT_TRUE(succeeded(out)); auto expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), dd::makeZeroState(1, *dd)); EXPECT_EQ(out->getVector(), expected.getVector()); dd->decRef(*out); @@ -1325,7 +1319,7 @@ TEST_F(QCODDFunctionalityTest, BindsClassicalIndexResults) { simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd, rng); ASSERT_TRUE(succeeded(out)); auto expected = dd->applyOperation( - dd->makeGateDD(dd::opToSingleQubitGateMatrix(qc::OpType::X), 0), + dd->makeGateDD(dd::opToSingleQubitGateMatrix(dd::GateType::X), 0), dd::makeZeroState(1, *dd)); EXPECT_EQ(out->getVector(), expected.getVector()); dd->decRef(*out); @@ -2352,12 +2346,11 @@ TEST_F(QCODDFunctionalityTest, BuildsThroughConcreteControlFlow) { }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(1); - qc.x(0); - qc.z(0); - qc.h(0); - qc.h(0); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 1, + {{dd::GateType::X, {0}}, + {dd::GateType::Z, {0}}, + {dd::GateType::H, {0}}, + {dd::GateType::H, {0}}}); } TEST_F(QCODDFunctionalityTest, StructuredScfAndWhileCarryValues) { @@ -2419,11 +2412,9 @@ TEST_F(QCODDFunctionalityTest, StructuredScfAndWhileCarryValues) { context.get()); ASSERT_TRUE(mod); - qc::QuantumComputation qc(1); - qc.x(0); - qc.z(0); - qc.x(0); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference( + mainFunc(*mod), 1, + {{dd::GateType::X, {0}}, {dd::GateType::Z, {0}}, {dd::GateType::X, {0}}}); } TEST_F(QCODDFunctionalityTest, DynamicAllocationsAndQTensorBookkeeping) { @@ -2897,9 +2888,7 @@ TEST_F(QCODDFunctionalityTest, InterpretsMinMaxAndCommonMathOperations) { }); ASSERT_TRUE(mod); - qc::QuantumComputation qc(1); - qc.x(0); - expectEqualToQc(mainFunc(*mod), qc); + expectEqualToReference(mainFunc(*mod), 1, {{dd::GateType::X, {0}}}); } TEST_F(QCODDFunctionalityTest, StatevectorSupportsTerminalMeasurements) { diff --git a/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp b/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp index dc813b6a8c..f6200290b7 100644 --- a/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp +++ b/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "ir/Definitions.hpp" +#include "dd/DDDefinitions.hpp" #include "mlir/Dialect/QIR/Execution/Runtime/QIR.h" #include "mlir/Dialect/QIR/Execution/Runtime/Runtime.h" @@ -91,6 +91,28 @@ TEST_F(QIRRuntimeTest, RejectsMixedStaticAndDynamicResourceManagement) { EXPECT_THROW(__quantum__rt__result_allocate(nullptr), std::logic_error); } +TEST_F(QIRRuntimeTest, RejectsStaticQubitBeyondDDRange) { + __quantum__rt__initialize(nullptr); + auto* qubit = reinterpret_cast(dd::Package::MAX_POSSIBLE_QUBITS); + EXPECT_THROW(__quantum__qis__x__body(qubit), std::out_of_range); + + __quantum__rt__initialize(nullptr); + constexpr std::array params{}; + std::array controls{}; + std::array targets{qubit}; + EXPECT_THROW( + Runtime::getInstance().apply(dd::GateType::X, params, controls, targets), + std::out_of_range); +} + +TEST_F(QIRRuntimeTest, RejectsDynamicQubitBeyondDDRange) { + Runtime runtime{0}; + for (size_t i = 0; i < dd::Package::MAX_POSSIBLE_QUBITS; ++i) { + static_cast(runtime.qAlloc()); + } + EXPECT_THROW(runtime.qAlloc(), std::out_of_range); +} + } // namespace // Any test that emits output relies on the runtime producing the spec-mandated @@ -235,66 +257,75 @@ TEST_F(QIRRuntimeTest, TdgGate) { } TEST_F(QIRRuntimeTest, GlobalPhase) { + auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - EXPECT_NO_THROW(__quantum__qis__gphase__body(qc::PI_2)); + __quantum__qis__i__body(q0); + __quantum__qis__gphase__body(dd::PI_2); + + const auto state = Runtime::getInstance().takeState(); + const auto vector = state.edge.getVector(); + ASSERT_EQ(vector.size(), 2); + EXPECT_NEAR(vector[0].real(), 0., 1e-12); + EXPECT_NEAR(vector[0].imag(), 1., 1e-12); + EXPECT_EQ(vector[1], 0.); } TEST_F(QIRRuntimeTest, PRXGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__prx__body(qc::PI_2, 0, q0); + __quantum__qis__prx__body(dd::PI_2, 0, q0); } TEST_F(QIRRuntimeTest, RXGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__rx__body(qc::PI_2, q0); + __quantum__qis__rx__body(dd::PI_2, q0); } TEST_F(QIRRuntimeTest, RYGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__ry__body(qc::PI_2, q0); + __quantum__qis__ry__body(dd::PI_2, q0); } TEST_F(QIRRuntimeTest, RZGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__rz__body(qc::PI_2, q0); + __quantum__qis__rz__body(dd::PI_2, q0); } TEST_F(QIRRuntimeTest, PGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__p__body(qc::PI_2, q0); + __quantum__qis__p__body(dd::PI_2, q0); } TEST_F(QIRRuntimeTest, RXXGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__rxx__body(qc::PI_2, q0, q1); + __quantum__qis__rxx__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, RYYGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__ryy__body(qc::PI_2, q0, q1); + __quantum__qis__ryy__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, RZZGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__rzz__body(qc::PI_2, q0, q1); + __quantum__qis__rzz__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, RZXGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__rzx__body(qc::PI_2, q0, q1); + __quantum__qis__rzx__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, ISwapGate) { @@ -322,26 +353,26 @@ TEST_F(QIRRuntimeTest, XXPlusYYGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__xx_plus_yy__body(qc::PI_2, qc::PI_4, q0, q1); + __quantum__qis__xx_plus_yy__body(dd::PI_2, dd::PI_4, q0, q1); } TEST_F(QIRRuntimeTest, XXMinusYYGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__xx_minus_yy__body(qc::PI_2, qc::PI_4, q0, q1); + __quantum__qis__xx_minus_yy__body(dd::PI_2, dd::PI_4, q0, q1); } TEST_F(QIRRuntimeTest, U3Gate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__u3__body(qc::PI_2, 0, qc::PI_4, q0); + __quantum__qis__u3__body(dd::PI_2, 0, dd::PI_4, q0); } TEST_F(QIRRuntimeTest, U2Gate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); - __quantum__qis__u2__body(qc::PI_2, 0, q0); + __quantum__qis__u2__body(dd::PI_2, 0, q0); } TEST_F(QIRRuntimeTest, CNotGate) { @@ -412,28 +443,28 @@ TEST_F(QIRRuntimeTest, CRZGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__crz__body(qc::PI_2, q0, q1); + __quantum__qis__crz__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, CRYGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__cry__body(qc::PI_2, q0, q1); + __quantum__qis__cry__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, CRXGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__crx__body(qc::PI_2, q0, q1); + __quantum__qis__crx__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, CPGate) { auto* q0 = reinterpret_cast(0UL); auto* q1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); - __quantum__qis__cp__body(qc::PI_2, q0, q1); + __quantum__qis__cp__body(dd::PI_2, q0, q1); } TEST_F(QIRRuntimeTest, CCXGate) { @@ -508,7 +539,7 @@ TEST_F(QIRRuntimeTest, GenericControlledRotationUsesArgumentTuple) { double angle; Qubit* target; }; - const Args args{.angle = qc::PI, .target = target}; + const Args args{.angle = dd::PI, .target = target}; auto* tuple = __quantum__rt__tuple_create(sizeof(Args)); std::memcpy(tuple, &args, sizeof(Args)); @@ -536,8 +567,12 @@ TEST_F(QIRRuntimeTest, MzGate) { TEST_F(QIRRuntimeTest, ResetGate) { auto* q0 = reinterpret_cast(0UL); + auto* r0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); + __quantum__qis__x__body(q0); __quantum__qis__reset__body(q0); + __quantum__qis__mz__body(q0, r0); + EXPECT_FALSE(__quantum__rt__read_result(r0)); } TEST_F(QIRRuntimeTest, Qir21BulkResourceManagement) { diff --git a/noxfile.py b/noxfile.py index 5c41704163..a556175c20 100755 --- a/noxfile.py +++ b/noxfile.py @@ -112,7 +112,7 @@ def cpp_lint(session: nox.Session) -> None: f"--files-changed-only={'false' if all_files else 'true'}", "--lines-changed-only=false", *(() if all_files else (f"--diff-base={diff_base}",)), - "--file-annotations=false", + "--file-annotations=true", "--jobs=0", "--verbosity=info", env={"GITHUB_OUTPUT": str(output)}, @@ -360,8 +360,6 @@ def stubs(session: nox.Session) -> None: "--output-dir", str(package_root), "--module", - "mqt.core.ir", - "--module", "mqt.core.bench", "--module", "mqt.core.dd", diff --git a/pyproject.toml b/pyproject.toml index a5af5dbfe6..705a4b1cc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -290,7 +290,7 @@ select = ["cp*-win_arm64", "cp315*"] before-test = "" test-groups = [] test-sources = [] -test-command = "python -c \"import mqt.core.dd, mqt.core.ir, mqt.core.mlir, mqt.core.qdmi\"" +test-command = "python -c \"import importlib.util; import mqt.core, mqt.core.dd, mqt.core.mlir, mqt.core.qdmi; assert not hasattr(mqt.core, 'load'); assert importlib.util.find_spec('mqt.core.ir') is None\"" [tool.cibuildwheel.linux] before-all = """ diff --git a/python/mqt/core/__init__.py b/python/mqt/core/__init__.py index 5f2febb262..66aa3022d8 100644 --- a/python/mqt/core/__init__.py +++ b/python/mqt/core/__init__.py @@ -30,6 +30,5 @@ def _dll_patch() -> None: from ._version import version as __version__ from ._version import version_tuple as version_info -from .load import load -__all__ = ["__version__", "load", "version_info"] +__all__ = ["__version__", "version_info"] diff --git a/python/mqt/core/dd.pyi b/python/mqt/core/dd.pyi index 6ee21c1dd2..e4f53b7116 100644 --- a/python/mqt/core/dd.pyi +++ b/python/mqt/core/dd.pyi @@ -16,8 +16,34 @@ from typing import Annotated import numpy as np from numpy.typing import NDArray -import mqt.core.ir -import mqt.core.ir.operations +class Control: + """A control qubit used by the raw matrix DD constructors. + + Args: + qubit: The index of the control qubit. + type_: Whether the control is positive or negative. + """ + + def __init__(self, qubit: int, type_: Control.Type = ...) -> None: ... + + class Type(enum.Enum): + """Enumeration of control polarities.""" + + Pos = 1 + + Neg = 0 + + @property + def qubit(self) -> int: + """The index of the control qubit.""" + + @property + def type_(self) -> Control.Type: + """Whether the control is positive or negative.""" + + def __eq__(self, arg: object, /) -> bool: ... + def __ne__(self, arg: object, /) -> bool: ... + def __hash__(self) -> int: ... class VectorDD: """A class representing a vector decision diagram (DD).""" @@ -379,91 +405,6 @@ class DDPackage: The resulting state is guaranteed to have its reference count increased. """ - def apply_unitary_operation( - self, vec: VectorDD, operation: mqt.core.ir.operations.Operation, permutation: mqt.core.ir.Permutation = ... - ) -> VectorDD: - """Apply a unitary operation to the DD. - - Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - - Args: - vec: The input DD. - operation: The operation. Must be unitary. - permutation: The permutation of the qubits. Defaults to the identity permutation. - - Returns: - The resulting DD. - """ - - def apply_measurement( - self, - vec: VectorDD, - operation: mqt.core.ir.operations.NonUnitaryOperation, - measurements: Sequence[bool], - permutation: mqt.core.ir.Permutation = ..., - ) -> tuple[VectorDD, list[bool]]: - """Apply a measurement to the DD. - - Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count - - Args: - vec: The input DD. - operation: The measurement operation. - measurements: A list of bits with existing measurement outcomes. - permutation: The permutation of the qubits. Defaults to the identity permutation. - - Returns: - The resulting DD after the measurement as well as the updated measurement outcomes. - """ - - def apply_reset( - self, - vec: VectorDD, - operation: mqt.core.ir.operations.NonUnitaryOperation, - permutation: mqt.core.ir.Permutation = ..., - ) -> VectorDD: - """Apply a reset to the DD. - - Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - - Args: - vec: The input DD. - operation: The reset operation. - permutation: The permutation of the qubits. Defaults to the identity permutation. - - Returns: - The resulting DD after the reset. - """ - - def apply_if_else_operation( - self, - vec: VectorDD, - operation: mqt.core.ir.operations.IfElseOperation, - measurements: Sequence[bool], - permutation: mqt.core.ir.Permutation = ..., - ) -> VectorDD: - """Apply a classically controlled operation to the DD. - - Notes: - Automatically manages the reference count of the input and output DDs. - The input DD must have a non-zero reference count. - - Args: - vec: The input DD. - operation: The classically controlled operation. - measurements: A list of bits with stored measurement outcomes. - permutation: The permutation of the qubits. Defaults to the identity permutation. - - Returns: - The resulting DD after the operation. - """ - def measure_collapsing(self, vec: VectorDD, qubit: int) -> str: """Measure a qubit and collapse the DD. @@ -517,10 +458,7 @@ class DDPackage: """ def controlled_single_qubit_gate( - self, - matrix: Annotated[NDArray[np.complex128], {"shape": (2, 2)}], - control: mqt.core.ir.operations.Control | int, - target: int, + self, matrix: Annotated[NDArray[np.complex128], {"shape": (2, 2)}], control: Control | int, target: int ) -> MatrixDD: """Create the DD for a controlled single-qubit gate. @@ -536,7 +474,7 @@ class DDPackage: def multi_controlled_single_qubit_gate( self, matrix: Annotated[NDArray[np.complex128], {"shape": (2, 2)}], - controls: AbstractSet[mqt.core.ir.operations.Control | int], + controls: AbstractSet[Control | int], target: int, ) -> MatrixDD: """Create the DD for a multi-controlled single-qubit gate. @@ -567,7 +505,7 @@ class DDPackage: def controlled_two_qubit_gate( self, matrix: Annotated[NDArray[np.complex128], {"shape": (4, 4)}], - control: mqt.core.ir.operations.Control | int, + control: Control | int, target0: int, target1: int, ) -> MatrixDD: @@ -586,7 +524,7 @@ class DDPackage: def multi_controlled_two_qubit_gate( self, matrix: Annotated[NDArray[np.complex128], {"shape": (4, 4)}], - controls: AbstractSet[mqt.core.ir.operations.Control | int], + controls: AbstractSet[Control | int], target0: int, target1: int, ) -> MatrixDD: @@ -612,17 +550,6 @@ class DDPackage: The DD for the matrix. """ - def from_operation(self, operation: mqt.core.ir.operations.Operation, invert: bool = False) -> MatrixDD: - """Create a DD from an operation. - - Args: - operation: The operation. Must be unitary. - invert: Whether to get the inverse of the operation. - - Returns: - The DD for the operation. - """ - def inc_ref_vec(self, vec: VectorDD) -> None: """Increment the reference count of a vector.""" @@ -865,102 +792,3 @@ class BasisStates(enum.Enum): """ The superposition state :math:`|L\\rangle = \\frac{1}{\\sqrt{2}} (|0\\rangle + i |1\\rangle)`. """ - -def sample(qc: mqt.core.ir.QuantumComputation, shots: int = 1024, seed: int = 0) -> dict[str, int]: - """Sample from the output distribution of a quantum computation. - - This function classically simulates the quantum computation and repeatedly samples from the output distribution. - It supports mid-circuit measurements, resets, and classical control. - - Args: - qc: The quantum computation. - shots: The number of samples to take. - If the quantum computation contains no mid-circuit measurements or resets, the circuit is simulated once and the samples are drawn from the final state. - Otherwise, the circuit is simulated once for each sample. - Defaults to 1024. - seed: The seed for the random number generator. - If set to a specific non-zero value, the simulation is deterministic. - If set to 0, the RNG is randomly seeded. - Defaults to 0. - - Returns: - A histogram of the samples. - Each sample is a bitstring representing the measurement outcomes of the qubits in the quantum computation. - The leftmost bit corresponds to the most significant qubit, that is, the qubit with the highest index (big-endian). - If the circuit contains measurements, only the qubits that are actively measured are included in the output distribution. - Otherwise, all qubits in the circuit are measured. - """ - -def simulate_statevector(qc: mqt.core.ir.QuantumComputation) -> Annotated[NDArray[np.complex128], {"shape": (None,)}]: - """Simulate the quantum computation and return the final state vector. - - This function classically simulates the quantum computation and returns the state vector of the final state. - It does not support measurements, resets, or classical control. - - Since the state vector is guaranteed to be exponentially large in the number of qubits, this function is only suitable for small quantum computations. - Consider using the :func:`~mqt.core.dd.simulate` or the :func:`~mqt.core.dd.sample` functions, which never explicitly construct the state vector, for larger quantum computations. - - Notes: - This function internally constructs a :class:`~mqt.core.dd.DDPackage`, creates the zero state, and simulates the quantum computation via the :func:`simulate` function. - The state vector is then extracted from the resulting DD via the :meth:`~mqt.core.dd.VectorDD.get_vector` method. - - Args: - qc: The quantum computation. Must only contain unitary operations. - - Returns: - The state vector of the final state. - """ - -def build_unitary(qc: mqt.core.ir.QuantumComputation) -> Annotated[NDArray[np.complex128], {"shape": (None, None)}]: - """Build a unitary matrix representation of a quantum computation. - - This function builds a matrix representation of the unitary representing the functionality of a quantum computation. - This function does not support measurements, resets, or classical control, as the corresponding operations are non-unitary. - - Since the unitary matrix is guaranteed to be exponentially large in the number of qubits, this function is only suitable for small quantum computations. - Consider using the :func:`~mqt.core.dd.build_functionality` function, which never explicitly constructs the unitary matrix, for larger quantum computations. - - Notes: - This function internally constructs a :class:`~mqt.core.dd.DDPackage`, creates the identity matrix, and builds the unitary matrix via the :func:`~mqt.core.dd.build_functionality` function. - The unitary matrix is then extracted from the resulting DD via the :meth:`~mqt.core.dd.MatrixDD.get_matrix` method. - - Args: - qc: The quantum computation. Must only contain unitary operations. - - Returns: - The unitary matrix representing the functionality of the quantum computation. - """ - -def simulate(qc: mqt.core.ir.QuantumComputation, initial_state: VectorDD, dd_package: DDPackage) -> VectorDD: - """Simulate a quantum computation. - - This function classically simulates a quantum computation for a given initial state and returns the final state (represented as a DD). - Compared to the `sample` function, this function does not support measurements, resets, or classical control. - It only supports unitary operations. - - The simulation is effectively computed by sequentially applying the operations of the quantum computation to the initial state. - - Args: - qc: The quantum computation. Must only contain unitary operations. - initial_state: The initial state as a DD. Must have the same number of qubits as the quantum computation. - The reference count of the initial state is decremented during the simulation, so the caller must ensure that the initial state has a non-zero reference count. - dd_package: The DD package. Must be configured with a sufficient number of qubits to accommodate the quantum computation. - - Returns: - The final state as a DD. The reference count of the final state is non-zero and must be manually decremented by the caller if it is no longer needed. - """ - -def build_functionality(qc: mqt.core.ir.QuantumComputation, dd_package: DDPackage) -> MatrixDD: - """Build a functional representation of a quantum computation. - - This function builds a matrix DD representation of the unitary representing the functionality of a quantum computation. - This function does not support measurements, resets, or classical control, as the corresponding operations are non-unitary. - - Args: - qc: The quantum computation. - Must only contain unitary operations. - dd_package: The DD package. Must be configured with a sufficient number of qubits to accommodate the quantum computation. - - Returns: - The functionality as a DD. The reference count of the result is non-zero and must be manually decremented by the caller if it is no longer needed. - """ diff --git a/python/mqt/core/ir/__init__.pyi b/python/mqt/core/ir/__init__.pyi deleted file mode 100644 index 020b978863..0000000000 --- a/python/mqt/core/ir/__init__.pyi +++ /dev/null @@ -1,2111 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""MQT Core IR - The MQT Core Intermediate Representation (IR) module.""" - -import os -from collections.abc import ItemsView, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence -from collections.abc import Set as AbstractSet -from typing import overload - -from mqt.core.ir import operations as operations -from mqt.core.ir import registers as registers -from mqt.core.ir import symbolic as symbolic - -class Permutation(MutableMapping[int, int]): - """A class to represent a permutation of the qubits in a quantum circuit. - - Args: - permutation: The permutation to initialize the object with. - """ - - @overload - def __init__(self) -> None: ... - @overload - def __init__(self, permutation: dict[int, int]) -> None: - """Create a permutation from a dictionary.""" - - @overload - def apply(self, controls: AbstractSet[operations.Control]) -> set[operations.Control]: - """Apply the permutation to a set of controls. - - Args: - controls: The set of controls to apply the permutation to. - - Returns: - The set of controls with the permutation applied. - """ - - @overload - def apply(self, targets: Sequence[int]) -> list[int]: - """Apply the permutation to a list of targets. - - Args: - targets: The list of targets to apply the permutation to. - - Returns: - The list of targets with the permutation applied. - """ - - def clear(self) -> None: - """Clear the permutation of all indices and values.""" - - def __getitem__(self, index: int) -> int: - """Get the value of the permutation at the given index. - - Args: - index: The index to get the value of the permutation at. - - Returns: - The value of the permutation at the given index. - """ - - def __setitem__(self, index: int, value: int) -> None: - """Set the value of the permutation at the given index. - - Args: - index: The index to set the value of the permutation at. - value: The value to set the permutation at the given index to. - """ - - def __delitem__(self, index: int) -> None: - """Delete the value of the permutation at the given index. - - Args: - index: The index to delete the value of the permutation at. - """ - - def __len__(self) -> int: - """Return the number of indices in the permutation.""" - - def __iter__(self) -> Iterator[int]: ... - def items(self) -> ItemsView[int, int]: ... - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - -class QuantumComputation(MutableSequence[operations.Operation]): - """The main class for representing quantum computations within the MQT. - - Acts as mutable sequence of :class:`~mqt.core.ir.operations.Operation` objects, which represent the individual operations in the quantum computation. - - Args: - nq: The number of qubits in the quantum computation. - nc: The number of classical bits in the quantum computation. - """ - - def __init__(self, nq: int = 0, nc: int = 0) -> None: ... - @staticmethod - def from_qasm_str(qasm: str) -> QuantumComputation: - """Create a QuantumComputation object from an OpenQASM string. - - Args: - qasm: The OpenQASM string to create the QuantumComputation object from. - - Returns: - The QuantumComputation object created from the OpenQASM string. - """ - - @staticmethod - def from_qasm(filename: str) -> QuantumComputation: - """Create a QuantumComputation object from an OpenQASM file. - - Args: - filename: The filename of the OpenQASM file to create the QuantumComputation object from. - - Returns: - The QuantumComputation object created from the OpenQASM file. - """ - - @property - def name(self) -> str: - """The name of the quantum computation.""" - - @name.setter - def name(self, arg: str, /) -> None: ... - @property - def num_qubits(self) -> int: - """The total number of qubits in the quantum computation.""" - - @property - def num_ancilla_qubits(self) -> int: - """The number of ancilla qubits in the quantum computation. - - Note: - Ancilla qubits are qubits that always start in a fixed state (usually :math:`|0\\rangle`). - """ - - @property - def num_garbage_qubits(self) -> int: - """The number of garbage qubits in the quantum computation. - - Note: - Garbage qubits are qubits whose final state is not relevant for the computation. - """ - - @property - def num_output_qubits(self) -> int: - """The number of logical output qubits in the quantum computation. - - Output qubits are the qubits that are not marked as garbage. - """ - - @property - def num_data_qubits(self) -> int: - """The number of data qubits in the quantum computation. - - Computed as :math:`| \\text{qubits} | - | \\text{ancilla} |`. - """ - - @property - def num_classical_bits(self) -> int: - """The number of classical bits in the quantum computation.""" - - @property - def num_ops(self) -> int: - """The number of operations in the quantum computation.""" - - def num_single_qubit_ops(self) -> int: - """Return the number of single-qubit operations in the quantum computation.""" - - def num_total_ops(self) -> int: - """Return the total number of operations in the quantum computation. - - Recursively counts sub-operations (e.g., from :class:`~mqt.core.ir.operations.CompoundOperation` objects). - """ - - def depth(self) -> int: - """Return the depth of the quantum computation.""" - - @property - def global_phase(self) -> float: - """The global phase of the quantum computation.""" - - @global_phase.setter - def global_phase(self, arg: float, /) -> None: ... - def invert(self) -> None: - """Invert the quantum computation in-place by inverting each operation and reversing the order of operations.""" - - def to_operation(self) -> operations.Operation: - """Convert the quantum computation to a single operation. - - This gives ownership of the operations to the resulting operation, so the quantum computation will be empty after this operation. - - When the quantum computation contains more than one operation, the resulting operation is a :class:`~mqt.core.ir.operations.CompoundOperation`. - - Returns: - The operation representing the quantum computation. - """ - - @overload - def __getitem__(self, index: int) -> operations.Operation: - """Get the operation at the given index. - - Note: - This gives write access to the operation at the given index. - - Args: - index: The index of the operation to get. - - Returns: - The operation at the given index. - """ - - @overload - def __getitem__(self, index: slice) -> list[operations.Operation]: - """Get a slice of operations from the quantum computation. - - Note: - This gives write access to the operations in the given slice. - - Args: - index: The slice of operations to get. - - Returns: - The operations in the given slice. - """ - - @overload - def __setitem__(self, index: int, value: operations.Operation) -> None: - """Set the operation at the given index. - - Args: - index: The index of the operation to set. - value: The operation to set at the given index. - """ - - @overload - def __setitem__(self, index: slice, value: Iterable[operations.Operation]) -> None: - """Set the operations in the given slice. - - Args: - index: The slice of operations to set. - value: The operations to set in the given slice. - """ - - @overload - def __delitem__(self, index: int) -> None: - """Delete the operation at the given index. - - Args: - index: The index of the operation to delete. - """ - - @overload - def __delitem__(self, index: slice) -> None: - """Delete the operations in the given slice. - - Args: - index: The slice of operations to delete. - """ - - def __len__(self) -> int: - """Return the number of operations in the quantum computation.""" - - def insert(self, index: int, value: operations.Operation) -> None: - """Insert an operation at the given index. - - Args: - index: The index to insert the operation at. - value: The operation to insert. - """ - - def append(self, value: operations.Operation) -> None: - """Append an operation to the end of the quantum computation. - - Args: - value: The operation to append. - """ - - def reverse(self) -> None: - """Reverse the order of the operations in the quantum computation (in-place).""" - - def clear(self) -> None: - """Clear the quantum computation of all operations.""" - - def add_qubit_register(self, n: int, name: str = "q") -> registers.QuantumRegister: - """Add a qubit register to the quantum computation. - - Args: - n: The number of qubits in the qubit register. - name: The name of the qubit register. - - Returns: - The qubit register added to the quantum computation. - """ - - def add_classical_register(self, n: int, name: str = "c") -> registers.ClassicalRegister: - """Add a classical register to the quantum computation. - - Args: - n: The number of bits in the classical register. - name: The name of the classical register. - - Returns: - The classical register added to the quantum computation. - """ - - def add_ancillary_register(self, n: int, name: str = "anc") -> registers.QuantumRegister: - """Add an ancillary register to the quantum computation. - - Args: - n: The number of qubits in the ancillary register. - name: The name of the ancillary register. - - Returns: - The ancillary register added to the quantum computation. - """ - - def unify_quantum_registers(self, name: str = "q") -> registers.QuantumRegister: - """Unify all quantum registers in the quantum computation. - - Args: - name: The name of the unified quantum register. - - Returns: - The unified quantum register. - """ - - @property - def qregs(self) -> dict[str, registers.QuantumRegister]: - """The quantum registers in the quantum computation.""" - - @property - def cregs(self) -> dict[str, registers.ClassicalRegister]: - """The classical registers in the quantum computation.""" - - @property - def ancregs(self) -> dict[str, registers.QuantumRegister]: - """The ancillary registers in the quantum computation.""" - - @property - def initial_layout(self) -> Permutation: - """The initial layout of the qubits in the quantum computation. - - This is a permutation of the qubits in the quantum computation. - It is mainly used to track the mapping of circuit qubits to device qubits during quantum circuit compilation. - The keys are the device qubits (in which a compiled circuit is expressed in), and the values are the circuit qubits (in which the original quantum circuit is expressed in). - - Any operations in the quantum circuit are expected to be expressed in terms of the keys of the initial layout. - - Examples: - - If no initial layout is explicitly specified (which is the default), the initial layout is assumed to be the identity permutation. - - Assume a three-qubit circuit has been compiled to a four qubit device and circuit qubit 0 is mapped to device qubit 1, circuit qubit 1 is mapped to device qubit 2, and circuit qubit 2 is mapped to device qubit 3. - Then the initial layout is {1: 0, 2: 1, 3: 2}. - """ - - @initial_layout.setter - def initial_layout(self, arg: Permutation, /) -> None: ... - @property - def output_permutation(self) -> Permutation: - """The output permutation of the qubits in the quantum computation. - - This is a permutation of the qubits in the quantum computation. - It is mainly used to track where individual qubits end up at the end of the quantum computation, for example after a circuit has been compiled to a specific device and SWAP gates have been inserted, which permute the qubits. - Similar to the initial layout, the keys are the qubits in the circuit and the values are the qubits in the "original" circuit. - - Examples: - - If no output permutation is explicitly specified and the circuit does not contain measurements at the end, the output permutation is assumed to be the identity permutation. - - If the circuit contains measurements at the end, these measurements are used to infer the output permutation. - Assume a three-qubit circuit has been compiled to a four qubit device and, at the end of the circuit, circuit qubit 0 is measured into classical bit 2, circuit qubit 1 is measured into classical bit 1, and circuit qubit 3 is measured into classical bit 0. - Then the output permutation is {0: 2, 1: 1, 3: 0}. - """ - - @output_permutation.setter - def output_permutation(self, arg: Permutation, /) -> None: ... - def initialize_io_mapping(self) -> None: - """Initialize the I/O mapping of the quantum computation. - - If no initial layout is explicitly specified, the initial layout is assumed to be the identity permutation. - If the circuit contains measurements at the end, these measurements are used to infer the output permutation. - If the output permutation is not empty, it must contain every measured device qubit as a key. - Clear the output permutation before calling this method to infer it only from measurements. - - Raises: - ValueError: If a measured device qubit is missing from a non-empty output permutation. - """ - - @property - def ancillary(self) -> list[bool]: - """A list of booleans indicating whether each qubit is ancillary.""" - - def set_circuit_qubit_ancillary(self, q: int) -> None: - """Set a circuit (i.e., logical) qubit to be ancillary. - - Args: - q: The index of the circuit qubit to set as ancillary. - """ - - def set_circuit_qubits_ancillary(self, q_min: int, q_max: int) -> None: - """Set a range of circuit (i.e., logical) qubits to be ancillary. - - Args: - q_min: The minimum index of the circuit qubits to set as ancillary. - q_max: The maximum index of the circuit qubits to set as ancillary. - """ - - def is_circuit_qubit_ancillary(self, q: int) -> bool: - """Check if a circuit (i.e., logical) qubit is ancillary. - - Args: - q: The index of the circuit qubit to check. - - Returns: - True if the circuit qubit is ancillary, False otherwise. - """ - - @property - def garbage(self) -> list[bool]: - """A list of booleans indicating whether each qubit is garbage.""" - - def set_circuit_qubit_garbage(self, q: int) -> None: - """Set a circuit (i.e., logical) qubit to be garbage. - - Args: - q: The index of the circuit qubit to set as garbage. - """ - - def set_circuit_qubits_garbage(self, q_min: int, q_max: int) -> None: - """Set a range of circuit (i.e., logical) qubits to be garbage. - - Args: - q_min: The minimum index of the circuit qubits to set as garbage. - q_max: The maximum index of the circuit qubits to set as garbage. - """ - - def is_circuit_qubit_garbage(self, q: int) -> bool: - """Check if a circuit (i.e., logical) qubit is garbage. - - Args: - q: The index of the circuit qubit to check. - - Returns: - True if the circuit qubit is garbage, False otherwise. - """ - - @property - def variables(self) -> set[symbolic.Variable]: - """The set of variables in the quantum computation.""" - - def add_variable(self, var: symbolic.Expression | float) -> None: - """Add a variable to the quantum computation. - - Args: - var: The variable to add. - """ - - def add_variables(self, vars_: Sequence[symbolic.Expression | float]) -> None: - """Add multiple variables to the quantum computation. - - Args: - vars_: The variables to add. - """ - - def is_variable_free(self) -> bool: - """Check if the quantum computation is free of variables. - - Returns: - True if the quantum computation is free of variables, False otherwise. - """ - - def instantiate(self, assignment: Mapping[symbolic.Variable, float]) -> QuantumComputation: - """Instantiate the quantum computation with the given variable assignment. - - Args: - assignment: The variable assignment to instantiate the quantum computation with. - - Returns: - The instantiated quantum computation. - """ - - def instantiate_inplace(self, assignment: Mapping[symbolic.Variable, float]) -> None: - """Instantiate the quantum computation with the given variable assignment in-place. - - Args: - assignment: The variable assignment to instantiate the quantum computation with. - """ - - def qasm2_str(self) -> str: - """Return the OpenQASM2 representation of the quantum computation as a string. - - Note: - This uses some custom extensions to OpenQASM 2.0 that allow for easier definition of multi-controlled gates. - These extensions might not be supported by all OpenQASM 2.0 parsers. - Consider using the :meth:`qasm3_str` method instead, which uses OpenQASM 3.0 that natively supports multi-controlled gates. - The export also assumes the bigger, non-standard `qelib1.inc` from Qiskit is available. - - Returns: - The OpenQASM2 representation of the quantum computation as a string. - """ - - def qasm2(self, filename: os.PathLike[str] | str) -> None: - """Write the OpenQASM2 representation of the quantum computation to a file. - - See Also: - :meth:`qasm2_str` - - Args: - filename: The filename of the file to write the OpenQASM2 representation to. - """ - - def qasm3_str(self) -> str: - """Return the OpenQASM3 representation of the quantum computation as a string. - - Returns: - The OpenQASM3 representation of the quantum computation as a string. - """ - - def qasm3(self, filename: os.PathLike[str] | str) -> None: - """Write the OpenQASM3 representation of the quantum computation to a file. - - See Also: - :meth:`qasm3_str` - - Args: - filename: The filename of the file to write the OpenQASM3 representation to. - """ - - def i(self, q: int) -> None: - """Apply an identity operation. - - .. math:: - I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def ci(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled identity operation. - - See Also: - :meth:`i` - - Args: - control: The control qubit - target: The target qubit - """ - - def mci(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled identity operation. - - See Also: - :meth:`i` - - Args: - controls: The control qubits - target: The target qubit - """ - - def x(self, q: int) -> None: - """Apply a Pauli-X gate. - - .. math:: - X = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cx(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled Pauli-X (i.e., CNOT or CX) gate. - - See Also: - :meth:`x` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcx(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled Pauli-X (i.e., Toffoli or MCX) gate. - - See Also: - :meth:`x` - - Args: - controls: The control qubits - target: The target qubit - """ - - def y(self, q: int) -> None: - """Apply a Pauli-Y gate. - - .. math:: - Y = \\begin{pmatrix} 0 & -i \\\\ i & 0 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cy(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled Pauli-Y gate. - - See Also: - :meth:`y` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcy(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled Pauli-Y gate. - - See Also: - :meth:`y` - - Args: - controls: The control qubits - target: The target qubit - """ - - def z(self, q: int) -> None: - """Apply a Pauli-Z gate. - - .. math:: - Z = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cz(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled Pauli-Z gate. - - See Also: - :meth:`z` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcz(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled Pauli-Z gate. - - See Also: - :meth:`z` - - Args: - controls: The control qubits - target: The target qubit - """ - - def h(self, q: int) -> None: - """Apply a Hadamard gate. - - .. math:: - H = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def ch(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled Hadamard gate. - - See Also: - :meth:`h` - - Args: - control: The control qubit - target: The target qubit - """ - - def mch(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled Hadamard gate. - - See Also: - :meth:`h` - - Args: - controls: The control qubits - target: The target qubit - """ - - def s(self, q: int) -> None: - """Apply an S (i.e., phase) gate. - - .. math:: - S = \\begin{pmatrix} 1 & 0 \\\\ 0 & i \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cs(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled S gate. - - See Also: - :meth:`s` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcs(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled S gate. - - See Also: - :meth:`s` - - Args: - controls: The control qubits - target: The target qubit - """ - - def sdg(self, q: int) -> None: - """Apply an :math:`S^\\dagger` gate. - - .. math:: - S^\\dagger = \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\end{pmatrix} - - Args: - q: The target qubit - """ - - def csdg(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`S^\\dagger` gate. - - See Also: - :meth:`sdg` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcsdg(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled :math:`S^\\dagger` gate. - - See Also: - :meth:`sdg` - - Args: - controls: The control qubits - target: The target qubit - """ - - def t(self, q: int) -> None: - """Apply a T gate. - - .. math:: - T = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i \\pi / 4} \\end{pmatrix} - - Args: - q: The target qubit - """ - - def ct(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled T gate. - - See Also: - :meth:`t` - - Args: - control: The control qubit - target: The target qubit - """ - - def mct(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled T gate. - - See Also: - :meth:`t` - - Args: - controls: The control qubits - target: The target qubit - """ - - def tdg(self, q: int) -> None: - """Apply a :math:`T^\\dagger` gate. - - .. math:: - T^\\dagger = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{-i \\pi / 4} \\end{pmatrix} - - Args: - q: The target qubit - """ - - def ctdg(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`T^\\dagger` gate. - - See Also: - :meth:`tdg` - - Args: - control: The control qubit - target: The target qubit - """ - - def mctdg(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled :math:`T^\\dagger` gate. - - See Also: - :meth:`tdg` - - Args: - controls: The control qubits - target: The target qubit - """ - - def v(self, q: int) -> None: - """Apply a V gate. - - .. math:: - V = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & -i \\\\ -i & 1 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cv(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled V gate. - - See Also: - :meth:`v` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcv(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled V gate. - - See Also: - :meth:`v` - - Args: - controls: The control qubits - target: The target qubit - """ - - def vdg(self, q: int) -> None: - """Apply a :math:`V^\\dagger` gate. - - .. math:: - V^\\dagger = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & i \\\\ i & 1 \\end{pmatrix} - - Args: - q: The target qubit - """ - - def cvdg(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`V^\\dagger` gate. - - See Also: - :meth:`vdg` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcvdg(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled :math:`V^\\dagger` gate. - - See Also: - :meth:`vdg` - - Args: - controls: The control qubits - target: The target qubit - """ - - def sx(self, q: int) -> None: - """Apply a :math:`\\sqrt{X}` gate. - - .. math:: - \\sqrt{X} = \\frac{1}{2} \\begin{pmatrix} 1 + i & 1 - i \\\\ 1 - i & 1 + i \\end{pmatrix} - - Args: - q: The target qubit - """ - - def csx(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`\\sqrt{X}` gate. - - See Also: - :meth:`sx` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcsx(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled :math:`\\sqrt{X}` gate. - - See Also: - :meth:`sx` - - Args: - controls: The control qubits - target: The target qubit - """ - - def sxdg(self, q: int) -> None: - """Apply a :math:`\\sqrt{X}^\\dagger` gate. - - .. math:: - \\sqrt{X}^{\\dagger} = \\frac{1}{2} \\begin{pmatrix} 1 - i & 1 + i \\\\ 1 + i & 1 - i \\end{pmatrix} - - Args: - q: The target qubit - """ - - def csxdg(self, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`\\sqrt{X}^\\dagger` gate. - - See Also: - :meth:`sxdg` - - Args: - control: The control qubit - target: The target qubit - """ - - def mcsxdg(self, controls: AbstractSet[operations.Control | int], target: int) -> None: - """Apply a multi-controlled :math:`\\sqrt{X}^\\dagger` gate. - - See Also: - :meth:`sxdg` - - Args: - controls: The control qubits - target: The target qubit - """ - - def rx(self, theta: symbolic.Expression | float, q: int) -> None: - """Apply an :math:`R_x(\\theta)` gate. - - .. math:: - R_x(\\theta) = e^{-i \\theta X / 2} = \\cos(\\theta / 2) I - i \\sin(\\theta / 2) X - = \\begin{pmatrix} \\cos(\\theta / 2) & -i \\sin(\\theta / 2) \\\\ -i \\sin(\\theta / 2) & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - q: The target qubit - """ - - def crx(self, theta: symbolic.Expression | float, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`R_x(\\theta)` gate. - - See Also: - :meth:`rx` - - Args: - theta: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcrx( - self, theta: symbolic.Expression | float, controls: AbstractSet[operations.Control | int], target: int - ) -> None: - """Apply a multi-controlled :math:`R_x(\\theta)` gate. - - See Also: - :meth:`rx` - - Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def ry(self, theta: symbolic.Expression | float, q: int) -> None: - """Apply an :math:`R_y(\\theta)` gate. - - .. math:: - R_y(\\theta) = e^{-i \\theta Y / 2} = \\cos(\\theta / 2) I - i \\sin(\\theta / 2) Y - = \\begin{pmatrix} \\cos(\\theta / 2) & -\\sin(\\theta / 2) \\\\ \\sin(\\theta / 2) & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - q: The target qubit - """ - - def cry(self, theta: symbolic.Expression | float, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`R_y(\\theta)` gate. - - See Also: - :meth:`ry` - - Args: - theta: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcry( - self, theta: symbolic.Expression | float, controls: AbstractSet[operations.Control | int], target: int - ) -> None: - """Apply a multi-controlled :math:`R_y(\\theta)` gate. - - See Also: - :meth:`ry` - - Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def rz(self, theta: symbolic.Expression | float, q: int) -> None: - """Apply an :math:`R_z(\\theta)` gate. - - .. math:: - R_z(\\theta) = e^{-i \\theta Z / 2} = \\begin{pmatrix} e^{-i \\theta / 2} & 0 \\\\ 0 & e^{i \\theta / 2} \\end{pmatrix} - - Args: - theta: The rotation angle - q: The target qubit - """ - - def crz(self, theta: symbolic.Expression | float, control: operations.Control | int, target: int) -> None: - """Apply a controlled :math:`R_z(\\theta)` gate. - - See Also: - :meth:`rz` - - Args: - theta: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcrz( - self, theta: symbolic.Expression | float, controls: AbstractSet[operations.Control | int], target: int - ) -> None: - """Apply a multi-controlled :math:`R_z(\\theta)` gate. - - See Also: - :meth:`rz` - - Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def p(self, theta: symbolic.Expression | float, q: int) -> None: - """Apply a phase gate. - - .. math:: - P(\\theta) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i \\theta} \\end{pmatrix} - - Args: - theta: The rotation angle - q: The target qubit - """ - - def cp(self, theta: symbolic.Expression | float, control: operations.Control | int, target: int) -> None: - """Apply a controlled phase gate. - - See Also: - :meth:`p` - - Args: - theta: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcp( - self, theta: symbolic.Expression | float, controls: AbstractSet[operations.Control | int], target: int - ) -> None: - """Apply a multi-controlled phase gate. - - See Also: - :meth:`p` - - Args: - theta: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def u2(self, phi: symbolic.Expression | float, lambda_: symbolic.Expression | float, q: int) -> None: - """Apply a :math:`U_2(\\phi, \\lambda)` gate. - - .. math:: - U_2(\\phi, \\lambda) = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & -e^{i \\lambda} \\\\ e^{i \\phi} & e^{i (\\phi + \\lambda)} \\end{pmatrix} - - Args: - phi: The rotation angle - lambda_: The rotation angle - q: The target qubit - """ - - def cu2( - self, - phi: symbolic.Expression | float, - lambda_: symbolic.Expression | float, - control: operations.Control | int, - target: int, - ) -> None: - """Apply a controlled :math:`U_2(\\phi, \\lambda)` gate. - - See Also: - :meth:`u2` - - Args: - phi: The rotation angle - lambda_: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcu2( - self, - phi: symbolic.Expression | float, - lambda_: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target: int, - ) -> None: - """Apply a multi-controlled :math:`U_2(\\phi, \\lambda)` gate. - - See Also: - :meth:`u2` - - Args: - phi: The rotation angle - lambda_: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def r(self, theta: symbolic.Expression | float, phi: symbolic.Expression | float, q: int) -> None: - """Apply an :math:`R(\\theta, \\phi)` gate. - - .. math:: - R(\\theta, \\phi) = e^{-i \\frac{\\theta}{2} (\\cos(\\phi) X + \\sin(\\phi) Y)} - = \\begin{pmatrix} \\cos(\\theta / 2) & -i e^{-i \\phi} \\sin(\\theta / 2) \\\\ -i e^{i \\phi} \\sin(\\theta / 2) & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - phi: The rotation angle - q: The target qubit - """ - - def cr( - self, - theta: symbolic.Expression | float, - phi: symbolic.Expression | float, - control: operations.Control | int, - target: int, - ) -> None: - """Apply a controlled :math:`R(\\theta, \\phi)` gate. - - See Also: - :meth:`r` - - Args: - theta: The rotation angle - phi: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcr( - self, - theta: symbolic.Expression | float, - phi: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target: int, - ) -> None: - """Apply a multi-controlled :math:`R(\\theta, \\phi)` gate. - - See Also: - :meth:`r` - - Args: - theta: The rotation angle - phi: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def u( - self, - theta: symbolic.Expression | float, - phi: symbolic.Expression | float, - lambda_: symbolic.Expression | float, - q: int, - ) -> None: - """Apply a :math:`U(\\theta, \\phi, \\lambda)` gate. - - .. math:: - U(\\theta, \\phi, \\lambda) = \\begin{pmatrix} \\cos(\\theta / 2) & -e^{i \\lambda} \\sin(\\theta / 2) \\\\ e^{i \\phi} \\sin(\\theta / 2) & e^{i (\\phi + \\lambda)}\\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - q: The target qubit - """ - - def cu( - self, - theta: symbolic.Expression | float, - phi: symbolic.Expression | float, - lambda_: symbolic.Expression | float, - control: operations.Control | int, - target: int, - ) -> None: - """Apply a controlled :math:`U(\\theta, \\phi, \\lambda)` gate. - - See Also: - :meth:`u` - - Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - control: The control qubit - target: The target qubit - """ - - def mcu( - self, - theta: symbolic.Expression | float, - phi: symbolic.Expression | float, - lambda_: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target: int, - ) -> None: - """Apply a multi-controlled :math:`U(\\theta, \\phi, \\lambda)` gate. - - See Also: - :meth:`u` - - Args: - theta: The rotation angle - phi: The rotation angle - lambda_: The rotation angle - controls: The control qubits - target: The target qubit - """ - - def swap(self, target1: int, target2: int) -> None: - """Apply a SWAP gate. - - .. math:: - \\text{SWAP} = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def cswap(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled SWAP gate. - - See Also: - :meth:`swap` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcswap(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled SWAP gate. - - See Also: - :meth:`swap` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def dcx(self, target1: int, target2: int) -> None: - """Apply a DCX (i.e., double CNOT) gate. - - .. math:: - DCX = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def cdcx(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled DCX gate. - - See Also: - :meth:`dcx` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcdcx(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled DCX gate. - - See Also: - :meth:`dcx` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def ecr(self, target1: int, target2: int) -> None: - """Apply a ECR (echoed cross-resonance) gate. - - .. math:: - ECR = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 0 & 0 & 1 & i \\\\ 0 & 0 & i & 1 \\\\ 1 & -i & 0 & 0 \\\\ -i & 1 & 0 & 0 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def cecr(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled ECR gate. - - See Also: - :meth:`ecr` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcecr(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled ECR gate. - - See Also: - :meth:`ecr` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def iswap(self, target1: int, target2: int) -> None: - """Apply a :math:`i\\text{SWAP}` gate. - - .. math:: - i\\text{SWAP} = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & i & 0 \\\\ 0 & i & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def ciswap(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled :math:`i\\text{SWAP}` gate. - - See Also: - :meth:`iswap` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mciswap(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled :math:`i\\text{SWAP}` gate. - - See Also: - :meth:`iswap` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def iswapdg(self, target1: int, target2: int) -> None: - """Apply a :math:`i\\text{SWAP}^\\dagger` gate. - - .. math:: - i\\text{SWAP}^\\dagger = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & -i & 0 \\\\ 0 & -i & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def ciswapdg(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled :math:`i\\text{SWAP}^\\dagger` gate. - - See Also: - :meth:`iswapdg` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mciswapdg(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled :math:`i\\text{SWAP}^\\dagger` gate. - - See Also: - :meth:`iswapdg` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def peres(self, target1: int, target2: int) -> None: - """Apply a Peres gate. - - .. math:: - \\text{Peres} = \\begin{pmatrix} 0 & 0 & 0 & 1 \\\\ 0 & 0 & 1 & 0 \\\\ 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def cperes(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled Peres gate. - - See Also: - :meth:`peres` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcperes(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled Peres gate. - - See Also: - :meth:`peres` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def peresdg(self, target1: int, target2: int) -> None: - """Apply a :math:`\\text{Peres}^\\dagger` gate. - - .. math:: - \\text{Peres}^\\dagger = \\begin{pmatrix} 0 & 0 & 0 & 1 \\\\ 0 & 0 & 1 & 0 \\\\ 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\end{pmatrix} - - Args: - target1: The first target qubit - target2: The second target qubit - """ - - def cperesdg(self, control: operations.Control | int, target1: int, target2: int) -> None: - """Apply a controlled :math:`\\text{Peres}^\\dagger` gate. - - See Also: - :meth:`peresdg` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcperesdg(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int) -> None: - """Apply a multi-controlled :math:`\\text{Peres}^\\dagger` gate. - - See Also: - :meth:`peresdg` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def rxx(self, theta: symbolic.Expression | float, target1: int, target2: int) -> None: - """Apply an :math:`R_{xx}(\\theta)` gate. - - .. math:: - R_{xx}(\\theta) = e^{-i \\theta XX / 2} = \\cos(\\theta / 2) I \\otimes I - i \\sin(\\theta / 2) X \\otimes X - = \\begin{pmatrix} \\cos(\\theta / 2) & 0 & 0 & -i \\sin(\\theta / 2) \\\\ - 0 & \\cos(\\theta / 2) & -i \\sin(\\theta / 2) & 0 \\\\ - 0 & -i \\sin(\\theta / 2) & \\cos(\\theta / 2) & 0 \\\\ - -i \\sin(\\theta / 2) & 0 & 0 & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def crxx( - self, theta: symbolic.Expression | float, control: operations.Control | int, target1: int, target2: int - ) -> None: - """Apply a controlled :math:`R_{xx}(\\theta)` gate. - - See Also: - :meth:`rxx` - - Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcrxx( - self, - theta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{xx}(\\theta)` gate. - - See Also: - :meth:`rxx` - - Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def ryy(self, theta: symbolic.Expression | float, target1: int, target2: int) -> None: - """Apply an :math:`R_{yy}(\\theta)` gate. - - .. math:: - R_{yy}(\\theta) = e^{-i \\theta YY / 2} = \\cos(\\theta / 2) I \\otimes I - i \\sin(\\theta / 2) Y \\otimes Y - = \\begin{pmatrix} \\cos(\\theta / 2) & 0 & 0 & i \\sin(\\theta / 2) \\\\ - 0 & \\cos(\\theta / 2) & -i \\sin(\\theta / 2) & 0 \\\\ - 0 & -i \\sin(\\theta / 2) & \\cos(\\theta / 2) & 0 \\\\ - i \\sin(\\theta / 2) & 0 & 0 & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def cryy( - self, theta: symbolic.Expression | float, control: operations.Control | int, target1: int, target2: int - ) -> None: - """Apply a controlled :math:`R_{yy}(\\theta)` gate. - - See Also: - :meth:`ryy` - - Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcryy( - self, - theta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{yy}(\\theta)` gate. - - See Also: - :meth:`ryy` - - Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def rzx(self, theta: symbolic.Expression | float, target1: int, target2: int) -> None: - """Apply an :math:`R_{zx}(\\theta)` gate. - - .. math:: - R_{zx}(\\theta) = e^{-i \\theta ZX / 2} = \\cos(\\theta / 2) I \\otimes I - i \\sin(\\theta / 2) Z \\otimes X - = \\begin{pmatrix} \\cos(\\theta/2) & -i \\sin(\\theta/2) & 0 & 0 \\\\ - -i \\sin(\\theta/2) & \\cos(\\theta/2) & 0 & 0 \\\\ - 0 & 0 & \\cos(\\theta/2) & i \\sin(\\theta/2) \\\\ - 0 & 0 & i \\sin(\\theta/2) & \\cos(\\theta/2) \\end{pmatrix} - - Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def crzx( - self, theta: symbolic.Expression | float, control: operations.Control | int, target1: int, target2: int - ) -> None: - """Apply a controlled :math:`R_{zx}(\\theta)` gate. - - See Also: - :meth:`rzx` - - Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcrzx( - self, - theta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{zx}(\\theta)` gate. - - See Also: - :meth:`rzx` - - Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def rzz(self, theta: symbolic.Expression | float, target1: int, target2: int) -> None: - """Apply an :math:`R_{zz}(\\theta)` gate. - - .. math:: - R_{zz}(\\theta) = e^{-i \\theta ZZ / 2} - = \\begin{pmatrix} e^{-i \\theta / 2} & 0 & 0 & 0 \\\\ - 0 & e^{i \\theta / 2} & 0 & 0 \\\\ - 0 & 0 & e^{i \\theta / 2} & 0 \\\\ - 0 & 0 & 0 & e^{-i \\theta / 2} \\end{pmatrix} - - Args: - theta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def crzz( - self, theta: symbolic.Expression | float, control: operations.Control | int, target1: int, target2: int - ) -> None: - """Apply a controlled :math:`R_{zz}(\\theta)` gate. - - See Also: - :meth:`rzz` - - Args: - theta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcrzz( - self, - theta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{zz}(\\theta)` gate. - - See Also: - :meth:`rzz` - - Args: - theta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def xx_minus_yy( - self, theta: symbolic.Expression | float, beta: symbolic.Expression | float, target1: int, target2: int - ) -> None: - """Apply an :math:`R_{XX - YY}(\\theta, \\beta)` gate. - - .. math:: - R_{XX - YY}(\\theta, \\beta) = R_{z_2}(\\beta) \\cdot e^{-i \\frac{\\theta}{2} \\frac{XX - YY}{2}} \\cdot R_{z_2}(-\\beta) - = \\begin{pmatrix} \\cos(\\theta / 2) & 0 & 0 & -i \\sin(\\theta / 2) e^{-i \\beta} \\\\ - 0 & 1 & 0 & 0 \\\\ - 0 & 0 & 1 & 0 \\\\ - -i \\sin(\\theta / 2) e^{i \\beta} & 0 & 0 & \\cos(\\theta / 2) \\end{pmatrix} - - Args: - theta: The rotation angle - beta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def cxx_minus_yy( - self, - theta: symbolic.Expression | float, - beta: symbolic.Expression | float, - control: operations.Control | int, - target1: int, - target2: int, - ) -> None: - """Apply a controlled :math:`R_{XX - YY}(\\theta, \\beta)` gate. - - See Also: - :meth:`xx_minus_yy` - - Args: - theta: The rotation angle - beta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcxx_minus_yy( - self, - theta: symbolic.Expression | float, - beta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{XX - YY}(\\theta, \\beta)` gate. - - See Also: - :meth:`xx_minus_yy` - - Args: - theta: The rotation angle - beta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def xx_plus_yy( - self, theta: symbolic.Expression | float, beta: symbolic.Expression | float, target1: int, target2: int - ) -> None: - """Apply an :math:`R_{XX + YY}(\\theta, \\beta)` gate. - - .. math:: - R_{XX + YY}(\\theta, \\beta) = R_{z_1}(\\beta) \\cdot e^{-i \\frac{\\theta}{2} \\frac{XX + YY}{2}} \\cdot R_{z_1}(-\\beta) - = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ - 0 & \\cos(\\theta / 2) & -i \\sin(\\theta / 2) e^{-i \\beta} & 0 \\\\ - 0 & -i \\sin(\\theta / 2) e^{i \\beta} & \\cos(\\theta / 2) & 0 \\\\ - 0 & 0 & 0 & 1 \\end{pmatrix} - - Args: - theta: The rotation angle - beta: The rotation angle - target1: The first target qubit - target2: The second target qubit - """ - - def cxx_plus_yy( - self, - theta: symbolic.Expression | float, - beta: symbolic.Expression | float, - control: operations.Control | int, - target1: int, - target2: int, - ) -> None: - """Apply a controlled :math:`R_{XX + YY}(\\theta, \\beta)` gate. - - See Also: - :meth:`xx_plus_yy` - - Args: - theta: The rotation angle - beta: The rotation angle - control: The control qubit - target1: The first target qubit - target2: The second target qubit - """ - - def mcxx_plus_yy( - self, - theta: symbolic.Expression | float, - beta: symbolic.Expression | float, - controls: AbstractSet[operations.Control | int], - target1: int, - target2: int, - ) -> None: - """Apply a multi-controlled :math:`R_{XX + YY}(\\theta, \\beta)` gate. - - See Also: - :meth:`xx_plus_yy` - - Args: - theta: The rotation angle - beta: The rotation angle - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - """ - - def rccx(self, target1: int, target2: int, target3: int) -> None: - """Apply a relative-phase CCX (RCCX) gate. - - Args: - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit - """ - - def crccx(self, control: operations.Control | int, target1: int, target2: int, target3: int) -> None: - """Apply a controlled RCCX gate. - - See Also: - :meth:`rccx` - - Args: - control: The control qubit - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit - """ - - def mcrccx(self, controls: AbstractSet[operations.Control | int], target1: int, target2: int, target3: int) -> None: - """Apply a multi-controlled RCCX gate. - - See Also: - :meth:`rccx` - - Args: - controls: The control qubits - target1: The first target qubit - target2: The second target qubit - target3: The third target qubit - """ - - def gphase(self, phase: float) -> None: - """Apply a global phase gate. - - .. math:: - GPhase(\\theta) = (e^{i \\theta}) - - Args: - phase: The rotation angle - """ - - @overload - def measure(self, qubit: int, cbit: int) -> None: - """Measure a qubit and store the result in a classical bit. - - Args: - qubit: The qubit to measure - cbit: The classical bit to store the result - """ - - @overload - def measure(self, qubits: Sequence[int], cbits: Sequence[int]) -> None: - """Measure multiple qubits and store the results in classical bits. - - This method is equivalent to calling :meth:`measure` multiple times. - - Args: - qubits: The qubits to measure - cbits: The classical bits to store the results - """ - - def measure_all(self, *, add_bits: bool = True, add_barrier: bool = True) -> None: - """Measure all qubits and store the results in classical bits. - - Details: - If `add_bits` is `True`, a new classical register (named "`meas`") with the same size as the number of qubits will be added to the circuit and the results will be stored in it. - If `add_bits` is `False`, the classical register must already exist and have a sufficient number of bits to store the results. - If `add_barrier` is `True`, a barrier is added before the measurements. - - Args: - add_bits: Whether to explicitly add a classical register - add_barrier: Whether to add a barrier before the measurements - """ - - @overload - def reset(self, q: int) -> None: - """Add a reset operation to the circuit. - - Args: - q: The qubit to reset - """ - - @overload - def reset(self, qubits: Sequence[int]) -> None: - """Add a reset operation to the circuit. - - Args: - qubits: The qubits to reset - """ - - @overload - def barrier(self) -> None: - """Add a barrier to the circuit.""" - - @overload - def barrier(self, q: int) -> None: - """Add a barrier to the circuit. - - Args: - q: The qubit to add the barrier to - """ - - @overload - def barrier(self, qubits: Sequence[int]) -> None: - """Add a barrier to the circuit. - - Args: - qubits: The qubits to add the barrier to - """ - - @overload - def if_else( - self, - then_operation: operations.Operation, - else_operation: operations.Operation, - control_register: registers.ClassicalRegister, - expected_value: int = 1, - comparison_kind: operations.ComparisonKind = operations.ComparisonKind.eq, - ) -> None: - """Add an if-else operation to the circuit. - - Args: - then_operation: The operation to apply if the condition is met - else_operation: The operation to apply if the condition is not met - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - """ - - @overload - def if_else( - self, - then_operation: operations.Operation, - else_operation: operations.Operation, - control_bit: int, - expected_value: int = 1, - comparison_kind: operations.ComparisonKind = operations.ComparisonKind.eq, - ) -> None: - """Add an if-else operation to the circuit. - - Args: - then_operation: The operation to apply if the condition is met - else_operation: The operation to apply if the condition is not met - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - control_register: registers.ClassicalRegister, - expected_value: int = 1, - comparison_kind: operations.ComparisonKind = operations.ComparisonKind.eq, - params: Sequence[float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - control: operations.Control | int, - control_register: registers.ClassicalRegister, - expected_value: int = 1, - comparison_kind: operations.ComparisonKind = ..., - params: Sequence[symbolic.Expression | float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - control: The control qubit - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation. - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - controls: AbstractSet[operations.Control | int], - control_register: registers.ClassicalRegister, - expected_value: int = 1, - comparison_kind: operations.ComparisonKind = ..., - params: Sequence[symbolic.Expression | float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - controls: The control qubits - control_register: The classical register to check against - expected_value: The expected value of the control register - comparison_kind: The kind of comparison to perform - params: The parameters of the operation. - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - control_bit: int, - expected_value: bool = True, - comparison_kind: operations.ComparisonKind = operations.ComparisonKind.eq, - params: Sequence[float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation. - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - control: operations.Control | int, - control_bit: int, - expected_value: bool = True, - comparison_kind: operations.ComparisonKind = ..., - params: Sequence[symbolic.Expression | float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - control: The control qubit - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation. - """ - - @overload - def if_( - self, - op_type: operations.OpType, - target: int, - controls: AbstractSet[operations.Control | int], - control_bit: int, - expected_value: bool = True, - comparison_kind: operations.ComparisonKind = ..., - params: Sequence[symbolic.Expression | float] = ..., - ) -> None: - """Add an if operation to the circuit. - - Args: - op_type: The operation to apply - target: The target qubit - controls: The control qubits - control_bit: The index of the classical bit to check against - expected_value: The expected value of the control bit - comparison_kind: The kind of comparison to perform - params: The parameters of the operation. - """ diff --git a/python/mqt/core/ir/operations.pyi b/python/mqt/core/ir/operations.pyi deleted file mode 100644 index fd4c201cca..0000000000 --- a/python/mqt/core/ir/operations.pyi +++ /dev/null @@ -1,891 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -import enum -from collections.abc import Iterable, Mapping, MutableSequence, Sequence -from collections.abc import Set as AbstractSet -from typing import overload - -import mqt.core.ir.registers -import mqt.core.ir.symbolic - -class OpType(enum.Enum): - """Enumeration of operation types.""" - - none = 0 - """ - A placeholder operation. - - It is used to represent an operation that is not yet defined. - """ - - gphase = 4 - """ - A global phase operation. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.gphase` - """ - - i = 10 - """ - An identity operation. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.i` - """ - - h = 16 - """ - A Hadamard gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.h` - """ - - x = 20 - """ - An X gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.x` - """ - - y = 24 - """ - A Y gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.y` - """ - - z = 30 - """ - A Z gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.z` - """ - - s = 34 - """ - An S gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.s` - """ - - sdg = 35 - """ - An :math:`S^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.sdg` - """ - - t = 38 - """ - A T gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.t` - """ - - tdg = 39 - """ - A :math:`T^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.tdg` - """ - - v = 40 - """ - A V gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.v` - """ - - vdg = 41 - """ - A :math:`V^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.vdg` - """ - - u = 44 - """ - A U gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.u` - """ - - u2 = 48 - """ - A U2 gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.u2` - """ - - p = 54 - """ - A phase gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.p` - """ - - sx = 56 - """ - A :math:`\\sqrt{X}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.sx` - """ - - sxdg = 57 - """ - A :math:`\\sqrt{X}^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.sxdg` - """ - - rx = 60 - """ - A :math:`R_x` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rx` - """ - - ry = 64 - """ - A :math:`R_y` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.ry` - """ - - rz = 70 - """ - A :math:`R_z` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rz` - """ - - r = 164 - """ - An :math:`R` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.r` - """ - - swap = 72 - """ - A SWAP gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.swap` - """ - - iswap = 76 - """ - A iSWAP gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.iswap` - """ - - iswapdg = 77 - """ - A :math:`i\\text{SWAP}^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.iswapdg` - """ - - peres = 80 - """ - A Peres gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.peres` - """ - - peresdg = 81 - """ - A :math:`\\text{Peres}^\\dagger` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.peresdg` - """ - - dcx = 84 - """ - A DCX gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.dcx` - """ - - ecr = 88 - """ - An ECR gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.ecr` - """ - - rxx = 92 - """ - A :math:`R_{xx}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rxx` - """ - - ryy = 96 - """ - A :math:`R_{yy}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.ryy` - """ - - rzz = 102 - """ - A :math:`R_{zz}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rzz` - """ - - rzx = 104 - """ - A :math:`R_{zx}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rzx` - """ - - xx_minus_yy = 108 - """ - A :math:`R_{XX - YY}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.xx_minus_yy` - """ - - xx_plus_yy = 112 - """ - A :math:`R_{XX + YY}` gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.xx_plus_yy` - """ - - rccx = 132 - """ - A relative-phase CCX gate. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.rccx` - """ - - compound = 116 - """ - A compound operation. - - It is used to group multiple operations into a single operation. - - See also :class:`.CompoundOperation` - """ - - measure = 120 - """ - A measurement operation. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.measure` - """ - - reset = 124 - """ - A reset operation. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.reset` - """ - - barrier = 14 - """ - A barrier operation. - - It is used to separate operations in the circuit. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.barrier` - """ - - if_else = 128 - """ - An if-else operation. - - It is used to control the execution of an operation based on the value of a classical register. - - See Also: - :meth:`mqt.core.ir.QuantumComputation.if_else` - """ - -class Control: - """A control is a pair of a qubit and a type. The type can be either positive or negative. - - Args: - qubit: The qubit that is the control. - type_: The type of the control. - """ - - def __init__(self, qubit: int, type_: Control.Type = ...) -> None: ... - - class Type(enum.Enum): - """Enumeration of control types.""" - - Pos = 1 - - Neg = 0 - - @property - def qubit(self) -> int: - """The qubit that is the control.""" - - @property - def type_(self) -> Control.Type: - """The type of the control.""" - - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - -class Operation: - @property - def name(self) -> str: - """The name of the operation.""" - - @property - def type_(self) -> OpType: - """The type of the operation.""" - - @type_.setter - def type_(self, arg: OpType, /) -> None: ... - @property - def targets(self) -> list[int]: - """The targets of the operation. - - Note: - The notion of a target might not make sense for all types of operations. - """ - - @targets.setter - def targets(self, arg: Sequence[int], /) -> None: ... - @property - def num_targets(self) -> int: - """The number of targets of the operation.""" - - @property - def controls(self) -> set[Control]: - """The controls of the operation. - - Note: - The notion of a control might not make sense for all types of operations. - """ - - @controls.setter - def controls(self, arg: AbstractSet[Control], /) -> None: ... - @property - def num_controls(self) -> int: - """The number of controls of the operation.""" - - def add_control(self, control: Control) -> None: - """Add a control to the operation. - - Args: - control: The control to add. - """ - - def add_controls(self, controls: AbstractSet[Control]) -> None: - """Add multiple controls to the operation. - - Args: - controls: The controls to add. - """ - - def clear_controls(self) -> None: - """Clear all controls of the operation.""" - - def remove_control(self, control: Control) -> None: - """Remove a control from the operation. - - Args: - control: The control to remove. - """ - - def remove_controls(self, controls: AbstractSet[Control]) -> None: - """Remove multiple controls from the operation. - - Args: - controls: The controls to remove. - """ - - def get_used_qubits(self) -> set[int]: - """Get the qubits that are used by the operation. - - Returns: - The set of qubits that are used by the operation. - """ - - def acts_on(self, qubit: int) -> bool: - """Check if the operation acts on a specific qubit. - - Args: - qubit: The qubit to check. - - Returns: - True if the operation acts on the qubit, False otherwise. - """ - - @property - def parameter(self) -> list[float]: - """The parameters of the operation. - - Note: - The notion of a parameter might not make sense for all types of operations. - """ - - @parameter.setter - def parameter(self, arg: Sequence[float], /) -> None: ... - def is_unitary(self) -> bool: - """Check if the operation is unitary. - - Returns: - True if the operation is unitary, False otherwise. - """ - - def is_standard_operation(self) -> bool: - """Check if the operation is a :class:`StandardOperation`. - - Returns: - True if the operation is a :class:`StandardOperation`, False otherwise. - """ - - def is_compound_operation(self) -> bool: - """Check if the operation is a :class:`CompoundOperation`. - - Returns: - True if the operation is a :class:`CompoundOperation`, False otherwise. - """ - - def is_non_unitary_operation(self) -> bool: - """Check if the operation is a :class:`NonUnitaryOperation`. - - Returns: - True if the operation is a :class:`NonUnitaryOperation`, False otherwise. - """ - - def is_if_else_operation(self) -> bool: - """Check if the operation is a :class:`IfElseOperation`. - - Returns: - True if the operation is a :class:`IfElseOperation`, False otherwise. - """ - - def is_symbolic_operation(self) -> bool: - """Check if the operation is a :class:`SymbolicOperation`. - - Returns: - True if the operation is a :class:`SymbolicOperation`, False otherwise. - """ - - def is_controlled(self) -> bool: - """Check if the operation is controlled. - - Returns: - True if the operation is controlled, False otherwise. - """ - - def get_inverted(self) -> Operation: - """Get the inverse of the operation. - - Returns: - The inverse of the operation. - """ - - def invert(self) -> None: - """Invert the operation (in-place).""" - - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - -class StandardOperation(Operation): - """Standard quantum operation. - - This class is used to represent all standard quantum operations, i.e., operations that are unitary. - This includes all possible quantum gates. - Such Operations are defined by their :class:`OpType`, the qubits (controls and targets) they act on, and their parameters. - - Args: - control: The control qubit(s) of the operation (if any). - target: The target qubit(s) of the operation. - op_type: The type of the operation. - params: The parameters of the operation (if any). - """ - - @overload - def __init__(self) -> None: ... - @overload - def __init__(self, target: int, op_type: OpType, params: Sequence[float] = ...) -> None: ... - @overload - def __init__(self, targets: Sequence[int], op_type: OpType, params: Sequence[float] = ...) -> None: ... - @overload - def __init__(self, control: Control, target: int, op_type: OpType, params: Sequence[float] = ...) -> None: ... - @overload - def __init__( - self, control: Control, targets: Sequence[int], op_type: OpType, params: Sequence[float] = ... - ) -> None: ... - @overload - def __init__( - self, controls: AbstractSet[Control], target: int, op_type: OpType, params: Sequence[float] = ... - ) -> None: ... - @overload - def __init__( - self, controls: AbstractSet[Control], targets: Sequence[int], op_type: OpType, params: Sequence[float] = ... - ) -> None: ... - @overload - def __init__( - self, controls: AbstractSet[Control], target0: int, target1: int, op_type: OpType, params: Sequence[float] = ... - ) -> None: ... - -class CompoundOperation(Operation, MutableSequence[Operation]): - """Compound quantum operation. - - This class is used to aggregate and group multiple operations into a single object. - This is useful for optimizations and for representing complex quantum functionality. - A :class:`CompoundOperation` can contain any number of operations, including other :class:`CompoundOperation`'s. - - Args: - ops: The operations that are part of the compound operation. - """ - - @overload - def __init__(self) -> None: ... - @overload - def __init__(self, ops: Sequence[Operation]) -> None: ... - def __len__(self) -> int: - """The number of operations in the compound operation.""" - - @overload - def __getitem__(self, index: int) -> Operation: - """Get the operation at the given index. - - Note: - This gives direct access to the operations in the compound operation - - Args: - index: The index of the operation to get. - - Returns: - The operation at the given index. - """ - - @overload - def __getitem__(self, index: slice) -> list[Operation]: - """Get the operations in the given slice. - - Note: - This gives direct access to the operations in the compound operation. - - Args: - index: The slice of the operations to get. - - Returns: - The operations in the given slice. - """ - - @overload - def __setitem__(self, index: int, value: Operation) -> None: - """Set the operation at the given index. - - Args: - index: The index of the operation to set. - value: The operation to set at the given index. - """ - - @overload - def __setitem__(self, index: slice, value: Iterable[Operation]) -> None: - """Set the operations in the given slice. - - Args: - index: The slice of operations to set. - value: The operations to set in the given slice. - """ - - @overload - def __delitem__(self, index: int) -> None: - """Delete the operation at the given index. - - Args: - index: The index of the operation to delete. - """ - - @overload - def __delitem__(self, index: slice) -> None: - """Delete the operations in the given slice. - - Args: - index: The slice of operations to delete. - """ - - def append(self, value: Operation) -> None: - """Append an operation to the compound operation.""" - - def insert(self, index: int, value: Operation) -> None: - """Insert an operation at the given index. - - Args: - index: The index to insert the operation at. - value: The operation to insert. - """ - - def empty(self) -> bool: - """Check if the compound operation is empty.""" - - def clear(self) -> None: - """Clear all operations in the compound operation.""" - -class NonUnitaryOperation(Operation): - """Non-unitary operation. - - This class is used to represent all non-unitary operations, i.e., operations that are not reversible. - This includes measurements and resets. - - Args: - targets: The target qubit(s) of the operation. - classics: The classical bit(s) that are associated with the operation (only relevant for measurements). - op_type: The type of the operation. - """ - - @overload - def __init__(self, targets: Sequence[int], classics: Sequence[int]) -> None: ... - @overload - def __init__(self, target: int, classic: int) -> None: ... - @overload - def __init__(self, targets: Sequence[int], op_type: OpType = OpType.reset) -> None: ... - @property - def classics(self) -> list[int]: - """The classical bits that are associated with the operation.""" - -class SymbolicOperation(StandardOperation): - """Symbolic quantum operation. - - This class is used to represent quantum operations that are not yet fully defined. - This can be useful for representing operations that depend on parameters that are not yet known. - A :class:`SymbolicOperation` is defined by its :class:`OpType`, the qubits (controls and targets) it acts on, and its parameters. - The parameters can be either fixed values or symbolic expressions. - - Args: - controls: The control qubit(s) of the operation (if any). - targets: The target qubit(s) of the operation. - op_type: The type of the operation. - params: The parameters of the operation (if any). - """ - - @overload - def __init__(self) -> None: ... - @overload - def __init__( - self, target: int, op_type: OpType, params: Sequence[mqt.core.ir.symbolic.Expression | float] = ... - ) -> None: ... - @overload - def __init__( - self, targets: Sequence[int], op_type: OpType, params: Sequence[mqt.core.ir.symbolic.Expression | float] = ... - ) -> None: ... - @overload - def __init__( - self, - control: Control, - target: int, - op_type: OpType, - params: Sequence[mqt.core.ir.symbolic.Expression | float] = ..., - ) -> None: ... - @overload - def __init__( - self, - control: Control, - targets: Sequence[int], - op_type: OpType, - params: Sequence[mqt.core.ir.symbolic.Expression | float] = ..., - ) -> None: ... - @overload - def __init__( - self, - controls: AbstractSet[Control], - target: int, - op_type: OpType, - params: Sequence[mqt.core.ir.symbolic.Expression | float] = ..., - ) -> None: ... - @overload - def __init__( - self, - controls: AbstractSet[Control], - targets: Sequence[int], - op_type: OpType, - params: Sequence[mqt.core.ir.symbolic.Expression | float] = ..., - ) -> None: ... - @overload - def __init__( - self, - controls: AbstractSet[Control], - target0: int, - target1: int, - op_type: OpType, - params: Sequence[mqt.core.ir.symbolic.Expression | float] = ..., - ) -> None: ... - def get_parameter(self, index: int) -> mqt.core.ir.symbolic.Expression | float: - """Get the parameter at the given index. - - Args: - index: The index of the parameter to get. - - Returns: - The parameter at the given index. - """ - - def get_parameters(self) -> list[mqt.core.ir.symbolic.Expression | float]: - """Get all parameters of the operation. - - Returns: - The parameters of the operation. - """ - - def get_instantiated_operation( - self, assignment: Mapping[mqt.core.ir.symbolic.Variable, float] - ) -> StandardOperation: - """Get the instantiated operation. - - Args: - assignment: The assignment of the symbolic parameters. - - Returns: - The instantiated operation. - """ - - def instantiate(self, assignment: Mapping[mqt.core.ir.symbolic.Variable, float]) -> None: - """Instantiate the operation (in-place). - - Args: - assignment: The assignment of the symbolic parameters. - """ - -class ComparisonKind(enum.Enum): - """Enumeration of comparison types for classic-controlled operations.""" - - eq = 0 - """Equality comparison.""" - - neq = 1 - """Inequality comparison.""" - - lt = 2 - """Less-than comparison.""" - - leq = 3 - """Less-than-or-equal comparison.""" - - gt = 4 - """Greater-than comparison.""" - - geq = 5 - """Greater-than-or-equal comparison.""" - -class IfElseOperation(Operation): - """If-else quantum operation. - - This class is used to represent an if-else operation. - The then operation is executed if the value of the classical register matches the expected value. - Otherwise, the else operation is executed. - - Args: - then_operation: The operation that is executed if the condition is met. - else_operation: The operation that is executed if the condition is not met. - control_register: The classical register that controls the operation. - expected_value: The expected value of the classical register. - comparison_kind: The kind of comparison (default is equality). - """ - - @overload - def __init__( - self, - then_operation: Operation, - else_operation: Operation | None, - control_register: mqt.core.ir.registers.ClassicalRegister, - expected_value: int = 1, - comparison_kind: ComparisonKind = ComparisonKind.eq, - ) -> None: ... - @overload - def __init__( - self, - then_operation: Operation, - else_operation: Operation | None, - control_bit: int, - expected_value: bool = True, - comparison_kind: ComparisonKind = ComparisonKind.eq, - ) -> None: ... - @property - def then_operation(self) -> Operation: - """The operation that is executed if the condition is met.""" - - @property - def else_operation(self) -> Operation | None: - """The operation that is executed if the condition is not met.""" - - @property - def control_register(self) -> mqt.core.ir.registers.ClassicalRegister | None: - """The classical register that controls the operation.""" - - @property - def control_bit(self) -> int | None: - """The classical bit that controls the operation.""" - - @property - def expected_value_register(self) -> int: - """The expected value of the classical register. - - The then-operation is executed if the value of the classical register matches the expected value based on the kind of comparison. - The expected value is an integer that is interpreted as a binary number, where the least significant bit is at the start index of the classical register. - """ - - @property - def expected_value_bit(self) -> bool: - """The expected value of the classical bit. - - The then-operation is executed if the value of the classical bit matches the expected value based on the kind of comparison. - """ - - @property - def comparison_kind(self) -> ComparisonKind: - """The kind of comparison. - - The then-operation is executed if the value of the control matches the expected value based on the kind of comparison. - """ diff --git a/python/mqt/core/ir/registers.pyi b/python/mqt/core/ir/registers.pyi deleted file mode 100644 index 907a6d7561..0000000000 --- a/python/mqt/core/ir/registers.pyi +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -class QuantumRegister: - """A class to represent a collection of qubits. - - Args: - start: The starting index of the quantum register. - size: The number of qubits in the quantum register. - name: The name of the quantum register. A name will be generated if not provided. - """ - - def __init__(self, start: int, size: int, name: str = "") -> None: ... - @property - def name(self) -> str: - """The name of the quantum register.""" - - @property - def start(self) -> int: - """The index of the first qubit in the quantum register.""" - - @start.setter - def start(self, arg: int, /) -> None: ... - @property - def size(self) -> int: - """The number of qubits in the quantum register.""" - - @size.setter - def size(self, arg: int, /) -> None: ... - @property - def end(self) -> int: - """Index of the last qubit in the quantum register.""" - - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - def __getitem__(self, key: int) -> int: - """Get the qubit at the specified index.""" - - def __contains__(self, item: int) -> bool: - """Check if the quantum register contains a qubit.""" - -class ClassicalRegister: - """A class to represent a collection of classical bits. - - Args: - start: The starting index of the classical register. - size: The number of bits in the classical register. - name: The name of the classical register. A name will be generated if not provided. - """ - - def __init__(self, start: int, size: int, name: str = "") -> None: ... - @property - def name(self) -> str: - """The name of the classical register.""" - - @property - def start(self) -> int: - """The index of the first bit in the classical register.""" - - @start.setter - def start(self, arg: int, /) -> None: ... - @property - def size(self) -> int: - """The number of bits in the classical register.""" - - @size.setter - def size(self, arg: int, /) -> None: ... - @property - def end(self) -> int: - """Index of the last bit in the classical register.""" - - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - def __getitem__(self, key: int) -> int: - """Get the bit at the specified index.""" - - def __contains__(self, item: int) -> bool: - """Check if the classical register contains a bit.""" diff --git a/python/mqt/core/ir/symbolic.pyi b/python/mqt/core/ir/symbolic.pyi deleted file mode 100644 index b673186be7..0000000000 --- a/python/mqt/core/ir/symbolic.pyi +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -from collections.abc import Iterator, Mapping, Sequence -from typing import overload - -class Variable: - """A symbolic variable. - - Note: - Variables are uniquely identified by their name, so if a variable with the same name already exists, the existing variable will be returned. - - Args: - name: The name of the variable. - """ - - def __init__(self, name: str = "") -> None: ... - @property - def name(self) -> str: - """The name of the variable.""" - - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - def __lt__(self, arg: Variable, /) -> bool: ... - def __gt__(self, arg: Variable, /) -> bool: ... - -class Term: - """A symbolic term which consists of a variable with a given coefficient. - - Args: - variable: The variable of the term. - coefficient: The coefficient of the term. - """ - - def __init__(self, variable: Variable, coefficient: float = 1.0) -> None: ... - @property - def variable(self) -> Variable: - """The variable of the term.""" - - @property - def coefficient(self) -> float: - """The coefficient of the term.""" - - def has_zero_coefficient(self) -> bool: - """Check if the coefficient of the term is zero.""" - - def add_coefficient(self, coeff: float) -> None: - """Add a coefficient to the coefficient of this term. - - Args: - coeff: The coefficient to add. - """ - - def evaluate(self, assignment: Mapping[Variable, float]) -> float: - """Evaluate the term with a given variable assignment. - - Args: - assignment: The variable assignment. - - Returns: - The evaluated value of the term. - """ - - def __mul__(self, arg: float, /) -> Term: ... - def __rmul__(self, arg: float, /) -> Term: ... - def __truediv__(self, arg: float, /) -> Term: ... - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... - -class Expression: - """A symbolic expression which consists of a sum of terms and a constant. - - The expression is of the form :math:`constant + term_1 + term_2 + \\dots + term_n`. - Alternatively, an expression can be created with a single term and a constant or just a constant. - - Args: - terms: The list of terms. - constant: The constant. - """ - - @overload - def __init__(self, constant: float = 0.0) -> None: ... - @overload - def __init__(self, terms: Sequence[Term], constant: float = 0.0) -> None: ... - @overload - def __init__(self, term: Term, constant: float = 0.0) -> None: ... - @property - def constant(self) -> float: - """The constant of the expression.""" - - @constant.setter - def constant(self, arg: float, /) -> None: ... - def __iter__(self) -> Iterator[Term]: ... - def __getitem__(self, index: int) -> Term: ... - def is_zero(self) -> bool: - """Check if the expression is zero.""" - - def is_constant(self) -> bool: - """Check if the expression is a constant.""" - - def num_terms(self) -> int: - """The number of terms in the expression.""" - - def __len__(self) -> int: ... - @property - def terms(self) -> list[Term]: - """The terms of the expression.""" - - @property - def variables(self) -> set[Variable]: - """The variables in the expression.""" - - def evaluate(self, assignment: Mapping[Variable, float]) -> float: - """Evaluate the expression with a given variable assignment. - - Args: - assignment: The variable assignment. - - Returns: - The evaluated value of the expression. - """ - - @overload - def __add__(self, arg: Expression, /) -> Expression: ... - @overload - def __add__(self, arg: float, /) -> Expression: ... - @overload - def __add__(self, arg: Term, /) -> Expression: ... - @overload - def __radd__(self, arg: Term, /) -> Expression: ... - @overload - def __radd__(self, arg: float, /) -> Expression: ... - @overload - def __sub__(self, arg: Expression, /) -> Expression: ... - @overload - def __sub__(self, arg: float, /) -> Expression: ... - @overload - def __sub__(self, arg: Term, /) -> Expression: ... - @overload - def __rsub__(self, arg: float, /) -> Expression: ... - @overload - def __rsub__(self, arg: Term, /) -> Expression: ... - def __mul__(self, arg: float, /) -> Expression: ... - def __rmul__(self, arg: float, /) -> Expression: ... - def __truediv__(self, arg: float, /) -> Expression: ... - def __eq__(self, arg: object, /) -> bool: ... - def __ne__(self, arg: object, /) -> bool: ... - def __hash__(self) -> int: ... diff --git a/python/mqt/core/load.py b/python/mqt/core/load.py deleted file mode 100644 index 61de3bcfcf..0000000000 --- a/python/mqt/core/load.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Main module for loading quantum circuits.""" - -from __future__ import annotations - -import os -from pathlib import Path -from typing import TYPE_CHECKING - -from .ir import QuantumComputation - -if TYPE_CHECKING: - from qiskit.circuit import QuantumCircuit - - -def load(input_circuit: QuantumComputation | str | os.PathLike[str] | QuantumCircuit) -> QuantumComputation: - """Load a quantum circuit from any supported format as a :class:`~mqt.core.ir.QuantumComputation`. - - Args: - input_circuit: The input circuit to translate to a :class:`~mqt.core.ir.QuantumComputation`. - This can be a :class:`~mqt.core.ir.QuantumComputation` itself, - a file name to any of the supported file formats, - an OpenQASM (2.0 or 3.0) string, or - a Qiskit :class:`~qiskit.circuit.QuantumCircuit`. - - Returns: - The :class:`~mqt.core.ir.QuantumComputation`. - - Raises: - FileNotFoundError: If the input circuit is a file name and the file does not exist. - """ - match input_circuit: - case QuantumComputation(): - return input_circuit - case str() | os.PathLike(): - input_str = str(input_circuit) - max_filename_length = 255 if os.name == "nt" else os.pathconf("/", "PC_NAME_MAX") - if len(input_str) > max_filename_length or not Path(input_str).is_file(): - if isinstance(input_circuit, os.PathLike) or "OPENQASM" not in input_circuit: - msg = f"File {input_circuit} does not exist." - raise FileNotFoundError(msg) - # otherwise, we assume that this is a QASM string - return QuantumComputation.from_qasm_str(input_str) - return QuantumComputation.from_qasm(input_str) - case _: - # At this point, we know that the input is a Qiskit QuantumCircuit - from .plugins.qiskit import qiskit_to_mqt # ruff:ignore[import-outside-top-level] lazy import - - return qiskit_to_mqt(input_circuit) diff --git a/python/mqt/core/plugins/qiskit/__init__.py b/python/mqt/core/plugins/qiskit/__init__.py index 077c5dbb38..81bb6f5b62 100644 --- a/python/mqt/core/plugins/qiskit/__init__.py +++ b/python/mqt/core/plugins/qiskit/__init__.py @@ -40,9 +40,7 @@ UnsupportedOperationError, ) from .job import QDMIJob - from .mqt_to_qiskit import mqt_to_qiskit from .provider import QDMIProvider - from .qiskit_to_mqt import qiskit_to_mqt from .sampler import QDMISampler from .serializers import ( ProgramSerializer, @@ -64,9 +62,7 @@ "TranslationError", "UnsupportedFormatError", "UnsupportedOperationError", - "mqt_to_qiskit", "program_serializer", - "qiskit_to_mqt", "register_program_serializer", "unregister_program_serializer", ] diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index 0be946e167..7070f15fd3 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -123,6 +123,20 @@ def _serialize_to_qasm3(circuit: QuantumCircuit, backend: QDMIBackend) -> str: Returns: The OpenQASM 3 program. """ + # Qiskit classical bits start at zero, while OpenQASM 3 bits are + # uninitialized. Preserve Qiskit's semantics and make every output valid + # even when the circuit measures only part of a register. + if circuit.num_clbits: + initialization = circuit.copy_empty_like(vars_mode="captures") + initialization.global_phase = 0 + for clbit in initialization.clbits: + initialization.store( + clbit, + False, # ruff: ignore[boolean-positional-value-in-call] Qiskit store arguments are positional-only. + ) + circuit = circuit.copy() + circuit.compose(initialization, front=True, inplace=True, inline_captures=True) + # Qiskit's OpenQASM3 exporter is fairly limited in terms of which gates it supports natively. # So it needs some help from us. exclusion_list = set() diff --git a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py deleted file mode 100644 index a1cd0a81f3..0000000000 --- a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py +++ /dev/null @@ -1,422 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Functionality for translating from the MQT to Qiskit.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from qiskit.circuit import ( - AncillaRegister, - ClassicalRegister, - IfElseOp, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.classical import expr -from qiskit.circuit.library import ( - DCXGate, - ECRGate, - HGate, - IGate, - PhaseGate, - RCCXGate, - RGate, - RXGate, - RXXGate, - RYGate, - RYYGate, - RZGate, - RZXGate, - RZZGate, - SdgGate, - SGate, - SwapGate, - SXdgGate, - SXGate, - TdgGate, - TGate, - U2Gate, - U3Gate, - XGate, - XXMinusYYGate, - XXPlusYYGate, - YGate, - ZGate, - iSwapGate, -) -from qiskit.transpiler.layout import Layout, TranspileLayout - -from ...ir import Permutation -from ...ir.operations import ( - ComparisonKind, - CompoundOperation, - Control, - IfElseOperation, - NonUnitaryOperation, - OpType, - StandardOperation, -) - -if TYPE_CHECKING: - from collections.abc import Mapping, Sequence - - from qiskit.circuit import Clbit, Qubit - from qiskit.circuit.singleton import SingletonGate - - from ...ir import QuantumComputation - from ...ir.operations import Operation - -__all__ = ["mqt_to_qiskit"] - - -def __dir__() -> list[str]: - return __all__ - - -def _translate_controls(controls: set[Control], qubit_map: Mapping[int, Qubit]) -> tuple[list[Qubit], str]: - """Translate a set of :class:`~mqt.core.ir.operations.Control` to Qiskit. - - Args: - controls: The controls to translate. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - - Returns: - A tuple containing the translated qubits and control states. - """ - qubits: list[Qubit] = [] - ctrl_state: str = "" - for control in controls: - qubit = qubit_map[control.qubit] - qubits.append(qubit) - # MSB to the left - ctrl_state = "1" + ctrl_state if control.type_ == Control.Type.Pos else "0" + ctrl_state - return qubits, ctrl_state - - -def _translate_targets(targets: Sequence[int], qubit_map: Mapping[int, Qubit]) -> list[Qubit]: - """Translate a sequence of target qubit indices to a list of Qiskit :class:`~qiskit.circuit.Qubit`. - - Args: - targets: The target qubit indices to translate. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - - Returns: - The translated qubits. - """ - return [qubit_map[target] for target in targets] - - -def _add_standard_operation(circ: QuantumCircuit, op: StandardOperation, qubit_map: Mapping[int, Qubit]) -> None: - """Add a :class:`~mqt.core.ir.operations.StandardOperation`. - - Args: - circ: The Qiskit circuit to add the operation to. - op: The MQT operation to add. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - - Raises: - TypeError: If the operation type is not supported. - """ - targets = _translate_targets(op.targets, qubit_map) - - if op.type_ == OpType.barrier: - circ.barrier(targets) - return - - controls, ctrl_state = _translate_controls(op.controls, qubit_map) - - gate_map_singleton: dict[OpType, SingletonGate] = { - OpType.i: IGate(), - OpType.x: XGate(), - OpType.y: YGate(), - OpType.z: ZGate(), - OpType.h: HGate(), - OpType.s: SGate(), - OpType.sdg: SdgGate(), - OpType.t: TGate(), - OpType.tdg: TdgGate(), - OpType.sx: SXGate(), - OpType.sxdg: SXdgGate(), - OpType.dcx: DCXGate(), - OpType.ecr: ECRGate(), - OpType.swap: SwapGate(), - OpType.iswap: iSwapGate(), - OpType.rccx: RCCXGate(), - } - - if op.type_ in gate_map_singleton: - gate = gate_map_singleton[op.type_] - if len(controls) == 0: - circ.append(gate, targets) - else: - circ.append(gate.control(len(controls), ctrl_state=ctrl_state), [*controls, *targets]) - return - - gate_map_single_param: dict[OpType, type] = { - OpType.rx: RXGate, - OpType.ry: RYGate, - OpType.rz: RZGate, - OpType.p: PhaseGate, - OpType.rxx: RXXGate, - OpType.ryy: RYYGate, - OpType.rzz: RZZGate, - OpType.rzx: RZXGate, - } - - if op.type_ in gate_map_single_param: - gate = gate_map_single_param[op.type_] - parameter = op.parameter[0] - if len(controls) == 0: - circ.append(gate(parameter), targets) - else: - circ.append(gate(parameter).control(len(controls), ctrl_state=ctrl_state), [*controls, *targets]) - return - - gate_map_two_param: dict[OpType, type] = { - OpType.u2: U2Gate, - OpType.r: RGate, - OpType.xx_plus_yy: XXPlusYYGate, - OpType.xx_minus_yy: XXMinusYYGate, - } - - if op.type_ in gate_map_two_param: - gate = gate_map_two_param[op.type_] - parameter1, parameter2 = op.parameter - if len(controls) == 0: - circ.append(gate(parameter1, parameter2), targets) - else: - circ.append( - gate(parameter1, parameter2).control(len(controls), ctrl_state=ctrl_state), [*controls, *targets] - ) - return - - gate_map_three_param: dict[OpType, type] = { - OpType.u: U3Gate, - } - - if op.type_ in gate_map_three_param: - gate = gate_map_three_param[op.type_] - parameter1, parameter2, parameter3 = op.parameter - if len(controls) == 0: - circ.append(gate(parameter1, parameter2, parameter3), targets) - else: - circ.append( - gate(parameter1, parameter2, parameter3).control(len(controls), ctrl_state=ctrl_state), - [*controls, *targets], - ) - return - - msg = f"Unsupported operation type: {op.type_}" - raise TypeError(msg) - - -def _add_non_unitary_operation( - circ: QuantumCircuit, - op: NonUnitaryOperation, - qubit_map: Mapping[int, Qubit], - clbit_map: Mapping[int, Clbit], -) -> None: - """Add a :class:`~mqt.core.ir.operations.NonUnitaryOperation`. - - Args: - circ: The Qiskit circuit to add the operation to. - op: The MQT operation to add. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - clbit_map: A mapping from classical bit indices to Qiskit :class:`~qiskit.circuit.Clbit`. - """ - if op.type_ == OpType.measure: - for qubit, clbit in zip(op.targets, op.classics, strict=False): - circ.measure(qubit_map[qubit], clbit_map[clbit]) - return - - if op.type_ == OpType.reset: - for qubit in op.targets: - circ.reset(qubit_map[qubit]) - return - - -def _add_compound_operation( - circ: QuantumCircuit, - op: CompoundOperation, - qubit_map: Mapping[int, Qubit], - clbit_map: Mapping[int, Clbit], -) -> None: - """Add a :class:`~mqt.core.ir.operations.CompoundOperation`. - - Args: - circ: The Qiskit circuit to add the operation to. - op: The MQT operation to add. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - clbit_map: A mapping from classical bit indices to Qiskit :class:`~qiskit.circuit.Clbit`. - """ - inner_circ = QuantumCircuit(*circ.qregs, *circ.cregs) - for inner_op in op: - _add_operation(inner_circ, inner_op, qubit_map, clbit_map) - circ.append(inner_circ.to_instruction(), circ.qubits, circ.clbits) - - -def _add_if_else_operation( - circ: QuantumCircuit, - op: IfElseOperation, - qubit_map: Mapping[int, Qubit], - clbit_map: Mapping[int, Clbit], -) -> None: - """Add a :class:`~.IfElseOperation`. - - Args: - circ: The Qiskit circuit to add the operation to. - op: The :class:`~.IfElseOperation` operation to add. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - clbit_map: A mapping from classical bit indices to Qiskit :class:`~qiskit.circuit.Clbit`. - """ - if op.control_register is not None: - left_hand_side = next(reg for reg in circ.cregs if reg.name == op.control_register.name) - right_hand_side = op.expected_value_register - else: - assert op.control_bit is not None - left_hand_side = clbit_map[op.control_bit] - right_hand_side = op.expected_value_bit - - condition: expr.Expr | tuple[ClassicalRegister | Clbit, int] - if op.comparison_kind == ComparisonKind.eq: - # directly handle equality conditions without expressions - condition = (left_hand_side, right_hand_side) - elif op.comparison_kind == ComparisonKind.neq: - condition = expr.not_equal(left_hand_side, right_hand_side) - elif op.comparison_kind == ComparisonKind.lt: - condition = expr.less(left_hand_side, right_hand_side) - elif op.comparison_kind == ComparisonKind.leq: - condition = expr.less_equal(left_hand_side, right_hand_side) - elif op.comparison_kind == ComparisonKind.gt: - condition = expr.greater(left_hand_side, right_hand_side) - elif op.comparison_kind == ComparisonKind.geq: - condition = expr.greater_equal(left_hand_side, right_hand_side) - - then_circ = QuantumCircuit(*circ.qregs, *circ.cregs) - _add_operation(then_circ, op.then_operation, qubit_map, clbit_map) - - else_circ: QuantumCircuit | None = None - if op.else_operation is not None: - else_circ = QuantumCircuit(*circ.qregs, *circ.cregs) - _add_operation(else_circ, op.else_operation, qubit_map, clbit_map) - - circ.append(IfElseOp(condition, then_circ, else_circ), circ.qubits, circ.clbits) - - -def _add_operation( - circ: QuantumCircuit, - op: Operation, - qubit_map: Mapping[int, Qubit], - clbit_map: Mapping[int, Clbit], -) -> None: - """Add an operation to a Qiskit circuit. - - Args: - circ: The Qiskit circuit to add the operation to. - op: The MQT operation to add. - qubit_map: A mapping from qubit indices to Qiskit :class:`~qiskit.circuit.Qubit`. - clbit_map: A mapping from classical bit indices to Qiskit :class:`~qiskit.circuit.Clbit`. - - Raises: - TypeError: If the operation type is not supported. - """ - if isinstance(op, StandardOperation): - _add_standard_operation(circ, op, qubit_map) - elif isinstance(op, NonUnitaryOperation): - _add_non_unitary_operation(circ, op, qubit_map, clbit_map) - elif isinstance(op, CompoundOperation): - _add_compound_operation(circ, op, qubit_map, clbit_map) - elif isinstance(op, IfElseOperation): - _add_if_else_operation(circ, op, qubit_map, clbit_map) - else: - msg = f"Unsupported operation type: {type(op)}" - raise TypeError(msg) - - -def mqt_to_qiskit(qc: QuantumComputation, *, set_layout: bool = False) -> QuantumCircuit: - """Convert a :class:`~mqt.core.ir.QuantumComputation` to a Qiskit :class:`~qiskit.circuit.QuantumCircuit`. - - Args: - qc: The MQT circuit to convert. - set_layout: If true, the :attr:`~qiskit.circuit.QuantumCircuit.layout` property is populated with the - initial layout and output permutation of the MQT circuit. - - Returns: - The converted circuit. - - Raises: - NotImplementedError: If the MQT circuit contains variables. - """ - if not qc.is_variable_free(): - msg = "Converting symbolic circuits with variables to Qiskit is not yet supported." - raise NotImplementedError(msg) - - circ = QuantumCircuit() - - if qc.name is not None: - circ.name = qc.name - - qregs = sorted((qc.qregs | qc.ancregs).values(), key=lambda reg: reg.start) - qubit_map: dict[int, Qubit] = {} - for qreg in qregs: - qiskit_reg = ( - QuantumRegister(size=qreg.size, name=qreg.name) - if qreg.name in qc.qregs - else AncillaRegister(size=qreg.size, name=qreg.name) - ) - circ.add_register(qiskit_reg) - for i, qubit in enumerate(qiskit_reg): - qubit_map[qreg.start + i] = qubit - - cregs = sorted(qc.cregs.values(), key=lambda reg: reg.start) - clbit_map: dict[int, Clbit] = {} - for creg in cregs: - qiskit_creg = ClassicalRegister(size=creg.size, name=creg.name) - circ.add_register(qiskit_creg) - for i, clbit in enumerate(qiskit_creg): - clbit_map[creg.start + i] = clbit - - for op in qc: - _add_operation(circ, op, qubit_map, clbit_map) - - if not set_layout: - return circ - - # create a list of physical qubits initialized to none, but with the correct length - p2v: list[Qubit | None] = [None] * len(circ.qubits) - # fill the list with the correct virtual qubits - for virtual, physical in qc.initial_layout.items(): - p2v[virtual] = qubit_map[physical] - initial_layout = Layout().from_qubit_list(p2v, *circ.qregs) - - # reconstruct the final layout, which is the permutation between the initial layout and the output permutation - permutation = Permutation() - for physical, virtual in qc.output_permutation.items(): - # find the virtual qubit in the initial layout and store the corresponding physical qubit - for p, v in qc.initial_layout.items(): - if v == virtual: - permutation[p] = physical - continue - - p2v = [None] * len(circ.qubits) - # fill the list with the correct virtual qubits - for physical, virtual in permutation.items(): - p2v[virtual] = qubit_map[physical] - final_layout = Layout().from_qubit_list(p2v, *circ.qregs) - - circ._layout = TranspileLayout( # ruff:ignore[private-member-access] - initial_layout=initial_layout, - input_qubit_mapping={qubit: idx for idx, qubit in qubit_map.items()}, - final_layout=final_layout, - _input_qubit_count=qc.num_qubits, - _output_qubit_list=list(final_layout.get_virtual_bits()), - ) - - return circ diff --git a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py b/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py deleted file mode 100644 index df08c38040..0000000000 --- a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py +++ /dev/null @@ -1,568 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Functionality for translating from Qiskit to the MQT.""" - -from __future__ import annotations - -import re -import warnings -from typing import TYPE_CHECKING, cast - -from qiskit.circuit import AncillaRegister, Clbit -from qiskit.circuit import ClassicalRegister as QiskitClassicalRegister -from qiskit.circuit.classical import expr - -from ...ir import QuantumComputation -from ...ir.operations import ( - ComparisonKind, - CompoundOperation, - Control, - IfElseOperation, - NonUnitaryOperation, - OpType, - StandardOperation, - SymbolicOperation, -) -from ...ir.symbolic import Expression, Term, Variable - -if TYPE_CHECKING: - from collections.abc import Mapping, Sequence - - from qiskit.circuit import IfElseOp, Instruction, ParameterExpression, QuantumCircuit, Qubit - - from ...ir.operations import Operation - from ...ir.registers import ClassicalRegister - - -__all__ = ["qiskit_to_mqt"] - - -def __dir__() -> list[str]: - return __all__ - - -def qiskit_to_mqt(circ: QuantumCircuit) -> QuantumComputation: - """Convert a Qiskit :class:`~qiskit.circuit.QuantumCircuit` to a :class:`~mqt.core.ir.QuantumComputation` object. - - Args: - circ: The Qiskit circuit to convert. - - Returns: - The converted circuit. - """ - qc = QuantumComputation() - - if circ.name is not None: - qc.name = circ.name - - qubit_index = 0 - qubit_map: dict[Qubit, int] = {} - for reg in circ.qregs: - size = reg.size - if size == 0: - continue - if isinstance(reg, AncillaRegister): - qc.add_ancillary_register(size, reg.name) - else: - qc.add_qubit_register(size, reg.name) - for qubit in reg: - qubit_map[qubit] = qubit_index - qubit_index += 1 - - clbit_index = 0 - clbit_map: dict[Clbit, int] = {} - for reg in circ.cregs: - size = reg.size - if size == 0: - continue - qc.add_classical_register(size, reg.name) - for bit in reg: - clbit_map[bit] = clbit_index - clbit_index += 1 - - try: - qc.global_phase = circ.global_phase - except TypeError: - warnings.warn( - "Symbolic global phase values are not supported yet. Setting global phase to 0.", - RuntimeWarning, - stacklevel=2, - ) - qc.global_phase = 0 - - for instruction in circ.data: - symb_params = _emplace_operation( - qc, - instruction.operation, - instruction.qubits, - instruction.clbits, - instruction.operation.params, - qubit_map, - clbit_map, - qc.cregs, - ) - for symb_param in symb_params: - qc.add_variable(symb_param) - - # import initial layout and output permutation if available - if circ.layout is not None: - _import_layouts(qc, circ) - - qc.initialize_io_mapping() - return qc - - -_NATIVELY_SUPPORTED_GATES = frozenset({ - "i", - "id", - "iden", - "x", - "y", - "z", - "h", - "s", - "sdg", - "t", - "tdg", - "p", - "u1", - "r", - "rx", - "ry", - "rz", - "u2", - "u", - "u3", - "cx", - "cy", - "cz", - "cs", - "csdg", - "cp", - "cu1", - "ch", - "crx", - "cry", - "crz", - "cu3", - "ccx", - "swap", - "cswap", - "iswap", - "sx", - "sxdg", - "csx", - "mcx", - # we keep the "mcx_*" variants here for compatibility with Qiskit 1.x - "mcx_gray", - "mcx_recursive", - "mcx_vchain", - "mcphase", - "mcrx", - "mcry", - "mcrz", - "dcx", - "ecr", - "rxx", - "ryy", - "rzx", - "rzz", - "xx_minus_yy", - "xx_plus_yy", - "rccx", - "if_else", - "reset", - "barrier", - "measure", -}) - - -def _emplace_operation( - qc: QuantumComputation | CompoundOperation, - instr: Instruction, - qargs: Sequence[Qubit], - cargs: Sequence[Clbit], - params: Sequence[float | ParameterExpression], - qubit_map: Mapping[Qubit, int], - clbit_map: Mapping[Clbit, int], - cregs: Mapping[str, ClassicalRegister], -) -> list[float | ParameterExpression]: - name = instr.name - - if name not in _NATIVELY_SUPPORTED_GATES: - try: - return _import_definition(qc, instr.definition, qargs, cargs, qubit_map, clbit_map, cregs) - except Exception as ex: # PRAGMA: NO COVER - msg = f"Unsupported gate {name} with definition {instr.definition}" - raise NotImplementedError(msg) from ex - - qubits = [qubit_map[qubit] for qubit in qargs] - - if name == "measure": - clbits = [clbit_map[clbit] for clbit in cargs] - qc.append(NonUnitaryOperation(qubits, clbits)) - return [] - - if name == "reset": - qc.append(NonUnitaryOperation(qubits)) - return [] - - if name == "barrier": - qc.append(StandardOperation(qubits, OpType.barrier)) - return [] - - if name in {"i", "id", "iden"}: - return _add_operation(qc, OpType.i, qargs, params, qubit_map) - - # we keep "mcx_gray" here for compatibility reasons with older Qiskit versions, e.g., 1.0.0 where "noancilla" - # is the default for the argument "mode" which leads to the gate name "gray_code" - if name in {"x", "cx", "ccx", "mcx", "mcx_gray"}: - return _add_operation(qc, OpType.x, qargs, params, qubit_map) - - if name in {"y", "cy"}: - return _add_operation(qc, OpType.y, qargs, params, qubit_map) - - if name in {"z", "cz"}: - return _add_operation(qc, OpType.z, qargs, params, qubit_map) - - if name in {"h", "ch"}: - return _add_operation(qc, OpType.h, qargs, params, qubit_map) - - if name in {"s", "cs"}: - return _add_operation(qc, OpType.s, qargs, params, qubit_map) - - if name in {"sdg", "csdg"}: - return _add_operation(qc, OpType.sdg, qargs, params, qubit_map) - - if name == "t": - return _add_operation(qc, OpType.t, qargs, params, qubit_map) - - if name == "tdg": - return _add_operation(qc, OpType.tdg, qargs, params, qubit_map) - - if name in {"sx", "csx"}: - return _add_operation(qc, OpType.sx, qargs, params, qubit_map) - - if name == "sxdg": - return _add_operation(qc, OpType.sxdg, qargs, params, qubit_map) - - if name == "mcx_recursive": - if len(qargs) <= 5: - return _add_operation(qc, OpType.x, qargs, params, qubit_map) - # reconfigure controls and targets (drops the last qubit as ancilla) - qargs = qargs[:-1] - return _add_operation(qc, OpType.x, qargs, params, qubit_map) - - if name == "mcx_vchain": - size = len(qargs) - num_controls = (size + 1) // 2 - # reconfigure controls and targets (drops the last num_controls - 2 qubits as ancilla) - if num_controls > 2: - qargs = qargs[: -num_controls + 2] - return _add_operation(qc, OpType.x, qargs, params, qubit_map) - - if name in {"r", "prx"}: - return _add_operation(qc, OpType.r, qargs, params, qubit_map) - - if name in {"rx", "crx", "mcrx"}: - return _add_operation(qc, OpType.rx, qargs, params, qubit_map) - - if name in {"ry", "cry", "mcry"}: - return _add_operation(qc, OpType.ry, qargs, params, qubit_map) - - if name in {"rz", "crz", "mcrz"}: - return _add_operation(qc, OpType.rz, qargs, params, qubit_map) - - if name in {"p", "u1", "cp", "cu1", "mcphase"}: - return _add_operation(qc, OpType.p, qargs, params, qubit_map) - - if name == "u2": - return _add_operation(qc, OpType.u2, qargs, params, qubit_map) - - if name in {"u", "u3", "cu3"}: - return _add_operation(qc, OpType.u, qargs, params, qubit_map) - - if name in {"swap", "cswap"}: - return _add_two_target_operation(qc, OpType.swap, qargs, params, qubit_map) - - if name == "iswap": - return _add_two_target_operation(qc, OpType.iswap, qargs, params, qubit_map) - - if name == "dcx": - return _add_two_target_operation(qc, OpType.dcx, qargs, params, qubit_map) - - if name == "ecr": - return _add_two_target_operation(qc, OpType.ecr, qargs, params, qubit_map) - - if name == "rxx": - return _add_two_target_operation(qc, OpType.rxx, qargs, params, qubit_map) - - if name == "ryy": - return _add_two_target_operation(qc, OpType.ryy, qargs, params, qubit_map) - - if name == "rzz": - return _add_two_target_operation(qc, OpType.rzz, qargs, params, qubit_map) - - if name == "rzx": - return _add_two_target_operation(qc, OpType.rzx, qargs, params, qubit_map) - - if name == "xx_minus_yy": - return _add_two_target_operation(qc, OpType.xx_minus_yy, qargs, params, qubit_map) - - if name == "xx_plus_yy": - return _add_two_target_operation(qc, OpType.xx_plus_yy, qargs, params, qubit_map) - - if name == "rccx": - qc.append(StandardOperation(qubits, OpType.rccx)) - return [] - - if name == "if_else": - return _add_if_else_operation(qc, cast("IfElseOp", instr), qargs, cargs, qubit_map, clbit_map, cregs) - - msg = f"Unsupported gate {name}" # pragma: no cover - raise NotImplementedError(msg) - - -_SUM_REGEX = re.compile(r"[+|-]?[^+-]+") -_PROD_REGEX = re.compile(r"[*/]?[^*/]+") - - -def _parse_symbolic_expression(qiskit_expr: ParameterExpression | float) -> float | Expression: - if isinstance(qiskit_expr, float): - return qiskit_expr - - expr_str = str(qiskit_expr).strip() - expr = Expression() - is_const = True - for summand in _SUM_REGEX.findall(expr_str): - sign = 1 - summand_no_operator = summand - if summand[0] == "+": - summand_no_operator = summand[1:] - elif summand[0] == "-": - summand_no_operator = summand[1:] - sign = -1 - - coeff = 1.0 - var = "" - for factor in _PROD_REGEX.findall(summand_no_operator): - is_div = False - factor_no_operator = factor - if factor[0] == "*": - factor_no_operator = factor[1:] - elif factor[0] == "/": - factor_no_operator = factor[1:] - is_div = True - - factor_no_operator = factor_no_operator.strip() - if factor_no_operator.replace(".", "").isnumeric(): - f = float(factor_no_operator) - coeff *= 1.0 / f if is_div else f - else: - var = factor_no_operator - - if not var: - expr += coeff - else: - is_const = False - expr += Term(Variable(var), sign * coeff) - - if is_const: - return expr.constant - return expr - - -def _add_operation( - qc: QuantumComputation | CompoundOperation, - type_: OpType, - qargs: Sequence[Qubit], - params: Sequence[float | ParameterExpression], - qubit_map: Mapping[Qubit, int], -) -> list[float | ParameterExpression]: - qubits = [qubit_map[qubit] for qubit in qargs] - target = qubits.pop() - controls = {Control(qubit) for qubit in qubits} - parameters = [_parse_symbolic_expression(param) for param in params] - if any(isinstance(parameter, Expression) for parameter in parameters): - qc.append(SymbolicOperation(controls, target, type_, parameters)) - else: - qc.append(StandardOperation(controls, target, type_, cast("list[float]", parameters))) - return parameters - - -def _add_two_target_operation( - qc: QuantumComputation | CompoundOperation, - type_: OpType, - qargs: Sequence[Qubit], - params: Sequence[float | ParameterExpression], - qubit_map: Mapping[Qubit, int], -) -> list[float | ParameterExpression]: - qubits = [qubit_map[qubit] for qubit in qargs] - target2 = qubits.pop() - target1 = qubits.pop() - controls = {Control(qubit) for qubit in qubits} - parameters = [_parse_symbolic_expression(param) for param in params] - if any(isinstance(parameter, Expression) for parameter in parameters): - qc.append(SymbolicOperation(controls, target1, target2, type_, parameters)) - else: - qc.append(StandardOperation(controls, target1, target2, type_, cast("list[float]", parameters))) - return parameters - - -def _add_if_else_operation( - qc: QuantumComputation | CompoundOperation, - if_else_op: IfElseOp, - qargs: Sequence[Qubit], - cargs: Sequence[Clbit], - qubit_map: Mapping[Qubit, int], - clbit_map: Mapping[Clbit, int], - cregs: Mapping[str, ClassicalRegister], -) -> list[float | ParameterExpression]: - then_operation_compound = CompoundOperation() - then_params = _import_definition( - then_operation_compound, - if_else_op.params[0], - qargs, - cargs, - qubit_map, - clbit_map, - cregs, - ) - then_operation: Operation = then_operation_compound[0] - if isinstance(then_operation, CompoundOperation) and len(then_operation) == 1: - then_operation = then_operation[0] - - else_operation: Operation | None = None - else_params: list[float | ParameterExpression] = [] - if if_else_op.params[1] is not None: - else_operation_compound = CompoundOperation() - else_params = _import_definition( - else_operation_compound, - if_else_op.params[1], - qargs, - cargs, - qubit_map, - clbit_map, - cregs, - ) - else_operation = else_operation_compound[0] - if isinstance(else_operation, CompoundOperation) and len(else_operation) == 1: - else_operation = else_operation[0] - - condition = if_else_op.condition - if isinstance(condition, tuple): - assert len(condition) == 2 - classical_control = condition[0] - expected_value = condition[1] - comparison_kind = ComparisonKind.eq - else: - assert isinstance(condition, expr.Binary) - classical_control = condition.left.var - expected_value = condition.right.value - if condition.op == expr.Binary.Op.EQUAL: - comparison_kind = ComparisonKind.eq - elif condition.op == expr.Binary.Op.NOT_EQUAL: - comparison_kind = ComparisonKind.neq - elif condition.op == expr.Binary.Op.LESS: - comparison_kind = ComparisonKind.lt - elif condition.op == expr.Binary.Op.LESS_EQUAL: - comparison_kind = ComparisonKind.leq - elif condition.op == expr.Binary.Op.GREATER: - comparison_kind = ComparisonKind.gt - elif condition.op == expr.Binary.Op.GREATER_EQUAL: - comparison_kind = ComparisonKind.geq - else: - msg = f"Unsupported binary operation {condition.op}" - raise TypeError(msg) - - if isinstance(classical_control, Clbit): - if_else_operation = IfElseOperation( - then_operation, - else_operation, - clbit_map[classical_control], - bool(expected_value), - comparison_kind, - ) - else: - assert isinstance(classical_control, QiskitClassicalRegister) - if_else_operation = IfElseOperation( - then_operation, - else_operation, - cregs[classical_control.name], - int(expected_value), - comparison_kind, - ) - qc.append(if_else_operation) - - return then_params + else_params - - -def _import_layouts(qc: QuantumComputation, circ: QuantumCircuit) -> None: - assert circ.layout is not None - qc.initial_layout.clear() - initial_index_layout = circ.layout.initial_index_layout() - for virtual, physical in enumerate(initial_index_layout): - qc.initial_layout[physical] = virtual - - # Properly mark ancillary qubits - for register in circ.layout.initial_layout.get_registers(): - if register.name != "ancilla" and not isinstance(register, AncillaRegister): - continue - for qubit in reversed(register): - physical_qubit = circ.layout.initial_layout[qubit] - virtual_qubit = qc.initial_layout[physical_qubit] - qc.set_circuit_qubit_ancillary(virtual_qubit) - - if circ.layout.final_layout is None: - qc.output_permutation = qc.initial_layout - return - - qc.output_permutation.clear() - final_index_layout = circ.layout.final_index_layout() - for virtual, physical in enumerate(final_index_layout): - qc.output_permutation[physical] = virtual - - # Properly mark garbage qubits - # Any qubit in the initial layout that is not in the final layout is garbage - for virtual_qubit in range(len(final_index_layout), len(initial_index_layout)): - qc.set_circuit_qubit_garbage(virtual_qubit) - - -def _import_definition( - qc: QuantumComputation | CompoundOperation, - circ: QuantumCircuit, - qargs: Sequence[Qubit], - cargs: Sequence[Clbit], - qubit_map: Mapping[Qubit, int], - clbit_map: Mapping[Clbit, int], - cregs: Mapping[str, ClassicalRegister], -) -> list[float | ParameterExpression]: - qarg_map = dict(zip(circ.qubits, qargs, strict=False)) - carg_map = dict(zip(circ.clbits, cargs, strict=False)) - - qc.append(CompoundOperation()) - comp_op = cast("CompoundOperation", qc[-1]) - - params = [] - for instruction in circ.data: - mapped_qargs = [qarg_map[qarg] for qarg in instruction.qubits] - mapped_cargs = [carg_map[carg] for carg in instruction.clbits] - operation = instruction.operation - new_params = _emplace_operation( - comp_op, - operation, - mapped_qargs, - mapped_cargs, - operation.params, - qubit_map, - clbit_map, - cregs, - ) - params.extend(new_params) - return params diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 58daccfade..c36efdd037 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,7 @@ if(MQT_CORE_INSTALL) set(MQT_CORE_CMAKE_VERSION_CONFIG_FILE "${MQT_CORE_CMAKE_CONFIG_DIR}/mqt-core-config-version.cmake") set(MQT_CORE_CMAKE_PROJECT_CONFIG_FILE "${MQT_CORE_CMAKE_CONFIG_DIR}/mqt-core-config.cmake") - set(MQT_CORE_CMAKE_PROJECT_TARGETS_FILE "${MQT_CORE_CMAKE_CONFIG_DIR}/mqt-core-targets.cmake") + set(MQT_CORE_CMAKE_PROJECT_TARGETS_FILE "mqt-core-targets.cmake") endif() if(NOT TARGET MQT::ProjectWarnings) @@ -59,12 +59,6 @@ endif() # add the benchmark semantic package add_subdirectory(bench) -# add the IR package -add_subdirectory(ir) - -# add the QASM package -add_subdirectory(qasm3) - # add DD package library add_subdirectory(dd) @@ -106,7 +100,7 @@ if(MQT_CORE_INSTALL) install( EXPORT ${MQT_CORE_TARGETS_EXPORT_NAME} - FILE ${MQT_CORE_PROJECT_TARGETS_FILE} + FILE ${MQT_CORE_CMAKE_PROJECT_TARGETS_FILE} NAMESPACE MQT:: DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Development) diff --git a/src/dd/CMakeLists.txt b/src/dd/CMakeLists.txt index ebd08ad931..00469d0807 100644 --- a/src/dd/CMakeLists.txt +++ b/src/dd/CMakeLists.txt @@ -23,10 +23,8 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-dd) FILES ${DD_HEADERS}) # add link libraries - target_link_libraries( - ${MQT_CORE_TARGET_NAME}-dd - PUBLIC MQT::CoreIR - PRIVATE $) + target_link_libraries(${MQT_CORE_TARGET_NAME}-dd + PRIVATE $) # generate export header include(GenerateExportHeader) diff --git a/src/dd/CachedEdge.cpp b/src/dd/CachedEdge.cpp index 7abefb20e3..6b925c4fab 100644 --- a/src/dd/CachedEdge.cpp +++ b/src/dd/CachedEdge.cpp @@ -17,7 +17,6 @@ #include "dd/MemoryManager.hpp" #include "dd/Node.hpp" #include "dd/RealNumber.hpp" -#include "ir/Definitions.hpp" #include #include @@ -167,7 +166,7 @@ auto std::hash>::operator()( const dd::CachedEdge& e) const noexcept -> std::size_t { const auto h1 = dd::murmur64(reinterpret_cast(e.p)); const auto h2 = std::hash{}(e.w); - return qc::combineHash(h1, h2); + return dd::combineHash(h1, h2); } // NOLINTNEXTLINE(bugprone-std-namespace-modification) diff --git a/src/dd/ComplexValue.cpp b/src/dd/ComplexValue.cpp index 1eb2d12d1c..9b08d13a74 100644 --- a/src/dd/ComplexValue.cpp +++ b/src/dd/ComplexValue.cpp @@ -12,7 +12,6 @@ #include "dd/DDDefinitions.hpp" #include "dd/RealNumber.hpp" -#include "ir/Definitions.hpp" #include #include @@ -306,5 +305,5 @@ std::size_t std::hash::operator()( static_cast(std::round(c.r / dd::RealNumber::eps))); const auto h2 = dd::murmur64( static_cast(std::round(c.i / dd::RealNumber::eps))); - return qc::combineHash(h1, h2); + return dd::combineHash(h1, h2); } diff --git a/src/dd/Edge.cpp b/src/dd/Edge.cpp index e7defeebd7..f1954ed3ac 100644 --- a/src/dd/Edge.cpp +++ b/src/dd/Edge.cpp @@ -16,7 +16,6 @@ #include "dd/MemoryManager.hpp" #include "dd/Node.hpp" #include "dd/RealNumber.hpp" -#include "ir/Definitions.hpp" #include #include @@ -519,7 +518,7 @@ auto std::hash>::operator()( const dd::Edge& e) const noexcept -> std::size_t { const auto h1 = dd::murmur64(reinterpret_cast(e.p)); const auto h2 = std::hash{}(e.w); - return qc::combineHash(h1, h2); + return dd::combineHash(h1, h2); } // NOLINTNEXTLINE(bugprone-std-namespace-modification) diff --git a/src/dd/FunctionalityConstruction.cpp b/src/dd/FunctionalityConstruction.cpp deleted file mode 100644 index 069b9e499a..0000000000 --- a/src/dd/FunctionalityConstruction.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "dd/FunctionalityConstruction.hpp" - -#include "dd/Operations.hpp" -#include "dd/Package.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/OpType.hpp" - -#include - -namespace dd { -MatrixDD buildFunctionality(const qc::QuantumComputation& qc, Package& dd) { - if (qc.getNqubits() == 0U) { - return MatrixDD::one(); - } - - auto permutation = qc.initialLayout; - auto e = dd.createInitialMatrix(qc.getAncillary()); - - for (const auto& op : qc) { - // SWAP gates can be executed virtually by changing the permutation - if (op->getType() == qc::OpType::SWAP && !op->isControlled()) { - const auto& targets = op->getTargets(); - std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); - continue; - } - - e = applyUnitaryOperation(*op, e, dd, permutation); - } - // correct permutation if necessary - changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceAncillae(e, qc.getAncillary()); - e = dd.reduceGarbage(e, qc.getGarbage()); - - return e; -} - -} // namespace dd diff --git a/src/dd/GateMatrixDefinitions.cpp b/src/dd/GateMatrixDefinitions.cpp index 0c86a63d72..9d35d0180f 100644 --- a/src/dd/GateMatrixDefinitions.cpp +++ b/src/dd/GateMatrixDefinitions.cpp @@ -11,7 +11,6 @@ #include "dd/GateMatrixDefinitions.hpp" #include "dd/DDDefinitions.hpp" -#include "ir/operations/OpType.hpp" #include #include @@ -160,92 +159,81 @@ TwoQubitGateMatrix xxPlusYYMat(const fp theta, const fp beta = 0.) { namespace dd { -GateMatrix opToSingleQubitGateMatrix(const qc::OpType t, +GateMatrix opToSingleQubitGateMatrix(const GateType t, const std::vector& params) { switch (t) { - case qc::I: + case GateType::I: return {1, 0, 0, 1}; - case qc::H: + case GateType::H: return {SQRT2_2, SQRT2_2, SQRT2_2, -SQRT2_2}; - case qc::X: + case GateType::X: return {0, 1, 1, 0}; - case qc::Y: + case GateType::Y: return {0, {0, -1}, {0, 1}, 0}; - case qc::Z: + case GateType::Z: return {1, 0, 0, -1}; - case qc::S: + case GateType::S: return {1, 0, 0, {0, 1}}; - case qc::Sdg: + case GateType::Sdg: return {1, 0, 0, {0, -1}}; - case qc::T: + case GateType::T: return {1, 0, 0, {SQRT2_2, SQRT2_2}}; - case qc::Tdg: + case GateType::Tdg: return {1, 0, 0, {SQRT2_2, -SQRT2_2}}; - case qc::SX: + case GateType::SX: return {std::complex{0.5, 0.5}, std::complex{0.5, -0.5}, std::complex{0.5, -0.5}, std::complex{0.5, 0.5}}; - case qc::SXdg: + case GateType::SXdg: return {std::complex{0.5, -0.5}, std::complex{0.5, 0.5}, std::complex{0.5, 0.5}, std::complex{0.5, -0.5}}; - case qc::V: - return {SQRT2_2, {0., -SQRT2_2}, {0., -SQRT2_2}, SQRT2_2}; - case qc::Vdg: - return {SQRT2_2, {0., SQRT2_2}, {0., SQRT2_2}, SQRT2_2}; - case qc::U: + case GateType::U: // shuffle parameters to match semantics of parameter <-> matrix from - // getStandardOperationDD + // getGateDD return uMat(params.at(2), params.at(1), params.at(0)); - case qc::U2: + case GateType::U2: // swap parameters to match semantics of parameter <-> matrix from - // getStandardOperationDD + // getGateDD return u2Mat(params.at(1), params.at(0)); - case qc::P: + case GateType::P: return pMat(params.at(0)); - case qc::RX: + case GateType::RX: return rxMat(params.at(0)); - case qc::RY: + case GateType::RY: return ryMat(params.at(0)); - case qc::RZ: + case GateType::RZ: return rzMat(params.at(0)); - case qc::R: + case GateType::R: return rMat(params.at(0), params.at(1)); default: throw std::invalid_argument("Invalid single-qubit gate type"); } } -TwoQubitGateMatrix opToTwoQubitGateMatrix(const qc::OpType t, +TwoQubitGateMatrix opToTwoQubitGateMatrix(const GateType t, const std::vector& params) { switch (t) { - case qc::SWAP: + case GateType::SWAP: return {{{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}}; - case qc::iSWAP: + case GateType::iSWAP: return {{{1, 0, 0, 0}, {0, 0, {0, 1}, 0}, {0, {0, 1}, 0, 0}, {0, 0, 0, 1}}}; - case qc::iSWAPdg: - return { - {{1, 0, 0, 0}, {0, 0, {0, -1}, 0}, {0, {0, -1}, 0, 0}, {0, 0, 0, 1}}}; - case qc::ECR: + case GateType::ECR: return {{{0, 0, SQRT2_2, {0, SQRT2_2}}, {0, 0, {0, SQRT2_2}, SQRT2_2}, {SQRT2_2, {0, -SQRT2_2}, 0, 0}, {std::complex{0., -SQRT2_2}, SQRT2_2, 0, 0}}}; - case qc::DCX: + case GateType::DCX: return {{{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}, {0, 1, 0, 0}}}; - case qc::Peres: - return {{{0, 0, 0, 1}, {0, 0, 1, 0}, {1, 0, 0, 0}, {0, 1, 0, 0}}}; - case qc::Peresdg: - return {{{0, 0, 1, 0}, {0, 0, 0, 1}, {0, 1, 0, 0}, {1, 0, 0, 0}}}; - case qc::RXX: + case GateType::RXX: return rxxMat(params.at(0)); - case qc::RYY: + case GateType::RYY: return ryyMat(params.at(0)); - case qc::RZZ: + case GateType::RZZ: return rzzMat(params.at(0)); - case qc::RZX: + case GateType::RZX: return rzxMat(params.at(0)); - case qc::XXminusYY: + case GateType::XXminusYY: return xxMinusYYMat(params.at(0), params.at(1)); - case qc::XXplusYY: + case GateType::XXplusYY: return xxPlusYYMat(params.at(0), params.at(1)); default: throw std::invalid_argument("Invalid two-qubit gate type"); @@ -253,10 +241,9 @@ TwoQubitGateMatrix opToTwoQubitGateMatrix(const qc::OpType t, } ThreeQubitGateMatrix -opToThreeQubitGateMatrix(const qc::OpType t, - const std::vector& /*params*/) { +opToThreeQubitGateMatrix(const GateType t, const std::vector& /*params*/) { switch (t) { - case qc::RCCX: { + case GateType::RCCX: { ThreeQubitGateMatrix matrix{}; for (size_t i = 0; i < THREE_QUBIT_GATE_DIM; ++i) { matrix[i][i] = 1.; diff --git a/src/dd/Operations.cpp b/src/dd/Operations.cpp index 76a682b989..250d3d5820 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -12,328 +12,48 @@ #include "dd/Complex.hpp" #include "dd/DDDefinitions.hpp" -#include "dd/Edge.hpp" #include "dd/GateMatrixDefinitions.hpp" #include "dd/Package.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include -#include #include -#include -#include -#include -#include #include -#include #include namespace dd { -MatrixDD getStandardOperationDD(Package& dd, const qc::OpType type, - const std::vector& params, - const qc::Controls& controls, - const std::vector& targets) { - if (qc::isSingleQubitGate(type)) { +MatrixDD getGateDD(Package& dd, const GateType type, + const std::vector& params, const Controls& controls, + const Targets& targets) { + if (isSingleQubitGate(type)) { if (targets.size() != 1) { throw std::invalid_argument( "Expected exactly one target qubit for single-qubit gate"); } return dd.makeGateDD(opToSingleQubitGateMatrix(type, params), controls, - targets[0U]); + targets[0]); } - if (qc::isTwoQubitGate(type)) { + if (isTwoQubitGate(type)) { if (targets.size() != 2) { throw std::invalid_argument( "Expected two target qubits for two-qubit gate"); } return dd.makeTwoQubitGateDD(opToTwoQubitGateMatrix(type, params), controls, - targets[0U], targets[1U]); + targets[0], targets[1]); } - if (qc::isThreeQubitGate(type)) { + if (isThreeQubitGate(type)) { if (targets.size() != 3) { throw std::invalid_argument( "Expected three target qubits for three-qubit gate"); } return dd.makeThreeQubitGateDD(opToThreeQubitGateMatrix(type, params), - controls, targets[0U], targets[1U], - targets[2U]); - } - throw std::runtime_error("Unexpected operation type"); -} - -MatrixDD getStandardOperationDD(const qc::StandardOperation& op, Package& dd, - const qc::Controls& controls, - const std::vector& targets, - const bool inverse) { - auto type = op.getType(); - - if (!inverse) { - return getStandardOperationDD(dd, type, op.getParameter(), controls, - targets); - } - - // invert the operation - std::vector params = op.getParameter(); - std::vector targetQubits = targets; - - switch (type) { - // operations that are self-inverse do not need any changes - case qc::I: - case qc::H: - case qc::X: - case qc::Y: - case qc::Z: - case qc::SWAP: - case qc::ECR: - case qc::RCCX: - break; - // operations that have an inverse gate with the same parameters - case qc::iSWAP: - case qc::iSWAPdg: - case qc::Peres: - case qc::Peresdg: - case qc::S: - case qc::Sdg: - case qc::T: - case qc::Tdg: - case qc::V: - case qc::Vdg: - case qc::SX: - case qc::SXdg: - type = static_cast(+type ^ qc::OpTypeInv); - break; - // operations that can be inversed by negating the first parameter - case qc::RXX: - case qc::RYY: - case qc::RZZ: - case qc::RZX: - case qc::RX: - case qc::RY: - case qc::RZ: - case qc::R: - case qc::P: - case qc::XXminusYY: - case qc::XXplusYY: - params[0U] = -params[0U]; - break; - // other special cases - case qc::DCX: - if (targetQubits.size() != 2) { - throw std::runtime_error("Invalid target qubits for DCX"); - } - // DCX is not self-inverse, but the inverse is just swapping the targets - std::swap(targetQubits[0], targetQubits[1]); - break; - // invert all parameters - case qc::U: - // swap [a, b, c] to [a, c, b] - std::swap(params[1U], params[2U]); - for (auto& param : params) { - param = -param; - } - break; - case qc::U2: - std::swap(params[0U], params[1U]); - params[0U] = -params[0U] - PI; - params[1U] = -params[1U] + PI; - break; - - default: - std::ostringstream oss{}; - oss << "negation for gate " << op.getName() << " not available!"; - throw std::runtime_error(oss.str()); - } - return getStandardOperationDD(dd, type, params, controls, targetQubits); -} - -MatrixDD getDD(const qc::Operation& op, Package& dd, - const qc::Permutation& permutation, const bool inverse) { - const auto type = op.getType(); - - if (type == qc::Barrier) { - return Package::makeIdent(); - } - - if (type == qc::GPhase) { - auto phase = op.getParameter()[0U]; - if (inverse) { - phase = -phase; - } - auto id = Package::makeIdent(); - id.w = dd.cn.lookup(std::cos(phase), std::sin(phase)); - return id; - } - - if (op.isStandardOperation()) { - const auto& standardOp = dynamic_cast(op); - const auto& targets = permutation.apply(standardOp.getTargets()); - const auto& controls = permutation.apply(standardOp.getControls()); - - return getStandardOperationDD(standardOp, dd, controls, targets, inverse); - } - - if (op.isCompoundOperation()) { - const auto& compoundOp = dynamic_cast(op); - auto e = Package::makeIdent(); - if (inverse) { - for (const auto& operation : compoundOp) { - e = dd.multiply(e, getInverseDD(*operation, dd, permutation)); - } - } else { - for (const auto& operation : compoundOp) { - e = dd.multiply(getDD(*operation, dd, permutation), e); - } - } - return e; - } - - assert(op.isNonUnitaryOperation()); - throw std::invalid_argument("DD for non-unitary operation not available!"); -} - -MatrixDD getInverseDD(const qc::Operation& op, Package& dd, - const qc::Permutation& permutation) { - return getDD(op, dd, permutation, true); -} - -VectorDD applyUnitaryOperation(const qc::Operation& op, const VectorDD& in, - Package& dd, - const qc::Permutation& permutation) { - return dd.applyOperation(getDD(op, dd, permutation), in); -} - -MatrixDD applyUnitaryOperation(const qc::Operation& op, const MatrixDD& in, - Package& dd, const qc::Permutation& permutation, - const bool applyFromLeft) { - return dd.applyOperation(getDD(op, dd, permutation), in, applyFromLeft); -} - -VectorDD applyMeasurement(const qc::NonUnitaryOperation& op, VectorDD in, - Package& dd, std::mt19937_64& rng, - std::vector& measurements, - const qc::Permutation& permutation) { - assert(op.getType() == qc::Measure); - const auto& qubits = permutation.apply(op.getTargets()); - const auto& bits = op.getClassics(); - for (size_t j = 0U; j < qubits.size(); ++j) { - measurements.at(bits.at(j)) = - dd.measureOneCollapsing(in, static_cast(qubits.at(j)), - rng) == '1'; - } - return in; -} - -VectorDD applyReset(const qc::NonUnitaryOperation& op, VectorDD in, Package& dd, - std::mt19937_64& rng, const qc::Permutation& permutation) { - assert(op.getType() == qc::Reset); - const auto& qubits = permutation.apply(op.getTargets()); - for (const auto& qubit : qubits) { - const auto bit = - dd.measureOneCollapsing(in, static_cast(qubit), rng); - // apply an X operation whenever the measured result is one - if (bit == '1') { - const auto x = qc::StandardOperation(qubit, qc::X); - in = applyUnitaryOperation(x, in, dd); - } - } - return in; -} - -VectorDD applyIfElseOperation(const qc::IfElseOperation& op, const VectorDD& in, - Package& dd, - const std::vector& measurements, - const qc::Permutation& permutation) { - const auto& comparisonKind = op.getComparisonKind(); - - // determine the actual value from measurements - std::uint64_t expectedValue = 0U; - auto actualValue = 0ULL; - if (const auto& controlRegister = op.getControlRegister(); - controlRegister.has_value()) { - assert(!op.getControlBit().has_value()); - expectedValue = op.getExpectedValueRegister(); - const auto regStart = controlRegister->getStartIndex(); - const auto regSize = controlRegister->getSize(); - for (std::size_t j = 0; j < regSize; ++j) { - if (measurements[regStart + j]) { - actualValue |= 1ULL << j; - } - } - } - if (const auto& controlBit = op.getControlBit(); controlBit.has_value()) { - assert(!op.getControlRegister().has_value()); - expectedValue = op.getExpectedValueBit() ? 1U : 0U; - actualValue = measurements[*controlBit] ? 1U : 0U; - } - - // check if the actual value matches the expected value according to the - // comparison kind - const auto control = [actualValue, expectedValue, comparisonKind]() { - switch (comparisonKind) { - case qc::ComparisonKind::Eq: - return actualValue == expectedValue; - case qc::ComparisonKind::Neq: - return actualValue != expectedValue; - case qc::ComparisonKind::Lt: - return actualValue < expectedValue; - case qc::ComparisonKind::Leq: - return actualValue <= expectedValue; - case qc::ComparisonKind::Gt: - return actualValue > expectedValue; - case qc::ComparisonKind::Geq: - return actualValue >= expectedValue; - } - qc::unreachable(); - }(); - - if (!control) { - auto* elseOp = op.getElseOp(); - if (elseOp == nullptr) { - return in; - } - return applyUnitaryOperation(*elseOp, in, dd, permutation); - } - - auto* thenOp = op.getThenOp(); - if (thenOp == nullptr) { - return in; - } - return applyUnitaryOperation(*thenOp, in, dd, permutation); -} - -bool isExecutableVirtually(const qc::Operation& op) noexcept { - switch (op.getType()) { - case qc::I: - case qc::Barrier: - return true; - case qc::SWAP: - return !op.isControlled(); - default: - return false; - } -} - -void applyVirtualOperation(const qc::Operation& op, - qc::Permutation& permutation) noexcept { - // SWAP gates can be executed virtually by changing the permutation - if (op.getType() == qc::SWAP) { - const auto& targets = op.getTargets(); - std::swap(permutation.at(targets[0U]), permutation.at(targets[1U])); + controls, targets[0], targets[1], + targets[2]); } + throw std::invalid_argument("Unsupported gate type"); } VectorDD applyGlobalPhase(VectorDD& in, const fp& phase, Package& dd) { in.w = dd.cn.lookup(in.w * ComplexValue{std::polar(1.0, phase)}); - return in; } diff --git a/src/dd/Package.cpp b/src/dd/Package.cpp index cbbf702f1f..cfd1e8387e 100644 --- a/src/dd/Package.cpp +++ b/src/dd/Package.cpp @@ -25,9 +25,6 @@ #include "dd/RealNumberUniqueTable.hpp" #include "dd/UnaryComputeTable.hpp" #include "dd/UniqueTable.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" #include #include @@ -172,8 +169,8 @@ namespace { } void ensureGateQubitsInRange(const std::size_t nqubits, - const qc::Controls& controls, - const std::initializer_list targets) { + const Controls& controls, + const std::initializer_list targets) { if (nqubits == 0U || std::ranges::any_of(controls, [nqubits](const auto& c) { @@ -218,14 +215,14 @@ void fillTerminalVector(std::array& em, [[nodiscard]] mCachedEdge makeControlledNode(Package& dd, const Qubit controlQubit, - const qc::Control::Type type, + const Control::Type type, const mCachedEdge& gate, const bool identity) { std::array edges{mCachedEdge::zero(), mCachedEdge::zero(), mCachedEdge::zero(), mCachedEdge::zero()}; const auto idEdge = identity ? mCachedEdge::one() : mCachedEdge::zero(); - if (type == qc::Control::Type::Neg) { + if (type == Control::Type::Neg) { edges[0] = gate; edges[3] = idEdge; } else { @@ -241,9 +238,8 @@ void fillTerminalVector(std::array& em, } template -void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, - const qc::Controls::const_iterator end, - const Qubit bound, +void wrapControlsUntil(Package& dd, Controls::const_iterator& it, + const Controls::const_iterator end, const Qubit bound, std::array, Dim>& em) { for (; it != end && it->qubit < bound; ++it) { for (std::size_t row = 0; row < Dim; ++row) { @@ -255,9 +251,9 @@ void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, } } -void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, - const qc::Controls::const_iterator end, - const Qubit bound, std::array& em) { +void wrapControlsUntil(Package& dd, Controls::const_iterator& it, + const Controls::const_iterator end, const Qubit bound, + std::array& em) { for (; it != end && it->qubit < bound; ++it) { for (std::size_t i = 0; i < NEDGE; ++i) { em[i] = makeControlledNode(dd, it->qubit, it->type, em[i], @@ -266,8 +262,8 @@ void wrapControlsUntil(Package& dd, qc::Controls::const_iterator& it, } } -void wrapControlsAbove(Package& dd, qc::Controls::const_iterator& it, - const qc::Controls::const_iterator end, mCachedEdge& e) { +void wrapControlsAbove(Package& dd, Controls::const_iterator& it, + const Controls::const_iterator end, mCachedEdge& e) { for (; it != end; ++it) { e = makeControlledNode(dd, it->qubit, it->type, e, true); } @@ -279,15 +275,15 @@ void wrapControlsAbove(Package& dd, qc::Controls::const_iterator& it, } // namespace -mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Qubit target) { - return makeGateDD(mat, qc::Controls{}, target); +mEdge Package::makeGateDD(const GateMatrix& mat, const Qubit target) { + return makeGateDD(mat, Controls{}, target); } -mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Control& control, - const qc::Qubit target) { - return makeGateDD(mat, qc::Controls{control}, target); +mEdge Package::makeGateDD(const GateMatrix& mat, const Control& control, + const Qubit target) { + return makeGateDD(mat, Controls{control}, target); } -mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Controls& controls, - const qc::Qubit target) { +mEdge Package::makeGateDD(const GateMatrix& mat, const Controls& controls, + const Qubit target) { ensureGateQubitsInRange(nqubits, controls, {target}); std::array em{}; @@ -308,20 +304,17 @@ mEdge Package::makeGateDD(const GateMatrix& mat, const qc::Controls& controls, return toMatrixDD(*this, e); } mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, - const qc::Qubit target0, - const qc::Qubit target1) { - return makeTwoQubitGateDD(mat, qc::Controls{}, target0, target1); + const Qubit target0, const Qubit target1) { + return makeTwoQubitGateDD(mat, Controls{}, target0, target1); } mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, - const qc::Control& control, - const qc::Qubit target0, - const qc::Qubit target1) { - return makeTwoQubitGateDD(mat, qc::Controls{control}, target0, target1); + const Control& control, const Qubit target0, + const Qubit target1) { + return makeTwoQubitGateDD(mat, Controls{control}, target0, target1); } mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, - const qc::Controls& controls, - const qc::Qubit target0, - const qc::Qubit target1) { + const Controls& controls, const Qubit target0, + const Qubit target1) { ensureGateQubitsInRange(nqubits, controls, {target0, target1}); std::array, NEDGE> em{}; @@ -368,24 +361,20 @@ mEdge Package::makeTwoQubitGateDD(const TwoQubitGateMatrix& mat, return toMatrixDD(*this, e); } mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, - const qc::Qubit target0, - const qc::Qubit target1, - const qc::Qubit target2) { - return makeThreeQubitGateDD(mat, qc::Controls{}, target0, target1, target2); + const Qubit target0, const Qubit target1, + const Qubit target2) { + return makeThreeQubitGateDD(mat, Controls{}, target0, target1, target2); } mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, - const qc::Control& control, - const qc::Qubit target0, - const qc::Qubit target1, - const qc::Qubit target2) { - return makeThreeQubitGateDD(mat, qc::Controls{control}, target0, target1, + const Control& control, const Qubit target0, + const Qubit target1, const Qubit target2) { + return makeThreeQubitGateDD(mat, Controls{control}, target0, target1, target2); } mEdge Package::makeThreeQubitGateDD(const ThreeQubitGateMatrix& mat, - const qc::Controls& controls, - const qc::Qubit target0, - const qc::Qubit target1, - const qc::Qubit target2) { + const Controls& controls, + const Qubit target0, const Qubit target1, + const Qubit target2) { // Bottom-up construction analogous to makeTwoQubitGateDD: materialize the // 8×8 as terminals in MSB-first order (targets[0] = high bit), sort targets // by qubit index, then reduce 8×8 → 4×4 → 4 edges → root while inserting @@ -791,7 +780,7 @@ fp Package::fidelity(const vEdge& x, const vEdge& y) { } fp Package::fidelityOfMeasurementOutcomes(const vEdge& e, const SparsePVec& probs, - const qc::Permutation& permutation) { + const Permutation& permutation) { if (e.w.approximatelyZero()) { return 0.; } @@ -842,7 +831,7 @@ ComplexValue Package::innerProduct(const vEdge& x, const vEdge& y, } fp Package::fidelityOfMeasurementOutcomesRecursive( const vEdge& e, const SparsePVec& probs, const std::size_t i, - const qc::Permutation& permutation, const std::size_t nQubits) { + const Permutation& permutation, const std::size_t nQubits) { const auto top = ComplexNumbers::mag(e.w); if (e.isTerminal()) { auto idx = i; diff --git a/src/dd/Simulation.cpp b/src/dd/Simulation.cpp deleted file mode 100644 index 2fccb7f6cc..0000000000 --- a/src/dd/Simulation.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "dd/Simulation.hpp" - -#include "dd/Operations.hpp" -#include "dd/Package.hpp" -#include "dd/StateGeneration.hpp" -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dd { - -std::map sample(const qc::QuantumComputation& qc, - const VectorDD& in, Package& dd, - const std::size_t shots, - const std::size_t seed) { - auto isDynamicCircuit = false; - auto hasMeasurements = false; - auto measurementsLast = true; - - std::mt19937_64 mt{}; - if (seed != 0U) { - mt.seed(seed); - } else { - // create and properly seed rng - std::array - randomData{}; - std::random_device rd; - std::ranges::generate(randomData, [&rd]() { return rd(); }); - std::seed_seq seeds(std::begin(randomData), std::end(randomData)); - mt.seed(seeds); - } - - std::map measurementMap{}; - - // rudimentary check whether circuit is dynamic - for (const auto& op : qc) { - // if it contains any dynamic circuit primitives, it certainly is dynamic - if (op->isIfElseOperation() || op->getType() == qc::Reset) { - isDynamicCircuit = true; - break; - } - - // once a measurement is encountered we store the corresponding mapping - // (qubit -> bit) - if (const auto* measure = dynamic_cast(op.get()); - measure != nullptr && measure->getType() == qc::Measure) { - hasMeasurements = true; - - const auto& quantum = measure->getTargets(); - const auto& classic = measure->getClassics(); - - for (std::size_t i = 0; i < quantum.size(); ++i) { - measurementMap[quantum.at(i)] = classic.at(i); - } - } - - // if an operation happens after a measurement, the resulting circuit can - // only be simulated in single shots - if (hasMeasurements && (op->isUnitary() || op->isIfElseOperation())) { - measurementsLast = false; - } - } - - if (!measurementsLast) { - isDynamicCircuit = true; - } - - if (!isDynamicCircuit) { - // if all gates are unitary (besides measurements at the end), we just - // simulate once and measure all qubits repeatedly - auto permutation = qc.initialLayout; - auto e = in; - - for (const auto& op : qc) { - // simply skip any non-unitary - if (!op->isUnitary()) { - continue; - } - - if (isExecutableVirtually(*op)) { - applyVirtualOperation(*op, permutation); - continue; - } - - e = applyUnitaryOperation(*op, e, dd, permutation); - } - - // correct permutation if necessary - if (!hasMeasurements) { - changePermutation(e, permutation, qc.outputPermutation, dd); - e = dd.reduceGarbage(e, qc.getGarbage()); - } - - // measure all qubits - std::map counts{}; - for (std::size_t i = 0U; i < shots; ++i) { - // measure all returns a string of the form "q(n-1) ... q(0)" - auto measurement = dd.measureAll(e, false, mt); - counts.operator[](measurement) += 1U; - } - // reduce reference count of measured state - dd.decRef(e); - - std::map actualCounts{}; - const auto numBits = - qc.getClassicalRegisters().empty() ? qc.getNqubits() : qc.getNcbits(); - for (const auto& [bitstring, count] : counts) { - std::string measurement(numBits, '0'); - if (hasMeasurements) { - // if the circuit contains measurements, we only want to return the - // measured bits - for (const auto& [qubit, bit] : measurementMap) { - // measurement map specifies that the circuit `qubit` is measured into - // a certain `bit` - measurement[numBits - 1U - bit] = - bitstring[bitstring.size() - 1U - permutation.at(qubit)]; - } - } else { - // otherwise, we consider the output permutation for determining where - // to measure the qubits to - for (const auto& [qubit, bit] : qc.outputPermutation) { - measurement[numBits - 1U - bit] = - bitstring[bitstring.size() - 1U - qubit]; - } - } - actualCounts[measurement] += count; - } - return actualCounts; - } - - std::map counts{}; - - for (std::size_t i = 0U; i < shots; i++) { - std::vector measurements(qc.getNcbits(), false); - - auto permutation = qc.initialLayout; - auto e = in; - dd.incRef(e); - for (const auto& op : qc) { - if (op->isUnitary()) { - // SWAP gates can be executed virtually by changing the permutation - if (isExecutableVirtually(*op)) { - applyVirtualOperation(*op, permutation); - continue; - } - - e = applyUnitaryOperation(*op, e, dd, permutation); - continue; - } - - if (op->getType() == qc::OpType::Measure) { - const auto& measure = dynamic_cast(*op); - e = applyMeasurement(measure, e, dd, mt, measurements, permutation); - continue; - } - - if (op->getType() == qc::OpType::Reset) { - const auto& reset = dynamic_cast(*op); - e = applyReset(reset, e, dd, mt, permutation); - continue; - } - - if (op->isIfElseOperation()) { - const auto& ifElse = dynamic_cast(*op); - e = applyIfElseOperation(ifElse, e, dd, measurements, permutation); - continue; - } - - qc::unreachable(); - } - - // reduce reference count of measured state - dd.decRef(e); - - std::string shot(qc.getNcbits(), '0'); - for (size_t bit = 0U; bit < qc.getNcbits(); ++bit) { - if (measurements[bit]) { - shot[qc.getNcbits() - bit - 1U] = '1'; - } - } - counts[shot]++; - } - return counts; -} - -VectorDD simulate(const qc::QuantumComputation& qc, const VectorDD& in, - Package& dd) { - auto permutation = qc.initialLayout; - auto out = in; - for (const auto& op : qc) { - if (isExecutableVirtually(*op)) { - applyVirtualOperation(*op, permutation); - } else { - out = applyUnitaryOperation(*op, out, dd, permutation); - } - } - - changePermutation(out, permutation, qc.outputPermutation, dd); - out = dd.reduceGarbage(out, qc.getGarbage()); - - // properly account for the global phase of the circuit - if (qc.hasGlobalPhase()) { - out = applyGlobalPhase(out, qc.getGlobalPhase(), dd); - } - - return out; -} - -std::map sample(const qc::QuantumComputation& qc, - const std::size_t shots, - const std::size_t seed) { - const auto nqubits = qc.getNqubits(); - const auto dd = std::make_unique(nqubits); - return sample(qc, makeZeroState(nqubits, *dd), *dd, shots, seed); -} -} // namespace dd diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt deleted file mode 100644 index 421782245b..0000000000 --- a/src/ir/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -if(NOT TARGET MQT::CoreIR) - # collect headers and source files - file(GLOB_RECURSE IR_HEADERS ${MQT_CORE_INCLUDE_BUILD_DIR}/ir/*.hpp - ${MQT_CORE_INCLUDE_BUILD_DIR}/ir/*.inc) - file(GLOB_RECURSE IR_SOURCES **.cpp) - - # create the library target (initially empty) - add_mqt_core_library(${MQT_CORE_TARGET_NAME}-ir ALIAS_NAME IR) - - # add sources to target - target_sources(${MQT_CORE_TARGET_NAME}-ir PRIVATE ${IR_SOURCES}) - - # add headers using file sets - target_sources( - ${MQT_CORE_TARGET_NAME}-ir PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} - FILES ${IR_HEADERS}) - - # generate export header - include(GenerateExportHeader) - generate_export_header(${MQT_CORE_TARGET_NAME}-ir BASE_NAME mqt_core_ir) - target_sources( - ${MQT_CORE_TARGET_NAME}-ir PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/.. - FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_ir_export.h) - if(NOT BUILD_MQT_CORE_SHARED_LIBS) - target_compile_definitions(${MQT_CORE_TARGET_NAME}-ir PUBLIC MQT_CORE_IR_STATIC_DEFINE) - endif() - - # add to list of MQT core target - set(MQT_CORE_TARGETS - ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-ir - PARENT_SCOPE) -endif() diff --git a/src/ir/CircuitOptimization.cpp b/src/ir/CircuitOptimization.cpp deleted file mode 100644 index 61e63ec3ef..0000000000 --- a/src/ir/CircuitOptimization.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -namespace { -using DAG = std::vector*>>; -using DAGReverseIterator = - std::deque*>::reverse_iterator; -using DAGReverseIterators = std::vector; - -void addToDag(DAG& dag, std::unique_ptr* op) { - const auto usedQubits = (*op)->getUsedQubits(); - for (const auto q : usedQubits) { - dag.at(q).push_back(op); - } -} - -DAG constructDAG(QuantumComputation& qc) { - auto dag = DAG(qc.getHighestPhysicalQubitIndex() + 1); - for (auto& op : qc) { - addToDag(dag, &op); - } - return dag; -} - -void removeIdentities(QuantumComputation& qc) { - auto it = qc.begin(); - while (it != qc.end()) { - if ((*it)->getType() == I) { - it = qc.erase(it); - } else if ((*it)->isCompoundOperation()) { - auto& compOp = dynamic_cast(**it); - auto cit = compOp.cbegin(); - while (cit != compOp.cend()) { - if ((*cit)->getType() == I) { - cit = compOp.erase(cit); - } else { - ++cit; - } - } - if (compOp.empty()) { - it = qc.erase(it); - } else { - if (compOp.size() == 1) { - (*it) = std::move(*(compOp.begin())); - } - ++it; - } - } else { - ++it; - } - } -} - -bool removeFinalMeasurement(DAG& dag, DAGReverseIterators& dagIterators, - Qubit idx, const DAGReverseIterator& it, - Operation* op); - -void removeFinalMeasurementsRecursive(DAG& dag, - DAGReverseIterators& dagIterators, - Qubit idx, const Operation* until) { - if (dagIterators.at(idx) == dag.at(idx).rend()) { - if (idx < static_cast(dag.size() - 1)) { - removeFinalMeasurementsRecursive(dag, dagIterators, idx + 1, nullptr); - } - return; - } - if (until != nullptr && (*dagIterators.at(idx))->get() == until) { - return; - } - - auto& it = dagIterators.at(idx); - while (it != dag.at(idx).rend()) { - if (until != nullptr && (*dagIterators.at(idx))->get() == until) { - break; - } - auto* op = (*it)->get(); - if (op->getType() == Measure || op->getType() == Barrier) { - const bool onlyMeasurement = - removeFinalMeasurement(dag, dagIterators, idx, it, op); - if (onlyMeasurement) { - for (const auto& target : op->getTargets()) { - if (dagIterators.at(target) == dag.at(target).rend()) { - break; - } - ++dagIterators.at(target); - } - } - } else if (op->isCompoundOperation() && op->isNonUnitaryOperation()) { - auto* compOp = dynamic_cast(op); - bool onlyMeasurement = true; - auto cit = compOp->rbegin(); - while (cit != compOp->rend()) { - auto* cop = cit->get(); - if (cop->getNtargets() > 0 && cop->getTargets()[0] != idx) { - ++cit; - continue; - } - onlyMeasurement = - removeFinalMeasurement(dag, dagIterators, idx, it, cop); - if (!onlyMeasurement) { - break; - } - ++cit; - } - if (onlyMeasurement) { - ++dagIterators.at(idx); - } - } else { - dagIterators.at(idx) = dag.at(idx).rend(); - break; - } - } - if (dagIterators.at(idx) == dag.at(idx).rend() && - idx < static_cast(dag.size() - 1)) { - removeFinalMeasurementsRecursive(dag, dagIterators, idx + 1, nullptr); - } -} - -bool removeFinalMeasurement(DAG& dag, DAGReverseIterators& dagIterators, - const Qubit idx, const DAGReverseIterator& it, - Operation* op) { - if (op->getNtargets() == 0) { - return false; - } - - bool onlyMeasurements = true; - for (const auto& target : op->getTargets()) { - if (target == idx) { - continue; - } - if (dagIterators.at(target) == dag.at(target).rend()) { - onlyMeasurements = false; - break; - } - removeFinalMeasurementsRecursive(dag, dagIterators, target, (*it)->get()); - if (dagIterators.at(target) == dag.at(target).rend() || - *dagIterators.at(target) != *it) { - onlyMeasurements = false; - break; - } - } - if (!onlyMeasurements) { - dagIterators.at(idx) = dag.at(idx).rend(); - } else { - op->setGate(I); - } - return onlyMeasurements; -} - -using Iterator = QuantumComputation::iterator; - -void flattenCompoundOperation(QuantumComputation& qc, Iterator& it) { - assert((*it)->isCompoundOperation()); - auto& op = dynamic_cast(**it); - auto opIt = op.begin(); - int64_t movedOperations = 0; - while (opIt != op.end()) { - it = qc.insert(it, std::move(*opIt)); - ++opIt; - ++it; - ++movedOperations; - } - it = qc.erase(it); - std::advance(it, -movedOperations); -} -} // namespace - -void QuantumComputation::removeFinalMeasurements() { - auto dag = constructDAG(*this); - DAGReverseIterators dagIterators{dag.size()}; - for (size_t q = 0; q < dag.size(); ++q) { - dagIterators.at(q) = dag.at(q).rbegin(); - } - - removeFinalMeasurementsRecursive(dag, dagIterators, 0, nullptr); - removeIdentities(*this); -} - -void QuantumComputation::flattenOperations(const bool customGatesOnly) { - auto it = begin(); - while (it != end()) { - if ((*it)->isCompoundOperation()) { - auto& op = dynamic_cast(**it); - if (!customGatesOnly || op.isCustomGate()) { - flattenCompoundOperation(*this, it); - } else { - ++it; - } - } else { - ++it; - } - } -} - -} // namespace qc diff --git a/src/ir/OpenQASMSerializer.cpp b/src/ir/OpenQASMSerializer.cpp deleted file mode 100644 index a1f9c44e1b..0000000000 --- a/src/ir/OpenQASMSerializer.cpp +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/OpenQASMSerializer.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/Register.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -namespace { - -constexpr std::size_t OUTPUT_INDENT_SIZE = 2U; - -template -void printSortedRegisters( - const std::unordered_map& registers, - const std::string& identifier, std::ostream& output, const bool openQASM3) { - std::map sortedRegs{}; - for (const auto& [name, reg] : registers) { - sortedRegs.emplace(reg.getStartIndex(), reg); - } - - for (const auto& [_, reg] : sortedRegs) { - if (openQASM3) { - output << identifier << "[" << reg.getSize() << "] " << reg.getName() - << ";\n"; - } else { - output << identifier << " " << reg.getName() << "[" << reg.getSize() - << "];\n"; - } - } -} - -template -bool isWholeRegister(const RegisterMap& regMap, const Indices& indices) { - const auto& reg = regMap.at(indices.front()).first; - auto expected = reg.getStartIndex(); - return indices.size() == reg.getSize() && - std::ranges::all_of(indices, [&expected](const auto index) { - return index == expected++; - }); -} - -void dumpControls(std::ostringstream& serialized, - const StandardOperation& operation) { - const auto& controls = operation.getControls(); - if (controls.empty()) { - return; - } - - // If an operation is in stdgates.inc, use a c prefix instead of ctrl @. - if (bool printBuiltin = std::ranges::none_of(controls, - [](const Control& control) { - return control.type == - Control::Type::Neg; - }); - printBuiltin) { - const auto numControls = controls.size(); - switch (operation.getType()) { - case P: - case RX: - case Y: - case RY: - case Z: - case RZ: - case H: - case SWAP: - printBuiltin = numControls == 1U; - break; - case X: - printBuiltin = numControls == 1U || numControls == 2U; - break; - default: - printBuiltin = false; - } - if (printBuiltin) { - serialized << std::string(numControls, 'c'); - return; - } - } - - auto currentType = controls.begin()->type; - std::size_t count = 0U; - const auto dumpModifier = [&serialized](const Control::Type type, - const std::size_t modifierCount) { - serialized << (type == Control::Type::Neg ? "negctrl" : "ctrl"); - if (modifierCount > 1U) { - serialized << "(" << modifierCount << ")"; - } - serialized << " @ "; - }; - - for (const auto& control : controls) { - if (control.type == currentType) { - ++count; - } else { - dumpModifier(currentType, count); - currentType = control.type; - count = 1U; - } - } - - dumpModifier(currentType, count); -} - -void dumpGateType(std::ostream& output, std::ostringstream& serialized, - const StandardOperation& operation, - const QubitIndexToRegisterMap& qubitMap) { - const auto type = operation.getType(); - const auto& controls = operation.getControls(); - const auto& targets = operation.getTargets(); - const auto& parameter = operation.getParameter(); - - switch (type) { - case GPhase: - serialized << "gphase(" << parameter.at(0) << ")"; - break; - case I: - serialized << "id"; - break; - case Barrier: - assert(controls.empty()); - serialized << "barrier"; - break; - case H: - serialized << "h"; - break; - case X: - serialized << "x"; - break; - case Y: - serialized << "y"; - break; - case Z: - serialized << "z"; - break; - case S: - serialized << (controls.empty() ? "s" : "p(pi/2)"); - break; - case Sdg: - serialized << (controls.empty() ? "sdg" : "p(-pi/2)"); - break; - case T: - serialized << (controls.empty() ? "t" : "p(pi/4)"); - break; - case Tdg: - serialized << (controls.empty() ? "tdg" : "p(-pi/4)"); - break; - case V: - serialized << "U(pi/2,-pi/2,pi/2)"; - break; - case Vdg: - serialized << "U(pi/2,pi/2,-pi/2)"; - break; - case U: - serialized << "U(" << parameter[0] << "," << parameter[1] << "," - << parameter[2] << ")"; - break; - case U2: - serialized << "U(pi/2," << parameter[0] << "," << parameter[1] << ")"; - break; - case P: - serialized << "p(" << parameter[0] << ")"; - break; - case SX: - serialized << "sx"; - break; - case SXdg: - serialized << "sxdg"; - break; - case RX: - serialized << "rx(" << parameter[0] << ")"; - break; - case RY: - serialized << "ry(" << parameter[0] << ")"; - break; - case RZ: - serialized << "rz(" << parameter[0] << ")"; - break; - case R: - serialized << "r(" << parameter[0] << "," << parameter[1] << ")"; - break; - case DCX: - serialized << "dcx"; - break; - case ECR: - serialized << "ecr"; - break; - case RXX: - serialized << "rxx(" << parameter[0] << ")"; - break; - case RYY: - serialized << "ryy(" << parameter[0] << ")"; - break; - case RZZ: - serialized << "rzz(" << parameter[0] << ")"; - break; - case RZX: - serialized << "rzx(" << parameter[0] << ")"; - break; - case XXminusYY: - serialized << "xx_minus_yy(" << parameter[0] << "," << parameter[1] << ")"; - break; - case XXplusYY: - serialized << "xx_plus_yy(" << parameter[0] << "," << parameter[1] << ")"; - break; - case RCCX: - serialized << "rccx"; - break; - case SWAP: - serialized << "swap"; - break; - case iSWAP: - serialized << "iswap"; - break; - case iSWAPdg: - serialized << "iswapdg"; - break; - case Peres: - output << serialized.str() << "cx"; - for (const auto& control : controls) { - output << " " << qubitMap.at(control.qubit).second << ","; - } - output << " " << qubitMap.at(targets[1]).second << ", " - << qubitMap.at(targets[0]).second << ";\n"; - - output << serialized.str() << "x"; - for (const auto& control : controls) { - output << " " << qubitMap.at(control.qubit).second << ","; - } - output << " " << qubitMap.at(targets[1]).second << ";\n"; - return; - case Peresdg: - output << serialized.str() << "x"; - for (const auto& control : controls) { - output << " " << qubitMap.at(control.qubit).second << ","; - } - output << " " << qubitMap.at(targets[1]).second << ";\n"; - - output << serialized.str() << "cx"; - for (const auto& control : controls) { - output << " " << qubitMap.at(control.qubit).second << ","; - } - output << " " << qubitMap.at(targets[1]).second << ", " - << qubitMap.at(targets[0]).second << ";\n"; - return; - default: - std::cerr << "gate type " << toString(type) - << " could not be converted to OpenQASM\n."; - } - - output << serialized.str(); - - for (auto it = controls.begin(); it != controls.end();) { - output << " " << qubitMap.at(it->qubit).second; - if (++it != controls.end() || !targets.empty()) { - output << ","; - } - } - - if (!targets.empty() && type == Barrier && - isWholeRegister(qubitMap, targets)) { - output << " " << qubitMap.at(targets.front()).first.getName(); - } else { - for (auto it = targets.begin(); it != targets.end();) { - output << " " << qubitMap.at(*it).second; - if (++it != targets.end()) { - output << ","; - } - } - } - output << ";\n"; -} - -void dumpStandardOperation(std::ostream& output, - const StandardOperation& operation, - const QubitIndexToRegisterMap& qubitMap, - const std::size_t indent, const bool openQASM3) { - std::ostringstream serialized; - serialized << std::setprecision(std::numeric_limits::digits10); - const auto indentPrefix = std::string(indent * OUTPUT_INDENT_SIZE, ' '); - serialized << indentPrefix; - - const auto& controls = operation.getControls(); - if (openQASM3) { - dumpControls(serialized, operation); - dumpGateType(output, serialized, operation, qubitMap); - return; - } - - const auto type = operation.getType(); - if ((controls.size() > 1U && type != X) || controls.size() > 2U) { - std::cout << "[WARNING] Multiple controlled gates are not natively " - "supported by OpenQASM. " - << "However, this library can parse .qasm files with multiple " - "controlled gates (e.g., cccx) correctly. " - << "Thus, while not valid vanilla OpenQASM, the dumped file will " - "work with this library.\n"; - } - - for (const auto& control : controls) { - if (control.type == Control::Type::Neg) { - output << indentPrefix << "x " << qubitMap.at(control.qubit).second - << ";\n"; - } - } - - serialized << std::string(controls.size(), 'c'); - dumpGateType(output, serialized, operation, qubitMap); - - for (const auto& control : controls) { - if (control.type == Control::Type::Neg) { - output << indentPrefix << "x " << qubitMap.at(control.qubit).second - << ";\n"; - } - } -} - -void dumpNonUnitaryOperation(std::ostream& output, - const NonUnitaryOperation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, - const std::size_t indent, const bool openQASM3) { - const auto indentPrefix = std::string(indent * OUTPUT_INDENT_SIZE, ' '); - - const auto& targets = operation.getTargets(); - const auto& classics = operation.getClassics(); - const auto type = operation.getType(); - if (isWholeRegister(qubitMap, targets) && - (type != Measure || isWholeRegister(bitMap, classics))) { - output << indentPrefix; - if (type == Measure && openQASM3) { - output << bitMap.at(classics.front()).first.getName() << " = "; - } - output << toString(type) << " " - << qubitMap.at(targets.front()).first.getName(); - if (type == Measure && !openQASM3) { - output << " -> " << bitMap.at(classics.front()).first.getName(); - } - output << ";\n"; - return; - } - - auto classicsIt = classics.cbegin(); - for (const auto& target : targets) { - output << indentPrefix; - const auto& qreg = qubitMap.at(target); - if (type == Measure && openQASM3) { - const auto& creg = bitMap.at(*classicsIt); - output << creg.second << " = "; - } - output << toString(type) << " " << qreg.second; - if (type == Measure && !openQASM3) { - const auto& creg = bitMap.at(*classicsIt); - output << " -> " << creg.second; - } - output << ";\n"; - if (type == Measure) { - ++classicsIt; - } - } -} - -void dumpOperation(std::ostream& output, const Operation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, std::size_t indent, - bool openQASM3, - const OpenQASMSerializer::CustomOperationSerializer& - customOperationSerializer); - -void dumpIfElseOperation(std::ostream& output, const IfElseOperation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, - const std::size_t indent, const bool openQASM3, - const OpenQASMSerializer::CustomOperationSerializer& - customOperationSerializer) { - const auto indentPrefix = std::string(indent * OUTPUT_INDENT_SIZE, ' '); - output << indentPrefix << "if ("; - if (const auto& controlRegister = operation.getControlRegister(); - controlRegister.has_value()) { - assert(!operation.getControlBit().has_value()); - output << controlRegister->getName() << ' ' << operation.getComparisonKind() - << ' ' << operation.getExpectedValueRegister(); - } else if (const auto& controlBit = operation.getControlBit(); - controlBit.has_value()) { - output << (!operation.getExpectedValueBit() ? "!" : "") - << bitMap.at(*controlBit).second; - } - output << ") {\n"; - - if (const auto* thenOperation = operation.getThenOp(); - thenOperation != nullptr) { - dumpOperation(output, *thenOperation, qubitMap, bitMap, indent + 1U, - openQASM3, customOperationSerializer); - } - - const auto* elseOperation = operation.getElseOp(); - if (elseOperation == nullptr) { - output << indentPrefix << "}\n"; - return; - } - - output << indentPrefix << "}"; - if (openQASM3) { - output << " else {\n"; - dumpOperation(output, *elseOperation, qubitMap, bitMap, indent + 1U, - openQASM3, customOperationSerializer); - } else { - output << '\n' << indentPrefix << "if ("; - if (const auto& controlRegister = operation.getControlRegister(); - controlRegister.has_value()) { - assert(!operation.getControlBit().has_value()); - output << controlRegister->getName() << ' ' - << getInvertedComparisonKind(operation.getComparisonKind()) << ' ' - << operation.getExpectedValueRegister(); - } - if (const auto& controlBit = operation.getControlBit(); - controlBit.has_value()) { - assert(!operation.getControlRegister().has_value()); - output << (operation.getExpectedValueBit() ? "!" : "") - << bitMap.at(*controlBit).second; - } - output << ") {\n"; - dumpOperation(output, *elseOperation, qubitMap, bitMap, indent + 1U, - openQASM3, customOperationSerializer); - } - output << indentPrefix << "}\n"; -} - -void dumpOperation(std::ostream& output, const Operation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, - const std::size_t indent, const bool openQASM3, - const OpenQASMSerializer::CustomOperationSerializer& - customOperationSerializer) { - if (dynamic_cast(&operation) != nullptr) { - if (openQASM3) { - throw std::runtime_error( - "Printing OpenQASM 3.0 parameterized gates is not supported yet!"); - } - throw std::runtime_error( - "OpenQASM 2.0 doesn't support parameterized gates!"); - } - if (const auto* ifElse = dynamic_cast(&operation); - ifElse != nullptr) { - dumpIfElseOperation(output, *ifElse, qubitMap, bitMap, indent, openQASM3, - customOperationSerializer); - return; - } - if (const auto* compound = dynamic_cast(&operation); - compound != nullptr) { - for (const auto& nestedOperation : *compound) { - dumpOperation(output, *nestedOperation, qubitMap, bitMap, indent, - openQASM3, customOperationSerializer); - } - return; - } - if (const auto* nonUnitary = - dynamic_cast(&operation); - nonUnitary != nullptr) { - dumpNonUnitaryOperation(output, *nonUnitary, qubitMap, bitMap, indent, - openQASM3); - return; - } - if (const auto* standard = dynamic_cast(&operation); - standard != nullptr) { - dumpStandardOperation(output, *standard, qubitMap, indent, openQASM3); - return; - } - if (customOperationSerializer && - customOperationSerializer(output, operation, qubitMap, bitMap, indent)) { - return; - } - throw std::invalid_argument( - "Operation type is not supported by the OpenQASM serializer."); -} - -} // namespace - -void OpenQASMSerializer::serialize( - const QuantumComputation& computation) const { - const auto openQASM3 = format == Format::OpenQASM3; - - Permutation qubitToIndex{}; - Permutation inverseInitialLayout{}; - Qubit index = 0U; - for (const auto& [physical, logical] : computation.initialLayout) { - inverseInitialLayout.emplace(logical, index); - qubitToIndex[physical] = index; - ++index; - } - output << "// i"; - for (const auto& [logical, physical] : inverseInitialLayout) { - output << " " << static_cast(physical); - } - output << "\n"; - - Permutation inverseOutputPermutation{}; - for (const auto& [physical, logical] : computation.outputPermutation) { - inverseOutputPermutation.emplace(logical, qubitToIndex[physical]); - } - output << "// o"; - for (const auto& [logical, physical] : inverseOutputPermutation) { - output << " " << physical; - } - output << "\n"; - - if (openQASM3) { - output << "OPENQASM 3.0;\n"; - output << "include \"stdgates.inc\";\n"; - } else { - output << "OPENQASM 2.0;\n"; - output << "include \"qelib1.inc\";\n"; - } - - auto combinedRegs = computation.getQuantumRegisters(); - for (const auto& reg : computation.getAncillaRegisters()) { - combinedRegs.emplace(reg); - } - printSortedRegisters(combinedRegs, openQASM3 ? "qubit" : "qreg", output, - openQASM3); - - const auto& classicalRegisters = computation.getClassicalRegisters(); - printSortedRegisters(classicalRegisters, openQASM3 ? "bit" : "creg", output, - openQASM3); - - QubitIndexToRegisterMap qubitMap{}; - for (const auto& [_, reg] : combinedRegs) { - const auto bound = reg.getStartIndex() + reg.getSize(); - for (Qubit i = reg.getStartIndex(); i < bound; ++i) { - qubitMap.try_emplace(i, reg, reg.toString(i)); - } - } - - BitIndexToRegisterMap bitMap{}; - for (const auto& [_, reg] : classicalRegisters) { - const auto bound = reg.getStartIndex() + reg.getSize(); - for (Bit i = reg.getStartIndex(); i < bound; ++i) { - bitMap.try_emplace(i, reg, reg.toString(i)); - } - } - - for (const auto& operation : computation) { - serialize(*operation, qubitMap, bitMap); - } -} - -void OpenQASMSerializer::serialize(const Operation& operation, - const QubitIndexToRegisterMap& qubitMap, - const BitIndexToRegisterMap& bitMap, - const std::size_t indent) const { - dumpOperation(output, operation, qubitMap, bitMap, indent, - format == Format::OpenQASM3, customOperationSerializer); -} - -} // namespace qc diff --git a/src/ir/Permutation.cpp b/src/ir/Permutation.cpp deleted file mode 100644 index 4cc3c7bb44..0000000000 --- a/src/ir/Permutation.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Permutation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" - -#include - -namespace qc { -[[nodiscard]] auto Permutation::apply(const Controls& controls) const - -> Controls { - if (empty()) { - return controls; - } - Controls c{}; - for (const auto& control : controls) { - c.emplace(at(control.qubit), control.type); - } - return c; -} -[[nodiscard]] auto Permutation::apply(const Targets& targets) const -> Targets { - if (empty()) { - return targets; - } - Targets t{}; - for (const auto& target : targets) { - t.emplace_back(at(target)); - } - return t; -} - -[[nodiscard]] auto Permutation::apply(const Qubit qubit) const -> Qubit { - if (empty()) { - return qubit; - } - return at(qubit); -} - -[[nodiscard]] auto Permutation::maxKey() const -> Qubit { - if (empty()) { - return 0; - } - return crbegin()->first; -} - -[[nodiscard]] auto Permutation::maxValue() const -> Qubit { - if (empty()) { - return 0; - } - return std::ranges::max_element( - permutation, - [](const auto& a, const auto& b) { return a.second < b.second; }) - ->second; -} -} // namespace qc diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp deleted file mode 100644 index 08350cfeff..0000000000 --- a/src/ir/QuantumComputation.cpp +++ /dev/null @@ -1,1535 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/QuantumComputation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/OpenQASMSerializer.hpp" -#include "ir/Register.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -namespace { -void consolidateRegister(QuantumRegisterMap& regs) { - bool finished = regs.empty(); - while (!finished) { - for (const auto& [name, qreg] : regs) { - finished = true; - // check if lower part of register - if (name.ends_with("_l")) { - auto lowidx = qreg.getStartIndex(); - auto lownum = qreg.getSize(); - // search for higher part of register - auto highname = name.substr(0, name.size() - 1) + 'h'; - if (const auto it = regs.find(highname); it != regs.end()) { - auto& highReg = it->second; - auto highidx = highReg.getStartIndex(); - auto highnum = highReg.getSize(); - // fusion of registers possible - if (lowidx + lownum == highidx) { - finished = false; - auto targetname = name.substr(0, name.size() - 2); - auto targetidx = lowidx; - auto targetnum = lownum + highnum; - regs.erase(name); - regs.erase(highname); - regs.try_emplace(targetname, targetidx, targetnum, targetname); - } - } - break; - } - } - } -} - -/** - * @brief Removes a certain qubit in a register from the register map - * @details If this was the last qubit in the register, the register is - * deleted. Removals at the beginning or the end of a register just modify the - * existing register. Removals in the middle of a register split the register - * into two new registers. The new registers are named by appending "_l" and - * "_h" to the original register name. - * @param regs A collection of all the registers - * @param reg The name of the register containing the qubit to be removed - * @param idx The index of the qubit in the register to be removed - */ -void removeQubitfromQubitRegister(QuantumRegisterMap& regs, - QuantumRegister& reg, const Qubit idx) { - if (idx == 0) { - // last remaining qubit of register - if (reg.getSize() == 1) { - // delete register - regs.erase(reg.getName()); - } - // first qubit of register - else { - reg.getStartIndex()++; - reg.getSize()--; - } - // last index - } else if (idx == reg.getSize() - 1) { - // reduce count of register - reg.getSize()--; - } else { - const auto startIndex = reg.getStartIndex(); - const auto count = reg.getSize(); - const auto lowPart = reg.getName() + "_l"; - const auto lowIndex = startIndex; - const auto lowCount = idx; - const auto highPart = reg.getName() + "_h"; - const auto highIndex = startIndex + idx + 1; - const auto highCount = count - idx - 1; - - regs.erase(reg.getName()); - regs.try_emplace(lowPart, lowIndex, lowCount, lowPart); - regs.try_emplace(highPart, highIndex, highCount, highPart); - } -} - -/** - * @brief Adds a qubit to a register in the register map - * @details If the register map is empty, a new register is created with the - * default name. If the qubit can be appended to the start or the end of an - * existing register, it is appended. Otherwise a new register is created with - * the default name and the qubit index appended. - * @param regs A collection of all the registers - * @param physicalQubitIndex The index of the qubit to be added - * @param defaultRegName The default name of the register to be created - */ -void addQubitToQubitRegister(QuantumRegisterMap& regs, Qubit physicalQubitIndex, - const std::string& defaultRegName) { - auto fusionPossible = false; - for (auto& [name, reg] : regs) { - auto& startIndex = reg.getStartIndex(); - auto& count = reg.getSize(); - // 1st case: can append to start of existing register - if (startIndex == physicalQubitIndex + 1) { - startIndex--; - count++; - fusionPossible = true; - break; - } - // 2nd case: can append to end of existing register - if (startIndex + count == physicalQubitIndex) { - count++; - fusionPossible = true; - break; - } - } - - consolidateRegister(regs); - - if (regs.empty()) { - regs.try_emplace(defaultRegName, physicalQubitIndex, 1, defaultRegName); - } else if (!fusionPossible) { - const auto newRegName = - defaultRegName + "_" + std::to_string(physicalQubitIndex); - regs.try_emplace(newRegName, physicalQubitIndex, 1, newRegName); - } -} -} // namespace - -/*** - * Public Methods - ***/ -std::size_t QuantumComputation::getNindividualOps() const { - std::size_t nops = 0; - for (const auto& op : ops) { - if (const auto* const comp = - dynamic_cast(op.get()); - comp != nullptr) { - nops += comp->size(); - } else { - ++nops; - } - } - - return nops; -} - -std::size_t QuantumComputation::getNsingleQubitOps() const { - std::size_t nops = 0; - for (const auto& op : ops) { - if (!op->isUnitary()) { - continue; - } - - if (const auto* const comp = - dynamic_cast(op.get()); - comp != nullptr) { - for (const auto& subop : *comp) { - if (subop->isUnitary() && !subop->isControlled() && - subop->getNtargets() == 1U) { - ++nops; - } - } - } else { - if (!op->isControlled() && op->getNtargets() == 1U) { - ++nops; - } - } - } - return nops; -} - -std::size_t QuantumComputation::getDepth() const { - if (empty()) { - return 0U; - } - - std::vector depths(getNqubits(), 0U); - for (const auto& op : ops) { - op->addDepthContribution(depths); - } - - return *std::ranges::max_element(depths); -} - -void QuantumComputation::initializeIOMapping() { - // try gathering (additional) output permutation information from - // measurements, e.g., a measurement - // `measure q[i] -> c[j];` - // implies that the j-th (logical) output is obtained from measuring the i-th - // physical qubit. - const bool outputPermutationFound = !outputPermutation.empty(); - - if (outputPermutationFound) { - for (const auto& opIt : ops) { - const auto* const op = dynamic_cast(opIt.get()); - if (op == nullptr || op->getType() != Measure) { - continue; - } - for (const auto& qubit : op->getTargets()) { - if (outputPermutation.find(qubit) == outputPermutation.end()) { - throw std::invalid_argument( - "[initializeIOMapping] Measured device qubit " + - std::to_string(qubit) + - " is missing from the output permutation. A non-empty output " - "permutation must contain every measured device qubit. Set a " - "consistent output permutation or clear it before " - "initialization."); - } - } - } - } - - // track whether the circuit contains measurements at the end of the circuit - // if it does, then all qubits that are not measured shall be considered - // garbage outputs - bool outputPermutationFromMeasurements = false; - std::set measuredQubits{}; - - for (const auto& opIt : ops) { - if (const auto* const op = dynamic_cast(opIt.get()); - op != nullptr && op->getType() == Measure) { - outputPermutationFromMeasurements = true; - assert(op->getTargets().size() == op->getClassics().size()); - auto classicIt = op->getClassics().cbegin(); - for (const auto& q : op->getTargets()) { - const auto qubitidx = q; - // only the first measurement of a qubit is used to determine the output - // permutation - if (measuredQubits.contains(qubitidx)) { - continue; - } - - const auto bitidx = *classicIt; - if (outputPermutationFound) { - // output permutation was already set before -> permute existing - // values - if (const auto current = outputPermutation.at(qubitidx); - static_cast(current) != bitidx) { - for (auto& p : outputPermutation) { - if (static_cast(p.second) == bitidx) { - p.second = current; - break; - } - } - outputPermutation.at(qubitidx) = static_cast(bitidx); - } - } else { - // directly set permutation if none was set beforehand - outputPermutation[qubitidx] = static_cast(bitidx); - } - measuredQubits.emplace(qubitidx); - ++classicIt; - } - } - } - - // clear any qubits that were not measured from the output permutation - // these will be marked garbage further down below - if (outputPermutationFromMeasurements) { - auto it = outputPermutation.begin(); - while (it != outputPermutation.end()) { - if (!measuredQubits.contains(it->first)) { - it = outputPermutation.erase(it); - } else { - ++it; - } - } - } - - garbage.assign(nqubits + nancillae, false); - for (const auto& [physicalIn, logicalIn] : initialLayout) { - // if the qubit is not an output, mark it as garbage - const bool isOutput = std::ranges::any_of( - outputPermutation, - [&logicIn = logicalIn](const auto& p) { return p.second == logicIn; }); - if (!isOutput) { - setLogicalQubitGarbage(logicalIn); - } - - // if the qubit is an ancillary and idle, mark it as garbage - if (const bool isIdle = isIdleQubit(physicalIn); - logicalQubitIsAncillary(logicalIn) && isIdle) { - setLogicalQubitGarbage(logicalIn); - } - } -} - -const QuantumRegister& -QuantumComputation::addQubitRegister(std::size_t nq, - const std::string& regName) { - if (quantumRegisters.contains(regName)) { - throw std::runtime_error("[addQubitRegister] Register " + regName + - " already exists"); - } - - if (nq == 0) { - throw std::runtime_error( - "[addQubitRegister] New register size must be larger than 0"); - } - - if (nancillae != 0) { - throw std::runtime_error( - "[addQubitRegister] Cannot add qubit register after ancillary " - "qubits have been added"); - } - - quantumRegisters.try_emplace(regName, static_cast(nqubits), nq, - regName); - for (std::size_t i = 0; i < nq; ++i) { - auto j = static_cast(nqubits + i); - initialLayout.emplace(j, j); - outputPermutation.emplace(j, j); - } - nqubits += nq; - ancillary.resize(nqubits + nancillae); - garbage.resize(nqubits + nancillae); - return quantumRegisters.at(regName); -} - -const ClassicalRegister& -QuantumComputation::addClassicalRegister(std::size_t nc, - const std::string& regName) { - if (classicalRegisters.contains(regName)) { - throw std::runtime_error("[addClassicalRegister] Register " + regName + - " already exists"); - } - if (nc == 0) { - throw std::runtime_error( - "[addClassicalRegister] New register size must be larger than 0"); - } - - const auto [it, success] = - classicalRegisters.try_emplace(regName, nclassics, nc, regName); - assert(success); - nclassics += nc; - return it->second; -} - -const QuantumRegister& -QuantumComputation::addAncillaryRegister(std::size_t nq, - const std::string& regName) { - if (ancillaRegisters.contains(regName)) { - throw std::runtime_error("[addAncillaryRegister] Register " + regName + - " already exists"); - } - - if (nq == 0) { - throw std::runtime_error( - "[addAncillaryRegister] New register size must be larger than 0"); - } - - const auto totalqubits = static_cast(nqubits + nancillae); - ancillaRegisters.try_emplace(regName, totalqubits, nq, regName); - ancillary.resize(totalqubits + nq); - garbage.resize(totalqubits + nq); - for (std::size_t i = 0; i < nq; ++i) { - auto j = static_cast(totalqubits + i); - initialLayout.emplace(j, j); - outputPermutation.emplace(j, j); - ancillary[j] = true; - } - nancillae += nq; - return ancillaRegisters.at(regName); -} - -std::pair> -QuantumComputation::removeQubit(const Qubit logicalQubitIndex) { - // Find index of the physical qubit i is assigned to - const auto physicalQubitIndex = getPhysicalQubitIndex(logicalQubitIndex); - - // get register and register-index of the corresponding qubit - auto& reg = getQubitRegister(physicalQubitIndex); - const auto& idx = reg.getLocalIndex(physicalQubitIndex); - - if (physicalQubitIsAncillary(physicalQubitIndex)) { - removeQubitfromQubitRegister(ancillaRegisters, reg, idx); - // reduce ancilla count - nancillae--; - } else { - removeQubitfromQubitRegister(quantumRegisters, reg, idx); - // reduce qubit count - if (ancillary.at(logicalQubitIndex)) { - // if the qubit is ancillary, it is not counted as a qubit - nancillae--; - } else { - nqubits--; - } - } - - // adjust initial layout permutation - initialLayout.erase(physicalQubitIndex); - - // remove potential output permutation entry - std::optional outputQubitIndex{}; - if (const auto it = outputPermutation.find(physicalQubitIndex); - it != outputPermutation.end()) { - outputQubitIndex = it->second; - // erasing entry - outputPermutation.erase(physicalQubitIndex); - } - - // update ancillary and garbage tracking - const auto totalQubits = nqubits + nancillae; - for (std::size_t i = logicalQubitIndex; i < totalQubits; ++i) { - ancillary[i] = ancillary[i + 1]; - garbage[i] = garbage[i + 1]; - } - // unset last entry - ancillary[totalQubits] = false; - garbage[totalQubits] = false; - - return {physicalQubitIndex, outputQubitIndex}; -} - -// adds j-th physical qubit as ancilla to the end of reg or creates the register -// if necessary -void QuantumComputation::addAncillaryQubit( - Qubit physicalQubitIndex, std::optional outputQubitIndex) { - if (initialLayout.count(physicalQubitIndex) > 0 || - outputPermutation.count(physicalQubitIndex) > 0) { - throw std::runtime_error( - "[addAncillaryQubit] Attempting to insert physical " - "qubit that is already assigned"); - } - - addQubitToQubitRegister(ancillaRegisters, physicalQubitIndex, "anc"); - - // index of logical qubit - const auto logicalQubitIndex = nqubits + nancillae; - - // resize ancillary and garbage tracking vectors - ancillary.resize(logicalQubitIndex + 1U); - garbage.resize(logicalQubitIndex + 1U); - - // increase ancillae count and mark as ancillary - nancillae++; - ancillary[logicalQubitIndex] = true; - - // adjust initial layout - initialLayout.emplace(physicalQubitIndex, - static_cast(logicalQubitIndex)); - - // adjust output permutation - if (outputQubitIndex.has_value()) { - outputPermutation.emplace(physicalQubitIndex, *outputQubitIndex); - } else { - // if a qubit is not relevant for the output, it is considered garbage - garbage[logicalQubitIndex] = true; - } -} - -void QuantumComputation::addQubit(const Qubit logicalQubitIndex, - const Qubit physicalQubitIndex, - const std::optional outputQubitIndex) { - if (initialLayout.count(physicalQubitIndex) > 0 || - outputPermutation.count(physicalQubitIndex) > 0) { - throw std::runtime_error( - "[addQubit] Attempting to insert physical qubit that is " - "already assigned"); - } - - if (logicalQubitIndex > nqubits) { - throw std::runtime_error( - "[addQubit] There are currently only " + std::to_string(nqubits) + - " qubits in the circuit. Adding " + std::to_string(logicalQubitIndex) + - " is therefore not possible at the moment."); - // TODO: this does not necessarily have to lead to an error. A new qubit - // register could be created and all ancillaries shifted - } - - addQubitToQubitRegister(quantumRegisters, physicalQubitIndex, "q"); - - // increase qubit count - nqubits++; - // adjust initial layout - initialLayout.emplace(physicalQubitIndex, logicalQubitIndex); - if (outputQubitIndex.has_value()) { - // adjust output permutation - outputPermutation.emplace(physicalQubitIndex, *outputQubitIndex); - } - - // update ancillary and garbage tracking - const auto totalQubits = nqubits + nancillae; - ancillary.resize(totalQubits); - garbage.resize(totalQubits); - for (auto i = totalQubits - 1; i > logicalQubitIndex; --i) { - ancillary[i] = ancillary[i - 1]; - garbage[i] = garbage[i - 1]; - } - // unset new entry - ancillary[logicalQubitIndex] = false; - garbage[logicalQubitIndex] = false; -} -QuantumComputation -QuantumComputation::instantiate(const VariableAssignment& assignment) const { - QuantumComputation result(*this); - result.instantiateInplace(assignment); - return result; -} - -void QuantumComputation::invert() { - for (const auto& op : ops) { - op->invert(); - } - std::ranges::reverse(ops); - - if (initialLayout.size() == outputPermutation.size()) { - std::swap(initialLayout, outputPermutation); - } else { - std::cerr << "Warning: Inverting a circuit with different initial layout " - "and output permutation sizes. This is not supported yet.\n" - "The circuit will be inverted, but the initial layout and " - "output permutation will not be swapped.\n"; - } -} - -bool QuantumComputation::operator==(const QuantumComputation& rhs) const { - if (nqubits != rhs.nqubits || nancillae != rhs.nancillae || - nclassics != rhs.nclassics || quantumRegisters != rhs.quantumRegisters || - classicalRegisters != rhs.classicalRegisters || - ancillaRegisters != rhs.ancillaRegisters || - initialLayout != rhs.initialLayout || - outputPermutation != rhs.outputPermutation || - ancillary != rhs.ancillary || garbage != rhs.garbage || - globalPhase != rhs.globalPhase || - occurringVariables != rhs.occurringVariables) { - return false; - } - - if (ops.size() != rhs.ops.size()) { - return false; - } - - for (std::size_t i = 0; i < ops.size(); ++i) { - if (*ops[i] != *rhs.ops[i]) { - return false; - } - } - - return true; -} - -std::ostream& QuantumComputation::print(std::ostream& os) const { - os << name << "\n"; - const auto width = - ops.empty() ? 1 : static_cast(std::log10(ops.size()) + 1.); - - os << std::setw(width + 1) << "i:"; - for (const auto& [physical, logical] : initialLayout) { - if (ancillary[logical]) { - os << "\033[31m"; - } - os << std::setw(4) << logical << "\033[0m"; - } - os << "\n"; - - size_t i = 0U; - for (const auto& op : ops) { - os << std::setw(width) << ++i << ":"; - op->print(os, initialLayout, static_cast(width) + 1U, - getNqubits()); - os << "\n"; - } - - os << std::setw(width + 1) << "o:"; - for (const auto& physicalQubit : initialLayout) { - auto it = outputPermutation.find(physicalQubit.first); - if (it == outputPermutation.end()) { - os << "\033[31m" << std::setw(4) << "|" << "\033[0m"; - } else { - os << std::setw(4) << it->second; - } - } - os << "\n"; - return os; -} - -void QuantumComputation::dumpOpenQASM(std::ostream& of, bool openQASM3) const { - OpenQASMSerializer(of, openQASM3 ? Format::OpenQASM3 : Format::OpenQASM2) - .serialize(*this); -} - -std::string QuantumComputation::toQASM(const bool qasm3) const { - std::stringstream ss; - dumpOpenQASM(ss, qasm3); - return ss.str(); -} -std::unique_ptr QuantumComputation::asOperation() { - if (ops.empty()) { - return {}; - } - if (ops.size() == 1) { - auto op = std::move(ops.front()); - ops.clear(); - return op; - } - return asCompoundOperation(); -} -void QuantumComputation::reset() { - ops.clear(); - nqubits = 0; - nclassics = 0; - nancillae = 0; - quantumRegisters.clear(); - classicalRegisters.clear(); - ancillaRegisters.clear(); - initialLayout.clear(); - outputPermutation.clear(); -} - -void QuantumComputation::dump(const std::string& filename, - const Format format) const { - auto of = std::ofstream(filename); - if (!of.good()) { - throw std::runtime_error("[dump] Error opening file: " + filename); - } - if (format == Format::OpenQASM3) { - dumpOpenQASM(of, true); - } else { - dumpOpenQASM(of, false); - } -} - -bool QuantumComputation::isIdleQubit(const Qubit physicalQubit) const { - return std::ranges::none_of(ops, [&physicalQubit](const auto& op) { - return op->actsOn(physicalQubit); - }); -} - -void QuantumComputation::stripIdleQubits(bool force) { - auto layoutCopy = initialLayout; - for (auto& physicalQubitIt : std::ranges::reverse_view(layoutCopy)) { - if (const auto physicalQubitIndex = physicalQubitIt.first; - isIdleQubit(physicalQubitIndex)) { - if (auto it = outputPermutation.find(physicalQubitIndex); - it != outputPermutation.end() && !force) { - continue; - } - - const auto logicalQubitIndex = initialLayout.at(physicalQubitIndex); - // check whether the logical qubit is used in the output permutation - auto usedInOutputPermutation = false; - for (const auto& [physical, logical] : outputPermutation) { - if (logical == logicalQubitIndex) { - usedInOutputPermutation = true; - break; - } - } - if (usedInOutputPermutation && !force) { - // cannot strip a logical qubit that is used in the output permutation - continue; - } - - removeQubit(logicalQubitIndex); - - if (logicalQubitIndex < nqubits + nancillae) { - for (auto& [physical, logical] : initialLayout) { - if (logical > logicalQubitIndex) { - --logical; - } - } - - for (auto& [physical, logical] : outputPermutation) { - if (logical > logicalQubitIndex) { - --logical; - } - } - } - } - } -} - -QuantumRegister& -QuantumComputation::getQubitRegister(const Qubit physicalQubitIndex) { - for (auto& reg : quantumRegisters | std::views::values) { - if (reg.contains(physicalQubitIndex)) { - return reg; - } - } - - for (auto& reg : ancillaRegisters | std::views::values) { - if (reg.contains(physicalQubitIndex)) { - return reg; - } - } - - throw std::runtime_error("[getQubitRegister] Qubit index " + - std::to_string(physicalQubitIndex) + - " not found in any register"); -} - -Qubit QuantumComputation::getPhysicalQubitIndex( - const Qubit logicalQubitIndex) const { - for (const auto& [physical, logical] : initialLayout) { - if (logical == logicalQubitIndex) { - return physical; - } - } - throw std::runtime_error("[getPhysicalQubitIndex] Logical qubit index " + - std::to_string(logicalQubitIndex) + - " not found in initial layout"); -} - -Qubit QuantumComputation::getHighestPhysicalQubitIndex() const { - return initialLayout.maxKey(); -} - -bool QuantumComputation::physicalQubitIsAncillary( - const Qubit physicalQubitIndex) const { - return std::ranges::any_of(ancillaRegisters, - [&physicalQubitIndex](const auto& reg) { - return reg.second.contains(physicalQubitIndex); - }); -} - -void QuantumComputation::setLogicalQubitAncillary( - const Qubit logicalQubitIndex) { - if (logicalQubitIsAncillary(logicalQubitIndex)) { - return; - } - - nqubits--; - nancillae++; - ancillary[logicalQubitIndex] = true; -} - -void QuantumComputation::setLogicalQubitsAncillary( - const Qubit minLogicalQubitIndex, const Qubit maxLogicalQubitIndex) { - for (Qubit i = minLogicalQubitIndex; i <= maxLogicalQubitIndex; i++) { - setLogicalQubitAncillary(i); - } -} - -void QuantumComputation::setLogicalQubitGarbage(const Qubit logicalQubitIndex) { - garbage[logicalQubitIndex] = true; - // setting a logical qubit garbage also means removing it from the output - // permutation if it was present before - for (auto it = outputPermutation.begin(); it != outputPermutation.end(); - ++it) { - if (it->second == logicalQubitIndex) { - outputPermutation.erase(it); - break; - } - } -} - -void QuantumComputation::setLogicalQubitsGarbage( - const Qubit minLogicalQubitIndex, const Qubit maxLogicalQubitIndex) { - for (Qubit i = minLogicalQubitIndex; i <= maxLogicalQubitIndex; i++) { - setLogicalQubitGarbage(i); - } -} - -[[nodiscard]] std::pair> -QuantumComputation::containsLogicalQubit(const Qubit logicalQubitIndex) const { - if (const auto it = - std::ranges::find_if(initialLayout, - [&logicalQubitIndex](const auto& mapping) { - return mapping.second == logicalQubitIndex; - }); - it != initialLayout.cend()) { - return {true, it->first}; - } - return {false, std::nullopt}; -} - -const QuantumRegister& -QuantumComputation::unifyQuantumRegisters(const std::string& regName) { - ancillaRegisters.clear(); - quantumRegisters.clear(); - nqubits += nancillae; - nancillae = 0; - quantumRegisters.try_emplace(regName, 0, nqubits, regName); - return quantumRegisters.at(regName); -} - -void QuantumComputation::checkQubitRange(const Qubit qubit) const { - if (const auto it = initialLayout.find(qubit); - it == initialLayout.end() || it->second >= getNqubits()) { - throw std::out_of_range("Qubit index out of range: " + - std::to_string(qubit)); - } -} -void QuantumComputation::checkQubitRange(const Qubit qubit, - const Controls& controls) const { - checkQubitRange(qubit); - for (const auto& [ctrl, _] : controls) { - checkQubitRange(ctrl); - } -} - -void QuantumComputation::checkQubitRange(const Qubit qubit0, const Qubit qubit1, - const Controls& controls) const { - checkQubitRange(qubit0, controls); - checkQubitRange(qubit1); -} - -void QuantumComputation::checkQubitRange(const Qubit qubit0, const Qubit qubit1, - const Qubit qubit2, - const Controls& controls) const { - checkQubitRange(qubit0, qubit1, controls); - checkQubitRange(qubit2); -} - -void QuantumComputation::checkQubitRange( - const std::vector& qubits) const { - for (const auto& qubit : qubits) { - checkQubitRange(qubit); - } -} - -void QuantumComputation::checkBitRange(const Bit bit) const { - if (bit >= nclassics) { - std::stringstream ss{}; - ss << "Classical bit index " << bit << " not found in any register"; - throw std::runtime_error(ss.str()); - } -} - -void QuantumComputation::checkBitRange(const std::vector& bits) const { - for (const auto& bit : bits) { - checkBitRange(bit); - } -} - -void QuantumComputation::checkClassicalRegister( - const ClassicalRegister& creg) const { - if (creg.getStartIndex() + creg.getSize() > nclassics) { - std::stringstream ss{}; - ss << "Classical register starting at index " << creg.getStartIndex() - << " with " << creg.getSize() << " bits is too large! The circuit has " - << nclassics << " classical bits."; - throw std::runtime_error(ss.str()); - } -} - -void QuantumComputation::reverse() { std::ranges::reverse(ops); } - -QuantumComputation::QuantumComputation(const std::size_t nq, - const std::size_t nc) { - if (nq > 0) { - addQubitRegister(nq); - } - if (nc > 0) { - addClassicalRegister(nc); - } -} - -QuantumComputation::QuantumComputation(const QuantumComputation& qc) - : nqubits(qc.nqubits), nclassics(qc.nclassics), nancillae(qc.nancillae), - name(qc.name), quantumRegisters(qc.quantumRegisters), - classicalRegisters(qc.classicalRegisters), - ancillaRegisters(qc.ancillaRegisters), ancillary(qc.ancillary), - garbage(qc.garbage), globalPhase(qc.globalPhase), - occurringVariables(qc.occurringVariables), - initialLayout(qc.initialLayout), outputPermutation(qc.outputPermutation) { - ops.reserve(qc.ops.size()); - for (const auto& op : qc.ops) { - emplace_back(op->clone()); - } -} -QuantumComputation& -QuantumComputation::operator=(const QuantumComputation& qc) { - if (this != &qc) { - nqubits = qc.nqubits; - nclassics = qc.nclassics; - nancillae = qc.nancillae; - name = qc.name; - quantumRegisters = qc.quantumRegisters; - classicalRegisters = qc.classicalRegisters; - ancillaRegisters = qc.ancillaRegisters; - globalPhase = qc.globalPhase; - occurringVariables = qc.occurringVariables; - initialLayout = qc.initialLayout; - outputPermutation = qc.outputPermutation; - ancillary = qc.ancillary; - garbage = qc.garbage; - - ops.clear(); - ops.reserve(qc.ops.size()); - for (const auto& op : qc.ops) { - emplace_back(op->clone()); - } - } - return *this; -} - -void QuantumComputation::addVariable(const SymbolOrNumber& expr) { - if (std::holds_alternative(expr)) { - const auto& sym = std::get(expr); - for (const auto& term : sym) { - occurringVariables.insert(term.getVar()); - } - } -} - -bool QuantumComputation::isVariableFree() const { - return std::ranges::all_of( - ops, [](const auto& op) { return !op->isSymbolicOperation(); }); -} - -// Instantiates this computation -void QuantumComputation::instantiateInplace( - const VariableAssignment& assignment) { - for (auto& op : ops) { - if (auto* symOp = dynamic_cast(op.get()); - symOp != nullptr) { - symOp->instantiate(assignment); - // if the operation is fully instantiated, it can be replaced by the - // corresponding standard operation - if (symOp->isStandardOperation()) { - op = std::make_unique( - *dynamic_cast(symOp)); - } - } - } - // after an operation is instantiated, the respective parameters can be - // removed from the circuit - for (const auto& [var, _] : assignment) { - occurringVariables.erase(var); - } -} - -void QuantumComputation::reorderOperations() { - using DAG = std::vector*>>; - using DAGIterator = std::deque*>::iterator; - using DAGIterators = std::vector; - - Qubit highestPhysicalQubit = 0; - for (const auto& q : initialLayout) { - highestPhysicalQubit = std::max(q.first, highestPhysicalQubit); - } - - auto dag = DAG(highestPhysicalQubit + 1); - - for (auto& op : ops) { - const auto usedQubits = op->getUsedQubits(); - for (const auto q : usedQubits) { - dag.at(q).push_back(&op); - } - } - - // initialize iterators - DAGIterators dagIterators{dag.size()}; - for (size_t q = 0; q < dag.size(); ++q) { - if (dag.at(q).empty()) { - // qubit is idle - dagIterators.at(q) = dag.at(q).end(); - } else { - // point to first operation - dagIterators.at(q) = dag.at(q).begin(); - } - } - - std::vector> newOps{}; - - // iterate over DAG in depth-first fashion starting from the top-most qubit - const auto msq = dag.size() - 1; - bool done = false; - while (!done) { - // assume that everything is done - done = true; - - // iterate over qubits in reverse order - for (auto q = static_cast>(msq); q >= 0; --q) { - // nothing to be done for this qubit - if (dagIterators.at(static_cast(q)) == - dag.at(static_cast(q)).end()) { - continue; - } - done = false; - - // get the current operation on the qubit - auto& it = dagIterators.at(static_cast(q)); - auto& op = **it; - - // check whether the gate can be scheduled, i.e. whether all qubits it - // acts on are at this operation - bool executable = true; - std::vector actsOn(dag.size()); - actsOn[static_cast(q)] = true; - for (std::size_t i = 0; i < dag.size(); ++i) { - // actually check in reverse order - const auto qb = - static_cast>(dag.size() - 1 - i); - if (qb != q && op->actsOn(static_cast(qb))) { - actsOn[static_cast(qb)] = true; - - assert(dagIterators.at(static_cast(qb)) != - dag.at(static_cast(qb)).end()); - // check whether operation is executable for the currently considered - // qubit - if (*dagIterators.at(static_cast(qb)) != *it) { - executable = false; - break; - } - } - } - - // continue, if this gate is not yet executable - if (!executable) { - continue; - } - - // gate is executable, move it to the new vector - newOps.emplace_back(std::move(op)); - - // now increase all corresponding iterators - for (std::size_t i = 0; i < dag.size(); ++i) { - if (actsOn[i]) { - ++(dagIterators.at(i)); - } - } - } - } - - // clear all the operations from the quantum circuit - ops.clear(); - // move all operations from the newly created vector to the original one - std::ranges::move(newOps, std::back_inserter(ops)); -} - -namespace { -bool isDynamicCircuit(const std::unique_ptr* op, - std::vector& measured) { - assert(op != nullptr); - const auto& it = *op; - // whenever a classic-controlled or a reset operation are encountered - // the circuit has to be dynamic. - if (it->getType() == Reset || it->isIfElseOperation()) { - return true; - } - - if (it->isStandardOperation()) { - // Whenever a qubit has already been measured, the circuit is dynamic - const auto& usedQubits = it->getUsedQubits(); - return std::ranges::any_of( - usedQubits, [&measured](const auto& q) { return measured[q]; }); - } - - if (it->getType() == Measure) { - for (const auto& b : it->getTargets()) { - measured[b] = true; - } - return false; - } - - assert(it->isCompoundOperation()); - const auto& compOp = dynamic_cast(*it); - return std::ranges::any_of(compOp, [&measured](const auto& g) { - return isDynamicCircuit(&g, measured); - }); -} -} // namespace - -bool QuantumComputation::isDynamic() const { - // marks whether a qubit in the DAG has been measured - std::vector measured(getHighestPhysicalQubitIndex() + 1, false); - return std::ranges::any_of(ops, [&measured](const auto& op) { - return ::qc::isDynamicCircuit(&op, measured); - }); -} - -QuantumComputation -QuantumComputation::fromCompoundOperation(const CompoundOperation& op) { - QuantumComputation qc{}; - Qubit maxQubitIndex = 0; - Bit maxBitIndex = 0; - for (const auto& g : op) { - // clone the gate and add it to the circuit - qc.emplace_back(g->clone()); - - // update the maximum qubit index - const auto& usedQubits = g->getUsedQubits(); - for (const auto& q : usedQubits) { - maxQubitIndex = std::max(maxQubitIndex, q); - } - - if (g->getType() == Measure) { - // update the maximum classical bit index - const auto& measureOp = dynamic_cast(*g); - const auto& classics = measureOp.getClassics(); - for (const auto& c : classics) { - maxBitIndex = std::max(maxBitIndex, c); - } - } - } - - // The following also sets the initial layout and the output permutation - qc.addQubitRegister(static_cast(maxQubitIndex) + 1); - qc.addClassicalRegister(static_cast(maxBitIndex) + 1); - - return qc; -} - -std::size_t QuantumComputation::getNoutputQubits() const noexcept { - return getNqubits() - getNgarbageQubits(); -} -std::size_t QuantumComputation::getNgarbageQubits() const { - return static_cast( - std::count(getGarbage().cbegin(), getGarbage().cend(), true)); -} - -///--------------------------------------------------------------------------- -/// \n Operations \n -///--------------------------------------------------------------------------- - -void QuantumComputation::gphase(const fp angle) { - globalPhase += angle; - // normalize to [0, 2pi) - while (globalPhase < 0) { - globalPhase += 2 * PI; - } - while (globalPhase >= 2 * PI) { - globalPhase -= 2 * PI; - } -} - -#define DEFINE_SINGLE_TARGET_OPERATION(op) \ - void QuantumComputation::op(const Qubit target) { \ - mc##op(Controls{}, target); \ - } \ - void QuantumComputation::c##op(const Control& control, const Qubit target) { \ - mc##op(Controls{control}, target); \ - } \ - void QuantumComputation::mc##op(const Controls& controls, \ - const Qubit target) { \ - checkQubitRange(target, controls); \ - emplace_back(controls, target, opTypeFromString(#op)); \ - } - -DEFINE_SINGLE_TARGET_OPERATION(i) -DEFINE_SINGLE_TARGET_OPERATION(x) -DEFINE_SINGLE_TARGET_OPERATION(y) -DEFINE_SINGLE_TARGET_OPERATION(z) -DEFINE_SINGLE_TARGET_OPERATION(h) -DEFINE_SINGLE_TARGET_OPERATION(s) -DEFINE_SINGLE_TARGET_OPERATION(sdg) -DEFINE_SINGLE_TARGET_OPERATION(t) -DEFINE_SINGLE_TARGET_OPERATION(tdg) -DEFINE_SINGLE_TARGET_OPERATION(v) -DEFINE_SINGLE_TARGET_OPERATION(vdg) -DEFINE_SINGLE_TARGET_OPERATION(sx) -DEFINE_SINGLE_TARGET_OPERATION(sxdg) - -#undef DEFINE_SINGLE_TARGET_OPERATION - -#define DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void QuantumComputation::op(const SymbolOrNumber&(param), \ - const Qubit target) { \ - mc##op(param, Controls{}, target); \ - } \ - void QuantumComputation::c##op(const SymbolOrNumber&(param), \ - const Control& control, const Qubit target) { \ - mc##op(param, Controls{control}, target); \ - } \ - void QuantumComputation::mc##op(const SymbolOrNumber&(param), \ - const Controls& controls, \ - const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param)) { \ - emplace_back(controls, target, opTypeFromString(#op), \ - std::vector{std::get(param)}); \ - } else { \ - addVariables(param); \ - emplace_back(controls, target, opTypeFromString(#op), \ - std::vector{param}); \ - } \ - } - -DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rx, theta) -DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(ry, theta) -DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(rz, theta) -DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) - -#undef DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION - -#define DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ - void QuantumComputation::op(const SymbolOrNumber&(param0), \ - const SymbolOrNumber&(param1), \ - const Qubit target) { \ - mc##op(param0, param1, Controls{}, target); \ - } \ - void QuantumComputation::c##op(const SymbolOrNumber&(param0), \ - const SymbolOrNumber&(param1), \ - const Control& control, const Qubit target) { \ - mc##op(param0, param1, Controls{control}, target); \ - } \ - void QuantumComputation::mc##op( \ - const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1)) { \ - emplace_back( \ - controls, target, opTypeFromString(#op), \ - std::vector{std::get(param0), std::get(param1)}); \ - } else { \ - addVariables(param0, param1); \ - emplace_back(controls, target, opTypeFromString(#op), \ - std::vector{param0, param1}); \ - } \ - } - -DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) -DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(r, theta, phi) - -#undef DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION - -#define DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ - param2) \ - void QuantumComputation::op( \ - const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), const Qubit target) { \ - mc##op(param0, param1, param2, Controls{}, target); \ - } \ - void QuantumComputation::c##op(const SymbolOrNumber&(param0), \ - const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), \ - const Control& control, const Qubit target) { \ - mc##op(param0, param1, param2, Controls{control}, target); \ - } \ - void QuantumComputation::mc##op( \ - const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const SymbolOrNumber&(param2), const Controls& controls, \ - const Qubit target) { \ - checkQubitRange(target, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1) && \ - std::holds_alternative(param2)) { \ - emplace_back(controls, target, opTypeFromString(#op), \ - std::vector{std::get(param0), \ - std::get(param1), \ - std::get(param2)}); \ - } else { \ - addVariables(param0, param1, param2); \ - emplace_back(controls, target, opTypeFromString(#op), \ - std::vector{param0, param1, param2}); \ - } \ - } - -DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(u, theta, phi, lambda) - -#undef DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION - -#define DEFINE_TWO_TARGET_OPERATION(op) \ - void QuantumComputation::op(const Qubit target0, const Qubit target1) { \ - mc##op(Controls{}, target0, target1); \ - } \ - void QuantumComputation::c##op(const Control& control, const Qubit target0, \ - const Qubit target1) { \ - mc##op(Controls{control}, target0, target1); \ - } \ - void QuantumComputation::mc##op(const Controls& controls, \ - const Qubit target0, const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - emplace_back(controls, target0, target1, \ - opTypeFromString(#op)); \ - } - -DEFINE_TWO_TARGET_OPERATION(swap) // NOLINT: bugprone-exception-escape -DEFINE_TWO_TARGET_OPERATION(dcx) -DEFINE_TWO_TARGET_OPERATION(ecr) -DEFINE_TWO_TARGET_OPERATION(iswap) -DEFINE_TWO_TARGET_OPERATION(iswapdg) -DEFINE_TWO_TARGET_OPERATION(peres) -DEFINE_TWO_TARGET_OPERATION(peresdg) -DEFINE_TWO_TARGET_OPERATION(move) - -#undef DEFINE_TWO_TARGET_OPERATION - -#define DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(op, param) \ - void QuantumComputation::op(const SymbolOrNumber&(param), \ - const Qubit target0, const Qubit target1) { \ - mc##op(param, Controls{}, target0, target1); \ - } \ - void QuantumComputation::c##op(const SymbolOrNumber&(param), \ - const Control& control, const Qubit target0, \ - const Qubit target1) { \ - mc##op(param, Controls{control}, target0, target1); \ - } \ - void QuantumComputation::mc##op(const SymbolOrNumber&(param), \ - const Controls& controls, \ - const Qubit target0, const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - if (std::holds_alternative(param)) { \ - emplace_back(controls, target0, target1, \ - opTypeFromString(#op), \ - std::vector{std::get(param)}); \ - } else { \ - addVariables(param); \ - emplace_back(controls, target0, target1, \ - opTypeFromString(#op), \ - std::vector{param}); \ - } \ - } - -DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rxx, theta) -DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(ryy, theta) -DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzz, theta) -DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION(rzx, theta) - -#undef DEFINE_TWO_TARGET_SINGLE_PARAMETER_OPERATION - -#define DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(op, param0, param1) \ - void QuantumComputation::op(const SymbolOrNumber&(param0), \ - const SymbolOrNumber&(param1), \ - const Qubit target0, const Qubit target1) { \ - mc##op(param0, param1, Controls{}, target0, target1); \ - } \ - void QuantumComputation::c##op( \ - const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Control& control, const Qubit target0, const Qubit target1) { \ - mc##op(param0, param1, Controls{control}, target0, target1); \ - } \ - void QuantumComputation::mc##op( \ - const SymbolOrNumber&(param0), const SymbolOrNumber&(param1), \ - const Controls& controls, const Qubit target0, const Qubit target1) { \ - checkQubitRange(target0, target1, controls); \ - if (std::holds_alternative(param0) && \ - std::holds_alternative(param1)) { \ - emplace_back( \ - controls, target0, target1, opTypeFromString(#op), \ - std::vector{std::get(param0), std::get(param1)}); \ - } else { \ - addVariables(param0, param1); \ - emplace_back(controls, target0, target1, \ - opTypeFromString(#op), \ - std::vector{param0, param1}); \ - } \ - } - -DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_minus_yy, theta, beta) -DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION(xx_plus_yy, theta, beta) - -#undef DEFINE_TWO_TARGET_TWO_PARAMETER_OPERATION - -#define DEFINE_THREE_TARGET_OPERATION(op) \ - void QuantumComputation::op(const Qubit target0, const Qubit target1, \ - const Qubit target2) { \ - mc##op(Controls{}, target0, target1, target2); \ - } \ - void QuantumComputation::c##op(const Control& control, const Qubit target0, \ - const Qubit target1, const Qubit target2) { \ - mc##op(Controls{control}, target0, target1, target2); \ - } \ - void QuantumComputation::mc##op(const Controls& controls, \ - const Qubit target0, const Qubit target1, \ - const Qubit target2) { \ - checkQubitRange(target0, target1, target2, controls); \ - emplace_back( \ - controls, Targets{target0, target1, target2}, opTypeFromString(#op)); \ - } - -DEFINE_THREE_TARGET_OPERATION(rccx) - -#undef DEFINE_THREE_TARGET_OPERATION - -void QuantumComputation::measure(const Qubit qubit, const std::size_t bit) { - checkQubitRange(qubit); - checkBitRange(bit); - emplace_back(qubit, bit); -} - -void QuantumComputation::measure(const Targets& qubits, - const std::vector& bits) { - checkQubitRange(qubits); - checkBitRange(bits); - emplace_back(qubits, bits); -} - -void QuantumComputation::measureAll(const bool addBits, const bool addBarrier) { - if (addBits) { - addClassicalRegister(getNqubits(), "meas"); - } - - if (nclassics < getNqubits()) { - std::stringstream ss{}; - ss << "The number of classical bits (" << nclassics - << ") is smaller than the number of qubits (" << getNqubits() << ")!"; - throw std::runtime_error(ss.str()); - } - - if (addBarrier) { - barrier(); - } - Qubit start = 0U; - if (addBits) { - start = static_cast(classicalRegisters.at("meas").getStartIndex()); - } - // measure i -> (start+i) in descending order - // (this is an optimization for the simulator) - for (std::size_t i = getNqubits(); i > 0; --i) { - const auto q = static_cast(i - 1); - measure(q, start + q); - } -} - -void QuantumComputation::reset(const Qubit target) { - checkQubitRange(target); - emplace_back(std::vector{target}, Reset); -} -void QuantumComputation::reset(const Targets& targets) { - checkQubitRange(targets); - emplace_back(targets, Reset); -} - -void QuantumComputation::barrier() { - std::vector targets(getNqubits()); - std::iota(targets.begin(), targets.end(), 0); - emplace_back(targets, Barrier); -} -void QuantumComputation::barrier(const Qubit target) { - checkQubitRange(target); - emplace_back(target, Barrier); -} -void QuantumComputation::barrier(const Targets& targets) { - checkQubitRange(targets); - emplace_back(targets, Barrier); -} - -void QuantumComputation::ifElse(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue, - const ComparisonKind cmp) { - checkClassicalRegister(controlRegister); - emplace_back(std::move(thenOp), std::move(elseOp), - controlRegister, expectedValue, cmp); -} -void QuantumComputation::ifElse(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const Bit controlBit, const bool expectedValue, - const ComparisonKind cmp) { - emplace_back(std::move(thenOp), std::move(elseOp), - controlBit, expectedValue, cmp); -} - -void QuantumComputation::if_(const OpType op, const Qubit target, - const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue, - const ComparisonKind cmp, - const std::vector& params) { - if_(op, target, Controls{}, controlRegister, expectedValue, cmp, params); -} -void QuantumComputation::if_(const OpType op, const Qubit target, - const Control control, - const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue, - const ComparisonKind cmp, - const std::vector& params) { - if_(op, target, Controls{control}, controlRegister, expectedValue, cmp, - params); -} -void QuantumComputation::if_(const OpType op, const Qubit target, - const Controls& controls, - const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue, - const ComparisonKind cmp, - const std::vector& params) { - checkQubitRange(target, controls); - checkClassicalRegister(controlRegister); - std::unique_ptr gate = - std::make_unique(controls, target, op, params); - emplace_back(std::move(gate), nullptr, controlRegister, - expectedValue, cmp); -} -void QuantumComputation::if_(const OpType op, const Qubit target, - const Bit controlBit, const bool expectedValue, - const ComparisonKind cmp, - const std::vector& params) { - if_(op, target, Controls{}, controlBit, expectedValue, cmp, params); -} -void QuantumComputation::if_(const OpType op, const Qubit target, - const Control control, const Bit controlBit, - const bool expectedValue, const ComparisonKind cmp, - const std::vector& params) { - if_(op, target, Controls{control}, controlBit, expectedValue, cmp, params); -} -void QuantumComputation::if_(const OpType op, const Qubit target, - const Controls& controls, const Bit controlBit, - const bool expectedValue, const ComparisonKind cmp, - const std::vector& params) { - checkQubitRange(target, controls); - checkClassicalRegister({1, controlBit}); - std::unique_ptr gate = - std::make_unique(controls, target, op, params); - emplace_back(std::move(gate), nullptr, controlBit, - expectedValue, cmp); -} - -} // namespace qc diff --git a/src/ir/operations/CompoundOperation.cpp b/src/ir/operations/CompoundOperation.cpp deleted file mode 100644 index 7116a045e7..0000000000 --- a/src/ir/operations/CompoundOperation.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/CompoundOperation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -CompoundOperation::CompoundOperation(bool isCustom) : customGate(isCustom) { - name = "Compound operation:"; - type = Compound; -} - -CompoundOperation::CompoundOperation( - std::vector>&& operations, bool isCustom) - : CompoundOperation(isCustom) { - // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) - ops = std::move(operations); -} - -CompoundOperation::CompoundOperation(const CompoundOperation& co) - : Operation(co), ops(co.ops.size()), customGate(co.customGate) { - for (std::size_t i = 0; i < co.ops.size(); ++i) { - ops[i] = co.ops[i]->clone(); - } -} - -CompoundOperation& CompoundOperation::operator=(const CompoundOperation& co) { - if (this != &co) { - Operation::operator=(co); - ops.resize(co.ops.size()); - for (std::size_t i = 0; i < co.ops.size(); ++i) { - ops[i] = co.ops[i]->clone(); - } - customGate = co.customGate; - } - return *this; -} - -std::unique_ptr CompoundOperation::clone() const { - return std::make_unique(*this); -} - -std::size_t CompoundOperation::getNqubits() const { - return getUsedQubits().size(); -} - -bool CompoundOperation::isNonUnitaryOperation() const { - return std::ranges::any_of( - ops, [](const auto& op) { return op->isNonUnitaryOperation(); }); -} - -bool CompoundOperation::isCompoundOperation() const noexcept { return true; } - -bool CompoundOperation::isCustomGate() const noexcept { return customGate; } - -bool CompoundOperation::isGlobal(const size_t nQubits) const noexcept { - const auto& params = ops.front()->getParameter(); - const auto& t = ops.front()->getType(); - return getUsedQubits().size() == nQubits && - std::ranges::all_of(ops.cbegin() + 1, ops.cend(), - [&](const auto& operation) { - return operation->isStandardOperation() && - operation->getNcontrols() == 0 && - operation->getType() == t && - operation->getParameter() == params; - }); -} - -bool CompoundOperation::isClifford() const { - return std::ranges::all_of(ops, - [](const auto& op) { return op->isClifford(); }); -} - -bool CompoundOperation::isSymbolicOperation() const { - return std::ranges::any_of( - ops, [](const auto& op) { return op->isSymbolicOperation(); }); -} - -void CompoundOperation::addControl(const Control c) { - controls.insert(c); - // we can just add the controls to each operation, as the operations will - // check if they already act on the control qubits. - for (const auto& op : ops) { - op->addControl(c); - } -} - -void CompoundOperation::clearControls() { - // we remove just our controls from nested operations - removeControls(controls); -} - -void CompoundOperation::removeControl(const Control c) { - // first we iterate over our controls and check if we are actually allowed - // to remove them - if (controls.erase(c) == 0) { - throw std::runtime_error( - "Cannot remove control from compound operation as it " - "is not a control."); - } - - for (const auto& op : ops) { - op->removeControl(c); - } -} - -Controls::iterator -CompoundOperation::removeControl(const Controls::iterator it) { - for (const auto& op : ops) { - op->removeControl(*it); - } - - return controls.erase(it); -} -bool CompoundOperation::equals(const Operation& operation) const { - if (const auto* comp = dynamic_cast(&operation)) { - if (comp->ops.size() != ops.size()) { - return false; - } - - auto it = comp->ops.cbegin(); - for (const auto& operation : ops) { - if (!operation->equals(**it)) { - return false; - } - ++it; - } - return true; - } - return false; -} - -std::ostream& CompoundOperation::print(std::ostream& os, - const Permutation& permutation, - const std::size_t prefixWidth, - const std::size_t nqubits) const { - const auto prefix = std::string(prefixWidth - 1, ' '); - os << std::string(4 * nqubits, '-') << "\n"; - for (const auto& op : ops) { - os << prefix << ":"; - op->print(os, permutation, prefixWidth, nqubits); - os << "\n"; - } - os << prefix << std::string((4 * nqubits) + 1, '-'); - return os; -} - -bool CompoundOperation::actsOn(const Qubit i) const { - return std::ranges::any_of(ops, - [&i](const auto& op) { return op->actsOn(i); }); -} - -void CompoundOperation::addDepthContribution( - std::vector& depths) const { - for (const auto& op : ops) { - op->addDepthContribution(depths); - } -} - -auto CompoundOperation::getUsedQubits() const -> std::set { - std::set usedQubits{}; - for (const auto& op : ops) { - usedQubits.merge(op->getUsedQubits()); - } - return usedQubits; -} - -auto CompoundOperation::commutesAtQubit(const Operation& other, - const Qubit& qubit) const -> bool { - return std::ranges::all_of(ops, [&other, &qubit](const auto& op) { - return op->commutesAtQubit(other, qubit); - }); -} - -auto CompoundOperation::isInverseOf(const Operation& other) const -> bool { - if (other.isCompoundOperation()) { - // cast other to CompoundOperation - const auto& co = dynamic_cast(other); - if (size() != co.size()) { - return false; - } - // here both compound operations have the same size - if (empty()) { - return true; - } - // transform compound to a QuantumComputation such that the invert method - // and the reorderOperations method can be used to get a canonical form of - // the compound operations - const auto& thisUsedQubits = getUsedQubits(); - assert(!thisUsedQubits.empty()); - const auto thisMaxQubit = *std::ranges::max_element(thisUsedQubits); - QuantumComputation thisQc(thisMaxQubit + 1); - std::ranges::for_each( - ops, [&](const auto& op) { thisQc.emplace_back(op->clone()); }); - const auto& otherUsedQubits = co.getUsedQubits(); - assert(!otherUsedQubits.empty()); - const auto otherMaxQubit = *std::ranges::max_element(otherUsedQubits); - QuantumComputation otherQc(otherMaxQubit + 1); - std::ranges::for_each( - co, [&](const auto& op) { otherQc.emplace_back(op->clone()); }); - thisQc.reorderOperations(); - otherQc.invert(); - otherQc.reorderOperations(); - return std::equal( - thisQc.cbegin(), thisQc.cend(), otherQc.cbegin(), - [](const auto& op1, const auto& op2) { return *op1 == *op2; }); - } - return false; -} - -void CompoundOperation::invert() { - for (const auto& op : ops) { - op->invert(); - } - std::ranges::reverse(ops); -} - -void CompoundOperation::apply(const Permutation& permutation) { - Operation::apply(permutation); - for (const auto& op : ops) { - op->apply(permutation); - } -} - -void CompoundOperation::merge(CompoundOperation& op) { - ops.reserve(ops.size() + op.size()); - ops.insert(ops.end(), std::make_move_iterator(op.begin()), - std::make_move_iterator(op.end())); - op.clear(); -} - -bool CompoundOperation::isConvertibleToSingleOperation() const { - if (ops.size() != 1) { - return false; - } - assert(ops.front() != nullptr); - if (!ops.front()->isCompoundOperation()) { - return true; - } - return dynamic_cast(*ops.front()) - .isConvertibleToSingleOperation(); -} - -std::unique_ptr CompoundOperation::collapseToSingleOperation() { - assert(isConvertibleToSingleOperation()); - if (!ops.front()->isCompoundOperation()) { - return std::move(ops.front()); - } - return dynamic_cast(*ops.front()) - .collapseToSingleOperation(); -} - -} // namespace qc - -std::size_t std::hash::operator()( - const qc::CompoundOperation& co) const noexcept { - std::size_t seed = 0U; - for (const auto& op : co) { - qc::hashCombine(seed, std::hash{}(*op)); - } - return seed; -} diff --git a/src/ir/operations/Expression.cpp b/src/ir/operations/Expression.cpp deleted file mode 100644 index 85f68011cb..0000000000 --- a/src/ir/operations/Expression.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Expression.hpp" - -#include -#include -#include - -namespace sym { - -Variable::Variable(const std::string& name) { - const std::scoped_lock lock(registryMutex); - if (const auto it = registered.find(name); it != registered.end()) { - id = it->second; - } else { - registered[name] = nextId; - names[nextId] = name; - id = nextId; - ++nextId; - } -} - -std::string Variable::getName() const { - const std::scoped_lock lock(registryMutex); - return names.at(id); -} - -std::ostream& operator<<(std::ostream& os, const Variable& var) { - os << var.getName(); - return os; -} -} // namespace sym diff --git a/src/ir/operations/IfElseOperation.cpp b/src/ir/operations/IfElseOperation.cpp deleted file mode 100644 index e207c49159..0000000000 --- a/src/ir/operations/IfElseOperation.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/IfElseOperation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/Register.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -ComparisonKind getInvertedComparisonKind(const ComparisonKind kind) { - switch (kind) { - case Lt: - return Geq; - case Leq: - return Gt; - case Gt: - return Leq; - case Geq: - return Lt; - case Eq: - return Neq; - case Neq: - return Eq; - } - unreachable(); -} - -std::string toString(const ComparisonKind& kind) { - switch (kind) { - case Eq: - return "=="; - case Neq: - return "!="; - case Lt: - return "<"; - case Leq: - return "<="; - case Gt: - return ">"; - case Geq: - return ">="; - } - unreachable(); -} - -std::ostream& operator<<(std::ostream& os, const ComparisonKind& kind) { - os << toString(kind); - return os; -} - -IfElseOperation::IfElseOperation(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const ClassicalRegister& controlRegister, - const std::uint64_t expectedValue, - const ComparisonKind kind) - : thenOp_(std::move(thenOp)), elseOp_(std::move(elseOp)), - controlRegister_(controlRegister), expectedValueRegister_(expectedValue), - comparisonKind_(kind) { - name = "if_else"; - type = IfElse; - canonicalize(); -} - -IfElseOperation::IfElseOperation(std::unique_ptr&& thenOp, - std::unique_ptr&& elseOp, - const Bit controlBit, const bool expectedValue, - const ComparisonKind kind) - : thenOp_(std::move(thenOp)), elseOp_(std::move(elseOp)), - controlBit_(controlBit), expectedValueBit_(expectedValue), - comparisonKind_(kind) { - name = "if_else"; - type = IfElse; - canonicalize(); -} - -IfElseOperation::IfElseOperation(const IfElseOperation& op) - : Operation(op), thenOp_(op.thenOp_ ? op.thenOp_->clone() : nullptr), - elseOp_(op.elseOp_ ? op.elseOp_->clone() : nullptr), - controlRegister_(op.controlRegister_), controlBit_(op.controlBit_), - expectedValueRegister_(op.expectedValueRegister_), - expectedValueBit_(op.expectedValueBit_), - comparisonKind_(op.comparisonKind_) {} - -IfElseOperation& IfElseOperation::operator=(const IfElseOperation& op) { - if (this != &op) { - Operation::operator=(op); - thenOp_ = op.thenOp_ ? op.thenOp_->clone() : nullptr; - elseOp_ = op.elseOp_ ? op.elseOp_->clone() : nullptr; - controlRegister_ = op.controlRegister_; - controlBit_ = op.controlBit_; - expectedValueRegister_ = op.expectedValueRegister_; - expectedValueBit_ = op.expectedValueBit_; - comparisonKind_ = op.comparisonKind_; - } - return *this; -} - -void IfElseOperation::apply(const Permutation& permutation) { - if (thenOp_) { - thenOp_->apply(permutation); - } - if (elseOp_) { - elseOp_->apply(permutation); - } -} - -bool IfElseOperation::equals(const Operation& operation) const { - if (const auto* other = dynamic_cast(&operation)) { - if (controlRegister_ != other->controlRegister_) { - return false; - } - if (controlBit_ != other->controlBit_) { - return false; - } - if (expectedValueRegister_ != other->expectedValueRegister_) { - return false; - } - if (expectedValueBit_ != other->expectedValueBit_) { - return false; - } - if (comparisonKind_ != other->comparisonKind_) { - return false; - } - if (thenOp_ && other->thenOp_) { - if (!thenOp_->equals(*other->thenOp_)) { - return false; - } - } else if (thenOp_ || other->thenOp_) { - return false; - } - if (elseOp_ && other->elseOp_) { - if (!elseOp_->equals(*other->elseOp_)) { - return false; - } - } else if (elseOp_ || other->elseOp_) { - return false; - } - return true; - } - return false; -} - -std::ostream& -IfElseOperation::print(std::ostream& os, const Permutation& permutation, - [[maybe_unused]] const std::size_t prefixWidth, - const std::size_t nqubits) const { - const std::string indent(prefixWidth, ' '); - - // print condition header line - os << indent << "\033[1m\033[35m" << "if ("; - if (controlRegister_.has_value()) { - assert(!controlBit_.has_value()); - os << controlRegister_->getName() << ' ' << comparisonKind_ << ' ' - << expectedValueRegister_; - } else if (controlBit_.has_value()) { - assert(!controlRegister_.has_value()); - os << (!expectedValueBit_ ? "!" : "") << "c[" << controlBit_.value() << "]"; - } - os << ") {\033[0m" << '\n'; // cyan brace - - // then-block - if (thenOp_) { - os << indent; - thenOp_->print(os, permutation, prefixWidth, nqubits); - } - os << '\n'; - - // else-block (only if present) - if (elseOp_) { - os << indent << " \033[1m\033[35m} else {\033[0m" << '\n' << indent; - elseOp_->print(os, permutation, prefixWidth, nqubits); - os << '\n'; - } - - // closing brace aligned with prefixWidth - os << indent << " \033[1m\033[35m}\033[0m"; - - return os; -} - -/** - * @brief Canonicalizes the IfElseOperation by normalizing its internal - * representation. - * - * This method ensures that the then/else branches and comparison kinds are in a - * standard form. - * - If the thenOp is null, swap thenOp and elseOp, and invert the comparison - * kind. - * - For single-bit control, only equality comparisons are supported; Neq is - * converted to Eq with inverted expectedValueBit. - * - If expectedValueBit is false and elseOp exists, swap thenOp and elseOp, and - * set expectedValueBit to true. - * - * This normalization simplifies further processing and ensures consistent - * behavior. - */ -void IfElseOperation::canonicalize() { - // If thenOp is null, swap thenOp and elseOp, and invert the comparison kind. - if (thenOp_ == nullptr) { - std::swap(thenOp_, elseOp_); - comparisonKind_ = getInvertedComparisonKind(comparisonKind_); - } - // If control is a single bit, only equality comparisons are supported. - if (controlBit_.has_value()) { - // Convert Neq to Eq by inverting expectedValueBit. - if (comparisonKind_ == Neq) { - comparisonKind_ = Eq; - expectedValueBit_ = !expectedValueBit_; - } - // Throw if comparison is not Eq (after possible conversion above). - if (comparisonKind_ != Eq) { - throw std::invalid_argument( - "Inequality comparisons on a single bit are not supported."); - } - // If expectedValueBit is false and elseOp exists, swap thenOp and elseOp, - // and set expectedValueBit to true. - if (!expectedValueBit_ && elseOp_ != nullptr) { - std::swap(thenOp_, elseOp_); - expectedValueBit_ = true; - } - } -} - -} // namespace qc - -std::size_t std::hash::operator()( - qc::IfElseOperation const& op) const noexcept { - std::size_t seed = 0U; - if (op.getThenOp() != nullptr) { - qc::hashCombine(seed, std::hash{}(*op.getThenOp())); - } - if (op.getElseOp() != nullptr) { - qc::hashCombine(seed, std::hash{}(*op.getElseOp())); - } - if (const auto& reg = op.getControlRegister(); reg.has_value()) { - assert(!op.getControlBit().has_value()); - qc::hashCombine(seed, std::hash{}(*reg)); - qc::hashCombine(seed, op.getExpectedValueRegister()); - } - if (const auto& bit = op.getControlBit(); bit.has_value()) { - assert(!op.getControlRegister().has_value()); - qc::hashCombine(seed, *bit); - qc::hashCombine(seed, static_cast(op.getExpectedValueBit())); - } - qc::hashCombine(seed, op.getComparisonKind()); - return seed; -} diff --git a/src/ir/operations/NonUnitaryOperation.cpp b/src/ir/operations/NonUnitaryOperation.cpp deleted file mode 100644 index f0f57c2931..0000000000 --- a/src/ir/operations/NonUnitaryOperation.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/* - * This file is part of MQT QFR library which is released under the MIT license. - * See file README.md or go to https://www.cda.cit.tum.de/research/quantum/ for - * more information. - */ - -#include "ir/operations/NonUnitaryOperation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -// Measurement constructor -NonUnitaryOperation::NonUnitaryOperation(std::vector qubitRegister, - std::vector classicalRegister) - : classics(std::move(classicalRegister)) { - type = Measure; - targets = std::move(qubitRegister); - name = toString(type); - if (targets.size() != classics.size()) { - throw std::invalid_argument( - "Sizes of qubit register and classical register do not match."); - } -} -NonUnitaryOperation::NonUnitaryOperation(const Qubit qubit, const Bit cbit) - : classics({cbit}) { - type = Measure; - targets = {qubit}; - name = toString(type); -} - -// General constructor -NonUnitaryOperation::NonUnitaryOperation(Targets qubits, OpType op) { - type = op; - targets = std::move(qubits); - std::ranges::sort(targets); - name = toString(type); -} - -std::ostream& -NonUnitaryOperation::print(std::ostream& os, const Permutation& permutation, - [[maybe_unused]] const std::size_t prefixWidth, - const std::size_t nqubits) const { - switch (type) { - case Measure: - printMeasurement(os, targets, classics, permutation, nqubits); - break; - case Reset: - printReset(os, targets, permutation, nqubits); - break; - default: - break; - } - return os; -} - -bool NonUnitaryOperation::equals(const Operation& op) const { - if (const auto* nonunitary = dynamic_cast(&op)) { - if (getType() != nonunitary->getType()) { - return false; - } - - if (getType() == Measure) { - // check number of qubits to be measured - const auto nq1 = targets.size(); - const auto nq2 = nonunitary->targets.size(); - if (nq1 != nq2) { - return false; - } - - // these are just sanity checks and should always be fulfilled - assert(targets.size() == classics.size()); - assert(nonunitary->targets.size() == nonunitary->classics.size()); - - std::set> measurements1{}; - auto qubitIt1 = targets.cbegin(); - auto classicIt1 = classics.cbegin(); - while (qubitIt1 != targets.cend()) { - measurements1.emplace(*qubitIt1, *classicIt1); - ++qubitIt1; - ++classicIt1; - } - - std::set> measurements2{}; - auto qubitIt2 = nonunitary->targets.cbegin(); - auto classicIt2 = nonunitary->classics.cbegin(); - while (qubitIt2 != nonunitary->targets.cend()) { - measurements2.emplace(*qubitIt2, *classicIt2); - ++qubitIt2; - ++classicIt2; - } - - return measurements1 == measurements2; - } - return Operation::equals(op); - } - return false; -} - -void NonUnitaryOperation::printMeasurement(std::ostream& os, - const std::vector& q, - const std::vector& c, - const Permutation& permutation, - const std::size_t nqubits) { - auto qubitIt = q.cbegin(); - auto classicIt = c.cbegin(); - if (permutation.empty()) { - for (std::size_t i = 0; i < nqubits; ++i) { - if (qubitIt != q.cend() && *qubitIt == i) { - os << "\033[34m" << std::setw(4) << *classicIt << "\033[0m"; - ++qubitIt; - ++classicIt; - } else { - os << std::setw(4) << "|"; - } - } - } else { - for (const auto& [physical, logical] : permutation) { - if (qubitIt != q.cend() && *qubitIt == physical) { - os << "\033[34m" << std::setw(4) << *classicIt << "\033[0m"; - ++qubitIt; - ++classicIt; - } else { - os << std::setw(4) << "|"; - } - } - } -} - -void NonUnitaryOperation::printReset(std::ostream& os, - const std::vector& q, - const Permutation& permutation, - const std::size_t nqubits) const { - const auto actualTargets = permutation.apply(q); - for (std::size_t i = 0; i < nqubits; ++i) { - if (std::ranges::find(actualTargets, i) != actualTargets.cend()) { - os << "\033[31m" << std::setw(4) << shortName(type) << "\033[0m"; - continue; - } - os << std::setw(4) << "|"; - } -} - -void NonUnitaryOperation::addDepthContribution( - std::vector& depths) const { - for (const auto& target : getTargets()) { - depths[target] += 1; - } -} - -void NonUnitaryOperation::apply(const Permutation& permutation) { - getTargets() = permutation.apply(getTargets()); -} -} // namespace qc diff --git a/src/ir/operations/OpType.cpp b/src/ir/operations/OpType.cpp deleted file mode 100644 index a7cbc498c4..0000000000 --- a/src/ir/operations/OpType.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/OpType.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qc { -std::string toString(const OpType opType) { - static const std::unordered_map OP_NAMES{ -#define HANDLE_OP_TYPE(N, id, flags, repr) {id, {repr}}, -#define LAST_OP_TYPE(N) -#include "ir/operations/OpType.inc" - -#undef HANDLE_OP_TYPE -#undef LAST_OP_TYPE - }; - - if (const auto it = OP_NAMES.find(opType); it != OP_NAMES.end()) { - return std::string(it->second); - } - throw std::invalid_argument("Invalid OpType!"); -} - -std::string shortName(const OpType opType) { - switch (opType) { - case GPhase: - return "GPh"; - case SXdg: - return "sxd"; - case SWAP: - return "sw"; - case iSWAP: - return "isw"; - case iSWAPdg: - return "isd"; - case Peres: - return "pr"; - case Peresdg: - return "prd"; - case XXminusYY: - return "x-y"; - case XXplusYY: - return "x+y"; - case Barrier: - return "===="; - case Measure: - return "msr"; - case Reset: - return "rst"; - case IfElse: - return "if"; - default: - return toString(opType); - } -} - -namespace { -struct NameToType { - std::string_view name; - OpType type; -}; - -// Sorted lexicographically by `name` -constexpr std::array OP_NAME_TO_TYPE{ - NameToType{.name = "barrier", .type = Barrier}, - NameToType{.name = "ch", .type = H}, - NameToType{.name = "cnot", .type = X}, - NameToType{.name = "compound", .type = Compound}, - NameToType{.name = "cp", .type = P}, - NameToType{.name = "cphase", .type = P}, - NameToType{.name = "cr", .type = R}, - NameToType{.name = "crx", .type = RX}, - NameToType{.name = "cry", .type = RY}, - NameToType{.name = "crz", .type = RZ}, - NameToType{.name = "cs", .type = S}, - NameToType{.name = "csdg", .type = Sdg}, - NameToType{.name = "cswap", .type = SWAP}, - NameToType{.name = "csx", .type = SX}, - NameToType{.name = "csxdg", .type = SXdg}, - NameToType{.name = "ct", .type = T}, - NameToType{.name = "ctdg", .type = Tdg}, - NameToType{.name = "cu", .type = U}, - NameToType{.name = "cu1", .type = P}, - NameToType{.name = "cu2", .type = U2}, - NameToType{.name = "cu3", .type = U}, - NameToType{.name = "cx", .type = X}, - NameToType{.name = "cy", .type = Y}, - NameToType{.name = "cz", .type = Z}, - NameToType{.name = "dcx", .type = DCX}, - NameToType{.name = "ecr", .type = ECR}, - NameToType{.name = "gphase", .type = GPhase}, - NameToType{.name = "h", .type = H}, - NameToType{.name = "i", .type = I}, - NameToType{.name = "id", .type = I}, - NameToType{.name = "if_else", .type = IfElse}, - NameToType{.name = "iswap", .type = iSWAP}, - NameToType{.name = "iswapdg", .type = iSWAPdg}, - NameToType{.name = "mcp", .type = P}, - NameToType{.name = "mcphase", .type = P}, - NameToType{.name = "mcx", .type = X}, - NameToType{.name = "measure", .type = Measure}, - NameToType{.name = "none", .type = None}, - NameToType{.name = "p", .type = P}, - NameToType{.name = "peres", .type = Peres}, - NameToType{.name = "peresdg", .type = Peresdg}, - NameToType{.name = "phase", .type = P}, - NameToType{.name = "prx", .type = R}, - NameToType{.name = "r", .type = R}, - NameToType{.name = "rccx", .type = RCCX}, - NameToType{.name = "reset", .type = Reset}, - NameToType{.name = "rx", .type = RX}, - NameToType{.name = "rxx", .type = RXX}, - NameToType{.name = "ry", .type = RY}, - NameToType{.name = "ryy", .type = RYY}, - NameToType{.name = "rz", .type = RZ}, - NameToType{.name = "rzx", .type = RZX}, - NameToType{.name = "rzz", .type = RZZ}, - NameToType{.name = "s", .type = S}, - NameToType{.name = "sdg", .type = Sdg}, - NameToType{.name = "swap", .type = SWAP}, - NameToType{.name = "sx", .type = SX}, - NameToType{.name = "sxdg", .type = SXdg}, - NameToType{.name = "t", .type = T}, - NameToType{.name = "tdg", .type = Tdg}, - NameToType{.name = "u", .type = U}, - NameToType{.name = "u1", .type = P}, - NameToType{.name = "u2", .type = U2}, - NameToType{.name = "u3", .type = U}, - NameToType{.name = "v", .type = V}, - NameToType{.name = "vdg", .type = Vdg}, - NameToType{.name = "x", .type = X}, - NameToType{.name = "xx_minus_yy", .type = XXminusYY}, - NameToType{.name = "xx_plus_yy", .type = XXplusYY}, - NameToType{.name = "y", .type = Y}, - NameToType{.name = "z", .type = Z}, -}; -static_assert(std::ranges::is_sorted(OP_NAME_TO_TYPE.cbegin(), - OP_NAME_TO_TYPE.cend(), - [](const auto& lhs, const auto& rhs) { - return lhs.name < rhs.name; - })); -} // namespace - -OpType opTypeFromString(const std::string& opType) { - // clang-tidy produces a false-positive that produces a Windows compile error - // when accepted. NOLINTNEXTLINE(*-qualified-auto) - const auto it = - std::ranges::lower_bound(OP_NAME_TO_TYPE, opType, {}, &NameToType::name); - if (it != OP_NAME_TO_TYPE.end() && it->name == opType) { - return it->type; - } - throw std::invalid_argument("Unsupported operation type: " + - std::string(opType)); -} -} // namespace qc diff --git a/src/ir/operations/Operation.cpp b/src/ir/operations/Operation.cpp deleted file mode 100644 index 411127a899..0000000000 --- a/src/ir/operations/Operation.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/Operation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -std::ostream& Operation::printParameters(std::ostream& os) const { - bool isZero = true; - for (const auto& p : parameter) { - if (p != static_cast(0)) { - isZero = false; - break; - } - } - - if (!isZero) { - os << " p: (" << parameter[0] << ") "; - for (size_t j = 1; j < parameter.size(); ++j) { - isZero = true; - for (size_t i = j; i < parameter.size(); ++i) { - if (parameter.at(i) != static_cast(0)) { - isZero = false; - break; - } - } - if (isZero) { - break; - } - os << "(" << parameter.at(j) << ") "; - } - } - - return os; -} - -std::ostream& Operation::print(std::ostream& os, const Permutation& permutation, - [[maybe_unused]] const std::size_t prefixWidth, - const std::size_t nqubits) const { - const auto precBefore = std::cout.precision(20); - const auto& actualControls = permutation.apply(getControls()); - const auto& actualTargets = permutation.apply(getTargets()); - - for (std::size_t i = 0; i < nqubits; ++i) { - const auto q = static_cast(i); - if (std::ranges::find(actualTargets, q) != actualTargets.cend()) { - if (type == Barrier) { - os << "\033[1m\033[32m" << std::setw(4) << shortName(type); - } else { - os << "\033[1m\033[36m" << std::setw(4) << shortName(type); - } - os << "\033[0m"; - continue; - } - - if (const auto it = std::ranges::find_if( - actualControls, [&](const Control& c) { return c.qubit == q; }); - it != actualControls.cend()) { - if (it->type == Control::Type::Pos) { - os << "\033[32m"; - } else { - os << "\033[31m"; - } - os << std::setw(4) << "c" - << "\033[0m"; - continue; - } - - os << std::setw(4) << "|" - << "\033[0m"; - } - - printParameters(os); - - std::cout.precision(precBefore); - - return os; -} - -bool Operation::equals(const Operation& op) const { - // check type - if (getType() != op.getType()) { - return false; - } - - // check number of controls - const auto nc1 = getNcontrols(); - const auto nc2 = op.getNcontrols(); - if (nc1 != nc2) { - return false; - } - - // check parameters - const auto& param1 = getParameter(); - const auto& param2 = op.getParameter(); - if (param1 != param2) { - return false; - } - - if (isDiagonalGate()) { - // check pos. controls and targets together - const auto& usedQubits1 = getUsedQubits(); - const auto& usedQubits2 = op.getUsedQubits(); - if (usedQubits1 != usedQubits2) { - return false; - } - - std::set negControls1{}; - for (const auto& control : getControls()) { - if (control.type == Control::Type::Neg) { - negControls1.emplace(control.qubit); - } - } - std::set negControls2{}; - for (const auto& control : op.getControls()) { - if (control.type == Control::Type::Neg) { - negControls2.emplace(control.qubit); - } - } - return negControls1 == negControls2; - } - // check controls - if (nc1 != 0U && getControls() != op.getControls()) { - return false; - } - - return getTargets() == op.getTargets(); -} - -void Operation::addDepthContribution(std::vector& depths) const { - if (type == Barrier) { - return; - } - - std::size_t maxDepth = 0; - for (const auto& target : getTargets()) { - maxDepth = std::max(maxDepth, depths[target]); - } - for (const auto& control : getControls()) { - maxDepth = std::max(maxDepth, depths[control.qubit]); - } - maxDepth += 1; - for (const auto& target : getTargets()) { - depths[target] = maxDepth; - } - for (const auto& control : getControls()) { - depths[control.qubit] = maxDepth; - } -} - -void Operation::apply(const Permutation& permutation) { - getTargets() = permutation.apply(getTargets()); - getControls() = permutation.apply(getControls()); -} - -auto Operation::isInverseOf(const Operation& other) const -> bool { - return operator==(*other.getInverted()); -} - -auto Operation::getUsedQubits() const -> std::set { - std::set usedQubits; - for (const auto& target : getTargets()) { - usedQubits.emplace(target); - } - for (const auto& control : getControls()) { - usedQubits.emplace(control.qubit); - } - return usedQubits; -} -} // namespace qc diff --git a/src/ir/operations/StandardOperation.cpp b/src/ir/operations/StandardOperation.cpp deleted file mode 100644 index 2f46320162..0000000000 --- a/src/ir/operations/StandardOperation.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/StandardOperation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace qc { -/*** - * Protected Methods - ***/ -OpType StandardOperation::parseU3(fp& theta, fp& phi, fp& lambda) { - if (std::abs(theta) < PARAMETER_TOLERANCE && - std::abs(phi) < PARAMETER_TOLERANCE) { - parameter = {lambda}; - return parseU1(parameter[0]); - } - - if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { - parameter = {phi, lambda}; - return parseU2(parameter[0], parameter[1]); - } - - if (std::abs(lambda) < PARAMETER_TOLERANCE) { - lambda = 0.0; - if (std::abs(phi) < PARAMETER_TOLERANCE) { - checkInteger(theta); - checkFractionPi(theta); - parameter = {theta}; - return RY; - } - } - - if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { - lambda = PI_2; - if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { - checkInteger(theta); - checkFractionPi(theta); - parameter = {theta}; - return RX; - } - - if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { - phi = PI_2; - if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { - parameter.clear(); - return Y; - } - } - } - - if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { - lambda = -PI_2; - if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { - phi = PI_2; - parameter = {-theta}; - return RX; - } - } - - if (std::abs(lambda - PI) < PARAMETER_TOLERANCE) { - lambda = PI; - if (std::abs(phi) < PARAMETER_TOLERANCE) { - phi = 0.0; - if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { - parameter.clear(); - return X; - } - } - } - - // parse a real u3 gate - checkInteger(lambda); - checkFractionPi(lambda); - checkInteger(phi); - checkFractionPi(phi); - checkInteger(theta); - checkFractionPi(theta); - - return U; -} - -OpType StandardOperation::parseU2(fp& phi, fp& lambda) { - if (std::abs(phi) < PARAMETER_TOLERANCE) { - phi = 0.0; - if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { - parameter.clear(); - return H; - } - if (std::abs(lambda) < PARAMETER_TOLERANCE) { - parameter = {PI_2}; - return RY; - } - } - - if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { - lambda = PI_2; - if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { - parameter.clear(); - return V; - } - } - - if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { - lambda = -PI_2; - if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { - parameter.clear(); - return Vdg; - } - } - - checkInteger(lambda); - checkFractionPi(lambda); - checkInteger(phi); - checkFractionPi(phi); - - return U2; -} - -OpType StandardOperation::parseU1(fp& lambda) { - if (std::abs(lambda) < PARAMETER_TOLERANCE) { - parameter.clear(); - return I; - } - const bool sign = std::signbit(lambda); - - if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { - parameter.clear(); - return Z; - } - - if (std::abs(std::abs(lambda) - PI_2) < PARAMETER_TOLERANCE) { - parameter.clear(); - return sign ? Sdg : S; - } - - if (std::abs(std::abs(lambda) - PI_4) < PARAMETER_TOLERANCE) { - parameter.clear(); - return sign ? Tdg : T; - } - - checkInteger(lambda); - checkFractionPi(lambda); - - return P; -} - -void StandardOperation::checkUgate() { - if (parameter.empty()) { - return; - } - if (type == P) { - assert(parameter.size() == 1); - type = parseU1(parameter.at(0)); - } else if (type == U2) { - assert(parameter.size() == 2); - type = parseU2(parameter.at(0), parameter.at(1)); - } else if (type == U) { - assert(parameter.size() == 3); - type = parseU3(parameter.at(0), parameter.at(1), parameter.at(2)); - } -} - -void StandardOperation::setup() { - checkUgate(); - name = toString(type); -} - -/*** - * Constructors - ***/ -StandardOperation::StandardOperation(const Qubit target, const OpType g, - std::vector params) { - type = g; - parameter = std::move(params); - setup(); - targets.emplace_back(target); -} - -StandardOperation::StandardOperation(const Targets& targ, const OpType g, - std::vector params) { - type = g; - parameter = std::move(params); - setup(); - targets = targ; -} - -StandardOperation::StandardOperation(const Control control, const Qubit target, - const OpType g, - const std::vector& params) - : StandardOperation(target, g, params) { - StandardOperation::addControl(control); -} - -StandardOperation::StandardOperation(const Control control, const Targets& targ, - const OpType g, - const std::vector& params) - : StandardOperation(targ, g, params) { - StandardOperation::addControl(control); -} - -StandardOperation::StandardOperation(const Controls& c, const Qubit target, - const OpType g, - const std::vector& params) - : StandardOperation(target, g, params) { - addControls(c); -} - -StandardOperation::StandardOperation(const Controls& c, const Targets& targ, - const OpType g, - const std::vector& params) - : StandardOperation(targ, g, params) { - addControls(c); -} - -// MCF (cSWAP), Peres, parameterized two target Constructor -StandardOperation::StandardOperation(const Controls& c, const Qubit target0, - const Qubit target1, const OpType g, - const std::vector& params) - : StandardOperation(c, {target0, target1}, g, params) {} - -bool StandardOperation::isGlobal(const size_t nQubits) const { - return getUsedQubits().size() == nQubits; -} - -/*** - * Public Methods - ***/ -bool StandardOperation::isClifford() const { - switch (type) { - case I: - return true; - case X: - case Y: - case Z: - return (controls.size() <= 1); - case H: - case S: - case Sdg: - case SX: - case SXdg: - case DCX: - case SWAP: - case iSWAP: - case ECR: - return !isControlled(); - default: - return false; - } -} - -auto StandardOperation::commutesAtQubit(const Operation& other, - const Qubit& qubit) const -> bool { - if (other.isCompoundOperation()) { - return other.commutesAtQubit(*this, qubit); - } - // check whether both operations act on the given qubit - if (!actsOn(qubit) || !other.actsOn(qubit)) { - return true; - } - if (controls.contains(qubit)) { - // if this is controlled on the given qubit - if (const auto& controls2 = other.getControls(); - controls2.contains(qubit)) { - // if other is controlled on the given qubit - // q: ──■────■── - // | | - return true; - } - // here: qubit is a target of other - return other.isDiagonalGate(); - // true, iff qubit is a target and other is a diagonal gate, e.g., rz - // ┌────┐ - // q: ──■──┤ RZ ├ - // | └────┘ - } - // here: qubit is a target of this - if (const auto& controls2 = other.getControls(); controls2.contains(qubit)) { - return isDiagonalGate(); - // true, iff qubit is a target and this is a diagonal gate and other is - // controlled, e.g. - // ┌────┐ - // q: ┤ RZ ├──■── - // └────┘ | - } - // here: qubit is a target of both operations - if (isDiagonalGate() && other.isDiagonalGate()) { - // if both operations are diagonal gates, e.g. - // ┌────┐┌────┐ - // q: ┤ RZ ├┤ RZ ├ - // └────┘└────┘ - return true; - } - if (parameter.size() <= 1) { - return type == other.getType() && targets == other.getTargets(); - // true, iff both operations are of the same type, e.g. - // ┌───┐┌───┐ - // q: ┤ E ├┤ E ├ - // | C || C | - // ┤ R ├┤ R ├ - // └───┘└───┘ - // | | - // ──■────┼── - // | - // ───────■── - } - // operations with more than one parameter might not be commutative when the - // parameter are not the same, i.e. a general U3 gate - // TODO: this check might introduce false negatives - return type == other.getType() && targets == other.getTargets() && - parameter == other.getParameter(); -} - -void StandardOperation::invert() { - switch (type) { - // self-inverting gates - case I: - case X: - case Y: - case Z: - case H: - case SWAP: - case ECR: - case RCCX: - case Barrier: - break; - // gates where we just update parameters - case GPhase: - case P: - case RX: - case RY: - case RZ: - case R: - case RXX: - case RYY: - case RZZ: - case RZX: - parameter[0] = -parameter[0]; - break; - case U2: - std::swap(parameter[0], parameter[1]); - parameter[0] = -parameter[0] + PI; - parameter[1] = -parameter[1] - PI; - break; - case U: - parameter[0] = -parameter[0]; - parameter[1] = -parameter[1]; - parameter[2] = -parameter[2]; - std::swap(parameter[1], parameter[2]); - break; - case XXminusYY: - case XXplusYY: - parameter[0] = -parameter[0]; - break; - case DCX: - std::swap(targets[0], targets[1]); - break; - // gates where we have specialized inverted operation types - case S: - type = Sdg; - break; - case Sdg: - type = S; - break; - case T: - type = Tdg; - break; - case Tdg: - type = T; - break; - case V: - type = Vdg; - break; - case Vdg: - type = V; - break; - case SX: - type = SXdg; - break; - case SXdg: - type = SX; - break; - case Peres: - type = Peresdg; - break; - case Peresdg: - type = Peres; - break; - case iSWAP: - type = iSWAPdg; - break; - case iSWAPdg: - type = iSWAP; - break; - default: - throw std::runtime_error("Inverting gate" + toString(type) + - " is not supported."); - } -} - -} // namespace qc diff --git a/src/ir/operations/SymbolicOperation.cpp b/src/ir/operations/SymbolicOperation.cpp deleted file mode 100644 index e63c149592..0000000000 --- a/src/ir/operations/SymbolicOperation.cpp +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/operations/SymbolicOperation.hpp" - -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" - -#include -#include -#include -#include -#include -#include -#include -namespace qc { - -// Overload pattern for std::visit -namespace { -// NOLINTNEXTLINE(misc-multiple-inheritance) -template struct Overload : Ts... { - using Ts::operator()...; -}; -template Overload(Ts...) -> Overload; -} // namespace - -void SymbolicOperation::storeSymbolOrNumber(const SymbolOrNumber& param, - const std::size_t i) { - if (std::holds_alternative(param)) { - parameter.at(i) = std::get(param); - } else { - symbolicParameter.at(i) = std::get(param); - } -} -bool SymbolicOperation::isSymbolicParameter(const std::size_t i) const { - return symbolicParameter.at(i).has_value(); -} -bool SymbolicOperation::isSymbol(const SymbolOrNumber& param) { - return std::holds_alternative(param); -} -Symbolic& SymbolicOperation::getSymbol(SymbolOrNumber& param) { - return std::get(param); -} -fp& SymbolicOperation::getNumber(SymbolOrNumber& param) { - return std::get(param); -} - -OpType SymbolicOperation::parseU3([[maybe_unused]] const Symbolic& theta, - fp& phi, fp& lambda) { - if (std::abs(lambda) < PARAMETER_TOLERANCE) { - lambda = 0.0; - if (std::abs(phi) < PARAMETER_TOLERANCE) { - phi = 0.0; - } - } - - if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { - lambda = PI_2; - if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { - phi = PI_2; - } - } - - if (std::abs(lambda - PI) < PARAMETER_TOLERANCE) { - lambda = PI; - if (std::abs(phi) < PARAMETER_TOLERANCE) { - phi = 0.0; - } - } - - // parse a real u3 gate - checkInteger(lambda); - checkFractionPi(lambda); - checkInteger(phi); - checkFractionPi(phi); - - return U; -} -OpType SymbolicOperation::parseU3(fp& theta, const Symbolic& phi, fp& lambda) { - if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { - theta = PI_2; - return parseU2(phi, lambda); - } - - if (std::abs(lambda) < PARAMETER_TOLERANCE) { - lambda = 0.0; - } - - if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { - lambda = PI_2; - } - - if (std::abs(lambda - PI) < PARAMETER_TOLERANCE) { - lambda = PI; - } - - // parse a real u3 gate - checkInteger(lambda); - checkFractionPi(lambda); - checkInteger(theta); - checkFractionPi(theta); - - return U; -} -OpType SymbolicOperation::parseU3(fp& theta, fp& phi, - [[maybe_unused]] const Symbolic& lambda) { - if (std::abs(theta) < PARAMETER_TOLERANCE && - std::abs(phi) < PARAMETER_TOLERANCE) { - phi = 0.0; - theta = 0.0; - return SymbolicOperation::parseU1(lambda); - } - - if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { - theta = PI_2; - return parseU2(phi, lambda); - } - // parse a real u3 gate - checkInteger(phi); - checkFractionPi(phi); - checkInteger(theta); - checkFractionPi(theta); - - return U; -} -OpType SymbolicOperation::parseU3([[maybe_unused]] const Symbolic& theta, - [[maybe_unused]] const Symbolic& phi, - fp& lambda) { - // parse a real u3 gate - checkInteger(lambda); - checkFractionPi(lambda); - - return U; -} -OpType SymbolicOperation::parseU3([[maybe_unused]] const Symbolic& theta, - fp& phi, - [[maybe_unused]] const Symbolic& lambda) { - // parse a real u3 gate - checkInteger(phi); - checkFractionPi(phi); - - return U; -} -OpType SymbolicOperation::parseU3(fp& theta, const Symbolic& phi, - const Symbolic& lambda) { - if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { - theta = PI_2; - return parseU2(phi, lambda); - } - - // parse a real u3 gate - checkInteger(theta); - checkFractionPi(theta); - - return U; -} - -OpType SymbolicOperation::parseU2([[maybe_unused]] const Symbolic& phi, - [[maybe_unused]] const Symbolic& lambda) { - return U2; -} - -OpType SymbolicOperation::parseU2([[maybe_unused]] const Symbolic& phi, - fp& lambda) { - checkInteger(lambda); - checkFractionPi(lambda); - - return U2; -} -OpType SymbolicOperation::parseU2(fp& phi, - [[maybe_unused]] const Symbolic& lambda) { - checkInteger(phi); - checkFractionPi(phi); - - return U2; -} - -OpType SymbolicOperation::parseU1([[maybe_unused]] const Symbolic& lambda) { - return P; -} - -void SymbolicOperation::checkSymbolicUgate() { - // NOLINTBEGIN(bugprone-unchecked-optional-access) – we check for this - if (type == P) { - if (!isSymbolicParameter(0)) { - type = StandardOperation::parseU1(parameter[0]); - } - } else if (type == U2) { - if (!isSymbolicParameter(0) && !isSymbolicParameter(1)) { - type = StandardOperation::parseU2(parameter[0], parameter[1]); - } else if (isSymbolicParameter(0)) { - type = parseU2(symbolicParameter[0].value(), parameter[1]); - } else if (isSymbolicParameter(1)) { - type = parseU2(parameter[0], symbolicParameter[1].value()); - } - } else if (type == U) { - if (!isSymbolicParameter(0) && !isSymbolicParameter(1) && - !isSymbolicParameter(2)) { - type = - StandardOperation::parseU3(parameter[0], parameter[1], parameter[2]); - } else if (!isSymbolicParameter(0) && !isSymbolicParameter(1)) { - type = parseU3(parameter[0], parameter[1], symbolicParameter[2].value()); - } else if (!isSymbolicParameter(0) && !isSymbolicParameter(2)) { - type = parseU3(parameter[0], symbolicParameter[1].value(), parameter[2]); - } else if (!isSymbolicParameter(1) && !isSymbolicParameter(2)) { - type = parseU3(symbolicParameter[0].value(), parameter[1], parameter[2]); - } else if (!isSymbolicParameter(0)) { - type = parseU3(parameter[0], symbolicParameter[1].value(), - symbolicParameter[2].value()); - } else if (!isSymbolicParameter(1)) { - type = parseU3(symbolicParameter[0].value(), parameter[1], - symbolicParameter[2].value()); - } else if (!isSymbolicParameter(2)) { - type = parseU3(symbolicParameter[0].value(), symbolicParameter[1].value(), - parameter[2]); - } - } - // NOLINTEND(bugprone-unchecked-optional-access) -} - -void SymbolicOperation::setup(const std::vector& params) { - const auto numParams = params.size(); - parameter.resize(numParams); - symbolicParameter.resize(numParams); - for (std::size_t i = 0; i < numParams; ++i) { - storeSymbolOrNumber(params[i], i); - } - checkSymbolicUgate(); - name = toString(type); -} - -[[nodiscard]] fp -SymbolicOperation::getInstantiation(const SymbolOrNumber& symOrNum, - const VariableAssignment& assignment) { - return std::visit( - Overload{[&](const fp num) { return num; }, - [&](const Symbolic& sym) { return sym.evaluate(assignment); }}, - symOrNum); -} - -SymbolOrNumber SymbolicOperation::getParameter(const std::size_t i) const { - if (const auto& param = symbolicParameter.at(i); param.has_value()) { - return *param; - } - return parameter.at(i); -} -std::vector SymbolicOperation::getParameters() const { - std::vector params{}; - params.reserve(parameter.size()); - for (std::size_t i = 0; i < parameter.size(); ++i) { - params.emplace_back(getParameter(i)); - } - return params; -} -SymbolicOperation::SymbolicOperation( - const Qubit target, const OpType g, - const std::vector& params) { - type = g; - setup(params); - targets.emplace_back(target); -} - -SymbolicOperation::SymbolicOperation( - const Targets& targ, const OpType g, - const std::vector& params) { - type = g; - setup(params); - targets = targ; -} - -SymbolicOperation::SymbolicOperation(const Control control, const Qubit target, - const OpType g, - const std::vector& params) - : SymbolicOperation(target, g, params) { - SymbolicOperation::addControl(control); -} - -SymbolicOperation::SymbolicOperation(const Control control, const Targets& targ, - const OpType g, - const std::vector& params) - : SymbolicOperation(targ, g, params) { - SymbolicOperation::addControl(control); -} - -SymbolicOperation::SymbolicOperation(const Controls& c, const Qubit target, - const OpType g, - const std::vector& params) - : SymbolicOperation(target, g, params) { - addControls(c); -} - -SymbolicOperation::SymbolicOperation(const Controls& c, const Targets& targ, - const OpType g, - const std::vector& params) - : SymbolicOperation(targ, g, params) { - addControls(c); -} - -// MCF (cSWAP), Peres, parameterized two target Constructor -SymbolicOperation::SymbolicOperation(const Controls& c, const Qubit target0, - const Qubit target1, const OpType g, - const std::vector& params) - : SymbolicOperation(c, {target0, target1}, g, params) {} - -std::unique_ptr SymbolicOperation::clone() const { - return std::make_unique(*this); -} -bool SymbolicOperation::isSymbolicOperation() const { - return std::ranges::any_of(symbolicParameter, - [](const auto& sym) { return sym.has_value(); }); -} -bool SymbolicOperation::isStandardOperation() const { - return std::ranges::all_of(symbolicParameter, - [](const auto& sym) { return !sym.has_value(); }); -} - -bool SymbolicOperation::equals(const Operation& op) const { - if (!op.isSymbolicOperation() && !isStandardOperation()) { - return false; - } - if (isStandardOperation() && StandardOperation::equals(op)) { - return true; - } - - if (!op.isSymbolicOperation()) { - return false; - } - const auto& symOp = dynamic_cast(op); - for (std::size_t i = 0; i < symbolicParameter.size(); ++i) { - const auto& symParam = symbolicParameter.at(i); - const auto& symOpParam = symOp.symbolicParameter.at(i); - const auto symParamIsSymbolic = symParam.has_value(); - const auto symOpParamIsSymbolic = symOpParam.has_value(); - - if (symParamIsSymbolic != symOpParamIsSymbolic) { - return false; - } - - if (symParamIsSymbolic) { - return symParam.value() == symOpParam.value(); - } - } - return true; -} - -StandardOperation SymbolicOperation::getInstantiatedOperation( - const VariableAssignment& assignment) const { - std::vector parameters; - const auto size = symbolicParameter.size(); - parameters.reserve(size); - for (std::size_t i = 0; i < size; ++i) { - parameters.emplace_back(getInstantiation(getParameter(i), assignment)); - } - return {controls, targets, type, parameters}; -} - -// Instantiates this Operation -// Afterward casting to StandardOperation can be done if assignment is total -void SymbolicOperation::instantiate(const VariableAssignment& assignment) { - for (std::size_t i = 0; i < symbolicParameter.size(); ++i) { - parameter.at(i) = getInstantiation(getParameter(i), assignment); - symbolicParameter.at(i).reset(); - } - checkUgate(); -} - -void SymbolicOperation::negateSymbolicParameter(const std::size_t index) { - if (isSymbolicParameter(index)) { - // NOLINTBEGIN(bugprone-unchecked-optional-access) – we check for this - symbolicParameter.at(index) = -symbolicParameter.at(index).value(); - // NOLINTEND(bugprone-unchecked-optional-access) - } else { - parameter.at(index) = -parameter.at(index); - } -} - -void SymbolicOperation::addToSymbolicParameter(const std::size_t index, - const fp value) { - if (isSymbolicParameter(index)) { - // NOLINTBEGIN(bugprone-unchecked-optional-access) – we check for this - symbolicParameter.at(index) = symbolicParameter.at(index).value() + value; - // NOLINTEND(bugprone-unchecked-optional-access) - } else { - parameter.at(index) += value; - } -} - -void SymbolicOperation::invert() { - switch (type) { - case GPhase: - case P: - case RX: - case RY: - case RZ: - case R: - case RXX: - case RYY: - case RZZ: - case RZX: - negateSymbolicParameter(0); - break; - case U2: - negateSymbolicParameter(0); - negateSymbolicParameter(1); - - addToSymbolicParameter(0, -PI); - addToSymbolicParameter(1, PI); - std::swap(parameter[0], parameter[1]); - std::swap(symbolicParameter[0], symbolicParameter[1]); - break; - case U: - negateSymbolicParameter(0); - negateSymbolicParameter(1); - negateSymbolicParameter(2); - - std::swap(parameter[1], parameter[2]); - std::swap(symbolicParameter[1], symbolicParameter[2]); - break; - case XXminusYY: - case XXplusYY: - negateSymbolicParameter(0); - break; - default: - StandardOperation::invert(); - } -} -} // namespace qc diff --git a/src/qasm3/CMakeLists.txt b/src/qasm3/CMakeLists.txt deleted file mode 100644 index 57501dcf7e..0000000000 --- a/src/qasm3/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -if(NOT TARGET MQT::CoreQASM) - # collect headers and source files - file(GLOB_RECURSE QASM_HEADERS ${MQT_CORE_INCLUDE_BUILD_DIR}/qasm3/*.hpp) - file(GLOB_RECURSE QASM_SOURCES **.cpp) - - # create the library target (initially empty) - add_mqt_core_library(${MQT_CORE_TARGET_NAME}-qasm ALIAS_NAME QASM) - - # add sources to target - target_sources(${MQT_CORE_TARGET_NAME}-qasm PRIVATE ${QASM_SOURCES}) - - # add headers using file sets - target_sources( - ${MQT_CORE_TARGET_NAME}-qasm PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} - FILES ${QASM_HEADERS}) - - # add link libraries - target_link_libraries(${MQT_CORE_TARGET_NAME}-qasm PUBLIC MQT::CoreIR) - - # generate export header - include(GenerateExportHeader) - generate_export_header(${MQT_CORE_TARGET_NAME}-qasm BASE_NAME mqt_core_qasm) - target_sources( - ${MQT_CORE_TARGET_NAME}-qasm PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/.. - FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_qasm_export.h) - if(NOT BUILD_MQT_CORE_SHARED_LIBS) - target_compile_definitions(${MQT_CORE_TARGET_NAME}-qasm PUBLIC MQT_CORE_QASM_STATIC_DEFINE) - endif() - - # add to list of MQT core target - set(MQT_CORE_TARGETS - ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-qasm - PARENT_SCOPE) -endif() diff --git a/src/qasm3/Importer.cpp b/src/qasm3/Importer.cpp deleted file mode 100644 index 611ba238b2..0000000000 --- a/src/qasm3/Importer.cpp +++ /dev/null @@ -1,1044 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Importer.hpp" - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Gate.hpp" -#include "qasm3/Parser.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/StdGates.hpp" -#include "qasm3/Types.hpp" -#include "qasm3/passes/ConstEvalPass.hpp" -#include "qasm3/passes/TypeCheckPass.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qasm3 { -namespace { - -std::unique_ptr -applyPowerModifier(std::unique_ptr operation, - const std::size_t repetitions) { - if (operation == nullptr || repetitions == 0) { - return nullptr; - } - if (repetitions == 1) { - return operation; - } - - auto poweredOperation = std::make_unique(); - poweredOperation->getOps().reserve(repetitions); - for (std::size_t i = 1; i < repetitions; ++i) { - poweredOperation->getOps().emplace_back(operation->clone()); - } - poweredOperation->getOps().emplace_back(std::move(operation)); - return poweredOperation; -} - -} // namespace - -auto Importer::importf(const std::string& filename) -> qc::QuantumComputation { - std::ifstream file(filename); - if (!file.good()) { - throw std::runtime_error("Could not open file " + filename); - } - return import(file); -} - -auto Importer::import(std::istream& is) -> qc::QuantumComputation { - // parse the program into an AST - Parser parser(is); - const auto program = parser.parseProgram(); - // translate the AST into a quantum computation - qc::QuantumComputation qc; - Importer importer(qc); - importer.visitProgram(program); - // initialize the initial layout and output permutation - qc.initializeIOMapping(); - return qc; -} - -auto Importer::imports(const std::string& qasm) -> qc::QuantumComputation { - std::istringstream is(qasm); - return import(is); -} - -std::map> -Importer::initializeBuiltins() { - std::map> - builtins{}; - - type_checking::InferredType const floatTy{ - std::dynamic_pointer_cast( - std::make_shared>(Float, 64))}; - - builtins.emplace("pi", - std::pair{const_eval::ConstEvalValue(qc::PI), floatTy}); - builtins.emplace("π", std::pair{const_eval::ConstEvalValue(qc::PI), floatTy}); - builtins.emplace("tau", - std::pair{const_eval::ConstEvalValue(qc::TAU), floatTy}); - builtins.emplace("τ", - std::pair{const_eval::ConstEvalValue(qc::TAU), floatTy}); - builtins.emplace("euler", - std::pair{const_eval::ConstEvalValue(qc::E), floatTy}); - builtins.emplace("ℇ", std::pair{const_eval::ConstEvalValue(qc::E), floatTy}); - - return builtins; -} - -void Importer::translateGateOperand( - const std::shared_ptr& gateOperand, - std::vector& qubits, const qc::QuantumRegisterMap& qregs, - const std::shared_ptr& debugInfo) const { - if (gateOperand->isHardwareQubit()) { - const auto hardwareQubit = gateOperand->getHardwareQubit(); - // Ensure that the circuit has enough qubits. - // Currently, we emulate hardware qubits via a single quantum register q. - for (size_t i = qc->getNqubits(); i <= hardwareQubit; ++i) { - const auto q = static_cast(i); - qc->addQubit(q, q, q); - } - - qubits.emplace_back( - static_cast(gateOperand->getHardwareQubit())); - return; - } - const auto indexedIdentifier = gateOperand->getIdentifier(); - const auto qregIterator = qregs.find(indexedIdentifier->identifier); - if (qregIterator == qregs.end()) { - throw CompilerError("Usage of unknown quantum register.", debugInfo); - } - const auto& qreg = qregIterator->second; - - if (!indexedIdentifier->indices.empty()) { - if (const auto declaration = - declarations.find(indexedIdentifier->identifier); - declaration.has_value()) { - const auto type = std::get<1>(declaration.value()->type); - const auto unsizedType = - std::dynamic_pointer_cast>(type); - if (unsizedType && unsizedType->type == SingleQubit) { - throw CompilerError("Type 'qubit' cannot be indexed.", debugInfo); - } - } - } - - // full register - if (indexedIdentifier->indices.empty()) { - for (size_t i = 0; i < qreg.getSize(); ++i) { - qubits.emplace_back(static_cast(qreg.getStartIndex() + i)); - } - return; - } - - if (indexedIdentifier->indices.size() > 1) { - throw CompilerError("Only single index expressions are supported.", - debugInfo); - } - const auto indexOperator = indexedIdentifier->indices[0]; - if (indexOperator->indexExpressions.size() > 1) { - throw CompilerError("Only single index expressions are supported.", - debugInfo); - } - const auto indexExpression = indexOperator->indexExpressions[0]; - const auto result = evaluatePositiveConstant(indexExpression, debugInfo); - - if (result >= qreg.getSize()) { - throw CompilerError( - "Index expression must be smaller than the width of the " - "quantum register.", - debugInfo); - } - qubits.emplace_back(qreg.getStartIndex() + static_cast(result)); -} - -void Importer::translateBitOperand( - const std::shared_ptr& indexedIdentifier, - std::vector& bits, - const std::shared_ptr& debugInfo) const { - const auto iter = - qc->getClassicalRegisters().find(indexedIdentifier->identifier); - if (iter == qc->getClassicalRegisters().end()) { - throw CompilerError("Usage of unknown classical register.", debugInfo); - } - const auto& creg = iter->second; - const auto& indices = indexedIdentifier->indices; - // full register - if (indices.empty()) { - for (size_t i = 0; i < creg.getSize(); ++i) { - bits.emplace_back(creg.getStartIndex() + i); - } - return; - } - - if (indices.size() > 1) { - throw CompilerError("Only single index expressions are supported.", - debugInfo); - } - const auto& indexExpressions = indices[0]->indexExpressions; - if (indexExpressions.size() > 1) { - throw CompilerError("Only single index expressions are supported.", - debugInfo); - } - const auto& indexExpression = indexExpressions[0]; - const auto index = evaluatePositiveConstant(indexExpression, debugInfo); - if (index >= creg.getSize()) { - throw CompilerError( - "Index expression must be smaller than the width of the " - "classical register.", - debugInfo); - } - bits.emplace_back(creg.getStartIndex() + index); -} - -std::variant, - std::tuple> -Importer::translateCondition( - const std::shared_ptr& condition, - const std::shared_ptr& debugInfo) const { - if (const auto binaryExpression = - std::dynamic_pointer_cast(condition); - binaryExpression != nullptr) { - const auto comparisonKind = getComparisonKind(binaryExpression->op); - if (!comparisonKind) { - throw CompilerError("Unsupported comparison operator.", debugInfo); - } - auto lhsIsIdentifier = true; - std::shared_ptr lhs = - std::dynamic_pointer_cast(binaryExpression->lhs); - if (lhs == nullptr) { - lhsIsIdentifier = false; - lhs = std::dynamic_pointer_cast(binaryExpression->lhs); - } - std::shared_ptr rhs{}; - if (lhsIsIdentifier) { - rhs = std::dynamic_pointer_cast(binaryExpression->rhs); - } else { - rhs = std::dynamic_pointer_cast(binaryExpression->rhs); - } - if (lhs == nullptr || rhs == nullptr) { - throw CompilerError("Only classical registers and constants are " - "supported in conditions.", - debugInfo); - } - - const auto& indexedIdentifier = - lhsIsIdentifier ? std::dynamic_pointer_cast(lhs) - : std::dynamic_pointer_cast(rhs); - const auto& identifier = indexedIdentifier->identifier; - const auto val = lhsIsIdentifier - ? std::dynamic_pointer_cast(rhs)->getUInt() - : std::dynamic_pointer_cast(lhs)->getUInt(); - - const auto creg = qc->getClassicalRegisters().find(identifier); - if (creg == qc->getClassicalRegisters().end()) { - throw CompilerError("Usage of unknown or invalid identifier '" + - identifier + "' in condition.", - debugInfo); - } - return std::tuple{creg->second, *comparisonKind, val}; - } - if (const auto unaryExpression = - std::dynamic_pointer_cast(condition); - unaryExpression != nullptr) { - // This should already be caught by the type checker. - assert(unaryExpression->op == UnaryExpression::LogicalNot || - unaryExpression->op == UnaryExpression::BitwiseNot); - const auto& indexedIdentifier = - std::dynamic_pointer_cast(unaryExpression->operand); - std::vector bits{}; - translateBitOperand(indexedIdentifier, bits, debugInfo); - // This should already be caught by the type checker. - assert(bits.size() == 1); - return std::pair{bits[0], false}; - } - // must be a single bit at this point - const auto& indexedIdentifier = - std::dynamic_pointer_cast(condition); - // should also be caught by the type checker - assert(indexedIdentifier != nullptr); - std::vector bits{}; - translateBitOperand(indexedIdentifier, bits, debugInfo); - // This should already be caught by the type checker. - assert(bits.size() == 1); - return std::pair{bits[0], true}; -} - -uint64_t -Importer::evaluatePositiveConstant(const std::shared_ptr& expr, - const std::shared_ptr& debugInfo, - const uint64_t defaultValue) { - if (expr == nullptr) { - return defaultValue; - } - - const auto constInt = std::dynamic_pointer_cast(expr); - if (!constInt) { - throw CompilerError("Expected a constant integer expression.", debugInfo); - } - - return constInt->getUInt(); -} - -Importer::Importer(qc::QuantumComputation& quantumComputation) - : typeCheckPass(constEvalPass), qc(&quantumComputation), - gates(STANDARD_GATES) { - for (const auto& [identifier, builtin] : initializeBuiltins()) { - constEvalPass.addConst(identifier, builtin.first); - typeCheckPass.addBuiltin(identifier, builtin.second); - } -} - -void Importer::visitProgram( - const std::vector>& program) { - // TODO: in the future, don't exit early, but collect all errors - // To do this, we need to insert make sure that erroneous declarations - // actually insert a dummy entry; also, we need to synchronize to the next - // semicolon, to make sure we don't do some weird stuff and report false - // errors. - for (const auto& statement : program) { - constEvalPass.processStatement(*statement); - typeCheckPass.processStatement(*statement); - statement->accept(this); - } - - // Finally, if we have a initial layout and output permutation specified, - // apply them. - if (!initialLayout.empty()) { - qc->initialLayout = initialLayout; - } - if (!outputPermutation.empty()) { - qc->outputPermutation = outputPermutation; - } -} - -void Importer::visitVersionDeclaration( - const std::shared_ptr versionDeclaration) { - if (versionDeclaration->version < 3) { - openQASM2CompatMode = true; - } -} - -void Importer::visitDeclarationStatement( - const std::shared_ptr declarationStatement) { - const auto identifier = declarationStatement->identifier; - if (declarations.find(identifier).has_value()) { - // TODO: show the location of the previous declaration - throw CompilerError("Identifier '" + identifier + "' already declared.", - declarationStatement->debugInfo); - } - - std::shared_ptr const ty = - std::get<1>(declarationStatement->type); - - if (const auto sizedTy = - std::dynamic_pointer_cast>(ty)) { - const auto designator = sizedTy->getDesignator(); - switch (sizedTy->type) { - case Qubit: - qc->addQubitRegister(designator, identifier); - break; - case Bit: - case Int: - case Uint: - qc->addClassicalRegister(designator, identifier); - break; - case Float: - // not adding to qc - break; - case Angle: - throw CompilerError("Angle type is currently not supported.", - declarationStatement->debugInfo); - } - } else if (const auto unsizedTy = - std::dynamic_pointer_cast>(ty)) { - if (unsizedTy->type == SingleQubit) { - qc->addQubitRegister(1, identifier); - } else { - throw CompilerError("Only sized types or single qubits are supported.", - declarationStatement->debugInfo); - } - } else { - throw CompilerError("Only sized types or single qubits are supported.", - declarationStatement->debugInfo); - } - declarations.emplace(identifier, declarationStatement); - - if (declarationStatement->expression == nullptr) { - // value is uninitialized - return; - } - if (const auto measureExpression = - std::dynamic_pointer_cast( - declarationStatement->expression->expression)) { - assert(!declarationStatement->isConst && - "Type check pass should catch this"); - visitMeasureAssignment(std::make_shared(identifier), - measureExpression, declarationStatement->debugInfo); - return; - } - if (declarationStatement->isConst) { - // nothing to do - return; - } - - throw CompilerError( - "Only measure statements are supported for initialization.", - declarationStatement->debugInfo); -} - -void Importer::visitAssignmentStatement( - const std::shared_ptr assignmentStatement) { - const auto identifier = assignmentStatement->identifier->identifier; - const auto declaration = declarations.find(identifier); - assert(declaration.has_value() && "Checked by type check pass"); - assert(!declaration->get()->isConst && "Checked by type check pass"); - - if (const auto measureExpression = - std::dynamic_pointer_cast( - assignmentStatement->expression->expression)) { - visitMeasureAssignment(assignmentStatement->identifier, measureExpression, - assignmentStatement->debugInfo); - return; - } - - // In the future, handle classical computation. - throw CompilerError("Classical computation not supported.", - assignmentStatement->debugInfo); -} - -void Importer::visitInitialLayout(const std::shared_ptr layout) { - if (!initialLayout.empty()) { - throw CompilerError("Multiple initial layout specifications found.", - layout->debugInfo); - } - initialLayout = layout->permutation; -} - -void Importer::visitOutputPermutation( - const std::shared_ptr permutation) { - if (!outputPermutation.empty()) { - throw CompilerError("Multiple output permutation specifications found.", - permutation->debugInfo); - } - outputPermutation = permutation->permutation; -} - -void Importer::visitGateStatement( - const std::shared_ptr gateStatement) { - auto identifier = gateStatement->identifier; - if (gateStatement->isOpaque) { - if (!gates.contains(identifier)) { - // only builtin gates may be declared as opaque. - throw CompilerError("Unsupported opaque gate '" + identifier + "'.", - gateStatement->debugInfo); - } - - return; - } - - if (openQASM2CompatMode) { - // we need to check if this is a standard gate - identifier = parseGateIdentifierCompatMode(identifier).first; - } - - if (auto prevDeclaration = gates.find(identifier); - prevDeclaration != gates.end()) { - if (std::dynamic_pointer_cast(prevDeclaration->second)) { - // we ignore redeclarations of standard gates - return; - } - // TODO: print location of previous declaration - throw CompilerError("Gate '" + identifier + "' already declared.", - gateStatement->debugInfo); - } - - const auto parameters = gateStatement->parameters; - const auto qubits = gateStatement->qubits; - - // first we check that all parameters and qubits are unique - std::vector parameterIdentifiers{}; - for (const auto& parameter : parameters->identifiers) { - if (std::ranges::find(parameterIdentifiers, parameter->identifier) != - parameterIdentifiers.end()) { - throw CompilerError("Parameter '" + parameter->identifier + - "' already declared.", - gateStatement->debugInfo); - } - parameterIdentifiers.emplace_back(parameter->identifier); - } - std::vector qubitIdentifiers{}; - for (const auto& qubit : qubits->identifiers) { - if (std::ranges::find(qubitIdentifiers, qubit->identifier) != - qubitIdentifiers.end()) { - throw CompilerError("Qubit '" + qubit->identifier + "' already declared.", - gateStatement->debugInfo); - } - qubitIdentifiers.emplace_back(qubit->identifier); - } - - auto compoundGate = std::make_shared(CompoundGate( - parameterIdentifiers, qubitIdentifiers, gateStatement->statements)); - - gates.emplace(identifier, compoundGate); -} - -void Importer::visitGateCallStatement( - const std::shared_ptr gateCallStatement) { - const auto& qregs = qc->getQuantumRegisters(); - if (auto op = evaluateGateCall( - gateCallStatement, gateCallStatement->identifier, - gateCallStatement->arguments, gateCallStatement->operands, qregs); - op != nullptr) { - qc->emplace_back(std::move(op)); - } -} - -std::unique_ptr Importer::evaluateGateCall( - const std::shared_ptr& gateCallStatement, - const std::string& identifier, - const std::vector>& parameters, - std::vector> targets, - const qc::QuantumRegisterMap& qregs) { - auto iter = gates.find(identifier); - std::shared_ptr gate; - size_t implicitControls{0}; - - if (iter == gates.end()) { - if (identifier == "mcx" || identifier == "mcx_gray" || - identifier == "mcx_vchain" || identifier == "mcx_recursive" || - identifier == "mcphase") { - // we create a temp gate definition for these gates - gate = getMcGateDefinition(identifier, gateCallStatement->operands.size(), - gateCallStatement->debugInfo); - } else if (openQASM2CompatMode) { - auto [updatedIdentifier, nControls] = - parseGateIdentifierCompatMode(identifier); - - iter = gates.find(updatedIdentifier); - if (iter == gates.end()) { - throw CompilerError("Usage of unknown gate '" + identifier + "'.", - gateCallStatement->debugInfo); - } - gate = iter->second; - implicitControls = nControls; - } else { - throw CompilerError("Usage of unknown gate '" + identifier + "'.", - gateCallStatement->debugInfo); - } - } else { - gate = iter->second; - } - - if (gate->getNParameters() != parameters.size()) { - throw CompilerError( - "Gate '" + identifier + "' takes " + - std::to_string(gate->getNParameters()) + " parameters, but " + - std::to_string(parameters.size()) + " were supplied.", - gateCallStatement->debugInfo); - } - - // here we count the number of controls - std::vector, bool>> controls{}; - // since standard gates may define a number of control targets, we first - // need to handle those - size_t nControls{gate->getNControls() + implicitControls}; - if (targets.size() < nControls) { - throw CompilerError("Gate '" + identifier + "' takes " + - std::to_string(nControls) + " controls, but only " + - std::to_string(targets.size()) + - " qubits were supplied.", - gateCallStatement->debugInfo); - } - - controls.reserve(nControls); - for (size_t i = 0; i < nControls; ++i) { - controls.emplace_back(targets[i], true); - } - - bool invertOperation = false; - std::size_t repetitions = 1; - for (const auto& modifier : gateCallStatement->modifiers) { - if (auto ctrlModifier = - std::dynamic_pointer_cast(modifier); - ctrlModifier != nullptr) { - size_t const n = evaluatePositiveConstant(ctrlModifier->expression, - gateCallStatement->debugInfo, - /*defaultValue=*/ - 1); - if (targets.size() < n + nControls) { - throw CompilerError( - "Gate '" + identifier + "' takes " + std::to_string(n + nControls) + - " controls, but only " + std::to_string(targets.size()) + - " were supplied.", - gateCallStatement->debugInfo); - } - - for (size_t i = 0; i < n; ++i) { - controls.emplace_back(targets[nControls + i], ctrlModifier->ctrlType); - } - nControls += n; - } else if (auto invModifier = - std::dynamic_pointer_cast(modifier); - invModifier != nullptr) { - // if we have an even number of inv modifiers, they cancel each other - // out - invertOperation = !invertOperation; - } else if (auto powModifier = - std::dynamic_pointer_cast(modifier); - powModifier != nullptr) { - const auto exponent = - std::dynamic_pointer_cast(powModifier->expression); - if (exponent == nullptr || !exponent->isInt() || exponent->isBool()) { - throw CompilerError( - "Only constant integer expressions are supported as power " - "modifier exponents.", - gateCallStatement->debugInfo); - } - - uint64_t magnitude = exponent->getUInt(); - if (exponent->isSInt() && exponent->getSInt() < 0) { - const auto signedExponent = exponent->getSInt(); - magnitude = static_cast(-(signedExponent + 1)) + 1; - invertOperation = !invertOperation; - } - - if (magnitude != 0 && - repetitions > std::numeric_limits::max() / magnitude) { - throw CompilerError("Power modifier exponent is too large.", - gateCallStatement->debugInfo); - } - repetitions *= static_cast(magnitude); - } else { - throw CompilerError("Only ctrl/negctrl/inv/pow modifiers are supported.", - gateCallStatement->debugInfo); - } - } - targets.erase(targets.begin(), - targets.begin() + static_cast(nControls)); - - if (gate->getNTargets() != targets.size()) { - throw CompilerError("Gate '" + identifier + "' takes " + - std::to_string(gate->getNTargets()) + - " targets, but " + std::to_string(targets.size()) + - " were supplied.", - gateCallStatement->debugInfo); - } - - // now evaluate all arguments; we only support const arguments. - std::vector evaluatedParameters{}; - for (const auto& param : parameters) { - auto result = constEvalPass.visit(param); - if (!result.has_value()) { - throw CompilerError( - "Only const expressions are supported as gate parameters, but " - "found '" + - param->getName() + "'.", - gateCallStatement->debugInfo); - } - - evaluatedParameters.emplace_back(result->toExpr()->asFP()); - } - - size_t broadcastingWidth{1}; - qc::Targets targetBits{}; - std::vector targetBroadcastingIndices{}; - size_t i{0}; - for (const auto& target : targets) { - qc::Targets t{}; - translateGateOperand(target, t, qregs, gateCallStatement->debugInfo); - - targetBits.emplace_back(t[0]); - - if (t.size() > 1) { - if (broadcastingWidth != 1 && t.size() != broadcastingWidth) { - throw CompilerError( - "When broadcasting, all registers must be of the same width.", - gateCallStatement->debugInfo); - } - broadcastingWidth = t.size(); - - targetBroadcastingIndices.emplace_back(i); - } - - i++; - } - - std::vector controlBits{}; - std::vector controlBroadcastingIndices{}; - i = 0; - for (const auto& [control, type] : controls) { - qc::Targets c{}; - translateGateOperand(control, c, qregs, gateCallStatement->debugInfo); - - controlBits.emplace_back(c[0], type ? qc::Control::Type::Pos - : qc::Control::Type::Neg); - - if (c.size() > 1) { - if (broadcastingWidth != 1 && c.size() != broadcastingWidth) { - throw CompilerError( - "When broadcasting, all registers must be of the same width.", - gateCallStatement->debugInfo); - } - broadcastingWidth = c.size(); - - controlBroadcastingIndices.emplace_back(i); - } - - i++; - } - - auto op = std::make_unique(); - for (size_t j = 0; j < broadcastingWidth; ++j) { - // check if any of the bits are duplicate - std::unordered_set allQubits; - for (const auto& control : controlBits) { - if (allQubits.contains(control.qubit)) { - throw CompilerError("Duplicate qubit in control list.", - gateCallStatement->debugInfo); - } - allQubits.emplace(control.qubit); - } - for (const auto& qubit : targetBits) { - if (allQubits.contains(qubit)) { - throw CompilerError("Duplicate qubit in target list.", - gateCallStatement->debugInfo); - } - allQubits.emplace(qubit); - } - - // first we apply the operation - auto nestedOp = applyPowerModifier( - applyQuantumOperation(gate, targetBits, controlBits, - evaluatedParameters, invertOperation, - gateCallStatement->debugInfo), - repetitions); - if (nestedOp == nullptr || broadcastingWidth == 1) { - return nestedOp; - } - op->getOps().emplace_back(std::move(nestedOp)); - - // after applying the operation, we update the broadcast bits - if (j == broadcastingWidth - 1) { - break; - } - for (auto index : targetBroadcastingIndices) { - targetBits[index] = qc::Qubit{targetBits[index] + 1}; - } - for (auto index : controlBroadcastingIndices) { - controlBits[index].qubit = qc::Qubit{controlBits[index].qubit + 1}; - } - } - return op; -} - -std::shared_ptr -Importer::getMcGateDefinition(const std::string& identifier, size_t operandSize, - const std::shared_ptr& debugInfo) { - std::vector targetParams{}; - std::vector> operands; - size_t nTargets = operandSize; - if (identifier == "mcx_vchain") { - nTargets -= ((nTargets + 1) / 2) - 2; - } else if (identifier == "mcx_recursive" && nTargets > 5) { - nTargets -= 1; - } - for (size_t i = 0; i < operandSize; ++i) { - targetParams.emplace_back("q" + std::to_string(i)); - if (i < nTargets) { - operands.emplace_back(std::make_shared( - std::make_shared("q" + std::to_string(i)))); - } - } - const size_t nControls = nTargets - 1; - - std::string nestedGateIdentifier = "x"; - std::vector> nestedParameters{}; - std::vector nestedParameterNames{}; - if (identifier == "mcphase") { - nestedGateIdentifier = "p"; - nestedParameters.emplace_back(std::make_shared("x")); - nestedParameterNames.emplace_back("x"); - } - - // ctrl(nTargets - 1) @ x q0, ..., q(nTargets - 1) - const auto gateCall = GateCallStatement( - debugInfo, nestedGateIdentifier, - std::vector>{ - std::make_shared( - true, std::make_shared(nControls, false))}, - nestedParameters, operands); - const auto inner = std::make_shared(gateCall); - - const CompoundGate g{nestedParameterNames, targetParams, {inner}}; - return std::make_shared(g); -} - -std::unique_ptr Importer::applyQuantumOperation( - const std::shared_ptr& gate, const qc::Targets& targetBits, - const std::vector& controlBits, - const std::vector& evaluatedParameters, const bool invertOperation, - const std::shared_ptr& debugInfo) { - if (auto* standardGate = dynamic_cast(gate.get())) { - auto op = std::make_unique( - qc::Controls{controlBits.begin(), controlBits.end()}, targetBits, - standardGate->info.type, evaluatedParameters); - if (invertOperation) { - op->invert(); - } - return op; - } - if (auto* compoundGate = dynamic_cast(gate.get())) { - constEvalPass.pushEnv(); - - for (size_t i = 0; i < compoundGate->parameterNames.size(); ++i) { - constEvalPass.addConst(compoundGate->parameterNames[i], - evaluatedParameters[i]); - } - - auto nestedQubits = qc::QuantumRegisterMap{}; - size_t index = 0; - for (const auto& qubitIdentifier : compoundGate->targetNames) { - nestedQubits.try_emplace(qubitIdentifier, targetBits[index], 1, - qubitIdentifier); - index++; - } - - auto op = std::make_unique(true); - for (const auto& nestedGate : compoundGate->body) { - if (auto barrierStatement = - std::dynamic_pointer_cast(nestedGate); - barrierStatement != nullptr) { - std::vector qubits{}; - for (const auto& g : barrierStatement->gates) { - translateGateOperand(g, qubits, nestedQubits, - barrierStatement->debugInfo); - } - op->emplace_back(qubits, qc::Barrier); - } else if (auto resetStatement = - std::dynamic_pointer_cast(nestedGate); - resetStatement != nullptr) { - std::vector qubits{}; - translateGateOperand(resetStatement->gate, qubits, nestedQubits, - resetStatement->debugInfo); - op->emplace_back(qubits, qc::Reset); - } else if (auto gateCallStatement = - std::dynamic_pointer_cast(nestedGate); - gateCallStatement != nullptr) { - for (const auto& operand : gateCallStatement->operands) { - if (operand->isHardwareQubit()) { - continue; - } - const auto& identifier = operand->getIdentifier(); - // OpenQASM 3.0 doesn't support indexing of gate arguments. - if (!identifier->indices.empty() && - std::ranges::find(compoundGate->targetNames, - identifier->identifier) != - compoundGate->targetNames.end()) { - throw CompilerError( - "Gate arguments cannot be indexed within gate body.", - debugInfo); - } - } - - auto nestedOp = - evaluateGateCall(gateCallStatement, gateCallStatement->identifier, - gateCallStatement->arguments, - gateCallStatement->operands, nestedQubits); - if (nestedOp != nullptr) { - op->getOps().emplace_back(std::move(nestedOp)); - } - } else { - throw CompilerError("Unhandled quantum statement.", debugInfo); - } - } - op->setControls(qc::Controls{controlBits.begin(), controlBits.end()}); - if (invertOperation) { - op->invert(); - } - - constEvalPass.popEnv(); - - if (op->getOps().empty()) { - return nullptr; - } - if (op->getOps().size() == 1) { - return std::move(op->getOps()[0]); - } - - return op; - } - - throw CompilerError("Unknown gate type.", debugInfo); -} - -void Importer::visitMeasureAssignment( - const std::shared_ptr& indexedIdentifier, - const std::shared_ptr& measureExpression, - const std::shared_ptr& debugInfo) { - const auto& identifier = indexedIdentifier->identifier; - const auto decl = declarations.find(identifier); - if (!decl.has_value()) { - throw CompilerError("Usage of unknown identifier '" + identifier + "'.", - debugInfo); - } - - if (!std::get<1>(decl.value()->type)->isBit()) { - throw CompilerError( - "Measure expression can only be assigned to a bit register.", - debugInfo); - } - - std::vector qubits{}; - std::vector bits{}; - translateGateOperand(measureExpression->gate, qubits, - qc->getQuantumRegisters(), debugInfo); - translateBitOperand(indexedIdentifier, bits, debugInfo); - - if (qubits.size() != bits.size()) { - throw CompilerError( - "Classical and quantum register must have the same width in " - "measure statement. Classical register '" + - identifier + "' has " + std::to_string(bits.size()) + - " bits, but quantum register '" + - measureExpression->gate->getName() + "' has " + - std::to_string(qubits.size()) + " qubits.", - debugInfo); - } - - qc->measure(qubits, bits); -} - -void Importer::visitBarrierStatement( - const std::shared_ptr barrierStatement) { - std::vector qubits{}; - for (const auto& gate : barrierStatement->gates) { - translateGateOperand(gate, qubits, qc->getQuantumRegisters(), - barrierStatement->debugInfo); - } - qc->barrier(qubits); -} - -void Importer::visitResetStatement( - const std::shared_ptr resetStatement) { - std::vector qubits{}; - translateGateOperand(resetStatement->gate, qubits, qc->getQuantumRegisters(), - resetStatement->debugInfo); - qc->reset(qubits); -} - -void Importer::visitIfStatement( - const std::shared_ptr ifStatement) { - const auto& condition = - translateCondition(ifStatement->condition, ifStatement->debugInfo); - - // translate statements in then/else blocks - if (ifStatement->thenStatements.empty() && - ifStatement->elseStatements.empty()) { - return; - } - - std::unique_ptr thenOps = nullptr; - if (!ifStatement->thenStatements.empty()) { - thenOps = translateBlockOperations(ifStatement->thenStatements); - } - - std::unique_ptr elseOps = nullptr; - if (!ifStatement->elseStatements.empty()) { - elseOps = translateBlockOperations(ifStatement->elseStatements); - } - - if (std::holds_alternative>(condition)) { - const auto& [bit, value] = std::get>(condition); - qc->emplace_back(std::move(thenOps), - std::move(elseOps), bit, value); - } else { - const auto& [creg, comparisonKind, value] = std::get< - std::tuple>( - condition); - qc->emplace_back( - std::move(thenOps), std::move(elseOps), creg, value, comparisonKind); - } -} - -std::unique_ptr Importer::translateBlockOperations( - const std::vector>& statements) { - auto blockOps = std::make_unique(); - for (const auto& statement : statements) { - auto gateCall = std::dynamic_pointer_cast(statement); - if (gateCall == nullptr) { - throw CompilerError("Only quantum statements are supported in blocks.", - statement->debugInfo); - } - const auto& qregs = qc->getQuantumRegisters(); - - auto op = evaluateGateCall(gateCall, gateCall->identifier, - gateCall->arguments, gateCall->operands, qregs); - - if (statements.size() == 1) { - return op; - } - - if (op != nullptr) { - blockOps->emplace_back(std::move(op)); - } - } - - if (blockOps->getOps().empty()) { - return nullptr; - } - return blockOps; -} - -std::pair -Importer::parseGateIdentifierCompatMode(const std::string& identifier) { - // we need to copy as we modify the string and need to return the original - // string if we don't find a match. - std::string gateIdentifier = identifier; - size_t implicitControls = 0; - while (!gateIdentifier.empty() && gateIdentifier[0] == 'c') { - gateIdentifier = gateIdentifier.substr(1); - implicitControls++; - } - - if (!gates.contains(gateIdentifier)) { - return std::pair{identifier, 0}; - } - return std::pair{gateIdentifier, implicitControls}; -} -} // namespace qasm3 diff --git a/src/qasm3/Parser.cpp b/src/qasm3/Parser.cpp deleted file mode 100644 index 4a2a8f1248..0000000000 --- a/src/qasm3/Parser.cpp +++ /dev/null @@ -1,937 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Parser.hpp" - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/StdGates.hpp" -#include "qasm3/Token.hpp" -#include "qasm3/Types.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qasm3 { -void Parser::scan() { - if (scanner.empty()) { - throw std::runtime_error("No scanner available"); - } - - if (!scanner.top().scan() && scanner.size() > 1) { - scanner.pop(); - if (includeDebugInfo) { - includeDebugInfo = includeDebugInfo->parent; - } - } -} - -std::shared_ptr Parser::makeDebugInfo(Token const& begin, - Token const& /*end*/) { - // Parameter `end` is currently not used. - return std::make_shared(begin.line, begin.col, - scanner.top().filename.value_or(""), - includeDebugInfo); -} - -std::shared_ptr Parser::makeDebugInfo(Token const& token) { - return std::make_shared(token.line, token.col, - scanner.top().filename.value_or(""), - includeDebugInfo); -} - -void Parser::error(const Token& token, const std::string& msg) { - throw CompilerError(msg, makeDebugInfo(token)); -} - -Token Parser::last() const { - if (scanner.empty()) { - throw std::runtime_error("No scanner available"); - } - return scanner.top().last; -} - -Token Parser::current() const { - if (scanner.empty()) { - throw std::runtime_error("No scanner available"); - } - return scanner.top().t; -} - -Token Parser::peek() const { - if (scanner.empty()) { - throw std::runtime_error("No scanner available"); - } - return scanner.top().next; -} - -Token Parser::expect(const Token::Kind& expected, - const std::optional& context) { - if (current().kind != expected) { - std::string message = "Expected '" + Token::kindToString(expected) + - "', got '" + Token::kindToString(current().kind) + - "'."; - if (context.has_value()) { - message += " " + context.value(); - } - error(current(), message); - } - - auto token = current(); - scan(); - return token; -} - -Parser::Parser(std::istream& is, const bool implicitlyIncludeStdgates) { - scanner.emplace(&is); - scan(); - if (implicitlyIncludeStdgates) { - scanner.emplace(std::make_unique(STDGATES), - "stdgates.inc", true); - scan(); - } -} - -std::shared_ptr Parser::parseVersionDeclaration() { - auto const tBegin = expect(Token::Kind::OpenQasm); - - double versionValue = 0.0; - if (current().kind == Token::Kind::FloatLiteral) { - Token const versionToken = expect(Token::Kind::FloatLiteral); - versionValue = versionToken.valReal; - } else if (current().kind == Token::Kind::IntegerLiteral) { - Token const versionToken = expect(Token::Kind::IntegerLiteral); - versionValue = static_cast(versionToken.val); - } else { - error(current(), "Version declaration must be a float or integer literal."); - } - - auto const tEnd = expect(Token::Kind::Semicolon); - return std::make_shared(makeDebugInfo(tBegin, tEnd), - versionValue); -} - -std::vector> Parser::parseProgram() { - std::vector> statements{}; - - bool versionDeclarationAllowed = true; - - while (!isAtEnd()) { - if (!scanner.top().isImplicitInclude) { - // We allow a version declaration at the beginning of the file. - if (current().kind == Token::Kind::OpenQasm) { - if (!versionDeclarationAllowed) { - error(current(), - "Version declaration must be at the beginning of the file."); - } - statements.emplace_back(parseVersionDeclaration()); - versionDeclarationAllowed = false; - continue; - } - // Once we encounter a non-comment token, we don't allow a version - // declaration anymore. - if (current().kind != Token::Kind::InitialLayout && - current().kind != Token::Kind::OutputPermutation) { - versionDeclarationAllowed = false; - } - } - - statements.push_back(parseStatement()); - } - return statements; -} - -std::shared_ptr Parser::parseStatement() { - if (current().kind == Token::Kind::Include) { - // We parse include and then continue in parseStatement, as the include - // statement just adds a new file to the scanner. - parseInclude(); - } - - if (current().kind == Token::Kind::Const) { - scan(); - return parseDeclaration(true, false); - } - if (current().kind == Token::Kind::Output) { - scan(); - return parseDeclaration(false, true); - } - - if (current().kind == Token::Kind::Int || - current().kind == Token::Kind::Uint || - current().kind == Token::Kind::Bit || - current().kind == Token::Kind::Qubit || - current().kind == Token::Kind::Float || - current().kind == Token::Kind::Angle || - current().kind == Token::Kind::Bool || - current().kind == Token::Kind::Duration || - current().kind == Token::Kind::CReg || - current().kind == Token::Kind::Qreg) { - return parseDeclaration(false, false); - } - - if (current().kind == Token::Kind::InitialLayout) { - const auto tBegin = current(); - scan(); - return std::make_shared( - InitialLayout{makeDebugInfo(tBegin), parsePermutation(tBegin.str)}); - } - if (current().kind == Token::Kind::OutputPermutation) { - const auto tBegin = current(); - scan(); - return std::make_shared( - OutputPermutation{makeDebugInfo(tBegin), parsePermutation(tBegin.str)}); - } - - if (current().kind == Token::Kind::Gate) { - return parseGateDefinition(); - } - if (current().kind == Token::Kind::Opaque) { - return parseOpaqueGateDefinition(); - } - - if (current().kind == Token::Kind::Identifier) { - // switch for readability - switch (peek().kind) { - case Token::Kind::LBracket: - case Token::Kind::Equals: - case Token::Kind::PlusEquals: - case Token::Kind::MinusEquals: - case Token::Kind::AsteriskEquals: - case Token::Kind::SlashEquals: - case Token::Kind::AmpersandEquals: - case Token::Kind::PipeEquals: - case Token::Kind::TildeEquals: - case Token::Kind::CaretEquals: - case Token::Kind::LeftShitEquals: - case Token::Kind::RightShiftEquals: - case Token::Kind::PercentEquals: - case Token::Kind::DoubleAsteriskEquals: - return parseAssignmentStatement(); - default: - break; - } - } - - if (current().kind == Token::Kind::If) { - return parseIfStatement(); - } - if (current().kind == Token::Kind::Measure) { - return parseMeasureStatement(); - } - - return parseQuantumStatement(); -} - -std::shared_ptr Parser::parseQuantumStatement() { - if (current().kind == Token::Kind::Inv || - current().kind == Token::Kind::Pow || - current().kind == Token::Kind::Ctrl || - current().kind == Token::Kind::NegCtrl || - current().kind == Token::Kind::Identifier || - current().kind == Token::Kind::Gphase) { - // TODO: since we do not support classical function calls yet, we can assume - // that this is a gate statement - return parseGateCallStatement(); - } - - if (current().kind == Token::Kind::Reset) { - return parseResetStatement(); - } - - if (current().kind == Token::Kind::Barrier) { - return parseBarrierStatement(); - } - - error(current(), - "Expected quantum statement, got '" + current().toString() + "'."); -} - -void Parser::parseInclude() { - auto const tBegin = expect(Token::Kind::Include); - auto filename = expect(Token::Kind::StringLiteral).str; - auto const tEnd = expect(Token::Kind::Semicolon); - - // we need to make sure to report errors across includes - includeDebugInfo = makeDebugInfo(tBegin, tEnd); - - // Here we add a new scanner to our stack and then continue with that one - - auto in = std::make_unique(filename, std::ifstream::in); - std::unique_ptr is{nullptr}; - if (in->fail()) { - if (filename == "stdgates.inc") { - // stdgates.inc has already been included implicitly, so we just return - if (includeDebugInfo) { - includeDebugInfo = includeDebugInfo->parent; - } - return; - } - if (filename == "qelib1.inc") { - is = std::make_unique(QE1LIB); - } else { - error(current(), "Failed to open file " + filename + "."); - } - } else { - is = std::move(in); - } - - scanner.emplace(std::move(is), filename); - scan(); -} - -std::shared_ptr Parser::parseAssignmentStatement() { - const auto indexedIdentifierToken = current(); - auto indexedIdentifier = parseIndexedIdentifier(); - AssignmentStatement::Type type{}; - switch (current().kind) { - case Token::Kind::Equals: - type = AssignmentStatement::Type::Assignment; - break; - case Token::Kind::PlusEquals: - type = AssignmentStatement::Type::PlusAssignment; - break; - case Token::Kind::MinusEquals: - type = AssignmentStatement::Type::MinusAssignment; - break; - case Token::Kind::AsteriskEquals: - type = AssignmentStatement::Type::TimesAssignment; - break; - case Token::Kind::SlashEquals: - type = AssignmentStatement::Type::DivAssignment; - break; - case Token::Kind::AmpersandEquals: - type = AssignmentStatement::Type::BitwiseAndAssignment; - break; - case Token::Kind::PipeEquals: - type = AssignmentStatement::Type::BitwiseOrAssignment; - break; - case Token::Kind::TildeEquals: - type = AssignmentStatement::Type::BitwiseNotAssignment; - break; - case Token::Kind::CaretEquals: - type = AssignmentStatement::Type::BitwiseXorAssignment; - break; - case Token::Kind::LeftShitEquals: - type = AssignmentStatement::Type::LeftShiftAssignment; - break; - case Token::Kind::RightShiftEquals: - type = AssignmentStatement::Type::RightShiftAssignment; - break; - case Token::Kind::PercentEquals: - type = AssignmentStatement::Type::ModuloAssignment; - break; - case Token::Kind::DoubleAsteriskEquals: - type = AssignmentStatement::Type::PowerAssignment; - break; - default: - error(current(), "Expected assignment operator"); - } - - scan(); - - auto declarationExpression = parseDeclarationExpression(); - - auto const tEnd = expect(Token::Kind::Semicolon); - - return std::make_shared( - makeDebugInfo(indexedIdentifierToken, tEnd), type, indexedIdentifier, - declarationExpression); -} - -std::shared_ptr Parser::parseMeasureStatement() { - auto const tBegin = expect(Token::Kind::Measure); - - auto gateOperand = parseGateOperand(); - - expect(Token::Kind::Arrow); - - auto cbitIdentifier = parseIndexedIdentifier(); - - auto const tEnd = expect(Token::Kind::Semicolon); - - std::shared_ptr const gateOperandExpr{ - std::make_shared(gateOperand)}; - return std::make_shared( - makeDebugInfo(tBegin, tEnd), AssignmentStatement::Type::Assignment, - cbitIdentifier, std::make_shared(gateOperandExpr)); -} - -std::shared_ptr Parser::parseResetStatement() { - auto const tBegin = expect(Token::Kind::Reset); - - auto operand = parseGateOperand(); - - auto const tEnd = expect(Token::Kind::Semicolon); - - return std::make_shared(makeDebugInfo(tBegin, tEnd), operand); -} - -std::shared_ptr Parser::parseBarrierStatement() { - auto const tBegin = expect(Token::Kind::Barrier); - - std::vector> operands{}; - while (current().kind != Token::Kind::Semicolon) { - operands.push_back(parseGateOperand()); - if (current().kind != Token::Kind::Semicolon) { - expect(Token::Kind::Comma); - } - } - - auto const tEnd = expect(Token::Kind::Semicolon); - - return std::make_shared(makeDebugInfo(tBegin, tEnd), - operands); -} - -std::shared_ptr Parser::parseIfStatement() { - const auto tBegin = expect(Token::Kind::If); - expect(Token::Kind::LParen, "after if keyword."); - auto condition = parseExpression(); - expect(Token::Kind::RParen, "after if condition."); - - std::vector> const thenStatements = - parseBlockOrStatement(); - std::vector> elseStatements; - - if (current().kind == Token::Kind::Else) { - expect(Token::Kind::Else); - - elseStatements = parseBlockOrStatement(); - } - - const auto tEnd = last(); - - return std::make_shared(std::move(condition), thenStatements, - elseStatements, - makeDebugInfo(tBegin, tEnd)); -} - -std::vector> Parser::parseBlockOrStatement() { - std::vector> statements; - - if (current().kind == Token::Kind::LBrace) { - scan(); - - while (!isAtEnd() && current().kind != Token::Kind::RBrace) { - statements.push_back(parseStatement()); - } - - expect(Token::Kind::RBrace); - } else { - statements.push_back(parseStatement()); - } - - return statements; -} - -std::shared_ptr Parser::parseGateCallStatement() { - auto const tBegin = current(); - std::vector> modifiers{}; - - while (current().kind == Token::Kind::Inv || - current().kind == Token::Kind::Pow || - current().kind == Token::Kind::Ctrl || - current().kind == Token::Kind::NegCtrl) { - modifiers.push_back(parseGateModifier()); - expect(Token::Kind::At); - } - - bool operandsOptional = false; - std::string identifier; - if (current().kind == Token::Kind::Gphase) { - scan(); - identifier = "gphase"; - operandsOptional = true; - } else { - identifier = expect(Token::Kind::Identifier).str; - } - - std::vector> arguments{}; - if (current().kind == Token::Kind::LParen) { - scan(); - while (current().kind != Token::Kind::RParen) { - arguments.push_back(parseExpression()); - if (current().kind != Token::Kind::RParen) { - expect(Token::Kind::Comma); - } - } - expect(Token::Kind::RParen); - } - - if (current().kind == Token::Kind::LBracket) { - // TODO: support designator - error(current(), "Designator not yet supported for gate call statements"); - } - - std::vector> operands{}; - while (current().kind != Token::Kind::Semicolon) { - operands.push_back(parseGateOperand()); - if (current().kind != Token::Kind::Semicolon) { - expect(Token::Kind::Comma); - } - } - - if (!operandsOptional && operands.empty()) { - // operands are only optional for gphase - error(current(), "Expected gate operands"); - } - - auto const tEnd = expect(Token::Kind::Semicolon); - - return std::make_shared( - GateCallStatement{makeDebugInfo(tBegin, tEnd), std::move(identifier), - modifiers, arguments, operands}); -} - -std::shared_ptr Parser::parseGateModifier() { - if (current().kind == Token::Kind::Inv) { - scan(); - return std::make_shared(InvGateModifier{}); - } - if (current().kind == Token::Kind::Pow) { - scan(); - expect(Token::Kind::LParen); - auto modifier = - std::make_shared(PowGateModifier{parseExpression()}); - expect(Token::Kind::RParen); - return modifier; - } - if (current().kind == Token::Kind::Ctrl || - current().kind == Token::Kind::NegCtrl) { - bool const ctrlType = current().kind == Token::Kind::Ctrl; - scan(); - - std::shared_ptr expression{nullptr}; - if (current().kind == Token::Kind::LParen) { - scan(); - expression = parseExpression(); - expect(Token::Kind::RParen); - } - - return std::make_shared( - CtrlGateModifier(ctrlType, expression)); - } - - error(current(), "Expected gate modifier"); -} - -std::shared_ptr Parser::parseIndexOperator() { - expect(Token::Kind::LBracket); - std::vector> indices{}; - while (current().kind != Token::Kind::RBracket) { - indices.push_back(parseExpression()); - if (current().kind != Token::Kind::RBracket) { - expect(Token::Kind::Comma); - } - } - expect(Token::Kind::RBracket); - return std::make_shared(indices); -} - -std::shared_ptr Parser::parseIndexedIdentifier() { - const auto identifier = expect(Token::Kind::Identifier); - std::vector> indexOperators{}; - while (current().kind == Token::Kind::LBracket) { - indexOperators.push_back(parseIndexOperator()); - } - return std::make_shared(identifier.str, indexOperators); -} - -std::shared_ptr Parser::parseGateOperand() { - if (current().kind == Token::Kind::HardwareQubit) { - const auto qubit = current().val; - scan(); - return std::make_shared(static_cast(qubit)); - } - return std::make_shared(parseIndexedIdentifier()); -} - -std::shared_ptr Parser::parseDeclaration(bool isConst, - bool isOutput) { - auto const tBegin = current(); - auto [type, isOldStyleDeclaration] = parseType(); - Token const identifier = expect(Token::Kind::Identifier); - - auto const name = identifier.str; - - if (current().kind == Token::Kind::LBracket) { - if (isOldStyleDeclaration) { - if (!type->allowsDesignator()) { - error(current(), "Type does not allow designator"); - } - // in this case, the designator expression is after the identifier - auto const designator = parseTypeDesignator(); - type->setDesignator(designator); - } else { - error(current(), "In OpenQASM 3.0, the designator has been changed to " - "`type[designator] identifier;`"); - } - } - - std::shared_ptr expression{nullptr}; - if (current().kind == Token::Kind::Equals) { - scan(); - expression = parseDeclarationExpression(); - } - - auto const tEnd = expect(Token::Kind::Semicolon); - - auto statement = std::make_shared(DeclarationStatement{ - makeDebugInfo(tBegin, tEnd), isConst, isOutput, type, name, expression}); - - return statement; -} - -std::shared_ptr Parser::parseGateDefinition() { - auto const tBegin = expect(Token::Kind::Gate); - auto const identifier = expect(Token::Kind::Identifier); - - std::shared_ptr parameters{nullptr}; - if (current().kind == Token::Kind::LParen) { - scan(); - parameters = parseIdentifierList(); - expect(Token::Kind::RParen); - } else { - parameters = std::make_shared(IdentifierList{}); - } - - const auto qubits = parseIdentifierList(); - - std::vector> statements{}; - expect(Token::Kind::LBrace); - while (current().kind != Token::Kind::RBrace) { - statements.emplace_back(parseQuantumStatement()); - } - auto const tEnd = expect(Token::Kind::RBrace); - - return std::make_shared( - GateDeclaration(makeDebugInfo(tBegin, tEnd), identifier.str, parameters, - qubits, statements)); -} - -std::shared_ptr Parser::parseOpaqueGateDefinition() { - auto const tBegin = expect(Token::Kind::Opaque); - auto const identifier = expect(Token::Kind::Identifier); - - std::shared_ptr parameters{nullptr}; - if (current().kind == Token::Kind::LParen) { - scan(); - parameters = parseIdentifierList(); - expect(Token::Kind::RParen); - } else { - parameters = std::make_shared(IdentifierList{}); - } - - const auto qubits = parseIdentifierList(); - - auto const tEnd = expect(Token::Kind::Semicolon); - - return std::make_shared( - GateDeclaration(makeDebugInfo(tBegin, tEnd), identifier.str, parameters, - qubits, {}, true)); -} - -std::shared_ptr Parser::parseDeclarationExpression() { - if (current().kind == Token::Kind::Measure) { - return std::make_shared( - DeclarationExpression{parseMeasureExpression()}); - } - - if (current().kind == Token::Kind::LBracket) { - error(current(), "Array expressions not supported yet"); - } - - return std::make_shared( - DeclarationExpression{parseExpression()}); -} - -std::shared_ptr Parser::parseMeasureExpression() { - expect(Token::Kind::Measure); - auto const gateOperand = parseGateOperand(); - return std::make_shared(MeasureExpression{gateOperand}); -} - -std::shared_ptr Parser::exponentiation() { - switch (current().kind) { - case Token::Kind::Minus: { - scan(); - const auto x = exponentiation(); - return std::make_shared( - UnaryExpression{UnaryExpression::Op::Negate, x}); - } - case Token::Kind::FloatLiteral: { - const auto val = current().valReal; - scan(); - return std::make_shared(Constant{val}); - } - case Token::Kind::IntegerLiteral: { - auto const val = current().val; - auto const isSigned = current().isSigned; - scan(); - return std::make_shared(Constant{val, isSigned}); - } - case Token::Kind::Identifier: { - return parseIndexedIdentifier(); - } - case Token::Kind::False: { - scan(); - return std::make_shared(false); - } - case Token::Kind::True: { - scan(); - return std::make_shared(true); - } - case Token::Kind::LParen: { - scan(); - auto x = parseExpression(); - expect(Token::Kind::RParen); - return x; - } - case Token::Kind::Sin: - case Token::Kind::Cos: - case Token::Kind::Tan: - case Token::Kind::Exp: - case Token::Kind::Ln: - case Token::Kind::Sqrt: { - UnaryExpression::Op op = UnaryExpression::Op::Sin; - switch (current().kind) { - case Token::Kind::Sin: - op = UnaryExpression::Op::Sin; - break; - case Token::Kind::Cos: - op = UnaryExpression::Op::Cos; - break; - case Token::Kind::Tan: - op = UnaryExpression::Op::Tan; - break; - case Token::Kind::Exp: - op = UnaryExpression::Op::Exp; - break; - case Token::Kind::Ln: - op = UnaryExpression::Op::Ln; - break; - case Token::Kind::Sqrt: - op = UnaryExpression::Op::Sqrt; - break; - default: - error(current(), "Expected unary operator"); - } - scan(); - expect(Token::Kind::LParen); - const auto x = parseExpression(); - expect(Token::Kind::RParen); - return std::make_shared(UnaryExpression{op, x}); - } - default: { - error(current(), "Expected expression, got " + current().toString() + "."); - } - } -} - -std::shared_ptr Parser::factor() { - auto x = exponentiation(); - while (current().kind == Token::Kind::Caret) { - scan(); - const auto y = exponentiation(); - x = std::make_shared(BinaryExpression::Op::Power, x, y); - } - return x; -} - -std::shared_ptr Parser::term() { - auto x = factor(); - while (current().kind == Token::Kind::Asterisk || - current().kind == Token::Kind::Slash) { - auto const op = current().kind == Token::Kind::Asterisk - ? BinaryExpression::Op::Multiply - : BinaryExpression::Op::Divide; - scan(); - const auto y = factor(); - x = std::make_shared(op, x, y); - } - return x; -} - -std::shared_ptr Parser::comparison() { - auto x = term(); - while (current().kind == Token::Kind::DoubleEquals || - current().kind == Token::Kind::NotEquals || - current().kind == Token::Kind::LessThan || - current().kind == Token::Kind::GreaterThan || - current().kind == Token::Kind::LessThanEquals || - current().kind == Token::Kind::GreaterThanEquals) { - BinaryExpression::Op op = BinaryExpression::Op::Equal; - switch (current().kind) { - case Token::Kind::DoubleEquals: - op = BinaryExpression::Op::Equal; - break; - case Token::Kind::NotEquals: - op = BinaryExpression::Op::NotEqual; - break; - case Token::Kind::LessThan: - op = BinaryExpression::Op::LessThan; - break; - case Token::Kind::GreaterThan: - op = BinaryExpression::Op::GreaterThan; - break; - case Token::Kind::LessThanEquals: - op = BinaryExpression::Op::LessThanOrEqual; - break; - case Token::Kind::GreaterThanEquals: - op = BinaryExpression::Op::GreaterThanOrEqual; - break; - default: - error(current(), "Expected comparison operator"); - } - scan(); - const auto y = term(); - x = std::make_shared(op, x, y); - } - return x; -} - -std::shared_ptr Parser::parseExpression() { - std::shared_ptr x{}; - if (current().kind == Token::Kind::Minus) { - scan(); - x = std::make_shared( - UnaryExpression{UnaryExpression::Op::Negate, term()}); - } else if (current().kind == Token::Kind::ExclamationPoint) { - scan(); - x = std::make_shared( - UnaryExpression{UnaryExpression::Op::LogicalNot, term()}); - } else if (current().kind == Token::Kind::Tilde) { - scan(); - x = std::make_shared( - UnaryExpression{UnaryExpression::Op::BitwiseNot, term()}); - } else { - x = comparison(); - } - - while (current().kind == Token::Kind::Plus || - current().kind == Token::Kind::Minus) { - auto const op = current().kind == Token::Kind::Plus - ? BinaryExpression::Op::Add - : BinaryExpression::Op::Subtract; - scan(); - const auto y = comparison(); - x = std::make_shared(BinaryExpression{op, x, y}); - } - - return x; -} - -std::shared_ptr Parser::parseIdentifierList() { - std::vector> identifierList{}; - - identifierList.emplace_back(std::make_shared( - IdentifierExpression{expect(Token::Kind::Identifier).str})); - - while (current().kind == Token::Kind::Comma) { - scan(); - identifierList.emplace_back(std::make_shared( - IdentifierExpression{expect(Token::Kind::Identifier).str})); - } - - return std::make_shared(IdentifierList{identifierList}); -} - -std::pair, bool> Parser::parseType() { - std::shared_ptr type; - bool isOldStyleDeclaration = false; - - const auto keyword = current().kind; - switch (keyword) { - case Token::Kind::CReg: - type = DesignatedType>::getBitTy(nullptr); - isOldStyleDeclaration = true; - break; - case Token::Kind::Qreg: - type = DesignatedType>::getQubitTy(nullptr); - isOldStyleDeclaration = true; - break; - case Token::Kind::Int: - type = DesignatedType>::getIntTy(nullptr); - break; - case Token::Kind::Uint: - type = DesignatedType>::getUintTy(nullptr); - break; - case Token::Kind::Bit: - type = DesignatedType>::getBitTy(nullptr); - break; - case Token::Kind::Qubit: - type = DesignatedType>::getQubitTy(nullptr); - break; - case Token::Kind::Float: - type = DesignatedType>::getFloatTy(nullptr); - break; - case Token::Kind::Angle: - type = DesignatedType>::getAngleTy(nullptr); - break; - case Token::Kind::Bool: - type = UnsizedType>::getBoolTy(); - break; - case Token::Kind::Duration: - type = UnsizedType>::getDurationTy(); - break; - default: - error(peek(), "Expected type"); - } - - scan(); - - if (!isOldStyleDeclaration && current().kind == Token::Kind::LBracket) { - if (!type->allowsDesignator()) { - error(peek(), "Type does not allow designator"); - } - auto designator = parseTypeDesignator(); - type->setDesignator(std::move(designator)); - return std::pair{std::move(type), isOldStyleDeclaration}; - } - if (keyword == Token::Kind::Qubit) { - return std::pair{ - UnsizedType>::getSingleQubitTy(), - isOldStyleDeclaration}; - } - - return std::pair{std::move(type), isOldStyleDeclaration}; -} - -std::shared_ptr Parser::parseTypeDesignator() { - expect(Token::Kind::LBracket); - auto expr = parseExpression(); - expect(Token::Kind::RBracket); - return expr; -} - -qc::Permutation Parser::parsePermutation(std::string s) { - qc::Permutation permutation{}; - static const auto QUBIT_REGEX = std::regex("\\d+"); - qc::Qubit logicalQubit = 0; - for (std::smatch m; std::regex_search(s, m, QUBIT_REGEX); s = m.suffix()) { - auto physicalQubit = static_cast(std::stoul(m.str())); - permutation.emplace(physicalQubit, logicalQubit); - ++logicalQubit; - } - return permutation; -} -} // namespace qasm3 diff --git a/src/qasm3/Scanner.cpp b/src/qasm3/Scanner.cpp deleted file mode 100644 index 7e08a18da5..0000000000 --- a/src/qasm3/Scanner.cpp +++ /dev/null @@ -1,688 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Scanner.hpp" - -#include "ir/Definitions.hpp" -#include "qasm3/Token.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace qasm3 { -char Scanner::readUtf8Codepoint(std::istream* in) { - char c = 0; - in->get(c); - return c; -} - -void Scanner::nextCh() { - if (!is->eof()) { - col++; - ch = readUtf8Codepoint(is); - } else { - ch = 0; - } - if (ch == '\n') { - col = 0; - line++; - } -} - -char Scanner::peek() const { - if (!is->eof()) { - return static_cast(is->peek()); - } - return 0; -} - -std::optional Scanner::consumeWhitespaceAndComments() { - while (isSpace(ch)) { - nextCh(); - } - if (ch == '/' && peek() == '/') { - Token t(line, col); - - std::string lineStr; - while (ch != '\n' && ch != 0) { - lineStr.push_back(ch); - nextCh(); - } - if (ch == '\n') { - nextCh(); - } - - // Keep original text (with //) - t.str = lineStr; - - std::string_view sv(lineStr); - sv.remove_prefix(2); - // trim leading spaces / tabs - while (!sv.empty() && (sv.front() == ' ' || sv.front() == '\t')) { - sv.remove_prefix(1); - } - - static const std::regex I_RE(R"(^i\s*\d+)", - std::regex::ECMAScript | std::regex::optimize); - static const std::regex O_RE(R"(^o\s*\d+)", - std::regex::ECMAScript | std::regex::optimize); - - if (std::regex_search(sv.begin(), sv.end(), I_RE)) { - t.kind = Token::Kind::InitialLayout; - } else if (std::regex_search(sv.begin(), sv.end(), O_RE)) { - t.kind = Token::Kind::OutputPermutation; - } else { - return consumeWhitespaceAndComments(); // recurse as before - } - - t.endCol = col; - t.endLine = line; - return t; - } - if (ch == '/' && peek() == '*') { - // consume /* - nextCh(); - nextCh(); - while (ch != 0 && (ch != '*' || peek() != '/')) { - nextCh(); - } - // consume */ - expect('*'); - expect('/'); - - // tail calls should be optimized away - return consumeWhitespaceAndComments(); - } - - return {}; -} - -Token Scanner::consumeName() { - Token t(line, col); - std::stringstream name; - while (isFirstIdChar(ch) || isNum(ch)) { - name << ch; - nextCh(); - } - - t.str = name.str(); - if (keywords.contains(t.str)) { - t.kind = keywords[t.str]; - } else { - t.kind = Token::Kind::Identifier; - } - - t.endCol = col; - t.endLine = line; - return t; -} - -void Scanner::error(const std::string& msg) const { - std::cerr << "Error at line " << line << ", column " << col << ": " << msg - << '\n'; -} - -void Scanner::expect(const char expected) { - if (ch != expected) { - error("Expected '" + std::to_string(expected) + "', got '" + ch + "'"); - } else { - nextCh(); - } -} - -bool Scanner::isValidDigit(const uint8_t base, const char c) { - if (base == 2) { - return c == '0' || c == '1'; - } - if (base == 8) { - return c >= '0' && c <= '7'; - } - if (base == 10) { - return isNum(c); - } - if (base == 16) { - return isHex(c); - } - return false; -} - -std::string Scanner::consumeNumberLiteral(const uint8_t base) { - std::stringstream ss; - while (isValidDigit(base, ch) || ch == '_') { - if (ch != '_') { - ss << ch; - } - nextCh(); - } - - return ss.str(); -} - -uint64_t Scanner::parseIntegerLiteral(const std::string& str, - const uint8_t base) { - uint64_t val = 0; - for (const auto c : str) { - if (isNum(c)) { - val *= base; - val += static_cast(c) - '0'; - } else { - val *= base; - val += static_cast(c) - 'a' + 10; - } - } - return val; -} - -Token Scanner::consumeNumberLiteral() { - Token t(line, col); - uint8_t base = 10; - - if (ch == '0') { - switch (peek()) { - case 'b': - case 'B': - base = 2; - nextCh(); - nextCh(); - break; - case 'o': - base = 8; - nextCh(); - nextCh(); - break; - case 'x': - case 'X': - base = 16; - nextCh(); - nextCh(); - break; - default: - break; - } - } - bool negative = false; - if (ch == '-') { - if (base != 10) { - error("Negative numbers are only allowed in base 10"); - } - negative = true; - nextCh(); - } - - const auto valBeforeDecimalSeparator = consumeNumberLiteral(base); - - if (ch == '.' || ch == 'e' || ch == 'E') { - if (base != 10) { - error("Float literals are only allowed in base 10"); - } - - std::stringstream ss{}; - ss << valBeforeDecimalSeparator; - - if (ch == '.') { - ss << ch; - nextCh(); - const auto valAfterDecimalSeparator = consumeNumberLiteral(base); - ss << valAfterDecimalSeparator; - } - - if (ch == 'e' || ch == 'E') { - ss << ch; - nextCh(); - if (ch == '+' || ch == '-') { - ss << ch; - nextCh(); - } - const auto valAfterExponent = consumeNumberLiteral(base); - ss << valAfterExponent; - } - - try { - t.valReal = std::stod(ss.str()); - } catch (std::invalid_argument&) { - error("Unable to parse float literal"); - } - - t.kind = Token::Kind::FloatLiteral; - if (negative) { - t.valReal *= -1; - } - } else { - t.val = static_cast( - parseIntegerLiteral(valBeforeDecimalSeparator, base)); - t.kind = Token::Kind::IntegerLiteral; - if (negative) { - t.val *= -1; - t.isSigned = true; - } - } - - const auto suffix1 = ch; - const auto suffix2 = peek(); - if (hasTimingSuffix(suffix1, suffix2)) { - double factor = 1.0; - nextCh(); - if (suffix1 != 's' && (suffix1 != 'd' || suffix2 != 't')) { - nextCh(); - const auto suffix = std::string{suffix1, suffix2}; - if (suffix == "ms") { - factor = 1e-3; - } else if (suffix == "us") { - factor = 1e-6; - } else if (suffix == "ns") { - factor = 1e-9; - } else if (suffix == "ps") { - factor = 1e-12; - } - } - if (t.kind == Token::Kind::FloatLiteral) { - t.valReal *= factor; - } else { - t.valReal = static_cast(t.val) * factor; - } - t.kind = Token::Kind::TimingLiteral; - } - - t.endCol = col; - t.endLine = line; - - return t; -} - -Token Scanner::consumeHardwareQubit() { - Token t(line, col); - - expect('$'); - - t.kind = Token::Kind::HardwareQubit; - t.val = 0; - while (isNum(ch)) { - t.val *= 10; - t.val += static_cast(ch - '0'); - nextCh(); - } - - t.endCol = col; - t.endLine = line; - - return t; -} - -Token Scanner::consumeString() { - Token t(line, col); - t.kind = Token::Kind::StringLiteral; - - if (ch != '"' && ch != '\'') { - error("expected `\"` or `'`"); - t.kind = Token::Kind::None; - return t; - } - const auto delim = ch; - nextCh(); - - std::stringstream content; - while (ch != delim) { - content << ch; - nextCh(); - } - - t.str = content.str(); - - expect(delim); - - t.endCol = col; - t.endLine = line; - - return t; -} - -Scanner::Scanner(std::istream* in) : is(in) { - keywords["OPENQASM"] = Token::Kind::OpenQasm; - keywords["include"] = Token::Kind::Include; - keywords["defcalgrammar"] = Token::Kind::DefCalGrammar; - keywords["def"] = Token::Kind::Def; - keywords["cal"] = Token::Kind::Cal; - keywords["defcal"] = Token::Kind::DefCal; - keywords["gate"] = Token::Kind::Gate; - keywords["opaque"] = Token::Kind::Opaque; - keywords["extern"] = Token::Kind::Extern; - keywords["box"] = Token::Kind::Box; - keywords["let"] = Token::Kind::Let; - keywords["break"] = Token::Kind::Break; - keywords["continue"] = Token::Kind::Continue; - keywords["if"] = Token::Kind::If; - keywords["else"] = Token::Kind::Else; - keywords["end"] = Token::Kind::End; - keywords["return"] = Token::Kind::Return; - keywords["for"] = Token::Kind::For; - keywords["while"] = Token::Kind::While; - keywords["in"] = Token::Kind::In; - keywords["pragma"] = Token::Kind::Pragma; - keywords["input"] = Token::Kind::Input; - keywords["output"] = Token::Kind::Output; - keywords["const"] = Token::Kind::Const; - keywords["readonly"] = Token::Kind::ReadOnly; - keywords["mutable"] = Token::Kind::Mutable; - keywords["qreg"] = Token::Kind::Qreg; - keywords["qubit"] = Token::Kind::Qubit; - keywords["creg"] = Token::Kind::CReg; - keywords["bool"] = Token::Kind::Bool; - keywords["bit"] = Token::Kind::Bit; - keywords["int"] = Token::Kind::Int; - keywords["uint"] = Token::Kind::Uint; - keywords["float"] = Token::Kind::Float; - keywords["angle"] = Token::Kind::Angle; - keywords["complex"] = Token::Kind::Complex; - keywords["array"] = Token::Kind::Array; - keywords["void"] = Token::Kind::Void; - keywords["duration"] = Token::Kind::Duration; - keywords["stretch"] = Token::Kind::Stretch; - keywords["gphase"] = Token::Kind::Gphase; - keywords["inv"] = Token::Kind::Inv; - keywords["pow"] = Token::Kind::Pow; - keywords["ctrl"] = Token::Kind::Ctrl; - keywords["negctrl"] = Token::Kind::NegCtrl; - keywords["#dim"] = Token::Kind::Dim; - keywords["durationof"] = Token::Kind::DurationOf; - keywords["delay"] = Token::Kind::Delay; - keywords["reset"] = Token::Kind::Reset; - keywords["measure"] = Token::Kind::Measure; - keywords["barrier"] = Token::Kind::Barrier; - keywords["true"] = Token::Kind::True; - keywords["false"] = Token::Kind::False; - keywords["im"] = Token::Kind::Imag; - keywords["sin"] = Token::Kind::Sin; - keywords["cos"] = Token::Kind::Cos; - keywords["tan"] = Token::Kind::Tan; - keywords["exp"] = Token::Kind::Exp; - keywords["ln"] = Token::Kind::Ln; - keywords["sqrt"] = Token::Kind::Sqrt; - - nextCh(); -} - -Token Scanner::next() { - if (const auto commentToken = consumeWhitespaceAndComments()) { - return *commentToken; - } - - if (isFirstIdChar(ch)) { - return consumeName(); - } - if (isNum(ch) || (ch == '.' && isNum(peek())) || - (ch == '-' && isNum(peek()))) { - return consumeNumberLiteral(); - } - if (ch == '$') { - return consumeHardwareQubit(); - } - - if (ch == '"' || ch == '\'') { - return consumeString(); - } - - Token t(line, col); - switch (ch) { - case 0: - t.kind = Token::Kind::Eof; - // Here we return as we don't want to call nextCh after EOF. - // We also don't set length, as the eof token has no length. - return t; - case '[': - t.kind = Token::Kind::LBracket; - break; - case ']': - t.kind = Token::Kind::RBracket; - break; - case '{': - t.kind = Token::Kind::LBrace; - break; - case '}': - t.kind = Token::Kind::RBrace; - break; - case '(': - t.kind = Token::Kind::LParen; - break; - case ')': - t.kind = Token::Kind::RParen; - break; - case ':': - t.kind = Token::Kind::Colon; - break; - case ';': - t.kind = Token::Kind::Semicolon; - break; - case '.': - t.kind = Token::Kind::Dot; - break; - case ',': - t.kind = Token::Kind::Comma; - break; - case '-': - switch (peek()) { - case '>': - nextCh(); - t.kind = Token::Kind::Arrow; - break; - case '=': - nextCh(); - t.kind = Token::Kind::MinusEquals; - break; - default: - t.kind = Token::Kind::Minus; - break; - } - break; - case '+': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::PlusEquals; - break; - case '+': - nextCh(); - t.kind = Token::Kind::DoublePlus; - break; - default: - t.kind = Token::Kind::Plus; - break; - } - break; - case '*': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::AsteriskEquals; - break; - case '*': - nextCh(); - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::DoubleAsteriskEquals; - } else { - t.kind = Token::Kind::DoubleAsterisk; - } - break; - default: - t.kind = Token::Kind::Asterisk; - break; - } - break; - case '/': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::SlashEquals; - } else { - t.kind = Token::Kind::Slash; - } - break; - case '%': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::PercentEquals; - } else { - t.kind = Token::Kind::Percent; - } - break; - case '|': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::PipeEquals; - break; - case '|': - nextCh(); - t.kind = Token::Kind::DoublePipe; - break; - default: - t.kind = Token::Kind::Pipe; - break; - } - break; - case '&': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::AmpersandEquals; - break; - case '&': - nextCh(); - t.kind = Token::Kind::DoubleAmpersand; - break; - default: - t.kind = Token::Kind::Ampersand; - break; - } - break; - case '^': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::CaretEquals; - } else { - t.kind = Token::Kind::Caret; - } - break; - case '~': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::TildeEquals; - } else { - t.kind = Token::Kind::Tilde; - } - break; - case '!': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::NotEquals; - } else { - t.kind = Token::Kind::ExclamationPoint; - } - break; - case '<': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::LessThanEquals; - break; - case '<': - nextCh(); - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::LeftShitEquals; - } else { - t.kind = Token::Kind::LeftShift; - } - break; - default: - t.kind = Token::Kind::LessThan; - break; - } - break; - case '>': - switch (peek()) { - case '=': - nextCh(); - t.kind = Token::Kind::GreaterThanEquals; - break; - case '>': - nextCh(); - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::RightShiftEquals; - } else { - t.kind = Token::Kind::RightShift; - } - break; - default: - t.kind = Token::Kind::GreaterThan; - break; - } - break; - case '=': - if (peek() == '=') { - nextCh(); - t.kind = Token::Kind::DoubleEquals; - } else { - t.kind = Token::Kind::Equals; - } - break; - case '@': - t.kind = Token::Kind::At; - break; - default: { - error("Unknown character '" + std::to_string(ch) + "'"); - t.kind = Token::Kind::None; - nextCh(); - break; - } - } - - nextCh(); - - t.endCol = col; - t.endLine = line; - return t; -} - -bool Scanner::isSpace(const char c) { - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -} - -bool Scanner::isFirstIdChar(const char c) { - return isalpha(c) != 0 || c == '_'; -} - -bool Scanner::isNum(const char c) { return c >= '0' && c <= '9'; } - -bool Scanner::isHex(const char c) { - return isNum(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -bool Scanner::hasTimingSuffix(const char first, const char second) { - if (first == 's') { - return true; - } - const auto suffixes = std::vector>{ - {'m', 's'}, {'u', 's'}, {'n', 's'}, {'p', 's'}, {'d', 't'}}; - return std::ranges::any_of(suffixes, [first, second](const auto& suffix) { - return suffix.first == first && suffix.second == second; - }); -} -} // namespace qasm3 diff --git a/src/qasm3/Statement.cpp b/src/qasm3/Statement.cpp deleted file mode 100644 index cc33bbd66a..0000000000 --- a/src/qasm3/Statement.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Statement.hpp" - -#include "ir/operations/IfElseOperation.hpp" -#include "qasm3/InstVisitor.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qasm3 { - -std::optional -getComparisonKind(const BinaryExpression::Op op) { - switch (op) { - case BinaryExpression::Op::LessThan: - return qc::ComparisonKind::Lt; - case BinaryExpression::Op::LessThanOrEqual: - return qc::ComparisonKind::Leq; - case BinaryExpression::Op::GreaterThan: - return qc::ComparisonKind::Gt; - case BinaryExpression::Op::GreaterThanOrEqual: - return qc::ComparisonKind::Geq; - case BinaryExpression::Op::Equal: - return qc::ComparisonKind::Eq; - case BinaryExpression::Op::NotEqual: - return qc::ComparisonKind::Neq; - default: - return std::nullopt; - } -} -void OutputPermutation::accept(InstVisitor* visitor) { - visitor->visitOutputPermutation(shared_from_this()); -} -void DeclarationStatement::accept(InstVisitor* visitor) { - visitor->visitDeclarationStatement(shared_from_this()); -} -void GateCallStatement::accept(InstVisitor* visitor) { - visitor->visitGateCallStatement(shared_from_this()); -} -void AssignmentStatement::accept(InstVisitor* visitor) { - visitor->visitAssignmentStatement(shared_from_this()); -} -void BarrierStatement::accept(InstVisitor* visitor) { - visitor->visitBarrierStatement(shared_from_this()); -} -void ResetStatement::accept(InstVisitor* visitor) { - visitor->visitResetStatement(shared_from_this()); -} -void IfStatement::accept(InstVisitor* visitor) { - visitor->visitIfStatement(shared_from_this()); -} - -GateDeclaration::GateDeclaration( - std::shared_ptr debug, std::string id, - std::shared_ptr params, - std::shared_ptr qbits, - std::vector> stmts, const bool opaque) - : Statement(std::move(debug)), identifier(std::move(id)), - parameters(std::move(params)), qubits(std::move(qbits)), - statements(std::move(stmts)), isOpaque(opaque) { - if (opaque) { - assert(statements.empty() && "Opaque gate should not have statements."); - } -} - -void GateDeclaration::accept(InstVisitor* visitor) { - visitor->visitGateStatement(shared_from_this()); -} -void VersionDeclaration::accept(InstVisitor* visitor) { - visitor->visitVersionDeclaration(shared_from_this()); -} -void InitialLayout::accept(InstVisitor* visitor) { - visitor->visitInitialLayout(shared_from_this()); -} -} // namespace qasm3 diff --git a/src/qasm3/Token.cpp b/src/qasm3/Token.cpp deleted file mode 100644 index 8343fbdbbb..0000000000 --- a/src/qasm3/Token.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Token.hpp" - -#include "ir/Definitions.hpp" - -#include -#include -#include - -namespace qasm3 { - -std::string Token::kindToString(const Kind kind) { - // Print a token kind string representation. - // This is the representation used in the error messages. - switch (kind) { - case Kind::None: - return "None"; - case Kind::OpenQasm: - return "OPENQASM"; - case Kind::Include: - return "include"; - case Kind::DefCalGrammar: - return "DefCalGrammar"; - case Kind::Def: - return "Def"; - case Kind::Cal: - return "Cal"; - case Kind::DefCal: - return "DefCal"; - case Kind::Gate: - return "gate"; - case Kind::Opaque: - return "opaque"; - case Kind::Extern: - return "extern"; - case Kind::Box: - return "box"; - case Kind::Let: - return "let"; - case Kind::Break: - return "break"; - case Kind::Continue: - return "continue"; - case Kind::If: - return "if"; - case Kind::Else: - return "else"; - case Kind::End: - return "end"; - case Kind::Return: - return "return"; - case Kind::For: - return "for"; - case Kind::While: - return "while"; - case Kind::In: - return "in"; - case Kind::Pragma: - return "pragma"; - case Kind::Input: - return "input"; - case Kind::Output: - return "output"; - case Kind::Const: - return "const"; - case Kind::ReadOnly: - return "readOnly"; - case Kind::Mutable: - return "mutable"; - case Kind::Qreg: - return "qreg"; - case Kind::Qubit: - return "qubit"; - case Kind::CReg: - return "cReg"; - case Kind::Bool: - return "bool"; - case Kind::Bit: - return "bit"; - case Kind::Int: - return "int"; - case Kind::Uint: - return "uint"; - case Kind::Float: - return "float"; - case Kind::Angle: - return "angle"; - case Kind::Complex: - return "complex"; - case Kind::Array: - return "array"; - case Kind::Void: - return "void"; - case Kind::Duration: - return "duration"; - case Kind::Stretch: - return "stretch"; - case Kind::Gphase: - return "gphase"; - case Kind::Inv: - return "inv"; - case Kind::Pow: - return "pow"; - case Kind::Ctrl: - return "ctrl"; - case Kind::NegCtrl: - return "negCtrl"; - case Kind::Dim: - return "#dim"; - case Kind::DurationOf: - return "durationof"; - case Kind::Delay: - return "delay"; - case Kind::Reset: - return "reset"; - case Kind::Measure: - return "measure"; - case Kind::Barrier: - return "barrier"; - case Kind::True: - return "true"; - case Kind::False: - return "false"; - case Kind::LBracket: - return "["; - case Kind::RBracket: - return "]"; - case Kind::LBrace: - return "{"; - case Kind::RBrace: - return "}"; - case Kind::LParen: - return "("; - case Kind::RParen: - return ")"; - case Kind::Colon: - return ":"; - case Kind::Semicolon: - return ";"; - case Kind::Eof: - return "Eof"; - case Kind::Dot: - return "."; - case Kind::Comma: - return ","; - case Kind::Equals: - return "="; - case Kind::Arrow: - return "->"; - case Kind::Plus: - return "+"; - case Kind::DoublePlus: - return "++"; - case Kind::Minus: - return "-"; - case Kind::Asterisk: - return "*"; - case Kind::DoubleAsterisk: - return "**"; - case Kind::Slash: - return "/"; - case Kind::Percent: - return "%"; - case Kind::Pipe: - return "|"; - case Kind::DoublePipe: - return "||"; - case Kind::Ampersand: - return "&"; - case Kind::DoubleAmpersand: - return "&&"; - case Kind::Caret: - return "^"; - case Kind::At: - return "@"; - case Kind::Tilde: - return "~"; - case Kind::ExclamationPoint: - return "!"; - case Kind::DoubleEquals: - return "=="; - case Kind::NotEquals: - return "!="; - case Kind::PlusEquals: - return "+="; - case Kind::MinusEquals: - return "-="; - case Kind::AsteriskEquals: - return "*="; - case Kind::SlashEquals: - return "/="; - case Kind::AmpersandEquals: - return "&="; - case Kind::PipeEquals: - return "|="; - case Kind::TildeEquals: - return "~="; - case Kind::CaretEquals: - return "^="; - case Kind::LeftShitEquals: - return "<<="; - case Kind::RightShiftEquals: - return ">>="; - case Kind::PercentEquals: - return "%="; - case Kind::DoubleAsteriskEquals: - return "**="; - case Kind::LessThan: - return "<"; - case Kind::LessThanEquals: - return "<="; - case Kind::GreaterThan: - return ">"; - case Kind::GreaterThanEquals: - return ">="; - case Kind::LeftShift: - return "<<"; - case Kind::RightShift: - return ">>"; - case Kind::Imag: - return "imag"; - case Kind::Underscore: - return "underscore"; - case Kind::DoubleQuote: - return "\""; - case Kind::SingleQuote: - return "'"; - case Kind::BackSlash: - return "\\"; - case Kind::Identifier: - return "Identifier"; - case Kind::HardwareQubit: - return "HardwareQubit"; - case Kind::StringLiteral: - return "StringLiteral"; - case Kind::IntegerLiteral: - return "IntegerLiteral"; - case Kind::FloatLiteral: - return "FloatLiteral"; - case Kind::TimingLiteral: - return "TimingLiteral"; - case Kind::Sin: - return "sin"; - case Kind::Cos: - return "cos"; - case Kind::Tan: - return "tan"; - case Kind::Exp: - return "exp"; - case Kind::Ln: - return "ln"; - case Kind::Sqrt: - return "sqrt"; - case Kind::InitialLayout: - return "InitialLayout"; - case Kind::OutputPermutation: - return "OutputPermutation"; - } - qc::unreachable(); -} - -std::string Token::toString() const { - std::stringstream ss; - ss << kindToString(kind); - switch (kind) { - case Kind::Identifier: - ss << " (" << str << ")"; - break; - case Kind::StringLiteral: - ss << " (\"" << str << "\")"; - break; - case Kind::InitialLayout: - case Kind::OutputPermutation: - ss << " (" << str << ")"; - break; - case Kind::IntegerLiteral: - ss << " (" << val << ")"; - break; - case Kind::FloatLiteral: - ss << " (" << valReal << ")"; - break; - case Kind::TimingLiteral: - ss << " (" << valReal << " [s])"; - break; - default: - break; - } - return ss.str(); -} - -std::ostream& operator<<(std::ostream& os, const Token::Kind& k) { - os << Token::kindToString(k); - return os; -} - -std::ostream& operator<<(std::ostream& os, const Token& t) { - os << t.toString(); - return os; -} -} // namespace qasm3 diff --git a/src/qasm3/Types.cpp b/src/qasm3/Types.cpp deleted file mode 100644 index a88ca32e2b..0000000000 --- a/src/qasm3/Types.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/Types.hpp" - -#include -#include -#include - -namespace qasm3 { - -template -DesignatedType::DesignatedType(DesignatedTy ty) - : type(ty), designator(nullptr) {} -template <> -bool DesignatedType>::fits( - const Type>& other) { - if (const auto* o = dynamic_cast(&other)) { - if (type == Int && o->type == Uint) { - return true; - } - if (type == Float && (o->type == Int || o->type == Uint)) { - return true; - } - - return type == o->type; - } - return false; -} - -template <> bool DesignatedType::fits(const Type& other) { - if (const auto* o = dynamic_cast(&other)) { - bool typeFits = type == o->type; - if (type == Int && o->type == Uint) { - typeFits = true; - } - if (type == Float && (o->type == Int || o->type == Uint)) { - typeFits = true; - } - - return typeFits && designator >= o->designator; - } - return false; -} - -template <> -DesignatedType::DesignatedType(DesignatedTy ty) - : type(ty), designator(0) { - switch (ty) { - case Qubit: - case Bit: - designator = 1; - break; - case Int: - case Uint: - designator = 32; - break; - case Float: - case Angle: - designator = 64; - break; - } -} - -template <> -std::string DesignatedType>::designatorToString() { - return "expr"; -} - -template <> std::string DesignatedType::designatorToString() { - return std::to_string(designator); -} - -} // namespace qasm3 diff --git a/src/qasm3/passes/ConstEvalPass.cpp b/src/qasm3/passes/ConstEvalPass.cpp deleted file mode 100644 index 3baa8c749a..0000000000 --- a/src/qasm3/passes/ConstEvalPass.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/passes/ConstEvalPass.hpp" - -#include "ir/Definitions.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/Types.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qasm3::const_eval { -namespace { -template T power(T base, T exponent) { - if (exponent == 0) { - return 1; - } - - T result = 1; - for (T i = 0; i < exponent; ++i) { - if (result > std::numeric_limits::max() / base) { - throw ConstEvalError("Integer overflow in constant evaluation."); - } - result *= base; - } - return result; -} -} // namespace - -void ConstEvalPass::processStatement(Statement& statement) { - try { - statement.accept(this); - } catch (const ConstEvalError& e) { - throw CompilerError(e.what(), statement.debugInfo); - } -} - -void ConstEvalPass::visitDeclarationStatement( - const std::shared_ptr declarationStatement) { - // The type designator expression is already resolved by the type check pass. - if (!declarationStatement->isConst) { - return; - } - if (declarationStatement->expression == nullptr) { - throw ConstEvalError( - "Constant declaration initialization expression must be initialized."); - } - - const auto value = visit(declarationStatement->expression->expression); - if (!value) { - throw ConstEvalError( - "Constant declaration initialization expression must be const."); - } - - declarationStatement->expression->expression = value->toExpr(); - - this->env.emplace(declarationStatement->identifier, *value); -} - -void ConstEvalPass::visitGateCallStatement( - std::shared_ptr gateCallStatement) { - for (auto& arg : gateCallStatement->arguments) { - if (auto evaluatedArg = visit(arg)) { - arg = evaluatedArg->toExpr(); - } - } - for (auto& op : gateCallStatement->operands) { - if (op->isHardwareQubit()) { - continue; - } - auto id = op->getIdentifier(); - for (auto& indexOperator : id->indices) { - for (auto& index : indexOperator->indexExpressions) { - if (auto evaluatedArg = visit(index)) { - index = evaluatedArg->toExpr(); - } - } - } - } - for (auto& modifier : gateCallStatement->modifiers) { - if (auto powModifier = std::dynamic_pointer_cast(modifier); - powModifier != nullptr && powModifier->expression != nullptr) { - if (auto evaluatedArg = visit(powModifier->expression)) { - powModifier->expression = evaluatedArg->toExpr(); - } - } else if (auto ctrlModifier = - std::dynamic_pointer_cast(modifier); - ctrlModifier != nullptr && ctrlModifier->expression != nullptr) { - if (auto evaluatedArg = visit(ctrlModifier->expression)) { - ctrlModifier->expression = evaluatedArg->toExpr(); - } - } - } -} - -std::shared_ptr ConstEvalValue::toExpr() const { - switch (type) { - case ConstInt: - return std::make_shared(Constant(std::get<0>(value), true)); - case ConstUint: - return std::make_shared(Constant(std::get<0>(value), false)); - case ConstFloat: - return std::make_shared(Constant(std::get<1>(value))); - case ConstBool: - return std::make_shared(Constant(std::get<2>(value))); - } - qc::unreachable(); -} - -bool ConstEvalValue::operator==(const ConstEvalValue& rhs) const { - if (type != rhs.type) { - return false; - } - - switch (type) { - case ConstInt: - case ConstUint: - return std::get<0>(value) == std::get<0>(rhs.value); - case ConstFloat: - return std::abs(std::get<1>(value) - std::get<1>(rhs.value)) < - std::numeric_limits::epsilon() * 1024; - case ConstBool: - return std::get<2>(value) == std::get<2>(rhs.value); - } - - return false; -} - -std::string ConstEvalValue::toString() const { - std::stringstream ss{}; - switch (type) { - case ConstInt: - ss << "ConstInt(" << std::get<0>(value) << ")"; - break; - case ConstUint: - ss << "ConstUint(" << std::get<0>(value) << ")"; - break; - case ConstFloat: - ss << "ConstFloat(" << std::get<1>(value) << ")"; - break; - case ConstBool: - ss << "ConstBool(" << std::get<2>(value) << ")"; - break; - } - - return ss.str(); -} - -namespace { -template int64_t castToWidth(const int64_t value) { - return static_cast(static_cast(value)); -} - -ConstEvalValue evalIntExpression(BinaryExpression::Op op, int64_t lhs, - int64_t rhs, size_t width, bool isSigned) { - auto lhsU = static_cast(lhs); - auto rhsU = static_cast(rhs); - ConstEvalValue result{0, isSigned, width}; - - // First evaluate the result. For some operations (e.g. division, comparison) - // we need to handle signed and unsigned integers differently. For others, - // such as addition and subtraction we can use the unsigned version directly. - switch (op) { - case BinaryExpression::Power: - if (isSigned) { - result.value = power(lhs, rhs); - } else { - result.value = static_cast(power(lhsU, rhsU)); - } - break; - case BinaryExpression::Add: - result.value = static_cast(lhsU + rhsU); - break; - case BinaryExpression::Subtract: - result.value = static_cast(lhsU - rhsU); - break; - case BinaryExpression::Multiply: - if (isSigned) { - result.value = lhs * rhs; - } else { - result.value = static_cast(lhsU * rhsU); - } - break; - case BinaryExpression::Divide: - if (isSigned) { - result.value = lhs / rhs; - } else { - result.value = static_cast(lhsU / rhsU); - } - break; - case BinaryExpression::Modulo: - if (isSigned) { - result.value = lhs % rhs; - } else { - result.value = static_cast(lhsU % rhsU); - } - break; - case BinaryExpression::LeftShift: - if (isSigned) { - result.value = lhs << rhs; - } else { - result.value = static_cast(lhsU << rhsU); - } - break; - case BinaryExpression::RightShift: - if (isSigned) { - result.value = lhs >> rhs; - } else { - result.value = static_cast(lhsU >> rhsU); - } - break; - case BinaryExpression::LessThan: - if (isSigned) { - result.value = lhs < rhs; - } else { - result.value = lhsU < rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::LessThanOrEqual: - if (isSigned) { - result.value = lhs <= rhs; - } else { - result.value = lhsU <= rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::GreaterThan: - if (isSigned) { - result.value = lhs > rhs; - } else { - result.value = lhsU > rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::GreaterThanOrEqual: - if (isSigned) { - result.value = lhs >= rhs; - } else { - result.value = lhsU >= rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::Equal: - if (isSigned) { - result.value = lhs == rhs; - } else { - result.value = lhsU == rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::NotEqual: - if (isSigned) { - result.value = lhs != rhs; - } else { - result.value = lhsU != rhsU; - } - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::BitwiseAnd: - result.value = static_cast(lhsU & rhsU); - break; - case BinaryExpression::BitwiseXor: - result.value = static_cast(lhsU ^ rhsU); - break; - case BinaryExpression::BitwiseOr: - result.value = static_cast(lhsU | rhsU); - break; - default: - throw ConstEvalError("Unsupported binary expression operator on integer."); - } - - // now we need to make sure the result is correct according to the bit width - // of the types - if (result.type == ConstEvalValue::ConstInt || - result.type == ConstEvalValue::ConstUint) { - switch (width) { - case 8: - result.value = castToWidth(std::get<0>(result.value)); - break; - case 16: - result.value = castToWidth(std::get<0>(result.value)); - break; - case 32: - result.value = castToWidth(std::get<0>(result.value)); - break; - case 64: - result.value = castToWidth(std::get<0>(result.value)); - break; - default: - throw ConstEvalError("Unsupported bit width."); - } - } - - return result; -} - -ConstEvalValue evalFloatExpression(const BinaryExpression::Op op, - const double lhs, const double rhs) { - ConstEvalValue result{0.0}; - - switch (op) { - case BinaryExpression::Power: - result.value = std::pow(lhs, rhs); - break; - case BinaryExpression::Add: - result.value = lhs + rhs; - break; - case BinaryExpression::Subtract: - result.value = lhs - rhs; - break; - case BinaryExpression::Multiply: - result.value = lhs * rhs; - break; - case BinaryExpression::Divide: - result.value = lhs / rhs; - break; - case BinaryExpression::Modulo: - result.value = fmod(lhs, rhs); - break; - case BinaryExpression::LessThan: - result.value = lhs < rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::LessThanOrEqual: - result.value = lhs <= rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::GreaterThan: - result.value = lhs > rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::GreaterThanOrEqual: - result.value = lhs >= rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::Equal: - result.value = lhs == rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - case BinaryExpression::NotEqual: - result.value = lhs != rhs; - result.type = ConstEvalValue::Type::ConstBool; - break; - default: - throw ConstEvalError( - "Unsupported binary expression operator on floating point."); - } - - return result; -} - -ConstEvalValue evalBoolExpression(const BinaryExpression::Op op, const bool lhs, - const bool rhs) { - ConstEvalValue result{false}; - - switch (op) { - case BinaryExpression::Op::Equal: - result.value = lhs == rhs; - break; - case BinaryExpression::Op::NotEqual: - result.value = lhs != rhs; - break; - case BinaryExpression::Op::BitwiseAnd: - result.value = lhs && rhs; - break; - case BinaryExpression::Op::BitwiseXor: - result.value = lhs != rhs; - break; - case BinaryExpression::Op::BitwiseOr: - result.value = lhs || rhs; - break; - case BinaryExpression::Op::LogicalAnd: - result.value = lhs && rhs; - break; - case BinaryExpression::Op::LogicalOr: - result.value = lhs || rhs; - break; - default: - throw ConstEvalError("Unsupported binary expression operator on boolean."); - } - - return result; -} -} // namespace - -std::optional ConstEvalPass::visitBinaryExpression( - const std::shared_ptr binaryExpression) { - // If we cannot evaluate either of the two operands, return. - auto lhsVal = visit(binaryExpression->lhs); - if (!lhsVal) { - return std::nullopt; - } - auto rhsVal = visit(binaryExpression->rhs); - if (!rhsVal) { - return std::nullopt; - } - - // We need to coerce the values to the correct type. - // ConstInt and ConstUint should be able to coerce to ConstFloat. - // ConstUint should coerce to ConstInt. - // ConstBool should not coerce. - // All other combinations are disallowed. - if ((lhsVal->type == ConstEvalValue::Type::ConstInt && - rhsVal->type == ConstEvalValue::Type::ConstUint) || - (lhsVal->type == ConstEvalValue::Type::ConstUint && - rhsVal->type == ConstEvalValue::Type::ConstInt)) { - lhsVal->type = ConstEvalValue::Type::ConstInt; - rhsVal->type = ConstEvalValue::Type::ConstInt; - } else if ((lhsVal->type == ConstEvalValue::Type::ConstUint || - lhsVal->type == ConstEvalValue::Type::ConstInt) && - rhsVal->type == ConstEvalValue::Type::ConstFloat) { - lhsVal->value = static_cast(std::get<0>(lhsVal->value)); - lhsVal->type = ConstEvalValue::Type::ConstFloat; - } else if (lhsVal->type == ConstEvalValue::Type::ConstFloat && - rhsVal->type == ConstEvalValue::Type::ConstUint) { - rhsVal->value = - static_cast(static_cast(std::get<0>(rhsVal->value))); - rhsVal->type = ConstEvalValue::Type::ConstFloat; - } else if (lhsVal->type == ConstEvalValue::Type::ConstFloat && - rhsVal->type == ConstEvalValue::Type::ConstInt) { - rhsVal->value = static_cast(std::get<0>(rhsVal->value)); - rhsVal->type = ConstEvalValue::Type::ConstFloat; - } else if (lhsVal->type != rhsVal->type) { - throw ConstEvalError( - "Type mismatch, cannot evaluate binary expression on types " + - std::to_string(lhsVal->type) + " and " + std::to_string(rhsVal->type) + - "."); - } - - // If we are operating on two types with different width, coerce to the wider. - size_t const width = std::max(lhsVal->width, rhsVal->width); - - switch (lhsVal->type) { - case ConstEvalValue::Type::ConstInt: - return evalIntExpression(binaryExpression->op, std::get<0>(lhsVal->value), - std::get<0>(rhsVal->value), width, true); - case ConstEvalValue::Type::ConstUint: - return evalIntExpression(binaryExpression->op, std::get<0>(lhsVal->value), - std::get<0>(rhsVal->value), width, false); - case ConstEvalValue::Type::ConstFloat: - return evalFloatExpression(binaryExpression->op, std::get<1>(lhsVal->value), - std::get<1>(rhsVal->value)); - case ConstEvalValue::Type::ConstBool: - return evalBoolExpression(binaryExpression->op, std::get<2>(lhsVal->value), - std::get<2>(rhsVal->value)); - } - - throw ConstEvalError("Unhandled binary expression type."); -} - -std::optional ConstEvalPass::visitUnaryExpression( - const std::shared_ptr unaryExpression) { - // If we cannot evaluate the operand, return. - auto val = visit(unaryExpression->operand); - if (!val) { - return std::nullopt; - } - - // For each unary operator, we need to check the type of the operand and - // determine whether the operation is valid. - switch (unaryExpression->op) { - case UnaryExpression::BitwiseNot: - if (val->type != ConstEvalValue::Type::ConstInt && - val->type != ConstEvalValue::Type::ConstUint) { - return std::nullopt; - } - val->value = ~std::get<0>(val->value); - break; - case UnaryExpression::LogicalNot: - if (val->type == ConstEvalValue::Type::ConstBool) { - val->value = !std::get<2>(val->value); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Negate: - if (val->type == ConstEvalValue::Type::ConstInt) { - val->value = -std::get<0>(val->value); - } else if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = -std::get<1>(val->value); - } else { - return std::nullopt; - } - break; - case UnaryExpression::DurationOf: - return std::nullopt; - case UnaryExpression::Sin: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::sin(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Cos: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::cos(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Tan: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::tan(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Exp: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::exp(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Ln: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::log(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - case UnaryExpression::Sqrt: - if (val->type == ConstEvalValue::Type::ConstFloat) { - val->value = std::sqrt(std::get<1>(val->value)); - } else { - return std::nullopt; - } - break; - } - - return val; -} - -std::optional ConstEvalPass::visitConstantExpression( - const std::shared_ptr constant) { - - if (constant->isFP()) { - return ConstEvalValue{constant->getFP()}; - } - if (constant->isSInt()) { - return ConstEvalValue{constant->getSInt(), true}; - } - if (constant->isBool()) { - return ConstEvalValue(constant->getBool()); - } - assert(constant->isUInt()); - // we still call getSInt here as we will store the int value as its bit - // representation and won't interpret it as such - return ConstEvalValue{constant->getSInt(), false}; -} - -std::optional ConstEvalPass::visitIdentifierExpression( - const std::shared_ptr identifierExpression) { - return env.find(identifierExpression->identifier); -} - -std::optional ConstEvalPass::visitIdentifierList( - std::shared_ptr /*identifierList*/) { - return std::nullopt; -} - -std::optional ConstEvalPass::visitIndexedIdentifier( - std::shared_ptr indexedIdentifier) { - if (indexedIdentifier->indices.empty()) { - return visitIdentifierExpression( - std::make_shared(indexedIdentifier->identifier)); - } - // Cannot yet evaluate indexed expressions - return std::nullopt; -} - -std::optional ConstEvalPass::visitMeasureExpression( - std::shared_ptr /*measureExpression*/) { - return std::nullopt; -} -std::shared_ptr ConstEvalPass::visitDesignatedType( - DesignatedType>* designatedType) { - if (designatedType->designator == nullptr) { - const auto ty = - std::make_shared>(designatedType->type); - return std::dynamic_pointer_cast(ty); - } - const auto result = visit(designatedType->designator); - if (!result) { - throw ConstEvalError("Designator must be a constant expression."); - } - if (result->type == ConstEvalValue::Type::ConstUint || - (result->type == ConstEvalValue::Type::ConstInt && - std::get<0>(result->value) >= 0)) { - const auto ty = std::make_shared>( - designatedType->type, - static_cast(std::get<0>(result->value))); - return std::dynamic_pointer_cast(ty); - } - throw ConstEvalError("Designator must be an unsigned integer."); -} -std::shared_ptr ConstEvalPass::visitUnsizedType( - UnsizedType>* unsizedType) { - return std::make_shared>(unsizedType->type); -} -std::shared_ptr ConstEvalPass::visitArrayType( - ArrayType>* arrayType) { - std::shared_ptr> const inner = arrayType->type->accept(this); - const auto size = visit(arrayType->size); - if (!size.has_value()) { - throw ConstEvalError("Array size must be a constant expression."); - } - if (size->type != ConstEvalValue::Type::ConstUint) { - throw ConstEvalError("Array size must be an unsigned integer."); - } - return std::make_shared>( - inner, static_cast(std::get<0>(size->value))); -} -} // namespace qasm3::const_eval diff --git a/src/qasm3/passes/TypeCheckPass.cpp b/src/qasm3/passes/TypeCheckPass.cpp deleted file mode 100644 index 79b4acb323..0000000000 --- a/src/qasm3/passes/TypeCheckPass.cpp +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "qasm3/passes/TypeCheckPass.hpp" - -#include "qasm3/Exception.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/Types.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qasm3::type_checking { - -InferredType TypeCheckPass::error(const std::string& msg, - const std::shared_ptr& debugInfo) { - std::cerr << "Type check error: " << msg << '\n'; - if (debugInfo) { - std::cerr << " " << debugInfo->toString() << '\n'; - } - hasError = true; - errMessage = msg; - return InferredType::error(); -} - -void TypeCheckPass::processStatement(Statement& statement) { - try { - statement.accept(this); - - if (hasError) { - throw TypeCheckError(errMessage); - } - } catch (const TypeCheckError& e) { - throw CompilerError(e.what(), statement.debugInfo); - } -} - -void TypeCheckPass::checkIndexOperator(const IndexOperator& indexOperator) { - for (const auto& index : indexOperator.indexExpressions) { - if (const auto type = visit(index); !type.isError && !type.type->isUint()) { - error("Index must be an unsigned integer"); - } - } -} - -void TypeCheckPass::checkIndexedIdentifier(const IndexedIdentifier& id) { - const auto it = env.find(id.identifier); - if (it == env.end()) { - error("Unknown identifier '" + id.identifier + "'."); - return; - } - if (!id.indices.empty() && !it->second.type->allowsDesignator()) { - error("Type '" + it->second.type->toString() + "' cannot be indexed."); - return; - } - for (const auto& index : id.indices) { - checkIndexOperator(*index); - } -} - -void TypeCheckPass::checkGateOperand(const GateOperand& operand) { - if (operand.isHardwareQubit()) { - return; - } - checkIndexedIdentifier(*operand.getIdentifier()); -} - -void TypeCheckPass::visitGateStatement( - const std::shared_ptr gateStatement) { - // we save the current environment to restore it afterward - const auto oldEnv = env; - - for (const auto& param : gateStatement->parameters->identifiers) { - env.emplace(param->identifier, - InferredType{std::dynamic_pointer_cast( - DesignatedType::getFloatTy(64))}); - } - for (const auto& operand : gateStatement->qubits->identifiers) { - env.emplace(operand->identifier, - InferredType{std::dynamic_pointer_cast( - DesignatedType::getQubitTy(1))}); - } - - for (const auto& stmt : gateStatement->statements) { - stmt->accept(this); - } - - // restore the environment - env = oldEnv; -} - -void TypeCheckPass::visitVersionDeclaration( - std::shared_ptr /*versionDeclaration*/) {} - -void TypeCheckPass::visitDeclarationStatement( - const std::shared_ptr declarationStatement) { - // Type checking declarations is a bit involved. If the type contains a - // designator expression, we need to resolve the statement in three steps. - const auto typeExpr = std::get<0>(declarationStatement->type); - // First, type-check the type itself. - if (typeExpr->allowsDesignator() && typeExpr->getDesignator() != nullptr) { - auto type = visit(typeExpr->getDesignator()); - if (type.isError || !type.type->isUint()) { - error("Designator expression type check failed.", - declarationStatement->debugInfo); - return; - } - } - // Now we know the type is valid, we can evaluate the designator expression. - auto resolvedType = - std::get<0>(declarationStatement->type)->accept(constEvalPass); - if (!resolvedType) { - throw TypeCheckError("Expression in types must be const."); - } - declarationStatement->type = resolvedType; - - // Lastly, we type check the actual expression - if (declarationStatement->expression != nullptr) { - const auto exprType = visit(declarationStatement->expression->expression); - if (!exprType.isError && !resolvedType->fits(*exprType.type)) { - std::stringstream ss; - ss << "Type mismatch in declaration statement: Expected '"; - ss << resolvedType->toString(); - ss << "', found '"; - ss << exprType.type->toString(); - ss << "'."; - error(ss.str()); - } - } - - env.emplace(declarationStatement->identifier, InferredType{resolvedType}); -} - -void TypeCheckPass::visitInitialLayout( - std::shared_ptr /*initialLayout*/) {} - -void TypeCheckPass::visitOutputPermutation( - std::shared_ptr /*outputPermutation*/) {} - -void TypeCheckPass::visitGateCallStatement( - const std::shared_ptr gateCallStatement) { - for (const auto& arg : gateCallStatement->arguments) { - visit(arg); - } -} - -void TypeCheckPass::visitAssignmentStatement( - const std::shared_ptr assignmentStatement) { - checkIndexedIdentifier(*assignmentStatement->identifier); - - const auto exprTy = visit(assignmentStatement->expression->expression); - const auto idTy = env.find(assignmentStatement->identifier->identifier); - - if (idTy == env.end()) { - error("Unknown identifier '" + assignmentStatement->identifier->identifier + - "'.", - assignmentStatement->debugInfo); - return; - } - - if (exprTy.isError) { - return; - } - - if (!idTy->second.type->fits(*exprTy.type)) { - std::stringstream ss; - ss << "Type mismatch in assignment. Expected '"; - ss << idTy->second.type->toString(); - ss << "', found '"; - ss << exprTy.type->toString(); - ss << "'."; - error(ss.str(), assignmentStatement->debugInfo); - } -} - -void TypeCheckPass::visitBarrierStatement( - const std::shared_ptr barrierStatement) { - for (auto& gate : barrierStatement->gates) { - checkGateOperand(*gate); - } -} - -void TypeCheckPass::visitResetStatement( - const std::shared_ptr resetStatement) { - checkGateOperand(*resetStatement->gate); -} - -InferredType TypeCheckPass::visitBinaryExpression( - const std::shared_ptr binaryExpression) { - auto lhs = visit(binaryExpression->lhs); - auto rhs = visit(binaryExpression->rhs); - if (rhs.isError) { - return rhs; - } - if (lhs.isError) { - return lhs; - } - - auto ty = lhs; - if (lhs.type->isConvertibleToBool() && rhs.type->isConvertibleToBool()) { - ty = InferredType{UnsizedType::getBoolTy()}; - } else if (lhs.type->isNumber() && rhs.type->isNumber()) { - if (rhs.type->isFP() || lhs.type->isUint()) { - // coerce to float or signed int - ty = rhs; - } - ty.type->setDesignator( - std::max(lhs.type->getDesignator(), rhs.type->getDesignator())); - } else if (lhs.type != rhs.type) { - std::stringstream ss; - ss << "Type mismatch in binary expression: "; - ss << lhs.type->toString(); - ss << ", "; - ss << rhs.type->toString(); - ss << "."; - return error(ss.str()); - } - - switch (binaryExpression->op) { - case BinaryExpression::Power: - case BinaryExpression::Add: - case BinaryExpression::Subtract: - case BinaryExpression::Multiply: - case BinaryExpression::Divide: - case BinaryExpression::Modulo: - case BinaryExpression::LeftShift: - case BinaryExpression::RightShift: - if (!ty.type->isNumber()) { - return error("Cannot apply arithmetic operation to non-numeric type."); - } - break; - case BinaryExpression::LessThan: - case BinaryExpression::LessThanOrEqual: - case BinaryExpression::GreaterThan: - case BinaryExpression::GreaterThanOrEqual: - // all types except for bool - if (ty.type->isBool()) { - return error("Cannot compare boolean types."); - } - return InferredType{UnsizedType::getBoolTy()}; - case BinaryExpression::Equal: - case BinaryExpression::NotEqual: - return InferredType{UnsizedType::getBoolTy()}; - case BinaryExpression::BitwiseAnd: - case BinaryExpression::BitwiseXor: - case BinaryExpression::BitwiseOr: - if (!ty.type->isNumber()) { - return error("Cannot apply bitwise operation to non-numeric type."); - } - break; - case BinaryExpression::LogicalAnd: - case BinaryExpression::LogicalOr: - if (!ty.type->isBool()) { - return error("Cannot apply logical operation to non-boolean type."); - } - break; - } - - return ty; -} - -InferredType TypeCheckPass::visitUnaryExpression( - const std::shared_ptr unaryExpression) { - auto type = visit(unaryExpression->operand); - - switch (unaryExpression->op) { - case UnaryExpression::BitwiseNot: - if (!type.type->isNumber()) { - return error("Cannot apply bitwise not to non-numeric type."); - } - break; - case UnaryExpression::LogicalNot: - if (!type.type->isConvertibleToBool()) { - return error("Cannot apply logical not to non-boolean type."); - } - break; - case UnaryExpression::Negate: - break; - case UnaryExpression::DurationOf: - return InferredType{UnsizedType::getDurationTy()}; - case UnaryExpression::Sin: - case UnaryExpression::Cos: - case UnaryExpression::Tan: - case UnaryExpression::Exp: - case UnaryExpression::Ln: - case UnaryExpression::Sqrt: - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getFloatTy(64))}; - } - - return type; -} - -InferredType TypeCheckPass::visitConstantExpression( - const std::shared_ptr constant) { - if (constant->isFP()) { - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getFloatTy(64))}; - } - if (constant->isBool()) { - return InferredType(std::dynamic_pointer_cast( - UnsizedType::getBoolTy())); - } - assert(constant->isInt()); - - size_t width = 32; - if ((constant->isSInt() && constant->getSInt() > INT32_MAX) || - (constant->isUInt() && constant->getUInt() > UINT32_MAX)) { - width = 64; - } - - if (constant->isSInt()) { - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getIntTy(width))}; - } - - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getUintTy(width))}; -} - -InferredType TypeCheckPass::visitIdentifierExpression( - const std::shared_ptr identifierExpression) { - const auto type = env.find(identifierExpression->identifier); - if (type == env.end()) { - error("Unknown identifier '" + identifierExpression->identifier + "'."); - return InferredType::error(); - } - return type->second; -} - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" -#endif -InferredType TypeCheckPass::visitIdentifierList( - std::shared_ptr /*identifierList*/) { - throw TypeCheckError("TypeCheckPass::visitIdentifierList not implemented"); -} -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -InferredType TypeCheckPass::visitIndexedIdentifier( - const std::shared_ptr indexedIdentifier) { - auto type = visitIdentifierExpression( - std::make_shared(indexedIdentifier->identifier)); - if (indexedIdentifier->indices.empty()) { - return type; - } - if (type.isError) { - return type; - } - if (!type.type->allowsDesignator()) { - return error("Type '" + type.type->toString() + "' cannot be indexed."); - } - // Assume that indexed access always results in a single element - type.type->setDesignator(1); - return type; -} - -InferredType TypeCheckPass::visitMeasureExpression( - const std::shared_ptr measureExpression) { - if (measureExpression->gate->isHardwareQubit()) { - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getBitTy(1))}; - } - - const auto indexedIdentifier = measureExpression->gate->getIdentifier(); - checkIndexedIdentifier(*indexedIdentifier); - if (!indexedIdentifier->indices.empty()) { - // This will need modification once we want to support index ranges. - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getBitTy(1))}; - } - const auto it = env.find(indexedIdentifier->identifier); - if (it == env.end()) { - error("Unknown identifier '" + indexedIdentifier->identifier + "'."); - return InferredType::error(); - } - uint64_t width = 0; - const auto type = it->second.type; - if (type->allowsDesignator()) { - width = type->getDesignator(); - } else { - const auto unsizedType = - std::dynamic_pointer_cast>(type); - if (!unsizedType || unsizedType->type != SingleQubit) { - return error("Cannot measure non-qubit type."); - } - width = 1; - } - return InferredType{std::dynamic_pointer_cast( - DesignatedType::getBitTy(width))}; -} - -void TypeCheckPass::visitIfStatement( - const std::shared_ptr ifStatement) { - // We support ifs on bits and bools - if (const auto ty = visit(ifStatement->condition); - !ty.isError && !ty.type->isConvertibleToBool()) { - error("Condition expression must be bool."); - } - - for (const auto& stmt : ifStatement->thenStatements) { - stmt->accept(this); - } - for (const auto& stmt : ifStatement->elseStatements) { - stmt->accept(this); - } -} -} // namespace qasm3::type_checking diff --git a/src/qdmi/Client.cpp b/src/qdmi/Client.cpp index 3ad9976beb..f960114667 100644 --- a/src/qdmi/Client.cpp +++ b/src/qdmi/Client.cpp @@ -648,25 +648,13 @@ std::vector Job::getShots() const { return {}; } - std::string shots(shotsSize - 1, '\0'); + std::string shots(shotsSize, '\0'); qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_SHOTS, shotsSize, shots.data(), nullptr), "Querying shots"); + shots.pop_back(); - // Parse the shots (comma-separated) - std::vector shotsVec; - const auto numShots = getNumShots(); - shotsVec.reserve(numShots); - std::istringstream shotsStream(shots); - std::string shot; - while (std::getline(shotsStream, shot, ',')) { - shotsVec.emplace_back(shot); - } - if (shotsVec.size() != numShots) { - throw std::runtime_error("Number of shots mismatch"); - } - - return shotsVec; + return detail::parseShots(shots, getNumShots()); } std::map Job::getCounts() const { @@ -680,10 +668,11 @@ std::map Job::getCounts() const { return {}; // Empty histogram } - std::string keys(keysSize - 1, '\0'); + std::string keys(keysSize, '\0'); qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_HIST_KEYS, keysSize, keys.data(), nullptr), "Querying histogram keys"); + keys.pop_back(); // Get the histogram values size_t valuesSize = 0; @@ -705,6 +694,10 @@ std::map Job::getCounts() const { // Parse the keys (comma-separated) std::map counts; + if (keys.empty() && values.size() == 1) { + counts[""] = values.front(); + return counts; + } std::istringstream keysStream(keys); std::string key; size_t idx = 0; @@ -774,11 +767,12 @@ std::map> Job::getSparseStateVector() const { return {}; // Empty state vector } - std::string keys(keysSize - 1, '\0'); + std::string keys(keysSize, '\0'); qdmi::throwIfError( QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, keysSize, keys.data(), nullptr), "Querying sparse state vector keys"); + keys.pop_back(); size_t valuesSize = 0; qdmi::throwIfError(QDMI_job_get_results( @@ -801,6 +795,10 @@ std::map> Job::getSparseStateVector() const { // Parse the keys (comma-separated) std::map> stateVector; + if (keys.empty() && values.size() == 1) { + stateVector[""] = values.front(); + return stateVector; + } std::istringstream keysStream(keys); std::string key; size_t idx = 0; @@ -829,11 +827,12 @@ std::map Job::getSparseProbabilities() const { return {}; // Empty probabilities } - std::string keys(keysSize - 1, '\0'); + std::string keys(keysSize, '\0'); qdmi::throwIfError(QDMI_job_get_results( job_.get(), QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, keysSize, keys.data(), nullptr), "Querying sparse probabilities keys"); + keys.pop_back(); size_t valuesSize = 0; qdmi::throwIfError( @@ -856,6 +855,10 @@ std::map Job::getSparseProbabilities() const { // Parse the keys (comma-separated) std::map probabilities; + if (keys.empty() && values.size() == 1) { + probabilities[""] = values.front(); + return probabilities; + } std::istringstream keysStream(keys); std::string key; size_t idx = 0; diff --git a/src/qdmi/devices/CMakeLists.txt b/src/qdmi/devices/CMakeLists.txt index f833239714..bfe0e2aab6 100644 --- a/src/qdmi/devices/CMakeLists.txt +++ b/src/qdmi/devices/CMakeLists.txt @@ -6,7 +6,7 @@ # # Licensed under the MIT License -if(BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) +if(BUILD_MQT_CORE_MLIR AND BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) add_subdirectory(dd) endif() if(BUILD_MQT_CORE_QDMI_SC_DEVICE) diff --git a/src/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index 02509d87c9..a1ae276404 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -38,17 +38,15 @@ if(NOT TARGET ${TARGET_NAME}) ${QDMI_HDRS}) # Add link libraries - target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreQDMICommon) - if(BUILD_MQT_CORE_MLIR) - mqt_llvm_target_disable_rtti(${TARGET_NAME}) - target_compile_definitions(${TARGET_NAME} PRIVATE BUILD_MQT_CORE_MLIR) - if(UNIX AND NOT APPLE) - # Keep symbols from static dependencies local. A plugin-enabled host can otherwise interpose - # LLVM's process-wide registries when it loads this device. - target_link_options(${TARGET_NAME} PRIVATE "LINKER:--exclude-libs,ALL") - endif() - target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT MQT::CoreQIRRuntime) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreDD MQT::CoreQDMICommon) + mqt_llvm_target_disable_rtti(${TARGET_NAME}) + if(UNIX AND NOT APPLE) + # Keep symbols from static dependencies local. A plugin-enabled host can otherwise interpose + # LLVM's process-wide registries when it loads this device. + target_link_options(${TARGET_NAME} PRIVATE "LINKER:--exclude-libs,ALL") endif() + target_link_libraries(${TARGET_NAME} PRIVATE MLIRQCODDFunctionality MQTCompilerPipeline + MQT::CoreQIRJIT MQT::CoreQIRRuntime) mqt_configure_qdmi_device(${TARGET_NAME} ID mqt.ddsim.default PREFIX ${QDMI_PREFIX}) list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index ea43440489..452ff7f0fa 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -16,21 +16,19 @@ #include "dd/DDDefinitions.hpp" #include "dd/Package.hpp" -#include "dd/Simulation.hpp" -#include "dd/StateGeneration.hpp" -#include "ir/QuantumComputation.hpp" -#ifdef BUILD_MQT_CORE_MLIR +#include "mlir/Compiler/Programs.h" +#include "mlir/Dialect/CBit/IR/CBitDialect.h" +#include "mlir/Dialect/MQT/IR/MQTDialect.h" +#include "mlir/Dialect/QCO/Utils/DDFunctionality.h" #include "mlir/Dialect/QIR/Execution/JIT/Session.h" #include "mlir/Dialect/QIR/Execution/Runtime/Runtime.h" -#endif #include "mqt_ddsim_qdmi/device.h" -#include "qasm3/Importer.hpp" #include "qdmi/common/Common.hpp" -#ifdef BUILD_MQT_CORE_MLIR #include -#include -#endif +#include +#include +#include #include #include @@ -46,14 +44,17 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include -#include #include +#include #include #include #include @@ -150,14 +151,61 @@ constexpr auto OPERATION_ADDRESSES = makeOperationAddresses(OPERATIONS); constexpr std::array SUPPORTED_PROGRAM_FORMATS = { QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QASM3, -#ifdef BUILD_MQT_CORE_MLIR QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, -#endif }; +[[nodiscard]] auto parseQASMToQCO(const std::string_view source) + -> std::optional { + auto qcProgram = mlir::QCProgram::fromQASMString(source); + if (!qcProgram) { + return std::nullopt; + } + auto qcoProgram = std::move(*qcProgram).intoQCO(); + if (!qcoProgram) { + return std::nullopt; + } + return qcoProgram; +} + +[[nodiscard]] auto +reverseRegisterSegments(mlir::func::FuncOp entryPoint, + std::map counts) + -> std::map { + std::vector widths; + for (const auto type : entryPoint.getFunctionType().getResults()) { + if (const auto reg = mlir::dyn_cast(type)) { + widths.push_back(static_cast(reg.getWidth())); + } + } + if (widths.size() < 2) { + return counts; + } + + std::map reordered; + for (const auto& [outcome, count] : counts) { + std::string key; + key.reserve(outcome.size()); + auto offset = outcome.size(); + for (const auto width : widths | std::views::reverse) { + offset -= width; + key.append(outcome, offset, width); + } + reordered.emplace(std::move(key), count); + } + return reordered; +} + +[[nodiscard]] auto makeJobRng(const std::optional seed) + -> std::mt19937_64 { + using Seed = std::mt19937_64::result_type; + return std::mt19937_64(seed.has_value() + ? static_cast(*seed) + : static_cast(std::random_device{}())); +} + } // namespace namespace qdmi::dd { @@ -447,13 +495,12 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::queryProperty( return QDMI_ERROR_NOTSUPPORTED; } auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitProgramAsync( - std::function body) -> QDMI_STATUS { + std::function body) -> QDMI_STATUS { jobHandle_ = std::async(std::launch::async, [this, body = std::move(body)]() { qdmi::dd::Device::get().increaseRunningJobs(); status_.store(QDMI_JOB_STATUS_RUNNING); try { - body(); - status_.store(QDMI_JOB_STATUS_DONE); + status_.store(body() ? QDMI_JOB_STATUS_DONE : QDMI_JOB_STATUS_FAILED); } catch (const std::exception& e) { status_.store(QDMI_JOB_STATUS_FAILED); std::cerr << "Error: " << e.what() << '\n'; @@ -470,23 +517,49 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramSampling() -> QDMI_STATUS { return submitProgramAsync([this]() { const auto& text = std::get(program_); - const auto qc = qasm3::Importer::imports(text); - counts_ = dd::sample(qc, numShots_, - seed_.has_value() ? static_cast(*seed_) : 0U); + auto qcoProgram = parseQASMToQCO(text); + if (!qcoProgram) { + return false; + } + const auto entryPoint = mlir::mqt::getEntryPoint(qcoProgram->module()); + if (!entryPoint) { + std::cerr << "Error: QCO program has no entry point\n"; + return false; + } + dd::Package package; + auto rng = makeJobRng(seed_); + auto counts = mlir::qco::sample(entryPoint, package, numShots_, rng); + if (mlir::failed(counts)) { + std::cerr << "Error: failed to sample the QCO program\n"; + return false; + } + counts_ = reverseRegisterSegments(entryPoint, std::move(*counts)); + return true; }); } auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramStateExtraction() -> QDMI_STATUS { return submitProgramAsync([this]() { const auto& text = std::get(program_); - auto qc = qasm3::Importer::imports(text); - qc.removeFinalMeasurements(); - const auto nQubits = qc.getNqubits(); - dd_ = std::make_unique(nQubits); - stateVecDD_ = dd::simulate(qc, dd::makeZeroState(nQubits, *dd_), *dd_); + auto qcoProgram = parseQASMToQCO(text); + if (!qcoProgram) { + return false; + } + const auto entryPoint = mlir::mqt::getEntryPoint(qcoProgram->module()); + if (!entryPoint) { + std::cerr << "Error: QCO program has no entry point\n"; + return false; + } + dd_ = std::make_unique(); + auto state = mlir::qco::simulateStatevector(entryPoint, *dd_); + if (mlir::failed(state)) { + std::cerr << "Error: failed to simulate the QCO program\n"; + return false; + } + stateVecDD_ = *state; + return true; }); } -#ifdef BUILD_MQT_CORE_MLIR auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgram() -> QDMI_STATUS { return numShots_ > 0 ? submitQIRProgramSampling() : submitQIRProgramStateExtraction(); @@ -514,12 +587,13 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling() const auto rc = jitSession.run(); runtime.outputShotEnd(rc); if (rc != 0) { - throw std::runtime_error( - llvm::formatv("QIR program failed with error: {}", rc)); + std::cerr << "Error: QIR program failed with error: " << rc << '\n'; + return false; } // Update the measurement counts. ++counts_[runtime.getMeasurements()]; } + return true; }); } auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramStateExtraction() @@ -544,15 +618,15 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramStateExtraction() std::ostringstream output; runtime.setOstream(output); if (const auto rc = jitSession.run(); rc != 0) { - throw std::runtime_error( - llvm::formatv("QIR program failed with error: {}", rc)); + std::cerr << "Error: QIR program failed with error: " << rc << '\n'; + return false; } auto state = runtime.takeState(); dd_ = std::move(state.dd); stateVecDD_ = state.edge; + return true; }); } -#endif auto MQT_DDSIM_QDMI_Device_Job_impl_d::submit() -> QDMI_STATUS { if (status_.load() != QDMI_JOB_STATUS_CREATED) { return QDMI_ERROR_BADSTATE; @@ -562,12 +636,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submit() -> QDMI_STATUS { format_ == QDMI_PROGRAM_FORMAT_QASM3) { return submitQASMProgram(); } -#ifdef BUILD_MQT_CORE_MLIR return submitQIRProgram(); -#else - // Format is validated against the allowed set at setParameter time. - qdmi::unreachable(); -#endif } auto MQT_DDSIM_QDMI_Device_Job_impl_d::cancel() -> QDMI_STATUS { const auto s = status_.load(); @@ -687,7 +756,9 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getSparseResults( size_t* sizeRet) -> QDMI_STATUS { std::call_once(stateVecSparseOnce_, [this]() { stateVecSparse_ = stateVecDD_.getSparseVector(); }); - const size_t numQubits = stateVecDD_.p->v + 1; + const size_t numQubits = stateVecDD_.isTerminal() + ? 0U + : static_cast(stateVecDD_.p->v) + 1U; switch (result) { case QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS: case QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS: { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 83c61af847..95c7005a8b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,7 +9,6 @@ # add various subdirectories containing tests add_subdirectory(bench) add_subdirectory(dd) -add_subdirectory(ir) add_subdirectory(qdmi) # copy test circuits to build directory diff --git a/test/dd/CMakeLists.txt b/test/dd/CMakeLists.txt index a776581586..3b93c5d7ef 100644 --- a/test/dd/CMakeLists.txt +++ b/test/dd/CMakeLists.txt @@ -9,5 +9,5 @@ if(TARGET MQT::CoreDD) file(GLOB_RECURSE DD_TEST_SOURCES *.cpp) package_add_test(mqt-core-dd-test MQT::CoreDD ${DD_TEST_SOURCES}) - target_link_libraries(mqt-core-dd-test PRIVATE MQT::CoreQASM nlohmann_json::nlohmann_json) + target_link_libraries(mqt-core-dd-test PRIVATE nlohmann_json::nlohmann_json) endif() diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index 918509183e..362aa64890 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -8,506 +8,79 @@ * Licensed under the MIT License */ -#include "dd/FunctionalityConstruction.hpp" +#include "dd/DDDefinitions.hpp" +#include "dd/GateMatrixDefinitions.hpp" #include "dd/Node.hpp" #include "dd/Operations.hpp" #include "dd/Package.hpp" -#include "dd/Simulation.hpp" +#include "dd/RealNumber.hpp" #include "dd/StateGeneration.hpp" -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "qasm3/Importer.hpp" #include -#include -#include +#include #include -#include -#include +#include #include -#include #include -using namespace qc; -using namespace dd; +namespace dd { -namespace { +TEST(DDGateConstruction, DispatchesByArity) { + Package package(4); -class DDFunctionality : public testing::TestWithParam { -protected: - void TearDown() override {} - - void SetUp() override { - std::array - randomData{}; - std::random_device rd; - std::ranges::generate(randomData, [&]() { return rd(); }); - std::seed_seq seeds(begin(randomData), end(randomData)); - mt.seed(seeds); - dist = std::uniform_real_distribution(0.0, 2. * qc::PI); - } - - std::mt19937_64 mt; - std::uniform_real_distribution dist; -}; - -} // namespace - -INSTANTIATE_TEST_SUITE_P( - Parameters, DDFunctionality, - testing::Values(GPhase, I, H, X, Y, Z, S, Sdg, T, Tdg, SX, SXdg, V, Vdg, U, - U2, P, R, RX, RY, RZ, Peres, Peresdg, SWAP, iSWAP, iSWAPdg, - DCX, ECR, RXX, RYY, RZZ, RZX, RCCX, XXminusYY, XXplusYY), - [](const testing::TestParamInfo& inf) { - const auto gate = inf.param; - return toString(gate); - }); - -TEST_P(DDFunctionality, StandardOpBuildInverseBuild) { - using namespace literals; - - constexpr std::size_t nq = 4; - - const auto dd = std::make_unique(nq); - - StandardOperation op; - auto gate = static_cast(GetParam()); - switch (gate) { - case GPhase: - op = StandardOperation(Controls{}, Targets{}, gate, std::vector{dist(mt)}); - break; - case U: - op = StandardOperation(0, gate, std::vector{dist(mt), dist(mt), dist(mt)}); - break; - case U2: - case R: - op = StandardOperation(0, gate, std::vector{dist(mt), dist(mt)}); - break; - case RX: - case RY: - case RZ: - case P: - op = StandardOperation(0, gate, std::vector{dist(mt)}); - break; - case SWAP: - case iSWAP: - case iSWAPdg: - case DCX: - case ECR: - case Peres: - case Peresdg: - op = StandardOperation({}, 0, 1, gate); - break; - case RXX: - case RYY: - case RZZ: - case RZX: - op = StandardOperation(Controls{}, 0, 1, gate, std::vector{dist(mt)}); - break; - case XXminusYY: - case XXplusYY: - op = StandardOperation(Controls{}, 0, 1, gate, - std::vector{dist(mt), dist(mt)}); - break; - case RCCX: - op = StandardOperation(Targets{0, 1, 2}, gate); - break; - default: - op = StandardOperation(0, gate); - } - - MatrixDD mDD; - ASSERT_NO_THROW( - { mDD = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); - EXPECT_TRUE(mDD.isIdentity()); -} - -TEST_P(DDFunctionality, ControlledStandardOpBuildInverseBuild) { - using namespace literals; - - constexpr std::size_t nq = 4; - - const auto dd = std::make_unique(nq); - - StandardOperation op; - auto gate = static_cast(GetParam()); - switch (gate) { - case GPhase: - op = StandardOperation(Controls{0}, Targets{}, gate, std::vector{dist(mt)}); - break; - case U: - op = StandardOperation(0, 1, gate, - std::vector{dist(mt), dist(mt), dist(mt)}); - break; - case U2: - case R: - op = StandardOperation(0, 1, gate, std::vector{dist(mt), dist(mt)}); - break; - case RX: - case RY: - case RZ: - case P: - op = StandardOperation(0, 1, gate, std::vector{dist(mt)}); - break; - case SWAP: - case iSWAP: - case iSWAPdg: - case DCX: - case ECR: - case Peres: - case Peresdg: - op = StandardOperation(Controls{0}, 1, 2, gate); - break; - case RXX: - case RYY: - case RZZ: - case RZX: - op = StandardOperation(Controls{0}, 1, 2, gate, std::vector{dist(mt)}); - break; - case XXminusYY: - case XXplusYY: - op = StandardOperation(Controls{0}, 1, 2, gate, - std::vector{dist(mt), dist(mt)}); - break; - case RCCX: - op = StandardOperation(Controls{0}, Targets{1, 2, 3}, gate); - break; - default: - op = StandardOperation(0, 1, gate); - } - - MatrixDD mDD; - ASSERT_NO_THROW( - { mDD = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); - EXPECT_TRUE(mDD.isIdentity()); -} - -TEST_P(DDFunctionality, ControlledStandardNegOpBuildInverseBuild) { - using namespace literals; - - constexpr std::size_t nq = 4; - - const auto dd = std::make_unique(nq); - - StandardOperation op; - auto gate = static_cast(GetParam()); - switch (gate) { - case GPhase: - op = StandardOperation(Controls{0_nc}, Targets{}, gate, - std::vector{dist(mt)}); - break; - case U: - op = StandardOperation(Controls{0_nc}, 1, gate, - std::vector{dist(mt), dist(mt), dist(mt)}); - break; - case U2: - case R: - op = StandardOperation(Controls{0_nc}, 1, gate, - std::vector{dist(mt), dist(mt)}); - break; - case RX: - case RY: - case RZ: - case P: - op = StandardOperation(Controls{0_nc}, 1, gate, std::vector{dist(mt)}); - break; - case SWAP: - case iSWAP: - case iSWAPdg: - case DCX: - case ECR: - case Peres: - case Peresdg: - op = StandardOperation(Controls{0_nc}, 1, 2, gate); - break; - case RXX: - case RYY: - case RZZ: - case RZX: - op = StandardOperation(Controls{0_nc}, 1, 2, gate, std::vector{dist(mt)}); - break; - case XXminusYY: - case XXplusYY: - op = StandardOperation(Controls{0_nc}, 1, 2, gate, - std::vector{dist(mt), dist(mt)}); - break; - case RCCX: - op = StandardOperation(Controls{0_nc}, Targets{1, 2, 3}, gate); - break; - default: - op = StandardOperation(Controls{0_nc}, 1, gate); - } - - MatrixDD mDD; - ASSERT_NO_THROW( - { mDD = dd->multiply(getDD(op, *dd), getInverseDD(op, *dd)); }); - EXPECT_TRUE(mDD.isIdentity()); -} - -TEST_F(DDFunctionality, BuildCircuit) { - constexpr std::size_t nq = 4; - - const auto dd = std::make_unique(nq); - - QuantumComputation qc(nq); - qc.x(0); - qc.swap(0, 1); - qc.cswap(2, 0, 1); - qc.mcswap({2, 3}, 0, 1); - qc.iswap(0, 1); - qc.ciswap(2, 0, 1); - qc.mciswap({2, 3}, 0, 1); - qc.h(0); - qc.s(3); - qc.sdg(2); - qc.v(0); - qc.t(1); - qc.cx(0, 1); - qc.cx(3, 2); - qc.mcx({2, 3}, 0); - qc.dcx(0, 1); - qc.cdcx(2, 0, 1); - qc.ecr(0, 1); - qc.cecr(2, 0, 1); - const auto theta = dist(mt); - qc.rxx(theta, 0, 1); - qc.crxx(theta, 2, 0, 1); - qc.ryy(theta, 0, 1); - qc.cryy(theta, 2, 0, 1); - qc.rzz(theta, 0, 1); - qc.crzz(theta, 2, 0, 1); - qc.rzx(theta, 0, 1); - qc.crzx(theta, 2, 0, 1); - const auto beta = dist(mt); - qc.xx_minus_yy(theta, beta, 0, 1); - qc.cxx_minus_yy(theta, beta, 2, 0, 1); - qc.xx_plus_yy(theta, beta, 0, 1); - qc.cxx_plus_yy(theta, beta, 2, 0, 1); - qc.rccx(0, 1, 2); - qc.crccx(3, 0, 1, 2); - qc.r(theta, beta, 0); - qc.cr(theta, beta, 2, 0); - qc.mcr(theta, beta, {2, 3}, 0); - - // invert the circuit above - qc.mcr(-theta, beta, {2, 3}, 0); - qc.cr(-theta, beta, 2, 0); - qc.r(-theta, beta, 0); - qc.crccx(3, 0, 1, 2); - qc.rccx(0, 1, 2); - qc.cxx_plus_yy(-theta, beta, 2, 0, 1); - qc.xx_plus_yy(-theta, beta, 0, 1); - qc.cxx_minus_yy(-theta, beta, 2, 0, 1); - qc.xx_minus_yy(-theta, beta, 0, 1); - qc.crzx(-theta, 2, 0, 1); - qc.rzx(-theta, 0, 1); - qc.crzz(-theta, 2, 0, 1); - qc.rzz(-theta, 0, 1); - qc.cryy(-theta, 2, 0, 1); - qc.ryy(-theta, 0, 1); - qc.crxx(-theta, 2, 0, 1); - qc.rxx(-theta, 0, 1); - qc.cecr(2, 0, 1); - qc.ecr(0, 1); - qc.cdcx(2, 1, 0); - qc.dcx(1, 0); - qc.mcx({2, 3}, 0); - qc.cx(3, 2); - qc.cx(0, 1); - qc.tdg(1); - qc.vdg(0); - qc.s(2); - qc.sdg(3); - qc.h(0); - qc.mciswapdg({2, 3}, 0, 1); - qc.ciswapdg(2, 0, 1); - qc.iswapdg(0, 1); - qc.mcswap({2, 3}, 0, 1); - qc.cswap(2, 0, 1); - qc.swap(0, 1); - qc.x(0); - - const MatrixDD dd1 = buildFunctionality(qc, *dd); - - qc.x(0); - const MatrixDD dd2 = buildFunctionality(qc, *dd); - - EXPECT_TRUE(dd1.isIdentity()); - EXPECT_FALSE(dd2.isIdentity()); - - dd->decRef(dd1); - dd->decRef(dd2); - dd->garbageCollect(true); - - const auto [vector, matrix, reals] = dd->computeActiveCounts(); - EXPECT_EQ(vector, 0); - EXPECT_EQ(matrix, 0); - EXPECT_EQ(reals, 0); -} - -TEST_F(DDFunctionality, NonUnitary) { - constexpr std::size_t nq = 4; - - const auto dd = std::make_unique(nq); - - const QuantumComputation qc{}; - auto dummyMap = Permutation{}; - auto op = NonUnitaryOperation({0, 1, 2, 3}, {0, 1, 2, 3}); - EXPECT_FALSE(op.isUnitary()); - EXPECT_THROW(getDD(op, *dd), std::invalid_argument); - EXPECT_THROW(getInverseDD(op, *dd), std::invalid_argument); - EXPECT_THROW(getDD(op, *dd, dummyMap), std::invalid_argument); - EXPECT_THROW(getInverseDD(op, *dd, dummyMap), std::invalid_argument); - for (qc::Qubit i = 0; i < nq; ++i) { - EXPECT_TRUE(op.actsOn(i)); - } - - for (qc::Qubit i = 0; i < nq; ++i) { - dummyMap[i] = i; - } - auto barrier = StandardOperation({0, 1, 2, 3}, OpType::Barrier); - EXPECT_TRUE(getDD(barrier, *dd).isIdentity()); - EXPECT_TRUE(getInverseDD(barrier, *dd).isIdentity()); - EXPECT_TRUE(getDD(barrier, *dd, dummyMap).isIdentity()); - EXPECT_TRUE(getInverseDD(barrier, *dd, dummyMap).isIdentity()); -} - -TEST_F(DDFunctionality, CircuitEquivalence) { - constexpr std::size_t nq = 1; - - const auto dd = std::make_unique(nq); - - // verify that the IBM decomposition of the H gate into RZ-SX-RZ works as - // expected (i.e., realizes H up to a global phase) - QuantumComputation qc1(nq); - qc1.h(0); - - QuantumComputation qc2(nq); - qc2.rz(qc::PI_2, 0); - qc2.sx(0); - qc2.rz(qc::PI_2, 0); - - const MatrixDD dd1 = buildFunctionality(qc1, *dd); - const MatrixDD dd2 = buildFunctionality(qc2, *dd); - - EXPECT_EQ(dd1.p, dd2.p); - - dd->decRef(dd1); - dd->decRef(dd2); - dd->garbageCollect(true); - - const auto [vector, matrix, reals] = dd->computeActiveCounts(); - EXPECT_EQ(vector, 0); - EXPECT_EQ(matrix, 0); - EXPECT_EQ(reals, 0); + EXPECT_EQ(getGateDD(package, GateType::X, {}, {}, {0}), + package.makeGateDD(opToSingleQubitGateMatrix(GateType::X), 0)); + EXPECT_EQ(getGateDD(package, GateType::SWAP, {}, {{3}}, {0, 1}), + package.makeTwoQubitGateDD(opToTwoQubitGateMatrix(GateType::SWAP), + Controls{3}, 0, 1)); + EXPECT_EQ(getGateDD(package, GateType::RCCX, {}, {}, {0, 1, 2}), + package.makeThreeQubitGateDD( + opToThreeQubitGateMatrix(GateType::RCCX), 0, 1, 2)); } -TEST_F(DDFunctionality, ChangePermutation) { - const std::string testfile = "// o 1 0\n" - "OPENQASM 2.0;" - "include \"qelib1.inc\";" - "qreg q[2];" - "x q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - const auto dd = std::make_unique(qc.getNqubits()); - - const auto sim = simulate(qc, makeZeroState(qc.getNqubits(), *dd), *dd); - EXPECT_TRUE(sim.p->e[0].isZeroTerminal()); - EXPECT_TRUE(sim.p->e[1].w.exactlyOne()); - EXPECT_TRUE(sim.p->e[1].p->e[1].isZeroTerminal()); - EXPECT_TRUE(sim.p->e[1].p->e[0].w.exactlyOne()); - const auto func = buildFunctionality(qc, *dd); - EXPECT_FALSE(func.p->e[0].isZeroTerminal()); - EXPECT_FALSE(func.p->e[1].isZeroTerminal()); - EXPECT_FALSE(func.p->e[2].isZeroTerminal()); - EXPECT_FALSE(func.p->e[3].isZeroTerminal()); - EXPECT_TRUE(func.p->e[0].p->e[1].w.exactlyOne()); - EXPECT_TRUE(func.p->e[1].p->e[3].w.exactlyOne()); - EXPECT_TRUE(func.p->e[2].p->e[0].w.exactlyOne()); - EXPECT_TRUE(func.p->e[3].p->e[2].w.exactlyOne()); +TEST(DDGateConstruction, RejectsInvalidTargetsAndTypes) { + Package package(4); + + EXPECT_THROW(getGateDD(package, GateType::X, {}, {}, {}), + std::invalid_argument); + EXPECT_THROW(getGateDD(package, GateType::SWAP, {}, {}, {0}), + std::invalid_argument); + EXPECT_THROW(getGateDD(package, GateType::RCCX, {}, {}, {0, 1}), + std::invalid_argument); + EXPECT_THROW(getGateDD(package, GateType::None, {}, {}, {}), + std::invalid_argument); } -TEST_F(DDFunctionality, IfElseOperationConditions) { - const auto cmpKinds = {ComparisonKind::Eq, ComparisonKind::Neq}; - for (const auto kind : cmpKinds) { - QuantumComputation qc(1U, 1U); - // ensure that the state is |1>. - qc.x(0); - // measure the qubit to get a classical `1` result to condition on. - qc.measure(0, 0); - // apply a classic-controlled X gate whenever the measured result compares - // as specified by kind with the previously measured result. - qc.if_(X, 0, 0, true, kind); - // measure into the same register to check the result. - qc.measure(0, 0); +TEST(DDGateConstruction, AppliesGlobalPhase) { + Package package(1); + auto state = makeZeroState(1, package); - constexpr auto shots = 16U; - const auto hist = sample(qc, shots); + const auto phased = applyGlobalPhase(state, std::numbers::pi / 2., package); + const auto vector = phased.getVector(); - EXPECT_EQ(hist.size(), 1); - const auto& [key, value] = *hist.begin(); - EXPECT_EQ(value, shots); - if (kind == ComparisonKind::Eq) { - EXPECT_EQ(key, "0"); - } else { - EXPECT_EQ(key, "1"); - } - } + ASSERT_EQ(vector.size(), 2); + EXPECT_NEAR(vector[0].real(), 0., RealNumber::eps); + EXPECT_NEAR(vector[0].imag(), 1., RealNumber::eps); + EXPECT_EQ(vector[1], std::complex{}); } -TEST_F(DDFunctionality, IfElseOperationElseBranch) { - QuantumComputation qc(1U, 1U); - qc.x(0); - qc.measure(0, 0); - qc.ifElse(std::make_unique(0, I), - std::make_unique(0, X), 0, false); - qc.measure(0, 0); - - constexpr auto shots = 16U; - const auto hist = sample(qc, shots); - - EXPECT_EQ(hist.size(), 1); - const auto& [key, value] = *hist.begin(); - EXPECT_EQ(value, shots); - EXPECT_EQ(key, "0"); -} - -TEST_F(DDFunctionality, VectorKroneckerWithTerminal) { +TEST(DDGateConstruction, VectorKroneckerWithTerminal) { constexpr std::size_t nq = 1; constexpr auto root = vEdge::one(); + Package package(nq); - const auto dd = std::make_unique(nq); - - const auto zeroState = makeZeroState(nq, *dd); - const auto extendedRoot = dd->kronecker(zeroState, root, 0); + const auto zeroState = makeZeroState(nq, package); + const auto extendedRoot = package.kronecker(zeroState, root, 0); EXPECT_EQ(zeroState, extendedRoot); - dd->decRef(zeroState); - dd->garbageCollect(true); + package.decRef(zeroState); + package.garbageCollect(true); - const auto [vector, matrix, reals] = dd->computeActiveCounts(); + const auto [vector, matrix, reals] = package.computeActiveCounts(); EXPECT_EQ(vector, 0); EXPECT_EQ(matrix, 0); EXPECT_EQ(reals, 0); } -TEST_F(DDFunctionality, DynamicCircuitSimulationWithSWAP) { - QuantumComputation qc(2, 2); - qc.x(0); - qc.swap(0, 1); - qc.measure(1, 0); - qc.if_(X, 0, 0); - qc.measure(0, 1); - - constexpr auto shots = 16U; - const auto hist = sample(qc, shots); - EXPECT_EQ(hist.size(), 1); - const auto& [key, value] = *hist.begin(); - EXPECT_EQ(value, shots); - EXPECT_EQ(key, "11"); -} +} // namespace dd diff --git a/test/dd/test_package.cpp b/test/dd/test_package.cpp index 338a95e7b2..590ba54d31 100644 --- a/test/dd/test_package.cpp +++ b/test/dd/test_package.cpp @@ -18,10 +18,6 @@ #include "dd/RealNumber.hpp" #include "dd/StateGeneration.hpp" #include "dd/statistics/PackageStatistics.hpp" -#include "ir/Definitions.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" #include #include @@ -46,15 +42,54 @@ #include #include -using namespace qc::literals; - namespace dd { +namespace { +struct TestGate { + Controls controls; + Targets targets; + GateType type = GateType::None; + std::vector params; + + TestGate(const Qubit target, const GateType gate, + std::vector parameters = {}) + : targets{target}, type(gate), params(std::move(parameters)) {} + TestGate(Targets gateTargets, const GateType gate, + std::vector parameters = {}) + : targets(std::move(gateTargets)), type(gate), + params(std::move(parameters)) {} + TestGate(const Control control, const Qubit target, const GateType gate, + std::vector parameters = {}) + : controls{control}, targets{target}, type(gate), + params(std::move(parameters)) {} + TestGate(Controls gateControls, const Qubit target, const GateType gate, + std::vector parameters = {}) + : controls(std::move(gateControls)), targets{target}, type(gate), + params(std::move(parameters)) {} + TestGate(const Control control, Targets gateTargets, const GateType gate, + std::vector parameters = {}) + : controls{control}, targets(std::move(gateTargets)), type(gate), + params(std::move(parameters)) {} + TestGate(Controls gateControls, Targets gateTargets, const GateType gate, + std::vector parameters = {}) + : controls(std::move(gateControls)), targets(std::move(gateTargets)), + type(gate), params(std::move(parameters)) {} + TestGate(Controls gateControls, const Qubit target0, const Qubit target1, + const GateType gate, std::vector parameters = {}) + : controls(std::move(gateControls)), targets{target0, target1}, + type(gate), params(std::move(parameters)) {} +}; + +MatrixDD getDD(const TestGate& gate, Package& package) { + return getGateDD(package, gate.type, gate.params, gate.controls, + gate.targets); +} +} // namespace TEST(DDPackageTest, TrivialTest) { auto dd = std::make_unique(2); EXPECT_EQ(dd->qubits(), 2); - auto xGate = getDD(qc::StandardOperation(0, qc::X), *dd); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); + auto xGate = getDD(TestGate(0, GateType::X), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); ASSERT_EQ(hGate.getValueByPath(1, "0"), SQRT2_2); @@ -72,8 +107,8 @@ TEST(DDPackageTest, TrivialTest) { TEST(DDPackageTest, BellState) { auto dd = std::make_unique(2); - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); @@ -141,14 +176,13 @@ TEST(DDPackageTest, QFTState) { auto dd = std::make_unique(3); // Simulate a QFT on 3 qubits - auto h0Gate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto s0Gate = getDD(qc::StandardOperation(1_pc, 0, qc::S), *dd); - auto t0Gate = getDD(qc::StandardOperation(2_pc, 0, qc::T), *dd); - auto h1Gate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto s1Gate = getDD(qc::StandardOperation(2_pc, 1, qc::S), *dd); - auto h2Gate = getDD(qc::StandardOperation(2, qc::H), *dd); - auto swapGate = - getDD(qc::StandardOperation(qc::Targets{0, 2}, qc::SWAP), *dd); + auto h0Gate = getDD(TestGate(0, GateType::H), *dd); + auto s0Gate = getDD(TestGate(Control{1}, 0, GateType::S), *dd); + auto t0Gate = getDD(TestGate(Control{2}, 0, GateType::T), *dd); + auto h1Gate = getDD(TestGate(1, GateType::H), *dd); + auto s1Gate = getDD(TestGate(Control{2}, 1, GateType::S), *dd); + auto h2Gate = getDD(TestGate(2, GateType::H), *dd); + auto swapGate = getDD(TestGate(Targets{0, 2}, GateType::SWAP), *dd); auto qftOp = dd->multiply(s0Gate, h0Gate); qftOp = dd->multiply(t0Gate, qftOp); @@ -266,8 +300,8 @@ TEST(DDPackageTest, QFTState) { TEST(DDPackageTest, CorruptedBellState) { auto dd = std::make_unique(2); - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); @@ -286,39 +320,35 @@ TEST(DDPackageTest, CorruptedBellState) { TEST(DDPackageTest, InvalidStandardOperation) { auto dd = std::make_unique(); - const std::vector> invalidOps{ - {{qc::Targets{}, qc::I}, - {qc::Targets{0, 1}, qc::I}, - {qc::Targets{}, qc::SWAP}, - {qc::Targets{0}, qc::SWAP}, - {qc::Targets{0, 1, 2}, qc::SWAP}, - {qc::Targets{}, qc::RCCX}, - {qc::Targets{0}, qc::RCCX}, - {qc::Targets{0, 1}, qc::RCCX}, - {qc::Targets{0, 1, 2, 3}, qc::RCCX}, - {qc::Targets{0, 1}, qc::OpTypeEnd}}}; + const std::vector> invalidOps{ + {{Targets{}, GateType::I}, + {Targets{0, 1}, GateType::I}, + {Targets{}, GateType::SWAP}, + {Targets{0}, GateType::SWAP}, + {Targets{0, 1, 2}, GateType::SWAP}, + {Targets{}, GateType::RCCX}, + {Targets{0}, GateType::RCCX}, + {Targets{0, 1}, GateType::RCCX}, + {Targets{0, 1, 2, 3}, GateType::RCCX}, + {Targets{0, 1}, GateType::None}}}; for (const auto& [targets, type] : invalidOps) { - ASSERT_THROW(getDD(qc::StandardOperation(targets, type), *dd), - std::invalid_argument); + ASSERT_THROW(getDD(TestGate(targets, type), *dd), std::invalid_argument); } - ASSERT_THROW(opToSingleQubitGateMatrix(qc::SWAP), std::invalid_argument); - ASSERT_THROW(opToSingleQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); - ASSERT_THROW(opToTwoQubitGateMatrix(qc::I), std::invalid_argument); - ASSERT_THROW(opToTwoQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); - ASSERT_THROW(opToThreeQubitGateMatrix(qc::I), std::invalid_argument); - ASSERT_THROW(opToThreeQubitGateMatrix(qc::OpTypeEnd), std::invalid_argument); -} - -TEST(DDPackageTest, PrintNoneGateType) { - std::ostringstream oss; - oss << qc::None; - EXPECT_EQ(oss.str(), "none"); + ASSERT_THROW(opToSingleQubitGateMatrix(GateType::SWAP), + std::invalid_argument); + ASSERT_THROW(opToSingleQubitGateMatrix(GateType::None), + std::invalid_argument); + ASSERT_THROW(opToTwoQubitGateMatrix(GateType::I), std::invalid_argument); + ASSERT_THROW(opToTwoQubitGateMatrix(GateType::None), std::invalid_argument); + ASSERT_THROW(opToThreeQubitGateMatrix(GateType::I), std::invalid_argument); + ASSERT_THROW(opToThreeQubitGateMatrix(GateType::None), std::invalid_argument); } TEST(DDPackageTest, NegativeControl) { auto dd = std::make_unique(2); - auto xGate = getDD(qc::StandardOperation(1_nc, 0, qc::X), *dd); + auto xGate = + getDD(TestGate(Control{1, Control::Type::Neg}, 0, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto state01 = dd->multiply(xGate, zeroState); EXPECT_EQ(state01.getValueByIndex(0b01).real(), 1.); @@ -333,7 +363,7 @@ TEST(DDPackageTest, IdentityTrace) { TEST(DDPackageTest, CNotKronTrace) { auto dd = std::make_unique(4); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto cxGateKron = dd->kronecker(cxGate, cxGate, 2); auto fullTrace = dd->trace(cxGateKron, 4); ASSERT_EQ(fullTrace, 0.25); @@ -348,8 +378,7 @@ TEST(DDPackageTest, PartialIdentityTrace) { TEST(DDPackageTest, PartialSWapMatTrace) { auto dd = std::make_unique(2); - auto swapGate = - getDD(qc::StandardOperation(qc::Targets{0, 1}, qc::SWAP), *dd); + auto swapGate = getDD(TestGate(Targets{0, 1}, GateType::SWAP), *dd); auto ptr = dd->partialTrace(swapGate, {true, false}); auto fullTrace = dd->trace(ptr, 1); auto fullTraceOriginal = dd->trace(swapGate, 2); @@ -367,8 +396,7 @@ TEST(DDPackageTest, PartialTraceKeepInnerQubits) { constexpr std::size_t numQubits = 8; auto dd = std::make_unique(numQubits); - const auto swapGate = - getDD(qc::StandardOperation(qc::Targets{0, 1}, qc::SWAP), *dd); + const auto swapGate = getDD(TestGate(Targets{0, 1}, GateType::SWAP), *dd); auto swapKron = swapGate; for (std::size_t i = 0; i < 3; ++i) { swapKron = dd->kronecker(swapKron, swapGate, 2); @@ -390,7 +418,7 @@ TEST(DDPackageTest, TraceComplexity) { for (std::size_t numQubits = 1; numQubits <= 10; ++numQubits) { auto dd = std::make_unique(numQubits); auto& computeTable = dd->getTraceComputeTable(); - const auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); + const auto hGate = getDD(TestGate(0, GateType::H), *dd); auto hKron = hGate; for (std::size_t i = 0; i < numQubits - 1; ++i) { hKron = dd->kronecker(hKron, hGate, 1); @@ -409,7 +437,7 @@ TEST(DDPackageTest, KeepBottomQubitsPartialTraceComplexity) { constexpr std::size_t numQubits = 8; auto dd = std::make_unique(numQubits); auto& uniqueTable = dd->getUniqueTable(); - const auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); + const auto hGate = getDD(TestGate(0, GateType::H), *dd); auto hKron = hGate; for (std::size_t i = 0; i < numQubits - 1; ++i) { hKron = dd->kronecker(hKron, hGate, 1); @@ -438,7 +466,7 @@ TEST(DDPackageTest, PartialTraceComplexity) { constexpr std::size_t numQubits = 9; auto dd = std::make_unique(numQubits); auto& uniqueTable = dd->getUniqueTable(); - const auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); + const auto hGate = getDD(TestGate(0, GateType::H), *dd); auto hKron = hGate; for (std::size_t i = 0; i < numQubits - 2; ++i) { hKron = dd->kronecker(hKron, hGate, 1); @@ -488,8 +516,8 @@ TEST(DDPackageTest, VectorSerializationTest) { EXPECT_EQ(dd->deserialize(serialized, binary), vEdge::one()); } - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); @@ -508,8 +536,8 @@ TEST(DDPackageTest, VectorSerializationTest) { TEST(DDPackageTest, BellMatrix) { auto dd = std::make_unique(2); - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto bellMatrix = dd->multiply(cxGate, hGate); @@ -596,8 +624,8 @@ TEST(DDPackageTest, MatrixSerializationTest) { EXPECT_EQ(dd->deserialize(serialized, binary), mEdge::one()); } - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto bellMatrix = dd->multiply(cxGate, hGate); @@ -616,8 +644,8 @@ TEST(DDPackageTest, MatrixSerializationTest) { TEST(DDPackageTest, SerializationErrors) { auto dd = std::make_unique(2); - auto hGate = getDD(qc::StandardOperation(1, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + auto hGate = getDD(TestGate(1, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{1}, 0, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); @@ -666,8 +694,8 @@ TEST(DDPackageTest, SerializationErrors) { TEST(DDPackageTest, Ancillaries) { auto dd = std::make_unique(4); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(0_pc, 1, qc::X), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{0}, 1, GateType::X), *dd); auto bellMatrix = dd->multiply(cxGate, hGate); dd->incRef(bellMatrix); @@ -702,8 +730,8 @@ TEST(DDPackageTest, Ancillaries) { TEST(DDPackageTest, GarbageVector) { auto dd = std::make_unique(4); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(0_pc, 1, qc::X), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{0}, 1, GateType::X), *dd); auto zeroState = makeZeroState(2, *dd); auto bellState = dd->multiply(dd->multiply(cxGate, hGate), zeroState); std::cout << "Bell State:\n"; @@ -736,8 +764,8 @@ TEST(DDPackageTest, GarbageVector) { TEST(DDPackageTest, GarbageMatrix) { auto dd = std::make_unique(4); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto cxGate = getDD(qc::StandardOperation(0_pc, 1, qc::X), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); + auto cxGate = getDD(TestGate(Control{0}, 1, GateType::X), *dd); auto bellMatrix = dd->multiply(cxGate, hGate); dd->incRef(bellMatrix); @@ -773,8 +801,8 @@ TEST(DDPackageTest, GarbageMatrix) { TEST(DDPackageTest, ReduceGarbageVector) { auto dd = std::make_unique(3); - auto xGate = getDD(qc::StandardOperation(2, qc::X), *dd); - auto hGate = getDD(qc::StandardOperation(2, qc::H), *dd); + auto xGate = getDD(TestGate(2, GateType::X), *dd); + auto hGate = getDD(TestGate(2, GateType::H), *dd); auto zeroState = makeZeroState(3, *dd); auto initialState = dd->multiply(dd->multiply(hGate, xGate), zeroState); std::cout << "Initial State:\n"; @@ -796,9 +824,9 @@ TEST(DDPackageTest, ReduceGarbageVector) { TEST(DDPackageTest, ReduceGarbageVectorTGate) { constexpr auto nqubits = 2U; const auto dd = std::make_unique(nqubits); - const auto xGate0 = getDD(qc::StandardOperation(0, qc::X), *dd); - const auto xGate1 = getDD(qc::StandardOperation(1, qc::X), *dd); - const auto tdgGate0 = getDD(qc::StandardOperation(0, qc::Tdg), *dd); + const auto xGate0 = getDD(TestGate(0, GateType::X), *dd); + const auto xGate1 = getDD(TestGate(1, GateType::X), *dd); + const auto tdgGate0 = getDD(TestGate(0, GateType::Tdg), *dd); auto zeroState = makeZeroState(nqubits, *dd); auto initialState = dd->multiply( @@ -816,8 +844,8 @@ TEST(DDPackageTest, ReduceGarbageVectorTGate) { TEST(DDPackageTest, ReduceGarbageMatrix) { auto dd = std::make_unique(3); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto cNotGate = getDD(qc::StandardOperation(qc::Controls{0}, 1, qc::X), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); + auto cNotGate = getDD(TestGate(Controls{0}, 1, GateType::X), *dd); auto initialState = dd->multiply(hGate, cNotGate); @@ -858,13 +886,13 @@ TEST(DDPackageTest, ReduceGarbageMatrix) { TEST(DDPackageTest, ReduceGarbageMatrix2) { constexpr auto nqubits = 3U; const auto dd = std::make_unique(nqubits); - const auto controlledSwapGate = getDD( - qc::StandardOperation(qc::Controls{1}, qc::Targets{0, 2}, qc::SWAP), *dd); - const auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - const auto zGate = getDD(qc::StandardOperation(2, qc::Z), *dd); - const auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); + const auto controlledSwapGate = + getDD(TestGate(Controls{1}, Targets{0, 2}, GateType::SWAP), *dd); + const auto hGate = getDD(TestGate(0, GateType::H), *dd); + const auto zGate = getDD(TestGate(2, GateType::Z), *dd); + const auto xGate = getDD(TestGate(1, GateType::X), *dd); const auto controlledHGate = - getDD(qc::StandardOperation(qc::Controls{1}, 0, qc::H), *dd); + getDD(TestGate(Controls{1}, 0, GateType::H), *dd); auto c1 = dd->multiply( controlledSwapGate, @@ -891,8 +919,8 @@ TEST(DDPackageTest, ReduceGarbageMatrix2) { TEST(DDPackageTest, ReduceGarbageMatrixNoGarbage) { constexpr auto nqubits = 2U; const auto dd = std::make_unique(nqubits); - const auto tdgGate0 = getDD(qc::StandardOperation(0, qc::Tdg), *dd); - const auto tdgGate1 = getDD(qc::StandardOperation(1, qc::Tdg), *dd); + const auto tdgGate0 = getDD(TestGate(0, GateType::Tdg), *dd); + const auto tdgGate1 = getDD(TestGate(1, GateType::Tdg), *dd); auto c1 = Package::makeIdent(); auto c2 = dd->multiply(tdgGate0, tdgGate1); @@ -910,8 +938,8 @@ TEST(DDPackageTest, ReduceGarbageMatrixNoGarbage) { TEST(DDPackageTest, ReduceGarbageMatrixTGate) { constexpr auto nqubits = 2U; const auto dd = std::make_unique(nqubits); - const auto tdgGate0 = getDD(qc::StandardOperation(0, qc::Tdg), *dd); - const auto tdgGate1 = getDD(qc::StandardOperation(1, qc::Tdg), *dd); + const auto tdgGate0 = getDD(TestGate(0, GateType::Tdg), *dd); + const auto tdgGate1 = getDD(TestGate(1, GateType::Tdg), *dd); auto c1 = Package::makeIdent(); auto c2 = dd->multiply(tdgGate0, tdgGate1); @@ -936,31 +964,33 @@ TEST(DDPackageTest, ReduceGarbageMatrixTGate) { TEST(DDPackageTest, InvalidMakeBasisStateAndGate) { auto nqubits = 2U; auto dd = std::make_unique(nqubits); - EXPECT_THROW(getDD(qc::StandardOperation(3, qc::X), *dd), std::runtime_error); + EXPECT_THROW(getDD(TestGate(3, GateType::X), *dd), std::runtime_error); } TEST(DDPackageTest, RejectsGateConstructionInEmptyPackage) { auto dd = std::make_unique(0U); - EXPECT_THROW(dd->makeGateDD(opToSingleQubitGateMatrix(qc::X), 0U), - std::runtime_error); - EXPECT_THROW(dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(qc::SWAP), 0U, 0U), + EXPECT_THROW(dd->makeGateDD(opToSingleQubitGateMatrix(GateType::X), 0U), std::runtime_error); EXPECT_THROW( - dd->makeThreeQubitGateDD(opToThreeQubitGateMatrix(qc::RCCX), 0U, 0U, 0U), + dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(GateType::SWAP), 0U, 0U), std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD( + opToThreeQubitGateMatrix(GateType::RCCX), 0U, 0U, 0U), + std::runtime_error); } TEST(DDPackageTest, RejectsOverlappingGateQubits) { auto dd = std::make_unique(5U); - const auto rccxMatrix = opToThreeQubitGateMatrix(qc::RCCX); + const auto rccxMatrix = opToThreeQubitGateMatrix(GateType::RCCX); // Duplicate two-qubit targets - EXPECT_THROW(dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(qc::SWAP), 1U, 1U), - std::runtime_error); + EXPECT_THROW( + dd->makeTwoQubitGateDD(opToTwoQubitGateMatrix(GateType::SWAP), 1U, 1U), + std::runtime_error); // Control coincides with single-qubit target EXPECT_THROW( - dd->makeGateDD(opToSingleQubitGateMatrix(qc::X), qc::Controls{{1}}, 1U), + dd->makeGateDD(opToSingleQubitGateMatrix(GateType::X), Controls{{1}}, 1U), std::runtime_error); // Duplicate three-qubit targets (cases skipped by RCCXGateDDConstruction) @@ -974,10 +1004,9 @@ TEST(DDPackageTest, RejectsOverlappingGateQubits) { std::runtime_error); // Extra control coincides with an RCCX target - EXPECT_THROW( - dd->makeThreeQubitGateDD(rccxMatrix, qc::Controls{{1}}, 0U, 1U, 2U), - std::runtime_error); - EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, qc::Control{0}, 0U, 1U, 2U), + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, Controls{{1}}, 0U, 1U, 2U), + std::runtime_error); + EXPECT_THROW(dd->makeThreeQubitGateDD(rccxMatrix, Control{0}, 0U, 1U, 2U), std::runtime_error); } @@ -985,7 +1014,7 @@ TEST(DDPackageTest, PackageReset) { auto dd = std::make_unique(1); // one node in unique table of variable 0 - auto xGate = getDD(qc::StandardOperation(0, qc::X), *dd); + auto xGate = getDD(TestGate(0, GateType::X), *dd); const auto& unique = dd->mUniqueTable.getTables(); const auto& table = unique[0]; @@ -997,7 +1026,7 @@ TEST(DDPackageTest, PackageReset) { (*dd).reset(); // after clearing the tables, they should be empty EXPECT_EQ(table[ihash], nullptr); - xGate = getDD(qc::StandardOperation(0, qc::X), *dd); + xGate = getDD(TestGate(0, GateType::X), *dd); const auto* node2 = table[ihash]; // after recreating the DD, it should receive the same node EXPECT_EQ(node2, node); @@ -1010,7 +1039,7 @@ TEST(DDPackageTest, ResetClearsRoots) { const auto& mRoots = dd->getRootSet(); const auto vec = dd::makeZeroState(2, *dd); - const auto mat = getDD(qc::StandardOperation(0, qc::X), *dd); + const auto mat = getDD(TestGate(0, GateType::X), *dd); dd->incRef(vec); dd->incRef(mat); @@ -1042,7 +1071,7 @@ TEST(DDPackageTest, DuplicatetrackDoesNotLeaveStaleRoot) { EXPECT_THROW(dd->decRef(vec), std::invalid_argument); // matrix root - auto mat = getDD(qc::StandardOperation(0, qc::X), *dd); + auto mat = getDD(TestGate(0, GateType::X), *dd); dd->incRef(mat); dd->incRef(mat); EXPECT_EQ(mRoots.size(), 1U); @@ -1056,7 +1085,7 @@ TEST(DDPackageTest, DuplicatetrackDoesNotLeaveStaleRoot) { TEST(DDPackageTest, Inverse) { auto dd = std::make_unique(1); - auto x = getDD(qc::StandardOperation(0, qc::X), *dd); + auto x = getDD(TestGate(0, GateType::X), *dd); auto xdag = dd->conjugateTranspose(x); EXPECT_EQ(x, xdag); dd->garbageCollect(); @@ -1078,7 +1107,7 @@ TEST(DDPackageTest, trackTwiceThenuntrackTwice) { auto& mRoots = dd->getRootSet(); - auto x = getDD(qc::StandardOperation(0, qc::X), *dd); + auto x = getDD(TestGate(0, GateType::X), *dd); // add the same edge twice dd->incRef(x); @@ -1161,7 +1190,7 @@ TEST(DDPackageTest, SpecialCaseTerminal) { TEST(DDPackageTest, KroneckerProduct) { auto dd = std::make_unique(2); - auto x = getDD(qc::StandardOperation(0, qc::X), *dd); + auto x = getDD(TestGate(0, GateType::X), *dd); auto kronecker = dd->kronecker(x, x, 1); EXPECT_EQ(kronecker.p->v, 1); EXPECT_TRUE(kronecker.p->e[0].isZeroTerminal()); @@ -1189,7 +1218,7 @@ TEST(DDPackageTest, KroneckerProductVectors) { TEST(DDPackageTest, KroneckerIdentityHandling) { auto dd = std::make_unique(3U); // create a Hadamard gate on the middle qubit - auto h = getDD(qc::StandardOperation(1U, qc::H), *dd); + auto h = getDD(TestGate(1U, GateType::H), *dd); // create a single qubit identity auto id = Package::makeIdent(); // kronecker both DDs @@ -1267,8 +1296,8 @@ TEST(DDPackageTest, NearZeroNormalize) { TEST(DDPackageTest, DestructiveMeasurementAll) { auto dd = std::make_unique(4); - auto hGate0 = getDD(qc::StandardOperation(0, qc::H), *dd); - auto hGate1 = getDD(qc::StandardOperation(1, qc::H), *dd); + auto hGate0 = getDD(TestGate(0, GateType::H), *dd); + auto hGate1 = getDD(TestGate(1, GateType::H), *dd); auto plusMatrix = dd->multiply(hGate0, hGate1); auto zeroState = makeZeroState(2, *dd); auto plusState = dd->multiply(plusMatrix, zeroState); @@ -1292,8 +1321,8 @@ TEST(DDPackageTest, DestructiveMeasurementAll) { TEST(DDPackageTest, DestructiveMeasurementOne) { auto dd = std::make_unique(4); - auto hGate0 = getDD(qc::StandardOperation(0, qc::H), *dd); - auto hGate1 = getDD(qc::StandardOperation(1, qc::H), *dd); + auto hGate0 = getDD(TestGate(0, GateType::H), *dd); + auto hGate1 = getDD(TestGate(1, GateType::H), *dd); auto plusMatrix = dd->multiply(hGate0, hGate1); auto zeroState = makeZeroState(2, *dd); auto plusState = dd->multiply(plusMatrix, zeroState); @@ -1441,9 +1470,9 @@ TEST(DDPackageTest, BasicNumericStabilityTest) { auto tol = RealNumber::eps; ComplexNumbers::setTolerance(limits::epsilon()); auto state = makeZeroState(1, *dd); - auto h = getDD(qc::StandardOperation(0, qc::H), *dd); + auto h = getDD(TestGate(0, GateType::H), *dd); auto state1 = dd->multiply(h, state); - auto z = getDD(qc::StandardOperation(0, qc::Z), *dd); + auto z = getDD(TestGate(0, GateType::Z), *dd); auto result = dd->multiply(z, state1); const auto topWeight = result.w.toString(false, limits::max_digits10); @@ -1470,8 +1499,8 @@ TEST(DDPackageTest, NormalizationNumericStabilityTest) { std::cout << std::setprecision(17) << "x: " << x << " | lambda: " << lambda << " | cos(lambda): " << std::cos(lambda) << " | sin(lambda): " << std::sin(lambda) << "\n"; - auto p = getDD(qc::StandardOperation(0, qc::P, {lambda}), *dd); - auto pdag = getDD(qc::StandardOperation(0, qc::P, {-lambda}), *dd); + auto p = getDD(TestGate(0, GateType::P, {lambda}), *dd); + auto pdag = getDD(TestGate(0, GateType::P, {-lambda}), *dd); auto result = dd->multiply(p, pdag); EXPECT_TRUE(result.isIdentity()); dd->cUniqueTable.clear(); @@ -1482,9 +1511,9 @@ TEST(DDPackageTest, NormalizationNumericStabilityTest) { TEST(DDPackageTest, FidelityOfMeasurementOutcomes) { const auto dd = std::make_unique(3); - const auto hGate = getDD(qc::StandardOperation(2, qc::H), *dd); - const auto cxGate1 = getDD(qc::StandardOperation(2_pc, 1, qc::X), *dd); - const auto cxGate2 = getDD(qc::StandardOperation(1_pc, 0, qc::X), *dd); + const auto hGate = getDD(TestGate(2, GateType::H), *dd); + const auto cxGate1 = getDD(TestGate(Control{2}, 1, GateType::X), *dd); + const auto cxGate2 = getDD(TestGate(Control{1}, 0, GateType::X), *dd); const auto zeroState = makeZeroState(3, *dd); const auto ghzState = dd->multiply( @@ -1534,13 +1563,12 @@ TEST(DDPackageTest, CloseToIdentityWithGarbageAtTheBeginning) { constexpr fp tol = 1.0E-10; constexpr auto nqubits = 3U; auto dd = std::make_unique(nqubits); - auto controlledSwapGate = getDD( - qc::StandardOperation(qc::Controls{1}, qc::Targets{0, 2}, qc::SWAP), *dd); - auto hGate = getDD(qc::StandardOperation(0, qc::H), *dd); - auto zGate = getDD(qc::StandardOperation(2, qc::Z), *dd); - auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); - auto controlledHGate = - getDD(qc::StandardOperation(qc::Controls{1}, 0, qc::H), *dd); + auto controlledSwapGate = + getDD(TestGate(Controls{1}, Targets{0, 2}, GateType::SWAP), *dd); + auto hGate = getDD(TestGate(0, GateType::H), *dd); + auto zGate = getDD(TestGate(2, GateType::Z), *dd); + auto xGate = getDD(TestGate(1, GateType::X), *dd); + auto controlledHGate = getDD(TestGate(Controls{1}, 0, GateType::H), *dd); auto c1 = dd->multiply( controlledSwapGate, @@ -1560,14 +1588,14 @@ TEST(DDPackageTest, CloseToIdentityWithGarbageAtTheEnd) { constexpr auto nqubits = 3U; const auto dd = std::make_unique(nqubits); - const auto controlledSwapGate = getDD( - qc::StandardOperation(qc::Controls{1}, qc::Targets{0, 2}, qc::SWAP), *dd); - const auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); + const auto controlledSwapGate = + getDD(TestGate(Controls{1}, Targets{0, 2}, GateType::SWAP), *dd); + const auto xGate = getDD(TestGate(1, GateType::X), *dd); - const auto hGate2 = getDD(qc::StandardOperation(2, qc::H), *dd); - const auto zGate2 = getDD(qc::StandardOperation(0, qc::Z), *dd); + const auto hGate2 = getDD(TestGate(2, GateType::H), *dd); + const auto zGate2 = getDD(TestGate(0, GateType::Z), *dd); const auto controlledHGate2 = - getDD(qc::StandardOperation(qc::Controls{1}, 2, qc::H), *dd); + getDD(TestGate(Controls{1}, 2, GateType::H), *dd); const auto c3 = dd->multiply( controlledSwapGate, @@ -1589,14 +1617,14 @@ TEST(DDPackageTest, CloseToIdentityWithGarbageInTheMiddle) { constexpr auto nqubits = 3U; const auto dd = std::make_unique(nqubits); - const auto zGate = getDD(qc::StandardOperation(2, qc::Z), *dd); + const auto zGate = getDD(TestGate(2, GateType::Z), *dd); - const auto controlledSwapGate3 = getDD( - qc::StandardOperation(qc::Controls{0}, qc::Targets{1, 2}, qc::SWAP), *dd); - const auto hGate3 = getDD(qc::StandardOperation(1, qc::H), *dd); - const auto xGate3 = getDD(qc::StandardOperation(0, qc::X), *dd); + const auto controlledSwapGate3 = + getDD(TestGate(Controls{0}, Targets{1, 2}, GateType::SWAP), *dd); + const auto hGate3 = getDD(TestGate(1, GateType::H), *dd); + const auto xGate3 = getDD(TestGate(0, GateType::X), *dd); const auto controlledHGate3 = - getDD(qc::StandardOperation(qc::Controls{0}, 1, qc::H), *dd); + getDD(TestGate(Controls{0}, 1, GateType::H), *dd); const auto c5 = dd->multiply( controlledSwapGate3, @@ -1671,13 +1699,13 @@ TEST(DDPackageTest, expectationValueGlobalOperators) { const auto zeroState = makeZeroState(nrQubits, *dd); // Definition global operators - const auto singleSiteX = getDD(qc::StandardOperation(0, qc::X), *dd); + const auto singleSiteX = getDD(TestGate(0, GateType::X), *dd); auto globalX = singleSiteX; - const auto singleSiteZ = getDD(qc::StandardOperation(0, qc::Z), *dd); + const auto singleSiteZ = getDD(TestGate(0, GateType::Z), *dd); auto globalZ = singleSiteZ; - const auto singleSiteHadamard = getDD(qc::StandardOperation(0, qc::H), *dd); + const auto singleSiteHadamard = getDD(TestGate(0, GateType::H), *dd); auto globalHadamard = singleSiteHadamard; for (Qubit i = 1; i < nrQubits; ++i) { @@ -1703,9 +1731,9 @@ TEST(DDPackageTest, expectationValueLocalOperators) { // Local expectation values at each site for (Qubit site = 0; site < nrQubits - 1; ++site) { // Definition local operators - auto xGate = getDD(qc::StandardOperation(site, qc::X), *dd); - auto zGate = getDD(qc::StandardOperation(site, qc::Z), *dd); - auto hadamard = getDD(qc::StandardOperation(site, qc::H), *dd); + auto xGate = getDD(TestGate(site, GateType::X), *dd); + auto zGate = getDD(TestGate(site, GateType::Z), *dd); + auto hadamard = getDD(TestGate(site, GateType::H), *dd); EXPECT_EQ(dd->expectationValue(xGate, zeroState), 0); EXPECT_EQ(dd->expectationValue(zGate, zeroState), 1); @@ -1719,7 +1747,7 @@ TEST(DDPackageTest, expectationValueExceptions) { const auto dd = std::make_unique(nrQubits); const auto zeroState = makeZeroState(nrQubits - 1, *dd); - const auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); + const auto xGate = getDD(TestGate(1, GateType::X), *dd); EXPECT_ANY_THROW(dd->expectationValue(xGate, zeroState)); } @@ -1820,8 +1848,8 @@ TEST(DDPackageTest, TwoQubitControlledGateDDConstruction) { const auto dd = std::make_unique(nrQubits); const auto gateMatrices = - std::vector{std::pair{opToSingleQubitGateMatrix(qc::X), CX_MAT}, - std::pair{opToSingleQubitGateMatrix(qc::Z), CZ_MAT}}; + std::vector{std::pair{opToSingleQubitGateMatrix(GateType::X), CX_MAT}, + std::pair{opToSingleQubitGateMatrix(GateType::Z), CZ_MAT}}; // For every combination of control and target, test that the DD created by // makeTwoQubitGateDD is equal to the DD created by makeGateDD. This should @@ -1834,8 +1862,8 @@ TEST(DDPackageTest, TwoQubitControlledGateDDConstruction) { } const auto controlledGateDD = dd->makeTwoQubitGateDD(controlledGateMatrix, control, target); - const auto gateDD = dd->makeGateDD( - gateMatrix, qc::Control{static_cast(control)}, target); + const auto gateDD = + dd->makeGateDD(gateMatrix, Control{control}, target); EXPECT_EQ(controlledGateDD, gateDD); } } @@ -1847,8 +1875,8 @@ TEST(DDPackageTest, TwoQubitControlledGateDDConstructionNegativeControls) { const auto dd = std::make_unique(nrQubits); const auto gateMatrices = - std::vector{std::pair{opToSingleQubitGateMatrix(qc::X), CX_MAT}, - std::pair{opToSingleQubitGateMatrix(qc::Z), CZ_MAT}}; + std::vector{std::pair{opToSingleQubitGateMatrix(GateType::X), CX_MAT}, + std::pair{opToSingleQubitGateMatrix(GateType::Z), CZ_MAT}}; // For every combination of controls, control type, and target, test that the // DD created by makeTwoQubitGateDD is equal to the DD created by makeGateDD. @@ -1864,12 +1892,12 @@ TEST(DDPackageTest, TwoQubitControlledGateDDConstructionNegativeControls) { continue; } for (const auto controlType : - {qc::Control::Type::Pos, qc::Control::Type::Neg}) { + {Control::Type::Pos, Control::Type::Neg}) { const auto controlledGateDD = dd->makeTwoQubitGateDD( - controlledGateMatrix, qc::Controls{{control0, controlType}}, + controlledGateMatrix, Controls{{control0, controlType}}, control1, target); const auto gateDD = dd->makeGateDD( - gateMatrix, qc::Controls{{control0, controlType}, control1}, + gateMatrix, Controls{{control0, controlType}, control1}, target); EXPECT_EQ(controlledGateDD, gateDD); } @@ -1888,51 +1916,20 @@ TEST(DDPackageTest, SWAPGateDDConstruction) { if (control == target) { continue; } - const auto swapGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::SWAP), *dd); + const auto swapGateDD = + getDD(TestGate(Targets{control, target}, GateType::SWAP), *dd); - auto gateDD = getDD(qc::StandardOperation(control, target, qc::X), *dd); + auto gateDD = getDD(TestGate(control, target, GateType::X), *dd); gateDD = dd->multiply( - gateDD, dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), - gateDD)); + gateDD, + dd->multiply(getDD(TestGate(target, control, GateType::X), *dd), + gateDD)); EXPECT_EQ(swapGateDD, gateDD); } } } -TEST(DDPackageTest, PeresGateDDConstruction) { - constexpr auto nrQubits = 5U; - const auto dd = std::make_unique(nrQubits); - - for (Qubit control = 0; control < nrQubits; ++control) { - for (Qubit target = 0; target < nrQubits; ++target) { - if (control == target) { - continue; - } - const auto peresGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::Peres), *dd); - - auto gateDD = getDD(qc::StandardOperation(control, target, qc::X), *dd); - gateDD = dd->multiply(getDD(qc::StandardOperation(control, qc::X), *dd), - gateDD); - - EXPECT_EQ(peresGateDD, gateDD); - - const auto peresInvDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::Peresdg), - *dd); - - auto gateInvDD = getDD(qc::StandardOperation(control, qc::X), *dd); - gateInvDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), gateInvDD); - - EXPECT_EQ(peresInvDD, gateInvDD); - } - } -} - TEST(DDPackageTest, iSWAPGateDDConstruction) { constexpr auto nrQubits = 5U; const auto dd = std::make_unique(nrQubits); @@ -1942,48 +1939,22 @@ TEST(DDPackageTest, iSWAPGateDDConstruction) { if (control == target) { continue; } - const auto iswapGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::iSWAP), *dd); + const auto iswapGateDD = + getDD(TestGate(Targets{control, target}, GateType::iSWAP), *dd); - auto gateDD = getDD(qc::StandardOperation(target, qc::S), *dd); // S q[1) - gateDD = dd->multiply(getDD(qc::StandardOperation(control, qc::S), *dd), + auto gateDD = getDD(TestGate(target, GateType::S), *dd); // S q[1) + gateDD = dd->multiply(getDD(TestGate(control, GateType::S), *dd), gateDD); // S q[0) - gateDD = dd->multiply(getDD(qc::StandardOperation(control, qc::H), *dd), + gateDD = dd->multiply(getDD(TestGate(control, GateType::H), *dd), gateDD); // H q[0) - gateDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), - gateDD); // CX q[0], q[1] - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), - gateDD); // CX q[1], q[0] - gateDD = dd->multiply(getDD(qc::StandardOperation(target, qc::H), *dd), + gateDD = dd->multiply(getDD(TestGate(control, target, GateType::X), *dd), + gateDD); // CX q[0], q[1] + gateDD = dd->multiply(getDD(TestGate(target, control, GateType::X), *dd), + gateDD); // CX q[1], q[0] + gateDD = dd->multiply(getDD(TestGate(target, GateType::H), *dd), gateDD); // H q[1) EXPECT_EQ(iswapGateDD, gateDD); - - const auto iswapInvGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::iSWAPdg), - *dd); - - auto gateInvDD = - getDD(qc::StandardOperation(target, qc::H), *dd); // H q[1) - gateInvDD = dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), - gateInvDD); // CX q[1], q[0] - gateInvDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), - gateInvDD); // CX q[0], q[1] - gateInvDD = - dd->multiply(getDD(qc::StandardOperation(control, qc::H), *dd), - gateInvDD); // H q[0) - gateInvDD = - dd->multiply(getDD(qc::StandardOperation(control, qc::Sdg), *dd), - gateInvDD); // Sdag q[0] - gateInvDD = - dd->multiply(getDD(qc::StandardOperation(target, qc::Sdg), *dd), - gateInvDD); // Sdag q[1] - - EXPECT_EQ(iswapInvGateDD, gateInvDD); } } } @@ -1997,12 +1968,12 @@ TEST(DDPackageTest, DCXGateDDConstruction) { if (control == target) { continue; } - const auto dcxGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::DCX), *dd); + const auto dcxGateDD = + getDD(TestGate(Targets{control, target}, GateType::DCX), *dd); - const auto gateDD = dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), - getDD(qc::StandardOperation(control, target, qc::X), *dd)); + const auto gateDD = + dd->multiply(getDD(TestGate(target, control, GateType::X), *dd), + getDD(TestGate(control, target, GateType::X), *dd)); EXPECT_EQ(dcxGateDD, gateDD); } @@ -2021,14 +1992,14 @@ TEST(DDPackageTest, RZZGateDDConstruction) { continue; } for (const auto& param : params) { - const auto rzzGateDD = getDD( - qc::StandardOperation({control, target}, qc::RZZ, {param}), *dd); + const auto rzzGateDD = + getDD(TestGate({control, target}, GateType::RZZ, {param}), *dd); - auto gateDD = getDD(qc::StandardOperation(control, target, qc::X), *dd); + auto gateDD = getDD(TestGate(control, target, GateType::X), *dd); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {param}), *dd), gateDD); + getDD(TestGate(target, GateType::RZ, {param}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), gateDD); + getDD(TestGate(control, target, GateType::X), *dd), gateDD); EXPECT_EQ(rzzGateDD, gateDD); } @@ -2036,18 +2007,16 @@ TEST(DDPackageTest, RZZGateDDConstruction) { } const auto identity = Package::makeIdent(); - const auto rzzZero = getDD(qc::StandardOperation({0, 1}, qc::RZZ, {0.}), *dd); + const auto rzzZero = getDD(TestGate({0, 1}, GateType::RZZ, {0.}), *dd); EXPECT_EQ(rzzZero, identity); - const auto rzzTwoPi = - getDD(qc::StandardOperation({0, 1}, qc::RZZ, {2 * PI}), *dd); + const auto rzzTwoPi = getDD(TestGate({0, 1}, GateType::RZZ, {2 * PI}), *dd); EXPECT_EQ(rzzTwoPi.p, identity.p); EXPECT_EQ(RealNumber::val(rzzTwoPi.w.r), -1.); - const auto rzzPi = getDD(qc::StandardOperation({0, 1}, qc::RZZ, {PI}), *dd); - auto zz = getDD(qc::StandardOperation(qc::Controls{}, 0, qc::Z), *dd); - zz = dd->multiply( - zz, getDD(qc::StandardOperation(qc::Controls{}, 1, qc::Z), *dd)); + const auto rzzPi = getDD(TestGate({0, 1}, GateType::RZZ, {PI}), *dd); + auto zz = getDD(TestGate(Controls{}, 0, GateType::Z), *dd); + zz = dd->multiply(zz, getDD(TestGate(Controls{}, 1, GateType::Z), *dd)); EXPECT_EQ(rzzPi.p, zz.p); } @@ -2063,24 +2032,21 @@ TEST(DDPackageTest, RYYGateDDConstruction) { continue; } for (const auto& param : params) { - const auto ryyGateDD = getDD( - qc::StandardOperation({control, target}, qc::RYY, {param}), *dd); + const auto ryyGateDD = + getDD(TestGate({control, target}, GateType::RYY, {param}), *dd); // no controls are necessary on the RX gates since they cancel if the // controls are 0. - auto gateDD = - getDD(qc::StandardOperation(control, qc::RX, {PI_2}), *dd); + auto gateDD = getDD(TestGate(control, GateType::RX, {PI_2}), *dd); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RX, {PI_2}), *dd), gateDD); + getDD(TestGate(target, GateType::RX, {PI_2}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation({control, target}, qc::RZZ, {param}), - *dd), + getDD(TestGate({control, target}, GateType::RZZ, {param}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RX, {-PI_2}), *dd), gateDD); + getDD(TestGate(target, GateType::RX, {-PI_2}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RX, {-PI_2}), *dd), - gateDD); + getDD(TestGate(control, GateType::RX, {-PI_2}), *dd), gateDD); EXPECT_EQ(ryyGateDD, gateDD); } @@ -2088,13 +2054,12 @@ TEST(DDPackageTest, RYYGateDDConstruction) { } const auto identity = Package::makeIdent(); - const auto ryyZero = getDD(qc::StandardOperation({0, 1}, qc::RYY, {0.}), *dd); + const auto ryyZero = getDD(TestGate({0, 1}, GateType::RYY, {0.}), *dd); EXPECT_EQ(ryyZero, identity); - const auto ryyPi = getDD(qc::StandardOperation({0, 1}, qc::RYY, {PI}), *dd); - auto yy = getDD(qc::StandardOperation(qc::Controls{}, 0, qc::Y), *dd); - yy = dd->multiply( - yy, getDD(qc::StandardOperation(qc::Controls{}, 1, qc::Y), *dd)); + const auto ryyPi = getDD(TestGate({0, 1}, GateType::RYY, {PI}), *dd); + auto yy = getDD(TestGate(Controls{}, 0, GateType::Y), *dd); + yy = dd->multiply(yy, getDD(TestGate(Controls{}, 1, GateType::Y), *dd)); EXPECT_EQ(ryyPi.p, yy.p); } @@ -2110,20 +2075,19 @@ TEST(DDPackageTest, RXXGateDDConstruction) { continue; } for (const auto& param : params) { - const auto rxxGateDD = getDD( - qc::StandardOperation({control, target}, qc::RXX, {param}), *dd); + const auto rxxGateDD = + getDD(TestGate({control, target}, GateType::RXX, {param}), *dd); - auto gateDD = getDD(qc::StandardOperation(control, qc::H), *dd); - gateDD = dd->multiply(getDD(qc::StandardOperation(target, qc::H), *dd), - gateDD); + auto gateDD = getDD(TestGate(control, GateType::H), *dd); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::H), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation({control, target}, qc::RZZ, {param}), - *dd), + getDD(TestGate({control, target}, GateType::RZZ, {param}), *dd), gateDD); - gateDD = dd->multiply(getDD(qc::StandardOperation(target, qc::H), *dd), - gateDD); - gateDD = dd->multiply(getDD(qc::StandardOperation(control, qc::H), *dd), - gateDD); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::H), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(control, GateType::H), *dd), gateDD); EXPECT_EQ(rxxGateDD, gateDD); } @@ -2131,13 +2095,12 @@ TEST(DDPackageTest, RXXGateDDConstruction) { } const auto identity = Package::makeIdent(); - const auto rxxZero = getDD(qc::StandardOperation({0, 1}, qc::RXX, {0.}), *dd); + const auto rxxZero = getDD(TestGate({0, 1}, GateType::RXX, {0.}), *dd); EXPECT_EQ(rxxZero, identity); - const auto rxxPi = getDD(qc::StandardOperation({0, 1}, qc::RXX, {PI}), *dd); - auto xx = getDD(qc::StandardOperation(qc::Controls{}, 0, qc::X), *dd); - xx = dd->multiply( - xx, getDD(qc::StandardOperation(qc::Controls{}, 1, qc::X), *dd)); + const auto rxxPi = getDD(TestGate({0, 1}, GateType::RXX, {PI}), *dd); + auto xx = getDD(TestGate(Controls{}, 0, GateType::X), *dd); + xx = dd->multiply(xx, getDD(TestGate(Controls{}, 1, GateType::X), *dd)); EXPECT_EQ(rxxPi.p, xx.p); } @@ -2153,18 +2116,17 @@ TEST(DDPackageTest, RZXGateDDConstruction) { continue; } for (const auto& param : params) { - const auto rzxGateDD = getDD( - qc::StandardOperation({control, target}, qc::RZX, {param}), *dd); + const auto rzxGateDD = + getDD(TestGate({control, target}, GateType::RZX, {param}), *dd); // no controls are necessary on the H gates since they cancel if the // controls are 0. - auto gateDD = getDD(qc::StandardOperation(target, qc::H), *dd); + auto gateDD = getDD(TestGate(target, GateType::H), *dd); gateDD = dd->multiply( - getDD(qc::StandardOperation({control, target}, qc::RZZ, {param}), - *dd), + getDD(TestGate({control, target}, GateType::RZZ, {param}), *dd), gateDD); - gateDD = dd->multiply(getDD(qc::StandardOperation(target, qc::H), *dd), - gateDD); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::H), *dd), gateDD); EXPECT_EQ(rzxGateDD, gateDD); } @@ -2172,13 +2134,12 @@ TEST(DDPackageTest, RZXGateDDConstruction) { } const auto identity = Package::makeIdent(); - const auto rzxZero = getDD(qc::StandardOperation({0, 1}, qc::RZX, {0.}), *dd); + const auto rzxZero = getDD(TestGate({0, 1}, GateType::RZX, {0.}), *dd); EXPECT_EQ(rzxZero, identity); - const auto rzxPi = getDD(qc::StandardOperation({0, 1}, qc::RZX, {PI}), *dd); - auto zx = getDD(qc::StandardOperation(qc::Controls{}, 0, qc::Z), *dd); - zx = dd->multiply( - zx, getDD(qc::StandardOperation(qc::Controls{}, 1, qc::X), *dd)); + const auto rzxPi = getDD(TestGate({0, 1}, GateType::RZX, {PI}), *dd); + auto zx = getDD(TestGate(Controls{}, 0, GateType::Z), *dd); + zx = dd->multiply(zx, getDD(TestGate(Controls{}, 1, GateType::X), *dd)); EXPECT_EQ(rzxPi.p, zx.p); } @@ -2192,16 +2153,14 @@ TEST(DDPackageTest, ECRGateDDConstruction) { continue; } - const auto ecrGateDD = getDD( - qc::StandardOperation(qc::Targets{control, target}, qc::ECR), *dd); + const auto ecrGateDD = + getDD(TestGate(Targets{control, target}, GateType::ECR), *dd); auto gateDD = - getDD(qc::StandardOperation({control, target}, qc::RZX, {PI_4}), *dd); - gateDD = dd->multiply(getDD(qc::StandardOperation(control, qc::X), *dd), - gateDD); + getDD(TestGate({control, target}, GateType::RZX, {PI_4}), *dd); + gateDD = dd->multiply(getDD(TestGate(control, GateType::X), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation({control, target}, qc::RZX, {-PI_4}), - *dd), + getDD(TestGate({control, target}, GateType::RZX, {-PI_4}), *dd), gateDD); EXPECT_EQ(ecrGateDD, gateDD); @@ -2224,49 +2183,40 @@ TEST(DDPackageTest, XXMinusYYGateDDConstruction) { for (const auto& theta : thetaAngles) { for (const auto& beta : betaAngles) { - const auto xxMinusYYGateDD = - getDD(qc::StandardOperation({control, target}, qc::XXminusYY, - {theta, beta}), - *dd); + const auto xxMinusYYGateDD = getDD( + TestGate({control, target}, GateType::XXminusYY, {theta, beta}), + *dd); - auto gateDD = - getDD(qc::StandardOperation(target, qc::RZ, {-beta}), *dd); + auto gateDD = getDD(TestGate(target, GateType::RZ, {-beta}), *dd); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RZ, {-PI_2}), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::SX), *dd), gateDD); + getDD(TestGate(control, GateType::RZ, {-PI_2}), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(control, GateType::SX), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RZ, {PI_2}), *dd), - gateDD); + getDD(TestGate(control, GateType::RZ, {PI_2}), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::S), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::S), *dd), gateDD); + getDD(TestGate(control, target, GateType::X), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), + getDD(TestGate(control, GateType::RY, {theta / 2.}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RY, {theta / 2.}), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RY, {-theta / 2.}), *dd), + getDD(TestGate(target, GateType::RY, {-theta / 2.}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, target, qc::X), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::Sdg), *dd), gateDD); + getDD(TestGate(control, target, GateType::X), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::Sdg), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RZ, {-PI_2}), *dd), - gateDD); + getDD(TestGate(control, GateType::RZ, {-PI_2}), *dd), gateDD); + gateDD = dd->multiply(getDD(TestGate(control, GateType::SXdg), *dd), + gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::SXdg), *dd), gateDD); + getDD(TestGate(control, GateType::RZ, {PI_2}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RZ, {PI_2}), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {beta}), *dd), - gateDD); + getDD(TestGate(target, GateType::RZ, {beta}), *dd), gateDD); EXPECT_EQ(xxMinusYYGateDD, gateDD); } @@ -2290,47 +2240,38 @@ TEST(DDPackageTest, XXPlusYYGateDDConstruction) { for (const auto& theta : thetaAngles) { for (const auto& beta : betaAngles) { - const auto xxPlusYYGateDD = - getDD(qc::StandardOperation({control, target}, qc::XXplusYY, - {theta, beta}), - *dd); - auto gateDD = - getDD(qc::StandardOperation(control, qc::RZ, {beta}), *dd); + const auto xxPlusYYGateDD = getDD( + TestGate({control, target}, GateType::XXplusYY, {theta, beta}), + *dd); + auto gateDD = getDD(TestGate(control, GateType::RZ, {beta}), *dd); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {-PI_2}), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::SX), *dd), gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {PI_2}), *dd), - gateDD); + getDD(TestGate(target, GateType::RZ, {-PI_2}), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(target, GateType::SX), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::S), *dd), gateDD); + getDD(TestGate(target, GateType::RZ, {PI_2}), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(control, GateType::S), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RY, {-theta / 2.}), *dd), - gateDD); + getDD(TestGate(target, control, GateType::X), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RY, {-theta / 2.}), *dd), + getDD(TestGate(control, GateType::RY, {-theta / 2.}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, control, qc::X), *dd), + getDD(TestGate(target, GateType::RY, {-theta / 2.}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::Sdg), *dd), gateDD); + getDD(TestGate(target, control, GateType::X), *dd), gateDD); + gateDD = dd->multiply(getDD(TestGate(control, GateType::Sdg), *dd), + gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {-PI_2}), *dd), - gateDD); + getDD(TestGate(target, GateType::RZ, {-PI_2}), *dd), gateDD); + gateDD = dd->multiply(getDD(TestGate(target, GateType::SXdg), *dd), + gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::SXdg), *dd), gateDD); + getDD(TestGate(target, GateType::RZ, {PI_2}), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(target, qc::RZ, {PI_2}), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(control, qc::RZ, {-beta}), *dd), - gateDD); + getDD(TestGate(control, GateType::RZ, {-beta}), *dd), gateDD); EXPECT_EQ(xxPlusYYGateDD, gateDD); } @@ -2342,35 +2283,32 @@ TEST(DDPackageTest, XXPlusYYGateDDConstruction) { TEST(DDPackageTest, RCCXGateDDConstruction) { constexpr auto nrQubits = 5U; const auto dd = std::make_unique(nrQubits); - const auto rccxMatrix = opToThreeQubitGateMatrix(qc::RCCX); + const auto rccxMatrix = opToThreeQubitGateMatrix(GateType::RCCX); - const auto rccxDecomposition = [&](const qc::Controls& extra, + const auto rccxDecomposition = [&](const Controls& extra, const Qubit control0, const Qubit control1, const Qubit target) { const auto withCtrl = [&](const Qubit ctrl) { - qc::Controls controls = extra; + Controls controls = extra; controls.emplace(ctrl); return controls; }; - auto gateDD = getDD(qc::StandardOperation(extra, target, qc::H), *dd); - gateDD = dd->multiply( - getDD(qc::StandardOperation(extra, target, qc::T), *dd), gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(withCtrl(control1), target, qc::X), *dd), - gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(extra, target, qc::Tdg), *dd), gateDD); - gateDD = dd->multiply( - getDD(qc::StandardOperation(withCtrl(control0), target, qc::X), *dd), - gateDD); + auto gateDD = getDD(TestGate(extra, target, GateType::H), *dd); + gateDD = + dd->multiply(getDD(TestGate(extra, target, GateType::T), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(extra, target, qc::T), *dd), gateDD); + getDD(TestGate(withCtrl(control1), target, GateType::X), *dd), gateDD); + gateDD = dd->multiply(getDD(TestGate(extra, target, GateType::Tdg), *dd), + gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(withCtrl(control1), target, qc::X), *dd), - gateDD); + getDD(TestGate(withCtrl(control0), target, GateType::X), *dd), gateDD); + gateDD = + dd->multiply(getDD(TestGate(extra, target, GateType::T), *dd), gateDD); gateDD = dd->multiply( - getDD(qc::StandardOperation(extra, target, qc::Tdg), *dd), gateDD); - return dd->multiply(getDD(qc::StandardOperation(extra, target, qc::H), *dd), + getDD(TestGate(withCtrl(control1), target, GateType::X), *dd), gateDD); + gateDD = dd->multiply(getDD(TestGate(extra, target, GateType::Tdg), *dd), + gateDD); + return dd->multiply(getDD(TestGate(extra, target, GateType::H), *dd), gateDD); }; @@ -2388,26 +2326,25 @@ TEST(DDPackageTest, RCCXGateDDConstruction) { const auto bareOverload = dd->makeThreeQubitGateDD(rccxMatrix, control0, control1, target); const auto bareControls = dd->makeThreeQubitGateDD( - rccxMatrix, qc::Controls{}, control0, control1, target); + rccxMatrix, Controls{}, control0, control1, target); EXPECT_EQ(bareOverload, bareControls); EXPECT_EQ(bareOverload, rccxDecomposition({}, control0, control1, target)); // Bare RCCX and RCCX with one extra positive/negative control. - std::vector controlSets{{}}; + std::vector controlSets{{}}; for (Qubit extra = 0; extra < nrQubits; ++extra) { if (extra == control0 || extra == control1 || extra == target) { continue; } - controlSets.push_back({{extra, qc::Control::Type::Pos}}); - controlSets.push_back({{extra, qc::Control::Type::Neg}}); + controlSets.push_back({{extra, Control::Type::Pos}}); + controlSets.push_back({{extra, Control::Type::Neg}}); } for (const auto& controls : controlSets) { - const auto rccxGateDD = - getDD(qc::StandardOperation( - controls, {control0, control1, target}, qc::RCCX), - *dd); + const auto rccxGateDD = getDD( + TestGate(controls, {control0, control1, target}, GateType::RCCX), + *dd); EXPECT_EQ(rccxGateDD, rccxDecomposition(controls, control0, control1, target)); @@ -2432,8 +2369,8 @@ TEST(DDPackageTest, InnerProductTopNodeConjugation) { constexpr auto nrQubits = 2U; const auto dd = std::make_unique(nrQubits); const auto zeroState = makeZeroState(nrQubits, *dd); - const auto rxx = getDD(qc::StandardOperation({0, 1}, qc::RXX, {-2}), *dd); - const auto op = getDD(qc::StandardOperation(0, qc::Z), *dd); + const auto rxx = getDD(TestGate({0, 1}, GateType::RXX, {-2}), *dd); + const auto op = getDD(TestGate(0, GateType::Z), *dd); const auto evolvedState = dd->multiply(rxx, zeroState); @@ -2506,7 +2443,7 @@ TEST(DDPackageTest, DataStructureStatistics) { TEST(DDPackageTest, DDStatistics) { constexpr auto nqubits = 2U; const auto dd = std::make_unique(nqubits); - const auto dummyGate = getDD(qc::StandardOperation(0U, qc::X), *dd); + const auto dummyGate = getDD(TestGate(0U, GateType::X), *dd); EXPECT_NE(dummyGate.p, nullptr); const auto statsString = getStatisticsString(*dd, true); const auto stats = nlohmann::json::parse(statsString); @@ -2587,7 +2524,7 @@ TEST(DDPackageTest, ReduceAncillaIdentity) { TEST(DDPackageTest, ReduceAncillaIdentityBeforeFirstNode) { const auto dd = std::make_unique(2); - auto xGate = getDD(qc::StandardOperation(0, qc::X), *dd); + auto xGate = getDD(TestGate(0, GateType::X), *dd); dd->incRef(xGate); const auto outputDD = dd->reduceAncillae(xGate, {false, true}); @@ -2600,7 +2537,7 @@ TEST(DDPackageTest, ReduceAncillaIdentityBeforeFirstNode) { TEST(DDPackageTest, ReduceAncillaIdentityAfterLastNode) { const auto dd = std::make_unique(2); - auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); + auto xGate = getDD(TestGate(1, GateType::X), *dd); dd->incRef(xGate); const auto outputDD = dd->reduceAncillae(xGate, {true, false}); @@ -2613,8 +2550,8 @@ TEST(DDPackageTest, ReduceAncillaIdentityAfterLastNode) { TEST(DDPackageTest, ReduceAncillaIdentityBetweenTwoNodes) { const auto dd = std::make_unique(3); - const auto xGate0 = getDD(qc::StandardOperation(0, qc::X), *dd); - const auto xGate2 = getDD(qc::StandardOperation(2, qc::X), *dd); + const auto xGate0 = getDD(TestGate(0, GateType::X), *dd); + const auto xGate2 = getDD(TestGate(2, GateType::X), *dd); auto state = dd->multiply(xGate0, xGate2); dd->incRef(state); @@ -2647,7 +2584,7 @@ TEST(DDPackageTest, ReduceGarbageIdentity) { TEST(DDPackageTest, ReduceGarbageIdentityBeforeFirstNode) { const auto dd = std::make_unique(2); - auto xGate = getDD(qc::StandardOperation(0, qc::X), *dd); + auto xGate = getDD(TestGate(0, GateType::X), *dd); dd->incRef(xGate); auto outputDD = dd->reduceGarbage(xGate, {false, true}); @@ -2667,7 +2604,7 @@ TEST(DDPackageTest, ReduceGarbageIdentityBeforeFirstNode) { TEST(DDPackageTest, ReduceGarbageIdentityAfterLastNode) { const auto dd = std::make_unique(2); - auto xGate = getDD(qc::StandardOperation(1, qc::X), *dd); + auto xGate = getDD(TestGate(1, GateType::X), *dd); dd->incRef(xGate); auto outputDD = dd->reduceGarbage(xGate, {true, false}); @@ -2687,8 +2624,8 @@ TEST(DDPackageTest, ReduceGarbageIdentityAfterLastNode) { TEST(DDPackageTest, ReduceGarbageIdentityBetweenTwoNodes) { const auto dd = std::make_unique(3); - const auto xGate0 = getDD(qc::StandardOperation(0, qc::X), *dd); - const auto xGate2 = getDD(qc::StandardOperation(2, qc::X), *dd); + const auto xGate0 = getDD(TestGate(0, GateType::X), *dd); + const auto xGate2 = getDD(TestGate(2, GateType::X), *dd); auto state = dd->multiply(xGate0, xGate2); dd->incRef(state); diff --git a/test/ir/CMakeLists.txt b/test/ir/CMakeLists.txt deleted file mode 100644 index cd6c28d22e..0000000000 --- a/test/ir/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -if(TARGET MQT::CoreIR) - file(GLOB_RECURSE IR_TEST_SOURCES *.cpp) - package_add_test_with_working_dir(mqt-core-ir-test MQT::CoreIR ${CMAKE_CURRENT_BINARY_DIR} - ${IR_TEST_SOURCES}) - target_link_libraries(mqt-core-ir-test PRIVATE MQT::CoreQASM) -endif() diff --git a/test/ir/test_flatten_operations.cpp b/test/ir/test_flatten_operations.cpp deleted file mode 100644 index 9a4e69a7fd..0000000000 --- a/test/ir/test_flatten_operations.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace qc { -TEST(FlattenOperations, EmptyCircuit) { - QuantumComputation qc; - qc.flattenOperations(); - EXPECT_TRUE(qc.empty()); -} - -TEST(FlattenOperations, FlattenRecursive) { - const std::size_t nqubits = 1U; - - // create a nested compound operation - QuantumComputation op(nqubits); - op.x(0); - op.z(0); - QuantumComputation op2(nqubits); - op2.emplace_back(op.asCompoundOperation()); - QuantumComputation qc(nqubits); - qc.emplace_back(op2.asCompoundOperation()); - std::cout << qc << "\n"; - - qc.flattenOperations(); - std::cout << qc << "\n"; - - for (const auto& g : qc) { - EXPECT_FALSE(g->isCompoundOperation()); - } - - ASSERT_EQ(qc.getNops(), 2U); - auto& gate = qc.at(0); - EXPECT_EQ(gate->getType(), X); - EXPECT_EQ(gate->getTargets().at(0), 0U); - EXPECT_TRUE(gate->getControls().empty()); - auto& gate2 = qc.at(1); - EXPECT_EQ(gate2->getType(), Z); - EXPECT_EQ(gate2->getTargets().at(0), 0U); - EXPECT_TRUE(gate2->getControls().empty()); -} - -TEST(FlattenOperations, FlattenCustomOnly) { - const std::size_t nqubits = 1U; - - // create a nested compound operation - QuantumComputation op(nqubits); - op.x(0); - op.z(0); - QuantumComputation op2(nqubits); - op2.emplace_back(op.asCompoundOperation()); - QuantumComputation qc(nqubits); - qc.emplace_back(op2.asCompoundOperation()); - std::cout << qc << "\n"; - - qc.flattenOperations(true); - std::cout << qc << "\n"; - - ASSERT_EQ(qc.getNops(), 1U); - auto& gate = qc.at(0); - EXPECT_EQ(gate->getType(), Compound); - - std::vector> opsCompound; - opsCompound.push_back(std::make_unique(0, X)); - opsCompound.push_back(std::make_unique(0, Z)); - QuantumComputation qc2(nqubits); - qc2.emplace_back(std::move(opsCompound), true); - std::cout << qc2 << "\n"; - - qc2.flattenOperations(true); - std::cout << qc2 << "\n"; - - for (const auto& g : qc2) { - EXPECT_FALSE(g->isCompoundOperation()); - } - - ASSERT_EQ(qc2.getNops(), 2U); - auto& gate3 = qc2.at(0); - EXPECT_EQ(gate3->getType(), X); - EXPECT_EQ(gate3->getTargets().at(0), 0U); - EXPECT_TRUE(gate3->getControls().empty()); - auto& gate4 = qc2.at(1); - EXPECT_EQ(gate4->getType(), Z); - EXPECT_EQ(gate4->getTargets().at(0), 0U); - EXPECT_TRUE(gate4->getControls().empty()); -} -} // namespace qc diff --git a/test/ir/test_if_else_operation.cpp b/test/ir/test_if_else_operation.cpp deleted file mode 100644 index 87f6d8034d..0000000000 --- a/test/ir/test_if_else_operation.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/Register.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" - -#include - -#include -#include -#include - -TEST(IfElseOperation, GetInvertedComparisonKind) { - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Lt), - qc::ComparisonKind::Geq); - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Leq), - qc::ComparisonKind::Gt); - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Gt), - qc::ComparisonKind::Leq); - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Geq), - qc::ComparisonKind::Lt); - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Eq), - qc::ComparisonKind::Neq); - EXPECT_EQ(qc::getInvertedComparisonKind(qc::ComparisonKind::Neq), - qc::ComparisonKind::Eq); -} - -TEST(IfElseOperation, Assignment) { - const qc::IfElseOperation ifElse1( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - qc::IfElseOperation ifElse2( - std::make_unique(0, qc::OpType::Y), - std::make_unique(1, qc::OpType::Z), 0); - - ifElse2 = ifElse1; - - EXPECT_TRUE(ifElse2.equals(ifElse1)); - - // Check that operations have been cloned - EXPECT_NE(ifElse2.getThenOp(), ifElse1.getThenOp()); - EXPECT_NE(ifElse2.getElseOp(), ifElse1.getElseOp()); -} - -TEST(IfElseOperation, Apply) { - qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - qc::Permutation permutation{}; - permutation[0] = 1; - permutation[1] = 0; - - ifElse.apply(permutation); - - EXPECT_EQ(ifElse.getThenOp()->getTargets().at(0), static_cast(1)); - EXPECT_EQ(ifElse.getElseOp()->getTargets().at(0), static_cast(0)); -} - -TEST(IfElseOperation, IsUnitary) { - const qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - ASSERT_FALSE(ifElse.isUnitary()); -} - -TEST(IfElseOperation, IsNonUnitaryOperation) { - const qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - ASSERT_TRUE(ifElse.isNonUnitaryOperation()); -} - -TEST(IfElseOperation, IsIfElseOperation) { - const qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - ASSERT_TRUE(ifElse.isIfElseOperation()); -} - -TEST(IfElseOperation, IsControlled) { - const qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - ASSERT_FALSE(ifElse.isControlled()); -} - -TEST(IfElseOperation, Equals) { - const qc::IfElseOperation ifElseBit1( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0, true, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit2( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0, true, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit3( - std::make_unique(1, qc::OpType::X), - std::make_unique(0, qc::OpType::Y), 0, true, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit4( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Z), 0, true, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit5( - nullptr, std::make_unique(1, qc::OpType::Y), 0, - true, qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit6( - std::make_unique(0, qc::OpType::X), nullptr, 0, - true, qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit7( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 1, true, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit8( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0, false, - qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseBit9( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0, true, - qc::ComparisonKind::Neq); - - const qc::ClassicalRegister controlRegister1(0, 1); - const qc::ClassicalRegister controlRegister2(0, 2); - - const qc::IfElseOperation ifElseRegister1( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), - controlRegister1, 1U, qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseRegister2( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), - controlRegister1, 1U, qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseRegister3( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), - controlRegister2, 1U, qc::ComparisonKind::Eq); - const qc::IfElseOperation ifElseRegister4( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), - controlRegister1, 2U, qc::ComparisonKind::Eq); - - EXPECT_TRUE(ifElseBit1.equals(ifElseBit2)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit3)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit4)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit5)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit6)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit7)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit8)); - EXPECT_FALSE(ifElseBit1.equals(ifElseBit9)); - - EXPECT_FALSE(ifElseBit1.equals(ifElseRegister1)); - - EXPECT_TRUE(ifElseRegister1.equals(ifElseRegister2)); - EXPECT_FALSE(ifElseRegister1.equals(ifElseRegister3)); - EXPECT_FALSE(ifElseRegister1.equals(ifElseRegister4)); - - const std::hash hasher; - - EXPECT_EQ(hasher(ifElseBit1), hasher(ifElseBit2)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit3)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit4)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit5)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit6)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit7)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit8)); - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseBit9)); - - EXPECT_NE(hasher(ifElseBit1), hasher(ifElseRegister1)); - - EXPECT_EQ(hasher(ifElseRegister1), hasher(ifElseRegister2)); - EXPECT_NE(hasher(ifElseRegister1), hasher(ifElseRegister3)); - EXPECT_NE(hasher(ifElseRegister1), hasher(ifElseRegister4)); -} - -TEST(IfElseOperation, RuntimeErrors) { - qc::IfElseOperation ifElse( - std::make_unique(0, qc::OpType::X), - std::make_unique(1, qc::OpType::Y), 0); - - EXPECT_THROW(ifElse.invert(), std::runtime_error); - EXPECT_THROW(ifElse.setTargets({}), std::runtime_error); - EXPECT_THROW(ifElse.setControls({}), std::runtime_error); - EXPECT_THROW(ifElse.addControl(qc::Control(0)), std::runtime_error); - EXPECT_THROW(ifElse.clearControls(), std::runtime_error); - EXPECT_THROW(ifElse.removeControl(qc::Control(0)), std::runtime_error); - EXPECT_THROW(ifElse.setGate(qc::OpType::None), std::runtime_error); - EXPECT_THROW(ifElse.setParameter({}), std::runtime_error); -} diff --git a/test/ir/test_io.cpp b/test/ir/test_io.cpp deleted file mode 100644 index ae42c70a6f..0000000000 --- a/test/ir/test_io.cpp +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/OpenQASMSerializer.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Importer.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -using namespace qc::literals; - -class IO : public testing::Test { -protected: - void TearDown() override {} - - void SetUp() override {} - - std::size_t nqubits = 0; - std::size_t seed = 0; - std::string output3 = "tmp"; - std::string output4 = "tmp.tmp.qasm"; - std::string output5 = "./tmpdir/circuit.qasm"; - std::string output5dir = "tmpdir"; - qc::QuantumComputation qc; -}; - -class UnsupportedOperation final : public qc::Operation { -public: - [[nodiscard]] auto clone() const -> std::unique_ptr override { - return std::make_unique(*this); - } - void addControl(qc::Control /*control*/) override {} - void clearControls() override {} - void removeControl(qc::Control /*control*/) override {} - auto removeControl(qc::Controls::iterator it) - -> qc::Controls::iterator override { - return it; - } - void invert() override {} -}; - -void compareFiles(const std::string& file1, const std::string& file2) { - std::ifstream fstream1(file1); - std::string str1((std::istreambuf_iterator(fstream1)), - std::istreambuf_iterator()); - std::ifstream fstream2(file2); - std::string str2((std::istreambuf_iterator(fstream2)), - std::istreambuf_iterator()); - std::erase_if(str1, isspace); - std::erase_if(str2, isspace); - ASSERT_EQ(str1, str2); -} - -void expectRccxSourceRoundTrip(const std::string& source, - const bool openQASM3) { - const auto imported = qasm3::Importer::imports(source); - ASSERT_EQ(imported.getNops(), 1U); - ASSERT_EQ(imported.front()->getType(), qc::RCCX); - EXPECT_EQ(imported.front()->getNcontrols(), 0U); - - std::ostringstream oss; - imported.dumpOpenQASM(oss, openQASM3); - EXPECT_EQ(oss.str(), source); -} - -void expectRccxExport(const qc::QuantumComputation& circuit, - const bool openQASM3, const std::string_view snippet, - const std::size_t expectedNops, - const std::size_t expectedNcontrols, - const std::size_t opIndex = 0, - const std::string_view extraSnippet = {}, - const bool expectNegControl = false) { - const auto qasm = circuit.toQASM(openQASM3); - EXPECT_NE(qasm.find(snippet), std::string::npos); - if (!extraSnippet.empty()) { - EXPECT_NE(qasm.find(extraSnippet), std::string::npos); - } - - const auto roundTrip = qasm3::Importer::imports(qasm); - ASSERT_EQ(roundTrip.getNops(), expectedNops); - const auto& op = roundTrip.at(opIndex); - EXPECT_EQ(op->getType(), qc::RCCX); - EXPECT_EQ(op->getNcontrols(), expectedNcontrols); - if (expectNegControl) { - EXPECT_EQ(op->getControls().begin()->type, qc::Control::Type::Neg); - } -} - -} // namespace - -TEST_F(IO, importAndDumpQASM) { - // Create temporary filenames - const auto tempDir = std::filesystem::temp_directory_path(); - const auto outputPath = tempDir / "tmp_importAndDumpQASM.txt"; - const auto output2Path = tempDir / "tmp2_importAndDumpQASM.txt"; - - constexpr auto input = "../circuits/test.qasm"; - constexpr auto format = qc::Format::OpenQASM2; - std::cout << "FILE: " << input << "\n"; - - qc = qasm3::Importer::importf(input); - qc.dump(outputPath.string(), format); - qc.reset(); - qc = qasm3::Importer::importf(outputPath.string()); - qc.dump(output2Path.string(), format); - - compareFiles(outputPath.string(), output2Path.string()); - std::filesystem::remove(outputPath); - std::filesystem::remove(output2Path); -} - -TEST_F(IO, importAndDumpQASMFromConstructor) { - // Create temporary filenames - const auto tempDir = std::filesystem::temp_directory_path(); - const auto outputPath = tempDir / "tmp_importAndDumpQASMFromConstructor.txt"; - const auto output2Path = - tempDir / "tmp2_importAndDumpQASMFromConstructor.txt"; - - constexpr auto input = "../circuits/test.qasm"; - constexpr auto format = qc::Format::OpenQASM2; - std::cout << "FILE: " << input << "\n"; - - qc = qasm3::Importer::importf(input); - qc.dump(outputPath.string(), format); - qc.reset(); - qc = qasm3::Importer::importf(outputPath.string()); - qc.dump(output2Path.string(), format); - - compareFiles(outputPath.string(), output2Path.string()); - std::filesystem::remove(outputPath); - std::filesystem::remove(output2Path); -} - -TEST_F(IO, dumpValidFilenames) { - qc.dump(output3, qc::Format::OpenQASM2); - qc.dump(output4, qc::Format::OpenQASM2); - qc.dump(output4); - - std::filesystem::create_directory(output5dir); - qc.dump(output5, qc::Format::OpenQASM2); - qc.dump(output5); - - std::filesystem::remove(output3); - std::filesystem::remove(output4); - std::filesystem::remove(output5); - std::filesystem::remove(output5dir); -} - -TEST_F(IO, importFromStringQASM) { - qc = qasm3::Importer::imports("qreg q[2];" - "U(pi/2,0,pi) q[0];" - "CX q[0],q[1];"); - std::cout << qc << "\n"; -} - -TEST_F(IO, insufficientRegistersQelib) { - EXPECT_THROW(qc = qasm3::Importer::imports("qreg q[2];" - "cx q[0];"), - qasm3::CompilerError); -} - -TEST_F(IO, insufficientRegistersEnhancedQelib) { - EXPECT_THROW(qc = qasm3::Importer::imports("qreg q[4];" - "ctrl(3) @ z q[0], q[1], q[2];"), - qasm3::CompilerError); -} - -TEST_F(IO, superfluousRegistersQelib) { - EXPECT_THROW(qc = qasm3::Importer::imports("qreg q[3];" - "cx q[0], q[1], q[2];"), - qasm3::CompilerError); -} - -TEST_F(IO, superfluousRegistersEnhancedQelib) { - EXPECT_THROW( - qc = qasm3::Importer::imports("qreg q[5];" - "ctrl(3) z q[0], q[1], q[2], q[3], q[4];"), - qasm3::CompilerError); -} - -TEST_F(IO, qiskitMcxGray) { - qc = qasm3::Importer::imports("qreg q[4];" - "mcx_gray q[0], q[1], q[2], q[3];"); - std::cout << qc << "\n"; - const auto& gate = qc.front(); - EXPECT_EQ(gate->getType(), qc::X); - EXPECT_EQ(gate->getNcontrols(), 3); - EXPECT_EQ(gate->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcxSkipGateDefinition) { - - qc = qasm3::Importer::imports( - "qreg q[4];" - "gate mcx q0,q1,q2,q3 { ctrl(3) @ x q0,q1,q2,q3; }" - "mcx q[0], q[1], q[2], q[3];"); - std::cout << qc << "\n"; - const auto& gate = qc.front(); - EXPECT_EQ(gate->getType(), qc::X); - EXPECT_EQ(gate->getNcontrols(), 3); - EXPECT_EQ(gate->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcphase) { - qc = qasm3::Importer::imports("qreg q[4];" - "mcphase(pi) q[0], q[1], q[2], q[3];"); - std::cout << qc << "\n"; - const auto& gate = qc.front(); - EXPECT_EQ(gate->getType(), qc::Z); - EXPECT_EQ(gate->getNcontrols(), 3); - EXPECT_EQ(gate->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcphaseInDeclaration) { - qc = qasm3::Importer::imports( - "qreg q[4];" - "gate foo q0, q1, q2, q3 { mcphase(pi) q0, q1, q2, q3; }" - "foo q[0], q[1], q[2], q[3];"); - std::cout << qc << "\n"; - const auto& op = qc.front(); - EXPECT_EQ(op->getType(), qc::Z); - EXPECT_EQ(op->getNcontrols(), 3); - EXPECT_EQ(op->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcxRecursive) { - qc = qasm3::Importer::imports( - "qreg q[6];" - "qreg anc[1];" - "mcx_recursive q[0], q[1], q[2], q[3], q[4];" - "mcx_recursive q[0], q[1], q[2], q[3], q[4], q[5], anc[0];"); - std::cout << qc << "\n"; - const auto& gate = qc.at(0); - EXPECT_EQ(gate->getType(), qc::X); - EXPECT_EQ(gate->getNcontrols(), 4); - EXPECT_EQ(gate->getTargets().at(0), 4); - const auto& second = qc.at(1); - EXPECT_EQ(second->getType(), qc::X); - EXPECT_EQ(second->getNcontrols(), 5); - EXPECT_EQ(second->getTargets().at(0), 5); -} - -TEST_F(IO, qiskitMcxVchain) { - qc = qasm3::Importer::imports("qreg q[4];" - "qreg anc[1];" - "mcx_vchain q[0], q[1], q[2], q[3], anc[0];"); - std::cout << qc << "\n"; - const auto& gate = qc.front(); - EXPECT_EQ(gate->getType(), qc::X); - EXPECT_EQ(gate->getNcontrols(), 3); - EXPECT_EQ(gate->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcxRecursiveInDeclaration) { - qc = qasm3::Importer::imports( - "qreg q[7];" - "gate foo q0, q1, q2, q3, q4 { mcx_recursive q0, q1, q2, q3, q4; }" - "gate bar q0, q1, q2, q3, q4, q5, anc { mcx_recursive q0, q1, q2, q3, " - "q4, q5, anc; }" - "foo q[0], q[1], q[2], q[3], q[4];" - "bar q[0], q[1], q[2], q[3], q[4], q[5], q[6];"); - std::cout << qc << "\n"; - const auto& op = qc.at(0); - EXPECT_EQ(op->getType(), qc::X); - EXPECT_EQ(op->getNcontrols(), 4); - EXPECT_EQ(op->getTargets().at(0), 4); - const auto& second = qc.at(1); - EXPECT_EQ(second->getType(), qc::X); - EXPECT_EQ(second->getNcontrols(), 5); - EXPECT_EQ(second->getTargets().at(0), 5); -} - -TEST_F(IO, qiskitMcxVchainInDeclaration) { - qc = qasm3::Importer::imports( - "qreg q[5];" - "gate foo q0, q1, q2, q3, anc { mcx_vchain q0, q1, q2, q3, anc; }" - "foo q[0], q[1], q[2], q[3], q[4];"); - std::cout << qc << "\n"; - const auto& op = qc.front(); - EXPECT_EQ(op->getType(), qc::X); - EXPECT_EQ(op->getNcontrols(), 3); - EXPECT_EQ(op->getTargets().at(0), 3); -} - -TEST_F(IO, qiskitMcxDuplicateQubit) { - EXPECT_THROW(qc = qasm3::Importer::imports( - "qreg q[4];" - "qreg anc[1];" - "mcx_vchain q[0], q[0], q[2], q[3], anc[0];"), - qasm3::CompilerError); -} - -TEST_F(IO, qiskitMcxQubitRegister) { - EXPECT_THROW( - qc = qasm3::Importer::imports("qreg q[4];" - "qreg anc[1];" - "mcx_vchain q, q[0], q[2], q[3], anc[0];"), - qasm3::CompilerError); -} - -TEST_F(IO, barrierInDeclaration) { - qc = qasm3::Importer::imports("qreg q[1];" - "gate foo q0 { h q0; barrier q0; h q0; }" - "foo q[0];"); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNops(), 1); - const auto& op = qc.at(0); - EXPECT_EQ(op->getType(), qc::Compound); - const auto* comp = dynamic_cast(op.get()); - ASSERT_NE(comp, nullptr); - EXPECT_EQ(comp->size(), 3); - EXPECT_EQ(comp->at(0)->getType(), qc::H); - EXPECT_EQ(comp->at(1)->getType(), qc::Barrier); - EXPECT_EQ(comp->at(2)->getType(), qc::H); -} - -TEST_F(IO, CommentInDeclaration) { - qc = qasm3::Importer::imports("qreg q[1];gate foo q0 {" - "h q0;" - "//x q0;\n" - "h q0;" - "}" - "foo q[0];"); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNops(), 1); - const auto& op = qc.at(0); - EXPECT_EQ(op->getType(), qc::Compound); - const auto* comp = dynamic_cast(op.get()); - ASSERT_NE(comp, nullptr); - EXPECT_EQ(comp->size(), 2); - EXPECT_EQ(comp->at(0)->getType(), qc::H); - EXPECT_EQ(comp->at(1)->getType(), qc::H); -} - -TEST_F(IO, iSWAPDumpIsValid) { - qc.addQubitRegister(2); - qc.iswap(0, 1); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(); - std::cout << qasm << "\n"; - EXPECT_NO_THROW(qc = qasm3::Importer::imports(qasm)); - std::cout << qc << "\n"; -} - -TEST_F(IO, iSWAPdagDumpIsValid) { - qc.addQubitRegister(2); - qc.iswapdg(0, 1); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(); - std::cout << qasm << "\n"; - EXPECT_NO_THROW(qc = qasm3::Importer::imports(qasm)); - std::cout << qc << "\n"; -} - -TEST_F(IO, PeresDumpIsValid) { - qc.addQubitRegister(2); - qc.peres(0, 1); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(); - std::cout << qasm << "\n"; - EXPECT_NO_THROW(qc = qasm3::Importer::imports(qasm)); - std::cout << qc << "\n"; -} - -TEST_F(IO, PeresdagDumpIsValid) { - qc.addQubitRegister(2); - qc.peresdg(0, 1); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(); - std::cout << qasm << "\n"; - EXPECT_NO_THROW(qc = qasm3::Importer::imports(qasm)); - std::cout << qc << "\n"; -} - -TEST_F(IO, printingNonUnitary) { - qc = qasm3::Importer::imports("qreg q[2];" - "creg c[2];" - "h q[0];" - "reset q[0];" - "h q[0];" - "barrier q;" - "measure q -> c;"); - std::cout << qc << "\n"; - for (const auto& op : qc) { - op->print(std::cout, qc.getNqubits()); - std::cout << "\n"; - } -} - -TEST_F(IO, sxAndSxdag) { - qc = qasm3::Importer::imports("qreg q[1];" - "creg c[1];" - "gate test q0 { sx q0; sxdg q0;}" - "sx q[0];" - "sxdg q[0];" - "test q[0];"); - std::cout << qc << "\n"; - const auto& op1 = qc.at(0); - EXPECT_EQ(op1->getType(), qc::OpType::SX); - const auto& op2 = qc.at(1); - EXPECT_EQ(op2->getType(), qc::OpType::SXdg); - const auto& op3 = qc.at(2); - ASSERT_TRUE(op3->isCompoundOperation()); - auto* compOp = dynamic_cast(op3.get()); - ASSERT_NE(compOp, nullptr); - const auto& compOp1 = compOp->at(0); - EXPECT_EQ(compOp1->getType(), qc::OpType::SX); - const auto& compOp2 = compOp->at(1); - EXPECT_EQ(compOp2->getType(), qc::OpType::SXdg); -} - -TEST_F(IO, unifyRegisters) { - qc = qasm3::Importer::imports("qreg q[1];" - "qreg r[1];" - "x q[0];" - "x r[0];"); - std::cout << qc << "\n"; - qc.unifyQuantumRegisters(); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(false); - EXPECT_EQ(qasm, "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[2];\n" - "x q[0];\n" - "x q[1];\n"); -} - -TEST_F(IO, NativeTwoQubitGateImportAndExport) { - const auto gates = std::vector{"dcx", - "ecr", - "rxx(0.5)", - "ryy(0.5)", - "rzz(0.5)", - "rzx(0.5)", - "xx_minus_yy(0.5,0.5)", - "xx_plus_yy(0.5,0.5)"}; - - const std::string header = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[2];\n"; - for (const auto& gate : gates) { - std::stringstream ss{}; - ss << header << gate << " q[0], q[1];\n"; - const auto target = ss.str(); - qc = qasm3::Importer::imports(target); - std::cout << qc << "\n"; - std::ostringstream oss{}; - qc.dumpOpenQASM(oss, false); - std::cout << oss.str() << "\n"; - EXPECT_STREQ(oss.str().c_str(), target.c_str()); - qc.reset(); - std::cout << "---\n"; - } -} - -TEST_F(IO, RccxUncontrolledOpenQASMRoundTrip) { - expectRccxSourceRoundTrip("// i 0 1 2\n" - "// o 0 1 2\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[3];\n" - "rccx q[0], q[1], q[2];\n", - false); - expectRccxSourceRoundTrip("// i 0 1 2\n" - "// o 0 1 2\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "rccx q[0], q[1], q[2];\n", - true); -} - -TEST_F(IO, RccxControlledOpenQASMRoundTrip) { - qc.addQubitRegister(4); - qc.crccx(0, 1, 2, 3); - expectRccxExport(qc, false, "crccx q[0], q[1], q[2], q[3];", 1, 1); - expectRccxExport(qc, true, "ctrl @ rccx q[0], q[1], q[2], q[3];", 1, 1); - - qc.reset(); - qc.addQubitRegister(4); - qc.crccx(0_nc, 1, 2, 3); - // OpenQASM 2 encodes negative controls via X conjugation around crccx. - expectRccxExport(qc, false, "crccx q[0], q[1], q[2], q[3];", 3, 1, 1, - "x q[0];\n"); - expectRccxExport(qc, true, "negctrl @ rccx q[0], q[1], q[2], q[3];", 1, 1, 0, - {}, true); - - qc.reset(); - qc.addQubitRegister(5); - qc.mcrccx({0, 1}, 2, 3, 4); - expectRccxExport(qc, false, "ccrccx q[0], q[1], q[2], q[3], q[4];", 1, 2); - expectRccxExport(qc, true, "ctrl(2) @ rccx q[0], q[1], q[2], q[3], q[4];", 1, - 2); -} - -TEST_F(IO, Rc3xFromQelib1IsCompoundNotControlledRccx) { - qc = qasm3::Importer::imports("OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[4];\n" - "rc3x q[0], q[1], q[2], q[3];\n"); - ASSERT_EQ(qc.getNops(), 1U); - EXPECT_EQ(qc.front()->getType(), qc::Compound); - EXPECT_EQ(qc.front()->getNcontrols(), 0U); - const auto* compound = - dynamic_cast(qc.front().get()); - ASSERT_NE(compound, nullptr); - EXPECT_GT(compound->size(), 1U); -} - -TEST_F(IO, ParameterizedGateDefinition) { - qc = qasm3::Importer::imports( - "qreg q[1];" - "gate foo(theta, beta) q { rz(theta) q; rx(beta) q; }" - "foo(2*cos(pi/4), 0.5*sin(pi/2)) q[0];"); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNqubits(), 1U); - EXPECT_EQ(qc.getNops(), 1U); - EXPECT_EQ(qc.at(0)->getType(), qc::Compound); - const auto& op = dynamic_cast(qc.at(0).get()); - ASSERT_NE(op, nullptr); - EXPECT_EQ(op->size(), 2U); - EXPECT_EQ(op->at(0)->getType(), qc::RZ); - EXPECT_EQ(op->at(1)->getType(), qc::RX); - const auto& rz = dynamic_cast(op->at(0).get()); - ASSERT_NE(rz, nullptr); - const auto& rx = dynamic_cast(op->at(1).get()); - ASSERT_NE(rx, nullptr); - EXPECT_EQ(rz->getParameter().at(0), 2 * std::cos(qc::PI_4)); - EXPECT_EQ(rx->getParameter().at(0), 0.5 * std::sin(qc::PI_2)); -} - -TEST_F(IO, NonExistingInclude) { - EXPECT_THROW(qc = qasm3::Importer::imports("include \"nonexisting.inc\";"), - qasm3::CompilerError); -} - -TEST_F(IO, NonStandardInclude) { - std::ofstream ofs{"defs.inc"}; - ofs << "gate foo q { h q; }\n"; - ofs.close(); - qc = qasm3::Importer::imports("include \"defs.inc\";" - "qreg q[1];" - "foo q[0];"); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNqubits(), 1U); - EXPECT_EQ(qc.getNops(), 1U); - EXPECT_EQ(qc.front()->getType(), qc::H); - std::filesystem::remove("defs.inc"); -} - -TEST_F(IO, SingleRegistersDoubleCreg) { - const auto* const qasmIn = "qreg p[1];\n" - "qreg q[1];\n" - "creg c[2];\n" - "measure p[0] -> c[0];"; - qc = qasm3::Importer::imports(qasmIn); - std::cout << qc << "\n"; - const auto qasmOut = qc.toQASM(false); - std::cout << qasmOut << "\n"; - EXPECT_NE(qasmOut.find(qasmIn), std::string::npos); -} - -TEST_F(IO, MarkAncillaryAndDump) { - qc = qasm3::Importer::imports("qreg q[2];" - "x q[0];" - "x q[1];"); - std::cout << qc << "\n"; - qc.setLogicalQubitAncillary(0U); - EXPECT_EQ(qc.getNancillae(), 1U); - EXPECT_TRUE(qc.logicalQubitIsAncillary(0U)); - std::cout << qc << "\n"; - const auto qasm = qc.toQASM(false); - std::cout << qasm << "\n"; - const auto* const expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[2];\n" - "x q[0];\n" - "x q[1];\n"; - EXPECT_EQ(qasm, expected); -} - -TEST_F(IO, dumpEmptyOpenQASM) { - qc = qasm3::Importer::imports(""); - - std::string const openQASM2 = - "// i\n// o\nOPENQASM 2.0;\ninclude \"qelib1.inc\";\n"; - std::string const openQASM3 = - "// i\n// o\nOPENQASM 3.0;\ninclude \"stdgates.inc\";\n"; - - EXPECT_EQ(openQASM2, qc.toQASM(false)); - EXPECT_EQ(openQASM3, qc.toQASM(true)); -} - -TEST_F(IO, fromCompoundOperation) { - qc.addQubitRegister(2); - qc.addClassicalRegister(2); - qc.x(1); - qc.measure(1, 1); - const auto compound = qc.asCompoundOperation(); - const auto qc2 = qc::QuantumComputation::fromCompoundOperation(*compound); - - EXPECT_EQ(qc2.getNqubits(), 2); - EXPECT_EQ(qc2.getNcbits(), 2); - EXPECT_EQ(qc2.getNops(), 2); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c;\n" - "x q[1];\n" - "c[1] = measure q[1];\n"; - const auto actual = qc2.toQASM(); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifOperationRegisterToOpenQASM2) { - qc.addQubitRegister(1); - const auto& creg = qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), nullptr, creg, - 1U); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[1];\n" - "creg c[1];\n" - "if (c == 1) {\n" - " x q[0];\n" - "}\n"; - const auto actual = qc.toQASM(false); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifOperationBitToOpenQASM2) { - qc.addQubitRegister(1); - qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), nullptr, 0); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[1];\n" - "creg c[1];\n" - "if (c[0]) {\n" - " x q[0];\n" - "}\n"; - const auto actual = qc.toQASM(false); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifElseOperationRegisterToOpenQASM2) { - qc.addQubitRegister(1); - const auto& creg = qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), - std::make_unique(0, qc::Y), creg, 1U); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[1];\n" - "creg c[1];\n" - "if (c == 1) {\n" - " x q[0];\n" - "}\n" - "if (c != 1) {\n" - " y q[0];\n" - "}\n"; - const auto actual = qc.toQASM(false); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifElseOperationBitToOpenQASM2) { - qc.addQubitRegister(1); - qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), - std::make_unique(0, qc::Y), 0); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[1];\n" - "creg c[1];\n" - "if (c[0]) {\n" - " x q[0];\n" - "}\n" - "if (!c[0]) {\n" - " y q[0];\n" - "}\n"; - const auto actual = qc.toQASM(false); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifOperationRegisterToOpenQASM3) { - qc.addQubitRegister(1); - const auto& creg = qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), nullptr, creg, - 1U); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "if (c == 1) {\n" - " x q[0];\n" - "}\n"; - const auto actual = qc.toQASM(); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifOperationBitToOpenQASM3) { - qc.addQubitRegister(1); - qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), nullptr, 0); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "if (c[0]) {\n" - " x q[0];\n" - "}\n"; - const auto actual = qc.toQASM(); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifElseOperationRegisterToOpenQASM3) { - qc.addQubitRegister(1); - const auto& creg = qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), - std::make_unique(0, qc::Y), creg, 1U); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "if (c == 1) {\n" - " x q[0];\n" - "} else {\n" - " y q[0];\n" - "}\n"; - const auto actual = qc.toQASM(); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifElseOperationBitToOpenQASM3) { - qc.addQubitRegister(1); - qc.addClassicalRegister(1); - qc.ifElse(std::make_unique(0, qc::X), - std::make_unique(0, qc::Y), 0); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "if (c[0]) {\n" - " x q[0];\n" - "} else {\n" - " y q[0];\n" - "}\n"; - const auto actual = qc.toQASM(); - EXPECT_EQ(expected, actual); -} - -TEST_F(IO, ifElseOperationInvalidBitComparison) { - qc.addQubitRegister(1); - qc.addClassicalRegister(1); - try { - qc.if_(qc::X, 0, 0, true, qc::Lt); - FAIL() << "Expected an exception for invalid expected value."; - } catch (const std::invalid_argument& e) { - EXPECT_STREQ(e.what(), - "Inequality comparisons on a single bit are not supported."); - SUCCEED(); - } catch (...) { - FAIL() << "Expected an invalid_argument exception."; - } -} - -TEST_F(IO, dumpingIncompleteOutputPermutationNotStartingAtZero) { - qc.addQubitRegister(2); - qc.addClassicalRegister(1); - qc.measure(1, 0); - qc.initializeIOMapping(); - const auto qasm = qc.toQASM(); - std::cout << qasm << "\n"; - const auto qc2 = qasm3::Importer::imports(qasm); - EXPECT_EQ(qc, qc2); -} - -TEST_F(IO, indexedRegisterOperands) { - const auto& q = qc.addQubitRegister(2); - const auto& c = qc.addClassicalRegister(2); - - qc.h(q[0]); - qc.cx(q[0], q[1]); - qc.measure(q[0], c[0]); - qc.measure(q[1], c[1]); - - const auto qasm = qc.toQASM(); - const auto* const expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c;\n" - "h q[0];\n" - "cx q[0], q[1];\n" - "c[0] = measure q[0];\n" - "c[1] = measure q[1];\n"; - EXPECT_EQ(qasm, expected); -} - -TEST(OpenQASMSerializer, serializesCompoundOperationBody) { - const qc::QuantumRegister qreg{0U, 3U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - qubitMap.try_emplace(0U, qreg, "left"); - qubitMap.try_emplace(1U, qreg, "right"); - qubitMap.try_emplace(2U, qreg, "aux"); - const qc::ClassicalRegister creg{0U, 2U, "c"}; - qc::BitIndexToRegisterMap bitMap{}; - bitMap.try_emplace(0U, creg, "c[0]"); - bitMap.try_emplace(1U, creg, "c[1]"); - - qc::CompoundOperation compound{}; - compound.emplace_back(0U, qc::H); - compound.emplace_back(qc::Control{0U}, 1U, qc::X); - compound.emplace_back( - qc::Control{0U, qc::Control::Type::Neg}, 1U, qc::X); - compound.emplace_back(qc::Controls{qc::Control{0U}}, - 1U, 2U, qc::Peres); - compound.emplace_back(qc::Controls{qc::Control{0U}}, - 1U, 2U, qc::Peresdg); - compound.emplace_back(qc::Targets{1U, 2U}, - std::vector{0U, 1U}); - - std::ostringstream output3{}; - qc::OpenQASMSerializer(output3).serialize(compound, qubitMap, bitMap); - EXPECT_EQ(output3.str(), - "h left;\ncx left, right;\nnegctrl @ x left, right;\n" - "ctrl @ cx left, aux, right;\nctrl @ x left, aux;\n" - "ctrl @ x left, aux;\nctrl @ cx left, aux, right;\n" - "c[0] = measure right;\nc[1] = measure aux;\n"); - - std::ostringstream output2{}; - qc::OpenQASMSerializer(output2, qc::Format::OpenQASM2) - .serialize(compound, qubitMap, bitMap, 1U); - EXPECT_EQ(output2.str(), - " h left;\n cx left, right;\n x left;\n" - " cx left, right;\n x left;\n" - " ccx left, aux, right;\n cx left, aux;\n" - " cx left, aux;\n ccx left, aux, right;\n" - " measure right -> c[0];\n measure aux -> c[1];\n"); -} - -TEST(OpenQASMSerializer, serializesOnlyContiguousWholeRegisters) { - const qc::QuantumRegister qreg{0U, 3U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - const qc::ClassicalRegister creg{0U, 3U, "c"}; - qc::BitIndexToRegisterMap bitMap{}; - for (qc::Qubit i = 0U; i < 3U; ++i) { - qubitMap.try_emplace(i, qreg, qreg.toString(i)); - } - for (qc::Bit i = 0U; i < 3U; ++i) { - bitMap.try_emplace(i, creg, creg.toString(i)); - } - - qc::CompoundOperation compound{}; - compound.emplace_back(qc::Targets{0U, 2U}, - qc::Barrier); - compound.emplace_back(qc::Targets{0U, 2U}); - compound.emplace_back(qc::Targets{0U, 2U}, - std::vector{0U, 2U}); - compound.emplace_back(qc::Targets{0U, 0U, 2U}, - qc::Barrier); - compound.emplace_back( - qc::Targets{0U, 1U, 2U}, std::vector{0U, 0U, 2U}); - compound.emplace_back(qc::Targets{0U, 1U, 2U}); - - std::ostringstream output{}; - qc::OpenQASMSerializer(output).serialize(compound, qubitMap, bitMap); - EXPECT_EQ(output.str(), "barrier q[0], q[2];\n" - "reset q[0];\nreset q[2];\n" - "c[0] = measure q[0];\nc[2] = measure q[2];\n" - "barrier q[0], q[0], q[2];\n" - "c[0] = measure q[0];\nc[0] = measure q[1];\n" - "c[2] = measure q[2];\n" - "reset q;\n"); -} - -TEST(OpenQASMSerializer, preservesNegativePeresControlsInOpenQASM2) { - const qc::QuantumRegister qreg{0U, 3U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - qubitMap.try_emplace(0U, qreg, "left"); - qubitMap.try_emplace(1U, qreg, "right"); - qubitMap.try_emplace(2U, qreg, "aux"); - - const qc::Controls controls{qc::Control{0U, qc::Control::Type::Neg}}; - qc::CompoundOperation compound{}; - compound.emplace_back(controls, 1U, 2U, qc::Peres); - compound.emplace_back(controls, 1U, 2U, qc::Peresdg); - - std::ostringstream output{}; - qc::OpenQASMSerializer(output, qc::Format::OpenQASM2) - .serialize(compound, qubitMap, {}); - EXPECT_EQ(output.str(), "x left;\n" - "ccx left, aux, right;\n" - "cx left, aux;\n" - "x left;\n" - "x left;\n" - "cx left, aux;\n" - "ccx left, aux, right;\n" - "x left;\n"); -} - -TEST(OpenQASMSerializer, indentsNestedIfElseStructure) { - const qc::QuantumRegister qreg{0U, 1U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - qubitMap.try_emplace(0U, qreg, "q[0]"); - const qc::ClassicalRegister creg{0U, 1U, "c"}; - qc::BitIndexToRegisterMap bitMap{}; - bitMap.try_emplace(0U, creg, "c[0]"); - - auto nested = std::make_unique( - std::make_unique(0U, qc::X), - std::make_unique(0U, qc::Y), 0U); - const qc::IfElseOperation operation{std::move(nested), nullptr, 0U}; - - std::ostringstream output3{}; - qc::OpenQASMSerializer(output3).serialize(operation, qubitMap, bitMap); - EXPECT_EQ(output3.str(), "if (c[0]) {\n" - " if (c[0]) {\n" - " x q[0];\n" - " } else {\n" - " y q[0];\n" - " }\n" - "}\n"); - - std::ostringstream output2{}; - qc::OpenQASMSerializer(output2, qc::Format::OpenQASM2) - .serialize(operation, qubitMap, bitMap); - EXPECT_EQ(output2.str(), "if (c[0]) {\n" - " if (c[0]) {\n" - " x q[0];\n" - " }\n" - " if (!c[0]) {\n" - " y q[0];\n" - " }\n" - "}\n"); -} - -TEST(OpenQASMSerializer, handlesUnsupportedOperations) { - const qc::QuantumRegister qreg{0U, 1U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - qubitMap.try_emplace(0U, qreg, "q[0]"); - - std::ostringstream output{}; - const qc::OpenQASMSerializer serializer{output}; - const qc::StandardOperation unsupportedGate{0U, qc::None}; - testing::internal::CaptureStderr(); - serializer.serialize(unsupportedGate, qubitMap, {}); - EXPECT_NE(testing::internal::GetCapturedStderr().find( - "gate type none could not be converted"), - std::string::npos); - - const UnsupportedOperation unsupportedOperation{}; - EXPECT_THROW(serializer.serialize(unsupportedOperation, {}, {}), - std::invalid_argument); -} - -TEST(OpenQASMSerializer, delegatesCustomOperationsRecursively) { - const qc::QuantumRegister qreg{0U, 1U, "q"}; - qc::QubitIndexToRegisterMap qubitMap{}; - qubitMap.try_emplace(0U, qreg, "q[0]"); - const qc::ClassicalRegister creg{0U, 1U, "c"}; - qc::BitIndexToRegisterMap bitMap{}; - bitMap.try_emplace(0U, creg, "c[0]"); - - std::ostringstream output{}; - const qc::OpenQASMSerializer serializer{ - output, qc::Format::OpenQASM2, - [](std::ostream& customOutput, const qc::Operation& operation, - const qc::QubitIndexToRegisterMap& qubits, - const qc::BitIndexToRegisterMap& bits, const std::size_t indent) { - if (dynamic_cast(&operation) == nullptr) { - return false; - } - customOutput << std::string(indent * 2U, ' ') << "custom " - << qubits.at(0U).second << " -> " << bits.at(0U).second - << ";\n"; - return true; - }}; - - const UnsupportedOperation direct{}; - serializer.serialize(direct, qubitMap, bitMap); - EXPECT_EQ(output.str(), "custom q[0] -> c[0];\n"); - - output.str({}); - qc::CompoundOperation compound{}; - compound.emplace_back(); - serializer.serialize(compound, qubitMap, bitMap); - EXPECT_EQ(output.str(), "custom q[0] -> c[0];\n"); - - output.str({}); - const qc::IfElseOperation conditional{ - std::make_unique(), - std::make_unique(), 0U}; - serializer.serialize(conditional, qubitMap, bitMap); - EXPECT_EQ(output.str(), "if (c[0]) {\n" - " custom q[0] -> c[0];\n" - "}\n" - "if (!c[0]) {\n" - " custom q[0] -> c[0];\n" - "}\n"); -} - -TEST(OpenQASMSerializer, serializesAncillaRegisters) { - qc::QuantumComputation computation{1U}; - computation.addAncillaryRegister(1U, "anc"); - EXPECT_NE(computation.toQASM().find("qubit[1] anc;"), std::string::npos); -} diff --git a/test/ir/test_operation.cpp b/test/ir/test_operation.cpp deleted file mode 100644 index bf9e95b539..0000000000 --- a/test/ir/test_operation.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" -#include "qasm3/Importer.hpp" - -#include - -#include -#include -#include -#include -#include - -TEST(StandardOperation, CommutesAtQubit) { - const qc::StandardOperation op1(0, 1, qc::OpType::RY, std::vector{qc::PI_2}); - const qc::StandardOperation op2(0, 1, qc::OpType::RY, std::vector{-qc::PI_4}); - const qc::StandardOperation op3(0, qc::OpType::RY, std::vector{-qc::PI_4}); - EXPECT_TRUE(op1.commutesAtQubit(op2, 0)); - EXPECT_TRUE(op1.commutesAtQubit(op2, 0)); - EXPECT_FALSE(op1.commutesAtQubit(op3, 0)); - EXPECT_TRUE(op1.commutesAtQubit(op2, 2)); -} - -TEST(CompoundOperation, CommutesAtQubit) { - qc::CompoundOperation op1; - op1.emplace_back(0, qc::OpType::RY, - std::vector{qc::PI_2}); - op1.emplace_back(1, qc::OpType::RX, - std::vector{qc::PI_2}); - qc::CompoundOperation op2; - op2.emplace_back(0, qc::OpType::RY, - std::vector{qc::PI_2}); - op2.emplace_back(1, qc::OpType::RY, - std::vector{qc::PI_2}); - op2.emplace_back(1, qc::OpType::RY, - std::vector{qc::PI_2}); - const qc::StandardOperation op3(0, qc::OpType::RY, std::vector{-qc::PI_4}); - const qc::StandardOperation op4(1, qc::OpType::RY, std::vector{-qc::PI_4}); - EXPECT_TRUE(op1.commutesAtQubit(op3, 0)); - EXPECT_TRUE(op3.commutesAtQubit(op1, 0)); - EXPECT_FALSE(op4.commutesAtQubit(op1, 1)); - EXPECT_TRUE(op4.commutesAtQubit(op2, 1)); - EXPECT_TRUE(op1.commutesAtQubit(op2, 0)); - EXPECT_FALSE(op1.commutesAtQubit(op2, 1)); - EXPECT_TRUE(op1.commutesAtQubit(op2, 2)); -} - -TEST(StandardOperation, IsInverseOf) { - const qc::StandardOperation op1(0, qc::OpType::RY, std::vector{qc::PI_2}); - qc::StandardOperation op1Inv = op1; - op1Inv.invert(); - EXPECT_TRUE(op1.isInverseOf(op1Inv)); - EXPECT_FALSE(op1.isInverseOf(op1)); - const qc::StandardOperation op2(0, qc::OpType::Sdg); - qc::StandardOperation op2Inv = op2; - op2Inv.invert(); - EXPECT_TRUE(op2.isInverseOf(op2Inv)); - EXPECT_FALSE(op2.isInverseOf(op2)); - const qc::StandardOperation op3(0, qc::OpType::X); - EXPECT_FALSE(op3.isInverseOf(op1)); -} - -TEST(CompoundOperation, GlobalIsInverseOf) { - qc::CompoundOperation op1; - op1.emplace_back(0, qc::RY, std::vector{qc::PI_2}); - op1.emplace_back(1, qc::RY, std::vector{qc::PI_2}); - // the actual inverse of op1 - qc::CompoundOperation op2; - op2.emplace_back(0, qc::RY, std::vector{-qc::PI_2}); - op2.emplace_back(1, qc::RY, std::vector{-qc::PI_2}); - // the compound operations with different number of operations - qc::CompoundOperation op3 = op2; - op3.emplace_back(2, qc::RY, std::vector{qc::PI_2}); - // the operations come in different order - qc::CompoundOperation op4; - op4.emplace_back(1, qc::RY, std::vector{-qc::PI_2}); - op4.emplace_back(0, qc::RY, std::vector{-qc::PI_2}); - EXPECT_TRUE(op1.isInverseOf(op2)); - EXPECT_TRUE(op2.isInverseOf(op1)); - EXPECT_FALSE(op1.isInverseOf(op3)); - EXPECT_FALSE(op1.isInverseOf(qc::StandardOperation(0, qc::RY))); - EXPECT_TRUE(op1.isInverseOf(op4)); -} - -TEST(CompoundOperation, IsInverseOf) { - // This functionality is not implemented yet, the function isInverseOf leads - // to false negatives - qc::CompoundOperation op1; - op1.emplace_back(0, qc::OpType::RY, - std::vector{-qc::PI_2}); - op1.emplace_back(1, qc::OpType::RY, - std::vector{-qc::PI_2}); - qc::CompoundOperation op2 = op1; - op2.invert(); - EXPECT_TRUE(op1.isInverseOf(op2)); -} - -TEST(CompoundOperation, GetNqubits) { - qc::CompoundOperation op; - op.emplace_back(0, qc::OpType::X); - op.emplace_back(1, qc::OpType::X); - op.emplace_back(3, qc::OpType::X); - EXPECT_EQ(op.getNqubits(), 3); -} - -TEST(CompoundOperation, IsClifford) { - qc::CompoundOperation op1; - op1.emplace_back(0, qc::OpType::H); - op1.emplace_back(1, qc::OpType::RX, - std::vector{qc::PI_2}); - EXPECT_FALSE(op1.isClifford()); -} - -TEST(OpType, General) { - EXPECT_EQ(qc::toString(qc::RZ), "rz"); - std::stringstream ss; - ss << qc::OpType::RZ; - EXPECT_EQ(ss.str(), "rz"); -} - -TEST(OpType, SingleQubitGate) { - EXPECT_TRUE(qc::isSingleQubitGate(qc::P)); - EXPECT_FALSE(qc::isSingleQubitGate(qc::ECR)); -} - -TEST(NonStandardOperation, IsInverseOf) { - const qc::StandardOperation op(0, qc::I); - EXPECT_FALSE(qc::NonUnitaryOperation(qc::Targets{0}).isInverseOf(op)); - EXPECT_FALSE(qc::SymbolicOperation().isInverseOf(op)); -} - -TEST(NonStandardOperation, CommutesAtQubit) { - const qc::StandardOperation op(0, qc::X); - EXPECT_FALSE(qc::NonUnitaryOperation(qc::Targets{0}).commutesAtQubit(op, 0)); - EXPECT_FALSE( - qc::SymbolicOperation(0, qc::P, {sym::Expression()}) - .commutesAtQubit(op, 0)); -} - -TEST(NonUnitaryOperation, ResetEquality) { - const qc::NonUnitaryOperation reset0(qc::Targets{0}); - const qc::NonUnitaryOperation equalReset(qc::Targets{0}); - const qc::NonUnitaryOperation differentReset(qc::Targets{1}); - - EXPECT_EQ(reset0, equalReset); - EXPECT_NE(reset0, differentReset); -} - -TEST(Operation, IsIndividualGate) { - const qc::StandardOperation op1(0, qc::X); - EXPECT_TRUE(op1.isSingleQubitGate()); - const qc::StandardOperation op2(0, 1, qc::X); - EXPECT_FALSE(op2.isSingleQubitGate()); - const qc::StandardOperation op3(1, qc::RXX); - EXPECT_FALSE(op3.isSingleQubitGate()); -} - -TEST(Operation, IsClifford) { - const qc::StandardOperation x(0, qc::X); - EXPECT_TRUE(x.isClifford()); - const qc::StandardOperation cx(0, 1, qc::X); - EXPECT_TRUE(cx.isClifford()); - const qc::StandardOperation ccx({0, 1}, 2, qc::X); - EXPECT_FALSE(ccx.isClifford()); - - const qc::StandardOperation y(0, qc::Y); - EXPECT_TRUE(y.isClifford()); - const qc::StandardOperation cy(0, 1, qc::Y); - EXPECT_TRUE(cy.isClifford()); - const qc::StandardOperation ccy({0, 1}, 2, qc::Y); - EXPECT_FALSE(ccy.isClifford()); - - const qc::StandardOperation z(0, qc::Z); - EXPECT_TRUE(z.isClifford()); - const qc::StandardOperation cz(0, 1, qc::Z); - EXPECT_TRUE(cz.isClifford()); - const qc::StandardOperation ccz({0, 1}, 2, qc::Z); - EXPECT_FALSE(ccz.isClifford()); - - const qc::StandardOperation t(0, qc::T); - EXPECT_FALSE(t.isClifford()); - const qc::StandardOperation tdg(0, qc::Tdg); - EXPECT_FALSE(tdg.isClifford()); - - const qc::StandardOperation h(0, qc::H); - EXPECT_TRUE(h.isClifford()); - - const qc::StandardOperation s(0, qc::S); - EXPECT_TRUE(s.isClifford()); - const qc::StandardOperation sdg(0, qc::Sdg); - EXPECT_TRUE(sdg.isClifford()); - - const qc::StandardOperation sx(0, qc::SX); - EXPECT_TRUE(sx.isClifford()); - const qc::StandardOperation sxdg(0, qc::SXdg); - EXPECT_TRUE(sxdg.isClifford()); - - const qc::StandardOperation dcx({0, 1}, qc::DCX); - EXPECT_TRUE(dcx.isClifford()); - const qc::StandardOperation swap({0, 1}, qc::SWAP); - EXPECT_TRUE(swap.isClifford()); - const qc::StandardOperation iswap({0, 1}, qc::iSWAP); - EXPECT_TRUE(iswap.isClifford()); - const qc::StandardOperation ecr({0, 1}, qc::ECR); - EXPECT_TRUE(ecr.isClifford()); -} - -TEST(Operation, IsDiagonalGate) { - const qc::StandardOperation op1(0, qc::X); - EXPECT_FALSE(op1.isDiagonalGate()); - const qc::StandardOperation op2(0, qc::Z); - EXPECT_TRUE(op2.isDiagonalGate()); -} - -TEST(Operation, IsGlobalGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "rz(pi/4) q[0];\n" - "ry(pi/2) q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - EXPECT_EQ(qc.getNqubits(), 3); - EXPECT_FALSE(qc.at(0)->isGlobal(3)); - EXPECT_TRUE(qc.at(1)->isGlobal(3)); -} - -TEST(Operation, Equality) { - const qc::StandardOperation op1(0, qc::Z); - const qc::StandardOperation op2(1, 0, qc::Z); - const qc::StandardOperation op3(0, 1, qc::Z); - const qc::StandardOperation op4({0, qc::Control::Type::Neg}, 1, qc::Z); - EXPECT_FALSE(op1 == op2); - EXPECT_TRUE(op2 == op3); - EXPECT_TRUE(op3 == op2); - EXPECT_FALSE(op2 == op4); -} - -TEST(StandardOperation, Constructor) { - EXPECT_NO_THROW(std::ignore = - qc::StandardOperation(0, 1, qc::OpType::P, {qc::PI})); - EXPECT_NO_THROW( - std::ignore = qc::StandardOperation(0, {1, 2}, qc::OpType::P, {qc::PI})); -} - -TEST(StandardOperation, DuplicateQubitThrowsError) { - EXPECT_THROW(std::ignore = - qc::StandardOperation(0, {0, 1}, qc::OpType::P, {qc::PI}), - std::runtime_error); - EXPECT_THROW(std::ignore = - qc::StandardOperation(0, 0, qc::OpType::P, {qc::PI}), - std::runtime_error); - - qc::QuantumComputation qc(2); - EXPECT_THROW(qc.cx(0, 0), std::runtime_error); - EXPECT_THROW(qc.mcx({0, 1}, 0), std::runtime_error); -} diff --git a/test/ir/test_qasm3_parser.cpp b/test/ir/test_qasm3_parser.cpp deleted file mode 100644 index bd917b153b..0000000000 --- a/test/ir/test_qasm3_parser.cpp +++ /dev/null @@ -1,2680 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/QuantumComputation.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Importer.hpp" -#include "qasm3/Parser.hpp" -#include "qasm3/Scanner.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/Token.hpp" -#include "qasm3/passes/ConstEvalPass.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace qc; - -namespace { - -class Qasm3ParserTest : public testing::TestWithParam {}; - -} // namespace - -TEST_F(Qasm3ParserTest, ImportQasm3) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "/* this is a comment, which can span multiple\n" - "\n" - "\n" - "// lines */\n" - "bit[3] c;\n"; - const auto qc = qasm3::Importer::imports(testfile); - EXPECT_EQ(qc.getNqubits(), 3); - EXPECT_EQ(qc.getNcbits(), 3); -} - -TEST_F(Qasm3ParserTest, ImportQasm3SingleQubit) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "x q;"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.getNqubits(), 1); - ASSERT_EQ(qc.getNindividualOps(), 1); - EXPECT_EQ(qc.front()->getType(), qc::X); -} - -TEST_F(Qasm3ParserTest, ImportQasm3MeasureSingleQubit) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit q;\n" - "bit c = measure q;"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.getNqubits(), 1); - EXPECT_EQ(qc.getNcbits(), 1); - ASSERT_EQ(qc.getNindividualOps(), 1); - EXPECT_EQ(qc.front()->getType(), qc::Measure); -} - -TEST_F(Qasm3ParserTest, ImportQasm3RejectsIndexingSingleQubit) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "x q[0];"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type 'qubit' cannot be indexed."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasm3IndexesSizedQubitRegister) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "x q[0];"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.getNqubits(), 1); - ASSERT_EQ(qc.getNindividualOps(), 1); - EXPECT_EQ(qc.front()->getType(), qc::X); -} - -TEST_F(Qasm3ParserTest, ImportQasm3OldSyntax) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qreg q[3];\n" - "creg r[3];\n"; - const auto qc = qasm3::Importer::imports(testfile); - EXPECT_EQ(qc.getNqubits(), 3); - EXPECT_EQ(qc.getNcbits(), 3); -} - -TEST_F(Qasm3ParserTest, ImportQasm3GateDecl) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "gate my_x q1, q2 {\n" - " x q1;\n" - " x q2;\n" - "}\n" - "my_x q[0], q[1];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "x q[0];\n" - "x q[1];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3CtrlModifier) { - const std::string testfile = - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[5] q;\n" - "x q[0];\n" - "ctrl @ x q[0], q[1];\n" - "ctrl(2) @ x q[0], q[1], q[2];\n" - "ctrl(3) @ x q[0], q[1], q[2], q[3];\n" - "ctrl(3) @ negctrl @ x q[0], q[1], q[2], q[3], q[4];\n" - "ctrl @ p(0.5) q[0], q[1];\n" - "ctrl @ rx(pi) q[0], q[1];\n" - "ctrl @ y q[0], q[1];\n" - "ctrl @ ry(pi) q[0], q[1];\n" - "ctrl @ z q[0], q[1];\n" - "ctrl @ rz(pi) q[0], q[1];\n" - "ctrl @ h q[0], q[1];\n" - "ctrl @ swap q[0], q[1], q[2];\n" - "ctrl @ rxx(pi) q[0], q[1], q[2];\n" - "ctrl @ negctrl @ x q[0], q[1], q[2];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = - "// i 0 1 2 3 4\n" - "// o 0 1 2 3 4\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[5] q;\n" - "x q[0];\n" - "cx q[0], q[1];\n" - "ccx q[0], q[1], q[2];\n" - "ctrl(3) @ x q[0], q[1], q[2], q[3];\n" - "ctrl(3) @ negctrl @ x q[0], q[1], q[2], q[3], q[4];\n" - "cp(0.5) q[0], q[1];\n" - "crx(3.14159265358979) q[0], q[1];\n" - "cy q[0], q[1];\n" - "cry(3.14159265358979) q[0], q[1];\n" - "cz q[0], q[1];\n" - "crz(3.14159265358979) q[0], q[1];\n" - "ch q[0], q[1];\n" - "cswap q[0], q[1], q[2];\n" - "ctrl @ rxx(3.14159265358979) q[0], q[1], q[2];\n" - "ctrl @ negctrl @ x q[0], q[1], q[2];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3PowModifier) { - const std::string testfile = R"(OPENQASM 3.0; -include "stdgates.inc"; -qubit[2] q; -pow(0) @ h q[0]; -pow(2) @ s q[0]; -pow(-2) @ s q[0]; -inv @ pow(-2) @ s q[0]; -pow(2) @ pow(3) @ x q[0]; -pow(2) @ ctrl @ s q[0], q[1]; -)"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.getNindividualOps(), 14U); - const std::string out = qc.toQASM(); - const std::string expected = R"(// i 0 1 -// o 0 1 -OPENQASM 3.0; -include "stdgates.inc"; -qubit[2] q; -s q[0]; -s q[0]; -sdg q[0]; -sdg q[0]; -s q[0]; -s q[0]; -x q[0]; -x q[0]; -x q[0]; -x q[0]; -x q[0]; -x q[0]; -ctrl @ p(pi/2) q[0], q[1]; -ctrl @ p(pi/2) q[0], q[1]; -)"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3PowModifierForCustomAndBroadcastGates) { - const std::string testfile = R"(OPENQASM 3.0; -include "stdgates.inc"; -gate hs q0 { - h q0; - s q0; -} -qubit[2] q; -pow(2) @ hs q[0]; -pow(2) @ x q; -)"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = R"(// i 0 1 -// o 0 1 -OPENQASM 3.0; -include "stdgates.inc"; -qubit[2] q; -h q[0]; -s q[0]; -h q[0]; -s q[0]; -x q[0]; -x q[0]; -x q[1]; -x q[1]; -)"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3PowModifierSkipsEmptyCustomGate) { - const std::string testfile = R"(OPENQASM 3.0; -include "stdgates.inc"; -gate empty q0 { - pow(0) @ x q0; -} -qubit q; -pow(2) @ empty q; -x q; -)"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.getNindividualOps(), 1U); - EXPECT_EQ(qc.front()->getType(), qc::X); -} - -TEST_F(Qasm3ParserTest, ImportQasm3PowModifierRejectsNonIntegerExponent) { - const auto expectRejected = [](const std::string& exponent) { - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports( - "OPENQASM 3.0; include \"stdgates.inc\"; qubit q; pow(" + - exponent + ") @ x q;"); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Only constant integer expressions are supported as " - "power modifier exponents."); - throw; - } - }, - qasm3::CompilerError); - }; - - expectRejected("0.5"); - expectRejected("true"); -} - -TEST_F(Qasm3ParserTest, ImportQasm3InvModifier) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "inv @ s q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "sdg q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3CompoundGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "gate my_compound_gate q {\n" - " // comment\n" - " x /* nested comment */ q;\n" - " h q;\n" - "}\n" - "my_compound_gate q;"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "x q[0];\n" - "h q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3ControlledCompoundGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "gate my_compound_gate q {\n" - " x q;\n" - "}\n" - "ctrl @ my_compound_gate q[0], q[1];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "cx q[0], q[1];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3ParamCompoundGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "gate my_compound_gate(a) q {\n" - " rz(a) q;\n" - "}\n" - "my_compound_gate(1.0 * pi) q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "rz(3.14159265358979) q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3Measure) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit r1;\n" - "bit[2] r2;\n" - "h q;\n" - "r1[0] = measure q[0];\n" - "r1 = measure q[0];\n" - "r2 = measure q;\n" - "measure q[1] -> r1;\n" - "measure q[1] -> r2[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] r1;\n" - "bit[2] r2;\n" - "h q[0];\n" - "h q[1];\n" - "r1[0] = measure q[0];\n" - "r1[0] = measure q[0];\n" - "r2 = measure q;\n" - "r1[0] = measure q[1];\n" - "r2[0] = measure q[1];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3InitialLayout) { - const std::string testfile = "// i 1 0\n" - "// o 1 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 1 0\n" - "// o 1 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n"; - - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3ConstEval) { - const std::string testfile = - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "const uint N = (0x4 + 8 - 0b10 - (0o10 / 4)) / 2;\n" - "qubit[N * 2] q;\n" - "ctrl @ x q[0], q[N * 2 - 1];\n" - "x q;"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3 4 5 6 7\n" - "// o 0 1 2 3 4 5 6 7\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[8] q;\n" - "bit[32] N;\n" - "cx q[0], q[7];\n" - "x q[0];\n" - "x q[1];\n" - "x q[2];\n" - "x q[3];\n" - "x q[4];\n" - "x q[5];\n" - "x q[6];\n" - "x q[7];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3NonUnitary) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q1;\n" - "qubit[2] q2;\n" - "reset q1[0];\n" - "barrier q1, q2;\n" - "reset q1;\n" - "bit c = measure q1[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q1;\n" - "qubit[2] q2;\n" - "bit[1] c;\n" - "reset q1[0];\n" - "barrier q1[0], q1[1], q2[0], q2[1];\n" - "reset q1;\n" - "c[0] = measure q1[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3IfStatement) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c) {\n" - " x q[1];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c[0] = measure q[0];\n" - "if (c[0]) {\n" - " x q[1];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3SingleBitIfStatement) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c[0]) {\n" - " x q[0];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q;\n" - "if (c[0]) {\n" - " x q[0];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3InvertedSingleBitIfStatement) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (!c[0]) {\n" - " x q[0];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q;\n" - "if (!c[0]) {\n" - " x q[0];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3SingleBitIfStatementRegister) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q[0];\n" - "if (c == 1) {\n" - " x q[0];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q;\n" - "if (c == 1) {\n" - " x q[0];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3SingleBitIfStatementRegisterFlipped) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q[0];\n" - "if (1 == c) {\n" - " x q[0];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q;\n" - "if (c == 1) {\n" - " x q[0];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3IfElseStatement) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c) {\n" - " x q[1];\n" - "} else {\n" - " x q[0];\n" - " x q[1];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c[0] = measure q[0];\n" - "if (c[0]) {\n" - " x q[1];\n" - "} else {\n" - " x q[0];\n" - " x q[1];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3IfElseStatementRegister) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c = measure q[0];\n" - "if (c == 1) {\n" - " x q[1];\n" - " x q[0];\n" - "} else {\n" - " x q[0];\n" - " x q[1];\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c[0] = measure q[0];\n" - "if (c == 1) {\n" - " x q[1];\n" - " x q[0];\n" - "} else {\n" - " x q[0];\n" - " x q[1];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3EmptyIfElse) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c) {\n" - "} else {\n" - "}"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c[0] = measure q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3UnsupportedSingleBitIfStatement) { - const auto comparisonKinds = {ComparisonKind::Lt, ComparisonKind::Leq, - ComparisonKind::Gt, ComparisonKind::Geq}; - - for (const auto comparisonKind : comparisonKinds) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c " + - toString(comparisonKind) + - " true) {\n" - " x q[0];\n" - "}"; - EXPECT_THROW( - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Type Check Error: Cannot compare boolean types."); - throw; - }, - qasm3::CompilerError); - } -} - -TEST_F(Qasm3ParserTest, ImportQasm3OutputPerm) { - const std::string testfile = "// i 0 2 1 3\n" - "// o 3 0\n" - "qubit[4] q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.outputPermutation, (Permutation{{0, 1}, {3, 0}})); -} - -TEST_F(Qasm3ParserTest, ImportQasm3OutputPermDefault) { - const std::string testfile = "// i 0 2 1 3\n" - "qubit[4] q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - EXPECT_EQ(qc.outputPermutation, - (Permutation{{0, 0}, {1, 1}, {2, 2}, {3, 3}})); -} - -TEST_F(Qasm3ParserTest, ImportQasm3IfElseNoBlock) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "h q[0];\n" - "bit c = measure q[0];\n" - "if (c) {} else \n" - " x q[1];\n" - "x q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[1] c;\n" - "h q[0];\n" - "c[0] = measure q[0];\n" - "if (!c[0]) {\n" - " x q[1];\n" - "}\n" - "x q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3InvalidStatementInBlock) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "bit c = measure q;\n" - "if (c) {\n" - " qubit invalid;\n" - "}"; - EXPECT_THROW( - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Only quantum statements are supported in blocks."); - throw; - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasm3ImplicitInclude) { - const std::string testfile = "qubit[1] q;\n" - "h q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3Qelib1) { - const std::string testfile = "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "h q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm3NestedGates) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "gate my_x q { x q; }\n" - "gate my_x2 q1 { x q1; }\n" - "qubit[1] q;\n" - "my_x2 q[0];\n"; - const auto qc = qasm3::Importer::imports(testfile); - EXPECT_EQ(qc.getNops(), 1); - EXPECT_EQ(qc.at(0)->getType(), OpType::X); -} - -TEST_F(Qasm3ParserTest, ImportQasm3AlternatingControl) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[7] q;\n" - "ctrl @ negctrl(2) @ negctrl @ ctrl @ ctrl @ x " - "q[0], q[1], q[2], q[3], q[4], q[5], q[6];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3 4 5 6\n" - "// o 0 1 2 3 4 5 6\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[7] q;\n" - "ctrl @ negctrl(3) @ ctrl(2) @ x q[0], q[1], " - "q[2], q[3], q[4], q[5], q[6];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasmConstEval) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "const uint N_1 = 0xa;\n" - "const uint N_2 = 8;\n" - "qubit[N_1 - N_2] q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[32] N_1;\n" - "bit[32] N_2;\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasmBroadcasting) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q1;\n" - "qubit[2] q2;\n" - "h q1;\n" - "reset q2;\n" - "cx q1, q2;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3\n" - "// o 0 1 2 3\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q1;\n" - "qubit[2] q2;\n" - "h q1[0];\n" - "h q1[1];\n" - "reset q2;\n" - "cx q1[0], q2[0];\n" - "cx q1[1], q2[1];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasmComparison) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c;\n" - "h q;\n" - "c[0] = measure q[0];\n" - "if (c < 0) { x q[0]; }\n" - "if (c <= 0) { x q[0]; }\n" - "if (c > 0) { x q[0]; }\n" - "if (c >= 0) { x q[0]; }\n" - "if (c == 0) { x q[0]; }\n" - "if (c != 0) { x q[0]; }\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c;\n" - "h q[0];\n" - "h q[1];\n" - "c[0] = measure q[0];\n" - "if (c < 0) {\n" - " x q[0];\n" - "}\n" - "if (c <= 0) {\n" - " x q[0];\n" - "}\n" - "if (c > 0) {\n" - " x q[0];\n" - "}\n" - "if (c >= 0) {\n" - " x q[0];\n" - "}\n" - "if (c == 0) {\n" - " x q[0];\n" - "}\n" - "if (c != 0) {\n" - " x q[0];\n" - "}\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasmNativeRedeclaration) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "bit c1;\n" - "gate h q { U(pi/2, 0, pi) q; }\n" - "h q;\n" - "c1 = measure q;\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c1;\n" - "h q[0];\n" - "c1 = measure q;\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm2CPrefix) { - const std::string testfile = "OPENQASM 2.0;\n" - "qubit[5] q;\n" - "// nothing in the declaration on purpose\n" - "gate ccccx q1, q2, q3, q4, q5 {\n" - "}\n" - "ccccx q[0], q[1], q[2], q[3], q[4];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3 4\n" - "// o 0 1 2 3 4\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[5] q;\n" - "ctrl(4) @ x q[0], q[1], q[2], q[3], q[4];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportMCXGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit[4] q;\n" - "mcx q[0], q[1], q[2], q[3];\n"; - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2 3\n" - "// o 0 1 2 3\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[4] q;\n" - "ctrl(3) @ x q[0], q[1], q[2], q[3];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportMQTBenchCircuit) { - const std::string qasm = R"( - // Benchmark was created by MQT Bench on 2024-03-17 - // For more information about MQT Bench, please visit https://www.cda.cit.tum.de/mqtbench/ - // MQT Bench version: 1.1.0 - // Qiskit version: 1.0.2 - - OPENQASM 2.0; - include "qelib1.inc"; - qreg eval[1]; - qreg q[1]; - creg meas[2]; - u2(0,-pi) eval[0]; - u3(0.9272952180016122,0,0) q[0]; - cx eval[0],q[0]; - u(-0.9272952180016122,0,0) q[0]; - cx eval[0],q[0]; - h eval[0]; - u(0.9272952180016122,0,0) q[0]; - barrier eval[0],q[0]; - measure eval[0] -> meas[0]; - measure q[0] -> meas[1]; - )"; - auto qc = qasm3::Importer::imports(qasm); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] eval;\n" - "qubit[1] q;\n" - "bit[2] meas;\n" - "h eval[0];\n" - "ry(0.927295218001612) q[0];\n" - "cx eval[0], q[0];\n" - "ry(-0.927295218001612) q[0];\n" - "cx eval[0], q[0];\n" - "h eval[0];\n" - "ry(0.927295218001612) q[0];\n" - "barrier eval[0], q[0];\n" - "meas[0] = measure eval[0];\n" - "meas[1] = measure q[0];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportMSGate) { - const std::string testfile = "OPENQASM 3.0;" - "qubit[3] q;" - "bit[3] c;" - "gate ms(p0) q0, q1, q2 {" - " rxx(p0) q0, q1;" - " rxx(p0) q0, q2;" - " rxx(p0) q1, q2;" - "}" - "ms(0.844396) q[0], q[1], q[2];" - "c = measure q;"; - - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2\n" - "// o 0 1 2\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "bit[3] c;\n" - "rxx(0.844396) q[0], q[1];\n" - "rxx(0.844396) q[0], q[2];\n" - "rxx(0.844396) q[1], q[2];\n" - "c = measure q;\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, HardwareQubitsInGates) { - const std::string testfile = "OPENQASM 3.0;" - "h $0;" - "cx $0, $1;"; - - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "h q[0];\n" - "cx q[0], q[1];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, HardwareQubitInMeasurement) { - const std::string testfile = "OPENQASM 3.0;" - "bit c = measure $0;"; - - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n" - "bit[1] c;\n" - "c = measure q;\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, HardwareQubitsNonConsecutive) { - const std::string testfile = "OPENQASM 3.0;" - "h $0;" - "cx $0, $2;"; - - const auto qc = qasm3::Importer::imports(testfile); - - const std::string out = qc.toQASM(); - const std::string expected = "// i 0 1 2\n" - "// o 0 1 2\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "h q[0];\n" - "cx q[0], q[2];\n"; - EXPECT_EQ(out, expected); -} - -TEST_F(Qasm3ParserTest, ImportQasm2CPrefixInvalidGate) { - const std::string testfile = "OPENQASM 2.0;\n" - "qubit[5] q;\n" - "cccck q[0], q[1], q[2], q[3], q[4];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Usage of unknown gate 'cccck'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasm3CPrefix) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit[5] q;\n" - "ccccx q[0], q[1], q[2], q[3], q[4];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Usage of unknown gate 'ccccx'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmScanner) { - std::stringstream ss{}; - const std::string testfile = - "$1 : . .5 -1. 1.25e-3 1e3 -= += ++ *= **= ** /= % %= |= || | &= " - "&& & ^= ^ ~= ~ ! <= <<= << < >= >>= >> >"; - const auto tokens = std::vector{ - qasm3::Token::Kind::HardwareQubit, - qasm3::Token::Kind::Colon, - qasm3::Token::Kind::Dot, - qasm3::Token::Kind::FloatLiteral, - qasm3::Token::Kind::FloatLiteral, - qasm3::Token::Kind::FloatLiteral, - qasm3::Token::Kind::FloatLiteral, - qasm3::Token::Kind::MinusEquals, - qasm3::Token::Kind::PlusEquals, - qasm3::Token::Kind::DoublePlus, - qasm3::Token::Kind::AsteriskEquals, - qasm3::Token::Kind::DoubleAsteriskEquals, - qasm3::Token::Kind::DoubleAsterisk, - qasm3::Token::Kind::SlashEquals, - qasm3::Token::Kind::Percent, - qasm3::Token::Kind::PercentEquals, - qasm3::Token::Kind::PipeEquals, - qasm3::Token::Kind::DoublePipe, - qasm3::Token::Kind::Pipe, - qasm3::Token::Kind::AmpersandEquals, - qasm3::Token::Kind::DoubleAmpersand, - qasm3::Token::Kind::Ampersand, - qasm3::Token::Kind::CaretEquals, - qasm3::Token::Kind::Caret, - qasm3::Token::Kind::TildeEquals, - qasm3::Token::Kind::Tilde, - qasm3::Token::Kind::ExclamationPoint, - qasm3::Token::Kind::LessThanEquals, - qasm3::Token::Kind::LeftShitEquals, - qasm3::Token::Kind::LeftShift, - qasm3::Token::Kind::LessThan, - qasm3::Token::Kind::GreaterThanEquals, - qasm3::Token::Kind::RightShiftEquals, - qasm3::Token::Kind::RightShift, - qasm3::Token::Kind::GreaterThan, - qasm3::Token::Kind::Eof, - }; - - ss << testfile; - qasm3::Scanner scanner(&ss); - - for (const auto& expected : tokens) { - auto token = scanner.next(); - EXPECT_EQ(token.kind, expected); - } -} - -TEST_F(Qasm3ParserTest, ImportQasmParseOperators) { - std::stringstream ss{}; - const std::string testfile = "x += 1;\n" - "x -= 1;\n" - "x *= 1;\n" - "x /= 1;\n" - "x &= 1;\n" - "x |= 1;\n" - "x ~= 1;\n" - "x ^= 1;\n" - "x <<= 1;\n" - "x >>= 1;\n" - "x %= 1;\n" - "x **= 1;\n"; - - ss << testfile; - qasm3::Parser parser(ss, false); - - const auto expectedTypes = std::vector{ - qasm3::AssignmentStatement::Type::PlusAssignment, - qasm3::AssignmentStatement::Type::MinusAssignment, - qasm3::AssignmentStatement::Type::TimesAssignment, - qasm3::AssignmentStatement::Type::DivAssignment, - qasm3::AssignmentStatement::Type::BitwiseAndAssignment, - qasm3::AssignmentStatement::Type::BitwiseOrAssignment, - qasm3::AssignmentStatement::Type::BitwiseNotAssignment, - qasm3::AssignmentStatement::Type::BitwiseXorAssignment, - qasm3::AssignmentStatement::Type::LeftShiftAssignment, - qasm3::AssignmentStatement::Type::RightShiftAssignment, - qasm3::AssignmentStatement::Type::ModuloAssignment, - qasm3::AssignmentStatement::Type::PowerAssignment, - }; - - for (const auto& expected : expectedTypes) { - const auto stmt = parser.parseAssignmentStatement(); - EXPECT_EQ(stmt->type, expected); - } -} - -TEST_F(Qasm3ParserTest, ImportQasmParseUnaryExpressions) { - std::stringstream ss{}; - const std::string testfile = "sin(x)\n" - "cos(x)\n" - "tan(x)\n" - "exp(x)\n" - "ln(x)\n" - "sqrt(x)\n"; - - ss << testfile; - qasm3::Parser parser(ss, false); - - const auto expectedTypes = std::vector{ - qasm3::UnaryExpression::Op::Sin, qasm3::UnaryExpression::Op::Cos, - qasm3::UnaryExpression::Op::Tan, qasm3::UnaryExpression::Op::Exp, - qasm3::UnaryExpression::Op::Ln, qasm3::UnaryExpression::Op::Sqrt, - }; - - for (const auto& expected : expectedTypes) { - const auto expr = parser.parseExpression(); - const auto unaryExpr = - std::dynamic_pointer_cast(expr); - EXPECT_NE(unaryExpr, nullptr); - EXPECT_EQ(unaryExpr->op, expected); - } -} - -TEST_F(Qasm3ParserTest, ImportQasmParseBinaryExpressions) { - std::stringstream ss{}; - const std::string testfile = "x^5\n" - "x == 5\n" - "x != 5\n" - "x <= 5\n" - "x < 5\n" - "x >= 5\n" - "x > 5\n"; - - ss << testfile; - qasm3::Parser parser(ss, false); - - const auto expectedTypes = std::vector{ - qasm3::BinaryExpression::Op::Power, - qasm3::BinaryExpression::Op::Equal, - qasm3::BinaryExpression::Op::NotEqual, - qasm3::BinaryExpression::Op::LessThanOrEqual, - qasm3::BinaryExpression::Op::LessThan, - qasm3::BinaryExpression::Op::GreaterThanOrEqual, - qasm3::BinaryExpression::Op::GreaterThan, - }; - - for (const auto& expected : expectedTypes) { - const auto expr = parser.parseExpression(); - const auto binaryExpr = - std::dynamic_pointer_cast(expr); - EXPECT_NE(binaryExpr, nullptr); - EXPECT_EQ(binaryExpr->op, expected); - } -} - -TEST_F(Qasm3ParserTest, ImportQasmUnknownQreg) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "x q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Usage of unknown quantum register."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmIndexOutOfBounds) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "x q[2];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Index expression must be smaller than the " - "width of the quantum register."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmIndexOutOfBoundsClassical) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c;\n" - "c[2] = measure q[0];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Index expression must be smaller than the " - "width of the classical register."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmDuplicateDeclaration) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "qubit[2] q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Identifier 'q' already declared."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInitConstRegWithMeasure) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "const bit c = measure q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Constant Evaluation: Constant declaration " - "initialization expression must be const."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmAssignmentUnknownIdentifier) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "c = measure q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Unknown identifier 'c'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmAssignmentConstVar) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "const bit c = 0;\n" - "c = measure q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Type Check Error: Type mismatch in declaration statement: " - "Expected 'bit[1]', found 'uint[32]'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmMultipleInputPermutations) { - const std::string testfile = "// i 0\n" - "// i 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Multiple initial layout specifications found."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmMultipleOutputPermutations) { - const std::string testfile = "// o 0\n" - "// o 0\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Multiple output permutation specifications found."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInvalidOpaqueGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "opaque asdf q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Unsupported opaque gate 'asdf'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmDuplicateGateDecl) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "gate my_x q { x q; }\n" - "gate my_x q { x q; }\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Gate 'my_x' already declared."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmDuplicateQubitArgGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "gate my_gate q, q { }\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Qubit 'q' already declared."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmUndeclaredGate) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q;\n" - "my_x q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Usage of unknown gate 'my_x'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInvalidGateTargets) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "gate my_x q { x q; }\n" - "my_x q[0], q[1];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Gate 'my_x' takes 1 targets, but 2 were supplied."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInvalidGateControls) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[3] q;\n" - "cx q[0];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Gate 'cx' takes 1 targets, but 0 were supplied."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInvalidGateModifiers) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "ctrl(2) @ x q[0];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Gate 'x' takes 2 controls, but only 1 were supplied."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateCallNonConst) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[2] c = measure q;\n" - "rz(c) q[0];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Only const expressions are supported as gate " - "parameters, but found 'IndexedIdentifier (c)'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateCallBroadcastingInvalidWidth) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q1;\n" - "qubit[3] q2;\n" - "cx q1, q2;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ( - e.message, - "When broadcasting, all registers must be of the same width."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateCallIndexingGateBody) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "gate my_x q { x q[0]; }\n" - "qubit q;\n" - "my_x q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Gate arguments cannot be indexed within gate body."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateMeasureInvalidSizes) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[2] q;\n" - "bit[3] c = measure q;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ( - e.message, - "Classical and quantum register must have the same width " - "in measure statement. Classical register 'c' has 3 bits, " - "but quantum register 'IndexedIdentifier (q)' has 2 qubits."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateOldStyleDesignator) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit q[2];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "In OpenQASM 3.0, the designator has been " - "changed to `type[designator] identifier;`"); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateExpectStatement) { - const std::string testfile = "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "+\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Expected quantum statement, got '+'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateVersionFloat) { - const std::string testfile = "OPENQASM 3.0;\n"; - EXPECT_NO_THROW(std::ignore = qasm3::Importer::imports(testfile)); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateVersionInteger) { - const std::string testfile = "OPENQASM 3;\n"; - EXPECT_NO_THROW(std::ignore = qasm3::Importer::imports(testfile)); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateVersionNotValid) { - const std::string testfile = "OPENQASM three;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Version declaration must be a float or integer literal."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmGateVersionNotAtBeginning) { - const std::string testfile = "qubit q;\n" - "OPENQASM 3.0;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ( - e.message, - "Version declaration must be at the beginning of the file."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmInvalidExpected) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit[2] q;\n" - "cx q[0] q[1];"; // missing comma - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Expected ',', got 'Identifier'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmTypeMismatchAssignment) { - const std::string testfile = "OPENQASM 3.0;\n" - "bit x;\n" - "x = 10;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Type mismatch in assignment. " - "Expected 'bit[1]', found 'uint[32]'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmAssignmentUnknownRightHandSide) { - const std::string testfile = "OPENQASM 3.0;\n" - "bit x;\n" - "x = y;"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Unknown identifier 'y'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmTypeMismatchBinaryExpr) { - const std::string testfile = "OPENQASM 3.0;\n" - "const bit x = 0;\n" - "const int y = 10 + x;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Type Check Error: Type mismatch in declaration statement: " - "Expected 'bit[1]', found 'uint[32]'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmConstNotInitialized) { - const std::string testfile = "OPENQASM 3.0;\n" - "const bit x;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, - "Constant Evaluation: Constant declaration initialization " - "expression must be initialized."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmUnaryTypeMismatchLogicalNot) { - const std::string testfile = "OPENQASM 3.0;\n" - "int x = !0;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Cannot apply logical not to " - "non-boolean type."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmUnaryTypeMismatchBitwiseNot) { - const std::string testfile = "OPENQASM 3.0;\n" - "bool x = ~false;\n" - "bool y = !true;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Cannot apply bitwise not to " - "non-numeric type."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmBinaryTypeMismatch) { - const std::string testfile = "OPENQASM 3.0;\n" - "int x = 1 + false;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Type mismatch in binary " - "expression: uint[32], bool."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmAssignmentIndexType) { - const std::string testfile = "OPENQASM 3.0;\n" - "bit[16] x;\n" - "x[-1] = 0;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Type mismatch in assignment. " - "Expected 'bit[16]', found 'uint[32]'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmUnknownIdentifier) { - const std::string testfile = "OPENQASM 3.0;\n" - "int x = y;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Unknown identifier 'y'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmUnknownQubit) { - const std::string testfile = "OPENQASM 3.0;\n" - "bit x = measure q;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Type Check Error: Unknown identifier 'q'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmNegativeTypeDesignator) { - const std::string testfile = "OPENQASM 3.0;\n" - "bit[-1] c;\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ( - e.message, - "Type Check Error: Designator expression type check failed."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmDuplicateQubitBroadcast) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit[2] q;\n" - "cx q, q[1];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Duplicate qubit in target list."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmDuplicateQubitBroadcastInControls) { - const std::string testfile = "OPENQASM 3.0;\n" - "qubit[2] q;\n" - "ccx q, q[0], q[1];\n"; - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Duplicate qubit in control list."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, ImportQasmRegisterDeclarationInDefinition) { - const std::string testfile = "qubit[1] q;" - "gate test a {" - "qubit[2] crash;" - "x a;}" - "test q[0];"; - - EXPECT_THROW( - { - try { - const auto qc = qasm3::Importer::imports(testfile); - } catch (const qasm3::CompilerError& e) { - EXPECT_EQ(e.message, "Expected quantum statement, got 'qubit'."); - throw; - } - }, - qasm3::CompilerError); -} - -TEST_F(Qasm3ParserTest, TestPrintTokens) { - // This test is to print all tokens and make the coverage report happy. - const auto tokens = std::vector{ - qasm3::Token(qasm3::Token::Kind::None, 0, 0), - qasm3::Token(qasm3::Token::Kind::OpenQasm, 0, 0), - qasm3::Token(qasm3::Token::Kind::Include, 0, 0), - qasm3::Token(qasm3::Token::Kind::DefCalGrammar, 0, 0), - qasm3::Token(qasm3::Token::Kind::Def, 0, 0), - qasm3::Token(qasm3::Token::Kind::Cal, 0, 0), - qasm3::Token(qasm3::Token::Kind::DefCal, 0, 0), - qasm3::Token(qasm3::Token::Kind::Gate, 0, 0), - qasm3::Token(qasm3::Token::Kind::Opaque, 0, 0), - qasm3::Token(qasm3::Token::Kind::Extern, 0, 0), - qasm3::Token(qasm3::Token::Kind::Box, 0, 0), - qasm3::Token(qasm3::Token::Kind::Let, 0, 0), - qasm3::Token(qasm3::Token::Kind::Break, 0, 0), - qasm3::Token(qasm3::Token::Kind::Continue, 0, 0), - qasm3::Token(qasm3::Token::Kind::If, 0, 0), - qasm3::Token(qasm3::Token::Kind::Else, 0, 0), - qasm3::Token(qasm3::Token::Kind::End, 0, 0), - qasm3::Token(qasm3::Token::Kind::Return, 0, 0), - qasm3::Token(qasm3::Token::Kind::For, 0, 0), - qasm3::Token(qasm3::Token::Kind::While, 0, 0), - qasm3::Token(qasm3::Token::Kind::In, 0, 0), - qasm3::Token(qasm3::Token::Kind::Pragma, 0, 0), - qasm3::Token(qasm3::Token::Kind::Input, 0, 0), - qasm3::Token(qasm3::Token::Kind::Output, 0, 0), - qasm3::Token(qasm3::Token::Kind::Const, 0, 0), - qasm3::Token(qasm3::Token::Kind::ReadOnly, 0, 0), - qasm3::Token(qasm3::Token::Kind::Mutable, 0, 0), - qasm3::Token(qasm3::Token::Kind::Qreg, 0, 0), - qasm3::Token(qasm3::Token::Kind::Qubit, 0, 0), - qasm3::Token(qasm3::Token::Kind::CReg, 0, 0), - qasm3::Token(qasm3::Token::Kind::Bool, 0, 0), - qasm3::Token(qasm3::Token::Kind::Bit, 0, 0), - qasm3::Token(qasm3::Token::Kind::Int, 0, 0), - qasm3::Token(qasm3::Token::Kind::Uint, 0, 0), - qasm3::Token(qasm3::Token::Kind::Float, 0, 0), - qasm3::Token(qasm3::Token::Kind::Angle, 0, 0), - qasm3::Token(qasm3::Token::Kind::Complex, 0, 0), - qasm3::Token(qasm3::Token::Kind::Array, 0, 0), - qasm3::Token(qasm3::Token::Kind::Void, 0, 0), - qasm3::Token(qasm3::Token::Kind::Duration, 0, 0), - qasm3::Token(qasm3::Token::Kind::Stretch, 0, 0), - qasm3::Token(qasm3::Token::Kind::Gphase, 0, 0), - qasm3::Token(qasm3::Token::Kind::Inv, 0, 0), - qasm3::Token(qasm3::Token::Kind::Pow, 0, 0), - qasm3::Token(qasm3::Token::Kind::Ctrl, 0, 0), - qasm3::Token(qasm3::Token::Kind::NegCtrl, 0, 0), - qasm3::Token(qasm3::Token::Kind::Dim, 0, 0), - qasm3::Token(qasm3::Token::Kind::DurationOf, 0, 0), - qasm3::Token(qasm3::Token::Kind::Delay, 0, 0), - qasm3::Token(qasm3::Token::Kind::Reset, 0, 0), - qasm3::Token(qasm3::Token::Kind::Measure, 0, 0), - qasm3::Token(qasm3::Token::Kind::Barrier, 0, 0), - qasm3::Token(qasm3::Token::Kind::True, 0, 0), - qasm3::Token(qasm3::Token::Kind::False, 0, 0), - qasm3::Token(qasm3::Token::Kind::LBracket, 0, 0), - qasm3::Token(qasm3::Token::Kind::RBracket, 0, 0), - qasm3::Token(qasm3::Token::Kind::LBrace, 0, 0), - qasm3::Token(qasm3::Token::Kind::RBrace, 0, 0), - qasm3::Token(qasm3::Token::Kind::LParen, 0, 0), - qasm3::Token(qasm3::Token::Kind::RParen, 0, 0), - qasm3::Token(qasm3::Token::Kind::Colon, 0, 0), - qasm3::Token(qasm3::Token::Kind::Semicolon, 0, 0), - qasm3::Token(qasm3::Token::Kind::Eof, 0, 0), - qasm3::Token(qasm3::Token::Kind::Dot, 0, 0), - qasm3::Token(qasm3::Token::Kind::Comma, 0, 0), - qasm3::Token(qasm3::Token::Kind::Equals, 0, 0), - qasm3::Token(qasm3::Token::Kind::Arrow, 0, 0), - qasm3::Token(qasm3::Token::Kind::Plus, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoublePlus, 0, 0), - qasm3::Token(qasm3::Token::Kind::Minus, 0, 0), - qasm3::Token(qasm3::Token::Kind::Asterisk, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoubleAsterisk, 0, 0), - qasm3::Token(qasm3::Token::Kind::Slash, 0, 0), - qasm3::Token(qasm3::Token::Kind::Percent, 0, 0), - qasm3::Token(qasm3::Token::Kind::Pipe, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoublePipe, 0, 0), - qasm3::Token(qasm3::Token::Kind::Ampersand, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoubleAmpersand, 0, 0), - qasm3::Token(qasm3::Token::Kind::Caret, 0, 0), - qasm3::Token(qasm3::Token::Kind::At, 0, 0), - qasm3::Token(qasm3::Token::Kind::Tilde, 0, 0), - qasm3::Token(qasm3::Token::Kind::ExclamationPoint, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoubleEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::NotEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::PlusEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::MinusEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::AsteriskEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::SlashEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::AmpersandEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::PipeEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::TildeEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::CaretEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::LeftShitEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::RightShiftEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::PercentEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoubleAsteriskEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::LessThan, 0, 0), - qasm3::Token(qasm3::Token::Kind::LessThanEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::GreaterThan, 0, 0), - qasm3::Token(qasm3::Token::Kind::GreaterThanEquals, 0, 0), - qasm3::Token(qasm3::Token::Kind::LeftShift, 0, 0), - qasm3::Token(qasm3::Token::Kind::RightShift, 0, 0), - qasm3::Token(qasm3::Token::Kind::Imag, 0, 0), - qasm3::Token(qasm3::Token::Kind::Underscore, 0, 0), - qasm3::Token(qasm3::Token::Kind::DoubleQuote, 0, 0), - qasm3::Token(qasm3::Token::Kind::SingleQuote, 0, 0), - qasm3::Token(qasm3::Token::Kind::BackSlash, 0, 0), - qasm3::Token(qasm3::Token::Kind::Identifier, 0, 0, "qubit"), - qasm3::Token(qasm3::Token::Kind::HardwareQubit, 0, 0), - qasm3::Token(qasm3::Token::Kind::StringLiteral, 0, 0, "hello, world"), - qasm3::Token(qasm3::Token::Kind::IntegerLiteral, 0, 0), - qasm3::Token(qasm3::Token::Kind::FloatLiteral, 0, 0), - qasm3::Token(qasm3::Token::Kind::TimingLiteral, 0, 0), - qasm3::Token(qasm3::Token::Kind::Sin, 0, 0), - qasm3::Token(qasm3::Token::Kind::Cos, 0, 0), - qasm3::Token(qasm3::Token::Kind::Tan, 0, 0), - qasm3::Token(qasm3::Token::Kind::Exp, 0, 0), - qasm3::Token(qasm3::Token::Kind::Ln, 0, 0), - qasm3::Token(qasm3::Token::Kind::Sqrt, 0, 0), - qasm3::Token(qasm3::Token::Kind::InitialLayout, 0, 0, "i 0 1 2 3"), - qasm3::Token(qasm3::Token::Kind::OutputPermutation, 0, 0, "o 0 1 2 3"), - }; - - // Print all tokens. - std::stringstream ss{}; - for (const auto& token : tokens) { - ss << token << "\n"; - } - - // We expect all tokens to look like this. - // These are just all tokens joined by a newline. - const std::string expected = - "None\n" - "OPENQASM\n" - "include\n" - "DefCalGrammar\n" - "Def\n" - "Cal\n" - "DefCal\n" - "gate\n" - "opaque\n" - "extern\n" - "box\n" - "let\n" - "break\n" - "continue\n" - "if\n" - "else\n" - "end\n" - "return\n" - "for\n" - "while\n" - "in\n" - "pragma\n" - "input\n" - "output\n" - "const\n" - "readOnly\n" - "mutable\n" - "qreg\n" - "qubit\n" - "cReg\n" - "bool\n" - "bit\n" - "int\n" - "uint\n" - "float\n" - "angle\n" - "complex\n" - "array\n" - "void\n" - "duration\n" - "stretch\n" - "gphase\n" - "inv\n" - "pow\n" - "ctrl\n" - "negCtrl\n" - "#dim\n" - "durationof\n" - "delay\n" - "reset\n" - "measure\n" - "barrier\n" - "true\n" - "false\n" - "[\n" - "]\n" - "{\n" - "}\n" - "(\n" - ")\n" - ":\n" - ";\n" - "Eof\n" - ".\n" - ",\n" - "=\n" - "->\n" - "+\n" - "++\n" - "-\n" - "*\n" - "**\n" - "/\n" - "%\n" - "|\n" - "||\n" - "&\n" - "&&\n" - "^\n" - "@\n" - "~\n" - "!\n" - "==\n" - "!=\n" - "+=\n" - "-=\n" - "*=\n" - "/=\n" - "&=\n" - "|=\n" - "~=\n" - "^=\n" - "<<=\n" - ">>=\n" - "%=\n" - "**=\n" - "<\n" - "<=\n" - ">\n" - ">=\n" - "<<\n" - ">>\n" - "imag\n" - "underscore\n" - "\"\n" - "'\n" - "\\\n" - // These tokens are not keywords, but have a value associated - "Identifier (qubit)\n" - "HardwareQubit\n" - "StringLiteral (\"hello, world\")\n" - "IntegerLiteral (0)\n" - "FloatLiteral (0)\n" - "TimingLiteral (0 [s])\n" - "sin\n" - "cos\n" - "tan\n" - "exp\n" - "ln\n" - "sqrt\n" - "InitialLayout (i 0 1 2 3)\n" - "OutputPermutation (o 0 1 2 3)\n"; - - // Now we check if they are the same. - EXPECT_EQ(ss.str(), expected); -} - -TEST_F(Qasm3ParserTest, TestConstEval) { - qasm3::const_eval::ConstEvalPass constEvalPass{}; - - // Test constant eval. - // The first element of the pair is the expression to be evaluated, the second - // element is the expected result. - const auto inputs = std::vector, - qasm3::const_eval::ConstEvalValue>>{ - // integer unsigned - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Power, - std::make_shared(2, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(4, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(3, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Subtract, - std::make_shared(5, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(3, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Multiply, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(2, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Divide, - std::make_shared(6, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(3, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Modulo, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(1, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LeftShift, - std::make_shared(2, false), - std::make_shared(1, false)), - qasm3::const_eval::ConstEvalValue(4, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::RightShift, - std::make_shared(2, false), - std::make_shared(1, false)), - qasm3::const_eval::ConstEvalValue(1, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThan, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThanOrEqual, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThan, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThanOrEqual, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Equal, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::NotEqual, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseAnd, - std::make_shared(1, false), - std::make_shared(3, false)), - qasm3::const_eval::ConstEvalValue(1, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseXor, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(3, false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseOr, - std::make_shared(1, false), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(3, false)}, - - // integer signed - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Power, - std::make_shared(2, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(4, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(3, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Subtract, - std::make_shared(5, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(3, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Multiply, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(2, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Divide, - std::make_shared(6, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(3, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Modulo, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(1, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LeftShift, - std::make_shared(2, true), - std::make_shared(1, true)), - qasm3::const_eval::ConstEvalValue(4, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::RightShift, - std::make_shared(2, true), - std::make_shared(1, true)), - qasm3::const_eval::ConstEvalValue(1, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThan, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThanOrEqual, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThan, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThanOrEqual, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Equal, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::NotEqual, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseAnd, - std::make_shared(1, true), - std::make_shared(3, true)), - qasm3::const_eval::ConstEvalValue(1, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseXor, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(3, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseOr, - std::make_shared(1, true), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(3, true)}, - - // float - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Power, - std::make_shared(2.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(4.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(3.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Subtract, - std::make_shared(5.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(3.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Multiply, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(2.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Divide, - std::make_shared(6.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(3.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Modulo, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(1.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThan, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LessThanOrEqual, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThan, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::GreaterThanOrEqual, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Equal, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::NotEqual, - std::make_shared(1.0), - std::make_shared(2.0)), - qasm3::const_eval::ConstEvalValue(true)}, - - // bool - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Equal, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::NotEqual, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseAnd, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseXor, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::BitwiseOr, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LogicalAnd, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(false)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::LogicalOr, - std::make_shared(true), - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(true)}, - - // coercion - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(2, true), - std::make_shared(3.0)), - qasm3::const_eval::ConstEvalValue(5.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(2, false), - std::make_shared(3.0)), - qasm3::const_eval::ConstEvalValue(5.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Subtract, - std::make_shared(6U, false), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(4, true)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(3.0), - std::make_shared(2, true)), - qasm3::const_eval::ConstEvalValue(5.0)}, - std::pair{std::make_shared( - qasm3::BinaryExpression::Op::Add, - std::make_shared(3.0), - std::make_shared(2, false)), - qasm3::const_eval::ConstEvalValue(5.0)}, - - // unary expr - std::pair{ - std::make_shared( - qasm3::UnaryExpression::Op::BitwiseNot, - std::make_shared(0xFFFF'FFFF'FFFF'FFFF, false)), - qasm3::const_eval::ConstEvalValue(0, false)}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::LogicalNot, - std::make_shared(false)), - qasm3::const_eval::ConstEvalValue(true)}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Negate, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(-1.0)}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Negate, - std::make_shared(1, true)), - qasm3::const_eval::ConstEvalValue(-1, true)}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Sin, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::sin(1.0))}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Cos, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::cos(1.0))}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Tan, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::tan(1.0))}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Exp, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::numbers::e)}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Ln, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::log(1.0))}, - std::pair{std::make_shared( - qasm3::UnaryExpression::Op::Sqrt, - std::make_shared(1.0)), - qasm3::const_eval::ConstEvalValue(std::sqrt(1.0))}, - - }; - - for (const auto& [expr, expected] : inputs) { - auto result = constEvalPass.visit(expr); - EXPECT_TRUE(result.has_value()); - EXPECT_EQ(result, expected); - } -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralSeconds) { - qasm3::Scanner scanner(new std::istringstream("1.0s")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralMilliseconds) { - qasm3::Scanner scanner(new std::istringstream("1.0ms")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0e-3); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralMicroseconds) { - qasm3::Scanner scanner(new std::istringstream("1.0us")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0e-6); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralNanoseconds) { - qasm3::Scanner scanner(new std::istringstream("1.0ns")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0e-9); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralPicoseconds) { - qasm3::Scanner scanner(new std::istringstream("1.0ps")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0e-12); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralDoubleSuffix) { - qasm3::Scanner scanner(new std::istringstream("1.0dt")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralInvalidSuffix) { - qasm3::Scanner scanner(new std::istringstream("1.0xs")); - const auto token = scanner.next(); - EXPECT_NE(token.kind, qasm3::Token::Kind::TimingLiteral); -} - -TEST_F(Qasm3ParserTest, TokenKindTimingLiteralMicrosecondsInteger) { - qasm3::Scanner scanner(new std::istringstream("1us")); - const auto token = scanner.next(); - EXPECT_EQ(token.kind, qasm3::Token::Kind::TimingLiteral); - EXPECT_DOUBLE_EQ(token.valReal, 1.0e-6); -} - -TEST_F(Qasm3ParserTest, LargeLayoutParsing) { - const auto qc = QuantumComputation(1024); - const auto qasm = qc.toQASM(); - const auto qc2 = qasm3::Importer::imports(qasm); - EXPECT_EQ(qc, qc2); -} diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp deleted file mode 100644 index e14ff90563..0000000000 --- a/test/ir/test_qfr_functionality.cpp +++ /dev/null @@ -1,1525 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/Permutation.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/Operation.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" -#include "qasm3/Importer.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace qc { - -namespace { - -void printRegisters(const QuantumComputation& qc) { - for (const auto& [name, reg] : qc.getQuantumRegisters()) { - std::cout << "QuantumRegister(name=" << name - << ", start=" << reg.getStartIndex() << ", size=" << reg.getSize() - << ")\n"; - } - for (const auto& [name, reg] : qc.getClassicalRegisters()) { - std::cout << "ClassicalRegister(name=" << name - << ", start=" << reg.getStartIndex() << ", size=" << reg.getSize() - << ")\n"; - } - for (const auto& [name, reg] : qc.getAncillaRegisters()) { - std::cout << "AncillaRegister(name=" << name - << ", start=" << reg.getStartIndex() << ", size=" << reg.getSize() - << ")\n"; - } -} - -class QFRFunctionality : public testing::TestWithParam { -protected: - void SetUp() override { - std::array - randomData{}; - std::random_device rd; - std::ranges::generate(randomData, [&]() { return rd(); }); - std::seed_seq seeds(begin(randomData), end(randomData)); - mt.seed(seeds); - dist = std::uniform_real_distribution(0.0, 2 * PI); - } - - std::mt19937_64 mt; - std::uniform_real_distribution dist; -}; - -} // namespace - -TEST_F(QFRFunctionality, removeTrailingIdleQubits) { - const std::size_t nqubits = 4; - QuantumComputation qc(nqubits, nqubits); - qc.x(0); - qc.x(2); - std::cout << qc; - printRegisters(qc); - - qc.outputPermutation.erase(1); - qc.outputPermutation.erase(3); - - qc.stripIdleQubits(); - EXPECT_EQ(qc.getNqubits(), 2); - std::cout << qc; - printRegisters(qc); - - qc.pop_back(); - qc.outputPermutation.erase(2); - std::cout << qc; - printRegisters(qc); - - qc.stripIdleQubits(); - EXPECT_EQ(qc.getNqubits(), 1); -} - -TEST_F(QFRFunctionality, ancillaryQubitAtEnd) { - const std::size_t nqubits = 2; - QuantumComputation qc(nqubits); - qc.x(0); - qc.addAncillaryRegister(1); - EXPECT_EQ(qc.getNancillae(), 1); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), nqubits); - EXPECT_EQ(qc.getNqubits(), 3); - qc.x(2); - printRegisters(qc); - auto p = qc.removeQubit(2); - EXPECT_EQ(p.first, nqubits); - EXPECT_EQ(p.second, nqubits); - EXPECT_EQ(qc.getNancillae(), 0); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), nqubits); - EXPECT_EQ(qc.getNqubits(), nqubits); - EXPECT_TRUE(qc.getAncillaRegisters().empty()); - printRegisters(qc); - qc.addAncillaryQubit(p.first, p.second); - EXPECT_EQ(qc.getNancillae(), 1); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), nqubits); - EXPECT_EQ(qc.getNqubits(), nqubits + 1); - EXPECT_FALSE(qc.getAncillaRegisters().empty()); - printRegisters(qc); - auto q = qc.removeQubit(2); - EXPECT_EQ(q.first, nqubits); - EXPECT_EQ(q.second, nqubits); - EXPECT_EQ(qc.getNancillae(), 0); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), nqubits); - EXPECT_EQ(qc.getNqubits(), nqubits); - EXPECT_TRUE(qc.getAncillaRegisters().empty()); - printRegisters(qc); - auto rm = qc.removeQubit(1); - EXPECT_EQ(rm.first, 1); - EXPECT_EQ(rm.second, 1); - EXPECT_EQ(qc.getNancillae(), 0); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), 1); - EXPECT_EQ(qc.getNqubits(), 1); - printRegisters(qc); - auto empty = qc.removeQubit(0); - EXPECT_EQ(empty.first, 0); - EXPECT_EQ(empty.second, 0); - EXPECT_EQ(qc.getNancillae(), 0); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), 0); - EXPECT_EQ(qc.getNqubits(), 0); - EXPECT_TRUE(qc.getQuantumRegisters().empty()); - printRegisters(qc); -} - -TEST_F(QFRFunctionality, ancillaryQubitRemoveMiddle) { - const std::size_t nqubits = 2; - QuantumComputation qc(nqubits); - qc.x(0); - qc.addAncillaryRegister(3); - auto p = qc.removeQubit(3); - EXPECT_EQ(p.first, 3); - EXPECT_EQ(p.second, 3); - EXPECT_EQ(qc.getNancillae(), 2); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), 2); - EXPECT_EQ(qc.getNqubits(), 4); - printRegisters(qc); -} - -TEST_F(QFRFunctionality, splitQreg) { - const std::size_t nqubits = 3; - QuantumComputation qc(nqubits); - qc.x(0); - auto p = qc.removeQubit(1); - EXPECT_EQ(p.first, 1); - EXPECT_EQ(p.second, 1); - EXPECT_EQ(qc.getNancillae(), 0); - EXPECT_EQ(qc.getNqubitsWithoutAncillae(), 2); - EXPECT_EQ(qc.getNqubits(), 2); - printRegisters(qc); -} - -TEST_F(QFRFunctionality, StripIdleAndDump) { - const std::string testfile = "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[5];\n" - "creg c[3];\n" - "x q[0];\n" - "x q[2];\n" - "barrier q;\n" - "barrier q[0];\n" - "reset q;\n" - "reset q[2];\n" - "cx q[0],q[4];\n"; - - auto qc = qasm3::Importer::imports(testfile); - qc.print(std::cout); - qc.stripIdleQubits(); - qc.print(std::cout); - std::stringstream goal{}; - qc.print(goal); - std::stringstream test{}; - qc.dumpOpenQASM(test, false); - std::cout << test.str() << "\n"; - qc.reset(); - qc = qasm3::Importer::import(test); - qc.print(std::cout); - qc.stripIdleQubits(); - qc.print(std::cout); - std::stringstream actual{}; - qc.print(actual); - EXPECT_EQ(goal.str(), actual.str()); -} - -TEST_F(QFRFunctionality, gateShortCutsAndCloning) { - // This test checks if the gate shortcuts are working correctly - // and if the cloning of gates is working correctly. - // To this end, we create a circuit with every possible gate in the following - // variants: - // - without controls, - // - with a single control, - // - with multiple controls. - // Then, we clone the circuit and check if the resulting circuit contains the - // same number of gates. - QuantumComputation qc(5, 5); - qc.gphase(PI); - qc.i(0); - qc.ci(1, 0); - qc.mci({1, 2_nc}, 0); - qc.h(0); - qc.ch(1, 0); - qc.mch({1, 2_nc}, 0); - qc.x(0); - qc.cx(1, 0); - qc.mcx({1, 2_nc}, 0); - qc.y(0); - qc.cy(1, 0); - qc.mcy({1, 2_nc}, 0); - qc.z(0); - qc.cz(1, 0); - qc.mcz({1, 2_nc}, 0); - qc.s(0); - qc.cs(1, 0); - qc.mcs({1, 2_nc}, 0); - qc.sdg(0); - qc.csdg(1, 0); - qc.mcsdg({1, 2_nc}, 0); - qc.t(0); - qc.ct(1, 0); - qc.mct({1, 2_nc}, 0); - qc.tdg(0); - qc.ctdg(1, 0); - qc.mctdg({1, 2_nc}, 0); - qc.v(0); - qc.cv(1, 0); - qc.mcv({1, 2_nc}, 0); - qc.vdg(0); - qc.cvdg(1, 0); - qc.mcvdg({1, 2_nc}, 0); - qc.u(PI, PI, PI, 0); - qc.cu(PI, PI, PI, 1, 0); - qc.mcu(PI, PI, PI, {1, 2_nc}, 0); - qc.u2(PI, PI, 0); - qc.cu2(PI, PI, 1, 0); - qc.mcu2(PI, PI, {1, 2_nc}, 0); - qc.p(PI, 0); - qc.cp(PI, 1, 0); - qc.mcp(PI, {1, 2_nc}, 0); - qc.sx(0); - qc.csx(1, 0); - qc.mcsx({1, 2_nc}, 0); - qc.sxdg(0); - qc.csxdg(1, 0); - qc.mcsxdg({1, 2_nc}, 0); - qc.r(PI, PI, 0); - qc.cr(PI, PI, 1, 0); - qc.mcr(PI, PI, {1, 2_nc}, 0); - qc.rx(PI, 0); - qc.crx(PI, 1, 0); - qc.mcrx(PI, {1, 2_nc}, 0); - qc.ry(PI, 0); - qc.cry(PI, 1, 0); - qc.mcry(PI, {1, 2_nc}, 0); - qc.rz(PI, 0); - qc.crz(PI, 1, 0); - qc.mcrz(PI, {1, 2_nc}, 0); - qc.swap(0, 1); - qc.cswap(2, 0, 1); - qc.mcswap({2, 3_nc}, 0, 1); - qc.iswap(0, 1); - qc.ciswap(2, 0, 1); - qc.mciswap({2, 3_nc}, 0, 1); - qc.iswapdg(0, 1); - qc.ciswapdg(2, 0, 1); - qc.mciswapdg({2, 3_nc}, 0, 1); - qc.peres(0, 1); - qc.cperes(2, 0, 1); - qc.mcperes({2, 3_nc}, 0, 1); - qc.peresdg(0, 1); - qc.cperesdg(2, 0, 1); - qc.mcperesdg({2, 3_nc}, 0, 1); - qc.dcx(0, 1); - qc.cdcx(2, 0, 1); - qc.mcdcx({2, 3_nc}, 0, 1); - qc.ecr(0, 1); - qc.cecr(2, 0, 1); - qc.mcecr({2, 3_nc}, 0, 1); - qc.rxx(PI, 0, 1); - qc.crxx(PI, 2, 0, 1); - qc.mcrxx(PI, {2, 3_nc}, 0, 1); - qc.ryy(PI, 0, 1); - qc.cryy(PI, 2, 0, 1); - qc.mcryy(PI, {2, 3_nc}, 0, 1); - qc.rzz(PI, 0, 1); - qc.crzz(PI, 2, 0, 1); - qc.mcrzz(PI, {2, 3_nc}, 0, 1); - qc.rzx(PI, 0, 1); - qc.crzx(PI, 2, 0, 1); - qc.mcrzx(PI, {2, 3_nc}, 0, 1); - qc.xx_minus_yy(PI, PI, 0, 1); - qc.cxx_minus_yy(PI, PI, 2, 0, 1); - qc.mcxx_minus_yy(PI, PI, {2, 3_nc}, 0, 1); - qc.xx_plus_yy(PI, PI, 0, 1); - qc.cxx_plus_yy(PI, PI, 2, 0, 1); - qc.mcxx_plus_yy(PI, PI, {2, 3_nc}, 0, 1); - qc.rccx(0, 1, 2); - qc.crccx(3_nc, 0, 1, 2); - qc.mcrccx({3_nc, 4}, 0, 1, 2); - qc.measure(0, 0); - qc.measure({1, 2}, {1, 2}); - qc.barrier(0); - qc.barrier({1, 2}); - qc.reset(0); - qc.reset({1, 2}); - - const auto qcCloned = qc; - ASSERT_EQ(qc.size(), qcCloned.size()); - ASSERT_EQ(qcCloned.getGlobalPhase(), PI); -} - -TEST_F(QFRFunctionality, cloningDifferentOperations) { - const std::size_t nqubits = 5; - QuantumComputation qc(nqubits, nqubits); - QuantumComputation comp(nqubits); - comp.barrier(0); - comp.h(0); - qc.emplace_back(comp.asOperation()); - auto creg = qc.getClassicalRegisters().at("c"); - qc.if_(X, 0, creg, 1U); - - const auto qcCloned = qc; - ASSERT_EQ(qc.size(), qcCloned.size()); -} - -TEST_F(QFRFunctionality, wrongRegisterSizes) { - const std::size_t nqubits = 5; - QuantumComputation qc(nqubits); - ASSERT_THROW(qc.measure({0}, {1, 2}), std::runtime_error); -} - -TEST_F(QFRFunctionality, OperationEquality) { - const auto x = StandardOperation(0, X); - const auto z = StandardOperation(0, Z); - EXPECT_TRUE(x.equals(x)); - EXPECT_EQ(x, x); - EXPECT_FALSE(x.equals(z)); - EXPECT_NE(x, z); - - const auto x0 = StandardOperation(0, X); - const auto x1 = StandardOperation(1, X); - EXPECT_FALSE(x0.equals(x1)); - EXPECT_NE(x0, x1); - const auto cx01 = StandardOperation(0, 1, X); - const auto cx10 = StandardOperation(1, 0, X); - EXPECT_FALSE(cx01.equals(cx10)); - EXPECT_NE(cx01, cx10); - EXPECT_FALSE(x0.equals(cx01)); - EXPECT_NE(x0, cx01); - - const auto p = StandardOperation(0, P, {2.0}); - const auto pm = StandardOperation(0, P, {-2.0}); - EXPECT_FALSE(p.equals(pm)); - EXPECT_NE(p, pm); - - const auto measure0 = NonUnitaryOperation(0, 0U); - const auto measure1 = NonUnitaryOperation(0, 1U); - const auto measure2 = NonUnitaryOperation(1, 0U); - EXPECT_FALSE(measure0.equals(x0)); - EXPECT_NE(measure0, x0); - EXPECT_TRUE(measure0.equals(measure0)); - EXPECT_EQ(measure0, measure0); - EXPECT_FALSE(measure0.equals(measure1)); - EXPECT_NE(measure0, measure1); - EXPECT_FALSE(measure0.equals(measure2)); - EXPECT_NE(measure0, measure2); - - std::unique_ptr xp0 = std::make_unique(0, X); - std::unique_ptr xp1 = std::make_unique(0, X); - std::unique_ptr xp2 = std::make_unique(0, X); - const auto expectedValue0 = false; - const auto expectedValue1 = true; - const auto ifElse0 = - IfElseOperation(std::move(xp0), nullptr, 0, expectedValue0); - const auto ifElse1 = - IfElseOperation(std::move(xp1), nullptr, 0, expectedValue1); - const auto ifElse2 = - IfElseOperation(std::move(xp2), nullptr, 1, expectedValue0); - std::unique_ptr zp = std::make_unique(0, Z); - const auto ifElse3 = - IfElseOperation(std::move(zp), nullptr, 0, expectedValue0); - EXPECT_FALSE(ifElse0.equals(x)); - EXPECT_NE(ifElse0, x); - EXPECT_TRUE(ifElse0.equals(ifElse0)); - EXPECT_EQ(ifElse0, ifElse0); - EXPECT_FALSE(ifElse0.equals(ifElse1)); - EXPECT_NE(ifElse0, ifElse1); - EXPECT_FALSE(ifElse0.equals(ifElse2)); - EXPECT_NE(ifElse0, ifElse2); - EXPECT_FALSE(ifElse0.equals(ifElse3)); - EXPECT_NE(ifElse0, ifElse3); - - auto compound0 = CompoundOperation(); - compound0.emplace_back(0, X); - - auto compound1 = CompoundOperation(); - compound1.emplace_back(0, X); - compound1.emplace_back(0, Z); - - auto compound2 = CompoundOperation(); - compound2.emplace_back(0, Z); - - EXPECT_FALSE(compound0.equals(x)); - EXPECT_NE(compound0, x); - EXPECT_TRUE(compound0.equals(compound0)); - EXPECT_EQ(compound0, compound0); - EXPECT_FALSE(compound0.equals(compound1)); - EXPECT_NE(compound0, compound1); - EXPECT_FALSE(compound0.equals(compound2)); - EXPECT_NE(compound0, compound2); -} - -TEST_F(QFRFunctionality, IndexOutOfRange) { - QuantumComputation qc(2); - Permutation layout{}; - layout[0] = 0; - layout[2] = 1; - qc.initialLayout = layout; - qc.x(0); - - EXPECT_THROW(qc.x(1), std::out_of_range); - EXPECT_THROW(qc.cx(1_nc, 0), std::out_of_range); - EXPECT_THROW(qc.mcx({2_nc, 1_nc}, 0), std::out_of_range); - EXPECT_THROW(qc.swap(0, 1), std::out_of_range); - EXPECT_THROW(qc.cswap(1_nc, 0, 2), std::out_of_range); - EXPECT_THROW(qc.reset({0, 1, 2}), std::out_of_range); -} - -TEST_F(QFRFunctionality, ContainsLogicalQubit) { - const QuantumComputation qc(2); - const auto [contains0, index0] = qc.containsLogicalQubit(0); - EXPECT_TRUE(contains0); - const auto hasValue0 = index0.has_value(); - ASSERT_TRUE(hasValue0); - if (hasValue0) { - EXPECT_EQ(*index0, 0); - } - const auto [contains1, index1] = qc.containsLogicalQubit(1); - EXPECT_TRUE(contains1); - const auto hasValue1 = index1.has_value(); - ASSERT_TRUE(hasValue1); - if (hasValue1) { - EXPECT_EQ(*index1, 1); - } - const auto [contains2, index2] = qc.containsLogicalQubit(2); - EXPECT_FALSE(contains2); - EXPECT_FALSE(index2.has_value()); -} - -TEST_F(QFRFunctionality, AddAncillaryQubits) { - QuantumComputation qc(1); - qc.addAncillaryQubit(1, std::nullopt); - EXPECT_EQ(qc.getNqubits(), 2); - EXPECT_EQ(qc.getNancillae(), 1); - ASSERT_EQ(qc.getAncillary().size(), 2U); - ASSERT_EQ(qc.getGarbage().size(), 2U); - EXPECT_FALSE(qc.getAncillary()[0]); - EXPECT_TRUE(qc.getAncillary()[1]); - EXPECT_FALSE(qc.getGarbage()[0]); - EXPECT_TRUE(qc.getGarbage()[1]); -} - -TEST_F(QFRFunctionality, CircuitDepthEmptyCircuit) { - const QuantumComputation qc(2); - EXPECT_EQ(qc.getDepth(), 0U); -} - -TEST_F(QFRFunctionality, CircuitDepthStandardOperations) { - QuantumComputation qc(2); - qc.h(0); - qc.h(1); - qc.cx(1, 0); - - EXPECT_EQ(qc.getDepth(), 2U); -} - -TEST_F(QFRFunctionality, CircuitDepthNonUnitaryOperations) { - QuantumComputation qc(2U, 2U); - qc.h(0); - qc.h(1); - qc.cx(1, 0); - qc.barrier({0, 1}); - qc.measure(0, 0); - qc.measure(1, 1); - - EXPECT_EQ(qc.getDepth(), 3U); -} - -// Test with compound operation -TEST_F(QFRFunctionality, CircuitDepthCompoundOperation) { - QuantumComputation comp(2); - comp.h(0); - comp.h(1); - comp.cx(1, 0); - - QuantumComputation qc(2); - qc.emplace_back(comp.asOperation()); - - EXPECT_EQ(qc.getDepth(), 2U); -} - -TEST_F(QFRFunctionality, CircuitToOperation) { - QuantumComputation qc(2U, 2U); - EXPECT_EQ(qc.asOperation(), nullptr); - qc.x(0); - const auto& op = qc.asOperation(); - ASSERT_NE(op, nullptr); - EXPECT_EQ(op->getType(), X); - EXPECT_EQ(op->getNcontrols(), 0U); - EXPECT_EQ(op->getTargets().front(), 0U); - EXPECT_TRUE(qc.empty()); - qc.x(0); - qc.h(0); - qc.if_(X, 0, 1, {0U, 1U}, 1U); - const auto& op2 = qc.asOperation(); - ASSERT_NE(op2, nullptr); - EXPECT_EQ(op2->getType(), Compound); - EXPECT_TRUE(qc.empty()); -} - -TEST_F(QFRFunctionality, AvoidStrippingIdleQubitWhenInOutputPermutation) { - // a qubit being present in the output permutation should not be stripped - QuantumComputation qc(2U, 2U); - qc.measure(1, 0); - qc.initializeIOMapping(); - qc.stripIdleQubits(); - EXPECT_EQ(qc.getNqubits(), 2U); - EXPECT_EQ(qc.outputPermutation[1], 0U); -} - -TEST_F(QFRFunctionality, InitializeIOMappingRejectsMissingMeasuredDeviceQubit) { - QuantumComputation qc(3U, 2U); - qc.measure(0, 1); - qc.measure(1, 0); - qc.setLogicalQubitGarbage(1); - const auto outputPermutation = qc.outputPermutation; - - try { - qc.initializeIOMapping(); - FAIL() << "Expected an invalid_argument exception."; - } catch (const std::invalid_argument& e) { - EXPECT_STREQ( - e.what(), - "[initializeIOMapping] Measured device qubit 1 is missing from the " - "output permutation. A non-empty output permutation must contain every " - "measured device qubit. Set a consistent output permutation or clear " - "it before initialization."); - } catch (...) { - FAIL() << "Expected an invalid_argument exception."; - } - - EXPECT_EQ(qc.outputPermutation, outputPermutation); -} - -TEST_F(QFRFunctionality, UpdateOutputPermutation) { - // Update output permutation if swap gate was applied even if physical qubit - // index matches logical qubit index - QuantumComputation qc(5U, 3U); - qc.h(0); - - // Swap qubits 2 and 3 - qc.swap(2, 3); - - qc.cx(1, 0); - qc.cx(1, 3); - qc.initialLayout[1] = 0; - qc.initialLayout[0] = 1; - qc.initialLayout[3] = 2; - qc.initialLayout[2] = 3; - qc.initialLayout[4] = 4; - qc.measure(1, 0); - qc.measure(0, 1); - qc.measure(2, 2); - qc.initializeIOMapping(); - - // Check that output permutation is equal to initialLayout for the measured - // qubits, except for the swap between qubits 2 and 3 - EXPECT_EQ(qc.outputPermutation[1], qc.initialLayout[1]); - EXPECT_EQ(qc.outputPermutation[0], qc.initialLayout[0]); - EXPECT_EQ(qc.outputPermutation[2], qc.initialLayout[3]); -} - -TEST_F(QFRFunctionality, RzAndPhaseDifference) { - const std::string qasm = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 2.0;\n" - "include \"qelib1.inc\";\n" - "qreg q[2];\n" - "rz(1/8) q[0];\n" - "p(1/8) q[1];\n" - "crz(1/8) q[0],q[1];\n" - "cp(1/8) q[0],q[1];\n"; - auto qc = qasm3::Importer::imports(qasm); - std::cout << qc << "\n"; - std::stringstream oss; - qc.dumpOpenQASM(oss, false); -} - -TEST_F(QFRFunctionality, U3toU2Gate) { - QuantumComputation qc(1); - qc.u(PI_2, 0., PI, 0); // H - qc.u(PI_2, 0., 0., 0); // RY(pi/2) - qc.u(PI_2, -PI_2, PI_2, 0); // V = RX(pi/2) - qc.u(PI_2, PI_2, -PI_2, 0); // Vdag = RX(-pi/2) - qc.u(PI_2, 0.25, 0.5, 0); // U2(0.25, 0.5) - std::cout << qc << "\n"; - EXPECT_EQ(qc.at(0)->getType(), H); - EXPECT_EQ(qc.at(1)->getType(), RY); - EXPECT_EQ(qc.at(1)->getParameter().at(0), PI_2); - EXPECT_EQ(qc.at(2)->getType(), V); - EXPECT_EQ(qc.at(3)->getType(), Vdg); - EXPECT_EQ(qc.at(4)->getType(), U2); - EXPECT_EQ(qc.at(4)->getParameter().at(0), 0.25); - EXPECT_EQ(qc.at(4)->getParameter().at(1), 0.5); -} - -TEST_F(QFRFunctionality, U3toU1Gate) { - QuantumComputation qc(1); - qc.u(0., 0., 0., 0); // I - qc.u(0., 0., PI, 0); // Z - qc.u(0., 0., PI_2, 0); // S - qc.u(0., 0., -PI_2, 0); // Sdg - qc.u(0., 0., PI_4, 0); // T - qc.u(0., 0., -PI_4, 0); // Tdg - qc.u(0., 0., 0.5, 0); // p(0.5) - - std::cout << qc << "\n"; - EXPECT_EQ(qc.at(0)->getType(), I); - EXPECT_EQ(qc.at(1)->getType(), Z); - EXPECT_EQ(qc.at(2)->getType(), S); - EXPECT_EQ(qc.at(3)->getType(), Sdg); - EXPECT_EQ(qc.at(4)->getType(), T); - EXPECT_EQ(qc.at(5)->getType(), Tdg); - EXPECT_EQ(qc.at(6)->getType(), P); - EXPECT_EQ(qc.at(6)->getParameter().at(0), 0.5); -} - -TEST_F(QFRFunctionality, U3SpecialCases) { - QuantumComputation qc(1); - qc.u(0.5, 0., 0., 0); // RY(0.5) - qc.u(0.5, -PI_2, PI_2, 0); // RX(0.5) - qc.u(0.5, PI_2, -PI_2, 0); // RX(-0.5) - qc.u(PI, PI_2, PI_2, 0); // Y - qc.u(PI, 0., PI, 0); // X - qc.u(0.5, 0.25, 0.125, 0); // U3(0.5, 0.25, 0.125) - - std::cout << qc << "\n"; - EXPECT_EQ(qc.at(0)->getType(), RY); - EXPECT_EQ(qc.at(0)->getParameter().at(0), 0.5); - EXPECT_EQ(qc.at(1)->getType(), RX); - EXPECT_EQ(qc.at(1)->getParameter().at(0), 0.5); - EXPECT_EQ(qc.at(2)->getType(), RX); - EXPECT_EQ(qc.at(2)->getParameter().at(0), -0.5); - EXPECT_EQ(qc.at(3)->getType(), Y); - EXPECT_EQ(qc.at(4)->getType(), X); - EXPECT_EQ(qc.at(5)->getType(), U); - EXPECT_EQ(qc.at(5)->getParameter().at(0), 0.5); - EXPECT_EQ(qc.at(5)->getParameter().at(1), 0.25); - EXPECT_EQ(qc.at(5)->getParameter().at(2), 0.125); -} - -TEST_F(QFRFunctionality, GlobalPhaseNormalization) { - QuantumComputation qc(1); - EXPECT_EQ(qc.getGlobalPhase(), 0.); - qc.gphase(-PI); - EXPECT_EQ(qc.getGlobalPhase(), PI); - qc.gphase(PI); - EXPECT_EQ(qc.getGlobalPhase(), 0.); -} - -TEST_F(QFRFunctionality, OpNameToTypeSimple) { - EXPECT_EQ(OpType::X, opTypeFromString("x")); - EXPECT_EQ(OpType::Y, opTypeFromString("y")); - EXPECT_EQ(OpType::Z, opTypeFromString("z")); - - EXPECT_EQ(OpType::H, opTypeFromString("h")); - EXPECT_EQ(OpType::S, opTypeFromString("s")); - EXPECT_EQ(OpType::Sdg, opTypeFromString("sdg")); - EXPECT_EQ(OpType::T, opTypeFromString("t")); - EXPECT_EQ(OpType::Tdg, opTypeFromString("tdg")); - - EXPECT_EQ(OpType::X, opTypeFromString("cnot")); - - EXPECT_THROW([[maybe_unused]] const auto type = opTypeFromString("foo"), - std::invalid_argument); -} - -TEST_F(QFRFunctionality, addControlStandardOperation) { - auto op = StandardOperation(0, OpType::X); - op.addControl(1); - op.addControl(2); - ASSERT_EQ(op.getNcontrols(), 2); - const auto expectedControls = Controls{1U, 2U}; - EXPECT_EQ(op.getControls(), expectedControls); - op.removeControl(1); - const auto expectedControlsAfterRemove = Controls{2U}; - EXPECT_EQ(op.getControls(), expectedControlsAfterRemove); - op.clearControls(); - EXPECT_EQ(op.getNcontrols(), 0); - ASSERT_THROW(op.removeControl(1), std::runtime_error); - - op.addControl(1); - const auto& controls = op.getControls(); - EXPECT_EQ(op.removeControl(controls.begin()), controls.end()); -} - -TEST_F(QFRFunctionality, addControlSymbolicOperation) { - auto op = SymbolicOperation(0, OpType::X); - - op.addControl(1); - op.addControl(2); - - ASSERT_EQ(op.getNcontrols(), 2); - auto expectedControls = Controls{1U, 2U}; - EXPECT_EQ(op.getControls(), expectedControls); - op.removeControl(1); - auto expectedControlsAfterRemove = Controls{2U}; - EXPECT_EQ(op.getControls(), expectedControlsAfterRemove); - op.clearControls(); - EXPECT_EQ(op.getNcontrols(), 0); - - op.addControl(1); - const auto& controls = op.getControls(); - EXPECT_EQ(op.removeControl(controls.begin()), controls.end()); -} - -TEST_F(QFRFunctionality, addControlNonUnitaryOperation) { - auto op = NonUnitaryOperation(0U, Measure); - - EXPECT_THROW(op.addControl(1), std::runtime_error); - EXPECT_THROW(op.removeControl(1), std::runtime_error); - EXPECT_THROW(op.clearControls(), std::runtime_error); - // we pass an invalid iterator to removeControl, which is fine, since the - // function call should unconditionally trap - EXPECT_THROW(op.removeControl(Controls::const_iterator{}), - std::runtime_error); -} - -TEST_F(QFRFunctionality, addControlCompoundOperation) { - auto op = CompoundOperation(); - - auto control0 = 0U; - auto control1 = 1U; - - auto xOp = std::make_unique(Targets{1}, OpType::X); - auto cxOp = std::make_unique(Targets{3}, OpType::X); - cxOp->addControl(control1); - - op.emplace_back(xOp); - op.emplace_back(cxOp); - - op.addControl(control0); - - ASSERT_EQ(op.getOps()[0]->getNcontrols(), 1); - ASSERT_EQ(op.getOps()[1]->getNcontrols(), 2); - - op.clearControls(); - - ASSERT_EQ(op.getOps()[0]->getNcontrols(), 0); - ASSERT_EQ(op.getOps()[1]->getNcontrols(), 1); - ASSERT_EQ(*op.getOps()[1]->getControls().begin(), control1); - EXPECT_THROW(op.removeControl(control0), std::runtime_error); -} - -TEST_F(QFRFunctionality, addControlTwice) { - auto control = 0U; - - std::unique_ptr op = - std::make_unique(Targets{1}, OpType::X); - op->addControl(control); - EXPECT_THROW(op->addControl(control), std::runtime_error); - - auto symbolicOp = SymbolicOperation(Targets{1}, OpType::X); - symbolicOp.addControl(control); - EXPECT_THROW(symbolicOp.addControl(control), std::runtime_error); -} - -TEST_F(QFRFunctionality, addTargetAsControl) { - // Adding a control that is already a target - auto control = 1U; - - std::unique_ptr op = - std::make_unique(Targets{1}, OpType::X); - EXPECT_THROW(op->addControl(control), std::runtime_error); - - auto symbolicOp = SymbolicOperation(Targets{1}, OpType::X); - EXPECT_THROW(symbolicOp.addControl(control), std::runtime_error); -} - -TEST_F(QFRFunctionality, addControlCompoundOperationInvalid) { - auto op = CompoundOperation(); - - auto control1 = 1U; - - auto xOp = std::make_unique(Targets{1}, OpType::X); - auto cxOp = std::make_unique(Targets{3}, OpType::X); - cxOp->addControl(control1); - - op.emplace_back(xOp); - op.emplace_back(cxOp); - - ASSERT_THROW(op.addControl(control1), std::runtime_error); - ASSERT_THROW(op.addControl(Control{1}), std::runtime_error); -} - -TEST_F(QFRFunctionality, invertUnsupportedOperation) { - auto op = NonUnitaryOperation(0U, OpType::Measure); - - ASSERT_THROW(op.invert(), std::runtime_error); -} - -TEST_F(QFRFunctionality, invertStandardOpSelfInverting) { - const auto opTypes = { - OpType::I, OpType::X, OpType::Y, OpType::Z, OpType::H, OpType::SWAP, - }; - - for (auto opType : opTypes) { - auto op = StandardOperation(0U, opType); - op.invert(); - ASSERT_EQ(op.getType(), opType); - } -} - -TEST_F(QFRFunctionality, invertStandardOpInvertClone) { - auto op1 = StandardOperation(0U, S); - auto op2 = op1.getInverted(); - ASSERT_EQ(op1.getType(), S); - ASSERT_EQ(op2->getType(), Sdg); -} - -TEST_F(QFRFunctionality, invertStandardOpSpecial) { - const auto opTypes = { - std::pair{S, Sdg}, std::pair{T, Tdg}, std::pair{V, Vdg}, - std::pair{SX, SXdg}, std::pair{Peres, Peresdg}, std::pair{iSWAP, iSWAPdg}, - }; - - for (const auto& [opType, opTypeInv] : opTypes) { - auto op = StandardOperation(0U, opType); - op.invert(); - ASSERT_EQ(op.getType(), opTypeInv); - - auto op2 = StandardOperation(0U, opTypeInv); - op2.invert(); - ASSERT_EQ(op2.getType(), opType); - } -} - -TEST_F(QFRFunctionality, invertStandardOpParamChange) { - const auto cases = { - std::tuple{OpType::GPhase, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::P, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::R, std::vector{1, 2}, std::vector{-1, 2}}, - std::tuple{OpType::RX, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RY, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RZ, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RXX, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RYY, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RZZ, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::RZX, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::U2, std::vector{1, 1}, - std::vector{-1 + PI, -1 - PI}}, - std::tuple{OpType::U, std::vector{1, 2, 3}, - std::vector{-1, -3, -2}}, - std::tuple{OpType::XXminusYY, std::vector{1}, std::vector{-1}}, - std::tuple{OpType::XXplusYY, std::vector{1}, std::vector{-1}}, - }; - - for (const auto& testcase : cases) { - auto op = - StandardOperation(0U, std::get<0>(testcase), std::get<1>(testcase)); - op.invert(); - ASSERT_EQ(op.getParameter(), std::get<2>(testcase)); - } - - auto op = StandardOperation(Targets{0U, 1U}, OpType::DCX); - op.invert(); - const auto expectedTargets = Targets{1U, 0U}; - ASSERT_EQ(op.getTargets(), expectedTargets); - - auto rccxOp = StandardOperation(Targets{0U, 1U, 2U}, OpType::RCCX); - rccxOp.invert(); - ASSERT_EQ(rccxOp.getType(), OpType::RCCX); - ASSERT_EQ(rccxOp.getTargets(), (Targets{0U, 1U, 2U})); -} - -TEST_F(QFRFunctionality, invertCompoundOperation) { - auto op = CompoundOperation(); - - op.emplace_back(0U, OpType::X); - op.emplace_back(1U, OpType::RZ, std::vector{1}); - op.emplace_back(1U, OpType::S); - - op.invert(); - - ASSERT_EQ(op.getOps()[0]->getType(), OpType::Sdg); - ASSERT_EQ(op.getOps()[1]->getType(), OpType::RZ); - ASSERT_EQ(op.getOps()[1]->getParameter(), std::vector{-1}); - ASSERT_EQ(op.getOps()[2]->getType(), OpType::X); -} - -TEST_F(QFRFunctionality, invertSymbolicOpParamChange) { - auto x = sym::Variable("x"); - auto y = sym::Variable("y"); - const auto cases = { - std::tuple{OpType::GPhase, std::vector{Symbolic({x})}, - std::vector{-Symbolic({x})}}, - std::tuple{OpType::GPhase, std::vector{1.0}, - std::vector{-1.0}}, - std::tuple{OpType::U2, std::vector{Symbolic({x}), 1.0}, - std::vector{-1.0 + PI, -Symbolic({x}) - PI}}, - std::tuple{OpType::R, std::vector{Symbolic({x}), 2.0}, - std::vector{-Symbolic({x}), 2.0}}, - std::tuple{ - OpType::U, - std::vector{Symbolic({x}), 2.0, Symbolic({y})}, - std::vector{-Symbolic({x}), -Symbolic({y}), -2.0}}, - std::tuple{OpType::XXminusYY, std::vector{Symbolic({x})}, - std::vector{-Symbolic({x})}}, - std::tuple{OpType::XXplusYY, std::vector{1.0}, - std::vector{-1.0}}, - }; - - for (const auto& testcase : cases) { - auto op = - SymbolicOperation(0U, std::get<0>(testcase), std::get<1>(testcase)); - op.invert(); - - for (size_t i = 0; i < std::get<1>(testcase).size(); ++i) { - ASSERT_EQ(op.getParameter(i), std::get<2>(testcase)[i]); - } - } - - // The following gate should be handled by the StandardOperation function - auto op = SymbolicOperation(Targets{0U, 1U}, OpType::DCX); - op.invert(); - const auto expectedTargets = Targets{1U, 0U}; - ASSERT_EQ(op.getTargets(), expectedTargets); -} - -TEST_F(QFRFunctionality, measureAll) { - QuantumComputation qc(2U); - qc.measureAll(); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNops(), 3U); - EXPECT_EQ(qc.getNcbits(), 2U); - EXPECT_EQ(qc.getClassicalRegisters().size(), 1U); -} - -TEST_F(QFRFunctionality, measureAllExistingRegister) { - QuantumComputation qc(2U, 2U); - qc.measureAll(false); - std::cout << qc << "\n"; - EXPECT_EQ(qc.getNops(), 3U); - EXPECT_EQ(qc.getNcbits(), 2U); - EXPECT_EQ(qc.getClassicalRegisters().size(), 1U); -} - -TEST_F(QFRFunctionality, measureAllInsufficientRegisterSize) { - QuantumComputation qc(2U, 1U); - EXPECT_THROW(qc.measureAll(false), std::runtime_error); -} - -TEST_F(QFRFunctionality, checkClassicalRegisters) { - QuantumComputation qc(1U, 1U); - const ClassicalRegister creg = {0U, 2U}; - EXPECT_THROW(qc.if_(X, 0, creg, 1U), std::runtime_error); -} - -TEST_F(QFRFunctionality, testSettingAncillariesProperlyCreatesRegisters) { - // create an empty circuit and assert some properties about its registers - QuantumComputation qc(3U); - const auto& qregs = qc.getQuantumRegisters(); - ASSERT_EQ(qregs.size(), 1U); - const auto& reg = *qregs.begin(); - const auto name = reg.first; - ASSERT_EQ(reg.second.getStartIndex(), 0U); - ASSERT_EQ(reg.second.getSize(), 3U); - const auto& ancRegs = qc.getAncillaRegisters(); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 3U); - ASSERT_EQ(qc.getNancillae(), 0U); - - // set some ancillaries and assert that the registers are created properly - qc.setLogicalQubitAncillary(2U); - qc.setLogicalQubitAncillary(1U); - ASSERT_EQ(qregs.size(), 1U); - ASSERT_EQ(reg.second.getStartIndex(), 0U); - ASSERT_EQ(reg.second.getSize(), 3U); - ASSERT_EQ(name, reg.first); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 1U); - ASSERT_EQ(qc.getNancillae(), 2U); - - // add one gate to the circuit, mark the last two qubits as garbage and call - // the `stripIdleQubits` method to remove the (idle) ancillary qubits. Then, - // assert that the registers are still correct. - qc.x(0); - qc.setLogicalQubitGarbage(1U); - qc.setLogicalQubitGarbage(2U); - qc.stripIdleQubits(); - ASSERT_EQ(qregs.size(), 1U); - ASSERT_EQ(reg.second.getStartIndex(), 0U); - ASSERT_EQ(reg.second.getSize(), 1U); - ASSERT_EQ(name, reg.first); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 1U); - ASSERT_EQ(qc.getNancillae(), 0U); -} - -TEST_F(QFRFunctionality, testSettingSetMultipleAncillariesAndGarbage) { - // create an empty circuit and assert some properties about its registers - QuantumComputation qc(3U); - const auto& ancRegs = qc.getAncillaRegisters(); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 3U); - ASSERT_EQ(qc.getNancillae(), 0U); - - // set some ancillaries garbage and assert that the registers are created - // properly - qc.setLogicalQubitsAncillary(1U, 2U); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 1U); - ASSERT_EQ(qc.getNancillae(), 2U); - qc.setLogicalQubitsGarbage(1U, 2U); - ASSERT_EQ(qc.getNgarbageQubits(), 2U); - ASSERT_EQ(qc.getNoutputQubits(), 1U); -} - -TEST_F(QFRFunctionality, StripIdleQubitsInMiddleOfCircuit) { - QuantumComputation qc{}; - qc.addQubitRegister(5, "q"); - qc.setLogicalQubitAncillary(3U); - qc.setLogicalQubitAncillary(4U); - qc.setLogicalQubitGarbage(3U); - qc.setLogicalQubitGarbage(4U); - qc.initialLayout.clear(); - qc.initialLayout[0U] = 3U; - qc.initialLayout[1U] = 0U; - qc.initialLayout[2U] = 4U; - qc.initialLayout[3U] = 2U; - qc.initialLayout[4U] = 1U; - qc.outputPermutation.clear(); - qc.outputPermutation[1U] = 2U; - qc.outputPermutation[3U] = 0U; - qc.outputPermutation[4U] = 1U; - - qc.x(1); - qc.x(3); - qc.x(4); - - const auto& qregs = qc.getQuantumRegisters(); - ASSERT_EQ(qregs.size(), 1U); - const auto& [name, reg] = *qregs.begin(); - ASSERT_EQ(name, "q"); - ASSERT_EQ(reg.getStartIndex(), 0U); - ASSERT_EQ(reg.getSize(), 5U); - const auto& ancRegs = qc.getAncillaRegisters(); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 3U); - ASSERT_EQ(qc.getNancillae(), 2U); - - qc.stripIdleQubits(); - - ASSERT_EQ(qregs.size(), 2U); - const auto& regAfter = qregs.at("q_l"); - ASSERT_EQ(regAfter.getStartIndex(), 1U); - ASSERT_EQ(regAfter.getSize(), 1U); - const auto& reg2After = qregs.at("q_h"); - ASSERT_EQ(reg2After.getStartIndex(), 3U); - ASSERT_EQ(reg2After.getSize(), 2U); - ASSERT_TRUE(ancRegs.empty()); - ASSERT_EQ(qc.getNqubitsWithoutAncillae(), 3U); - ASSERT_EQ(qc.getNancillae(), 0U); -} - -TEST_F(QFRFunctionality, trivialOperationReordering) { - QuantumComputation qc(2); - qc.h(0); - qc.h(1); - std::cout << qc << "\n"; - qc.reorderOperations(); - std::cout << qc << "\n"; - auto it = qc.begin(); - const auto target = (*it)->getTargets().at(0); - EXPECT_EQ(target, 1); - ++it; - const auto target2 = (*it)->getTargets().at(0); - EXPECT_EQ(target2, 0); -} - -TEST_F(QFRFunctionality, OperationReorderingBarrier) { - QuantumComputation qc(3); - qc.h(0); - qc.barrier({0, 1}); - qc.h(1); - std::cout << qc << "\n"; - qc.reorderOperations(); - std::cout << qc << "\n"; - auto it = qc.begin(); - const auto target = (*it)->getTargets().at(0); - EXPECT_EQ(target, 0); - ++it; - ++it; - const auto target2 = (*it)->getTargets().at(0); - EXPECT_EQ(target2, 1); -} - -TEST_F(QFRFunctionality, isDynamicCompoundOperation) { - QuantumComputation qc(1, 1); - QuantumComputation compound(1, 1); - compound.measure(0, 0); - compound.x(0); - compound.measure(0, 0); - qc.emplace_back(compound.asCompoundOperation()); - std::cout << qc << "\n"; - EXPECT_TRUE(qc.isDynamic()); -} - -TEST_F(QFRFunctionality, emptyPermutation) { - const Permutation perm{}; - - EXPECT_EQ(perm.size(), 0U); - EXPECT_EQ(perm.apply(0U), 0U); - EXPECT_EQ(perm.apply(Targets{0U}), Targets{0U}); - EXPECT_EQ(perm.apply(Controls{0U}), Controls{0U}); - EXPECT_EQ(perm.maxKey(), 0U); - EXPECT_EQ(perm.maxValue(), 0U); -} - -TEST_F(QFRFunctionality, NoRegisterOnEmptyCircuit) { - // This is a regression test. Previously, the following code would throw an - // exception because even zero-qubit circuits had an empty register named "q". - QuantumComputation qc(0U); - qc.addQubitRegister(1U, "p"); - EXPECT_NO_THROW(qc.addQubitRegister(1U, "q")); - EXPECT_EQ(qc.getQuantumRegisters().size(), 2U); -} - -TEST_F(QFRFunctionality, AddQubitAtFrontOfRegister) { - QuantumComputation qc{}; - qc.addQubitRegister(2, "q"); - const auto& qregs = qc.getQuantumRegisters(); - EXPECT_EQ(qregs.size(), 1U); - const auto& reg = qregs.at("q"); - EXPECT_EQ(reg.getStartIndex(), 0U); - EXPECT_EQ(reg.getSize(), 2U); - - // first remove the qubit - const auto& [physicalIndex, outputIndex] = qc.removeQubit(0); - EXPECT_EQ(physicalIndex, 0U); - EXPECT_EQ(outputIndex, 0U); - - const auto& qregsAfter = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfter.size(), 1U); - const auto& regAfter = qregsAfter.at("q"); - EXPECT_EQ(regAfter.getStartIndex(), 1U); - EXPECT_EQ(regAfter.getSize(), 1U); - - // add the qubit back at the front - qc.addQubit(0, physicalIndex, outputIndex); - const auto& qregsAfterAdd = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfterAdd.size(), 1U); - const auto& regAfterAdd = qregsAfterAdd.at("q"); - EXPECT_EQ(regAfterAdd.getStartIndex(), 0U); - EXPECT_EQ(regAfterAdd.getSize(), 2U); -} - -TEST_F(QFRFunctionality, AddQubitAtEndOfRegister) { - QuantumComputation qc{}; - qc.addQubitRegister(2, "q"); - const auto& qregs = qc.getQuantumRegisters(); - EXPECT_EQ(qregs.size(), 1U); - const auto& reg = qregs.at("q"); - EXPECT_EQ(reg.getStartIndex(), 0U); - EXPECT_EQ(reg.getSize(), 2U); - - // first remove the qubit - const auto& [physicalIndex, outputIndex] = qc.removeQubit(1); - EXPECT_EQ(physicalIndex, 1U); - EXPECT_EQ(outputIndex, 1U); - - const auto& qregsAfter = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfter.size(), 1U); - const auto& regAfter = qregsAfter.at("q"); - EXPECT_EQ(regAfter.getStartIndex(), 0U); - EXPECT_EQ(regAfter.getSize(), 1U); - - // add the qubit back at the end - qc.addQubit(1, physicalIndex, outputIndex); - const auto& qregsAfterAdd = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfterAdd.size(), 1U); - const auto& regAfterAdd = qregsAfterAdd.at("q"); - EXPECT_EQ(regAfterAdd.getStartIndex(), 0U); - EXPECT_EQ(regAfterAdd.getSize(), 2U); -} - -TEST_F(QFRFunctionality, AddQubitInMiddleOfSplitRegister) { - QuantumComputation qc{}; - qc.addQubitRegister(3, "q"); - const auto& qregs = qc.getQuantumRegisters(); - EXPECT_EQ(qregs.size(), 1U); - const auto& reg = qregs.at("q"); - EXPECT_EQ(reg.getStartIndex(), 0U); - EXPECT_EQ(reg.getSize(), 3U); - - // remove the middle qubit -> splits the register into q_l and q_h - const auto& [physicalIndex, outputIndex] = qc.removeQubit(1); - EXPECT_EQ(physicalIndex, 1U); - EXPECT_EQ(outputIndex, 1U); - - const auto& qregsAfter = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfter.size(), 2U); - const auto& regLow = qregsAfter.at("q_l"); - EXPECT_EQ(regLow.getStartIndex(), 0U); - EXPECT_EQ(regLow.getSize(), 1U); - const auto& regHigh = qregsAfter.at("q_h"); - EXPECT_EQ(regHigh.getStartIndex(), 2U); - EXPECT_EQ(regHigh.getSize(), 1U); - - // add back the qubit. should consolidate the registers again - qc.addQubit(1, physicalIndex, outputIndex); - const auto& qregsAfterAdd = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfterAdd.size(), 1U); - const auto& regConsolidated = qregsAfterAdd.at("q"); - EXPECT_EQ(regConsolidated.getStartIndex(), 0U); - EXPECT_EQ(regConsolidated.getSize(), 3U); -} - -TEST_F(QFRFunctionality, AddQubitWithoutNeighboringQubits) { - QuantumComputation qc{}; - qc.addQubitRegister(5, "q"); - const auto& qregs = qc.getQuantumRegisters(); - EXPECT_EQ(qregs.size(), 1U); - const auto& reg = qregs.at("q"); - EXPECT_EQ(reg.getStartIndex(), 0U); - EXPECT_EQ(reg.getSize(), 5U); - - // remove the middle 3 qubits -> splits the register into q_l and q_h with one - // qubit each. - const auto& [physicalIndex1, outputIndex1] = qc.removeQubit(1); - EXPECT_EQ(physicalIndex1, 1U); - EXPECT_EQ(outputIndex1, 1U); - const auto& [physicalIndex2, outputIndex2] = qc.removeQubit(2); - EXPECT_EQ(physicalIndex2, 2U); - EXPECT_EQ(outputIndex2, 2U); - const auto& [physicalIndex3, outputIndex3] = qc.removeQubit(3); - EXPECT_EQ(physicalIndex3, 3U); - EXPECT_EQ(outputIndex3, 3U); - - const auto& qregsAfter = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfter.size(), 2U); - const auto& regLow = qregsAfter.at("q_l"); - EXPECT_EQ(regLow.getStartIndex(), 0U); - EXPECT_EQ(regLow.getSize(), 1U); - - const auto& regHigh = qregsAfter.at("q_h"); - EXPECT_EQ(regHigh.getStartIndex(), 4U); - EXPECT_EQ(regHigh.getSize(), 1U); - - // add back the middle qubit. should create a new register for the qubit - qc.addQubit(2, physicalIndex2, outputIndex2); - const auto& qregsAfterAdd = qc.getQuantumRegisters(); - EXPECT_EQ(qregsAfterAdd.size(), 3U); - // expect to find a `q_2` register with 1 qubit starting at index 2 - const auto& it = qregsAfterAdd.find("q_2"); - ASSERT_NE(it, qregsAfterAdd.end()); - const auto& [nameMiddle, regMiddle] = *it; - EXPECT_EQ(nameMiddle, "q_2"); - EXPECT_EQ(regMiddle.getStartIndex(), 2U); - EXPECT_EQ(regMiddle.getSize(), 1U); -} - -TEST_F(QFRFunctionality, CopyConstructor) { - QuantumComputation qc(2, 2); - qc.h(0); - qc.swap(0, 1); - qc.barrier(); - qc.measure(0, 0); - const ClassicalRegister creg = {0U, 1U}; - qc.if_(X, 0, creg, 1U); - const sym::Variable theta{"theta"}; - qc.rx(Symbolic{theta}, 0); - - QuantumComputation compound(2, 2); - compound.h(0); - compound.cx(0, 1); - qc.emplace_back(compound.asOperation()); - - qc.initialLayout[0] = 1; - qc.initialLayout[1] = 0; - qc.outputPermutation[0] = 1; - qc.outputPermutation[1] = 0; - qc.gphase(0.25); - - qc.setLogicalQubitAncillary(1); - qc.setLogicalQubitGarbage(1); - - const auto qcCopy = qc; - EXPECT_EQ(qc, qcCopy); -} - -TEST_F(QFRFunctionality, InequalityDifferentNumberOfQubits) { - // Different number of qubits - const QuantumComputation qc1(2, 2); - const QuantumComputation qc2(3, 3); - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentInitialLayout) { - // Different initial layout - QuantumComputation qc1(2, 2); - QuantumComputation qc2(2, 2); - qc1.initialLayout[0] = 0; - qc1.initialLayout[1] = 1; - qc2.initialLayout[0] = 1; - qc2.initialLayout[1] = 0; - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentGateOperations) { - // Different gate operations - QuantumComputation qc1(2, 2); - QuantumComputation qc2(2, 2); - qc1.h(0); - qc2.cx(0, 1); // Different operation - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentGateOrder) { - // Same gates but different order - QuantumComputation qc1(2, 2); - QuantumComputation qc2(2, 2); - qc1.h(0); - qc1.cx(0, 1); - - qc2.cx(0, 1); - qc2.h(0); // Reversed order - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentAncillaryQubits) { - // Different ancillary qubits - QuantumComputation qc1(2, 2); - qc1.setLogicalQubitAncillary(1); - - const QuantumComputation qc2(2, 2); - // No ancillary qubits in qc2 - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentGarbageQubits) { - // Different garbage qubits - QuantumComputation qc1(2, 2); - qc1.setLogicalQubitGarbage(1); - - const QuantumComputation qc2(2, 2); - // No garbage qubits in qc2 - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentFinalLayout) { - // Different final layout - QuantumComputation qc1(2, 2); - QuantumComputation qc2(2, 2); - qc1.outputPermutation[0] = 1; - qc1.outputPermutation[1] = 0; - - qc2.outputPermutation[0] = 0; - qc2.outputPermutation[1] = 1; - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentQuantumPhase) { - // Different quantum phase - QuantumComputation qc1(2, 2); - qc1.gphase(0.1); // Add global phase - - const QuantumComputation qc2(2, 2); - // No global phase in qc2 - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentNumberOfClassicalBits) { - // Different number of classical bits - const QuantumComputation qc1(2, 3); // 2 qubits, 3 classical bits - const QuantumComputation qc2(2, 2); // 2 qubits, 2 classical bits - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentMeasurementMappings) { - // Different measurement mappings - QuantumComputation qc1(2, 2); - qc1.measure(0, 1); // Measure qubit 0 to classical bit 1 - - QuantumComputation qc2(2, 2); - qc2.measure(0, 0); // Measure qubit 0 to classical bit 0 - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, InequalityDifferentAdditionalOperations) { - // Different additional operations - QuantumComputation qc1(2, 2); - qc1.h(0); - - QuantumComputation qc2(2, 2); - qc2.h(0); - qc2.barrier(); // qc2 has an additional barrier - EXPECT_NE(qc1, qc2); -} - -TEST_F(QFRFunctionality, TryAddingExistingQuantumRegister) { - QuantumComputation qc{}; - qc.addQubitRegister(2, "q"); - EXPECT_THROW(qc.addQubitRegister(2, "q"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingExistingAncillaryRegister) { - QuantumComputation qc{}; - qc.addAncillaryRegister(2, "a"); - EXPECT_THROW(qc.addAncillaryRegister(2, "a"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingExistingClassicalRegister) { - QuantumComputation qc{}; - qc.addClassicalRegister(2, "c"); - EXPECT_THROW(qc.addClassicalRegister(2, "c"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingQubitRegisterAfterAncillaryRegister) { - QuantumComputation qc{}; - qc.addAncillaryRegister(2, "a"); - EXPECT_THROW(qc.addQubitRegister(2, "q"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingZeroSizeQuantumRegister) { - QuantumComputation qc{}; - EXPECT_THROW(qc.addQubitRegister(0, "q"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingZeroSizeAncillaryRegister) { - QuantumComputation qc{}; - EXPECT_THROW(qc.addAncillaryRegister(0, "a"), std::runtime_error); -} - -TEST_F(QFRFunctionality, TryAddingZeroSizeClassicalRegister) { - QuantumComputation qc{}; - EXPECT_THROW(qc.addClassicalRegister(0, "c"), std::runtime_error); -} - -TEST_F(QFRFunctionality, RemoveQubitMissingRegisterThrows) { - QuantumComputation qc{}; - qc.addQubitRegister(1, "q"); - qc.initialLayout.emplace(2, 1); - EXPECT_THROW(std::ignore = qc.removeQubit(1), std::runtime_error); -} - -TEST_F(QFRFunctionality, stripIdleQubits) { - QuantumComputation qc(3, 2); - qc.x(0); - qc.x(2); - qc.measure(0, 0); - qc.measure(2, 1); - qc.stripIdleQubits(true); - - const auto* const expected = "// i 0 1\n" - "// o 0 1\n" - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q_l;\n" - "qubit[1] q_h;\n" - "bit[2] c;\n" - "x q_l[0];\n" - "x q_h[0];\n" - "c[0] = measure q_l[0];\n" - "c[1] = measure q_h[0];\n"; - - EXPECT_EQ(qc.toQASM(), expected); - const auto qc2 = qasm3::Importer::imports(expected); - EXPECT_EQ(qc2.toQASM(), expected); -} - -TEST_F(QFRFunctionality, failOnAddingExistingQubit) { - QuantumComputation qc(1); - EXPECT_THROW(qc.addQubit(0, 0, 0);, std::runtime_error); - EXPECT_THROW(qc.addAncillaryQubit(0, 0);, std::runtime_error); -} - -TEST_F(QFRFunctionality, failOnAddingNonConsecutiveQubit) { - QuantumComputation qc(1); - EXPECT_THROW(qc.addQubit(2, 2, 2);, std::runtime_error); -} - -TEST_F(QFRFunctionality, RemoveQubitMissingLayoutThrows) { - QuantumComputation qc(1); - EXPECT_THROW(std::ignore = qc.removeQubit(1), std::runtime_error); -} - -} // namespace qc diff --git a/test/ir/test_remove_final_measurements.cpp b/test/ir/test_remove_final_measurements.cpp deleted file mode 100644 index 2050c2043a..0000000000 --- a/test/ir/test_remove_final_measurements.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/CompoundOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "qasm3/Importer.hpp" - -#include - -#include -#include -#include - -namespace qc { -TEST(RemoveFinalMeasurements, EmptyCircuit) { - QuantumComputation qc; - qc.removeFinalMeasurements(); - EXPECT_TRUE(qc.empty()); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurements) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - qc.h(0); - qc.h(1); - qc.measure(0, 0); - qc.measure(1, 1); - qc.h(1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - auto it = qc.begin(); - ++it; - ++it; // skip first two H - const auto& op = *it; - EXPECT_TRUE(op->isNonUnitaryOperation()); - EXPECT_EQ(op->getType(), qc::Measure); - ++it; - const auto& op2 = *it; - EXPECT_TRUE(op2->isStandardOperation()); - EXPECT_EQ(op2->getType(), qc::H); - EXPECT_EQ(op2->getTargets().at(0), 1); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsTwoQubitMeasurement) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - qc.h(0); - qc.h(1); - qc.measure({0, 1}, {0, 1}); - qc.h(1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - auto it = qc.begin(); - ++it; - ++it; // skip first two H - const auto& op = *it; - EXPECT_TRUE(op->isNonUnitaryOperation()); - EXPECT_EQ(op->getType(), qc::Measure); - ++it; - const auto& op2 = *it; - EXPECT_TRUE(op2->isStandardOperation()); - EXPECT_EQ(op2->getType(), qc::H); - EXPECT_EQ(op2->getTargets().at(0), 1); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsCompound) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - QuantumComputation comp(nqubits, nqubits); - comp.measure(0, 0); - comp.measure(1, 1); - comp.h(1); - qc.emplace_back(comp.asOperation()); - qc.h(1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - auto it = qc.begin(); - const auto& op = *it; - EXPECT_TRUE(op->isCompoundOperation()); - - const auto* cop = dynamic_cast(op.get()); - ASSERT_NE(cop, nullptr); - EXPECT_EQ(cop->size(), 2); - ++it; - const auto& op2 = *it; - EXPECT_TRUE(op2->isStandardOperation()); - EXPECT_EQ(op2->getType(), qc::H); - EXPECT_EQ(op2->getTargets().at(0), 1); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsCompoundDegraded) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - QuantumComputation comp(nqubits, nqubits); - comp.measure(0, 0); - comp.h(1); - qc.emplace_back(comp.asOperation()); - qc.h(1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - auto it = qc.begin(); - const auto& op = *it; - EXPECT_TRUE(op->isStandardOperation()); - EXPECT_EQ(op->getType(), qc::H); - EXPECT_EQ(op->getTargets().at(0), 1); - ++it; - const auto& op2 = *it; - EXPECT_TRUE(op2->isStandardOperation()); - EXPECT_EQ(op2->getType(), qc::H); - EXPECT_EQ(op2->getTargets().at(0), 1); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsCompoundEmpty) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - QuantumComputation comp(nqubits, nqubits); - comp.measure(0, 0); - qc.emplace_back(comp.asCompoundOperation()); - qc.h(1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - auto it = qc.begin(); - const auto& op = *it; - EXPECT_TRUE(op->isStandardOperation()); - EXPECT_EQ(op->getType(), qc::H); - EXPECT_EQ(op->getTargets().at(0), 1); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsWithOperationsInFront) { - const std::string circ = - "OPENQASM 2.0;include \"qelib1.inc\";qreg q[3];qreg r[3];h q;cx q, " - "r;creg c[3];creg d[3];barrier q;measure q->c;measure r->d;\n"; - auto qc = qasm3::Importer::imports(circ); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - ASSERT_EQ(qc.getNops(), 2); - ASSERT_EQ(qc.getNindividualOps(), 6); -} - -TEST(RemoveFinalMeasurements, removeFinalMeasurementsWithBarrier) { - constexpr std::size_t nqubits = 2; - QuantumComputation qc(nqubits, nqubits); - qc.barrier({0, 1}); - qc.measure(0, 0); - qc.measure(1, 1); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - qc.removeFinalMeasurements(); - std::cout << "-----------------------------\n"; - qc.print(std::cout); - EXPECT_TRUE(qc.empty()); -} -} // namespace qc diff --git a/test/ir/test_symbolic.cpp b/test/ir/test_symbolic.cpp deleted file mode 100644 index de51460d08..0000000000 --- a/test/ir/test_symbolic.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/SymbolicOperation.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace qc; -using namespace sym; - -namespace { - -class SymbolicTest : public ::testing::Test { -public: - Variable x = Variable("x"); - Variable y = Variable("y"); - Variable z = Variable("z"); - - Symbolic xMonom = Symbolic{Term{x}}; - Symbolic yMonom = Symbolic{Term{y}}; - Symbolic zMonom = Symbolic{Term{z}}; - - QuantumComputation symQc = QuantumComputation(4); - QuantumComputation qc = QuantumComputation(4); -}; - -} // namespace - -TEST(SymbolicVariableTest, ConcurrentRegistration) { - constexpr size_t threadCount = 8; - constexpr size_t variableCount = 128; - std::barrier syncPoint(threadCount); - std::barrier start(threadCount + 1); - std::atomic_size_t activeWriters = threadCount; - std::atomic_size_t readCount = 0; - std::atomic_bool readsCorrect = true; - const Variable observed("concurrent-observed"); - const auto expectedHash = std::hash{}(observed.getName()); - std::vector> variables(threadCount); - std::vector threads; - threads.reserve(threadCount); - - for (size_t thread = 0; thread < threadCount; ++thread) { - threads.emplace_back([&, thread] { - variables[thread].reserve(variableCount); - start.arrive_and_wait(); - for (size_t variable = 0; variable < variableCount; ++variable) { - syncPoint.arrive_and_wait(); - variables[thread].emplace_back("concurrent-" + - std::to_string(variable)); - } - activeWriters.fetch_sub(1, std::memory_order_release); - }); - } - start.arrive_and_wait(); - while (activeWriters.load(std::memory_order_acquire) != 0) { - if (observed.getName() != "concurrent-observed" || - std::hash{}(observed) != expectedHash) { - readsCorrect.store(false, std::memory_order_relaxed); - } - readCount.fetch_add(1, std::memory_order_relaxed); - } - for (auto& thread : threads) { - thread.join(); - } - - EXPECT_TRUE(readsCorrect.load(std::memory_order_relaxed)); - EXPECT_GT(readCount.load(std::memory_order_relaxed), 0); - for (size_t variable = 0; variable < variableCount; ++variable) { - const auto& expected = variables.front()[variable]; - for (size_t thread = 1; thread < threadCount; ++thread) { - EXPECT_EQ(variables[thread][variable], expected); - EXPECT_EQ(variables[thread][variable].getName(), expected.getName()); - } - } -} - -TEST_F(SymbolicTest, Gates) { - auto xVal = PI_4 / 2; - auto yVal = PI_4 / 4; - auto zVal = PI / 3; - - // test all kinds of symbolic operations supported - symQc.mcu(xMonom, yMonom, zMonom, {1, 2_nc}, 0); - symQc.cu(xMonom, yMonom, zMonom, 1, 0); - symQc.u(xMonom, yMonom, zMonom, 0); - - symQc.mcu2(xMonom, yMonom, {1, 2_nc}, 0); - symQc.cu2(xMonom, yMonom, 1, 0); - symQc.u2(xMonom, yMonom, 0); - - symQc.mcp(xMonom, {1, 2_nc}, 0); - symQc.cp(xMonom, 1, 0); - symQc.p(xMonom, 0); - - symQc.mcr(xMonom, yMonom, {1, 2_nc}, 0); - symQc.cr(xMonom, yMonom, 1, 0); - symQc.r(xMonom, yMonom, 0); - - symQc.mcrx(xMonom, {1, 2_nc}, 0); - symQc.crx(xMonom, 1, 0); - symQc.rx(xMonom, 0); - - symQc.mcry(xMonom, {1, 2_nc}, 0); - symQc.cry(xMonom, 1, 0); - symQc.ry(xMonom, 0); - - symQc.mcrz(xMonom, {1, 2_nc}, 0); - symQc.crz(xMonom, 1, 0); - symQc.rz(xMonom, 0); - - symQc.mcrxx(xMonom, {3, 2_nc}, 0, 1); - symQc.crxx(xMonom, 2, 0, 1); - symQc.rxx(xMonom, 0, 1); - - symQc.mcryy(xMonom, {3, 2_nc}, 0, 1); - symQc.cryy(xMonom, 2, 0, 1); - symQc.ryy(xMonom, 0, 1); - - symQc.mcrzz(xMonom, {3, 2_nc}, 0, 1); - symQc.crzz(xMonom, 2, 0, 1); - symQc.rzz(xMonom, 0, 1); - - symQc.mcrzx(xMonom, {3, 2_nc}, 0, 1); - symQc.crzx(xMonom, 2, 0, 1); - symQc.rzx(xMonom, 0, 1); - - symQc.mcxx_minus_yy(xMonom, yMonom, {3, 2_nc}, 0, 1); - symQc.cxx_minus_yy(xMonom, yMonom, 2, 0, 1); - symQc.xx_minus_yy(xMonom, yMonom, 0, 1); - - symQc.mcxx_plus_yy(xMonom, yMonom, {3, 2_nc}, 0, 1); - symQc.cxx_plus_yy(xMonom, yMonom, 2, 0, 1); - symQc.xx_plus_yy(xMonom, yMonom, 0, 1); - - EXPECT_FALSE(symQc.isVariableFree()); - for (const auto& symOp : symQc) { - EXPECT_TRUE(symOp->isSymbolicOperation()); - } - - // normal circuit - qc.mcu(xVal, yVal, zVal, {1, 2_nc}, 0); - qc.cu(xVal, yVal, zVal, 1, 0); - qc.u(xVal, yVal, zVal, 0); - - qc.mcu2(xVal, yVal, {1, 2_nc}, 0); - qc.cu2(xVal, yVal, 1, 0); - qc.u2(xVal, yVal, 0); - - qc.mcp(xVal, {1, 2_nc}, 0); - qc.cp(xVal, 1, 0); - qc.p(xVal, 0); - - qc.mcr(xVal, yVal, {1, 2_nc}, 0); - qc.cr(xVal, yVal, 1, 0); - qc.r(xVal, yVal, 0); - - qc.mcrx(xVal, {1, 2_nc}, 0); - qc.crx(xVal, 1, 0); - qc.rx(xVal, 0); - - qc.mcry(xVal, {1, 2_nc}, 0); - qc.cry(xVal, 1, 0); - qc.ry(xVal, 0); - - qc.mcrz(xVal, {1, 2_nc}, 0); - qc.crz(xVal, 1, 0); - qc.rz(xVal, 0); - - qc.mcrxx(xVal, {3, 2_nc}, 0, 1); - qc.crxx(xVal, 2, 0, 1); - qc.rxx(xVal, 0, 1); - - qc.mcryy(xVal, {3, 2_nc}, 0, 1); - qc.cryy(xVal, 2, 0, 1); - qc.ryy(xVal, 0, 1); - - qc.mcrzz(xVal, {3, 2_nc}, 0, 1); - qc.crzz(xVal, 2, 0, 1); - qc.rzz(xVal, 0, 1); - - qc.mcrzx(xVal, {3, 2_nc}, 0, 1); - qc.crzx(xVal, 2, 0, 1); - qc.rzx(xVal, 0, 1); - - qc.mcxx_minus_yy(xVal, yVal, {3, 2_nc}, 0, 1); - qc.cxx_minus_yy(xVal, yVal, 2, 0, 1); - qc.xx_minus_yy(xVal, yVal, 0, 1); - - qc.mcxx_plus_yy(xVal, yVal, {3, 2_nc}, 0, 1); - qc.cxx_plus_yy(xVal, yVal, 2, 0, 1); - qc.xx_plus_yy(xVal, yVal, 0, 1); - - EXPECT_TRUE(qc.isVariableFree()); - - // no operation in the uninstantiated circuit should be equal to the standard - // circuit - for (auto it1 = symQc.begin(), it2 = qc.begin(); - it1 != symQc.end() && it2 != qc.end(); ++it1, ++it2) { - EXPECT_FALSE((*it1)->equals(*(*it2))); - } - - const VariableAssignment assignment{{x, xVal}, {y, yVal}, {z, zVal}}; - auto instantiated = symQc.instantiate(assignment); - - // after the instantiation, the symbolic circuit should be equal to the - // standard circuit - for (auto it1 = instantiated.begin(), it2 = qc.begin(); - it1 != instantiated.end() && it2 != qc.end(); ++it1, ++it2) { - EXPECT_TRUE((*it1)->equals(*(*it2))); - } -} - -TEST_F(SymbolicTest, TestClone) { - symQc.mcu(xMonom, yMonom, zMonom, {1, 2_nc}, 0); - const auto clonedQc = symQc; - - symQc.u(xMonom, yMonom, zMonom, 0); - EXPECT_NE(symQc.getNops(), clonedQc.getNops()); -} - -TEST_F(SymbolicTest, TestU3SymLambdaPhase) { - symQc.u(0.0, 0.0, xMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::P); -} - -TEST_F(SymbolicTest, TestU3SymLambdaU2) { - symQc.u(PI_2, 1.234, xMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U2); -} - -TEST_F(SymbolicTest, TestU3SymLambdaU3) { - symQc.u(4.567, 1.234, xMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU3SymPhiU2) { - symQc.u(PI_2, xMonom, 1.234, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U2); -} - -TEST_F(SymbolicTest, TestU3SymPhiU3) { - symQc.u(4.567, xMonom, 0.0, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); - - symQc.u(1.234, xMonom, PI_2, 0); - EXPECT_EQ((*(symQc.begin() + 1))->getType(), OpType::U); - - symQc.u(3.465, xMonom, PI, 0); - EXPECT_EQ((*(symQc.begin() + 2))->getType(), OpType::U); - - symQc.u(1.2345, xMonom, 3.465, 0); - EXPECT_EQ((*(symQc.begin() + 3))->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU3SymThetaU3) { - symQc.u(xMonom, 0.0, 0.0, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); - - symQc.u(xMonom, PI_2, PI_2, 0); - EXPECT_EQ((*(symQc.begin() + 1))->getType(), OpType::U); - - symQc.u(xMonom, 0.0, PI, 0); - EXPECT_EQ((*(symQc.begin() + 2))->getType(), OpType::U); - - symQc.u(xMonom, 4.567, 1.234, 0); - EXPECT_EQ((*(symQc.begin() + 3))->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU3SymLambdaSymPhiU2) { - symQc.u(PI_2, xMonom, yMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U2); -} - -TEST_F(SymbolicTest, TestU3SymLambdaSymPhiU3) { - symQc.u(PI_2 - 0.2, xMonom, yMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU3SymLambdaSymThetaU3) { - symQc.u(xMonom, 0.0, yMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU3SymPhiSymThetaU3) { - symQc.u(xMonom, yMonom, 1.2345, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U); -} - -TEST_F(SymbolicTest, TestU2SymLambda) { - symQc.u2(0.0, xMonom, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U2); -} - -TEST_F(SymbolicTest, TestU2SymPhi) { - symQc.u2(xMonom, 1.2345, 0); - EXPECT_EQ((*symQc.begin())->getType(), OpType::U2); -} - -TEST_F(SymbolicTest, failPrintingQASM2) { - symQc.u(xMonom, yMonom, zMonom, 0); - try { - std::ignore = symQc.toQASM(false); - FAIL() << "Expected OpenQASM 2 serialization to fail."; - } catch (const std::runtime_error& error) { - EXPECT_STREQ(error.what(), - "OpenQASM 2.0 doesn't support parameterized gates!"); - } -} - -TEST_F(SymbolicTest, failPrintingQASM3) { - symQc.u(xMonom, yMonom, zMonom, 0); - try { - std::ignore = symQc.toQASM(true); - FAIL() << "Expected OpenQASM 3 serialization to fail."; - } catch (const std::runtime_error& error) { - EXPECT_STREQ( - error.what(), - "Printing OpenQASM 3.0 parameterized gates is not supported yet!"); - } -} - -TEST_F(SymbolicTest, Constructor) { - EXPECT_NO_THROW(std::ignore = - qc::SymbolicOperation(0, 1, qc::OpType::P, {xMonom})); - EXPECT_NO_THROW( - std::ignore = qc::SymbolicOperation(0, {1, 2}, qc::OpType::P, {xMonom})); -} diff --git a/test/python/dd/test_dd_package.py b/test/python/dd/test_dd_package.py deleted file mode 100644 index c3bc1bee72..0000000000 --- a/test/python/dd/test_dd_package.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Tests for the MQT Core DD package.""" - -from __future__ import annotations - -import sys - -import numpy as np - -from mqt.core.dd import DDPackage, build_functionality, build_unitary, sample, simulate -from mqt.core.ir import QuantumComputation -from mqt.core.ir.operations import OpType - - -def test_sample_simple_circuit() -> None: - """Test sampling a simple circuit.""" - qc = QuantumComputation(2) - qc.x(0) - qc.measure_all() - - shots = 1000 - results = sample(qc, shots) - assert results == {"01": shots} - - -def test_sample_dynamic_circuit() -> None: - """Test sampling a dynamic circuit.""" - qc = QuantumComputation(1, 1) - # put the qubit into superposition - qc.h(0) - # reset the qubit - qc.measure(0, 0) - qc.if_(OpType.x, target=0, control_bit=0) - # flip to |1> - qc.x(0) - # measure the qubit - qc.measure(0, 0) - - shots = 1000 - results = sample(qc, shots) - assert results == {"1": shots} - - -def test_build_unitary_and_functionality_simple_circuit() -> None: - """Test building the unitary and functionality for a simple circuit.""" - qc = QuantumComputation(2) - qc.h(0) - qc.cx(0, 1) - - p = DDPackage(2) - package_refcount = sys.getrefcount(p) - functionality = build_functionality(qc, p) - try: - assert sys.getrefcount(p) == package_refcount + 1 - mat = functionality.get_matrix(2) - arr = np.array(mat, copy=False) - assert arr.shape == (4, 4) - assert np.allclose(arr, np.array([[1, 1, 0, 0], [0, 0, 1, -1], [0, 0, 1, 1], [1, -1, 0, 0]]) / np.sqrt(2)) - assert np.allclose(np.array(build_unitary(qc), copy=False), arr) - finally: - p.dec_ref_mat(functionality) - - -def test_simulate_simple_circuit() -> None: - """Test simulating a simple circuit.""" - qc = QuantumComputation(2) - qc.h(0) - qc.cx(0, 1) - - p = DDPackage(2) - in_state = p.zero_state(2) - - out_state = simulate(qc, in_state, p) - vec = out_state.get_vector() - arr = np.array(vec, copy=False) - assert arr.shape == (4,) - assert np.allclose(arr, np.array([1, 0, 0, 1]) / np.sqrt(2)) diff --git a/test/python/dd/test_matrix_dds.py b/test/python/dd/test_matrix_dds.py index a4cddf04cd..697903a562 100644 --- a/test/python/dd/test_matrix_dds.py +++ b/test/python/dd/test_matrix_dds.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from mqt.core.dd import DDPackage, MatrixDD +from mqt.core.dd import Control, DDPackage, MatrixDD if TYPE_CHECKING: import numpy.typing as npt @@ -120,6 +120,28 @@ def test_controlled_single_qubit_gate(gate_matrices: dict[str, npt.NDArray[np.co assert np.allclose(arr, target) +def test_dd_owned_control_type(gate_matrices: dict[str, npt.NDArray[np.complex128]]) -> None: + """Expose controls as DD-owned binding types.""" + p = DDPackage(2) + x_matrix = gate_matrices["X"] + + positive = p.controlled_single_qubit_gate(x_matrix, 1, 0) + multi_positive = p.multi_controlled_single_qubit_gate(x_matrix, {1}, 0) + assert np.allclose(positive.get_matrix(2), multi_positive.get_matrix(2)) + + control = Control(1, Control.Type.Neg) + assert control == Control(1, Control.Type.Neg) + assert len({control, Control(1, Control.Type.Neg)}) == 1 + assert repr(control) == 'Control(qubit=1, type_="Neg")' + + negative = p.controlled_single_qubit_gate(x_matrix, control, 0) + expected = np.array( + [[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + dtype=np.complex128, + ) + assert np.allclose(negative.get_matrix(2), expected) + + def test_two_qubit_gate() -> None: """Test constructing two-qubit gate DDs.""" p = DDPackage(5) diff --git a/test/python/ir/test_if_else_operation.py b/test/python/ir/test_if_else_operation.py deleted file mode 100644 index 7f14cca730..0000000000 --- a/test/python/ir/test_if_else_operation.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Test the quantum computation IR.""" - -from __future__ import annotations - -import pytest - -from mqt.core.ir import QuantumComputation -from mqt.core.ir.operations import ComparisonKind, OpType, StandardOperation - - -@pytest.mark.parametrize( - ("comparison_kind", "comparison_kind_string"), - [ - (ComparisonKind.eq, "=="), - (ComparisonKind.neq, "!="), - (ComparisonKind.lt, "<"), - (ComparisonKind.leq, "<="), - (ComparisonKind.gt, ">"), - (ComparisonKind.geq, ">="), - ], -) -def test_if_else_operation_register(comparison_kind: ComparisonKind, comparison_kind_string: str) -> None: - """Test the creation of an if-else operation.""" - qc = QuantumComputation() - qc.add_qubit_register(1) - c = qc.add_classical_register(1) - - qc.if_else( - then_operation=StandardOperation(0, OpType.x), - else_operation=StandardOperation(0, OpType.y), - control_register=c, - comparison_kind=comparison_kind, - ) - - qasm = qc.qasm3_str() - expected = f""" - // i 0 - // o 0 - OPENQASM 3.0; - include "stdgates.inc"; - qubit[1] q; - bit[1] c; - if (c {comparison_kind_string} 1) {{ - x q[0]; - }} else {{ - y q[0]; - }} - """ - # Remove all whitespace from both strings before comparison - assert "".join(qasm.split()) == "".join(expected.split()) - - -def test_if_else_operation_bit() -> None: - """Test the creation of an if-else operation.""" - qc = QuantumComputation() - qc.add_qubit_register(1) - qc.add_classical_register(1) - - qc.if_else( - then_operation=StandardOperation(0, OpType.x), - else_operation=StandardOperation(0, OpType.y), - control_bit=0, - ) - - qasm = qc.qasm3_str() - expected = """ - // i 0 - // o 0 - OPENQASM 3.0; - include "stdgates.inc"; - qubit[1] q; - bit[1] c; - if (c[0]) { - x q[0]; - } else { - y q[0]; - } - """ - # Remove all whitespace from both strings before comparison - assert "".join(qasm.split()) == "".join(expected.split()) - - -def test_if_operation_register() -> None: - """Test the creation of an if-else operation.""" - qc = QuantumComputation() - qc.add_qubit_register(1) - c = qc.add_classical_register(1) - - qc.if_(OpType.x, target=0, control_register=c) - - qasm = qc.qasm3_str() - expected = """ - // i 0 - // o 0 - OPENQASM 3.0; - include "stdgates.inc"; - qubit[1] q; - bit[1] c; - if (c == 1) { - x q[0]; - } - """ - # Remove all whitespace from both strings before comparison - assert "".join(qasm.split()) == "".join(expected.split()) - - -def test_if_operation_bit() -> None: - """Test the creation of an if-else operation.""" - qc = QuantumComputation() - qc.add_qubit_register(1) - qc.add_classical_register(1) - - qc.if_(OpType.x, target=0, control_bit=0) - - qasm = qc.qasm3_str() - expected = """ - // i 0 - // o 0 - OPENQASM 3.0; - include "stdgates.inc"; - qubit[1] q; - bit[1] c; - if (c[0]) { - x q[0]; - } - """ - # Remove all whitespace from both strings before comparison - assert "".join(qasm.split()) == "".join(expected.split()) diff --git a/test/python/ir/test_ir.py b/test/python/ir/test_ir.py deleted file mode 100644 index bab3513603..0000000000 --- a/test/python/ir/test_ir.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Test the quantum computation IR.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from mqt.core.ir import QuantumComputation - -if TYPE_CHECKING: - from pathlib import Path - - -def test_bell_state_circuit() -> None: - """Test the creation of a Bell state circuit.""" - qc = QuantumComputation() - q = qc.add_qubit_register(2) - c = qc.add_classical_register(2) - - qc.h(q[0]) - qc.cx(q[0], q[1]) - qc.measure(q[0], c[0]) - qc.measure(q[1], c[1]) - - qasm = qc.qasm3_str() - expected = """ - // i 0 1 - // o 0 1 - OPENQASM 3.0; - include "stdgates.inc"; - qubit[2] q; - bit[2] c; - h q[0]; - cx q[0], q[1]; - c[0] = measure q[0]; - c[1] = measure q[1]; - """ - # Remove all whitespace from both strings before comparison - assert "".join(qasm.split()) == "".join(expected.split()) - - -def test_num_output_qubits_excludes_garbage() -> None: - """Test that the output count reflects the circuit's garbage metadata.""" - qc = QuantumComputation(3) - assert qc.num_output_qubits == 3 - - qc.set_circuit_qubit_garbage(1) - assert qc.num_output_qubits == 2 - - -def test_qasm_file_exports_match_string_exports(tmp_path: Path) -> None: - """Test that both OpenQASM file exporters use the circuit serializer.""" - qc = QuantumComputation(2) - qc.h(0) - qc.cx(0, 1) - - qasm2_path = tmp_path / "circuit.qasm2" - qc.qasm2(str(qasm2_path)) - assert qasm2_path.read_text() == qc.qasm2_str() - - qasm3_path = tmp_path / "circuit.qasm3" - qc.qasm3(str(qasm3_path)) - assert qasm3_path.read_text() == qc.qasm3_str() diff --git a/test/python/plugins/qiskit/test_mock_backend.py b/test/python/plugins/qiskit/test_mock_backend.py index bb111655f1..8870b14525 100644 --- a/test/python/plugins/qiskit/test_mock_backend.py +++ b/test/python/plugins/qiskit/test_mock_backend.py @@ -484,6 +484,25 @@ def test_backend_qasm3_serialization_success(mock_qdmi_device_factory: type[Mock assert "cx q[0], q[1]" in program +def test_backend_qasm3_zero_initializes_classical_bits( + mock_qdmi_device_factory: type[MockQDMIDevice], +) -> None: + """Initialize every QASM 3 classical bit before measurement.""" + qc = QuantumCircuit(2, 2) + qc.measure(0, 0) + + device = mock_qdmi_device_factory(num_qubits=2, operations=["measure"]) + backend = QDMIBackend(device) # ty: ignore[invalid-argument-type] + + program, fmt = backend._serialize_circuit(qc, [ProgramFormat.QASM3]) # ruff:ignore[private-member-access] + + assert fmt == ProgramFormat.QASM3 + assert isinstance(program, str) + assert "c[0] = false;" in program + assert "c[1] = false;" in program + assert program.index("c[0] = false;") < program.index("c[0] = measure q[0];") + + def test_backend_qasm2_serialization_success(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: """Backend should successfully serialize a circuit into OpenQASM 2.""" qc = QuantumCircuit(2) diff --git a/test/python/plugins/qiskit/test_sampler.py b/test/python/plugins/qiskit/test_sampler.py index a99389fd17..c2195dd8ae 100644 --- a/test/python/plugins/qiskit/test_sampler.py +++ b/test/python/plugins/qiskit/test_sampler.py @@ -92,14 +92,16 @@ def test_sampler_run_parameterized_circuit(sampler: QDMISampler) -> None: def test_sampler_run_multiple_cregs(sampler: QDMISampler) -> None: """Sampler correctly handles multiple classical registers.""" - c0 = ClassicalRegister(1, "c0") + c0 = ClassicalRegister(2, "c0") c1 = ClassicalRegister(1, "c1") - qc = QuantumCircuit(2) + qc = QuantumCircuit(3) qc.add_register(c0) qc.add_register(c1) - qc.h(0) + qc.x(0) + qc.x(2) qc.measure(0, c0[0]) - qc.measure(1, c1[0]) + qc.measure(1, c0[1]) + qc.measure(2, c1[0]) job = sampler.run([(qc,)], shots=100) result = job.result() @@ -108,8 +110,10 @@ def test_sampler_run_multiple_cregs(sampler: QDMISampler) -> None: c0_bits = pub_result.data["c0"] c1_bits = pub_result.data["c1"] - assert c0_bits.num_bits == 1 + assert c0_bits.num_bits == 2 assert c1_bits.num_bits == 1 + assert c0_bits.get_counts() == {"01": 100} + assert c1_bits.get_counts() == {"1": 100} def test_sampler_shot_defaults(sampler: QDMISampler) -> None: diff --git a/test/python/plugins/test_qiskit.py b/test/python/plugins/test_qiskit.py deleted file mode 100644 index db31b179ba..0000000000 --- a/test/python/plugins/test_qiskit.py +++ /dev/null @@ -1,865 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Test Qiskit import.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, cast - -import pytest -from packaging import version -from qiskit import __version__ as qiskit_version -from qiskit import transpile -from qiskit.circuit import ( - AncillaRegister, - ClassicalRegister, - IfElseOp, - Parameter, - QuantumCircuit, - QuantumRegister, -) -from qiskit.circuit.classical import expr -from qiskit.circuit.library import RCCXGate, U2Gate, XXMinusYYGate, XXPlusYYGate -from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.qasm3 import dumps -from qiskit.quantum_info import Operator - -from mqt.core.ir.operations import ( - ComparisonKind, - CompoundOperation, - IfElseOperation, - OpType, - StandardOperation, - SymbolicOperation, -) -from mqt.core.ir.symbolic import Expression -from mqt.core.plugins.qiskit import mqt_to_qiskit, qiskit_to_mqt - -if TYPE_CHECKING: - from collections.abc import Callable - - -def test_empty_circuit() -> None: - """Test roundtrip of empty circuit.""" - qc = QuantumCircuit() - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 0 - assert mqt_qc.num_ops == 0 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 0 - assert len(qiskit_qc) == 0 - assert qc == qiskit_qc - - -def test_multiple_quantum_registers() -> None: - """Test roundtrip of circuit with multiple quantum registers.""" - p = QuantumRegister(2, "p") - q = QuantumRegister(2, "q") - qc = QuantumCircuit(p, q) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 4 - assert mqt_qc.num_ops == 0 - assert len(mqt_qc.qregs) == 2 - assert "p" in mqt_qc.qregs - assert "q" in mqt_qc.qregs - assert mqt_qc.qregs["p"].size == 2 - assert mqt_qc.qregs["p"].start == 0 - assert mqt_qc.qregs["q"].size == 2 - assert mqt_qc.qregs["q"].start == 2 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 4 - assert len(qiskit_qc.qregs) == 2 - assert qc == qiskit_qc - - -def test_quantum_and_ancillary_registers() -> None: - """Test roundtrip of circuit with quantum and ancillary registers.""" - q = QuantumRegister(2, "q") - a = AncillaRegister(1, "a") - qc = QuantumCircuit(q, a) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 3 - assert mqt_qc.num_ops == 0 - assert mqt_qc.num_ancilla_qubits == 1 - assert mqt_qc.is_circuit_qubit_ancillary(2) - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 3 - assert qiskit_qc.num_ancillas == 1 - assert qc == qiskit_qc - - -def test_multiple_classical_registers() -> None: - """Test roundtrip of circuit with multiple classical registers.""" - c = ClassicalRegister(2, "c") - d = ClassicalRegister(2, "d") - qc = QuantumCircuit(c, d) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_classical_bits == 4 - assert len(mqt_qc.cregs) == 2 - assert "c" in mqt_qc.cregs - assert "d" in mqt_qc.cregs - assert mqt_qc.cregs["c"].size == 2 - assert mqt_qc.cregs["c"].start == 0 - assert mqt_qc.cregs["d"].size == 2 - assert mqt_qc.cregs["d"].start == 2 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_clbits == 4 - assert qc == qiskit_qc - - -def test_single_gate() -> None: - """Test roundtrip of single-gate circuit.""" - qc = QuantumCircuit(1) - qc.h(0) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 1 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "h" - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 1 - assert len(qiskit_qc) == 1 - assert qc == qiskit_qc - - -def test_two_qubit_gate() -> None: - """Test roundtrip of two-qubit gate.""" - qc = QuantumCircuit(2) - qc.cx(0, 1) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 2 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0} - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 2 - assert len(qiskit_qc) == 1 - assert qc == qiskit_qc - - -def test_ccx() -> None: - """Test roundtrip of ccx gate.""" - qc = QuantumCircuit(3) - qc.ccx(0, 1, 2) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 3 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0, 1} - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 3 - assert len(qiskit_qc) == 1 - assert qiskit_qc[0].operation.name == "ccx" - - -skip_if_qiskit_version_ge_2_1 = pytest.mark.skipif( - version.parse(qiskit_version) >= version.parse("2.1"), - reason="Qiskit 2.1+ deprecates setting a mode for `mcx` gates", -) - - -@skip_if_qiskit_version_ge_2_1 -def test_mcx_recursive() -> None: - """Test roundtrip of large mcx gate.""" - qc = QuantumCircuit(9) - qc.mcx(control_qubits=list(range(7)), target_qubit=7, ancilla_qubits=list(range(8, 9)), mode="recursion") - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 9 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0, 1, 2, 3, 4, 5, 6} - assert not mqt_qc[0].acts_on(8) - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 9 - assert len(qiskit_qc) == 1 - assert qiskit_qc[0].operation.name == "mcx" - - -@skip_if_qiskit_version_ge_2_1 -def test_small_mcx_recursive() -> None: - """Test roundtrip of small mcx_recursive gate.""" - qc = QuantumCircuit(5) - qc.mcx(target_qubit=4, control_qubits=list(range(4)), mode="recursion") - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 5 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0, 1, 2, 3} - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 5 - assert len(qiskit_qc) == 1 - assert qiskit_qc[0].operation.name == "mcx" - - -@skip_if_qiskit_version_ge_2_1 -def test_mcx_vchain() -> None: - """Test roundtrip of mcx gate with v-chain.""" - qc = QuantumCircuit(9) - qc.mcx(target_qubit=5, control_qubits=list(range(5)), ancilla_qubits=list(range(6, 9)), mode="v-chain") - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 9 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0, 1, 2, 3, 4} - for i in range(6, 9): - assert not mqt_qc[0].acts_on(i) - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 9 - assert len(qiskit_qc) == 1 - assert qiskit_qc[0].operation.name == "mcx" - - -def test_mcx_without_mode() -> None: - """Test roundtrip of mcx gate without a dedicated mode being set (Qiskit 2.1+).""" - qc = QuantumCircuit(5) - qc.mcx(target_qubit=4, control_qubits=list(range(4))) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 5 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "x" - assert {control.qubit for control in mqt_qc[0].controls} == {0, 1, 2, 3} - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 5 - assert len(qiskit_qc) == 1 - assert qiskit_qc[0].operation.name == "mcx" - - -def test_custom_gate() -> None: - """Test roundtrip of custom gate.""" - custom_instr = QuantumCircuit(3, 1) - custom_instr.h(0) - custom_instr.cx(0, 1) - custom_instr.cx(0, 2) - custom_instr.measure(0, 0) - custom_instr = custom_instr.to_instruction() - qc = QuantumCircuit(3, 1) - qc.append(custom_instr, range(3), range(1)) - print(qc.draw(cregbundle=False)) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 3 - assert mqt_qc.num_ops == 1 - assert isinstance(mqt_qc[0], CompoundOperation) - assert mqt_qc[0][0].name.strip() == "h" - assert mqt_qc[0][1].name.strip() == "x" - assert mqt_qc[0][2].name.strip() == "x" - assert mqt_qc[0][3].name.strip() == "measure" - assert {control.qubit for control in mqt_qc[0][1].controls} == {0} - assert {control.qubit for control in mqt_qc[0][2].controls} == {0} - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc.draw(cregbundle=False)) - assert qiskit_qc.num_qubits == 3 - assert len(qiskit_qc) == 1 - - -def test_ancilla() -> None: - """Test roundtrip of ancilla register.""" - anc_reg = AncillaRegister(1, "anc") - q_reg = QuantumRegister(1, "q") - qc = QuantumCircuit(q_reg, anc_reg) - qc.h(anc_reg[0]) - qc.cx(anc_reg[0], q_reg[0]) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_data_qubits == 1 - assert mqt_qc.num_ancilla_qubits == 1 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 2 - assert len(qiskit_qc) == 2 - assert qc == qiskit_qc - - -def test_classical() -> None: - """Test roundtrip of classical register.""" - c_reg = ClassicalRegister(1, "c") - q_reg = QuantumRegister(1, "q") - qc = QuantumCircuit(q_reg, c_reg) - qc.h(q_reg[0]) - qc.barrier(q_reg[0]) - qc.measure(q_reg[0], c_reg[0]) - qc.reset(q_reg[0]) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 1 - assert mqt_qc.num_classical_bits == 1 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 1 - assert qiskit_qc.num_clbits == 1 - assert qc == qiskit_qc - - -def test_operations() -> None: - """Test roundtrip of operations.""" - qc = QuantumCircuit(3) - qc.id(0) - qc.x(0) - qc.cx(0, 1) - qc.mcx([0, 1], 2) - qc.y(0) - qc.cy(0, 1) - qc.z(0) - qc.cz(0, 1) - qc.h(0) - qc.ch(0, 1) - qc.s(0) - qc.cs(0, 1) - qc.sdg(0) - qc.csdg(0, 1) - qc.t(0) - qc.tdg(0) - qc.sx(0) - qc.csx(0, 1) - qc.sxdg(0) - qc.r(0.5, 0.5, 0) - qc.rx(0.5, 0) - qc.crx(0.5, 0, 1) - qc.ry(0.5, 0) - qc.cry(0.5, 0, 1) - qc.rz(0.5, 0) - qc.crz(0.5, 0, 1) - qc.p(0.5, 0) - qc.cp(0.5, 0, 1) - qc.mcp(0.5, [0, 1], 2) - qc.u(0.5, 0.5, 0.5, 0) - qc.swap(0, 1) - qc.iswap(0, 1) - qc.dcx(0, 1) - qc.ecr(0, 1) - qc.rxx(0.5, 0, 1) - qc.ryy(0.5, 0, 1) - qc.rzz(0.5, 0, 1) - qc.rzx(0.5, 0, 1) - qc.append(U2Gate(0.5, 0.5), [0]) - qc.append(XXMinusYYGate(0.5, 0.5), [0, 1]) - qc.append(XXPlusYYGate(0.5, 0.5), [0, 1]) - qc.rccx(0, 1, 2) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 3 - assert mqt_qc.num_ops == len(qc) - assert mqt_qc.is_variable_free() - for op in mqt_qc: - assert isinstance(op, StandardOperation) - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc.num_qubits == 3 - assert len(qiskit_qc) == len(qc) - - -@pytest.mark.parametrize("ctrl_state", ["0", "1"]) -def test_controlled_rccx(ctrl_state: str) -> None: - """Controlled RCCX imports via gate definition and round-trips.""" - qc = QuantumCircuit(4) - qc.append(RCCXGate().control(1, ctrl_state=ctrl_state), [0, 1, 2, 3]) - - mqt_qc = qiskit_to_mqt(qc) - assert len(mqt_qc) == 1 - assert isinstance(mqt_qc[0], CompoundOperation) - - qiskit_qc = mqt_to_qiskit(mqt_qc) - assert Operator(qc).equiv(Operator(qiskit_qc)) - - -def test_symbolic() -> None: - """Test import of symbolic parameters.""" - qc = QuantumCircuit(1) - lambda_ = Parameter("lambda") - phi = Parameter("phi") - theta = Parameter("theta") - qc.rx(2 * theta + phi / 2 - lambda_ + 2, 0) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - - assert mqt_qc.num_qubits == 1 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "rx" - assert isinstance(mqt_qc[0], SymbolicOperation) - assert isinstance(mqt_qc[0].get_parameter(0), Expression) - expr = cast("Expression", mqt_qc[0].get_parameter(0)) - print(expr) - assert expr.num_terms() == 3 - coeffs_vars = {(term.coefficient, term.variable.name) for term in expr.terms} - assert (-1, "lambda") in coeffs_vars - assert (0.5, "phi") in coeffs_vars - assert (2, "theta") in coeffs_vars - assert expr.constant == 2 - assert not mqt_qc.is_variable_free() - - with pytest.raises(NotImplementedError): - mqt_to_qiskit(mqt_qc) - - qc = qc.assign_parameters({lambda_: 0, phi: 0, theta: 0}) - mqt_qc = qiskit_to_mqt(qc) - assert mqt_qc.is_variable_free() - assert mqt_qc[0].parameter[0] == 2 - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - assert qiskit_qc[0].operation.params[0] == 2 - - -def test_symbolic_two_qubit() -> None: - """Test import of symbolic two-qubit gate.""" - qc = QuantumCircuit(2) - theta = Parameter("theta") - qc.rxx(theta, 0, 1) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - - assert mqt_qc.num_qubits == 2 - assert mqt_qc.num_ops == 1 - assert mqt_qc[0].name.strip() == "rxx" - assert isinstance(mqt_qc[0], SymbolicOperation) - assert isinstance(mqt_qc[0].get_parameter(0), Expression) - expr = cast("Expression", mqt_qc[0].get_parameter(0)) - assert expr.num_terms() == 1 - assert expr.constant == 0 - assert not mqt_qc.is_variable_free() - - with pytest.raises(NotImplementedError): - mqt_to_qiskit(mqt_qc) - - -@pytest.mark.parametrize( - ("condition_callable", "comparison_kind"), - [ - (expr.equal, ComparisonKind.eq), - (expr.not_equal, ComparisonKind.neq), - (expr.less, ComparisonKind.lt), - (expr.less_equal, ComparisonKind.leq), - (expr.greater, ComparisonKind.gt), - (expr.greater_equal, ComparisonKind.geq), - ], -) -def test_if_else_operation_register( - condition_callable: Callable[[Any, Any], Any], - comparison_kind: ComparisonKind, -) -> None: - """Test import of if-else operation on register.""" - then_qc = QuantumCircuit(1) - then_qc.x(0) - - else_qc = QuantumCircuit(1) - else_qc.y(0) - - qc = QuantumCircuit(1, 1) - qc.append( - instruction=IfElseOp(condition_callable(qc.cregs[0], 0), then_qc, else_qc), - qargs=qc.qubits, - ) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 1 - assert mqt_qc.num_ops == 1 - if_else_operation = mqt_qc[0] - assert isinstance(if_else_operation, IfElseOperation) - assert if_else_operation.control_register is not None - assert if_else_operation.control_bit is None - assert if_else_operation.expected_value_register == 0 - assert if_else_operation.comparison_kind == comparison_kind - then_operation = if_else_operation.then_operation - assert isinstance(then_operation, StandardOperation) - then_operation.type_ = OpType.x - else_operation = if_else_operation.else_operation - assert isinstance(else_operation, StandardOperation) - else_operation.type_ = OpType.y - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - - qasm_input = dumps(qc) - qasm_output = dumps(qiskit_qc) - assert qasm_input == qasm_output - - -def test_if_else_operation_bit() -> None: - """Test import of if-else operation.""" - then_qc = QuantumCircuit(1) - then_qc.x(0) - - else_qc = QuantumCircuit(1) - else_qc.y(0) - - qc = QuantumCircuit(1, 1) - qc.if_else( - (qc.clbits[0], True), - then_qc, - else_qc, - [qc.qubits[0]], - [], - ) - print(qc) - - mqt_qc = qiskit_to_mqt(qc) - print(mqt_qc) - assert mqt_qc.num_qubits == 1 - assert mqt_qc.num_ops == 1 - if_else_operation = mqt_qc[0] - assert isinstance(if_else_operation, IfElseOperation) - assert if_else_operation.control_register is None - assert if_else_operation.control_bit == 0 - assert if_else_operation.expected_value_bit is True - assert if_else_operation.comparison_kind == ComparisonKind.eq - then_operation = if_else_operation.then_operation - assert isinstance(then_operation, StandardOperation) - then_operation.type_ = OpType.x - else_operation = if_else_operation.else_operation - assert isinstance(else_operation, StandardOperation) - else_operation.type_ = OpType.y - - qiskit_qc = mqt_to_qiskit(mqt_qc) - print(qiskit_qc) - - qasm_input = dumps(qc) - qasm_output = dumps(qiskit_qc) - - assert qasm_input == qasm_output - - -def test_trivial_initial_layout_multiple_registers() -> None: - """Test that trivial initial layout import works with multiple registers. - - Correctly inferring the initial layout is not an easy task; especially when - multiple registers are involved. This test checks that the initial layout - is imported properly from a circuit with multiple registers that are not - sorted alphabetically. - """ - a = QuantumRegister(2, "a") - b = QuantumRegister(2, "b") - qc = QuantumCircuit(b, a) - print(qc) - - initial_layout = [0, 1, 2, 3] - qc_transpiled = transpile(qc, initial_layout=initial_layout) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - for k, v in [(0, 0), (1, 1), (2, 2), (3, 3)]: - assert mqt_qc.initial_layout[k] == v - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_non_trivial_initial_layout_multiple_registers() -> None: - """Test that non-trivial initial layout import works with multiple registers.""" - a = QuantumRegister(2, "a") - b = QuantumRegister(2, "b") - qc = QuantumCircuit(b, a) - print(qc) - - initial_layout = [3, 2, 1, 0] - qc_transpiled = transpile(qc, initial_layout=initial_layout) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - for k, v in enumerate(initial_layout): - assert mqt_qc.initial_layout[k] == v - assert mqt_qc.output_permutation[k] == v - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_non_symmetric_initial_layout_multiple_registers() -> None: - """Test that non-symmetric initial layout import works with multiple registers.""" - a = QuantumRegister(2, "a") - b = QuantumRegister(1, "b") - qc = QuantumCircuit(b, a) - print(qc) - - initial_layout = [1, 2, 0] - qc_transpiled = transpile(qc, initial_layout=initial_layout) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - for k, v in [(0, 2), (1, 0), (2, 1)]: - assert mqt_qc.initial_layout[k] == v - assert mqt_qc.output_permutation[k] == v - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_initial_layout_with_ancilla_in_front() -> None: - """Test that initial layout import works with ancilla in front.""" - a = QuantumRegister(2, "a") - b_anc = AncillaRegister(1, "b") - qc = QuantumCircuit(b_anc, a) - qc.x(0) - print(qc) - - initial_layout = [0, 1, 2] - qc_transpiled = transpile(qc, initial_layout=initial_layout) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - for k, v in [(0, 0), (1, 1), (2, 2)]: - assert mqt_qc.initial_layout[k] == v - assert mqt_qc.num_ancilla_qubits == 1 - assert mqt_qc.is_circuit_qubit_ancillary(0) - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_initial_layout_with_ancilla_in_back() -> None: - """Test that initial layout import works with ancilla in back.""" - a = QuantumRegister(2, "a") - b_anc = AncillaRegister(1, "b") - qc = QuantumCircuit(a, b_anc) - qc.x(2) - print(qc) - - initial_layout = [0, 1, 2] - qc_transpiled = transpile(qc, initial_layout=initial_layout) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - for k, v in [(0, 0), (1, 1), (2, 2)]: - assert mqt_qc.initial_layout[k] == v - assert mqt_qc.num_ancilla_qubits == 1 - assert mqt_qc.is_circuit_qubit_ancillary(2) - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_symbolic_global_phase() -> None: - """Test whether symbolic global phase works properly.""" - qc = QuantumCircuit(1) - theta = Parameter("theta") - qc.global_phase = theta - - with pytest.warns(RuntimeWarning): - mqt_qc = qiskit_to_mqt(qc) - - assert mqt_qc.global_phase == 0 - - -def test_final_layout_without_permutation() -> None: - """Test that the output permutation remains the same as the initial layout when routing is not performed.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(0, 1) - qc.cx(0, 2) - print(qc) - - initial_layout = [1, 2, 0] - seed = 123 - qc_transpiled = transpile(qc, initial_layout=initial_layout, seed_transpiler=seed) - print(qc_transpiled) - - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - assert mqt_qc.initial_layout == {0: 2, 1: 0, 2: 1} - assert mqt_qc.output_permutation == mqt_qc.initial_layout - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -# test fixture for the backend using GenericBackendV2 -@pytest.fixture -def backend() -> GenericBackendV2: - """Fixture for the backend using GenericBackendV2. - - Returns: - A generic five-qubit backend to be used for compilation. - """ - return GenericBackendV2( - num_qubits=5, - basis_gates=["id", "rz", "sx", "x", "cx", "reset"], - coupling_map=[[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3]], - ) - - -def test_final_layout_with_permutation(backend: GenericBackendV2) -> None: - """Test that the output permutation gets updated correctly when routing is performed.""" - qc = QuantumCircuit(3) - qc.h(0) - qc.cx(1, 0) - qc.cx(1, 2) - qc.measure_all() - print(qc) - - initial_layout = [1, 0, 3] - seed = 123 - qc_transpiled = transpile(qc, backend, initial_layout=initial_layout, seed_transpiler=seed) - print(qc_transpiled) - - assert qc_transpiled.layout is not None - final_index_layout = qc_transpiled.layout.final_index_layout() - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - # Check the initial layout is properly translated - assert mqt_qc.initial_layout == {0: 1, 1: 0, 3: 2, 2: 3, 4: 4} - # Check initialize_io_mapping doesn't change the final_layout - assert mqt_qc.output_permutation == dict(enumerate(final_index_layout)) - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_final_layout_with_permutation_ancilla_in_front_and_back(backend: GenericBackendV2) -> None: - """Test that permutation update is correct with multiple registers and ancilla qubits.""" - e = QuantumRegister(2, "e") - f_anc = AncillaRegister(1, "f") - b_anc = AncillaRegister(2, "b") - qc = QuantumCircuit(f_anc, e, b_anc) - qc.h(0) - qc.cx(1, 0) - qc.cx(1, 2) - qc.measure_all() - print(qc) - - initial_layout = [1, 0, 3, 2, 4] - seed = 123 - qc_transpiled = transpile(qc, backend, initial_layout=initial_layout, seed_transpiler=seed) - print(qc_transpiled) - - assert qc_transpiled.layout is not None - routing_permutation = qc_transpiled.layout.routing_permutation() - mqt_qc = qiskit_to_mqt(qc_transpiled) - print(mqt_qc) - # Check the initial layout is properly translated - assert mqt_qc.initial_layout == {0: 1, 1: 0, 3: 2, 2: 3, 4: 4} - # Check that output_permutation matches the result of applying the routing permutation to input_layout - assert mqt_qc.output_permutation == {idx: routing_permutation[key] for idx, key in enumerate(initial_layout)} - - qiskit_qc = mqt_to_qiskit(mqt_qc, set_layout=True) - print(qiskit_qc) - mqt_qc_2 = qiskit_to_mqt(qiskit_qc) - print(mqt_qc_2) - assert mqt_qc.initial_layout == mqt_qc_2.initial_layout - assert mqt_qc.output_permutation == mqt_qc_2.output_permutation - - -def test_empty_quantum_register() -> None: - """Test an empty quantum register (valid in Qiskit) is handled correctly.""" - qr = QuantumRegister(0) - qc = QuantumCircuit(qr) - mqt_qc = qiskit_to_mqt(qc) - assert mqt_qc.num_qubits == 0 - assert mqt_qc.num_ops == 0 - - -def test_empty_classical_register() -> None: - """Test an empty classical register (valid in Qiskit) is handled correctly.""" - cr = ClassicalRegister(0) - qc = QuantumCircuit(cr) - mqt_qc = qiskit_to_mqt(qc) - assert mqt_qc.num_classical_bits == 0 - assert mqt_qc.num_ops == 0 diff --git a/test/python/qdmi/test_qdmi.py b/test/python/qdmi/test_qdmi.py index 5ac24a7275..cb5bd5e0e8 100644 --- a/test/python/qdmi/test_qdmi.py +++ b/test/python/qdmi/test_qdmi.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""Test the quantum computation IR.""" +"""Test the QDMI Python bindings.""" from __future__ import annotations @@ -768,6 +768,22 @@ def simulator_job(ddsim_device: Device) -> Job: return ddsim_device.submit_job(qasm3_program, ProgramFormat.QASM3, num_shots=0) +def test_empty_qasm_program_has_a_zero_width_vacuum_result(ddsim_device: Device) -> None: + """Return the zero-qubit vacuum for an empty QASM program.""" + program = "OPENQASM 3.0;" + + sample_job = ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=4) + sample_job.wait() + assert sample_job.get_counts() == {"": 4} + + state_job = ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=0) + state_job.wait() + assert state_job.get_dense_statevector() == [1 + 0j] + assert state_job.get_dense_probabilities() == [1.0] + assert state_job.get_sparse_statevector() == {"": 1 + 0j} + assert state_job.get_sparse_probabilities() == {"": 1.0} + + def test_simulator_job_get_dense_state_vector_returns_valid_state(simulator_job: Job) -> None: """Test that get_dense_statevector() returns the correct Bell state.""" simulator_job.wait() diff --git a/test/python/test_load.py b/test/python/test_load.py deleted file mode 100644 index a339fba80e..0000000000 --- a/test/python/test_load.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Test the circuit IO functionality.""" - -from __future__ import annotations - -from pathlib import Path - -from qiskit import QuantumCircuit -from qiskit.qasm2 import dumps - -from mqt.core import load -from mqt.core.ir import QuantumComputation - - -def test_loading_quantum_computation() -> None: - """Test that directly loading a ``QuantumComputation`` works.""" - qc = QuantumComputation(3, 3) - qc.h(0) - qc.cx(0, 1) - qc.mcx({0, 1}, 2) - qc.measure(range(3), range(3)) - - qc_loaded = load(qc) - print(qc_loaded) - - # check that the same object is returned - assert qc is qc_loaded - - -def test_loading_file() -> None: - """Test whether importing a simple QASM file works.""" - qasm = "qreg q[2];\ncreg c[2];\nh q[0];\ncx q[0], q[1];\nmeasure q -> c;\n" - Path("test_1.qasm").write_text(qasm, encoding="utf-8") - - # load the file - qc = load("test_1.qasm") - print(qc) - - # check the result - assert isinstance(qc, QuantumComputation) - qc_qasm = qc.qasm2_str() - - assert qasm in qc_qasm - - # remove the file - Path("test_1.qasm").unlink() - - -def test_loading_file_from_path() -> None: - """Test whether importing a simple QASM file works.""" - qasm = "qreg q[2];\ncreg c[2];\nh q[0];\ncx q[0], q[1];\nmeasure q -> c;\n" - path = Path("test_2.qasm") - path.write_text(qasm, encoding="utf-8") - - # load the file - qc = load(path) - print(qc) - - # check the result - assert isinstance(qc, QuantumComputation) - qc_qasm = qc.qasm2_str() - - assert qasm in qc_qasm - - # remove the file - path.unlink() - - -def test_loading_nonexistent_file() -> None: - """Test whether trying to load a non-existent file raises an error. - - Raises: - AssertionError: If no error is raised. - """ - try: - load("nonexistent.qasm") - except FileNotFoundError: - pass - else: - msg = "No error was raised when trying to load a non-existent file." - raise AssertionError(msg) - - -def test_loading_qiskit_circuit() -> None: - """Test whether importing a Qiskit circuit works.""" - qiskit_circuit = QuantumCircuit(2, 2) - qiskit_circuit.h(0) - qiskit_circuit.cx(0, 1) - qiskit_circuit.measure(range(2), range(2)) - qasm = dumps(qiskit_circuit) - - # load the circuit - qc = load(qiskit_circuit) - print(qc) - - # check the result - assert isinstance(qc, QuantumComputation) - qc_qasm = qc.qasm2_str() - - # remove any whitespace from both QASM strings and check for equality - assert "".join(qasm.split()) in "".join(qc_qasm.split()) - - -def test_loading_qasm2_string() -> None: - """Test whether importing a QASM2 string works.""" - qasm = 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0], q[1];\nmeasure q -> c;\n' - - # load the circuit - qc = load(qasm) - print(qc) - - # check the result - assert isinstance(qc, QuantumComputation) - qc_qasm = qc.qasm2_str() - - assert qasm in qc_qasm - - -def test_loading_qasm3_string() -> None: - """Test whether importing a QASM3 string works.""" - qasm = 'OPENQASM 3.0;\ninclude "stdgates.inc";\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n' - - # load the circuit - qc = load(qasm) - print(qc) - - # check the result - assert isinstance(qc, QuantumComputation) - qc_qasm = qc.qasm3_str() - - assert qasm in qc_qasm diff --git a/test/python/test_mlir.py b/test/python/test_mlir.py index e65e20e0a6..0d50cb68ae 100644 --- a/test/python/test_mlir.py +++ b/test/python/test_mlir.py @@ -22,7 +22,6 @@ from qiskit.circuit import Gate, library from qiskit.quantum_info import Operator -from mqt.core.ir import QuantumComputation from mqt.core.mlir import ( CompilerTarget, JeffProgram, @@ -177,12 +176,6 @@ def test_compile_program_qasm_file(tmp_path: Path) -> None: _assert_bell_program(result, measured=True) -def test_compile_program_rejects_quantum_computation() -> None: - """Reject the removed legacy compiler input.""" - with pytest.raises(RuntimeError, match="is not supported"): - compile_program(QuantumComputation(1)) # ty: ignore[invalid-argument-type] - - @requires_qiskit_translation def test_compile_program_qiskit_quantum_circuit() -> None: """Compile a ``QuantumCircuit``.""" diff --git a/test/python/test_mlir_qiskit_translation.py b/test/python/test_mlir_qiskit_translation.py index 7bdee690b1..0d989bf84d 100644 --- a/test/python/test_mlir_qiskit_translation.py +++ b/test/python/test_mlir_qiskit_translation.py @@ -41,7 +41,6 @@ from qiskit.quantum_info import Operator, random_unitary from mqt.core.mlir import CompilerTarget, QCProgram, compile_program -from mqt.core.plugins.qiskit import qiskit_to_mqt if TYPE_CHECKING: from collections.abc import Callable @@ -2866,16 +2865,12 @@ def test_target_aware_qiskit_export_rejects_dynamic_qubits(allocation: str) -> N program.to_qiskit(target=target) -def test_unknown_version_is_rejected_without_affecting_existing_conversion( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Keep direct version dispatch independent of existing conversion.""" +def test_unknown_version_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None: + """Reject unsupported Qiskit versions.""" monkeypatch.setattr(qiskit, "__version__", "2.6.0") with pytest.raises(RuntimeError, match=r"installed version '2\.6\.0'.*>=2\.5\.0,<2\.6\.0"): QCProgram.from_qiskit(QuantumCircuit(1)) - assert qiskit_to_mqt(QuantumCircuit(1)).num_qubits == 1 - def test_mlir_binding_import_does_not_import_qiskit() -> None: """Keep importing the MLIR extension independent of optional Qiskit.""" diff --git a/test/python/test_python_surface.py b/test/python/test_python_surface.py new file mode 100644 index 0000000000..3465dd2cbf --- /dev/null +++ b/test/python/test_python_surface.py @@ -0,0 +1,44 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for the public Python package surface.""" + +from __future__ import annotations + +import importlib + +import pytest + +import mqt.core + + +@pytest.mark.parametrize( + "module", + [ + "mqt.core.ir", + "mqt.core.load", + "mqt.core.plugins.qiskit.mqt_to_qiskit", + "mqt.core.plugins.qiskit.qiskit_to_mqt", + ], +) +def test_legacy_circuit_modules_are_absent(module: str) -> None: + """Omit removed legacy modules.""" + with pytest.raises(ModuleNotFoundError): + importlib.import_module(module) + + +def test_load_is_not_exported_from_mqt_core() -> None: + """Omit load from top-level exports.""" + assert not hasattr(mqt.core, "load") + + +def test_classic_qiskit_converters_are_not_exported() -> None: + """Omit classic Qiskit converters from plugin exports.""" + plugin = importlib.import_module("mqt.core.plugins.qiskit") + assert not hasattr(plugin, "mqt_to_qiskit") + assert not hasattr(plugin, "qiskit_to_mqt") diff --git a/test/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt index fa7e935a8e..1bba045502 100644 --- a/test/qdmi/CMakeLists.txt +++ b/test/qdmi/CMakeLists.txt @@ -17,6 +17,9 @@ if(TARGET MQT::CoreQDMI) target_compile_definitions( ${TARGET_NAME} PRIVATE "MQT_CORE_QDMI_SLURM_TEST_DEVICE=\"$\"") + if(TARGET MQT::CoreQDMI_DDSIM_Device) + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_QDMI_HAS_DDSIM_DEVICE) + endif() add_dependencies(${TARGET_NAME} mqt-core-qdmi-session-device) mqt_copy_qdmi_runtime(${TARGET_NAME}) endif() diff --git a/test/qdmi/devices/dd/CMakeLists.txt b/test/qdmi/devices/dd/CMakeLists.txt index df431fb9fb..b957c11e50 100644 --- a/test/qdmi/devices/dd/CMakeLists.txt +++ b/test/qdmi/devices/dd/CMakeLists.txt @@ -35,11 +35,8 @@ if(TARGET MQT::CoreQDMI_DDSIM_Device) QIR_FILES_DIR="${PROJECT_SOURCE_DIR}/test/circuits") target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - if(BUILD_MQT_CORE_MLIR) - llvm_map_components_to_libnames(llvm_native_libs asmparser bitwriter core support) - target_link_libraries(${TARGET_NAME} PRIVATE ${llvm_native_libs}) - target_compile_definitions(${TARGET_NAME} PRIVATE BUILD_MQT_CORE_MLIR) - endif() + llvm_map_components_to_libnames(llvm_native_libs asmparser bitwriter core support) + target_link_libraries(${TARGET_NAME} PRIVATE ${llvm_native_libs}) # On Windows, we need to copy the DLL to the test executable directory if(WIN32) diff --git a/test/qdmi/devices/dd/concurrency_test.cpp b/test/qdmi/devices/dd/concurrency_test.cpp index 4cbc6fbacf..38a949219b 100644 --- a/test/qdmi/devices/dd/concurrency_test.cpp +++ b/test/qdmi/devices/dd/concurrency_test.cpp @@ -125,7 +125,6 @@ TEST(Concurrency, ConcurrentCheckDuringRun) { poller.join(); } -#ifdef BUILD_MQT_CORE_MLIR TEST(Concurrency, ConcurrentQIRJobsOwnTheirRuntimeState) { constexpr size_t numJobs = 4; constexpr size_t shots = 1024; @@ -154,4 +153,3 @@ TEST(Concurrency, ConcurrentQIRJobsOwnTheirRuntimeState) { keys, [](const auto& key) { return key == "00" || key == "11"; })); } } -#endif diff --git a/test/qdmi/devices/dd/device_properties_test.cpp b/test/qdmi/devices/dd/device_properties_test.cpp index e922d1ea80..0781bc0387 100644 --- a/test/qdmi/devices/dd/device_properties_test.cpp +++ b/test/qdmi/devices/dd/device_properties_test.cpp @@ -137,12 +137,10 @@ TEST(DeviceProperties, SupportedProgramFormats) { const std::vector expected = { QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QASM3, -#ifdef BUILD_MQT_CORE_MLIR QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, -#endif }; EXPECT_EQ(formats, expected); } diff --git a/test/qdmi/devices/dd/error_handling_test.cpp b/test/qdmi/devices/dd/error_handling_test.cpp index 17e3d03886..7b9885b0d8 100644 --- a/test/qdmi/devices/dd/error_handling_test.cpp +++ b/test/qdmi/devices/dd/error_handling_test.cpp @@ -315,3 +315,20 @@ TEST_F(ErrorHandling, MalformedProgramFailsForBothModes) { EXPECT_EQ(js, QDMI_JOB_STATUS_FAILED); } } + +TEST_F(ErrorHandling, QASM3PartiallyInitializedOutputFails) { + constexpr std::string_view program = R"qasm(OPENQASM 3.0; +bit[2] c; +qubit[2] q; +c[0] = measure q[0]; +)qasm"; + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + ASSERT_EQ(qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, program), + QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::setShots(j.job, 1), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + QDMI_Job_Status status{}; + ASSERT_EQ(MQT_DDSIM_QDMI_device_job_check(j.job, &status), QDMI_SUCCESS); + EXPECT_EQ(status, QDMI_JOB_STATUS_FAILED); +} diff --git a/test/qdmi/devices/dd/helpers/circuits.hpp b/test/qdmi/devices/dd/helpers/circuits.hpp index b6f7564ff1..d2af067a39 100644 --- a/test/qdmi/devices/dd/helpers/circuits.hpp +++ b/test/qdmi/devices/dd/helpers/circuits.hpp @@ -12,6 +12,24 @@ namespace qdmi_test { +inline constexpr auto QASM2_BELL_SAMPLING = R"( +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c[2]; +h q[0]; +cx q[0], q[1]; +measure q -> c; +)"; + +inline constexpr auto QASM2_BELL_STATE = R"( +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +h q[0]; +cx q[0], q[1]; +)"; + inline constexpr auto QASM3_BELL_SAMPLING = R"( OPENQASM 3; include "stdgates.inc"; diff --git a/test/qdmi/devices/dd/job_parameters_test.cpp b/test/qdmi/devices/dd/job_parameters_test.cpp index f67fffbd68..e127c70a7b 100644 --- a/test/qdmi/devices/dd/job_parameters_test.cpp +++ b/test/qdmi/devices/dd/job_parameters_test.cpp @@ -133,12 +133,10 @@ TEST(JobParameters, ProgramFormatSupport) { for (QDMI_Program_Format fmt : { QDMI_PROGRAM_FORMAT_QASM2, QDMI_PROGRAM_FORMAT_QASM3, -#ifdef BUILD_MQT_CORE_MLIR QDMI_PROGRAM_FORMAT_QIRBASESTRING, QDMI_PROGRAM_FORMAT_QIRBASEMODULE, QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, -#endif }) { EXPECT_EQ(MQT_DDSIM_QDMI_device_job_set_parameter( j.job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, @@ -148,12 +146,6 @@ TEST(JobParameters, ProgramFormatSupport) { // Unsupported → NOTSUPPORTED for (QDMI_Program_Format fmt : { -#ifndef BUILD_MQT_CORE_MLIR - QDMI_PROGRAM_FORMAT_QIRBASESTRING, - QDMI_PROGRAM_FORMAT_QIRBASEMODULE, - QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, - QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE, -#endif QDMI_PROGRAM_FORMAT_CALIBRATION, QDMI_PROGRAM_FORMAT_QPY, QDMI_PROGRAM_FORMAT_IQMJSON, diff --git a/test/qdmi/devices/dd/results_sampling_test.cpp b/test/qdmi/devices/dd/results_sampling_test.cpp index d699ff750f..c23b0a7eda 100644 --- a/test/qdmi/devices/dd/results_sampling_test.cpp +++ b/test/qdmi/devices/dd/results_sampling_test.cpp @@ -17,13 +17,11 @@ #include "mqt_ddsim_qdmi/device.h" #include -#ifdef BUILD_MQT_CORE_MLIR #include #include #include #include #include -#endif #include #include @@ -91,7 +89,6 @@ class HistogramTest : public ::testing::Test { } }; -#ifdef BUILD_MQT_CORE_MLIR class QIRHistogramTestModule : public HistogramTest { protected: static std::string getProgram(const std::string_view file) { @@ -113,7 +110,6 @@ class QIRHistogramTestModule : public HistogramTest { }; class QIRHistogramTestString : public HistogramTest {}; -#endif } // namespace @@ -123,7 +119,40 @@ TEST_F(HistogramTest, QASM3Program) { checkHistogram(runProgram(format, program)); } -#ifdef BUILD_MQT_CORE_MLIR +TEST_F(HistogramTest, QASM3ProgramWithoutMeasurements) { + constexpr QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_QASM3; + constexpr std::string_view program = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +cx q[0], q[1]; +)qasm"; + checkHistogram(runProgram(format, program)); +} + +TEST_F(HistogramTest, QASM2Program) { + constexpr QDMI_Program_Format format = QDMI_PROGRAM_FORMAT_QASM2; + constexpr std::string_view program = qdmi_test::QASM2_BELL_SAMPLING; + checkHistogram(runProgram(format, program)); +} + +TEST_F(HistogramTest, QASM3MultipleRegistersFollowQiskitOrder) { + constexpr std::string_view program = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +bit[2] c0; +bit c1; +qubit[3] q; +x q[0]; +x q[2]; +c0[0] = measure q[0]; +c0[1] = measure q[1]; +c1 = measure q[2]; +)qasm"; + const auto [keys, values] = runProgram(QDMI_PROGRAM_FORMAT_QASM3, program); + EXPECT_EQ(keys, std::vector{"101"}); + EXPECT_EQ(values, std::vector{NUM_SHOTS}); +} + TEST_F(QIRHistogramTestModule, BaseStatic) { constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASEMODULE; checkHistogram(runProgram(format, getProgram("BellPairStatic.ll"))); @@ -168,7 +197,6 @@ TEST_F(QIRHistogramTestString, AdaptiveRecordOutputs) { checkSmokeHistogram( runProgram(format, qdmi_test::getQIRProgram("AdaptiveRecordOutputs.ll"))); } -#endif TEST_F(HistogramTest, SeedReproducesQASMSampling) { constexpr auto format = QDMI_PROGRAM_FORMAT_QASM3; @@ -176,13 +204,11 @@ TEST_F(HistogramTest, SeedReproducesQASMSampling) { EXPECT_EQ(runProgram(format, program, 7), runProgram(format, program, 7)); } -#ifdef BUILD_MQT_CORE_MLIR TEST_F(QIRHistogramTestString, SeedReproducesQIRSampling) { constexpr auto format = QDMI_PROGRAM_FORMAT_QIRBASESTRING; const auto program = qdmi_test::getQIRProgram("BellPairStatic.ll"); EXPECT_EQ(runProgram(format, program, 7), runProgram(format, program, 7)); } -#endif TEST(ResultsSampling, BufferTooSmallErrors) { const qdmi_test::SessionGuard s{}; diff --git a/test/qdmi/devices/dd/results_statevector_test.cpp b/test/qdmi/devices/dd/results_statevector_test.cpp index 5dc9ccf8a5..d2270ddd59 100644 --- a/test/qdmi/devices/dd/results_statevector_test.cpp +++ b/test/qdmi/devices/dd/results_statevector_test.cpp @@ -22,8 +22,80 @@ #include #include #include +#include #include +namespace { + +void expectBellState(const QDMI_Program_Format format, + const std::string_view program) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + ASSERT_EQ(qdmi_test::setProgram(j.job, format, program), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::setShots(j.job, 0), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + + const auto vec = qdmi_test::getDenseState(j.job); + ASSERT_EQ(vec.size(), 4U); + constexpr double invSqrt2 = 1.0 / std::numbers::sqrt2; + EXPECT_NEAR(std::abs(vec[0]), invSqrt2, 1e-6); + EXPECT_NEAR(std::abs(vec[1]), 0.0, 1e-6); + EXPECT_NEAR(std::abs(vec[2]), 0.0, 1e-6); + EXPECT_NEAR(std::abs(vec[3]), invSqrt2, 1e-6); +} + +} // namespace + +TEST(ResultsStatevector, QASM2YieldsBellState) { + expectBellState(QDMI_PROGRAM_FORMAT_QASM2, qdmi_test::QASM2_BELL_STATE); +} + +TEST(ResultsStatevector, QASM2IgnoresFinalMeasurements) { + expectBellState(QDMI_PROGRAM_FORMAT_QASM2, qdmi_test::QASM2_BELL_SAMPLING); +} + +TEST(ResultsStatevector, QASM3YieldsBellState) { + expectBellState(QDMI_PROGRAM_FORMAT_QASM3, qdmi_test::QASM3_BELL_STATE); +} + +TEST(ResultsStatevector, QASM3IgnoresFinalMeasurements) { + expectBellState(QDMI_PROGRAM_FORMAT_QASM3, qdmi_test::QASM3_BELL_SAMPLING); +} + +TEST(ResultsStatevector, EmptyQASM3YieldsVacuumState) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + ASSERT_EQ( + qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, "OPENQASM 3.0;"), + QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::setShots(j.job, 0), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + + const auto vec = qdmi_test::getDenseState(j.job); + ASSERT_EQ(vec.size(), 1U); + EXPECT_EQ(vec.front(), std::complex(1.0, 0.0)); + + const size_t keysSize = + qdmi_test::querySize(j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS); + ASSERT_EQ(keysSize, 1U); + std::vector keys(keysSize); + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results( + j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, keys.size(), + keys.data(), nullptr), + QDMI_SUCCESS); + EXPECT_EQ(keys.front(), '\0'); + + const size_t valuesSize = + qdmi_test::querySize(j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES); + ASSERT_EQ(valuesSize, sizeof(std::complex)); + std::complex value; + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results( + j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, sizeof(value), + &value, nullptr), + QDMI_SUCCESS); + EXPECT_EQ(value, std::complex(1.0, 0.0)); +} + TEST(ResultsStatevector, DenseNormalizedAndBufferTooSmall) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; @@ -106,7 +178,6 @@ TEST(ResultsStatevector, HistogramRequestsInvalidWithShotsZero) { QDMI_ERROR_INVALIDARGUMENT); } -#ifdef BUILD_MQT_CORE_MLIR TEST(ResultsStatevector, QIRBaseStringYieldsBellState) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; @@ -127,4 +198,3 @@ TEST(ResultsStatevector, QIRBaseStringYieldsBellState) { EXPECT_NEAR(std::abs(vec[2]), 0.0, 1e-6); EXPECT_NEAR(std::abs(vec[3]), invSqrt2, 1e-6); } -#endif diff --git a/test/qdmi/driver/CMakeLists.txt b/test/qdmi/driver/CMakeLists.txt index ce984109b1..638daead94 100644 --- a/test/qdmi/driver/CMakeLists.txt +++ b/test/qdmi/driver/CMakeLists.txt @@ -55,12 +55,27 @@ if(TARGET MQT::CoreQDMIDriver) ${DIAGNOSTIC_TARGET_NAME} PRIVATE "MQT_CORE_QDMI_DIAGNOSTIC_CONFIG_FILE=\"${diagnostic_config_file}\"") + set(configured_devices + " {\"id\": \"mqt.sc.default\", \"library\": \"$\", \"prefix\": \"MQT_SC\"}" + ) + set(test_device_libraries "std::pair{\"$\", \"MQT_SC\"}") + if(TARGET MQT::CoreQDMI_DDSIM_Device) + string( + APPEND + configured_devices + ",\n {\"id\": \"mqt.ddsim.default\", \"library\": \"$\", \"prefix\": \"MQT_DDSIM\"}" + ) + string(APPEND test_device_libraries + ", std::pair{\"$\", \"MQT_DDSIM\"}") + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_QDMI_HAS_DDSIM_DEVICE) + endif() + set(config_file "${CMAKE_CURRENT_BINARY_DIR}/$/configured-devices.json") file( GENERATE OUTPUT "${config_file}" CONTENT - "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n {\"id\": \"mqt.sc.default\", \"library\": \"$\", \"prefix\": \"MQT_SC\"},\n {\"id\": \"mqt.ddsim.default\", \"library\": \"$\", \"prefix\": \"MQT_DDSIM\"},\n {\"id\": \"test.disabled\", \"enabled\": false},\n {\"id\": \"broken.example\", \"library\": \"missing-device-library\", \"prefix\": \"BROKEN\"}\n ]\n }\n}\n" + "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n${configured_devices},\n {\"id\": \"test.disabled\", \"enabled\": false},\n {\"id\": \"broken.example\", \"library\": \"missing-device-library\", \"prefix\": \"BROKEN\"}\n ]\n }\n}\n" ) file(READ "${PROJECT_SOURCE_DIR}/json/sc/mqt-core-qdmi-sc-device.json" custom_sc_json) string(REPLACE "MQT SC Default QDMI Device" "Custom SC Driver Device" custom_sc_json @@ -80,8 +95,7 @@ if(TARGET MQT::CoreQDMIDriver) "MQT_CORE_QDMI_CUSTOM_SC_FILE=\"${custom_sc_file}\"" "MQT_CORE_QDMI_DEFAULT_SC_FILE=\"${PROJECT_SOURCE_DIR}/json/sc/mqt-core-qdmi-sc-device.json\"" "MQT_CORE_QDMI_SC_LIBRARY=\"$\"" - "TEST_DEVICE_LIBRARIES=std::array{ std::pair{\"$\", \"MQT_SC\"}, std::pair{\"$\", \"MQT_DDSIM\"} }" - ) + "TEST_DEVICE_LIBRARIES=std::array{ ${test_device_libraries} }") mqt_copy_qdmi_runtime(${TARGET_NAME}) mqt_copy_qdmi_runtime(${TARGET_NAME} mqt-core-qdmi-metadata-device) diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index ca191dc4c6..4aed6e1c7c 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -57,7 +57,7 @@ MATCHER_P2(IsBetween, a, b, } // namespace } // namespace testing -namespace qc { +namespace { namespace { @@ -840,8 +840,12 @@ TEST_P(DriverTest, QueryNeedsCalibration) { EXPECT_EQ(ret, QDMI_SUCCESS); EXPECT_THAT(needsCalibration, testing::AnyOf(0, 1)); } -constexpr std::array DEVICES{"MQT SC Default QDMI Device", - "MQT Core DDSIM QDMI Device"}; +constexpr std::array DEVICES{ + "MQT SC Default QDMI Device", +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE + "MQT Core DDSIM QDMI Device", +#endif +}; namespace { void registerSessionTestDevice() { @@ -897,10 +901,12 @@ TEST(ConfiguredDriverTest, ExposesWorkingDefinitionsAndIsolatesFailures) { std::vector names; std::ranges::transform(devices, std::back_inserter(names), queryName); - EXPECT_THAT(names, - testing::UnorderedElementsAre("IQM Emerald", "IQM Garnet", - "MQT SC Default QDMI Device", - "MQT Core DDSIM QDMI Device")); + std::vector expectedNames{"IQM Emerald", "IQM Garnet", + "MQT SC Default QDMI Device"}; +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE + expectedNames.emplace_back("MQT Core DDSIM QDMI Device"); +#endif + EXPECT_THAT(names, testing::UnorderedElementsAreArray(expectedNames)); QDMI_session_free(session); } @@ -1681,4 +1687,4 @@ INSTANTIATE_TEST_SUITE_P( std::erase(name, ')'); return name; }); -} // namespace qc +} // namespace diff --git a/test/qdmi/registry/CMakeLists.txt b/test/qdmi/registry/CMakeLists.txt index 0860295347..3bf9e19f70 100644 --- a/test/qdmi/registry/CMakeLists.txt +++ b/test/qdmi/registry/CMakeLists.txt @@ -12,5 +12,8 @@ if(TARGET MQT::CoreQDMIDriver) package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_device_registry.cpp) target_include_directories(${TARGET_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/qdmi/driver ${PROJECT_SOURCE_DIR}/test) + if(TARGET MQT::CoreQDMI_DDSIM_Device) + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_QDMI_HAS_DDSIM_DEVICE) + endif() mqt_copy_qdmi_runtime(${TARGET_NAME}) endif() diff --git a/test/qdmi/registry/test_device_registry.cpp b/test/qdmi/registry/test_device_registry.cpp index 81fcdee107..eb69898c1c 100644 --- a/test/qdmi/registry/test_device_registry.cpp +++ b/test/qdmi/registry/test_device_registry.cpp @@ -348,11 +348,18 @@ TEST(DeviceRegistry, DiscoversGeneratedBuildTreeManifests) { const ScopedEnvironmentVariable configJson("MQT_CORE_QDMI_CONFIG_JSON", ""); const qdmi::detail::DeviceRegistry registry; +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE ASSERT_EQ(registry.definitions().size(), 4); EXPECT_EQ(registry.definitions().at(0).id, "mqt.ddsim.default"); EXPECT_EQ(registry.definitions().at(1).id, "mqt.sc.default"); EXPECT_EQ(registry.definitions().at(2).id, "mqt.sc.iqm.emerald"); EXPECT_EQ(registry.definitions().at(3).id, "mqt.sc.iqm.garnet"); +#else + ASSERT_EQ(registry.definitions().size(), 3); + EXPECT_EQ(registry.definitions().at(0).id, "mqt.sc.default"); + EXPECT_EQ(registry.definitions().at(1).id, "mqt.sc.iqm.emerald"); + EXPECT_EQ(registry.definitions().at(2).id, "mqt.sc.iqm.garnet"); +#endif for (const auto& definition : registry.definitions()) { EXPECT_TRUE(std::filesystem::is_regular_file(definition.library)); } diff --git a/test/qdmi/test_client.cpp b/test/qdmi/test_client.cpp index d388c0d2a1..ec98052f74 100644 --- a/test/qdmi/test_client.cpp +++ b/test/qdmi/test_client.cpp @@ -78,6 +78,7 @@ class OperationTest : public DeviceTest { void SetUp() override { operations = device.getOperations(); } }; +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE class DDSimulatorDeviceTest : public testing::Test { protected: Device device; @@ -130,6 +131,7 @@ cx q[0], q[1]; return device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 0); } }; +#endif } // namespace @@ -275,6 +277,18 @@ TEST(QueuePositionTest, PropagatesOtherQueryErrors) { std::invalid_argument); } +TEST(JobShotsTest, PreservesZeroWidthShots) { + EXPECT_EQ(detail::parseShots("", 0), std::vector{}); + EXPECT_EQ(detail::parseShots("", 1), std::vector{""}); + EXPECT_EQ(detail::parseShots(",,,", 4), + (std::vector{"", "", "", ""})); +} + +TEST(JobShotsTest, ValidatesShotCount) { + EXPECT_THROW(std::ignore = detail::parseShots("0,1", 1), std::runtime_error); + EXPECT_THROW(std::ignore = detail::parseShots("0", 2), std::runtime_error); +} + TEST(QDMITest, StatusToString) { EXPECT_STREQ(qdmi::toString(QDMI_WARN_GENERAL), "General warning"); EXPECT_STREQ(qdmi::toString(QDMI_SUCCESS), "Success"); @@ -493,9 +507,11 @@ TEST_P(DeviceTest, NeedsCalibration) { EXPECT_NO_THROW(std::ignore = device.getNeedsCalibration()); } +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE TEST_F(DDSimulatorDeviceTest, QueueLengthIsUnavailable) { EXPECT_EQ(device.getQueueLength(), std::nullopt); } +#endif TEST_P(DeviceTest, LengthUnit) { EXPECT_NO_THROW(std::ignore = device.getLengthUnit()); @@ -792,6 +808,7 @@ TEST_P(DeviceTest, RegularSitesAndZones) { } } +#ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE TEST_F(DDSimulatorDeviceTest, SubmitJobReturnsValidJob) { const std::string qasm3Program = R"( OPENQASM 3.0; @@ -1120,6 +1137,7 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { ASSERT_NE(it11, sparseProbabilities.end()); EXPECT_NEAR(it11->second, 0.5, 1e-10); } +#endif TEST(AuthenticationTest, SessionParameterToString) { EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); From 5bbedfd6a4bf82e3fff69d09d0aed8f1be1490cf Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 12:55:07 +0200 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=A9=B9=20Address=20straightforward?= =?UTF-8?q?=20v4=20removal=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Internalize QCO sampling resources behind a shots-and-seed API, update callers and coverage, and remove redundant surface checks. Clarify the migration notes, references, wheel smoke test, and README execution example. Assisted-by: GPT-5.6 via Codex --- .agent/plans/qco-dd-execution-and-sampling.md | 4 +- CHANGELOG.md | 7 +- README.md | 28 +++- bindings/mlir/register_mlir.cpp | 10 +- docs/dd_package.md | 5 +- .../mlir/Dialect/QCO/Utils/DDFunctionality.h | 6 +- .../lib/Dialect/QCO/Utils/DDFunctionality.cpp | 4 +- .../QCO/Utils/test_dd_functionality.cpp | 148 ++++-------------- pyproject.toml | 2 +- python/mqt/core/mlir.pyi | 3 +- src/qdmi/devices/dd/Device.cpp | 14 +- test/python/test_python_surface.py | 44 ------ test/python/test_qco_dd.py | 13 +- test/qdmi/driver/test_driver.cpp | 4 - 14 files changed, 83 insertions(+), 209 deletions(-) delete mode 100644 test/python/test_python_surface.py diff --git a/.agent/plans/qco-dd-execution-and-sampling.md b/.agent/plans/qco-dd-execution-and-sampling.md index 71f991098f..dcd268e20c 100644 --- a/.agent/plans/qco-dd-execution-and-sampling.md +++ b/.agent/plans/qco-dd-execution-and-sampling.md @@ -51,8 +51,8 @@ checks public transform boundaries; and The Python API is `program.build_functionality(dd_package) -> MatrixDD`, `program.simulate(initial_state, dd_package, seed=0) -> VectorDD`, and -`program.sample(dd_package, shots=1024, seed=0) -> dict[str, int]`. The public -C++ simulation function always receives an RNG. Static sampling evolves once, +`program.sample(shots=1024, seed=0) -> dict[str, int]`. The public C++ +simulation function always receives an RNG. Static sampling evolves once, adaptive control runs per shot, and returned CBits share storage across calls. ## Milestones diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf8764a6c..3d238b01e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,8 +117,9 @@ releases may include breaking changes. ### Removed -- 💥 Remove the classic circuit APIs and adapters. Use compiler-backed QC/QCO - APIs in v4 or MQT Core 3.9.x for legacy code ([#2288]) ([**@simon1hofmann**]) +- 💥 Remove `qc::QuantumComputation`, `MQT::CoreIR`, and `MQT::CoreQASM`. Use + compiler-backed QC/QCO APIs in v4 or MQT Core 3.9.x for legacy code ([#2111], + [#2112], [#2288]) ([**@burgholzer**], [**@simon1hofmann**]) - 💥 Remove the `spdlog` dependency from MQT Core source builds, installed CMake packages, and Python wheels. QDMI diagnostics continue to be written to standard error ([#2270]) ([**@denialhaag**]) @@ -916,6 +917,8 @@ for previous changelogs._ [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 +[#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 +[#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 [#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 diff --git a/README.md b/README.md index 62164d1cb8..72840ec592 100644 --- a/README.md +++ b/README.md @@ -94,21 +94,37 @@ To support this endeavor, please consider: uv pip install mqt.core ``` -The following code gives an example on the usage: +The following example compiles an OpenQASM program to QIR and executes it on the +QDMI DDSIM device: ```python3 -from mqt.core.mlir import compile_program +from mqt.core.mlir import CompilerTarget, OutputFormat, compile_program +from mqt.core.qdmi import ProgramFormat +from mqt.core.qdmi.driver import open_device -program = compile_program("""OPENQASM 3.0; +source = """OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; bit[2] result; h q[0]; cx q[0], q[1]; result = measure q; -""") - -print(program.ir) +""" + +device = open_device("mqt.ddsim.default") +program = compile_program( + source, + target=CompilerTarget.from_device(device), + output=OutputFormat.QIR_BASE, +) +job = device.submit_job( + program.to_bitcode(), + ProgramFormat.QIR_BASE_MODULE, + num_shots=1024, + custom1=7, +) +job.wait() +print(job.get_counts()) ``` **Detailed documentation and examples are available at diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 27e0382eea..68548c341a 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -1066,19 +1066,17 @@ LLVM bitcode.)pb"); qcoProgram.def( "sample", - [](const mlir::QCOProgram& program, dd::Package& ddPackage, - const size_t shots, const uint64_t seed) { + [](const mlir::QCOProgram& program, const size_t shots, + const uint64_t seed) { auto func = entryFunc(program); - auto rng = makeRng(seed); return takeFailureOr( func.getContext(), "cannot sample this QCO program", - [&] { return mlir::qco::sample(func, ddPackage, shots, rng); }); + [&] { return mlir::qco::sample(func, shots, seed); }); }, - "dd_package"_a, "shots"_a = 1024U, "seed"_a = 0U, + "shots"_a = 1024U, "seed"_a = 0U, R"pb(Sample the declared outputs of a QCO program. Args: - dd_package: DD package with enough qubits for the program. shots: Number of shots (default 1024). seed: RNG seed. ``0`` (default) selects nondeterministic seeding. Any other value produces reproducible results. diff --git a/docs/dd_package.md b/docs/dd_package.md index 87c8a8038a..98bc7cbfc1 100644 --- a/docs/dd_package.md +++ b/docs/dd_package.md @@ -34,7 +34,6 @@ mid-circuit measurements, resets, and classically controlled operations. This example compiles and samples a Bell-state program: ```{code-cell} ipython3 -from mqt.core.dd import DDPackage from mqt.core.mlir import OutputFormat, compile_program bell_qasm = """OPENQASM 3.0; @@ -47,8 +46,7 @@ result = measure q; """ sample_program = compile_program(bell_qasm, output=OutputFormat.QCO_OPTIMIZED) -sample_package = DDPackage(2) -counts = sample_program.sample(sample_package, shots=1024, seed=1) +counts = sample_program.sample(shots=1024, seed=1) print(counts) ``` @@ -60,6 +58,7 @@ or {py:meth}`~mqt.core.dd.MatrixDD.get_matrix`. ```{code-cell} ipython3 import numpy as np +from mqt.core.dd import DDPackage from mqt.core.mlir import QCOProgram unitary_program = QCOProgram.from_mlir_str(""" diff --git a/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h index a97df3a49b..36a8792ff0 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -97,12 +98,11 @@ FailureOr simulateStatevector( /// `qco::verifyLinearity`. /// /// @param func QCO function to sample. -/// @param dd DD package. The function grows it when needed. /// @param shots Number of samples. -/// @param rng Random-number generator. +/// @param seed RNG seed. Zero selects nondeterministic seeding. /// @param argumentBindings Scalar values and dynamic QTensor argument sizes. /// @return Outcome counts, or failure for an unsupported program. FailureOr> -sample(func::FuncOp func, dd::Package& dd, size_t shots, std::mt19937_64& rng, +sample(func::FuncOp func, size_t shots, uint64_t seed = 0, const DDArgumentBindings& argumentBindings = DDArgumentBindings()); } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp index c3d689944e..c3f7745b16 100644 --- a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp +++ b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp @@ -1965,8 +1965,10 @@ sampleImpl(func::FuncOp func, const dd::VectorDD& in, dd::Package& dd, } FailureOr> -sample(func::FuncOp func, dd::Package& dd, size_t shots, std::mt19937_64& rng, +sample(func::FuncOp func, size_t shots, uint64_t seed, const DDArgumentBindings& argumentBindings) { + dd::Package dd; + std::mt19937_64 rng(seed == 0 ? std::random_device{}() : seed); auto prepared = prepare(func, dd, argumentBindings); if (failed(prepared)) { return failure(); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp index 0e77f52a63..9bd79440ad 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp @@ -150,7 +150,7 @@ class QCODDFunctionalityTest : public testing::Test { auto dd = std::make_unique(numQubits); EXPECT_TRUE( failed(simulate(func, dd::makeZeroState(numQubits, *dd), *dd, rng))); - EXPECT_TRUE(failed(sample(func, *dd, 1, rng))); + EXPECT_TRUE(failed(sample(func, 1, 1))); } void expectMlirSimulationFails(size_t numQubits, StringRef mlirCode) { @@ -988,10 +988,8 @@ TEST_F(QCODDFunctionalityTest, SampleUnitaryXIsDeterministic) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - std::mt19937_64 rng(1); constexpr size_t shots = 64; - const auto hist = sample(mainFunc(*mod), *dd, shots, rng); + const auto hist = sample(mainFunc(*mod), shots); ASSERT_TRUE(succeeded(hist)); ASSERT_EQ(hist->size(), 1U); EXPECT_EQ(hist->begin()->first, "1"); @@ -999,18 +997,18 @@ TEST_F(QCODDFunctionalityTest, SampleUnitaryXIsDeterministic) { } TEST_F(QCODDFunctionalityTest, SamplePreservesDeclaredStaticWidth) { - auto mod = buildModule([](QCOProgramBuilder& b) { - auto q = b.staticQubit(3); + constexpr auto index = static_cast(dd::Package::DEFAULT_QUBITS); + auto mod = buildModule([index](QCOProgramBuilder& b) { + auto q = b.staticQubit(index); b.sink(q); return b.intConstant(0); }); ASSERT_TRUE(mod); - auto dd = std::make_unique(4); - std::mt19937_64 rng(1); - const auto histogram = sample(mainFunc(*mod), *dd, 8, rng); + const auto histogram = sample(mainFunc(*mod), 8, 1); ASSERT_TRUE(succeeded(histogram)); - EXPECT_EQ(*histogram, (std::map{{"0000", 8}})); + const auto outcome = std::string(static_cast(index + 1), '0'); + EXPECT_EQ(*histogram, (std::map{{outcome, 8}})); } TEST_F(QCODDFunctionalityTest, SampleHadamardApproximatelyBalanced) { @@ -1021,10 +1019,8 @@ TEST_F(QCODDFunctionalityTest, SampleHadamardApproximatelyBalanced) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - std::mt19937_64 rng(42); constexpr size_t shots = 2000; - const auto hist = sample(mainFunc(*mod), *dd, shots, rng); + const auto hist = sample(mainFunc(*mod), shots, 42); ASSERT_TRUE(succeeded(hist)); ASSERT_EQ(hist->size(), 2U); EXPECT_EQ(hist->at("0") + hist->at("1"), shots); @@ -1039,9 +1035,7 @@ TEST_F(QCODDFunctionalityTest, SampleResetUsesDynamicSampling) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - std::mt19937_64 rng(7); - const auto histogram = sample(mainFunc(*mod), *dd, 16, rng); + const auto histogram = sample(mainFunc(*mod), 16, 7); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"0", 16}})); } @@ -1060,10 +1054,8 @@ TEST_F(QCODDFunctionalityTest, SampleDynamicMeasureIf) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - std::mt19937_64 rng(7); constexpr size_t shots = 32; - const auto hist = sample(mainFunc(*mod), *dd, shots, rng); + const auto hist = sample(mainFunc(*mod), shots, 7); ASSERT_TRUE(succeeded(hist)); ASSERT_EQ(hist->size(), 1U); EXPECT_EQ(hist->begin()->first, "1"); @@ -1078,9 +1070,7 @@ TEST_F(QCODDFunctionalityTest, SampleHandlesZeroShotsAndSimulationFailure) { }); ASSERT_TRUE(unitary); - auto dd = std::make_unique(1); - std::mt19937_64 rng(1); - const auto empty = sample(mainFunc(*unitary), *dd, 0, rng); + const auto empty = sample(mainFunc(*unitary), 0, 1); ASSERT_TRUE(succeeded(empty)); EXPECT_TRUE(empty->empty()); @@ -1096,7 +1086,7 @@ TEST_F(QCODDFunctionalityTest, SampleHandlesZeroShotsAndSimulationFailure) { )mlir", context.get()); ASSERT_TRUE(dynamic); - EXPECT_TRUE(failed(sample(mainFunc(*dynamic), *dd, 1, rng))); + EXPECT_TRUE(failed(sample(mainFunc(*dynamic), 1, 1))); auto measuredDynamic = parseSourceString(R"mlir( module { @@ -1111,14 +1101,7 @@ TEST_F(QCODDFunctionalityTest, SampleHandlesZeroShotsAndSimulationFailure) { )mlir", context.get()); ASSERT_TRUE(measuredDynamic); - EXPECT_TRUE(failed(sample(mainFunc(*measuredDynamic), *dd, 1, rng))); - - auto tooSmall = std::make_unique(0); - const auto grown = sample(mainFunc(*unitary), *tooSmall, 1, rng); - ASSERT_TRUE(succeeded(grown)); - EXPECT_EQ(*grown, (std::map{{"1", 1}})); - EXPECT_EQ(tooSmall->qubits(), 1U); - EXPECT_TRUE(dd->getRootSet().empty()); + EXPECT_TRUE(failed(sample(mainFunc(*measuredDynamic), 1, 1))); } TEST_F(QCODDFunctionalityTest, EmbedsWideLocalMatrixWithoutRegisterLimit) { @@ -1588,9 +1571,7 @@ TEST_F(QCODDFunctionalityTest, ScfForCarriesQubitsSimultaneously) { context.get()); ASSERT_TRUE(mod); - auto dd = std::make_unique(2); - std::mt19937_64 rng(1); - const auto histogram = sample(mainFunc(*mod), *dd, 1, rng); + const auto histogram = sample(mainFunc(*mod), 1, 1); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"01", 1}})); } @@ -1945,12 +1926,9 @@ TEST_F(QCODDFunctionalityTest, SampleReturnsCBitRegistersInDeclaredOrder) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(0); - std::mt19937_64 rng(3); - const auto histogram = sample(mainFunc(*mod), *dd, 8, rng); + const auto histogram = sample(mainFunc(*mod), 8, 3); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"101", 8}})); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, SampleRejectsUndefinedAndMixedResults) { @@ -1963,11 +1941,8 @@ TEST_F(QCODDFunctionalityTest, SampleRejectsUndefinedAndMixedResults) { ASSERT_TRUE(undefined); ASSERT_TRUE(mixed); - auto dd = std::make_unique(0); - std::mt19937_64 rng(3); - EXPECT_TRUE(failed(sample(mainFunc(*undefined), *dd, 1, rng))); - EXPECT_TRUE(failed(sample(mainFunc(*mixed), *dd, 1, rng))); - EXPECT_TRUE(dd->getRootSet().empty()); + EXPECT_TRUE(failed(sample(mainFunc(*undefined), 1, 3))); + EXPECT_TRUE(failed(sample(mainFunc(*mixed), 1, 3))); } TEST_F(QCODDFunctionalityTest, @@ -1985,20 +1960,9 @@ TEST_F(QCODDFunctionalityTest, }); ASSERT_TRUE(mod); - std::mt19937_64 rng(11); - auto singleDD = std::make_unique(2); - const auto single = sample(mainFunc(*mod), *singleDD, 1, rng); - ASSERT_TRUE(succeeded(single)); - const auto singleEvolutionLookups = - singleDD->matrixVectorMultiplication.getStats().lookups; - - auto dd = std::make_unique(2); - const auto histogram = sample(mainFunc(*mod), *dd, 64, rng); + const auto histogram = sample(mainFunc(*mod), 64, 11); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", 64}})); - EXPECT_EQ(dd->matrixVectorMultiplication.getStats().lookups, - singleEvolutionLookups); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, SampleDefersAllocatedQubitMeasurement) { @@ -2012,8 +1976,7 @@ TEST_F(QCODDFunctionalityTest, SampleDefersAllocatedQubitMeasurement) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - const auto histogram = sample(mainFunc(*mod), *dd, 8, rng); + const auto histogram = sample(mainFunc(*mod), 8, 1); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", 8}})); } @@ -2033,20 +1996,10 @@ TEST_F(QCODDFunctionalityTest, SampleExecutesControlMeasurementPerShot) { }); ASSERT_TRUE(mod); - std::mt19937_64 rng(11); - auto singleDD = std::make_unique(1); - ASSERT_TRUE(succeeded(sample(mainFunc(*mod), *singleDD, 1, rng))); - const auto singleShotLookups = - singleDD->matrixVectorMultiplication.getStats().lookups; - - auto dd = std::make_unique(1); constexpr size_t shots = 32; - const auto histogram = sample(mainFunc(*mod), *dd, shots, rng); + const auto histogram = sample(mainFunc(*mod), shots, 11); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", shots}})); - EXPECT_EQ(dd->matrixVectorMultiplication.getStats().lookups, - 1U + (singleShotLookups - 1U) * shots); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, @@ -2072,13 +2025,11 @@ TEST_F(QCODDFunctionalityTest, context.get()); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); constexpr size_t shots = 128; - const auto histogram = sample(mainFunc(*mod), *dd, shots, rng); + const auto histogram = sample(mainFunc(*mod), shots, 1); ASSERT_TRUE(succeeded(histogram)); ASSERT_EQ(histogram->size(), 2U); EXPECT_EQ(histogram->at("0") + histogram->at("1"), shots); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, FuncCallSharesClassicalCBitStorage) { @@ -2174,20 +2125,10 @@ TEST_F(QCODDFunctionalityTest, context.get()); ASSERT_TRUE(mod); - std::mt19937_64 rng(9); - auto singleDD = std::make_unique(1); - ASSERT_TRUE(succeeded(sample(mainFunc(*mod), *singleDD, 1, rng))); - const auto perShotLookups = - singleDD->matrixVectorMultiplication.getStats().lookups; - - auto dd = std::make_unique(1); - const auto histogram = sample(mainFunc(*mod), *dd, 128, rng); + const auto histogram = sample(mainFunc(*mod), 128, 9); ASSERT_TRUE(succeeded(histogram)); ASSERT_EQ(histogram->size(), 2U); EXPECT_EQ(histogram->at("0") + histogram->at("1"), 128U); - EXPECT_EQ(dd->matrixVectorMultiplication.getStats().lookups, - perShotLookups * 128U); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, SampleDefersNestedTerminalMeasurement) { @@ -2214,21 +2155,11 @@ TEST_F(QCODDFunctionalityTest, SampleDefersNestedTerminalMeasurement) { context.get()); ASSERT_TRUE(mod); - std::mt19937_64 rng(9); - auto singleDD = std::make_unique(1); - ASSERT_TRUE(succeeded(sample(mainFunc(*mod), *singleDD, 1, rng))); - const auto singleEvolutionLookups = - singleDD->matrixVectorMultiplication.getStats().lookups; - - auto dd = std::make_unique(1); constexpr size_t shots = 128; - const auto histogram = sample(mainFunc(*mod), *dd, shots, rng); + const auto histogram = sample(mainFunc(*mod), shots, 9); ASSERT_TRUE(succeeded(histogram)); ASSERT_EQ(histogram->size(), 2U); EXPECT_EQ(histogram->at("0") + histogram->at("1"), shots); - EXPECT_EQ(dd->matrixVectorMultiplication.getStats().lookups, - singleEvolutionLookups); - EXPECT_TRUE(dd->getRootSet().empty()); } TEST_F(QCODDFunctionalityTest, SymbolicParametersUseBindings) { @@ -2268,7 +2199,7 @@ TEST_F(QCODDFunctionalityTest, SymbolicParametersUseBindings) { dd->decRef(*actual); dd->decRef(*expected); - const auto histogram = sample(func, *dd, 8, rng, bindings); + const auto histogram = sample(func, 8, 1, bindings); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", 8}})); @@ -2320,9 +2251,7 @@ TEST_F(QCODDFunctionalityTest, RejectsNonFiniteParameters) { DDArgumentBindings bindings; bindings[func.getArgument(0)] = FloatAttr::get(Float64Type::get(context.get()), value); - auto dd = std::make_unique(1); - EXPECT_TRUE(failed(sample(func, *dd, 1, rng, bindings))); - EXPECT_TRUE(dd->getRootSet().empty()); + EXPECT_TRUE(failed(sample(func, 1, 1, bindings))); } } @@ -2432,17 +2361,10 @@ TEST_F(QCODDFunctionalityTest, DynamicAllocationsAndQTensorBookkeeping) { }); ASSERT_TRUE(mod); - auto dd = std::make_unique(2); - const auto histogram = sample(mainFunc(*mod), *dd, 8, rng); + const auto histogram = sample(mainFunc(*mod), 8, 1); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"11", 8}})); - auto smallDd = std::make_unique(1); - const auto grown = sample(mainFunc(*mod), *smallDd, 1, rng); - ASSERT_TRUE(succeeded(grown)); - EXPECT_EQ(*grown, (std::map{{"11", 1}})); - EXPECT_EQ(smallDd->qubits(), 2U); - auto invalidIndex = parseSourceString(R"mlir( module { func.func @main() { @@ -2458,8 +2380,7 @@ TEST_F(QCODDFunctionalityTest, DynamicAllocationsAndQTensorBookkeeping) { )mlir", context.get()); ASSERT_TRUE(invalidIndex); - auto oneQubitDd = std::make_unique(1); - EXPECT_TRUE(failed(sample(mainFunc(*invalidIndex), *oneQubitDd, 1, rng))); + EXPECT_TRUE(failed(sample(mainFunc(*invalidIndex), 1, 1))); } TEST_F(QCODDFunctionalityTest, DynamicQTensorArgumentUsesBoundExtent) { @@ -2485,7 +2406,7 @@ TEST_F(QCODDFunctionalityTest, DynamicQTensorArgumentUsesBoundExtent) { IntegerAttr::get(IndexType::get(context.get()), 2); auto dd = std::make_unique(2); - const auto histogram = sample(func, *dd, 4, rng, bindings); + const auto histogram = sample(func, 4, 1, bindings); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"10", 4}})); @@ -2539,8 +2460,7 @@ TEST_F(QCODDFunctionalityTest, QTensorFlowsThroughLoopAndCall) { context.get()); ASSERT_TRUE(mod); - auto dd = std::make_unique(1); - const auto histogram = sample(mainFunc(*mod), *dd, 4, rng); + const auto histogram = sample(mainFunc(*mod), 4, 1); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", 4}})); } @@ -2651,15 +2571,13 @@ TEST_F(QCODDFunctionalityTest, BindingsDriveObservableClassicalPath) { IntegerAttr::get(IntegerType::get(context.get(), 16), 3); bindings[func.getArgument(2)] = IntegerAttr::get(IntegerType::get(context.get(), 1), 1); - auto dd = std::make_unique(1); - const auto histogram = sample(func, *dd, 4, rng, bindings); + const auto histogram = sample(func, 4, 1, bindings); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(*histogram, (std::map{{"1", 4}})); bindings[func.getArgument(1)] = IntegerAttr::get(IntegerType::get(context.get(), 8), 3); - EXPECT_TRUE(failed(sample(func, *dd, 1, rng, bindings))); - EXPECT_TRUE(dd->getRootSet().empty()); + EXPECT_TRUE(failed(sample(func, 1, 1, bindings))); } TEST_F(QCODDFunctionalityTest, RepeatedSimulationPreservesFoldableIR) { @@ -2976,7 +2894,7 @@ TEST_F(QCODDFunctionalityTest, LifetimeMarkersPreserveEntangledState) { EXPECT_NEAR(std::norm(vector[7]), 0.5, 1e-12); dd->decRef(*state); - const auto histogram = sample(mainFunc(*mod), *dd, 64, rng); + const auto histogram = sample(mainFunc(*mod), 64, 1); ASSERT_TRUE(succeeded(histogram)); EXPECT_EQ(histogram->at("100") + histogram->at("111"), 64U); } diff --git a/pyproject.toml b/pyproject.toml index 705a4b1cc3..ffbf5b61ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -290,7 +290,7 @@ select = ["cp*-win_arm64", "cp315*"] before-test = "" test-groups = [] test-sources = [] -test-command = "python -c \"import importlib.util; import mqt.core, mqt.core.dd, mqt.core.mlir, mqt.core.qdmi; assert not hasattr(mqt.core, 'load'); assert importlib.util.find_spec('mqt.core.ir') is None\"" +test-command = "python -c \"import mqt.core.dd, mqt.core.mlir, mqt.core.qdmi\"" [tool.cibuildwheel.linux] before-all = """ diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 66f4cdc369..135f4c1e0d 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -509,11 +509,10 @@ class QCOProgram(Program): has too few qubits, or the program is unsupported for simulation. """ - def sample(self, dd_package: mqt.core.dd.DDPackage, shots: int = 1024, seed: int = 0) -> dict[str, int]: + def sample(self, shots: int = 1024, seed: int = 0) -> dict[str, int]: """Sample the declared outputs of a QCO program. Args: - dd_package: DD package with enough qubits for the program. shots: Number of shots (default 1024). seed: RNG seed. ``0`` (default) selects nondeterministic seeding. Any other value produces reproducible results. diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 452ff7f0fa..e40b37e887 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -198,14 +197,6 @@ reverseRegisterSegments(mlir::func::FuncOp entryPoint, return reordered; } -[[nodiscard]] auto makeJobRng(const std::optional seed) - -> std::mt19937_64 { - using Seed = std::mt19937_64::result_type; - return std::mt19937_64(seed.has_value() - ? static_cast(*seed) - : static_cast(std::random_device{}())); -} - } // namespace namespace qdmi::dd { @@ -526,9 +517,8 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramSampling() std::cerr << "Error: QCO program has no entry point\n"; return false; } - dd::Package package; - auto rng = makeJobRng(seed_); - auto counts = mlir::qco::sample(entryPoint, package, numShots_, rng); + auto counts = mlir::qco::sample(entryPoint, numShots_, + static_cast(seed_.value_or(0))); if (mlir::failed(counts)) { std::cerr << "Error: failed to sample the QCO program\n"; return false; diff --git a/test/python/test_python_surface.py b/test/python/test_python_surface.py deleted file mode 100644 index 3465dd2cbf..0000000000 --- a/test/python/test_python_surface.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Tests for the public Python package surface.""" - -from __future__ import annotations - -import importlib - -import pytest - -import mqt.core - - -@pytest.mark.parametrize( - "module", - [ - "mqt.core.ir", - "mqt.core.load", - "mqt.core.plugins.qiskit.mqt_to_qiskit", - "mqt.core.plugins.qiskit.qiskit_to_mqt", - ], -) -def test_legacy_circuit_modules_are_absent(module: str) -> None: - """Omit removed legacy modules.""" - with pytest.raises(ModuleNotFoundError): - importlib.import_module(module) - - -def test_load_is_not_exported_from_mqt_core() -> None: - """Omit load from top-level exports.""" - assert not hasattr(mqt.core, "load") - - -def test_classic_qiskit_converters_are_not_exported() -> None: - """Omit classic Qiskit converters from plugin exports.""" - plugin = importlib.import_module("mqt.core.plugins.qiskit") - assert not hasattr(plugin, "mqt_to_qiskit") - assert not hasattr(plugin, "qiskit_to_mqt") diff --git a/test/python/test_qco_dd.py b/test/python/test_qco_dd.py index fecf2d79f1..34b05bc312 100644 --- a/test/python/test_qco_dd.py +++ b/test/python/test_qco_dd.py @@ -74,7 +74,7 @@ def test_unitary_x_build_simulate_and_sample() -> None: package.dec_ref_vec(out) package.dec_ref_vec(expected) - assert program.sample(package, shots=32, seed=1) == {"1": 32} + assert program.sample(shots=32) == {"1": 32} def test_simulate_measure_uses_default_or_explicit_seed() -> None: @@ -129,7 +129,7 @@ def test_entry_func_required() -> None: @pytest.mark.parametrize( - ("source", "num_qubits", "expected"), + ("source", "expected"), [ ( """ @@ -143,7 +143,6 @@ def test_entry_func_required() -> None: c[0] = measure q0; c[1] = measure q1; """, - 2, {"00", "11"}, ), ( @@ -159,7 +158,6 @@ def test_entry_func_required() -> None: } c[1] = measure q; """, - 1, {"00", "01"}, ), ( @@ -173,19 +171,18 @@ def test_entry_func_required() -> None: output bit out; out = measure q; """, - 1, {"0"}, ), ], ids=["terminal-bell", "adaptive-reset", "while-reset"], ) -def test_compiler_to_sampler_outputs(source: str, num_qubits: int, expected: set[str]) -> None: +def test_compiler_to_sampler_outputs(source: str, expected: set[str]) -> None: """Compile optimized QCO and sample the declared CBit output.""" program = compile_program(source, output=OutputFormat.QCO_OPTIMIZED) - package = DDPackage(num_qubits) shots = 256 - counts = program.sample(package, shots=shots, seed=17) + counts = program.sample(shots=shots, seed=17) assert set(counts) == expected assert sum(counts.values()) == shots + assert program.sample(shots=shots, seed=17) == counts diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index 4aed6e1c7c..24bff5d1b2 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -59,8 +59,6 @@ MATCHER_P2(IsBetween, a, b, namespace { -namespace { - struct ConfiguredDriverEnvironment { ConfiguredDriverEnvironment() noexcept { #ifdef _WIN32 @@ -340,8 +338,6 @@ class DriverJobTest : public DriverTest { } }; -} // namespace - TEST(ChildDeviceTest, WrapsOpaqueHandlesInStableClientDevices) { const auto library = std::make_shared(); { From ea794610d3fb081b27e4bc8e25ca310c80306e89 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 13:10:23 +0200 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=93=9D=20Simplify=20the=20README=20?= =?UTF-8?q?execution=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leave the DDSIM-specific custom seed parameter out of the introductory QDMI workflow. Assisted-by: GPT-5.6 via Codex --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 72840ec592..47eee3e8ba 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,6 @@ job = device.submit_job( program.to_bitcode(), ProgramFormat.QIR_BASE_MODULE, num_shots=1024, - custom1=7, ) job.wait() print(job.get_counts()) From 7eb5d47e84ecaca4168229c87bddd5a719b58107 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 14:35:35 +0200 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=90=9B=20Allocate=20the=20sampling?= =?UTF-8?q?=20DD=20package=20on=20the=20heap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp index c3f7745b16..eb5517eb97 100644 --- a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp +++ b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp @@ -1967,14 +1967,14 @@ sampleImpl(func::FuncOp func, const dd::VectorDD& in, dd::Package& dd, FailureOr> sample(func::FuncOp func, size_t shots, uint64_t seed, const DDArgumentBindings& argumentBindings) { - dd::Package dd; + auto dd = std::make_unique(); std::mt19937_64 rng(seed == 0 ? std::random_device{}() : seed); - auto prepared = prepare(func, dd, argumentBindings); + auto prepared = prepare(func, *dd, argumentBindings); if (failed(prepared)) { return failure(); } - return sampleImpl(func, dd::makeZeroState(prepared->qubits.numQubits, dd), dd, - shots, rng, *prepared); + return sampleImpl(func, dd::makeZeroState(prepared->qubits.numQubits, *dd), + *dd, shots, rng, *prepared); } } // namespace mlir::qco From 8b85bc992486ec2f50bc1bc15da89d1a0f8730a4 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 14:36:51 +0200 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=90=9B=20Align=20DDSIM=20result=20e?= =?UTF-8?q?ncoding=20conventions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agent/plans/qco-dd-execution-and-sampling.md | 9 ++-- bindings/mlir/register_mlir.cpp | 5 +- .../mlir/Dialect/QCO/Utils/DDFunctionality.h | 9 ++-- .../lib/Dialect/QCO/Utils/DDFunctionality.cpp | 2 +- .../QCO/Utils/test_dd_functionality.cpp | 5 +- python/mqt/core/mlir.pyi | 5 +- src/qdmi/devices/dd/Device.cpp | 53 +++++++------------ test/python/qdmi/test_qdmi.py | 14 ++--- .../qdmi/devices/dd/results_sampling_test.cpp | 24 +++++++++ .../devices/dd/results_statevector_test.cpp | 43 +++++++-------- 10 files changed, 90 insertions(+), 79 deletions(-) diff --git a/.agent/plans/qco-dd-execution-and-sampling.md b/.agent/plans/qco-dd-execution-and-sampling.md index dcd268e20c..87032fb5be 100644 --- a/.agent/plans/qco-dd-execution-and-sampling.md +++ b/.agent/plans/qco-dd-execution-and-sampling.md @@ -30,9 +30,12 @@ final basis state. QC coalesces static references; QCO owns one root per index. - Decision: with an MQT entry point, every `qco.static` belongs to its entry block with unique indices; helpers take arguments. Verify transforms on both sides. Rationale: one QCO ownership boundary. Date: 2026-08-29. -- Decision: `sample` encodes returned CBits in return order, MSB-first; no CBit - uses `measureAll`; mixed or undefined outputs fail. Loops use widened `APInt` - and one 10,000-step budget. Date: 2026-08-26. +- Decision: `sample` uses conventional count-string order: the last returned + CBit register comes first, and each register is MSB-first. This avoids adapter + reordering. Date: 2026-09-02. +- Decision: without CBit results, `sample` uses `measureAll`; mixed or undefined + outputs fail. Loops use widened `APInt` and one 10,000-step budget. Date: + 2026-08-26. ## Outcomes & Retrospective diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 68548c341a..10e07d26d4 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -1082,8 +1082,9 @@ LLVM bitcode.)pb"); value produces reproducible results. Returns: - Histogram of returned CBit registers in return order, each MSB first. If - no CBit result exists, final ``measureAll`` bitstrings instead. + Histogram keys use conventional count-string order: the last returned + register comes first, and each register is MSB-first. If no CBit result + exists, final ``measureAll`` bitstrings are used instead. Raises: ValueError: When the program is unsupported for sampling.)pb"); diff --git a/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h index 36a8792ff0..2ddaa2ef63 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h @@ -89,10 +89,11 @@ FailureOr simulateStatevector( /// Sample a single-block QCO function from the zero state. /// -/// Returned CBit registers set the outcome in return order and from high to low -/// bit. Without CBit results, the function samples all DD wires. Terminal -/// measurements use one simulation for all shots. Programs that use a measured -/// value or wire, or reset a qubit, run once per shot. +/// Returned CBit registers use conventional count-string order: the last +/// returned register comes first, and each register is MSB-first. Without CBit +/// results, the function samples all DD wires. Terminal measurements use one +/// simulation for all shots. Programs that use a measured value or wire, or +/// reset a qubit, run once per shot. /// /// The containing module must pass MLIR verification and /// `qco::verifyLinearity`. diff --git a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp index eb5517eb97..4a8cfb300f 100644 --- a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp +++ b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp @@ -1869,7 +1869,7 @@ static FailureOr encodeOutcome(ArrayRef outputs, return basis.str(); } std::string outcome; - for (Value value : outputs) { + for (Value value : llvm::reverse(outputs)) { const auto reg = classical.registers.find(value); if (reg == classical.registers.end()) { return emitError(value.getLoc()) diff --git a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp index 9bd79440ad..9256119ae7 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp @@ -1914,7 +1914,7 @@ TEST_F(QCODDFunctionalityTest, SimulateRicherClassicalArithmetic) { expectSimulatesFromZero(mainFunc(*mod), true); } -TEST_F(QCODDFunctionalityTest, SampleReturnsCBitRegistersInDeclaredOrder) { +TEST_F(QCODDFunctionalityTest, SampleReturnsCBitRegistersInCountStringOrder) { auto mod = buildModule([](QCOProgramBuilder& b) -> SmallVector { auto wide = b.allocClassicalBitRegister(2, {}, cbit::Initialization::Undefined); @@ -1928,7 +1928,8 @@ TEST_F(QCODDFunctionalityTest, SampleReturnsCBitRegistersInDeclaredOrder) { const auto histogram = sample(mainFunc(*mod), 8, 3); ASSERT_TRUE(succeeded(histogram)); - EXPECT_EQ(*histogram, (std::map{{"101", 8}})); + // Count strings place the last returned register first: "1" + "10". + EXPECT_EQ(*histogram, (std::map{{"110", 8}})); } TEST_F(QCODDFunctionalityTest, SampleRejectsUndefinedAndMixedResults) { diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 135f4c1e0d..bc31c266f7 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -518,8 +518,9 @@ class QCOProgram(Program): value produces reproducible results. Returns: - Histogram of returned CBit registers in return order, each MSB first. If - no CBit result exists, final ``measureAll`` bitstrings instead. + Histogram keys use conventional count-string order: the last returned + register comes first, and each register is MSB-first. If no CBit result + exists, final ``measureAll`` bitstrings are used instead. Raises: ValueError: When the program is unsupported for sampling. diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index e40b37e887..88f858af8b 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -17,7 +17,6 @@ #include "dd/DDDefinitions.hpp" #include "dd/Package.hpp" #include "mlir/Compiler/Programs.h" -#include "mlir/Dialect/CBit/IR/CBitDialect.h" #include "mlir/Dialect/MQT/IR/MQTDialect.h" #include "mlir/Dialect/QCO/Utils/DDFunctionality.h" #include "mlir/Dialect/QIR/Execution/JIT/Session.h" @@ -26,7 +25,6 @@ #include "qdmi/common/Common.hpp" #include -#include #include #include @@ -44,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -169,32 +166,11 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = { return qcoProgram; } -[[nodiscard]] auto -reverseRegisterSegments(mlir::func::FuncOp entryPoint, - std::map counts) - -> std::map { - std::vector widths; - for (const auto type : entryPoint.getFunctionType().getResults()) { - if (const auto reg = mlir::dyn_cast(type)) { - widths.push_back(static_cast(reg.getWidth())); - } - } - if (widths.size() < 2) { - return counts; - } - - std::map reordered; - for (const auto& [outcome, count] : counts) { - std::string key; - key.reserve(outcome.size()); - auto offset = outcome.size(); - for (const auto width : widths | std::views::reverse) { - offset -= width; - key.append(outcome, offset, width); - } - reordered.emplace(std::move(key), count); +[[nodiscard]] auto reportEmptyResult(size_t* sizeRet) -> QDMI_STATUS { + if (sizeRet != nullptr) { + *sizeRet = 0; } - return reordered; + return QDMI_SUCCESS; } } // namespace @@ -523,7 +499,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQASMProgramSampling() std::cerr << "Error: failed to sample the QCO program\n"; return false; } - counts_ = reverseRegisterSegments(entryPoint, std::move(*counts)); + counts_ = std::move(*counts); return true; }); } @@ -682,6 +658,9 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::wait(const size_t timeout) const auto MQT_DDSIM_QDMI_Device_Job_impl_d::getHistogram( const QDMI_Job_Result result, const size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS { + if (counts_.size() == 1 && counts_.begin()->first.empty()) { + return reportEmptyResult(sizeRet); + } if (result == QDMI_JOB_RESULT_HIST_KEYS) { const size_t bitstringSize = counts_.empty() ? 0 : counts_.begin()->first.length(); @@ -727,6 +706,9 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getStateVector(const size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS { + if (stateVecDD_.isTerminal()) { + return reportEmptyResult(sizeRet); + } std::call_once(stateVecOnce_, [this]() { stateVec_ = stateVecDD_.getVector(); }); const size_t reqSize = stateVec_.size() * 2 * sizeof(double); @@ -744,11 +726,12 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getStateVector(const size_t size, auto MQT_DDSIM_QDMI_Device_Job_impl_d::getSparseResults( const QDMI_Job_Result result, const size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS { + if (stateVecDD_.isTerminal()) { + return reportEmptyResult(sizeRet); + } std::call_once(stateVecSparseOnce_, [this]() { stateVecSparse_ = stateVecDD_.getSparseVector(); }); - const size_t numQubits = stateVecDD_.isTerminal() - ? 0U - : static_cast(stateVecDD_.p->v) + 1U; + const size_t numQubits = static_cast(stateVecDD_.p->v) + 1U; switch (result) { case QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS: case QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS: { @@ -819,6 +802,9 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getProbabilities(const size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS { + if (stateVecDD_.isTerminal()) { + return reportEmptyResult(sizeRet); + } if (stateVec_.empty()) { stateVec_ = stateVecDD_.getVector(); } @@ -842,8 +828,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getResults(const QDMI_Job_Result result, const size_t size, void* data, size_t* sizeRet) -> QDMI_STATUS { - if ((data != nullptr && size == 0) || - IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { + if (IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_.load() != QDMI_JOB_STATUS_DONE) { diff --git a/test/python/qdmi/test_qdmi.py b/test/python/qdmi/test_qdmi.py index cb5bd5e0e8..825693a3eb 100644 --- a/test/python/qdmi/test_qdmi.py +++ b/test/python/qdmi/test_qdmi.py @@ -768,20 +768,20 @@ def simulator_job(ddsim_device: Device) -> Job: return ddsim_device.submit_job(qasm3_program, ProgramFormat.QASM3, num_shots=0) -def test_empty_qasm_program_has_a_zero_width_vacuum_result(ddsim_device: Device) -> None: - """Return the zero-qubit vacuum for an empty QASM program.""" +def test_empty_qasm_program_has_empty_results(ddsim_device: Device) -> None: + """Return empty results for an empty QASM program.""" program = "OPENQASM 3.0;" sample_job = ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=4) sample_job.wait() - assert sample_job.get_counts() == {"": 4} + assert sample_job.get_counts() == {} state_job = ddsim_device.submit_job(program, ProgramFormat.QASM3, num_shots=0) state_job.wait() - assert state_job.get_dense_statevector() == [1 + 0j] - assert state_job.get_dense_probabilities() == [1.0] - assert state_job.get_sparse_statevector() == {"": 1 + 0j} - assert state_job.get_sparse_probabilities() == {"": 1.0} + assert state_job.get_dense_statevector() == [] + assert state_job.get_dense_probabilities() == [] + assert state_job.get_sparse_statevector() == {} + assert state_job.get_sparse_probabilities() == {} def test_simulator_job_get_dense_state_vector_returns_valid_state(simulator_job: Job) -> None: diff --git a/test/qdmi/devices/dd/results_sampling_test.cpp b/test/qdmi/devices/dd/results_sampling_test.cpp index c23b0a7eda..333dee5eae 100644 --- a/test/qdmi/devices/dd/results_sampling_test.cpp +++ b/test/qdmi/devices/dd/results_sampling_test.cpp @@ -210,6 +210,30 @@ TEST_F(QIRHistogramTestString, SeedReproducesQIRSampling) { EXPECT_EQ(runProgram(format, program, 7), runProgram(format, program, 7)); } +TEST(ResultsSampling, EmptyQASM3YieldsEmptyHistogram) { + const qdmi_test::SessionGuard s{}; + const qdmi_test::JobGuard j{s.session}; + ASSERT_EQ( + qdmi_test::setProgram(j.job, QDMI_PROGRAM_FORMAT_QASM3, "OPENQASM 3.0;"), + QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::setShots(j.job, 4), QDMI_SUCCESS); + ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); + + constexpr QDMI_Job_Result results[]{QDMI_JOB_RESULT_HIST_KEYS, + QDMI_JOB_RESULT_HIST_VALUES}; + char dummy{}; + for (const auto result : results) { + size_t size = 1; + EXPECT_EQ( + MQT_DDSIM_QDMI_device_job_get_results(j.job, result, 0, nullptr, &size), + QDMI_SUCCESS); + EXPECT_EQ(size, 0U); + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results(j.job, result, 0, &dummy, + nullptr), + QDMI_SUCCESS); + } +} + TEST(ResultsSampling, BufferTooSmallErrors) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; diff --git a/test/qdmi/devices/dd/results_statevector_test.cpp b/test/qdmi/devices/dd/results_statevector_test.cpp index d2270ddd59..3e1e26a11b 100644 --- a/test/qdmi/devices/dd/results_statevector_test.cpp +++ b/test/qdmi/devices/dd/results_statevector_test.cpp @@ -62,7 +62,7 @@ TEST(ResultsStatevector, QASM3IgnoresFinalMeasurements) { expectBellState(QDMI_PROGRAM_FORMAT_QASM3, qdmi_test::QASM3_BELL_SAMPLING); } -TEST(ResultsStatevector, EmptyQASM3YieldsVacuumState) { +TEST(ResultsStatevector, EmptyQASM3YieldsEmptyResults) { const qdmi_test::SessionGuard s{}; const qdmi_test::JobGuard j{s.session}; ASSERT_EQ( @@ -71,29 +71,24 @@ TEST(ResultsStatevector, EmptyQASM3YieldsVacuumState) { ASSERT_EQ(qdmi_test::setShots(j.job, 0), QDMI_SUCCESS); ASSERT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS); - const auto vec = qdmi_test::getDenseState(j.job); - ASSERT_EQ(vec.size(), 1U); - EXPECT_EQ(vec.front(), std::complex(1.0, 0.0)); - - const size_t keysSize = - qdmi_test::querySize(j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS); - ASSERT_EQ(keysSize, 1U); - std::vector keys(keysSize); - EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results( - j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, keys.size(), - keys.data(), nullptr), - QDMI_SUCCESS); - EXPECT_EQ(keys.front(), '\0'); - - const size_t valuesSize = - qdmi_test::querySize(j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES); - ASSERT_EQ(valuesSize, sizeof(std::complex)); - std::complex value; - EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results( - j.job, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, sizeof(value), - &value, nullptr), - QDMI_SUCCESS); - EXPECT_EQ(value, std::complex(1.0, 0.0)); + constexpr QDMI_Job_Result results[]{ + QDMI_JOB_RESULT_STATEVECTOR_DENSE, + QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, + QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, + QDMI_JOB_RESULT_PROBABILITIES_DENSE, + QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, + QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES}; + char dummy{}; + for (const auto result : results) { + size_t size = 1; + EXPECT_EQ( + MQT_DDSIM_QDMI_device_job_get_results(j.job, result, 0, nullptr, &size), + QDMI_SUCCESS); + EXPECT_EQ(size, 0U); + EXPECT_EQ(MQT_DDSIM_QDMI_device_job_get_results(j.job, result, 0, &dummy, + nullptr), + QDMI_SUCCESS); + } } TEST(ResultsStatevector, DenseNormalizedAndBufferTooSmall) { From 8c65b0fe521bdf91c9789c66870c286a34fb39dc Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 14:58:10 +0200 Subject: [PATCH 06/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20Qiskit=20?= =?UTF-8?q?classical=20initialization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/qiskit/backend.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index 7070f15fd3..f156b62b60 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -127,15 +127,14 @@ def _serialize_to_qasm3(circuit: QuantumCircuit, backend: QDMIBackend) -> str: # uninitialized. Preserve Qiskit's semantics and make every output valid # even when the circuit measures only part of a register. if circuit.num_clbits: - initialization = circuit.copy_empty_like(vars_mode="captures") + initialization = circuit.copy_empty_like(vars_mode="drop") initialization.global_phase = 0 for clbit in initialization.clbits: initialization.store( clbit, False, # ruff: ignore[boolean-positional-value-in-call] Qiskit store arguments are positional-only. ) - circuit = circuit.copy() - circuit.compose(initialization, front=True, inplace=True, inline_captures=True) + circuit = circuit.compose(initialization, front=True, inplace=False) # Qiskit's OpenQASM3 exporter is fairly limited in terms of which gates it supports natively. # So it needs some help from us. From 77f4f4f5b3079daf92fbcb1aa9e3f0983560ffdd Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 16:21:12 +0200 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=92=9A=20Fix=20failing=20CI=20check?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 +++- bindings/mlir/register_mlir.cpp | 2 +- python/mqt/core/dd.pyi | 14 +++++++------- python/mqt/core/mlir.pyi | 2 +- python/mqt/core/plugins/qiskit/backend.py | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca4ec474dc..5695fcceb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,9 @@ jobs: with: clang-version: 22 build-project: true - files-changed-only: true + # Large changes can exceed GitHub's 20,000-line diff limit. Lint the + # complete tree so cpp-linter does not depend on a truncated PR patch. + files-changed-only: false setup-python: true install-pkgs: "nanobind==3.0.1" cpp-linter-extra-args: "-std=c++20 -Wunused-template" diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 10e07d26d4..a17eebaaf5 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -1082,7 +1082,7 @@ LLVM bitcode.)pb"); value produces reproducible results. Returns: - Histogram keys use conventional count-string order: the last returned + Histogram keys use conventional count-string order. The last returned register comes first, and each register is MSB-first. If no CBit result exists, final ``measureAll`` bitstrings are used instead. diff --git a/python/mqt/core/dd.pyi b/python/mqt/core/dd.pyi index e4f53b7116..2aa8d4f063 100644 --- a/python/mqt/core/dd.pyi +++ b/python/mqt/core/dd.pyi @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""MQT Core DD - The MQT Core Decision Diagram (DD) module.""" +"""MQT Core decision diagram module.""" import enum from collections.abc import Sequence @@ -17,17 +17,17 @@ import numpy as np from numpy.typing import NDArray class Control: - """A control qubit used by the raw matrix DD constructors. + """Control a raw matrix DD operation with one qubit. Args: - qubit: The index of the control qubit. - type_: Whether the control is positive or negative. + qubit: Control qubit index. + type_: Control polarity. """ def __init__(self, qubit: int, type_: Control.Type = ...) -> None: ... class Type(enum.Enum): - """Enumeration of control polarities.""" + """Control polarity.""" Pos = 1 @@ -35,11 +35,11 @@ class Control: @property def qubit(self) -> int: - """The index of the control qubit.""" + """Control qubit index.""" @property def type_(self) -> Control.Type: - """Whether the control is positive or negative.""" + """Control polarity.""" def __eq__(self, arg: object, /) -> bool: ... def __ne__(self, arg: object, /) -> bool: ... diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index bc31c266f7..b80b36690a 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -518,7 +518,7 @@ class QCOProgram(Program): value produces reproducible results. Returns: - Histogram keys use conventional count-string order: the last returned + Histogram keys use conventional count-string order. The last returned register comes first, and each register is MSB-first. If no CBit result exists, final ``measureAll`` bitstrings are used instead. diff --git a/python/mqt/core/plugins/qiskit/backend.py b/python/mqt/core/plugins/qiskit/backend.py index f156b62b60..41a9eda236 100644 --- a/python/mqt/core/plugins/qiskit/backend.py +++ b/python/mqt/core/plugins/qiskit/backend.py @@ -183,7 +183,7 @@ def _serialize_to_qasm3(circuit: QuantumCircuit, backend: QDMIBackend) -> str: # By excluding already defined gates, we allow the exporter to emit otherwise unsupported gates without # needing to provide a definition for them. The exporter will then treat them as opaque gates, which is fine # as long as the target device supports them. - basis_gates = [gate for gate in backend.target.operation_names if gate not in exclusion_list] + ["U"] + basis_gates = [gate for gate in backend.target.operation_names if gate not in exclusion_list] + ["mcx_gray", "U"] return qasm3.dumps(circuit, basis_gates=basis_gates) From 31af4b5ed73d266800e9e08f3f64c614c0c0b01f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 16:55:09 +0200 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=91=B7=20Restore=20changed-file=20C?= =?UTF-8?q?++=20linting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5695fcceb7..ca4ec474dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,9 +132,7 @@ jobs: with: clang-version: 22 build-project: true - # Large changes can exceed GitHub's 20,000-line diff limit. Lint the - # complete tree so cpp-linter does not depend on a truncated PR patch. - files-changed-only: false + files-changed-only: true setup-python: true install-pkgs: "nanobind==3.0.1" cpp-linter-extra-args: "-std=c++20 -Wunused-template" From 6b733b81c0ffce462080abdbb6167d1962f108a0 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 16:55:28 +0200 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=93=9D=20Broaden=20v3=20migration?= =?UTF-8?q?=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++-- UPGRADING.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d238b01e7..b2b3407ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,8 +118,8 @@ releases may include breaking changes. ### Removed - 💥 Remove `qc::QuantumComputation`, `MQT::CoreIR`, and `MQT::CoreQASM`. Use - compiler-backed QC/QCO APIs in v4 or MQT Core 3.9.x for legacy code ([#2111], - [#2112], [#2288]) ([**@burgholzer**], [**@simon1hofmann**]) + compiler-backed QC/QCO APIs in v4 or the MQT Core v3 release series for legacy + code ([#2111], [#2112], [#2288]) ([**@burgholzer**], [**@simon1hofmann**]) - 💥 Remove the `spdlog` dependency from MQT Core source builds, installed CMake packages, and Python wheels. QDMI diagnostics continue to be written to standard error ([#2270]) ([**@denialhaag**]) diff --git a/UPGRADING.md b/UPGRADING.md index 706c48d860..f7d5e7bbc8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -31,11 +31,11 @@ Qiskit. For decision-diagram simulation, lower to a `QCOProgram` and use its functions in `mqt.core.dd` and the operation-taking `DDPackage` methods have been removed; the raw vector and matrix DD constructors remain available. -MQT Core 3.9.x continues to provide the classic circuit interfaces for -repositories that have not migrated. Pin `mqt-core~=3.9.0` and stay on the 3.9 -release line for those consumers. C++ consumers should likewise use the 3.9 -release branch or a matching 3.9 version constraint. MQT Core 3.9 and 4 cannot -provide their Python or CMake packages in the same environment. +The MQT Core v3 release series continues to provide the classic circuit +interfaces for repositories that have not migrated. Pin `mqt-core>=3,<4` and +stay on that release series. C++ consumers should likewise use a v3 release +branch or a matching v3 version constraint. MQT Core v3 and v4 cannot provide +their Python or CMake packages in the same environment. ### Removal of the `spdlog` dependency From 74bbb6232d1f4372468df11b1bf5c595816fb868 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 17:31:26 +0200 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=94=A7=20Restore=20local=20C++=20li?= =?UTF-8?q?nt=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index a556175c20..83463dded8 100755 --- a/noxfile.py +++ b/noxfile.py @@ -112,7 +112,7 @@ def cpp_lint(session: nox.Session) -> None: f"--files-changed-only={'false' if all_files else 'true'}", "--lines-changed-only=false", *(() if all_files else (f"--diff-base={diff_base}",)), - "--file-annotations=true", + "--file-annotations=false", "--jobs=0", "--verbosity=info", env={"GITHUB_OUTPUT": str(output)}, From 59d4d7a0a69f636374d7ab524cca70006dad189a Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 17:51:19 +0200 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=92=9A=20Skip=20unlintable=20delete?= =?UTF-8?q?d=20parser=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub omits patch data for the large deleted file when the pull request diff exceeds its 20,000-line limit. Exclude only that removed path so cpp-linter retains changed-file-only coverage for every surviving source file. Assisted-by: GPT-5.6 via Codex --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca4ec474dc..b95d1b5364 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,7 +138,9 @@ jobs: cpp-linter-extra-args: "-std=c++20 -Wunused-template" # Vendored third-party headers are checked upstream and are not maintained # according to MQT Core's clang-tidy configuration. - cpp-linter-ignore-extra: "vendor/**" + # GitHub omits patch data for the removed large parser test. No source + # remains to lint. + cpp-linter-ignore-extra: "vendor/**|test/ir/test_qasm3_parser.cpp" setup-mlir: true llvm-version: 23.1.0 permissions: From a7ed4204fc9617f160fc594b208ac26026b50715 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 2 Sep 2026 17:54:43 +0200 Subject: [PATCH 12/12] =?UTF-8?q?=E2=8F=AA=EF=B8=8F=20Revert=20deleted=20p?= =?UTF-8?q?arser=20lint=20exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the standard changed-file lint scope. The known large-PR cpp-linter API failure is acceptable for this pull request. Assisted-by: GPT-5.6 via Codex --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b95d1b5364..ca4ec474dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,9 +138,7 @@ jobs: cpp-linter-extra-args: "-std=c++20 -Wunused-template" # Vendored third-party headers are checked upstream and are not maintained # according to MQT Core's clang-tidy configuration. - # GitHub omits patch data for the removed large parser test. No source - # remains to lint. - cpp-linter-ignore-extra: "vendor/**|test/ir/test_qasm3_parser.cpp" + cpp-linter-ignore-extra: "vendor/**" setup-mlir: true llvm-version: 23.1.0 permissions: