From 23e2715ff0a00a7b6cc7bc27136fec8bfb6af8ca Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 10:02:50 +0200 Subject: [PATCH 01/27] =?UTF-8?q?=E2=9C=A8=20Add=20fuse-two-qubit-unitary-?= =?UTF-8?q?runs=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `fuse-two-qubit-unitary-runs` MLIR pass that fuses compile-time two-qubit unitary windows and resynthesizes them to a native gateset via Weyl/KAK decomposition. Includes the pass definition, implementation, unit tests, and CMake wiring. Also add the `isEquivalentUpToGlobalPhase` matrix helper used by the new tests to compare unitaries up to a global phase. Assisted-by: Claude Opus 4.8 via Cursor Co-authored-by: Cursor --- CHANGELOG.md | 5 +- .../mlir/Dialect/QCO/Transforms/Passes.td | 48 ++ mlir/include/mlir/Dialect/QCO/Utils/Matrix.h | 27 + .../lib/Dialect/QCO/Transforms/CMakeLists.txt | 10 +- .../FuseTwoQubitUnitaryRuns.cpp | 707 ++++++++++++++++ .../Dialect/QCO/Transforms/CMakeLists.txt | 1 + .../Transforms/NativeSynthesis/CMakeLists.txt | 30 + .../test_fuse_two_qubit_unitary_runs.cpp | 775 ++++++++++++++++++ .../Dialect/QCO/Utils/test_unitary_matrix.cpp | 8 + 9 files changed, 1605 insertions(+), 6 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt create mode 100644 mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 63090a64e2..c9a46b692f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,12 @@ releases may include breaking changes. ### Added +- ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit + unitary windows via Weyl/KAK resynthesis ([#1655]) ([**@simon1hofmann**]) - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function - ([#1826]) ([**@ystade]) + ([#1826]) ([**@ystade**]) - 🐳 Add dev container configuration for consistent local development environment ([#1786]) ([**@denialhaag**]) - ✨ Add two-qubit Weyl (KAK) decomposition and native-gateset synthesis support @@ -651,6 +653,7 @@ changelogs._ [#1664]: https://github.com/munich-quantum-toolkit/core/pull/1664 [#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662 [#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660 +[#1655]: https://github.com/munich-quantum-toolkit/core/pull/1655 [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 [#1638]: https://github.com/munich-quantum-toolkit/core/pull/1638 [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 971dc18f71..8053b2f6a3 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -67,6 +67,54 @@ def FuseSingleQubitUnitaryRuns "Target Euler basis (zyz, zxz, xzx, xyx, u, zsxx, r).">]; } +def FuseTwoQubitUnitaryRuns + : Pass<"fuse-two-qubit-unitary-runs", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect"]; + let summary = "Lower QCO unitary gates to a user-specified native gate menu."; + let description = [{ + Rewrites unitaries to match the comma-separated `native-gates` menu. + `qco.barrier` and `qco.gphase` are preserved; `qco.ctrl` must have a + single control and a single target. + + The menu is a comma-separated list of gate tokens (order not significant) + from which the pass builds a profile: a single-qubit synthesis mode + (generic `qco.u` when `u` is present; IBM-style surface gates when all of + `x`, `sx`, and `rz`/`p` are present; IQM-style `qco.r` when `r` is present; + or a supported rotation pair chosen from `rx`, `ry`, `rz`) plus optional + two-qubit entanglers `cx`, `cz`, and optional `rzz`. + + Recognised tokens: `u`, `x`, `sx`, `rz` (or `p`), `rx`, `ry`, `r`, `cx`, + `cz`, `rzz`. An empty or whitespace-only menu is a no-op, which is the + intended pipeline default when synthesis is not needed. An unrecognised + token causes the pass to fail. + + Example menus (each line is one illustrative menu; pick either `cx` or + `cz` as the entangler, or list both if both are native): + - IBM basic (no fractional two-qubit): `x,sx,rz,cx` or `x,sx,rz,cz` + - IBM fractional: `x,sx,rz,rx,rzz,cx` or `x,sx,rz,rx,rzz,cz` + - Generic single-qubit U: `u,cx` or `u,cz` + - IQM default: `r,cz` (or `r,cx` if CX is the native entangler) + - Rotation pair + entangler: `rx,rz,cx`, `rx,ry,cz`, `ry,rz,cx`, etc. + Supported pairs are exactly `rx`+`rz`, `rx`+`ry`, and `ry`+`rz`. + + Stages: fuse single-qubit runs; fuse two-qubit windows; up to four + synthesis sweeps over remaining off-menu ops; fuse 1q seams; up to four + further synthesis and fusion rounds until the full menu holds. The pass + fails if anything remains off-menu after those caps. + + Lowering is deterministic: `cx` is preferred over `cz`, single-qubit + factors use the first emitter's Euler basis, and two-qubit window + replacement uses the minimal entangler count from the synthesizer. + }]; + let options = [Option< + "nativeGates", "native-gates", "std::string", "\"\"", + "Comma-separated native gate menu. Empty or whitespace-only is " + "a no-op. Tokens: u, x, sx, rz (or p), rx, ry, r, cx, cz, rzz. " + "Examples: x,sx,rz,cx; x,sx,rz,rx,rzz,cz; u,cx; r,cz; rx,rz,cx.">]; +} + +//===----------------------------------------------------------------------===// + def QuantumLoopUnroll : InterfacePass<"quantum-loop-unroll", "FunctionOpInterface"> { let dependentDialects = ["mlir::qco::QCODialect", "mlir::scf::SCFDialect"]; diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index 38816a9c85..d046e7bb58 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -833,6 +834,32 @@ concept SupportedMatrix = std::same_as || std::same_as || std::same_as || std::same_as; +/** + * @brief Checks whether two unitaries are equal up to a global phase. + * + * Uses `trace(rhs.adjoint() * lhs)` to infer a unit-modulus phase factor, then + * compares `lhs` to `factor * rhs` with @ref isApprox. A near-zero overlap + * (`|trace| <= tol`) is treated as not equivalent to avoid dividing by a tiny + * number. + * + * @tparam Matrix Any supported fixed- or dynamic-size matrix type. + * @param lhs Left-hand unitary. + * @param rhs Right-hand unitary. + * @param tol Absolute tolerance for overlap and entry-wise comparison. + * @return True when @p lhs and @p rhs differ only by a global phase. + */ +template +[[nodiscard]] bool isEquivalentUpToGlobalPhase(const Matrix& lhs, + const Matrix& rhs, + double tol = MATRIX_TOLERANCE) { + const auto overlap = (rhs.adjoint() * lhs).trace(); + if (std::abs(overlap) <= tol) { + return false; + } + const auto factor = overlap / std::abs(overlap); + return lhs.isApprox(factor * rhs, tol); +} + /// Scalar-on-the-left multiply `scalar * matrix` (commutes with the member /// `matrix * scalar`). Provided so generic code can scale a matrix from /// either side. diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 77f594db17..77d847d3ba 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -24,11 +24,11 @@ add_mlir_library( mqt_mlir_target_use_project_options(MLIRQCOTransforms) -# collect header files -file(GLOB_RECURSE PASSES_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h) -file(GLOB_RECURSE PASSES_HEADERS_BUILD - ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.inc) +# collect header files (subdirs: Decomposition/, …) +file(GLOB_RECURSE PASSES_HEADERS_SOURCE CONFIGURE_DEPENDS + "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h") +file(GLOB_RECURSE PASSES_HEADERS_BUILD CONFIGURE_DEPENDS + "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.inc") # add public headers using file sets target_sources( diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp new file mode 100644 index 0000000000..b96b4de99f --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -0,0 +1,707 @@ +/* + * 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 "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/Weyl.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QCO/Utils/Matrix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace mlir::qco { + +#define GEN_PASS_DEF_FUSETWOQUBITUNITARYRUNS +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +namespace { + +using decomposition::NativeGateKind; +using decomposition::NativeGateset; + +std::optional nativeGateKindFor(UnitaryOpInterface op) { + return llvm::TypeSwitch>( + op.getOperation()) + .Case([](UOp) { return NativeGateKind::U; }) + .Case([](XOp) { return NativeGateKind::X; }) + .Case([](SXOp) { return NativeGateKind::SX; }) + .Case([](auto) { return NativeGateKind::RZ; }) + .Case([](RXOp) { return NativeGateKind::RX; }) + .Case([](RYOp) { return NativeGateKind::RY; }) + .Case([](ROp) { return NativeGateKind::R; }) + .Default([](Operation*) { return std::nullopt; }); +} + +std::optional nativeEntanglerKindFor(CtrlOp ctrl) { + if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1 || + ctrl.getNumBodyUnitaries() != 1) { + return std::nullopt; + } + return llvm::TypeSwitch>( + ctrl.getBodyUnitary(0).getOperation()) + .Case([](XOp) { return NativeGateKind::CX; }) + .Case([](ZOp) { return NativeGateKind::CZ; }) + .Default([](Operation*) { return std::nullopt; }); +} + +/// Returns true when @p op is already on the resolved native gateset. +/// +/// Barriers and global phase are always allowed. Single-qubit primitives and +/// single-target `CtrlOp` shells (`X`/`Z` bodies) are checked against +/// @p spec.gates. +bool allowsOp(Operation* op, const NativeGateset& spec) { + return llvm::TypeSwitch(op) + .Case([](auto) { return true; }) + .Case([&](CtrlOp ctrl) { + const auto kind = nativeEntanglerKindFor(ctrl); + return kind && spec.gates.contains(*kind); + }) + .Case([&](UnitaryOpInterface unitary) { + if (!unitary.isSingleQubit()) { + return false; + } + const auto gate = nativeGateKindFor(unitary); + return gate && spec.gates.contains(*gate); + }) + .Default([](Operation*) { return false; }); +} + +} // namespace + +/// Skips unitaries nested under `ctrl`/`inv` bodies (handled on the shell op). +static bool isExcludedFromTopLevelUnitaryWalk(Operation* op) { + if (op->getParentOfType()) { + return true; + } + return !llvm::isa(op) && op->getParentOfType(); +} + +static bool isWalkableUnitaryShell(Operation* op) { + return !llvm::isa(op) && + !isExcludedFromTopLevelUnitaryWalk(op); +} + +static void collectUnitaryOpsInPreOrder(Operation* root, + SmallVectorImpl& ops) { + root->walk([&](Operation* op) { + if (isExcludedFromTopLevelUnitaryWalk(op)) { + return; + } + if (llvm::isa(op)) { + ops.push_back(op); + } + }); +} + +static Value emitSingleQubitMatrix(IRRewriter& rewriter, Location loc, + Value inQubit, const Matrix2x2& matrix, + const decomposition::EulerBasis basis) { + return *decomposition::synthesizeUnitary1QEuler( + rewriter, loc, inQubit, matrix, /*runSize=*/0, + /*hasNonBasisGate=*/true, basis); +} + +static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { + if (llvm::isa(op)) { + return false; + } + if (auto ctrl = llvm::dyn_cast(op)) { + if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { + return false; + } + return llvm::cast(ctrl.getOperation()) + .getUnitaryMatrix4x4(matrix); + } + auto unitary = llvm::dyn_cast(op); + if (!unitary || !unitary.isTwoQubit()) { + return false; + } + return unitary.getUnitaryMatrix4x4(matrix); +} + +static bool isOneQubitWindowMember(UnitaryOpInterface unitary) { + if (!unitary || !unitary.isSingleQubit() || + !isWalkableUnitaryShell(unitary.getOperation())) { + return false; + } + Matrix2x2 matrix; + return unitary.getUnitaryMatrix2x2(matrix); +} + +static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { + if (!unitary || !unitary.isTwoQubit() || + !isWalkableUnitaryShell(unitary.getOperation())) { + return false; + } + Matrix4x4 matrix; + return assignTwoQubitOpMatrix(unitary.getOperation(), matrix); +} + +static UnitaryOpInterface fusibleSingleQubitOp(Operation* op) { + if (llvm::isa(op)) { + return {}; + } + auto unitary = llvm::dyn_cast(op); + return isOneQubitWindowMember(unitary) ? unitary : UnitaryOpInterface{}; +} + +static UnitaryOpInterface uniqueUnitaryUser(Value wire) { + if (!wire.hasOneUse()) { + return {}; + } + auto unitary = llvm::dyn_cast(*wire.user_begin()); + if (!unitary) { + return {}; + } + if (unitary.isTwoQubit()) { + return isTwoQubitRunMember(unitary) ? unitary : UnitaryOpInterface{}; + } + if (unitary.isSingleQubit()) { + return isOneQubitWindowMember(unitary) ? unitary : UnitaryOpInterface{}; + } + return {}; +} + +static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { + Value cur = wire; + while (Operation* def = cur.getDefiningOp()) { + auto unitary = llvm::dyn_cast(def); + if (!unitary) { + return nullptr; + } + if (unitary.isTwoQubit()) { + return isTwoQubitRunMember(unitary) ? def : nullptr; + } + if (!isOneQubitWindowMember(unitary)) { + return nullptr; + } + cur = unitary.getInputQubit(0); + } + return nullptr; +} + +static bool feedsFromSameTwoQubitWindow(UnitaryOpInterface op) { + const Value in0 = op.getInputQubit(0); + const Value in1 = op.getInputQubit(1); + if (!in0.hasOneUse() || !in1.hasOneUse()) { + return false; + } + Operation* gate0 = twoQubitGateAtEndOfOneQChain(in0); + Operation* gate1 = twoQubitGateAtEndOfOneQChain(in1); + return gate0 != nullptr && gate0 == gate1; +} + +static bool isTwoQubitRunStart(UnitaryOpInterface op) { + return isTwoQubitRunMember(op) && !feedsFromSameTwoQubitWindow(op); +} + +namespace { + +struct FusableTwoQubitRun { + SmallVector ops; + Matrix4x4 composed = Matrix4x4::identity(); + unsigned numTwoQ = 0; + bool anyNonNative = false; + Value tailA; + Value tailB; +}; + +struct OneQubitRun { + SmallVector ops; +}; + +} // namespace + +static void markNonNativeIfNeeded(FusableTwoQubitRun& run, Operation* op, + const decomposition::NativeGateset& spec) { + if (!allowsOp(op, spec)) { + run.anyNonNative = true; + } +} + +// Replace when off-menu ops must be lowered, or when resynthesis uses fewer +// entanglers than the fused window. +static bool shouldApplyTwoQubitRunReplacement(const FusableTwoQubitRun& run, + std::uint8_t numBasisUses) { + if (run.anyNonNative) { + return true; + } + return numBasisUses < run.numTwoQ; +} + +static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, + UnitaryOpInterface op, + const decomposition::NativeGateset& spec) { + Matrix4x4 opMatrix; + if (!assignTwoQubitOpMatrix(op.getOperation(), opMatrix)) { + return; + } + const Value in0 = op.getInputQubit(0); + const Value in1 = op.getInputQubit(1); + SmallVector ids; + if (in0 == run.tailA && in1 == run.tailB) { + ids = {0, 1}; + run.tailA = op.getOutputQubit(0); + run.tailB = op.getOutputQubit(1); + } else if (in0 == run.tailB && in1 == run.tailA) { + ids = {1, 0}; + run.tailA = op.getOutputQubit(1); + run.tailB = op.getOutputQubit(0); + } else { + return; + } + run.composed = opMatrix.reorderForQubits(ids[0], ids[1]) * run.composed; + run.ops.push_back(op.getOperation()); + ++run.numTwoQ; + markNonNativeIfNeeded(run, op.getOperation(), spec); +} + +static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, + UnitaryOpInterface op, + const decomposition::NativeGateset& spec, + unsigned wireIndex) { + Matrix2x2 raw; + if (!op.getUnitaryMatrix2x2(raw)) { + return; + } + const auto pad = raw.embedInTwoQubit(wireIndex); + run.composed = pad * run.composed; + run.ops.push_back(op.getOperation()); + markNonNativeIfNeeded(run, op.getOperation(), spec); + if (wireIndex == 0) { + run.tailA = op.getOutputQubit(0); + } else { + run.tailB = op.getOutputQubit(0); + } +} + +static FusableTwoQubitRun +scanFusableTwoQubitRun(UnitaryOpInterface head, + const decomposition::NativeGateset& spec) { + FusableTwoQubitRun run; + run.tailA = head.getOutputQubit(0); + run.tailB = head.getOutputQubit(1); + run.ops.push_back(head.getOperation()); + run.numTwoQ = 1; + if (!assignTwoQubitOpMatrix(head.getOperation(), run.composed)) { + run.composed = Matrix4x4::identity(); + run.numTwoQ = 0; + run.ops.clear(); + return run; + } + markNonNativeIfNeeded(run, head.getOperation(), spec); + + while (true) { + UnitaryOpInterface nextOnA = uniqueUnitaryUser(run.tailA); + UnitaryOpInterface nextOnB = uniqueUnitaryUser(run.tailB); + + if (nextOnA && nextOnB && + nextOnA.getOperation() == nextOnB.getOperation() && + nextOnA.isTwoQubit()) { + absorbTwoQubitIntoRun(run, nextOnA, spec); + continue; + } + + if (nextOnA && nextOnB && + nextOnA.getOperation() != nextOnB.getOperation() && + nextOnA.isSingleQubit() && nextOnB.isSingleQubit()) { + if (nextOnA->isBeforeInBlock(nextOnB)) { + absorbOneQubitIntoRun(run, nextOnA, spec, /*wireIndex=*/0); + continue; + } + absorbOneQubitIntoRun(run, nextOnB, spec, /*wireIndex=*/1); + continue; + } + + if (nextOnA && nextOnA.isSingleQubit() && + (!nextOnB || nextOnA.getOperation() != nextOnB.getOperation())) { + absorbOneQubitIntoRun(run, nextOnA, spec, /*wireIndex=*/0); + continue; + } + + if (nextOnB && nextOnB.isSingleQubit() && + (!nextOnA || nextOnB.getOperation() != nextOnA.getOperation())) { + absorbOneQubitIntoRun(run, nextOnB, spec, /*wireIndex=*/1); + continue; + } + + break; + } + return run; +} + +static void eraseFusableTwoQubitRun(PatternRewriter& rewriter, + const FusableTwoQubitRun& run) { + for (Operation* op : llvm::reverse(run.ops)) { + rewriter.eraseOp(op); + } +} + +static bool maybeFuseRun(IRRewriter& rewriter, OneQubitRun& run, + const decomposition::EulerBasis basis, + const decomposition::NativeGateset& spec) { + Matrix2x2 fused = Matrix2x2::identity(); + for (UnitaryOpInterface u : run.ops) { + Matrix2x2 m; + if (!u.getUnitaryMatrix2x2(m)) { + return false; + } + fused.premultiplyBy(m); + } + + const bool anyNonNative = llvm::any_of(run.ops, [&](UnitaryOpInterface u) { + return !allowsOp(u.getOperation(), spec); + }); + + Operation* firstOp = run.ops.front().getOperation(); + const Value inQubit = run.ops.front().getInputQubit(0); + const Value outQubit = run.ops.back().getOutputQubit(0); + + rewriter.setInsertionPoint(firstOp); + const auto replacement = decomposition::synthesizeUnitary1QEuler( + rewriter, firstOp->getLoc(), inQubit, fused, run.ops.size(), anyNonNative, + basis); + if (!replacement) { + return false; + } + rewriter.replaceAllUsesWith(outQubit, *replacement); + for (auto& op : llvm::reverse(run.ops)) { + rewriter.eraseOp(op.getOperation()); + } + return true; +} + +static bool hasNonNativeOps(Operation* root, + const decomposition::NativeGateset& spec, + bool singleQubitOnly) { + const mlir::WalkResult walkResult = root->walk([&](Operation* op) { + if (!isWalkableUnitaryShell(op)) { + return mlir::WalkResult::advance(); + } + if (singleQubitOnly) { + auto unitary = llvm::dyn_cast(op); + if (!unitary || !unitary.isSingleQubit()) { + return mlir::WalkResult::advance(); + } + } else if (!llvm::isa(op) && !llvm::isa(op)) { + return mlir::WalkResult::advance(); + } + if (!allowsOp(op, spec)) { + return mlir::WalkResult::interrupt(); + } + return mlir::WalkResult::advance(); + }); + return walkResult.wasInterrupted(); +} + +static LogicalResult synthesizeTwoQubitOp( + IRRewriter& rewriter, Operation* op, Location loc, Value in0, Value in1, + const decomposition::NativeGateset& spec, llvm::Twine matrixErrorMsg, + llvm::Twine synthesisErrorMsg) { + if (allowsOp(op, spec)) { + return success(); + } + Matrix4x4 matrix; + if (!assignTwoQubitOpMatrix(op, matrix)) { + op->emitError(matrixErrorMsg); + return failure(); + } + rewriter.setInsertionPoint(op); + Value out0; + Value out1; + if (failed(decomposition::synthesizeUnitary2QWeyl( + rewriter, loc, in0, in1, matrix, spec, out0, out1))) { + op->emitError(synthesisErrorMsg); + return failure(); + } + rewriter.replaceOp(op, ValueRange{out0, out1}); + return success(); +} + +namespace { + +struct FuseTwoQubitWindowPattern + : public OpInterfaceRewritePattern { + FuseTwoQubitWindowPattern(MLIRContext* ctx, + decomposition::NativeGateset specIn) + : OpInterfaceRewritePattern(ctx), spec(std::move(specIn)) {} + + LogicalResult matchAndRewrite(UnitaryOpInterface op, + PatternRewriter& rewriter) const override { + if (!isTwoQubitRunStart(op)) { + return failure(); + } + + FusableTwoQubitRun run = scanFusableTwoQubitRun(op, spec); + if (run.ops.size() < 2) { + return failure(); + } + + const auto native = spec.decomposeTarget(run.composed); + if (!native || + !shouldApplyTwoQubitRunReplacement(run, native->numBasisUses)) { + return failure(); + } + + Operation* firstOp = run.ops.front(); + auto firstUnitary = llvm::cast(firstOp); + const Value inA = firstUnitary.getInputQubit(0); + const Value inB = firstUnitary.getInputQubit(1); + + rewriter.setInsertionPoint(firstOp); + Value newA; + Value newB; + if (failed(decomposition::synthesizeUnitary2QWeyl( + rewriter, firstOp->getLoc(), inA, inB, run.composed, spec, newA, + newB))) { + firstOp->emitError("failed to emit synthesized two-qubit gate sequence"); + return failure(); + } + rewriter.replaceAllUsesWith(run.tailA, newA); + rewriter.replaceAllUsesWith(run.tailB, newB); + eraseFusableTwoQubitRun(rewriter, run); + return success(); + } + + decomposition::NativeGateset spec; +}; + +} // namespace + +static LogicalResult +fuseTwoQubitUnitaryRuns(Operation* root, + const decomposition::NativeGateset& spec) { + RewritePatternSet patterns(root->getContext()); + patterns.add(patterns.getContext(), spec); + return applyPatternsGreedily(root, std::move(patterns)); +} + +namespace { + +struct FuseTwoQubitUnitaryRunsPass + : impl::FuseTwoQubitUnitaryRunsBase { + FuseTwoQubitUnitaryRunsPass() = default; + + explicit FuseTwoQubitUnitaryRunsPass(FuseTwoQubitUnitaryRunsOptions options) + : FuseTwoQubitUnitaryRunsBase(std::move(options)) {} + +protected: + void runOnOperation() override { + if (llvm::StringRef(nativeGates).trim().empty()) { + return; + } + auto specOpt = decomposition::NativeGateset::parse(nativeGates); + if (!specOpt) { + getOperation().emitError() << "unsupported native gateset (native-gates='" + << nativeGates << "')"; + signalPassFailure(); + return; + } + const auto& spec = *specOpt; + const decomposition::EulerBasis oneQubitBasis = *spec.eulerBasis; + + IRRewriter rewriter(&getContext()); + + fuseOneQubitRuns(rewriter, spec, oneQubitBasis); + if (failed(fuseTwoQubitUnitaryRuns(getOperation(), spec))) { + signalPassFailure(); + return; + } + constexpr unsigned kMaxSynthesisSweeps = 4; + for (unsigned i = 0; i < kMaxSynthesisSweeps; ++i) { + if (failed(synthesizeRemainingOps(rewriter, spec, oneQubitBasis))) { + signalPassFailure(); + return; + } + if (!hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/true)) { + break; + } + } + if (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/true)) { + getOperation().emitError() + << "native gate synthesis did not converge within " + << kMaxSynthesisSweeps + << " sweeps (single-qubit ops remain outside the native menu)"; + signalPassFailure(); + return; + } + fuseOneQubitRuns(rewriter, spec, oneQubitBasis); + constexpr unsigned kPostMenuCleanupSweeps = 4; + unsigned postMenuSweepsRemaining = kPostMenuCleanupSweeps; + while (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/false) && + postMenuSweepsRemaining-- > 0) { + if (failed(synthesizeRemainingOps(rewriter, spec, oneQubitBasis))) { + signalPassFailure(); + return; + } + fuseOneQubitRuns(rewriter, spec, oneQubitBasis); + } + if (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/false)) { + getOperation().emitError() + << "native gate synthesis: operations remain outside the native menu " + "after final cleanup"; + signalPassFailure(); + return; + } + } + +private: + void fuseOneQubitRuns(IRRewriter& rewriter, + const decomposition::NativeGateset& spec, + const decomposition::EulerBasis basis) { + SmallVector runs; + llvm::DenseMap tailOpToRun; + + // Require single-use tail output so fan-out wires are not fused away. + getOperation()->walk([&](Operation* op) { + auto unitary = fusibleSingleQubitOp(op); + if (!unitary) { + return; + } + Value inQubit = unitary.getInputQubit(0); + Operation* defOp = inQubit.getDefiningOp(); + auto it = + (defOp != nullptr) ? tailOpToRun.find(defOp) : tailOpToRun.end(); + const bool canExtend = it != tailOpToRun.end() && inQubit.hasOneUse(); + if (canExtend) { + const size_t runIdx = it->second; + runs[runIdx].ops.push_back(unitary); + tailOpToRun.erase(it); + tailOpToRun[op] = runIdx; + } else { + runs.push_back(OneQubitRun{}); + runs.back().ops.push_back(unitary); + tailOpToRun[op] = runs.size() - 1; + } + }); + + for (auto& run : runs) { + if (run.ops.size() < 2) { + continue; + } + (void)maybeFuseRun(rewriter, run, basis, spec); + } + } + + LogicalResult synthesizeRemainingOps(IRRewriter& rewriter, + const decomposition::NativeGateset& spec, + const decomposition::EulerBasis basis) { + SmallVector ops; + collectUnitaryOpsInPreOrder(getOperation(), ops); + llvm::DenseSet erasedOps; + + for (Operation* op : ops) { + if (erasedOps.contains(op)) { + continue; + } + if (!isWalkableUnitaryShell(op)) { + continue; + } + + if (auto ctrl = llvm::dyn_cast(op)) { + const bool wasAlreadyNative = allowsOp(op, spec); + if (failed(synthesizeControlled(rewriter, ctrl, spec))) { + return failure(); + } + if (!wasAlreadyNative) { + erasedOps.insert(op); + } + continue; + } + + auto unitary = llvm::dyn_cast(op); + if (!unitary) { + continue; + } + + if (unitary.isSingleQubit()) { + if (!allowsOp(op, spec)) { + if (failed(rewriteSingleQubit(rewriter, op, unitary, basis))) { + return failure(); + } + erasedOps.insert(op); + } + continue; + } + + if (unitary.isTwoQubit()) { + if (failed(synthesizeBareTwoQubit(rewriter, op, unitary, spec))) { + return failure(); + } + erasedOps.insert(op); + } + } + return success(); + } + + static LogicalResult + rewriteSingleQubit(IRRewriter& rewriter, Operation* op, + UnitaryOpInterface unitary, + const decomposition::EulerBasis basis) { + rewriter.setInsertionPoint(op); + const Value in = unitary.getInputQubit(0); + Matrix2x2 matrix; + if (!unitary.getUnitaryMatrix2x2(matrix)) { + op->emitError("single-qubit operation with non-constant parameters is " + "not supported for native synthesis"); + return failure(); + } + const Value replaced = + emitSingleQubitMatrix(rewriter, op->getLoc(), in, matrix, basis); + rewriter.replaceOp(op, replaced); + return success(); + } + + static LogicalResult + synthesizeControlled(IRRewriter& rewriter, CtrlOp ctrl, + const decomposition::NativeGateset& spec) { + return synthesizeTwoQubitOp( + rewriter, ctrl.getOperation(), ctrl.getLoc(), ctrl.getInputControl(0), + ctrl.getInputTarget(0), spec, + "native synthesis: cannot build a constant 4x4 matrix for this " + "controlled gate (unsupported body or non-constant parameters)", + "controlled gate not allowed by selected gateset"); + } + + static LogicalResult + synthesizeBareTwoQubit(IRRewriter& rewriter, Operation* op, + UnitaryOpInterface unitary, + const decomposition::NativeGateset& spec) { + return synthesizeTwoQubitOp( + rewriter, op, op->getLoc(), unitary.getInputQubit(0), + unitary.getInputQubit(1), spec, + "unsupported two-qubit operation for selected gateset", + "unsupported two-qubit operation for selected gateset"); + } +}; + +} // namespace + +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt index d59780f461..163412b775 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt @@ -8,4 +8,5 @@ add_subdirectory(Decomposition) add_subdirectory(Mapping) +add_subdirectory(NativeSynthesis) add_subdirectory(Optimizations) diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt new file mode 100644 index 0000000000..0fa87a5daf --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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 + +set(target_name mqt-core-mlir-unittest-fuse-two-qubit-unitary-runs) +add_executable(${target_name} test_fuse_two_qubit_unitary_runs.cpp) + +target_link_libraries( + ${target_name} + PRIVATE MLIRParser + GTest::gtest_main + MLIRQCPrograms + MLIRQCOProgramBuilder + MLIRQCOUtils + MLIRQCToQCO + MLIRQCOTransforms + MLIRPass + MLIRFuncDialect + MLIRArithDialect + MLIRIR + MLIRSupport + LLVMSupport) + +mqt_mlir_configure_unittest_target(${target_name}) + +gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp new file mode 100644 index 0000000000..2a205fb599 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -0,0 +1,775 @@ +/* + * 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 "TestCaseUtils.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QCO/Utils/Matrix.h" +#include "qc_programs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mqt::test; + +using ProgramFn = void (*)(mlir::qc::QCProgramBuilder&); +using NativePredicate = bool (*)(OwningOpRef&); + +static void controlledXH(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.ctrl(q0, {q1}, [&](ValueRange targets) { + b.x(targets[0]); + b.h(targets[0]); + }); +} + +template +static bool onlyTheseOps(OwningOpRef& moduleOp, bool allowCx, + bool allowCz) { + bool ok = true; + std::ignore = moduleOp->walk([&](UnitaryOpInterface op) { + Operation* raw = op.getOperation(); + if (llvm::isa_and_present(raw->getParentOp())) { + return WalkResult::advance(); + } + if (llvm::isa(raw)) { + return WalkResult::advance(); + } + if (auto ctrl = llvm::dyn_cast(raw)) { + if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { + ok = false; + return WalkResult::interrupt(); + } + Operation* body = ctrl.getBodyUnitary(0).getOperation(); + const bool isCx = llvm::isa(body); + const bool isCz = llvm::isa(body); + if ((isCx && allowCx) || (isCz && allowCz)) { + return WalkResult::advance(); + } + ok = false; + return WalkResult::interrupt(); + } + if (!llvm::isa(raw)) { + ok = false; + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }); + return ok; +} + +static bool onlyIbmBasicCxOps(OwningOpRef& m) { + return onlyTheseOps(m, true, false); +} +static bool onlyIbmBasicCzOps(OwningOpRef& m) { + return onlyTheseOps(m, false, true); +} +static bool onlyGenericU3CxOps(OwningOpRef& m) { + return onlyTheseOps(m, true, false); +} +static bool onlyIqmDefaultOps(OwningOpRef& m) { + return onlyTheseOps(m, false, true); +} +static bool onlyAxisPairRxRzCxOps(OwningOpRef& m) { + return onlyTheseOps(m, true, false); +} +static bool onlyAxisPairRxRyCxOps(OwningOpRef& m) { + return onlyTheseOps(m, true, false); +} +static bool onlyAxisPairRyRzCzOps(OwningOpRef& m) { + return onlyTheseOps(m, false, true); +} +static bool onlyGenericU3CxOrCzOps(OwningOpRef& m) { + return onlyTheseOps(m, true, true); +} + +static std::optional unitaryQubitOperand(UnitaryOpInterface op, + std::size_t index) { + if (index >= op.getNumQubits()) { + return std::nullopt; + } + Value v = op->getOperand(index); + if (!llvm::isa(v.getType())) { + return std::nullopt; + } + return v; +} + +static std::optional unitaryQubitResult(UnitaryOpInterface op, + std::size_t index) { + if (index >= op.getNumQubits()) { + return std::nullopt; + } + Value v = op->getResult(index); + if (!llvm::isa(v.getType())) { + return std::nullopt; + } + return v; +} + +static bool extractSingleQubitMatrix(UnitaryOpInterface op, Matrix2x2& out) { + if (op.getUnitaryMatrix2x2(out)) { + return true; + } + DynamicMatrix dynamic; + if (!op.getUnitaryMatrixDynamic(dynamic) || dynamic.rows() != 2 || + dynamic.cols() != 2) { + return false; + } + out = Matrix2x2::fromElements(dynamic(0, 0), dynamic(0, 1), dynamic(1, 0), + dynamic(1, 1)); + return true; +} + +static bool extractTwoQubitMatrix(UnitaryOpInterface op, Matrix4x4& out) { + if (auto ctrl = llvm::dyn_cast(op.getOperation())) { + if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { + return false; + } + return op.getUnitaryMatrix4x4(out); + } + return op.getUnitaryMatrix4x4(out); +} + +static std::optional +computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { + ModuleOp module = moduleOp.get(); + if (!module) { + return std::nullopt; + } + + llvm::DenseMap qubitIds; + std::size_t nextQubitId = 0; + std::size_t numQubits = 0; + + for (auto func : module.getOps()) { + for (auto& block : func.getBlocks()) { + for (auto& rawOp : block.getOperations()) { + if (auto staticOp = llvm::dyn_cast(&rawOp)) { + const auto index = static_cast(staticOp.getIndex()); + qubitIds.try_emplace(staticOp.getQubit(), index); + numQubits = std::max(numQubits, index + 1); + } else if (auto alloc = llvm::dyn_cast(&rawOp)) { + qubitIds.try_emplace(alloc.getResult(), nextQubitId++); + numQubits = std::max(numQubits, nextQubitId); + } + } + } + } + + if (numQubits == 0) { + return std::nullopt; + } + + DynamicMatrix unitary = + DynamicMatrix::identity(static_cast(1ULL << numQubits)); + + auto getQubitId = [&](Value qubit) -> std::optional { + const auto it = qubitIds.find(qubit); + if (it == qubitIds.end()) { + return std::nullopt; + } + return it->second; + }; + + for (auto func : module.getOps()) { + for (auto& block : func.getBlocks()) { + for (auto& rawOp : block.getOperations()) { + auto op = llvm::dyn_cast(&rawOp); + if (!op) { + continue; + } + if (llvm::isa(op.getOperation())) { + continue; + } + + if (op.isSingleQubit()) { + const auto qIn = unitaryQubitOperand(op, 0); + if (!qIn) { + return std::nullopt; + } + const auto qid = getQubitId(*qIn); + if (!qid) { + return std::nullopt; + } + Matrix2x2 oneQ; + if (!extractSingleQubitMatrix(op, oneQ)) { + return std::nullopt; + } + unitary = oneQ.embedInNqubit(numQubits, *qid) * unitary; + const auto qOut = unitaryQubitResult(op, 0); + if (!qOut) { + return std::nullopt; + } + qubitIds[*qOut] = *qid; + continue; + } + + if (op.isTwoQubit()) { + const auto q0In = unitaryQubitOperand(op, 0); + const auto q1In = unitaryQubitOperand(op, 1); + if (!q0In || !q1In) { + return std::nullopt; + } + const auto q0id = getQubitId(*q0In); + const auto q1id = getQubitId(*q1In); + if (!q0id || !q1id) { + return std::nullopt; + } + Matrix4x4 twoQ; + if (!extractTwoQubitMatrix(op, twoQ)) { + return std::nullopt; + } + unitary = twoQ.embedInNqubit(numQubits, *q0id, *q1id) * unitary; + const auto q0Out = unitaryQubitResult(op, 0); + const auto q1Out = unitaryQubitResult(op, 1); + if (!q0Out || !q1Out) { + return std::nullopt; + } + qubitIds[*q0Out] = *q0id; + qubitIds[*q1Out] = *q1id; + continue; + } + + return std::nullopt; + } + } + } + + return unitary; +} + +namespace { + +struct ProfileCase { + const char* name; + ProgramFn program; + const char* nativeGates; + NativePredicate isNative; + bool checkEquivalence; +}; + +struct FusionCase { + const char* name; + ProgramFn program; + const char* nativeGates; + std::optional exactCtrlCount; + std::optional minCtrlCount; + bool checkTwoQUnitary; +}; + +class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { +protected: + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + static void runFusePipeline(OwningOpRef& moduleOp, + StringRef nativeGates) { + PassManager pm(moduleOp->getContext()); + pm.addPass(createQCToQCO()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = nativeGates.str(), + })); + ASSERT_TRUE(succeeded(pm.run(*moduleOp))); + } + + static void runQcToQco(OwningOpRef& moduleOp) { + PassManager pm(moduleOp->getContext()); + pm.addPass(createQCToQCO()); + ASSERT_TRUE(succeeded(pm.run(*moduleOp))); + } + + static void runTwoQFuse(OwningOpRef& moduleOp, + StringRef nativeGates) { + PassManager pm(moduleOp->getContext()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = nativeGates.str(), + })); + ASSERT_TRUE(succeeded(pm.run(*moduleOp))); + } + + static void expectQcoModulesEquivalent(const OwningOpRef& lhs, + const OwningOpRef& rhs) { + const auto lhsUnitary = computeUnitaryFromQcoModule(lhs); + ASSERT_TRUE(lhsUnitary.has_value()); + const auto rhsUnitary = computeUnitaryFromQcoModule(rhs); + ASSERT_TRUE(rhsUnitary.has_value()); + EXPECT_TRUE(isEquivalentUpToGlobalPhase(*lhsUnitary, *rhsUnitary)); + } + + void expectNativeAfterSynthesis(ProgramFn program, StringRef nativeGates, + NativePredicate isNative) { + auto moduleOp = mlir::qc::QCProgramBuilder::build(context.get(), program); + runFusePipeline(moduleOp, nativeGates); + EXPECT_TRUE(isNative(moduleOp)); + } + + void expectEquivalentAndNativeAfterSynthesis(ProgramFn program, + StringRef nativeGates, + NativePredicate isNative) { + auto expected = mlir::qc::QCProgramBuilder::build(context.get(), program); + runQcToQco(expected); + auto synthesized = + mlir::qc::QCProgramBuilder::build(context.get(), program); + runFusePipeline(synthesized, nativeGates); + EXPECT_TRUE(isNative(synthesized)); + expectQcoModulesEquivalent(expected, synthesized); + } + + void expectSynthesisFailure(ProgramFn program, StringRef nativeGates) { + auto moduleOp = mlir::qc::QCProgramBuilder::build(context.get(), program); + PassManager pm(moduleOp->getContext()); + pm.addPass(createQCToQCO()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = nativeGates.str(), + })); + EXPECT_TRUE(failed(pm.run(*moduleOp))); + } + + void expectTwoQFusePreservesUnitary(ProgramFn program, + StringRef nativeGates) { + auto expected = mlir::qc::QCProgramBuilder::build(context.get(), program); + ASSERT_TRUE(expected); + runQcToQco(expected); + auto fused = mlir::qc::QCProgramBuilder::build(context.get(), program); + ASSERT_TRUE(fused); + runQcToQco(fused); + runTwoQFuse(fused, nativeGates); + ASSERT_TRUE(succeeded(verify(*fused))); + expectQcoModulesEquivalent(expected, fused); + } + + static std::size_t countCtrlOps(const OwningOpRef& moduleOp) { + std::size_t count = 0; + moduleOp.get()->walk([&](CtrlOp) { ++count; }); + return count; + } + + std::unique_ptr context; +}; + +class FuseTwoQubitProfileTest + : public FuseTwoQubitUnitaryRunsPassTest, + public testing::WithParamInterface {}; + +class FuseTwoQubitFusionTest : public FuseTwoQubitUnitaryRunsPassTest, + public testing::WithParamInterface { +}; + +} // namespace + +static void fusionCxCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q0, q1); +} + +static void fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.t(q1); + b.s(q0); + b.cx(q0, q1); +} + +static void fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q1, q2); + b.cx(q0, q1); +} + +static void fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.cx(q0, q1); + b.rz(0.17, q1); + b.cx(q1, q2); +} + +static void fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.barrier({q0, q1}); + b.cx(q0, q1); +} + +static void fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); +} + +static void fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.h(q0); + b.cx(q0, q1); +} + +static void fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.rz(0.11, q0); + b.ry(0.22, q1); + b.cx(q0, q1); +} + +static void inverseTwoX(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + b.inv({q0}, [&](mlir::ValueRange qubits) { + b.x(qubits[0]); + b.x(qubits[0]); + }); +} + +static void broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.x(q0); + b.y(q1); + b.h(q0); + b.sx(q1); + b.rx(0.13, q0); + b.ry(-0.47, q1); + b.rz(0.29, q0); + b.cz(q0, q1); +} + +static void zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.rx(0.0, q0); + b.ry(0.0, q1); + b.rz(0.0, q0); + b.p(0.0, q1); + b.cz(q0, q1); +} + +static void hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.s(q0); + b.t(q0); + b.y(q0); + b.cx(q0, q1); +} + +static void cxYOnQ1(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.y(q1); +} + +static void hCxTOnQ1(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q1); + b.cx(q0, q1); + b.t(q1); +} + +static void xYSXCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.x(q0); + b.y(q0); + b.sx(q0); + b.cz(q0, q1); +} + +static void hYCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.y(q0); + b.cx(q0, q1); +} + +static void zCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.z(q0); + b.cx(q0, q1); +} + +static void xHCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.x(q0); + b.h(q0); + b.cz(q0, q1); +} + +static void hq0Yq1CxSq0(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.y(q1); + b.cx(q0, q1); + b.s(q0); +} + +static void hCxSq1(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.s(q1); +} + +static void threeQGhz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.cx(q1, q2); +} + +static void determinismSwap(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.swap(q0, q1); + b.dealloc(q0); + b.dealloc(q1); +} + +TEST_P(FuseTwoQubitProfileTest, SynthesizesToNativeMenu) { + const ProfileCase& c = GetParam(); + if (c.checkEquivalence) { + expectEquivalentAndNativeAfterSynthesis(c.program, c.nativeGates, + c.isNative); + } else { + expectNativeAfterSynthesis(c.program, c.nativeGates, c.isNative); + } +} + +INSTANTIATE_TEST_SUITE_P( + Menus, FuseTwoQubitProfileTest, + testing::Values( + ProfileCase{"SwapIbmBasic", mlir::qc::swap, "x,sx,rz,cx", + onlyIbmBasicCxOps, false}, + ProfileCase{"SwapGeneric", mlir::qc::swap, "u,cx", onlyGenericU3CxOps, + false}, + ProfileCase{"SwapIqm", mlir::qc::swap, "r,cz", onlyIqmDefaultOps, + false}, + ProfileCase{"HstycxIbm", hstycxTwoQ, "x,sx,rz,cx", onlyIbmBasicCxOps, + false}, + ProfileCase{"CxYIqm", cxYOnQ1, "r,cz", onlyIqmDefaultOps, false}, + ProfileCase{"BroadOneQIqm", broadOneQThenCz, "r,cz", onlyIqmDefaultOps, + false}, + ProfileCase{"ZeroAngleRyRzCz", zeroAngleThenCz, "ry,rz,cz", + onlyAxisPairRyRzCzOps, false}, + ProfileCase{"HCxTIbmCz", hCxTOnQ1, "x,sx,rz,cz", onlyIbmBasicCzOps, + false}, + ProfileCase{"XYSXCzIqm", xYSXCz, "r,cz", onlyIqmDefaultOps, false}, + ProfileCase{"HYCxRxRz", hYCx, "rx,rz,cx", onlyAxisPairRxRzCxOps, false}, + ProfileCase{"ZCxRxRy", zCx, "rx,ry,cx", onlyAxisPairRxRyCxOps, false}, + ProfileCase{"Hq0Yq1CxSq0", hq0Yq1CxSq0, "u,cx", onlyGenericU3CxOps, + true}, + ProfileCase{"XHCzRyRz", xHCz, "ry,rz,cz", onlyAxisPairRyRzCzOps, true}, + ProfileCase{"HCxSq1MultiEnt", hCxSq1, "u,cx,cz", onlyGenericU3CxOrCzOps, + true}), + [](const testing::TestParamInfo& info) { + return info.param.name; + }); + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, EmptyNativeGatesSkipsPass) { + auto module = mlir::qc::QCProgramBuilder::build(context.get(), fusionCxCx); + ASSERT_TRUE(module); + runQcToQco(module); + std::string before; + llvm::raw_string_ostream osBefore(before); + module->print(osBefore); + + PassManager pm(module->getContext()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = "", + })); + ASSERT_TRUE(succeeded(pm.run(*module))); + + std::string after; + llvm::raw_string_ostream osAfter(after); + module->print(osAfter); + EXPECT_EQ(before, after); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, InverseWrappedOpsSynthesize) { + expectNativeAfterSynthesis(inverseTwoX, "x,sx,rz,cx", onlyIbmBasicCxOps); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, ControlledXHSynthesizesToNativeMenu) { + expectEquivalentAndNativeAfterSynthesis(controlledXH, "x,sx,rz,cx", + onlyIbmBasicCxOps); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, FailsForUnsupportedNativeGateMenu) { + expectSynthesisFailure(mlir::qc::h, "not-a-gate"); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, + FailsForNativeGateMenuWithoutSingleQEmitter) { + expectSynthesisFailure(mlir::qc::singleControlledX, "cx,cz"); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, FailsForMultiControlledGateStructure) { + expectSynthesisFailure(mlir::qc::multipleControlledX, "x,sx,rz,cx"); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, + CandidateSelectionIsDeterministicAcrossRuns) { + auto buildFn = [&] { + return mlir::qc::QCProgramBuilder::build(context.get(), determinismSwap); + }; + auto firstModule = buildFn(); + runFusePipeline(firstModule, "u,cx"); + auto secondModule = buildFn(); + runFusePipeline(secondModule, "u,cx"); + + std::string first; + std::string second; + llvm::raw_string_ostream osFirst(first); + llvm::raw_string_ostream osSecond(second); + firstModule->print(osFirst); + secondModule->print(osSecond); + EXPECT_EQ(first, second); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, ThreeQubitGhzEquivalentOnCoreProfiles) { + const std::array profiles{{ + {.name = "GhzIbm", + .program = threeQGhz, + .nativeGates = "x,sx,rz,cx", + .isNative = onlyIbmBasicCxOps, + .checkEquivalence = false}, + {.name = "GhzGeneric", + .program = threeQGhz, + .nativeGates = "u,cx", + .isNative = onlyGenericU3CxOps, + .checkEquivalence = false}, + {.name = "GhzIqm", + .program = threeQGhz, + .nativeGates = "r,cz", + .isNative = onlyIqmDefaultOps, + .checkEquivalence = false}, + }}; + for (const ProfileCase& profile : profiles) { + auto expected = mlir::qc::QCProgramBuilder::build(context.get(), threeQGhz); + runQcToQco(expected); + auto synthesized = + mlir::qc::QCProgramBuilder::build(context.get(), threeQGhz); + runFusePipeline(synthesized, profile.nativeGates); + EXPECT_TRUE(profile.isNative(synthesized)); + expectQcoModulesEquivalent(expected, synthesized); + } +} + +TEST_P(FuseTwoQubitFusionTest, WindowFusionBehavior) { + const FusionCase& c = GetParam(); + if (c.checkTwoQUnitary) { + expectTwoQFusePreservesUnitary(c.program, c.nativeGates); + } + auto module = mlir::qc::QCProgramBuilder::build(context.get(), c.program); + ASSERT_TRUE(module); + runQcToQco(module); + runTwoQFuse(module, c.nativeGates); + if (c.exactCtrlCount) { + EXPECT_EQ(countCtrlOps(module), *c.exactCtrlCount); + } + if (c.minCtrlCount) { + EXPECT_GE(countCtrlOps(module), *c.minCtrlCount); + } +} + +INSTANTIATE_TEST_SUITE_P( + Windows, FuseTwoQubitFusionTest, + testing::Values(FusionCase{"AdjacentCxCancel", fusionCxCx, "u,cx", 0, + std::nullopt, true}, + FusionCase{"InterleavedOneQ", fusionHCxInterleavedTCx, + "u,cx", std::nullopt, std::nullopt, true}, + FusionCase{"DifferentPairBoundary", fusionThreeLineCx, + "u,cx", std::nullopt, 1, false}, + FusionCase{"SharedWireOneQ", fusionCxRSharedOtherPair, + "u,cx", std::nullopt, 2, false}, + FusionCase{"BarrierBoundary", fusionCxBarrierCx, "u,cx", 2, + std::nullopt, false}, + FusionCase{"SwappedWireOrder", fusionSwapCxPattern, "u,cx", + std::nullopt, std::nullopt, true}, + FusionCase{"OffMenuGateInWindow", fusionOffMenuGateInWindow, + "u,cx", std::nullopt, std::nullopt, true}, + FusionCase{"DualWireOneQBetweenCx", + fusionDualWireOneQBetweenCx, "u,cx", + std::nullopt, std::nullopt, true}), + [](const testing::TestParamInfo& info) { + return info.param.name; + }); + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, InvalidNativeGatesFailsPass) { + auto module = mlir::qc::QCProgramBuilder::build(context.get(), fusionCxCx); + ASSERT_TRUE(module); + runQcToQco(module); + PassManager pm(module->getContext()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = "not-a-gate", + })); + EXPECT_TRUE(failed(pm.run(*module))); +} diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index 618daff0db..2af990979f 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -806,6 +806,14 @@ TEST(SymmetricEigensolver, HandlesDegenerateSpectrum) { EXPECT_TRUE((v.transpose() * v).isIdentity()); } +TEST(GlobalPhaseEquivalence, IsEquivalentUpToGlobalPhase) { + const Matrix2x2 pauliX = Matrix2x2::fromElements(0, 1, 1, 0); + const Complex phase{0.0, 1.0}; + EXPECT_TRUE(isEquivalentUpToGlobalPhase(pauliX, phase * pauliX)); + EXPECT_TRUE(isEquivalentUpToGlobalPhase(pauliX, pauliX)); + EXPECT_FALSE(isEquivalentUpToGlobalPhase(pauliX, Matrix2x2::identity())); +} + TEST(Eigensolver, EmptyMatrixReturnsNullopt) { EXPECT_FALSE(DynamicMatrix{}.eigenDecomposition().has_value()); } From 9bde3bf1ad1f682b9a9317f13f22393f0780e19e Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 10:14:25 +0200 Subject: [PATCH 02/27] =?UTF-8?q?=F0=9F=94=A5=20Remove=20the=20`isEquivale?= =?UTF-8?q?ntUpToGlobalPhase`=20function=20from=20the=20matrix=20utilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Utils/Matrix.h | 27 ------------------- .../test_fuse_two_qubit_unitary_runs.cpp | 13 ++++++--- .../Dialect/QCO/Utils/test_unitary_matrix.cpp | 8 ------ 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index d046e7bb58..38816a9c85 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -834,32 +833,6 @@ concept SupportedMatrix = std::same_as || std::same_as || std::same_as || std::same_as; -/** - * @brief Checks whether two unitaries are equal up to a global phase. - * - * Uses `trace(rhs.adjoint() * lhs)` to infer a unit-modulus phase factor, then - * compares `lhs` to `factor * rhs` with @ref isApprox. A near-zero overlap - * (`|trace| <= tol`) is treated as not equivalent to avoid dividing by a tiny - * number. - * - * @tparam Matrix Any supported fixed- or dynamic-size matrix type. - * @param lhs Left-hand unitary. - * @param rhs Right-hand unitary. - * @param tol Absolute tolerance for overlap and entry-wise comparison. - * @return True when @p lhs and @p rhs differ only by a global phase. - */ -template -[[nodiscard]] bool isEquivalentUpToGlobalPhase(const Matrix& lhs, - const Matrix& rhs, - double tol = MATRIX_TOLERANCE) { - const auto overlap = (rhs.adjoint() * lhs).trace(); - if (std::abs(overlap) <= tol) { - return false; - } - const auto factor = overlap / std::abs(overlap); - return lhs.isApprox(factor * rhs, tol); -} - /// Scalar-on-the-left multiply `scalar * matrix` (commutes with the member /// `matrix * scalar`). Provided so generic code can scale a matrix from /// either side. diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 2a205fb599..68571420bd 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -202,6 +202,7 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { DynamicMatrix unitary = DynamicMatrix::identity(static_cast(1ULL << numQubits)); + Complex globalPhase{1.0, 0.0}; auto getQubitId = [&](Value qubit) -> std::optional { const auto it = qubitIds.find(qubit); @@ -218,7 +219,13 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { if (!op) { continue; } - if (llvm::isa(op.getOperation())) { + if (llvm::isa(op.getOperation())) { + continue; + } + if (auto gphase = llvm::dyn_cast(op.getOperation())) { + if (const auto matrix = gphase.getUnitaryMatrix()) { + globalPhase *= (*matrix)(0, 0); + } continue; } @@ -275,7 +282,7 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { } } - return unitary; + return globalPhase * unitary; } namespace { @@ -339,7 +346,7 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { ASSERT_TRUE(lhsUnitary.has_value()); const auto rhsUnitary = computeUnitaryFromQcoModule(rhs); ASSERT_TRUE(rhsUnitary.has_value()); - EXPECT_TRUE(isEquivalentUpToGlobalPhase(*lhsUnitary, *rhsUnitary)); + EXPECT_TRUE(lhsUnitary->isApprox(*rhsUnitary)); } void expectNativeAfterSynthesis(ProgramFn program, StringRef nativeGates, diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index 2af990979f..618daff0db 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -806,14 +806,6 @@ TEST(SymmetricEigensolver, HandlesDegenerateSpectrum) { EXPECT_TRUE((v.transpose() * v).isIdentity()); } -TEST(GlobalPhaseEquivalence, IsEquivalentUpToGlobalPhase) { - const Matrix2x2 pauliX = Matrix2x2::fromElements(0, 1, 1, 0); - const Complex phase{0.0, 1.0}; - EXPECT_TRUE(isEquivalentUpToGlobalPhase(pauliX, phase * pauliX)); - EXPECT_TRUE(isEquivalentUpToGlobalPhase(pauliX, pauliX)); - EXPECT_FALSE(isEquivalentUpToGlobalPhase(pauliX, Matrix2x2::identity())); -} - TEST(Eigensolver, EmptyMatrixReturnsNullopt) { EXPECT_FALSE(DynamicMatrix{}.eigenDecomposition().has_value()); } From 408b5143d01bfcea9e8858b1a0f76a28d45ea93f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 10:14:37 +0200 Subject: [PATCH 03/27] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG.md=20to?= =?UTF-8?q?=20reflect=20changes=20in=20the=20`fuse-two-qubit-unitary-runs`?= =?UTF-8?q?=20pass=20and=20remove=20outdated=20PR=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a46b692f..af4e282f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ releases may include breaking changes. ### Added - ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit - unitary windows via Weyl/KAK resynthesis ([#1655]) ([**@simon1hofmann**]) + unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**]) - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function @@ -602,6 +602,7 @@ changelogs._ +[#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 @@ -653,7 +654,6 @@ changelogs._ [#1664]: https://github.com/munich-quantum-toolkit/core/pull/1664 [#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662 [#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660 -[#1655]: https://github.com/munich-quantum-toolkit/core/pull/1655 [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 [#1638]: https://github.com/munich-quantum-toolkit/core/pull/1638 [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 From 6a23d6b842acf888a9eb1ff2a1bb848db6418906 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 10:22:37 +0200 Subject: [PATCH 04/27] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20CMakeLists.txt?= =?UTF-8?q?=20to=20simplify=20header=20file=20collection=20for=20QCO=20tra?= =?UTF-8?q?nsforms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 77d847d3ba..77f594db17 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -24,11 +24,11 @@ add_mlir_library( mqt_mlir_target_use_project_options(MLIRQCOTransforms) -# collect header files (subdirs: Decomposition/, …) -file(GLOB_RECURSE PASSES_HEADERS_SOURCE CONFIGURE_DEPENDS - "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h") -file(GLOB_RECURSE PASSES_HEADERS_BUILD CONFIGURE_DEPENDS - "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.inc") +# collect header files +file(GLOB_RECURSE PASSES_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h) +file(GLOB_RECURSE PASSES_HEADERS_BUILD + ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.inc) # add public headers using file sets target_sources( From 66e5465267c0d9d82def9c84b64440be98d138a6 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 11:10:09 +0200 Subject: [PATCH 05/27] =?UTF-8?q?=F0=9F=8E=A8=20Simplify=20FuseTwoQubitUni?= =?UTF-8?q?taryRuns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Decomposition/Euler.h | 15 + .../Transforms/Decomposition/NativeGateset.h | 13 + .../mlir/Dialect/QCO/Transforms/Passes.td | 44 +- .../Decomposition/NativeGateset.cpp | 46 ++ .../FuseSingleQubitUnitaryRuns.cpp | 26 +- .../FuseTwoQubitUnitaryRuns.cpp | 585 +++++------------- .../Decomposition/test_weyl_decomposition.cpp | 73 +-- 7 files changed, 282 insertions(+), 520 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Euler.h b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Euler.h index 4130967cde..952875d972 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Euler.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Euler.h @@ -20,6 +20,10 @@ #include #include +namespace mlir { +class RewritePatternSet; +} // namespace mlir + namespace mlir::qco::decomposition { /** @@ -99,4 +103,15 @@ synthesizeUnitary1QEuler(OpBuilder& builder, Location loc, Value qubit, */ void emitGPhaseIfNeeded(OpBuilder& builder, Location loc, double phase); +/** + * @brief Populates @p patterns with the single-qubit run fusion rewrite for + * @p basis (the reusable core of `fuse-single-qubit-unitary-runs`). + * + * @param skipControlledBodies When set, single-qubit gates nested in `qco.ctrl` + * bodies are left untouched. + */ +void populateFuseSingleQubitUnitaryRunsPatterns( + RewritePatternSet& patterns, EulerBasis basis, + bool skipControlledBodies = false); + } // namespace mlir::qco::decomposition diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h index caa11c7c1e..ac73592bae 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h @@ -18,6 +18,10 @@ #include #include +namespace mlir { +class Operation; +} // namespace mlir + namespace mlir::qco::decomposition { /** @@ -63,6 +67,15 @@ struct NativeGateset { */ [[nodiscard]] std::optional decomposeTarget(const Matrix4x4& target) const; + + /** + * @brief Whether @p op is already on this native gateset. + * + * `qco.barrier` and `qco.gphase` are always allowed. Single-qubit primitives + * and single-target `qco.ctrl` shells (with an `X`/`Z` body) are checked + * against @p gates. All other ops are rejected. + */ + [[nodiscard]] bool allowsOp(Operation* op) const; }; } // namespace mlir::qco::decomposition diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 8053b2f6a3..d49a44fa36 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -72,45 +72,45 @@ def FuseTwoQubitUnitaryRuns let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "Lower QCO unitary gates to a user-specified native gate menu."; let description = [{ - Rewrites unitaries to match the comma-separated `native-gates` menu. + Rewrites unitaries to match the comma-separated `native-gates` gateset. `qco.barrier` and `qco.gphase` are preserved; `qco.ctrl` must have a single control and a single target. - The menu is a comma-separated list of gate tokens (order not significant) - from which the pass builds a profile: a single-qubit synthesis mode + The gateset is a comma-separated list of gate tokens (order not + significant) from which the pass resolves a single-qubit Euler basis (generic `qco.u` when `u` is present; IBM-style surface gates when all of `x`, `sx`, and `rz`/`p` are present; IQM-style `qco.r` when `r` is present; - or a supported rotation pair chosen from `rx`, `ry`, `rz`) plus optional - two-qubit entanglers `cx`, `cz`, and optional `rzz`. + or a supported rotation pair chosen from `rx`, `ry`, `rz`) plus the + two-qubit entangler `cx` or `cz`. Recognised tokens: `u`, `x`, `sx`, `rz` (or `p`), `rx`, `ry`, `r`, `cx`, - `cz`, `rzz`. An empty or whitespace-only menu is a no-op, which is the - intended pipeline default when synthesis is not needed. An unrecognised - token causes the pass to fail. - - Example menus (each line is one illustrative menu; pick either `cx` or - `cz` as the entangler, or list both if both are native): - - IBM basic (no fractional two-qubit): `x,sx,rz,cx` or `x,sx,rz,cz` - - IBM fractional: `x,sx,rz,rx,rzz,cx` or `x,sx,rz,rx,rzz,cz` + `cz`. An empty or whitespace-only gateset is a no-op, which is the intended + pipeline default when synthesis is not needed. An unrecognised token causes + the pass to fail. + + Example gatesets (each line is one illustrative gateset; pick either `cx` + or `cz` as the entangler, or list both if both are native): + - IBM: `x,sx,rz,cx` or `x,sx,rz,cz` - Generic single-qubit U: `u,cx` or `u,cz` - IQM default: `r,cz` (or `r,cx` if CX is the native entangler) - Rotation pair + entangler: `rx,rz,cx`, `rx,ry,cz`, `ry,rz,cx`, etc. Supported pairs are exactly `rx`+`rz`, `rx`+`ry`, and `ry`+`rz`. - Stages: fuse single-qubit runs; fuse two-qubit windows; up to four - synthesis sweeps over remaining off-menu ops; fuse 1q seams; up to four - further synthesis and fusion rounds until the full menu holds. The pass - fails if anything remains off-menu after those caps. + Stages: fuse single-qubit runs (reusing `fuse-single-qubit-unitary-runs`, + which also lowers lone off-gateset single-qubit gates); fuse two-qubit + windows and lower any remaining off-gateset two-qubit ops via Weyl + synthesis; fuse the single-qubit seams introduced by that synthesis. The + pass fails if anything remains off the native gateset afterwards. Lowering is deterministic: `cx` is preferred over `cz`, single-qubit - factors use the first emitter's Euler basis, and two-qubit window - replacement uses the minimal entangler count from the synthesizer. + factors use the resolved Euler basis, and two-qubit window replacement uses + the minimal entangler count from the synthesizer. }]; let options = [Option< "nativeGates", "native-gates", "std::string", "\"\"", - "Comma-separated native gate menu. Empty or whitespace-only is " - "a no-op. Tokens: u, x, sx, rz (or p), rx, ry, r, cx, cz, rzz. " - "Examples: x,sx,rz,cx; x,sx,rz,rx,rzz,cz; u,cx; r,cz; rx,rz,cx.">]; + "Comma-separated native gateset. Empty or whitespace-only is " + "a no-op. Tokens: u, x, sx, rz (or p), rx, ry, r, cx, cz. " + "Examples: x,sx,rz,cx; u,cx; r,cz; rx,rz,cx.">]; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp index 0fdec01791..4baf5cbd95 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp @@ -10,12 +10,16 @@ #include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/Weyl.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" #include +#include #include +#include #include #include @@ -141,6 +145,48 @@ NativeGateset::decomposeTarget(const Matrix4x4& target) const { return cachedNativeBasisDecomposer(*entangler).decomposeTarget(target); } +static std::optional gateKindFor(UnitaryOpInterface op) { + return llvm::TypeSwitch>( + op.getOperation()) + .Case([](UOp) { return NativeGateKind::U; }) + .Case([](XOp) { return NativeGateKind::X; }) + .Case([](SXOp) { return NativeGateKind::SX; }) + .Case([](RZOp) { return NativeGateKind::RZ; }) + .Case([](RXOp) { return NativeGateKind::RX; }) + .Case([](RYOp) { return NativeGateKind::RY; }) + .Case([](ROp) { return NativeGateKind::R; }) + .Default([](Operation*) { return std::nullopt; }); +} + +static std::optional entanglerKindFor(CtrlOp ctrl) { + if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1 || + ctrl.getNumBodyUnitaries() != 1) { + return std::nullopt; + } + return llvm::TypeSwitch>( + ctrl.getBodyUnitary(0).getOperation()) + .Case([](XOp) { return NativeGateKind::CX; }) + .Case([](ZOp) { return NativeGateKind::CZ; }) + .Default([](Operation*) { return std::nullopt; }); +} + +bool NativeGateset::allowsOp(Operation* op) const { + return llvm::TypeSwitch(op) + .Case([](auto) { return true; }) + .Case([&](CtrlOp ctrl) { + const auto kind = entanglerKindFor(ctrl); + return kind && gates.contains(*kind); + }) + .Case([&](UnitaryOpInterface unitary) { + if (!unitary.isSingleQubit()) { + return false; + } + const auto gate = gateKindFor(unitary); + return gate && gates.contains(*gate); + }) + .Default([](Operation*) { return false; }); +} + std::optional NativeGateset::parse(StringRef nativeGates) { auto gates = parseGateSet(nativeGates); if (!gates) { diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp index 73fe887dfb..b6fa901035 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp @@ -137,10 +137,13 @@ namespace { struct FuseSingleQubitUnitaryRunsPattern final : OpInterfaceRewritePattern { FuseSingleQubitUnitaryRunsPattern(MLIRContext* context, - const decomposition::EulerBasis basis) - : OpInterfaceRewritePattern(context), basis(basis) {} + const decomposition::EulerBasis basis, + const bool skipControlledBodies) + : OpInterfaceRewritePattern(context), basis(basis), + skipControlledBodies(skipControlledBodies) {} decomposition::EulerBasis basis; + bool skipControlledBodies; /** * @brief Whether `op` starts a run. @@ -165,6 +168,10 @@ struct FuseSingleQubitUnitaryRunsPattern final */ LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { + if (skipControlledBodies && + (op.getOperation()->getParentOfType() != nullptr)) { + return failure(); + } if (!isRunStart(op)) { return failure(); } @@ -208,8 +215,8 @@ struct FuseSingleQubitUnitaryRunsPass final } RewritePatternSet patterns(&getContext()); - patterns.add(patterns.getContext(), - *parsed); + decomposition::populateFuseSingleQubitUnitaryRunsPatterns( + patterns, *parsed, /*skipControlledBodies=*/false); if (failed(applyPatternsGreedily(module, std::move(patterns)))) { signalPassFailure(); @@ -220,3 +227,14 @@ struct FuseSingleQubitUnitaryRunsPass final } // namespace } // namespace mlir::qco + +namespace mlir::qco::decomposition { + +void populateFuseSingleQubitUnitaryRunsPatterns( + RewritePatternSet& patterns, const EulerBasis basis, + const bool skipControlledBodies) { + patterns.add(patterns.getContext(), basis, + skipControlledBodies); +} + +} // namespace mlir::qco::decomposition diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index b96b4de99f..ef3970a617 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -16,12 +16,9 @@ #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" -#include -#include #include -#include #include -#include +#include #include #include #include @@ -32,7 +29,6 @@ #include #include -#include #include #include @@ -41,59 +37,10 @@ namespace mlir::qco { #define GEN_PASS_DEF_FUSETWOQUBITUNITARYRUNS #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -namespace { - -using decomposition::NativeGateKind; +using decomposition::EulerBasis; using decomposition::NativeGateset; - -std::optional nativeGateKindFor(UnitaryOpInterface op) { - return llvm::TypeSwitch>( - op.getOperation()) - .Case([](UOp) { return NativeGateKind::U; }) - .Case([](XOp) { return NativeGateKind::X; }) - .Case([](SXOp) { return NativeGateKind::SX; }) - .Case([](auto) { return NativeGateKind::RZ; }) - .Case([](RXOp) { return NativeGateKind::RX; }) - .Case([](RYOp) { return NativeGateKind::RY; }) - .Case([](ROp) { return NativeGateKind::R; }) - .Default([](Operation*) { return std::nullopt; }); -} - -std::optional nativeEntanglerKindFor(CtrlOp ctrl) { - if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1 || - ctrl.getNumBodyUnitaries() != 1) { - return std::nullopt; - } - return llvm::TypeSwitch>( - ctrl.getBodyUnitary(0).getOperation()) - .Case([](XOp) { return NativeGateKind::CX; }) - .Case([](ZOp) { return NativeGateKind::CZ; }) - .Default([](Operation*) { return std::nullopt; }); -} - -/// Returns true when @p op is already on the resolved native gateset. -/// -/// Barriers and global phase are always allowed. Single-qubit primitives and -/// single-target `CtrlOp` shells (`X`/`Z` bodies) are checked against -/// @p spec.gates. -bool allowsOp(Operation* op, const NativeGateset& spec) { - return llvm::TypeSwitch(op) - .Case([](auto) { return true; }) - .Case([&](CtrlOp ctrl) { - const auto kind = nativeEntanglerKindFor(ctrl); - return kind && spec.gates.contains(*kind); - }) - .Case([&](UnitaryOpInterface unitary) { - if (!unitary.isSingleQubit()) { - return false; - } - const auto gate = nativeGateKindFor(unitary); - return gate && spec.gates.contains(*gate); - }) - .Default([](Operation*) { return false; }); -} - -} // namespace +using decomposition::populateFuseSingleQubitUnitaryRunsPatterns; +using decomposition::synthesizeUnitary2QWeyl; /// Skips unitaries nested under `ctrl`/`inv` bodies (handled on the shell op). static bool isExcludedFromTopLevelUnitaryWalk(Operation* op) { @@ -108,26 +55,9 @@ static bool isWalkableUnitaryShell(Operation* op) { !isExcludedFromTopLevelUnitaryWalk(op); } -static void collectUnitaryOpsInPreOrder(Operation* root, - SmallVectorImpl& ops) { - root->walk([&](Operation* op) { - if (isExcludedFromTopLevelUnitaryWalk(op)) { - return; - } - if (llvm::isa(op)) { - ops.push_back(op); - } - }); -} - -static Value emitSingleQubitMatrix(IRRewriter& rewriter, Location loc, - Value inQubit, const Matrix2x2& matrix, - const decomposition::EulerBasis basis) { - return *decomposition::synthesizeUnitary1QEuler( - rewriter, loc, inQubit, matrix, /*runSize=*/0, - /*hasNonBasisGate=*/true, basis); -} - +/// Builds the constant 4x4 matrix for a two-qubit op (bare or single-target +/// `CtrlOp`). Returns false for barriers, global phase, non-two-qubit ops, and +/// ops whose matrix is not known at compile time. static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { if (llvm::isa(op)) { return false; @@ -164,14 +94,6 @@ static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { return assignTwoQubitOpMatrix(unitary.getOperation(), matrix); } -static UnitaryOpInterface fusibleSingleQubitOp(Operation* op) { - if (llvm::isa(op)) { - return {}; - } - auto unitary = llvm::dyn_cast(op); - return isOneQubitWindowMember(unitary) ? unitary : UnitaryOpInterface{}; -} - static UnitaryOpInterface uniqueUnitaryUser(Value wire) { if (!wire.hasOneUse()) { return {}; @@ -218,10 +140,6 @@ static bool feedsFromSameTwoQubitWindow(UnitaryOpInterface op) { return gate0 != nullptr && gate0 == gate1; } -static bool isTwoQubitRunStart(UnitaryOpInterface op) { - return isTwoQubitRunMember(op) && !feedsFromSameTwoQubitWindow(op); -} - namespace { struct FusableTwoQubitRun { @@ -233,229 +151,114 @@ struct FusableTwoQubitRun { Value tailB; }; -struct OneQubitRun { - SmallVector ops; -}; - } // namespace -static void markNonNativeIfNeeded(FusableTwoQubitRun& run, Operation* op, - const decomposition::NativeGateset& spec) { - if (!allowsOp(op, spec)) { - run.anyNonNative = true; - } -} - -// Replace when off-menu ops must be lowered, or when resynthesis uses fewer -// entanglers than the fused window. -static bool shouldApplyTwoQubitRunReplacement(const FusableTwoQubitRun& run, - std::uint8_t numBasisUses) { - if (run.anyNonNative) { - return true; - } - return numBasisUses < run.numTwoQ; -} - static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, UnitaryOpInterface op, - const decomposition::NativeGateset& spec) { + const NativeGateset& spec) { Matrix4x4 opMatrix; if (!assignTwoQubitOpMatrix(op.getOperation(), opMatrix)) { return; } const Value in0 = op.getInputQubit(0); const Value in1 = op.getInputQubit(1); - SmallVector ids; + std::size_t id0 = 0; + std::size_t id1 = 1; if (in0 == run.tailA && in1 == run.tailB) { - ids = {0, 1}; run.tailA = op.getOutputQubit(0); run.tailB = op.getOutputQubit(1); } else if (in0 == run.tailB && in1 == run.tailA) { - ids = {1, 0}; + id0 = 1; + id1 = 0; run.tailA = op.getOutputQubit(1); run.tailB = op.getOutputQubit(0); } else { return; } - run.composed = opMatrix.reorderForQubits(ids[0], ids[1]) * run.composed; + run.composed.premultiplyBy(opMatrix.reorderForQubits(id0, id1)); run.ops.push_back(op.getOperation()); ++run.numTwoQ; - markNonNativeIfNeeded(run, op.getOperation(), spec); + run.anyNonNative |= !spec.allowsOp(op.getOperation()); } static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, UnitaryOpInterface op, - const decomposition::NativeGateset& spec, + const NativeGateset& spec, unsigned wireIndex) { Matrix2x2 raw; if (!op.getUnitaryMatrix2x2(raw)) { return; } - const auto pad = raw.embedInTwoQubit(wireIndex); - run.composed = pad * run.composed; + run.composed.premultiplyBy(raw.embedInTwoQubit(wireIndex)); run.ops.push_back(op.getOperation()); - markNonNativeIfNeeded(run, op.getOperation(), spec); - if (wireIndex == 0) { - run.tailA = op.getOutputQubit(0); - } else { - run.tailB = op.getOutputQubit(0); - } + run.anyNonNative |= !spec.allowsOp(op.getOperation()); + (wireIndex == 0 ? run.tailA : run.tailB) = op.getOutputQubit(0); } -static FusableTwoQubitRun -scanFusableTwoQubitRun(UnitaryOpInterface head, - const decomposition::NativeGateset& spec) { +static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, + const NativeGateset& spec) { FusableTwoQubitRun run; + if (!assignTwoQubitOpMatrix(head.getOperation(), run.composed)) { + return run; + } run.tailA = head.getOutputQubit(0); run.tailB = head.getOutputQubit(1); run.ops.push_back(head.getOperation()); run.numTwoQ = 1; - if (!assignTwoQubitOpMatrix(head.getOperation(), run.composed)) { - run.composed = Matrix4x4::identity(); - run.numTwoQ = 0; - run.ops.clear(); - return run; - } - markNonNativeIfNeeded(run, head.getOperation(), spec); + run.anyNonNative |= !spec.allowsOp(head.getOperation()); while (true) { UnitaryOpInterface nextOnA = uniqueUnitaryUser(run.tailA); UnitaryOpInterface nextOnB = uniqueUnitaryUser(run.tailB); + const bool sameOp = + nextOnA && nextOnB && nextOnA.getOperation() == nextOnB.getOperation(); - if (nextOnA && nextOnB && - nextOnA.getOperation() == nextOnB.getOperation() && - nextOnA.isTwoQubit()) { + if (sameOp && nextOnA.isTwoQubit()) { absorbTwoQubitIntoRun(run, nextOnA, spec); continue; } - if (nextOnA && nextOnB && - nextOnA.getOperation() != nextOnB.getOperation() && - nextOnA.isSingleQubit() && nextOnB.isSingleQubit()) { - if (nextOnA->isBeforeInBlock(nextOnB)) { - absorbOneQubitIntoRun(run, nextOnA, spec, /*wireIndex=*/0); - continue; - } - absorbOneQubitIntoRun(run, nextOnB, spec, /*wireIndex=*/1); - continue; - } - - if (nextOnA && nextOnA.isSingleQubit() && - (!nextOnB || nextOnA.getOperation() != nextOnB.getOperation())) { + const bool aSingle = nextOnA && nextOnA.isSingleQubit() && !sameOp; + const bool bSingle = nextOnB && nextOnB.isSingleQubit() && !sameOp; + if (aSingle && (!bSingle || nextOnA->isBeforeInBlock(nextOnB))) { absorbOneQubitIntoRun(run, nextOnA, spec, /*wireIndex=*/0); continue; } - - if (nextOnB && nextOnB.isSingleQubit() && - (!nextOnA || nextOnB.getOperation() != nextOnA.getOperation())) { + if (bSingle) { absorbOneQubitIntoRun(run, nextOnB, spec, /*wireIndex=*/1); continue; } - break; } return run; } -static void eraseFusableTwoQubitRun(PatternRewriter& rewriter, - const FusableTwoQubitRun& run) { - for (Operation* op : llvm::reverse(run.ops)) { - rewriter.eraseOp(op); - } -} - -static bool maybeFuseRun(IRRewriter& rewriter, OneQubitRun& run, - const decomposition::EulerBasis basis, - const decomposition::NativeGateset& spec) { - Matrix2x2 fused = Matrix2x2::identity(); - for (UnitaryOpInterface u : run.ops) { - Matrix2x2 m; - if (!u.getUnitaryMatrix2x2(m)) { - return false; +/// Whether any two-qubit or single-qubit unitary op (including `ctrl` shells) +/// remains off the native gateset. Used as the pass' final convergence check. +static bool hasNonNativeOps(Operation* root, const NativeGateset& spec) { + const WalkResult walkResult = root->walk([&](Operation* op) { + if (!isWalkableUnitaryShell(op) || !llvm::isa(op)) { + return WalkResult::advance(); } - fused.premultiplyBy(m); - } - - const bool anyNonNative = llvm::any_of(run.ops, [&](UnitaryOpInterface u) { - return !allowsOp(u.getOperation(), spec); - }); - - Operation* firstOp = run.ops.front().getOperation(); - const Value inQubit = run.ops.front().getInputQubit(0); - const Value outQubit = run.ops.back().getOutputQubit(0); - - rewriter.setInsertionPoint(firstOp); - const auto replacement = decomposition::synthesizeUnitary1QEuler( - rewriter, firstOp->getLoc(), inQubit, fused, run.ops.size(), anyNonNative, - basis); - if (!replacement) { - return false; - } - rewriter.replaceAllUsesWith(outQubit, *replacement); - for (auto& op : llvm::reverse(run.ops)) { - rewriter.eraseOp(op.getOperation()); - } - return true; -} - -static bool hasNonNativeOps(Operation* root, - const decomposition::NativeGateset& spec, - bool singleQubitOnly) { - const mlir::WalkResult walkResult = root->walk([&](Operation* op) { - if (!isWalkableUnitaryShell(op)) { - return mlir::WalkResult::advance(); - } - if (singleQubitOnly) { - auto unitary = llvm::dyn_cast(op); - if (!unitary || !unitary.isSingleQubit()) { - return mlir::WalkResult::advance(); - } - } else if (!llvm::isa(op) && !llvm::isa(op)) { - return mlir::WalkResult::advance(); - } - if (!allowsOp(op, spec)) { - return mlir::WalkResult::interrupt(); - } - return mlir::WalkResult::advance(); + return spec.allowsOp(op) ? WalkResult::advance() : WalkResult::interrupt(); }); return walkResult.wasInterrupted(); } -static LogicalResult synthesizeTwoQubitOp( - IRRewriter& rewriter, Operation* op, Location loc, Value in0, Value in1, - const decomposition::NativeGateset& spec, llvm::Twine matrixErrorMsg, - llvm::Twine synthesisErrorMsg) { - if (allowsOp(op, spec)) { - return success(); - } - Matrix4x4 matrix; - if (!assignTwoQubitOpMatrix(op, matrix)) { - op->emitError(matrixErrorMsg); - return failure(); - } - rewriter.setInsertionPoint(op); - Value out0; - Value out1; - if (failed(decomposition::synthesizeUnitary2QWeyl( - rewriter, loc, in0, in1, matrix, spec, out0, out1))) { - op->emitError(synthesisErrorMsg); - return failure(); - } - rewriter.replaceOp(op, ValueRange{out0, out1}); - return success(); -} - namespace { -struct FuseTwoQubitWindowPattern - : public OpInterfaceRewritePattern { - FuseTwoQubitWindowPattern(MLIRContext* ctx, - decomposition::NativeGateset specIn) +/// Fuses a maximal window of two-qubit gates (plus interleaved single-qubit +/// gates) into a single composed unitary and resynthesizes it to the native +/// gateset when that is beneficial. +struct FuseTwoQubitWindowPattern final + : OpInterfaceRewritePattern { + FuseTwoQubitWindowPattern(MLIRContext* ctx, NativeGateset specIn) : OpInterfaceRewritePattern(ctx), spec(std::move(specIn)) {} LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { - if (!isTwoQubitRunStart(op)) { + // Only anchor on a run start: a run member not fed by an earlier window. + if (!isTwoQubitRunMember(op) || feedsFromSameTwoQubitWindow(op)) { return failure(); } @@ -464,241 +267,151 @@ struct FuseTwoQubitWindowPattern return failure(); } + // Replace when off-gateset ops must be lowered, or when resynthesis uses + // fewer entanglers than the fused window. const auto native = spec.decomposeTarget(run.composed); - if (!native || - !shouldApplyTwoQubitRunReplacement(run, native->numBasisUses)) { + if (!native || (!run.anyNonNative && native->numBasisUses >= run.numTwoQ)) { return failure(); } - Operation* firstOp = run.ops.front(); - auto firstUnitary = llvm::cast(firstOp); - const Value inA = firstUnitary.getInputQubit(0); - const Value inB = firstUnitary.getInputQubit(1); - + auto firstOp = llvm::cast(run.ops.front()); rewriter.setInsertionPoint(firstOp); Value newA; Value newB; - if (failed(decomposition::synthesizeUnitary2QWeyl( - rewriter, firstOp->getLoc(), inA, inB, run.composed, spec, newA, - newB))) { + if (failed(synthesizeUnitary2QWeyl( + rewriter, firstOp.getLoc(), firstOp.getInputQubit(0), + firstOp.getInputQubit(1), run.composed, spec, newA, newB))) { firstOp->emitError("failed to emit synthesized two-qubit gate sequence"); return failure(); } rewriter.replaceAllUsesWith(run.tailA, newA); rewriter.replaceAllUsesWith(run.tailB, newB); - eraseFusableTwoQubitRun(rewriter, run); + for (Operation* member : llvm::reverse(run.ops)) { + rewriter.eraseOp(member); + } + return success(); + } + + NativeGateset spec; +}; + +/// Lowers a single off-gateset two-qubit op (bare or single-target `CtrlOp`) +/// to the native entangler plus native single-qubit factors via Weyl synthesis. +/// Native two-qubit ops and windows fusable by @ref FuseTwoQubitWindowPattern +/// are left to that pattern. +struct LowerTwoQubitOpPattern final + : OpInterfaceRewritePattern { + LowerTwoQubitOpPattern(MLIRContext* ctx, NativeGateset specIn) + : OpInterfaceRewritePattern(ctx), spec(std::move(specIn)) {} + + LogicalResult matchAndRewrite(UnitaryOpInterface op, + PatternRewriter& rewriter) const override { + Operation* raw = op.getOperation(); + if (!isWalkableUnitaryShell(raw) || spec.allowsOp(raw)) { + return failure(); + } + Matrix4x4 matrix; + if (!assignTwoQubitOpMatrix(raw, matrix)) { + return failure(); + } + + Value in0; + Value in1; + if (auto ctrl = llvm::dyn_cast(raw)) { + in0 = ctrl.getInputControl(0); + in1 = ctrl.getInputTarget(0); + } else { + in0 = op.getInputQubit(0); + in1 = op.getInputQubit(1); + } + + rewriter.setInsertionPoint(raw); + Value out0; + Value out1; + if (failed(synthesizeUnitary2QWeyl(rewriter, raw->getLoc(), in0, in1, + matrix, spec, out0, out1))) { + return failure(); + } + rewriter.replaceOp(raw, ValueRange{out0, out1}); return success(); } - decomposition::NativeGateset spec; + NativeGateset spec; }; } // namespace -static LogicalResult -fuseTwoQubitUnitaryRuns(Operation* root, - const decomposition::NativeGateset& spec) { - RewritePatternSet patterns(root->getContext()); - patterns.add(patterns.getContext(), spec); - return applyPatternsGreedily(root, std::move(patterns)); +/// Fuses single-qubit runs (and lowers lone off-gateset single-qubit ops) by +/// reusing the `fuse-single-qubit-unitary-runs` rewrite. +/// +/// `qco.ctrl` bodies are skipped so the `X`/`Z` bodies of native entanglers are +/// preserved (both before and after two-qubit synthesis). +static LogicalResult fuseSingleQubitRuns(ModuleOp module, + const EulerBasis basis) { + RewritePatternSet patterns(module.getContext()); + populateFuseSingleQubitUnitaryRunsPatterns(patterns, basis, + /*skipControlledBodies=*/true); + return applyPatternsGreedily(module, std::move(patterns)); +} + +/// Fuses two-qubit windows, then lowers any remaining off-gateset two-qubit +/// ops. +static LogicalResult fuseAndLowerTwoQubitOps(ModuleOp module, + const NativeGateset& spec) { + MLIRContext* ctx = module.getContext(); + { + RewritePatternSet windowPatterns(ctx); + windowPatterns.add(ctx, spec); + if (failed(applyPatternsGreedily(module, std::move(windowPatterns)))) { + return failure(); + } + } + RewritePatternSet lowerPatterns(ctx); + lowerPatterns.add(ctx, spec); + return applyPatternsGreedily(module, std::move(lowerPatterns)); } namespace { -struct FuseTwoQubitUnitaryRunsPass +struct FuseTwoQubitUnitaryRunsPass final : impl::FuseTwoQubitUnitaryRunsBase { - FuseTwoQubitUnitaryRunsPass() = default; + using Base::Base; explicit FuseTwoQubitUnitaryRunsPass(FuseTwoQubitUnitaryRunsOptions options) - : FuseTwoQubitUnitaryRunsBase(std::move(options)) {} + : Base(std::move(options)) {} protected: void runOnOperation() override { if (llvm::StringRef(nativeGates).trim().empty()) { return; } - auto specOpt = decomposition::NativeGateset::parse(nativeGates); - if (!specOpt) { + const auto spec = NativeGateset::parse(nativeGates); + if (!spec) { getOperation().emitError() << "unsupported native gateset (native-gates='" << nativeGates << "')"; signalPassFailure(); return; } - const auto& spec = *specOpt; - const decomposition::EulerBasis oneQubitBasis = *spec.eulerBasis; - - IRRewriter rewriter(&getContext()); - - fuseOneQubitRuns(rewriter, spec, oneQubitBasis); - if (failed(fuseTwoQubitUnitaryRuns(getOperation(), spec))) { - signalPassFailure(); - return; - } - constexpr unsigned kMaxSynthesisSweeps = 4; - for (unsigned i = 0; i < kMaxSynthesisSweeps; ++i) { - if (failed(synthesizeRemainingOps(rewriter, spec, oneQubitBasis))) { - signalPassFailure(); - return; - } - if (!hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/true)) { - break; - } - } - if (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/true)) { - getOperation().emitError() - << "native gate synthesis did not converge within " - << kMaxSynthesisSweeps - << " sweeps (single-qubit ops remain outside the native menu)"; - signalPassFailure(); - return; - } - fuseOneQubitRuns(rewriter, spec, oneQubitBasis); - constexpr unsigned kPostMenuCleanupSweeps = 4; - unsigned postMenuSweepsRemaining = kPostMenuCleanupSweeps; - while (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/false) && - postMenuSweepsRemaining-- > 0) { - if (failed(synthesizeRemainingOps(rewriter, spec, oneQubitBasis))) { - signalPassFailure(); - return; - } - fuseOneQubitRuns(rewriter, spec, oneQubitBasis); - } - if (hasNonNativeOps(getOperation(), spec, /*singleQubitOnly=*/false)) { - getOperation().emitError() - << "native gate synthesis: operations remain outside the native menu " - "after final cleanup"; + const EulerBasis basis = *spec->eulerBasis; + ModuleOp module = getOperation(); + + // 1. Fuse single-qubit runs (also lowers lone off-gateset single-qubit + // ops). + // 2. Fuse two-qubit windows and lower remaining off-gateset two-qubit ops. + // 3. Fuse the single-qubit seams introduced by two-qubit synthesis. + if (failed(fuseSingleQubitRuns(module, basis)) || + failed(fuseAndLowerTwoQubitOps(module, *spec)) || + failed(fuseSingleQubitRuns(module, basis))) { signalPassFailure(); return; } - } - -private: - void fuseOneQubitRuns(IRRewriter& rewriter, - const decomposition::NativeGateset& spec, - const decomposition::EulerBasis basis) { - SmallVector runs; - llvm::DenseMap tailOpToRun; - - // Require single-use tail output so fan-out wires are not fused away. - getOperation()->walk([&](Operation* op) { - auto unitary = fusibleSingleQubitOp(op); - if (!unitary) { - return; - } - Value inQubit = unitary.getInputQubit(0); - Operation* defOp = inQubit.getDefiningOp(); - auto it = - (defOp != nullptr) ? tailOpToRun.find(defOp) : tailOpToRun.end(); - const bool canExtend = it != tailOpToRun.end() && inQubit.hasOneUse(); - if (canExtend) { - const size_t runIdx = it->second; - runs[runIdx].ops.push_back(unitary); - tailOpToRun.erase(it); - tailOpToRun[op] = runIdx; - } else { - runs.push_back(OneQubitRun{}); - runs.back().ops.push_back(unitary); - tailOpToRun[op] = runs.size() - 1; - } - }); - - for (auto& run : runs) { - if (run.ops.size() < 2) { - continue; - } - (void)maybeFuseRun(rewriter, run, basis, spec); - } - } - - LogicalResult synthesizeRemainingOps(IRRewriter& rewriter, - const decomposition::NativeGateset& spec, - const decomposition::EulerBasis basis) { - SmallVector ops; - collectUnitaryOpsInPreOrder(getOperation(), ops); - llvm::DenseSet erasedOps; - - for (Operation* op : ops) { - if (erasedOps.contains(op)) { - continue; - } - if (!isWalkableUnitaryShell(op)) { - continue; - } - - if (auto ctrl = llvm::dyn_cast(op)) { - const bool wasAlreadyNative = allowsOp(op, spec); - if (failed(synthesizeControlled(rewriter, ctrl, spec))) { - return failure(); - } - if (!wasAlreadyNative) { - erasedOps.insert(op); - } - continue; - } - - auto unitary = llvm::dyn_cast(op); - if (!unitary) { - continue; - } - - if (unitary.isSingleQubit()) { - if (!allowsOp(op, spec)) { - if (failed(rewriteSingleQubit(rewriter, op, unitary, basis))) { - return failure(); - } - erasedOps.insert(op); - } - continue; - } - - if (unitary.isTwoQubit()) { - if (failed(synthesizeBareTwoQubit(rewriter, op, unitary, spec))) { - return failure(); - } - erasedOps.insert(op); - } - } - return success(); - } - static LogicalResult - rewriteSingleQubit(IRRewriter& rewriter, Operation* op, - UnitaryOpInterface unitary, - const decomposition::EulerBasis basis) { - rewriter.setInsertionPoint(op); - const Value in = unitary.getInputQubit(0); - Matrix2x2 matrix; - if (!unitary.getUnitaryMatrix2x2(matrix)) { - op->emitError("single-qubit operation with non-constant parameters is " - "not supported for native synthesis"); - return failure(); + if (hasNonNativeOps(module, *spec)) { + module.emitError() << "native gate synthesis: operations remain outside " + "the native gateset (native-gates='" + << nativeGates << "')"; + signalPassFailure(); } - const Value replaced = - emitSingleQubitMatrix(rewriter, op->getLoc(), in, matrix, basis); - rewriter.replaceOp(op, replaced); - return success(); - } - - static LogicalResult - synthesizeControlled(IRRewriter& rewriter, CtrlOp ctrl, - const decomposition::NativeGateset& spec) { - return synthesizeTwoQubitOp( - rewriter, ctrl.getOperation(), ctrl.getLoc(), ctrl.getInputControl(0), - ctrl.getInputTarget(0), spec, - "native synthesis: cannot build a constant 4x4 matrix for this " - "controlled gate (unsupported body or non-constant parameters)", - "controlled gate not allowed by selected gateset"); - } - - static LogicalResult - synthesizeBareTwoQubit(IRRewriter& rewriter, Operation* op, - UnitaryOpInterface unitary, - const decomposition::NativeGateset& spec) { - return synthesizeTwoQubitOp( - rewriter, op, op->getLoc(), unitary.getInputQubit(0), - unitary.getInputQubit(1), spec, - "unsupported two-qubit operation for selected gateset", - "unsupported two-qubit operation for selected gateset"); } }; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index c10ea5df1f..8568ce2c30 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -769,48 +769,6 @@ TEST(NativeSpecTest, ResolvesEulerBasisFromGateset) { EXPECT_EQ(*zyz->eulerBasis, EulerBasis::ZYZ); } -static std::optional gateKindFor(UnitaryOpInterface op) { - return llvm::TypeSwitch>( - op.getOperation()) - .Case([](UOp) { return NativeGateKind::U; }) - .Case([](XOp) { return NativeGateKind::X; }) - .Case([](SXOp) { return NativeGateKind::SX; }) - .Case([](RZOp) { return NativeGateKind::RZ; }) - .Case([](RXOp) { return NativeGateKind::RX; }) - .Case([](RYOp) { return NativeGateKind::RY; }) - .Case([](ROp) { return NativeGateKind::R; }) - .Default([](Operation*) { return std::nullopt; }); -} - -static std::optional entanglerKindFor(CtrlOp ctrl) { - if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1 || - ctrl.getNumBodyUnitaries() != 1) { - return std::nullopt; - } - return llvm::TypeSwitch>( - ctrl.getBodyUnitary(0).getOperation()) - .Case([](XOp) { return NativeGateKind::CX; }) - .Case([](ZOp) { return NativeGateKind::CZ; }) - .Default([](Operation*) { return std::nullopt; }); -} - -static bool allowsOp(Operation* op, const NativeGateset& spec) { - return llvm::TypeSwitch(op) - .Case([](auto) { return true; }) - .Case([&](CtrlOp ctrl) { - const auto kind = entanglerKindFor(ctrl); - return kind && spec.gates.contains(*kind); - }) - .Case([&](UnitaryOpInterface unitary) { - if (!unitary.isSingleQubit()) { - return false; - } - const auto gate = gateKindFor(unitary); - return gate && spec.gates.contains(*gate); - }) - .Default([](Operation*) { return false; }); -} - TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { const auto spec = NativeGateset::parse("u,cx"); ASSERT_TRUE(spec); @@ -826,20 +784,19 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { Value q0 = entry->getArgument(0); Value q1 = entry->getArgument(1); - EXPECT_TRUE(allowsOp( - BarrierOp::create(builder, loc, ValueRange{q0, q1}).getOperation(), - *spec)); + EXPECT_TRUE(spec->allowsOp( + BarrierOp::create(builder, loc, ValueRange{q0, q1}).getOperation())); EXPECT_TRUE( - allowsOp(GPhaseOp::create(builder, loc, 0.1).getOperation(), *spec)); - EXPECT_TRUE(allowsOp( - UOp::create(builder, loc, q0, 0.1, 0.2, 0.3).getOperation(), *spec)); + spec->allowsOp(GPhaseOp::create(builder, loc, 0.1).getOperation())); + EXPECT_TRUE(spec->allowsOp( + UOp::create(builder, loc, q0, 0.1, 0.2, 0.3).getOperation())); auto cx = CtrlOp::create( builder, loc, ValueRange{q0}, ValueRange{q1}, [&builder, &loc](ValueRange targets) -> SmallVector { return {XOp::create(builder, loc, targets[0]).getOutputQubit(0)}; }); - EXPECT_TRUE(allowsOp(cx.getOperation(), *spec)); + EXPECT_TRUE(spec->allowsOp(cx.getOperation())); auto cxWithInterleavedH = CtrlOp::create( builder, loc, ValueRange{q0}, ValueRange{q1}, @@ -847,25 +804,25 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { auto wire = XOp::create(builder, loc, targets[0]).getOutputQubit(0); return {HOp::create(builder, loc, wire).getOutputQubit(0)}; }); - EXPECT_FALSE(allowsOp(cxWithInterleavedH.getOperation(), *spec)); + EXPECT_FALSE(spec->allowsOp(cxWithInterleavedH.getOperation())); - EXPECT_FALSE(allowsOp(XOp::create(builder, loc, q0).getOperation(), *spec)); + EXPECT_FALSE(spec->allowsOp(XOp::create(builder, loc, q0).getOperation())); EXPECT_FALSE( - allowsOp(RXXOp::create(builder, loc, q0, q1, 0.2).getOperation(), *spec)); + spec->allowsOp(RXXOp::create(builder, loc, q0, q1, 0.2).getOperation())); const auto rzSpec = NativeGateset::parse("x,sx,rz,cx"); ASSERT_TRUE(rzSpec); EXPECT_TRUE( - allowsOp(RZOp::create(builder, loc, q0, 0.3).getOperation(), *rzSpec)); + rzSpec->allowsOp(RZOp::create(builder, loc, q0, 0.3).getOperation())); EXPECT_FALSE( - allowsOp(POp::create(builder, loc, q0, 0.3).getOperation(), *rzSpec)); + rzSpec->allowsOp(POp::create(builder, loc, q0, 0.3).getOperation())); auto hCtrl = CtrlOp::create( builder, loc, ValueRange{q0}, ValueRange{q1}, [&builder, &loc](ValueRange targets) -> SmallVector { return {HOp::create(builder, loc, targets[0]).getOutputQubit(0)}; }); - EXPECT_FALSE(allowsOp(hCtrl.getOperation(), *spec)); + EXPECT_FALSE(spec->allowsOp(hCtrl.getOperation())); const auto funcTy3 = builder.getFunctionType({qubitTy, qubitTy, qubitTy}, {qubitTy, qubitTy, qubitTy}); @@ -880,7 +837,7 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { [&builder, &loc](ValueRange targets) -> SmallVector { return {XOp::create(builder, loc, targets[0]).getOutputQubit(0)}; }); - EXPECT_FALSE(allowsOp(ccx.getOperation(), *spec)); + EXPECT_FALSE(spec->allowsOp(ccx.getOperation())); const auto czSpec = NativeGateset::parse("u,cz"); ASSERT_TRUE(czSpec); @@ -889,6 +846,6 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { [&builder, &loc](ValueRange targets) -> SmallVector { return {ZOp::create(builder, loc, targets[0]).getOutputQubit(0)}; }); - EXPECT_TRUE(allowsOp(cz.getOperation(), *czSpec)); - EXPECT_FALSE(allowsOp(cx.getOperation(), *czSpec)); + EXPECT_TRUE(czSpec->allowsOp(cz.getOperation())); + EXPECT_FALSE(czSpec->allowsOp(cx.getOperation())); } From 3a1b758c61375e03e3c6818be36f2442dae7fdda Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 12:51:18 +0200 Subject: [PATCH 06/27] =?UTF-8?q?=F0=9F=8E=A8=20Refactor=20unit=20tests=20?= =?UTF-8?q?for=20two-qubit=20unitary=20fusion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_fuse_two_qubit_unitary_runs.cpp | 696 ++++++++---------- 1 file changed, 289 insertions(+), 407 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 68571420bd..7fc3607158 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -8,11 +8,11 @@ * Licensed under the MIT License */ -#include "TestCaseUtils.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" #include "qc_programs.h" @@ -48,47 +48,27 @@ using namespace mlir; using namespace mlir::qco; -using namespace mqt::test; using ProgramFn = void (*)(mlir::qc::QCProgramBuilder&); -using NativePredicate = bool (*)(OwningOpRef&); -static void controlledXH(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.ctrl(q0, {q1}, [&](ValueRange targets) { - b.x(targets[0]); - b.h(targets[0]); - }); -} +// --- Native-gateset membership check ------------------------------------- // -template -static bool onlyTheseOps(OwningOpRef& moduleOp, bool allowCx, - bool allowCz) { +/// Returns true when every operation in @p moduleOp is native to the gateset +/// parsed from @p nativeGates. Operations nested inside a controlled shell are +/// validated through the shell itself. +static bool allOpsNative(OwningOpRef& moduleOp, + StringRef nativeGates) { + const auto spec = decomposition::NativeGateset::parse(nativeGates); + if (!spec) { + return false; + } bool ok = true; std::ignore = moduleOp->walk([&](UnitaryOpInterface op) { Operation* raw = op.getOperation(); - if (llvm::isa_and_present(raw->getParentOp())) { + if (isa_and_present(raw->getParentOp())) { return WalkResult::advance(); } - if (llvm::isa(raw)) { - return WalkResult::advance(); - } - if (auto ctrl = llvm::dyn_cast(raw)) { - if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { - ok = false; - return WalkResult::interrupt(); - } - Operation* body = ctrl.getBodyUnitary(0).getOperation(); - const bool isCx = llvm::isa(body); - const bool isCz = llvm::isa(body); - if ((isCx && allowCx) || (isCz && allowCz)) { - return WalkResult::advance(); - } - ok = false; - return WalkResult::interrupt(); - } - if (!llvm::isa(raw)) { + if (!spec->allowsOp(raw)) { ok = false; return WalkResult::interrupt(); } @@ -97,53 +77,24 @@ static bool onlyTheseOps(OwningOpRef& moduleOp, bool allowCx, return ok; } -static bool onlyIbmBasicCxOps(OwningOpRef& m) { - return onlyTheseOps(m, true, false); -} -static bool onlyIbmBasicCzOps(OwningOpRef& m) { - return onlyTheseOps(m, false, true); -} -static bool onlyGenericU3CxOps(OwningOpRef& m) { - return onlyTheseOps(m, true, false); -} -static bool onlyIqmDefaultOps(OwningOpRef& m) { - return onlyTheseOps(m, false, true); -} -static bool onlyAxisPairRxRzCxOps(OwningOpRef& m) { - return onlyTheseOps(m, true, false); -} -static bool onlyAxisPairRxRyCxOps(OwningOpRef& m) { - return onlyTheseOps(m, true, false); -} -static bool onlyAxisPairRyRzCzOps(OwningOpRef& m) { - return onlyTheseOps(m, false, true); -} -static bool onlyGenericU3CxOrCzOps(OwningOpRef& m) { - return onlyTheseOps(m, true, true); -} +// --- Reference unitary reconstruction ------------------------------------ // -static std::optional unitaryQubitOperand(UnitaryOpInterface op, - std::size_t index) { - if (index >= op.getNumQubits()) { - return std::nullopt; - } - Value v = op->getOperand(index); - if (!llvm::isa(v.getType())) { +static std::optional unitaryQubit(Value v, std::size_t index, + std::size_t numQubits) { + if (index >= numQubits || !isa(v.getType())) { return std::nullopt; } return v; } +static std::optional unitaryQubitOperand(UnitaryOpInterface op, + std::size_t index) { + return unitaryQubit(op->getOperand(index), index, op.getNumQubits()); +} + static std::optional unitaryQubitResult(UnitaryOpInterface op, std::size_t index) { - if (index >= op.getNumQubits()) { - return std::nullopt; - } - Value v = op->getResult(index); - if (!llvm::isa(v.getType())) { - return std::nullopt; - } - return v; + return unitaryQubit(op->getResult(index), index, op.getNumQubits()); } static bool extractSingleQubitMatrix(UnitaryOpInterface op, Matrix2x2& out) { @@ -161,11 +112,9 @@ static bool extractSingleQubitMatrix(UnitaryOpInterface op, Matrix2x2& out) { } static bool extractTwoQubitMatrix(UnitaryOpInterface op, Matrix4x4& out) { - if (auto ctrl = llvm::dyn_cast(op.getOperation())) { - if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { - return false; - } - return op.getUnitaryMatrix4x4(out); + if (auto ctrl = dyn_cast(op.getOperation()); + ctrl && (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1)) { + return false; } return op.getUnitaryMatrix4x4(out); } @@ -177,18 +126,18 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { return std::nullopt; } - llvm::DenseMap qubitIds; + DenseMap qubitIds; std::size_t nextQubitId = 0; std::size_t numQubits = 0; for (auto func : module.getOps()) { for (auto& block : func.getBlocks()) { for (auto& rawOp : block.getOperations()) { - if (auto staticOp = llvm::dyn_cast(&rawOp)) { + if (auto staticOp = dyn_cast(&rawOp)) { const auto index = static_cast(staticOp.getIndex()); qubitIds.try_emplace(staticOp.getQubit(), index); numQubits = std::max(numQubits, index + 1); - } else if (auto alloc = llvm::dyn_cast(&rawOp)) { + } else if (auto alloc = dyn_cast(&rawOp)) { qubitIds.try_emplace(alloc.getResult(), nextQubitId++); numQubits = std::max(numQubits, nextQubitId); } @@ -215,14 +164,14 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { for (auto func : module.getOps()) { for (auto& block : func.getBlocks()) { for (auto& rawOp : block.getOperations()) { - auto op = llvm::dyn_cast(&rawOp); + auto op = dyn_cast(&rawOp); if (!op) { continue; } - if (llvm::isa(op.getOperation())) { + if (isa(op.getOperation())) { continue; } - if (auto gphase = llvm::dyn_cast(op.getOperation())) { + if (auto gphase = dyn_cast(op.getOperation())) { if (const auto matrix = gphase.getUnitaryMatrix()) { globalPhase *= (*matrix)(0, 0); } @@ -285,20 +234,210 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { return globalPhase * unitary; } +// --- Expressive circuits -------------------------------------------------- // +// +// A handful of circuits, each covering a distinct pass behavior. They are +// crossed with the gateset table below so that every native basis exercises the +// same structural variety, instead of pairing each circuit with a single menu. + +/// A bare SWAP (three-entangler class), the canonical two-qubit decomposition. +static void swapTwoQ(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.swap(q0, q1); +} + +/// Rich single-qubit variety on both wires, followed by a two-qubit entangler. +static void broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.x(q0); + b.y(q1); + b.h(q0); + b.sx(q1); + b.rx(0.13, q0); + b.ry(-0.47, q1); + b.rz(0.29, q0); + b.cz(q0, q1); +} + +/// Long single-qubit run on one wire, then an entangler (Euler-run fusion). +static void hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.s(q0); + b.t(q0); + b.y(q0); + b.cx(q0, q1); +} + +/// Zero-angle rotations that must canonicalize away before the entangler. +static void zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.rx(0.0, q0); + b.ry(0.0, q1); + b.rz(0.0, q0); + b.p(0.0, q1); + b.cz(q0, q1); +} + +/// Single-qubit gates surrounding an entangler on both sides. +static void hCxSq1(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.s(q1); +} + +/// Three-qubit program with chained entanglers on overlapping pairs. +static void threeQGhz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.cx(q1, q2); +} + +/// Single-qubit gates wrapped in an inverse modifier (no entangler). +static void inverseTwoX(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + b.inv({q0}, [&](mlir::ValueRange qubits) { + b.x(qubits[0]); + b.x(qubits[0]); + }); +} + +/// A controlled two-gate body that must be synthesized as a two-qubit unitary. +static void controlledXH(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.ctrl(q0, {q1}, [&](ValueRange targets) { + b.x(targets[0]); + b.h(targets[0]); + }); +} + +// --- Fusion-window circuits ---------------------------------------------- // +// +// These probe window geometry (where fusion starts/stops), so they run on a +// single fixed gateset rather than the full table. + +static void fusionCxCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q0, q1); +} + +static void fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.h(q0); + b.cx(q0, q1); + b.t(q1); + b.s(q0); + b.cx(q0, q1); +} + +static void fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q1, q2); + b.cx(q0, q1); +} + +static void fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.cx(q0, q1); + b.rz(0.17, q1); + b.cx(q1, q2); +} + +static void fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.barrier({q0, q1}); + b.cx(q0, q1); +} + +static void fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); +} + +static void fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.h(q0); + b.cx(q0, q1); +} + +static void fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.rz(0.11, q0); + b.ry(0.22, q1); + b.cx(q0, q1); +} + +static void determinismSwap(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.swap(q0, q1); + b.dealloc(q0); + b.dealloc(q1); +} + namespace { -struct ProfileCase { +/// A named circuit builder, used as a test parameter. +struct NamedProgram { const char* name; ProgramFn program; - const char* nativeGates; - NativePredicate isNative; - bool checkEquivalence; }; +/// Native gatesets spanning every supported single-qubit basis and both +/// entangler families (plus a multi-entangler menu). Because the pass +/// re-synthesizes each two-qubit window into the target basis, every circuit is +/// valid input for every gateset. +constexpr std::array GATESETS = { + // CX entangler family + "x,sx,rz,cx", // ZSXX + "u,cx", // U + "rx,rz,cx", // XZX + "rx,ry,cx", // XYX + // CZ entangler family + "r,cz", // R + "ry,rz,cz", // ZYZ + "x,sx,rz,cz", // ZSXX + "u,cz", // U + // Multiple entanglers (cx preferred) + "u,cx,cz", +}; + +/// Gateset used for the fusion-window suite, which asserts on structure rather +/// than on native-basis coverage. +constexpr StringRef FUSION_GATESET = "u,cx"; + +/// Structural expectations for a fusion-window circuit under @ref +/// FUSION_GATESET. struct FusionCase { const char* name; ProgramFn program; - const char* nativeGates; std::optional exactCtrlCount; std::optional minCtrlCount; bool checkTwoQUnitary; @@ -349,22 +488,14 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { EXPECT_TRUE(lhsUnitary->isApprox(*rhsUnitary)); } - void expectNativeAfterSynthesis(ProgramFn program, StringRef nativeGates, - NativePredicate isNative) { - auto moduleOp = mlir::qc::QCProgramBuilder::build(context.get(), program); - runFusePipeline(moduleOp, nativeGates); - EXPECT_TRUE(isNative(moduleOp)); - } - void expectEquivalentAndNativeAfterSynthesis(ProgramFn program, - StringRef nativeGates, - NativePredicate isNative) { + StringRef nativeGates) { auto expected = mlir::qc::QCProgramBuilder::build(context.get(), program); runQcToQco(expected); auto synthesized = mlir::qc::QCProgramBuilder::build(context.get(), program); runFusePipeline(synthesized, nativeGates); - EXPECT_TRUE(isNative(synthesized)); + EXPECT_TRUE(allOpsNative(synthesized, nativeGates)); expectQcoModulesEquivalent(expected, synthesized); } @@ -400,9 +531,11 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { std::unique_ptr context; }; -class FuseTwoQubitProfileTest +using SynthesisParam = std::tuple; + +class FuseTwoQubitSynthesisTest : public FuseTwoQubitUnitaryRunsPassTest, - public testing::WithParamInterface {}; + public testing::WithParamInterface {}; class FuseTwoQubitFusionTest : public FuseTwoQubitUnitaryRunsPassTest, public testing::WithParamInterface { @@ -410,236 +543,74 @@ class FuseTwoQubitFusionTest : public FuseTwoQubitUnitaryRunsPassTest, } // namespace -static void fusionCxCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.cx(q0, q1); -} - -static void fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q0); - b.cx(q0, q1); - b.t(q1); - b.s(q0); - b.cx(q0, q1); -} - -static void fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); - b.cx(q0, q1); - b.cx(q1, q2); - b.cx(q0, q1); -} - -static void fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); - b.cx(q0, q1); - b.rz(0.17, q1); - b.cx(q1, q2); -} - -static void fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.barrier({q0, q1}); - b.cx(q0, q1); -} - -static void fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.cx(q1, q0); - b.cx(q0, q1); -} - -static void fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.h(q0); - b.cx(q0, q1); -} - -static void fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.rz(0.11, q0); - b.ry(0.22, q1); - b.cx(q0, q1); -} - -static void inverseTwoX(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - b.inv({q0}, [&](mlir::ValueRange qubits) { - b.x(qubits[0]); - b.x(qubits[0]); - }); -} - -static void broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.x(q0); - b.y(q1); - b.h(q0); - b.sx(q1); - b.rx(0.13, q0); - b.ry(-0.47, q1); - b.rz(0.29, q0); - b.cz(q0, q1); -} - -static void zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.rx(0.0, q0); - b.ry(0.0, q1); - b.rz(0.0, q0); - b.p(0.0, q1); - b.cz(q0, q1); -} - -static void hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q0); - b.s(q0); - b.t(q0); - b.y(q0); - b.cx(q0, q1); -} - -static void cxYOnQ1(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.cx(q0, q1); - b.y(q1); -} - -static void hCxTOnQ1(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q1); - b.cx(q0, q1); - b.t(q1); -} - -static void xYSXCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.x(q0); - b.y(q0); - b.sx(q0); - b.cz(q0, q1); -} - -static void hYCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q0); - b.y(q0); - b.cx(q0, q1); -} - -static void zCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.z(q0); - b.cx(q0, q1); -} - -static void xHCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.x(q0); - b.h(q0); - b.cz(q0, q1); -} - -static void hq0Yq1CxSq0(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q0); - b.y(q1); - b.cx(q0, q1); - b.s(q0); -} +// --- Synthesis: every expressive circuit against every gateset ----------- // -static void hCxSq1(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.h(q0); - b.cx(q0, q1); - b.s(q1); +TEST_P(FuseTwoQubitSynthesisTest, IsNativeAndEquivalent) { + const auto& [circuit, gateset] = GetParam(); + expectEquivalentAndNativeAfterSynthesis(circuit.program, gateset); } -static void threeQGhz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); - b.h(q0); - b.cx(q0, q1); - b.cx(q1, q2); -} +INSTANTIATE_TEST_SUITE_P( + Circuits, FuseTwoQubitSynthesisTest, + testing::Combine( + testing::Values(NamedProgram{"Swap", swapTwoQ}, + NamedProgram{"BroadOneQThenCz", broadOneQThenCz}, + NamedProgram{"HstyThenCx", hstycxTwoQ}, + NamedProgram{"ZeroAngleThenCz", zeroAngleThenCz}, + NamedProgram{"SurroundedCx", hCxSq1}, + NamedProgram{"ThreeQubitGhz", threeQGhz}, + NamedProgram{"InverseBody", inverseTwoX}, + NamedProgram{"ControlledBody", controlledXH}), + testing::ValuesIn(GATESETS)), + [](const testing::TestParamInfo& info) { + std::string gateset = std::get<1>(info.param); + std::replace(gateset.begin(), gateset.end(), ',', '_'); + return std::string(std::get<0>(info.param).name) + "__" + gateset; + }); -static void determinismSwap(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - b.swap(q0, q1); - b.dealloc(q0); - b.dealloc(q1); -} +// --- Fusion windows: structural behavior on a fixed gateset -------------- // -TEST_P(FuseTwoQubitProfileTest, SynthesizesToNativeMenu) { - const ProfileCase& c = GetParam(); - if (c.checkEquivalence) { - expectEquivalentAndNativeAfterSynthesis(c.program, c.nativeGates, - c.isNative); - } else { - expectNativeAfterSynthesis(c.program, c.nativeGates, c.isNative); +TEST_P(FuseTwoQubitFusionTest, WindowFusionBehavior) { + const FusionCase& c = GetParam(); + if (c.checkTwoQUnitary) { + expectTwoQFusePreservesUnitary(c.program, FUSION_GATESET); + } + auto module = mlir::qc::QCProgramBuilder::build(context.get(), c.program); + ASSERT_TRUE(module); + runQcToQco(module); + runTwoQFuse(module, FUSION_GATESET); + if (c.exactCtrlCount) { + EXPECT_EQ(countCtrlOps(module), *c.exactCtrlCount); + } + if (c.minCtrlCount) { + EXPECT_GE(countCtrlOps(module), *c.minCtrlCount); } } INSTANTIATE_TEST_SUITE_P( - Menus, FuseTwoQubitProfileTest, + Windows, FuseTwoQubitFusionTest, testing::Values( - ProfileCase{"SwapIbmBasic", mlir::qc::swap, "x,sx,rz,cx", - onlyIbmBasicCxOps, false}, - ProfileCase{"SwapGeneric", mlir::qc::swap, "u,cx", onlyGenericU3CxOps, - false}, - ProfileCase{"SwapIqm", mlir::qc::swap, "r,cz", onlyIqmDefaultOps, - false}, - ProfileCase{"HstycxIbm", hstycxTwoQ, "x,sx,rz,cx", onlyIbmBasicCxOps, - false}, - ProfileCase{"CxYIqm", cxYOnQ1, "r,cz", onlyIqmDefaultOps, false}, - ProfileCase{"BroadOneQIqm", broadOneQThenCz, "r,cz", onlyIqmDefaultOps, - false}, - ProfileCase{"ZeroAngleRyRzCz", zeroAngleThenCz, "ry,rz,cz", - onlyAxisPairRyRzCzOps, false}, - ProfileCase{"HCxTIbmCz", hCxTOnQ1, "x,sx,rz,cz", onlyIbmBasicCzOps, - false}, - ProfileCase{"XYSXCzIqm", xYSXCz, "r,cz", onlyIqmDefaultOps, false}, - ProfileCase{"HYCxRxRz", hYCx, "rx,rz,cx", onlyAxisPairRxRzCxOps, false}, - ProfileCase{"ZCxRxRy", zCx, "rx,ry,cx", onlyAxisPairRxRyCxOps, false}, - ProfileCase{"Hq0Yq1CxSq0", hq0Yq1CxSq0, "u,cx", onlyGenericU3CxOps, - true}, - ProfileCase{"XHCzRyRz", xHCz, "ry,rz,cz", onlyAxisPairRyRzCzOps, true}, - ProfileCase{"HCxSq1MultiEnt", hCxSq1, "u,cx,cz", onlyGenericU3CxOrCzOps, - true}), - [](const testing::TestParamInfo& info) { + FusionCase{"AdjacentCxCancel", fusionCxCx, 0, std::nullopt, true}, + FusionCase{"InterleavedOneQ", fusionHCxInterleavedTCx, std::nullopt, + std::nullopt, true}, + FusionCase{"DifferentPairBoundary", fusionThreeLineCx, std::nullopt, 1, + false}, + FusionCase{"SharedWireOneQ", fusionCxRSharedOtherPair, std::nullopt, 2, + false}, + FusionCase{"BarrierBoundary", fusionCxBarrierCx, 2, std::nullopt, + false}, + FusionCase{"SwappedWireOrder", fusionSwapCxPattern, std::nullopt, + std::nullopt, true}, + FusionCase{"OffMenuGateInWindow", fusionOffMenuGateInWindow, + std::nullopt, std::nullopt, true}, + FusionCase{"DualWireOneQBetweenCx", fusionDualWireOneQBetweenCx, + std::nullopt, std::nullopt, true}), + [](const testing::TestParamInfo& info) { return info.param.name; }); +// --- Pass edge cases ----------------------------------------------------- // + TEST_F(FuseTwoQubitUnitaryRunsPassTest, EmptyNativeGatesSkipsPass) { auto module = mlir::qc::QCProgramBuilder::build(context.get(), fusionCxCx); ASSERT_TRUE(module); @@ -660,16 +631,7 @@ TEST_F(FuseTwoQubitUnitaryRunsPassTest, EmptyNativeGatesSkipsPass) { EXPECT_EQ(before, after); } -TEST_F(FuseTwoQubitUnitaryRunsPassTest, InverseWrappedOpsSynthesize) { - expectNativeAfterSynthesis(inverseTwoX, "x,sx,rz,cx", onlyIbmBasicCxOps); -} - -TEST_F(FuseTwoQubitUnitaryRunsPassTest, ControlledXHSynthesizesToNativeMenu) { - expectEquivalentAndNativeAfterSynthesis(controlledXH, "x,sx,rz,cx", - onlyIbmBasicCxOps); -} - -TEST_F(FuseTwoQubitUnitaryRunsPassTest, FailsForUnsupportedNativeGateMenu) { +TEST_F(FuseTwoQubitUnitaryRunsPassTest, FailsForInvalidNativeGateMenu) { expectSynthesisFailure(mlir::qc::h, "not-a-gate"); } @@ -700,83 +662,3 @@ TEST_F(FuseTwoQubitUnitaryRunsPassTest, secondModule->print(osSecond); EXPECT_EQ(first, second); } - -TEST_F(FuseTwoQubitUnitaryRunsPassTest, ThreeQubitGhzEquivalentOnCoreProfiles) { - const std::array profiles{{ - {.name = "GhzIbm", - .program = threeQGhz, - .nativeGates = "x,sx,rz,cx", - .isNative = onlyIbmBasicCxOps, - .checkEquivalence = false}, - {.name = "GhzGeneric", - .program = threeQGhz, - .nativeGates = "u,cx", - .isNative = onlyGenericU3CxOps, - .checkEquivalence = false}, - {.name = "GhzIqm", - .program = threeQGhz, - .nativeGates = "r,cz", - .isNative = onlyIqmDefaultOps, - .checkEquivalence = false}, - }}; - for (const ProfileCase& profile : profiles) { - auto expected = mlir::qc::QCProgramBuilder::build(context.get(), threeQGhz); - runQcToQco(expected); - auto synthesized = - mlir::qc::QCProgramBuilder::build(context.get(), threeQGhz); - runFusePipeline(synthesized, profile.nativeGates); - EXPECT_TRUE(profile.isNative(synthesized)); - expectQcoModulesEquivalent(expected, synthesized); - } -} - -TEST_P(FuseTwoQubitFusionTest, WindowFusionBehavior) { - const FusionCase& c = GetParam(); - if (c.checkTwoQUnitary) { - expectTwoQFusePreservesUnitary(c.program, c.nativeGates); - } - auto module = mlir::qc::QCProgramBuilder::build(context.get(), c.program); - ASSERT_TRUE(module); - runQcToQco(module); - runTwoQFuse(module, c.nativeGates); - if (c.exactCtrlCount) { - EXPECT_EQ(countCtrlOps(module), *c.exactCtrlCount); - } - if (c.minCtrlCount) { - EXPECT_GE(countCtrlOps(module), *c.minCtrlCount); - } -} - -INSTANTIATE_TEST_SUITE_P( - Windows, FuseTwoQubitFusionTest, - testing::Values(FusionCase{"AdjacentCxCancel", fusionCxCx, "u,cx", 0, - std::nullopt, true}, - FusionCase{"InterleavedOneQ", fusionHCxInterleavedTCx, - "u,cx", std::nullopt, std::nullopt, true}, - FusionCase{"DifferentPairBoundary", fusionThreeLineCx, - "u,cx", std::nullopt, 1, false}, - FusionCase{"SharedWireOneQ", fusionCxRSharedOtherPair, - "u,cx", std::nullopt, 2, false}, - FusionCase{"BarrierBoundary", fusionCxBarrierCx, "u,cx", 2, - std::nullopt, false}, - FusionCase{"SwappedWireOrder", fusionSwapCxPattern, "u,cx", - std::nullopt, std::nullopt, true}, - FusionCase{"OffMenuGateInWindow", fusionOffMenuGateInWindow, - "u,cx", std::nullopt, std::nullopt, true}, - FusionCase{"DualWireOneQBetweenCx", - fusionDualWireOneQBetweenCx, "u,cx", - std::nullopt, std::nullopt, true}), - [](const testing::TestParamInfo& info) { - return info.param.name; - }); - -TEST_F(FuseTwoQubitUnitaryRunsPassTest, InvalidNativeGatesFailsPass) { - auto module = mlir::qc::QCProgramBuilder::build(context.get(), fusionCxCx); - ASSERT_TRUE(module); - runQcToQco(module); - PassManager pm(module->getContext()); - pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ - .nativeGates = "not-a-gate", - })); - EXPECT_TRUE(failed(pm.run(*module))); -} From f79d12f6b7d5056a8b73befc0858072edf9f9019 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 13:31:12 +0200 Subject: [PATCH 07/27] =?UTF-8?q?=E2=9C=A8=20Enhance=20FuseTwoQubitUnitary?= =?UTF-8?q?Runs=20pass=20with=20improved=20run=20scanning=20and=20metadata?= =?UTF-8?q?=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mlir/Dialect/QCO/Transforms/Passes.td | 10 +- .../FuseTwoQubitUnitaryRuns.cpp | 139 +++++++++++------- .../test_fuse_two_qubit_unitary_runs.cpp | 4 +- 3 files changed, 92 insertions(+), 61 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index d49a44fa36..e7383499a3 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -97,13 +97,13 @@ def FuseTwoQubitUnitaryRuns Supported pairs are exactly `rx`+`rz`, `rx`+`ry`, and `ry`+`rz`. Stages: fuse single-qubit runs (reusing `fuse-single-qubit-unitary-runs`, - which also lowers lone off-gateset single-qubit gates); fuse two-qubit - windows and lower any remaining off-gateset two-qubit ops via Weyl - synthesis; fuse the single-qubit seams introduced by that synthesis. The - pass fails if anything remains off the native gateset afterwards. + which also lowers lone off-gateset single-qubit gates); fuse two-qubit runs + and lower any remaining off-gateset two-qubit ops via Weyl synthesis; fuse + the single-qubit seams introduced by that synthesis. The pass fails if + anything remains off the native gateset afterwards. Lowering is deterministic: `cx` is preferred over `cz`, single-qubit - factors use the resolved Euler basis, and two-qubit window replacement uses + factors use the resolved Euler basis, and two-qubit run replacement uses the minimal entangler count from the synthesizer. }]; let options = [Option< diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index ef3970a617..b22f7c3bdd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -42,7 +42,24 @@ using decomposition::NativeGateset; using decomposition::populateFuseSingleQubitUnitaryRunsPatterns; using decomposition::synthesizeUnitary2QWeyl; -/// Skips unitaries nested under `ctrl`/`inv` bodies (handled on the shell op). +namespace { + +/** Composed unitary and metadata for a fusable two-qubit run. */ +struct FusableTwoQubitRun { + SmallVector ops; ///< Members in program order. + Matrix4x4 composed = Matrix4x4::identity(); + unsigned numTwoQ = 0; ///< Number of two-qubit members (entanglers consumed). + bool hasNonNativeGate = false; ///< Any member off the native gateset. + Value tailA; ///< Current output wires of the run's tail. + Value tailB; +}; + +} // namespace + +// --- Run membership ------------------------------------------------------- // + +/// Whether `op` is nested under a `ctrl`/`inv` body. Such unitaries are handled +/// through their shell op, so the top-level walk skips them. static bool isExcludedFromTopLevelUnitaryWalk(Operation* op) { if (op->getParentOfType()) { return true; @@ -50,6 +67,7 @@ static bool isExcludedFromTopLevelUnitaryWalk(Operation* op) { return !llvm::isa(op) && op->getParentOfType(); } +/// Whether `op` is a unitary shell the pass may rewrite at top level. static bool isWalkableUnitaryShell(Operation* op) { return !llvm::isa(op) && !isExcludedFromTopLevelUnitaryWalk(op); @@ -76,7 +94,8 @@ static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { return unitary.getUnitaryMatrix4x4(matrix); } -static bool isOneQubitWindowMember(UnitaryOpInterface unitary) { +/// Whether `unitary` is a single-qubit gate that can join a run. +static bool isOneQubitRunMember(UnitaryOpInterface unitary) { if (!unitary || !unitary.isSingleQubit() || !isWalkableUnitaryShell(unitary.getOperation())) { return false; @@ -85,6 +104,7 @@ static bool isOneQubitWindowMember(UnitaryOpInterface unitary) { return unitary.getUnitaryMatrix2x2(matrix); } +/// Whether `unitary` is a two-qubit gate that can join a run. static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { if (!unitary || !unitary.isTwoQubit() || !isWalkableUnitaryShell(unitary.getOperation())) { @@ -94,6 +114,10 @@ static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { return assignTwoQubitOpMatrix(unitary.getOperation(), matrix); } +// --- Wire navigation ------------------------------------------------------ // + +/// The sole run-member consumer of `wire`, or a null interface when `wire` has +/// no unique user or its user cannot join a run. static UnitaryOpInterface uniqueUnitaryUser(Value wire) { if (!wire.hasOneUse()) { return {}; @@ -106,11 +130,13 @@ static UnitaryOpInterface uniqueUnitaryUser(Value wire) { return isTwoQubitRunMember(unitary) ? unitary : UnitaryOpInterface{}; } if (unitary.isSingleQubit()) { - return isOneQubitWindowMember(unitary) ? unitary : UnitaryOpInterface{}; + return isOneQubitRunMember(unitary) ? unitary : UnitaryOpInterface{}; } return {}; } +/// Traces `wire` upstream through single-qubit gates to the two-qubit run +/// member terminating the chain, or `nullptr` if the chain is broken. static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { Value cur = wire; while (Operation* def = cur.getDefiningOp()) { @@ -121,7 +147,7 @@ static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { if (unitary.isTwoQubit()) { return isTwoQubitRunMember(unitary) ? def : nullptr; } - if (!isOneQubitWindowMember(unitary)) { + if (!isOneQubitRunMember(unitary)) { return nullptr; } cur = unitary.getInputQubit(0); @@ -129,7 +155,9 @@ static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { return nullptr; } -static bool feedsFromSameTwoQubitWindow(UnitaryOpInterface op) { +/// Whether both input wires of `op` come from one earlier two-qubit run, making +/// `op` a continuation of that run rather than a fresh run start. +static bool feedsFromSameTwoQubitRun(UnitaryOpInterface op) { const Value in0 = op.getInputQubit(0); const Value in1 = op.getInputQubit(1); if (!in0.hasOneUse() || !in1.hasOneUse()) { @@ -140,19 +168,11 @@ static bool feedsFromSameTwoQubitWindow(UnitaryOpInterface op) { return gate0 != nullptr && gate0 == gate1; } -namespace { - -struct FusableTwoQubitRun { - SmallVector ops; - Matrix4x4 composed = Matrix4x4::identity(); - unsigned numTwoQ = 0; - bool anyNonNative = false; - Value tailA; - Value tailB; -}; - -} // namespace +// --- Run scanning --------------------------------------------------------- // +/// Appends a two-qubit gate to `run`, composing its matrix. No-op unless both +/// of `op`'s inputs are the run's current tail wires (in either order), keeping +/// the run confined to a single pair of wires. static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, UnitaryOpInterface op, const NativeGateset& spec) { @@ -178,9 +198,10 @@ static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, run.composed.premultiplyBy(opMatrix.reorderForQubits(id0, id1)); run.ops.push_back(op.getOperation()); ++run.numTwoQ; - run.anyNonNative |= !spec.allowsOp(op.getOperation()); + run.hasNonNativeGate |= !spec.allowsOp(op.getOperation()); } +/// Appends a single-qubit gate on run wire `wireIndex` (0 = A, 1 = B). static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, UnitaryOpInterface op, const NativeGateset& spec, @@ -191,10 +212,14 @@ static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, } run.composed.premultiplyBy(raw.embedInTwoQubit(wireIndex)); run.ops.push_back(op.getOperation()); - run.anyNonNative |= !spec.allowsOp(op.getOperation()); + run.hasNonNativeGate |= !spec.allowsOp(op.getOperation()); (wireIndex == 0 ? run.tailA : run.tailB) = op.getOutputQubit(0); } +/// Walks forward from `head`, composing the run's matrix and metadata. Absorbs +/// a following two-qubit gate when it keeps both run wires together, otherwise +/// the single-qubit gate first in program order; stops at the first boundary +/// that would split the run's two wires. static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, const NativeGateset& spec) { FusableTwoQubitRun run; @@ -205,7 +230,7 @@ static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, run.tailB = head.getOutputQubit(1); run.ops.push_back(head.getOperation()); run.numTwoQ = 1; - run.anyNonNative |= !spec.allowsOp(head.getOperation()); + run.hasNonNativeGate |= !spec.allowsOp(head.getOperation()); while (true) { UnitaryOpInterface nextOnA = uniqueUnitaryUser(run.tailA); @@ -233,8 +258,16 @@ static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, return run; } -/// Whether any two-qubit or single-qubit unitary op (including `ctrl` shells) -/// remains off the native gateset. Used as the pass' final convergence check. +/// Erases all run members, successors first so each is dead when erased. +static void eraseFusableRun(PatternRewriter& rewriter, + const FusableTwoQubitRun& run) { + for (Operation* member : llvm::reverse(run.ops)) { + rewriter.eraseOp(member); + } +} + +/// Whether any two-qubit or single-qubit unitary (including `ctrl` shells) +/// remains off the native gateset. Used as the pass' convergence check. static bool hasNonNativeOps(Operation* root, const NativeGateset& spec) { const WalkResult walkResult = root->walk([&](Operation* op) { if (!isWalkableUnitaryShell(op) || !llvm::isa(op)) { @@ -247,18 +280,26 @@ static bool hasNonNativeOps(Operation* root, const NativeGateset& spec) { namespace { -/// Fuses a maximal window of two-qubit gates (plus interleaved single-qubit -/// gates) into a single composed unitary and resynthesizes it to the native -/// gateset when that is beneficial. -struct FuseTwoQubitWindowPattern final +/// Fuses a maximal two-qubit run into one composed unitary and resynthesizes it +/// to the native gateset when beneficial. +struct FuseTwoQubitUnitaryRunsPattern final : OpInterfaceRewritePattern { - FuseTwoQubitWindowPattern(MLIRContext* ctx, NativeGateset specIn) + FuseTwoQubitUnitaryRunsPattern(MLIRContext* ctx, NativeGateset specIn) : OpInterfaceRewritePattern(ctx), spec(std::move(specIn)) {} + NativeGateset spec; + + /// Whether `op` anchors a run: a two-qubit run member whose two wires are not + /// both fed by the same earlier run (which would make it a continuation). + static bool isRunStart(UnitaryOpInterface op) { + return isTwoQubitRunMember(op) && !feedsFromSameTwoQubitRun(op); + } + + /// Fuses the run anchored at `op` if it contains an off-gateset gate or Weyl + /// resynthesis uses fewer entanglers than the run's two-qubit members. LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { - // Only anchor on a run start: a run member not fed by an earlier window. - if (!isTwoQubitRunMember(op) || feedsFromSameTwoQubitWindow(op)) { + if (!isRunStart(op)) { return failure(); } @@ -267,10 +308,9 @@ struct FuseTwoQubitWindowPattern final return failure(); } - // Replace when off-gateset ops must be lowered, or when resynthesis uses - // fewer entanglers than the fused window. const auto native = spec.decomposeTarget(run.composed); - if (!native || (!run.anyNonNative && native->numBasisUses >= run.numTwoQ)) { + if (!native || + (!run.hasNonNativeGate && native->numBasisUses >= run.numTwoQ)) { return failure(); } @@ -286,24 +326,22 @@ struct FuseTwoQubitWindowPattern final } rewriter.replaceAllUsesWith(run.tailA, newA); rewriter.replaceAllUsesWith(run.tailB, newB); - for (Operation* member : llvm::reverse(run.ops)) { - rewriter.eraseOp(member); - } + eraseFusableRun(rewriter, run); return success(); } - - NativeGateset spec; }; -/// Lowers a single off-gateset two-qubit op (bare or single-target `CtrlOp`) -/// to the native entangler plus native single-qubit factors via Weyl synthesis. -/// Native two-qubit ops and windows fusable by @ref FuseTwoQubitWindowPattern -/// are left to that pattern. +/// Lowers a single off-gateset two-qubit op (bare or single-target `CtrlOp`) to +/// the native entangler plus native single-qubit factors via Weyl synthesis. +/// Native two-qubit ops and fusable runs are left to +/// @ref FuseTwoQubitUnitaryRunsPattern. struct LowerTwoQubitOpPattern final : OpInterfaceRewritePattern { LowerTwoQubitOpPattern(MLIRContext* ctx, NativeGateset specIn) : OpInterfaceRewritePattern(ctx), spec(std::move(specIn)) {} + NativeGateset spec; + LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { Operation* raw = op.getOperation(); @@ -335,17 +373,13 @@ struct LowerTwoQubitOpPattern final rewriter.replaceOp(raw, ValueRange{out0, out1}); return success(); } - - NativeGateset spec; }; } // namespace /// Fuses single-qubit runs (and lowers lone off-gateset single-qubit ops) by -/// reusing the `fuse-single-qubit-unitary-runs` rewrite. -/// -/// `qco.ctrl` bodies are skipped so the `X`/`Z` bodies of native entanglers are -/// preserved (both before and after two-qubit synthesis). +/// reusing the `fuse-single-qubit-unitary-runs` rewrite. `qco.ctrl` bodies are +/// skipped so the `X`/`Z` bodies of native entanglers are preserved. static LogicalResult fuseSingleQubitRuns(ModuleOp module, const EulerBasis basis) { RewritePatternSet patterns(module.getContext()); @@ -354,15 +388,14 @@ static LogicalResult fuseSingleQubitRuns(ModuleOp module, return applyPatternsGreedily(module, std::move(patterns)); } -/// Fuses two-qubit windows, then lowers any remaining off-gateset two-qubit -/// ops. +/// Fuses two-qubit runs, then lowers any remaining off-gateset two-qubit ops. static LogicalResult fuseAndLowerTwoQubitOps(ModuleOp module, const NativeGateset& spec) { MLIRContext* ctx = module.getContext(); { - RewritePatternSet windowPatterns(ctx); - windowPatterns.add(ctx, spec); - if (failed(applyPatternsGreedily(module, std::move(windowPatterns)))) { + RewritePatternSet runPatterns(ctx); + runPatterns.add(ctx, spec); + if (failed(applyPatternsGreedily(module, std::move(runPatterns)))) { return failure(); } } @@ -397,7 +430,7 @@ struct FuseTwoQubitUnitaryRunsPass final // 1. Fuse single-qubit runs (also lowers lone off-gateset single-qubit // ops). - // 2. Fuse two-qubit windows and lower remaining off-gateset two-qubit ops. + // 2. Fuse two-qubit runs and lower remaining off-gateset two-qubit ops. // 3. Fuse the single-qubit seams introduced by two-qubit synthesis. if (failed(fuseSingleQubitRuns(module, basis)) || failed(fuseAndLowerTwoQubitOps(module, *spec)) || diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 7fc3607158..bd2b8e7714 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -236,9 +236,7 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { // --- Expressive circuits -------------------------------------------------- // // -// A handful of circuits, each covering a distinct pass behavior. They are -// crossed with the gateset table below so that every native basis exercises the -// same structural variety, instead of pairing each circuit with a single menu. +// A handful of circuits, which are crossed with the gateset table below. /// A bare SWAP (three-entangler class), the canonical two-qubit decomposition. static void swapTwoQ(mlir::qc::QCProgramBuilder& b) { From cf5b04f2837519dde8eefaf0e3c99cfe5492e5a9 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 13:40:10 +0200 Subject: [PATCH 08/27] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Decomposition/test_weyl_decomposition.cpp | 1 - .../NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index 8568ce2c30..37c175edc8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index bd2b8e7714..409a7eabd5 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -562,7 +561,7 @@ INSTANTIATE_TEST_SUITE_P( testing::ValuesIn(GATESETS)), [](const testing::TestParamInfo& info) { std::string gateset = std::get<1>(info.param); - std::replace(gateset.begin(), gateset.end(), ',', '_'); + std::ranges::replace(gateset, ',', '_'); return std::string(std::get<0>(info.param).name) + "__" + gateset; }); From 0b7922ded8605f5b3848a7274ed65831eabe5f44 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 14:17:10 +0200 Subject: [PATCH 09/27] =?UTF-8?q?=E2=98=82=EF=B8=8F=20Increase=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mlir/Dialect/QCO/Transforms/Passes.td | 11 ++- .../FuseTwoQubitUnitaryRuns.cpp | 38 +++++---- .../test_fuse_two_qubit_unitary_runs.cpp | 81 +++++++++++++++++-- 3 files changed, 103 insertions(+), 27 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index e7383499a3..259e033e54 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -73,8 +73,10 @@ def FuseTwoQubitUnitaryRuns let summary = "Lower QCO unitary gates to a user-specified native gate menu."; let description = [{ Rewrites unitaries to match the comma-separated `native-gates` gateset. - `qco.barrier` and `qco.gphase` are preserved; `qco.ctrl` must have a - single control and a single target. + `qco.barrier` and `qco.gphase` are preserved. A `qco.ctrl` is treated as a + two-qubit gate only when it has a single control and a single target; gates + acting on more than two qubits (e.g. multi-controlled `qco.ctrl`) are out of + scope and left untouched for a dedicated multi-controlled synthesis pass. The gateset is a comma-separated list of gate tokens (order not significant) from which the pass resolves a single-qubit Euler basis @@ -99,8 +101,9 @@ def FuseTwoQubitUnitaryRuns Stages: fuse single-qubit runs (reusing `fuse-single-qubit-unitary-runs`, which also lowers lone off-gateset single-qubit gates); fuse two-qubit runs and lower any remaining off-gateset two-qubit ops via Weyl synthesis; fuse - the single-qubit seams introduced by that synthesis. The pass fails if - anything remains off the native gateset afterwards. + the single-qubit seams introduced by that synthesis. The pass fails if any + single- or two-qubit op remains off the native gateset afterwards; gates on + more than two qubits are left untouched and do not cause a failure. Lowering is deterministic: `cx` is preferred over `cz`, single-qubit factors use the resolved Euler basis, and two-qubit run replacement uses diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index b22f7c3bdd..209a30fbc9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include #include #include #include @@ -160,9 +162,9 @@ static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { static bool feedsFromSameTwoQubitRun(UnitaryOpInterface op) { const Value in0 = op.getInputQubit(0); const Value in1 = op.getInputQubit(1); - if (!in0.hasOneUse() || !in1.hasOneUse()) { - return false; - } + assert(in0.hasOneUse() && in1.hasOneUse() && + "qubit values are single-use, so a run member consumes each input " + "exactly once"); Operation* gate0 = twoQubitGateAtEndOfOneQChain(in0); Operation* gate1 = twoQubitGateAtEndOfOneQChain(in1); return gate0 != nullptr && gate0 == gate1; @@ -177,9 +179,9 @@ static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, UnitaryOpInterface op, const NativeGateset& spec) { Matrix4x4 opMatrix; - if (!assignTwoQubitOpMatrix(op.getOperation(), opMatrix)) { - return; - } + [[maybe_unused]] const bool assigned = + assignTwoQubitOpMatrix(op.getOperation(), opMatrix); + assert(assigned && "a two-qubit run member always exposes a 4x4 matrix"); const Value in0 = op.getInputQubit(0); const Value in1 = op.getInputQubit(1); std::size_t id0 = 0; @@ -193,7 +195,8 @@ static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, run.tailA = op.getOutputQubit(1); run.tailB = op.getOutputQubit(0); } else { - return; + llvm_unreachable( + "a unique user of both tail wires connects to both of them"); } run.composed.premultiplyBy(opMatrix.reorderForQubits(id0, id1)); run.ops.push_back(op.getOperation()); @@ -207,9 +210,8 @@ static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, const NativeGateset& spec, unsigned wireIndex) { Matrix2x2 raw; - if (!op.getUnitaryMatrix2x2(raw)) { - return; - } + [[maybe_unused]] const bool assigned = op.getUnitaryMatrix2x2(raw); + assert(assigned && "a single-qubit run member always exposes a 2x2 matrix"); run.composed.premultiplyBy(raw.embedInTwoQubit(wireIndex)); run.ops.push_back(op.getOperation()); run.hasNonNativeGate |= !spec.allowsOp(op.getOperation()); @@ -223,9 +225,9 @@ static void absorbOneQubitIntoRun(FusableTwoQubitRun& run, static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, const NativeGateset& spec) { FusableTwoQubitRun run; - if (!assignTwoQubitOpMatrix(head.getOperation(), run.composed)) { - return run; - } + [[maybe_unused]] const bool assigned = + assignTwoQubitOpMatrix(head.getOperation(), run.composed); + assert(assigned && "a run head is a two-qubit member with a 4x4 matrix"); run.tailA = head.getOutputQubit(0); run.tailB = head.getOutputQubit(1); run.ops.push_back(head.getOperation()); @@ -266,11 +268,15 @@ static void eraseFusableRun(PatternRewriter& rewriter, } } -/// Whether any two-qubit or single-qubit unitary (including `ctrl` shells) -/// remains off the native gateset. Used as the pass' convergence check. +/// Whether any single- or two-qubit unitary (including `ctrl` shells) remains +/// off the native gateset. Used as the pass' convergence check. Gates acting on +/// more than two qubits are out of scope here (a dedicated multi-controlled +/// synthesis pass possibly lowers those) and are left untouched rather than +/// reported. static bool hasNonNativeOps(Operation* root, const NativeGateset& spec) { const WalkResult walkResult = root->walk([&](Operation* op) { - if (!isWalkableUnitaryShell(op) || !llvm::isa(op)) { + auto unitary = llvm::dyn_cast(op); + if (!unitary || !isWalkableUnitaryShell(op) || unitary.getNumQubits() > 2) { return WalkResult::advance(); } return spec.allowsOp(op) ? WalkResult::advance() : WalkResult::interrupt(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 409a7eabd5..a9f541826d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -52,9 +52,10 @@ using ProgramFn = void (*)(mlir::qc::QCProgramBuilder&); // --- Native-gateset membership check ------------------------------------- // -/// Returns true when every operation in @p moduleOp is native to the gateset -/// parsed from @p nativeGates. Operations nested inside a controlled shell are -/// validated through the shell itself. +/// Returns true when every single- or two-qubit operation in @p moduleOp is +/// native to the gateset parsed from @p nativeGates. Operations nested inside a +/// controlled shell are validated through the shell itself, and gates acting on +/// more than two qubits are out of scope for this pass and thus ignored. static bool allOpsNative(OwningOpRef& moduleOp, StringRef nativeGates) { const auto spec = decomposition::NativeGateset::parse(nativeGates); @@ -64,7 +65,7 @@ static bool allOpsNative(OwningOpRef& moduleOp, bool ok = true; std::ignore = moduleOp->walk([&](UnitaryOpInterface op) { Operation* raw = op.getOperation(); - if (isa_and_present(raw->getParentOp())) { + if (isa_and_present(raw->getParentOp()) || op.getNumQubits() > 2) { return WalkResult::advance(); } if (!spec->allowsOp(raw)) { @@ -168,6 +169,13 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { continue; } if (isa(op.getOperation())) { + // A barrier is an identity on its wires, but it still threads qubit + // values, so carry each input's id over to the matching output. + for (std::size_t i = 0; i < op.getNumQubits(); ++i) { + if (const auto id = getQubitId(op->getOperand(i))) { + qubitIds[op->getResult(i)] = *id; + } + } continue; } if (auto gphase = dyn_cast(op.getOperation())) { @@ -366,6 +374,28 @@ static void fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { b.cx(q0, q1); } +/// A single-wire barrier between two entanglers: a non-walkable single-qubit +/// shell terminates the run scan on wire A (and breaks the run-start chain of +/// the second entangler), so neither entangler fuses. +static void fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + b.cx(q0, q1); + b.barrier({q0}); + b.cx(q0, q1); +} + +/// An entangler followed by a three-qubit gate sharing both run wires: the run +/// scan must stop at the wider gate (it is neither a single- nor a two-qubit +/// run member), leaving the non-native three-qubit gate for the pass to reject. +static void fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.allocQubit(); + const auto q1 = b.allocQubit(); + const auto q2 = b.allocQubit(); + b.cx(q0, q1); + b.mcx({q0, q1}, q2); +} + static void fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); @@ -525,6 +555,18 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { return count; } + /// Counts unitaries acting on more than two qubits, i.e. gates left untouched + /// for the dedicated multi-controlled synthesis pass. + static std::size_t countWideGates(const OwningOpRef& moduleOp) { + std::size_t count = 0; + moduleOp.get()->walk([&](UnitaryOpInterface op) { + if (op.getNumQubits() > 2) { + ++count; + } + }); + return count; + } + std::unique_ptr context; }; @@ -557,7 +599,9 @@ INSTANTIATE_TEST_SUITE_P( NamedProgram{"SurroundedCx", hCxSq1}, NamedProgram{"ThreeQubitGhz", threeQGhz}, NamedProgram{"InverseBody", inverseTwoX}, - NamedProgram{"ControlledBody", controlledXH}), + NamedProgram{"ControlledBody", controlledXH}, + NamedProgram{"SingleWireBarrier", + fusionCxSingleWireBarrierCx}), testing::ValuesIn(GATESETS)), [](const testing::TestParamInfo& info) { std::string gateset = std::get<1>(info.param); @@ -596,6 +640,8 @@ INSTANTIATE_TEST_SUITE_P( false}, FusionCase{"BarrierBoundary", fusionCxBarrierCx, 2, std::nullopt, false}, + FusionCase{"SingleWireBarrierBoundary", fusionCxSingleWireBarrierCx, 2, + std::nullopt, true}, FusionCase{"SwappedWireOrder", fusionSwapCxPattern, std::nullopt, std::nullopt, true}, FusionCase{"OffMenuGateInWindow", fusionOffMenuGateInWindow, @@ -637,8 +683,29 @@ TEST_F(FuseTwoQubitUnitaryRunsPassTest, expectSynthesisFailure(mlir::qc::singleControlledX, "cx,cz"); } -TEST_F(FuseTwoQubitUnitaryRunsPassTest, FailsForMultiControlledGateStructure) { - expectSynthesisFailure(mlir::qc::multipleControlledX, "x,sx,rz,cx"); +TEST_F(FuseTwoQubitUnitaryRunsPassTest, LeavesMultiControlledGateUntouched) { + // A multi-controlled gate is out of scope for this pass; it is left untouched + // (for a dedicated multi-controlled synthesis pass) and does not fail the + // run. + auto module = mlir::qc::QCProgramBuilder::build( + context.get(), mlir::qc::multipleControlledX); + ASSERT_TRUE(module); + runFusePipeline(module, "x,sx,rz,cx"); + EXPECT_TRUE(allOpsNative(module, "x,sx,rz,cx")); + EXPECT_EQ(countWideGates(module), 1U); +} + +TEST_F(FuseTwoQubitUnitaryRunsPassTest, + LowersTwoQubitRunButLeavesWiderGateBoundary) { + // The `cx` is off-menu for this cz-family gateset, so it is Weyl-synthesized + // even though the run scan stops at the three-qubit boundary; the wider gate + // is left untouched, so the pass succeeds with the two-qubit run lowered. + auto module = mlir::qc::QCProgramBuilder::build(context.get(), + fusionCxThenMultiControlledX); + ASSERT_TRUE(module); + runFusePipeline(module, "u,cz"); + EXPECT_TRUE(allOpsNative(module, "u,cz")); + EXPECT_EQ(countWideGates(module), 1U); } TEST_F(FuseTwoQubitUnitaryRunsPassTest, From c4e09698286b24285d96a76783bb3987d5dd74aa Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 16:06:23 +0200 Subject: [PATCH 10/27] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 6 +++--- .../Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp | 3 +++ .../NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 259e033e54..ebdd8db428 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -81,11 +81,11 @@ def FuseTwoQubitUnitaryRuns The gateset is a comma-separated list of gate tokens (order not significant) from which the pass resolves a single-qubit Euler basis (generic `qco.u` when `u` is present; IBM-style surface gates when all of - `x`, `sx`, and `rz`/`p` are present; IQM-style `qco.r` when `r` is present; + `x`, `sx`, and `rz` are present; IQM-style `qco.r` when `r` is present; or a supported rotation pair chosen from `rx`, `ry`, `rz`) plus the two-qubit entangler `cx` or `cz`. - Recognised tokens: `u`, `x`, `sx`, `rz` (or `p`), `rx`, `ry`, `r`, `cx`, + Recognised tokens: `u`, `x`, `sx`, `rz`, `rx`, `ry`, `r`, `cx`, `cz`. An empty or whitespace-only gateset is a no-op, which is the intended pipeline default when synthesis is not needed. An unrecognised token causes the pass to fail. @@ -112,7 +112,7 @@ def FuseTwoQubitUnitaryRuns let options = [Option< "nativeGates", "native-gates", "std::string", "\"\"", "Comma-separated native gateset. Empty or whitespace-only is " - "a no-op. Tokens: u, x, sx, rz (or p), rx, ry, r, cx, cz. " + "a no-op. Tokens: u, x, sx, rz, rx, ry, r, cx, cz. " "Examples: x,sx,rz,cx; u,cx; r,cz; rx,rz,cx.">]; } diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index 209a30fbc9..ee8b9103ce 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -247,6 +247,9 @@ static FusableTwoQubitRun scanFusableTwoQubitRun(UnitaryOpInterface head, const bool aSingle = nextOnA && nextOnA.isSingleQubit() && !sameOp; const bool bSingle = nextOnB && nextOnB.isSingleQubit() && !sameOp; + if (aSingle && bSingle && nextOnA->getBlock() != nextOnB->getBlock()) { + break; + } if (aSingle && (!bSingle || nextOnA->isBeforeInBlock(nextOnB))) { absorbOneQubitIntoRun(run, nextOnA, spec, /*wireIndex=*/0); continue; diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index a9f541826d..c0691f9fb9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -387,7 +387,8 @@ static void fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { /// An entangler followed by a three-qubit gate sharing both run wires: the run /// scan must stop at the wider gate (it is neither a single- nor a two-qubit -/// run member), leaving the non-native three-qubit gate for the pass to reject. +/// run member) and leave it untouched, since gates on more than two qubits are +/// out of scope for this pass rather than a failure. static void fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); From 707cd97aa7bb6b762cd7fe3bd72e8fee6c8d493d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 16:29:53 +0200 Subject: [PATCH 11/27] =?UTF-8?q?=E2=98=82=EF=B8=8F=20Increase=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FuseTwoQubitUnitaryRuns.cpp | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index ee8b9103ce..be038e2413 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -76,12 +76,10 @@ static bool isWalkableUnitaryShell(Operation* op) { } /// Builds the constant 4x4 matrix for a two-qubit op (bare or single-target -/// `CtrlOp`). Returns false for barriers, global phase, non-two-qubit ops, and -/// ops whose matrix is not known at compile time. +/// `CtrlOp`). Returns false for a `CtrlOp` that is not +/// single-control/single-target, or an op whose matrix is not known at compile +/// time. static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { - if (llvm::isa(op)) { - return false; - } if (auto ctrl = llvm::dyn_cast(op)) { if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { return false; @@ -89,10 +87,9 @@ static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { return llvm::cast(ctrl.getOperation()) .getUnitaryMatrix4x4(matrix); } - auto unitary = llvm::dyn_cast(op); - if (!unitary || !unitary.isTwoQubit()) { - return false; - } + auto unitary = llvm::cast(op); + assert(unitary.isTwoQubit() && + "only two-qubit unitary shells are passed to assignTwoQubitOpMatrix"); return unitary.getUnitaryMatrix4x4(matrix); } @@ -118,12 +115,11 @@ static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { // --- Wire navigation ------------------------------------------------------ // -/// The sole run-member consumer of `wire`, or a null interface when `wire` has -/// no unique user or its user cannot join a run. +/// The sole run-member consumer of `wire`, or a null interface when its unique +/// user cannot join a run. `wire` is single-use by qubit linearity. static UnitaryOpInterface uniqueUnitaryUser(Value wire) { - if (!wire.hasOneUse()) { - return {}; - } + assert(wire.hasOneUse() && + "qubit values are single-use, so a run tail has exactly one user"); auto unitary = llvm::dyn_cast(*wire.user_begin()); if (!unitary) { return {}; From 872132bd5ba02dbb92e472b4250b8d7ad9400938 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 16:37:26 +0200 Subject: [PATCH 12/27] =?UTF-8?q?=F0=9F=8E=A8=20Drop=20llvm=20and=20std=20?= =?UTF-8?q?clarifiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Decomposition/NativeGateset.cpp | 6 ++-- .../FuseTwoQubitUnitaryRuns.cpp | 26 +++++++------- .../test_fuse_two_qubit_unitary_runs.cpp | 34 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp index 4baf5cbd95..fe3ae7aece 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp @@ -146,7 +146,7 @@ NativeGateset::decomposeTarget(const Matrix4x4& target) const { } static std::optional gateKindFor(UnitaryOpInterface op) { - return llvm::TypeSwitch>( + return TypeSwitch>( op.getOperation()) .Case([](UOp) { return NativeGateKind::U; }) .Case([](XOp) { return NativeGateKind::X; }) @@ -163,7 +163,7 @@ static std::optional entanglerKindFor(CtrlOp ctrl) { ctrl.getNumBodyUnitaries() != 1) { return std::nullopt; } - return llvm::TypeSwitch>( + return TypeSwitch>( ctrl.getBodyUnitary(0).getOperation()) .Case([](XOp) { return NativeGateKind::CX; }) .Case([](ZOp) { return NativeGateKind::CZ; }) @@ -171,7 +171,7 @@ static std::optional entanglerKindFor(CtrlOp ctrl) { } bool NativeGateset::allowsOp(Operation* op) const { - return llvm::TypeSwitch(op) + return TypeSwitch(op) .Case([](auto) { return true; }) .Case([&](CtrlOp ctrl) { const auto kind = entanglerKindFor(ctrl); diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index be038e2413..5c96d7851d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -66,12 +66,12 @@ static bool isExcludedFromTopLevelUnitaryWalk(Operation* op) { if (op->getParentOfType()) { return true; } - return !llvm::isa(op) && op->getParentOfType(); + return !isa(op) && op->getParentOfType(); } /// Whether `op` is a unitary shell the pass may rewrite at top level. static bool isWalkableUnitaryShell(Operation* op) { - return !llvm::isa(op) && + return !isa(op) && !isExcludedFromTopLevelUnitaryWalk(op); } @@ -80,14 +80,14 @@ static bool isWalkableUnitaryShell(Operation* op) { /// single-control/single-target, or an op whose matrix is not known at compile /// time. static bool assignTwoQubitOpMatrix(Operation* op, Matrix4x4& matrix) { - if (auto ctrl = llvm::dyn_cast(op)) { + if (auto ctrl = dyn_cast(op)) { if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1) { return false; } - return llvm::cast(ctrl.getOperation()) + return cast(ctrl.getOperation()) .getUnitaryMatrix4x4(matrix); } - auto unitary = llvm::cast(op); + auto unitary = cast(op); assert(unitary.isTwoQubit() && "only two-qubit unitary shells are passed to assignTwoQubitOpMatrix"); return unitary.getUnitaryMatrix4x4(matrix); @@ -120,7 +120,7 @@ static bool isTwoQubitRunMember(UnitaryOpInterface unitary) { static UnitaryOpInterface uniqueUnitaryUser(Value wire) { assert(wire.hasOneUse() && "qubit values are single-use, so a run tail has exactly one user"); - auto unitary = llvm::dyn_cast(*wire.user_begin()); + auto unitary = dyn_cast(*wire.user_begin()); if (!unitary) { return {}; } @@ -138,7 +138,7 @@ static UnitaryOpInterface uniqueUnitaryUser(Value wire) { static Operation* twoQubitGateAtEndOfOneQChain(Value wire) { Value cur = wire; while (Operation* def = cur.getDefiningOp()) { - auto unitary = llvm::dyn_cast(def); + auto unitary = dyn_cast(def); if (!unitary) { return nullptr; } @@ -180,8 +180,8 @@ static void absorbTwoQubitIntoRun(FusableTwoQubitRun& run, assert(assigned && "a two-qubit run member always exposes a 4x4 matrix"); const Value in0 = op.getInputQubit(0); const Value in1 = op.getInputQubit(1); - std::size_t id0 = 0; - std::size_t id1 = 1; + size_t id0 = 0; + size_t id1 = 1; if (in0 == run.tailA && in1 == run.tailB) { run.tailA = op.getOutputQubit(0); run.tailB = op.getOutputQubit(1); @@ -274,7 +274,7 @@ static void eraseFusableRun(PatternRewriter& rewriter, /// reported. static bool hasNonNativeOps(Operation* root, const NativeGateset& spec) { const WalkResult walkResult = root->walk([&](Operation* op) { - auto unitary = llvm::dyn_cast(op); + auto unitary = dyn_cast(op); if (!unitary || !isWalkableUnitaryShell(op) || unitary.getNumQubits() > 2) { return WalkResult::advance(); } @@ -319,7 +319,7 @@ struct FuseTwoQubitUnitaryRunsPattern final return failure(); } - auto firstOp = llvm::cast(run.ops.front()); + auto firstOp = cast(run.ops.front()); rewriter.setInsertionPoint(firstOp); Value newA; Value newB; @@ -360,7 +360,7 @@ struct LowerTwoQubitOpPattern final Value in0; Value in1; - if (auto ctrl = llvm::dyn_cast(raw)) { + if (auto ctrl = dyn_cast(raw)) { in0 = ctrl.getInputControl(0); in1 = ctrl.getInputTarget(0); } else { @@ -420,7 +420,7 @@ struct FuseTwoQubitUnitaryRunsPass final protected: void runOnOperation() override { - if (llvm::StringRef(nativeGates).trim().empty()) { + if (StringRef(nativeGates).trim().empty()) { return; } const auto spec = NativeGateset::parse(nativeGates); diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index c0691f9fb9..207363c98b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -79,8 +79,8 @@ static bool allOpsNative(OwningOpRef& moduleOp, // --- Reference unitary reconstruction ------------------------------------ // -static std::optional unitaryQubit(Value v, std::size_t index, - std::size_t numQubits) { +static std::optional unitaryQubit(Value v, size_t index, + size_t numQubits) { if (index >= numQubits || !isa(v.getType())) { return std::nullopt; } @@ -88,12 +88,12 @@ static std::optional unitaryQubit(Value v, std::size_t index, } static std::optional unitaryQubitOperand(UnitaryOpInterface op, - std::size_t index) { + size_t index) { return unitaryQubit(op->getOperand(index), index, op.getNumQubits()); } static std::optional unitaryQubitResult(UnitaryOpInterface op, - std::size_t index) { + size_t index) { return unitaryQubit(op->getResult(index), index, op.getNumQubits()); } @@ -126,15 +126,15 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { return std::nullopt; } - DenseMap qubitIds; - std::size_t nextQubitId = 0; - std::size_t numQubits = 0; + DenseMap qubitIds; + size_t nextQubitId = 0; + size_t numQubits = 0; for (auto func : module.getOps()) { for (auto& block : func.getBlocks()) { for (auto& rawOp : block.getOperations()) { if (auto staticOp = dyn_cast(&rawOp)) { - const auto index = static_cast(staticOp.getIndex()); + const auto index = static_cast(staticOp.getIndex()); qubitIds.try_emplace(staticOp.getQubit(), index); numQubits = std::max(numQubits, index + 1); } else if (auto alloc = dyn_cast(&rawOp)) { @@ -150,10 +150,10 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { } DynamicMatrix unitary = - DynamicMatrix::identity(static_cast(1ULL << numQubits)); + DynamicMatrix::identity(static_cast(1ULL << numQubits)); Complex globalPhase{1.0, 0.0}; - auto getQubitId = [&](Value qubit) -> std::optional { + auto getQubitId = [&](Value qubit) -> std::optional { const auto it = qubitIds.find(qubit); if (it == qubitIds.end()) { return std::nullopt; @@ -171,7 +171,7 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { if (isa(op.getOperation())) { // A barrier is an identity on its wires, but it still threads qubit // values, so carry each input's id over to the matching output. - for (std::size_t i = 0; i < op.getNumQubits(); ++i) { + for (size_t i = 0; i < op.getNumQubits(); ++i) { if (const auto id = getQubitId(op->getOperand(i))) { qubitIds[op->getResult(i)] = *id; } @@ -466,8 +466,8 @@ constexpr StringRef FUSION_GATESET = "u,cx"; struct FusionCase { const char* name; ProgramFn program; - std::optional exactCtrlCount; - std::optional minCtrlCount; + std::optional exactCtrlCount; + std::optional minCtrlCount; bool checkTwoQUnitary; }; @@ -550,16 +550,16 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { expectQcoModulesEquivalent(expected, fused); } - static std::size_t countCtrlOps(const OwningOpRef& moduleOp) { - std::size_t count = 0; + static size_t countCtrlOps(const OwningOpRef& moduleOp) { + size_t count = 0; moduleOp.get()->walk([&](CtrlOp) { ++count; }); return count; } /// Counts unitaries acting on more than two qubits, i.e. gates left untouched /// for the dedicated multi-controlled synthesis pass. - static std::size_t countWideGates(const OwningOpRef& moduleOp) { - std::size_t count = 0; + static size_t countWideGates(const OwningOpRef& moduleOp) { + size_t count = 0; moduleOp.get()->walk([&](UnitaryOpInterface op) { if (op.getNumQubits() > 2) { ++count; From 2f322eb6e73b77dd19b372d875c225562c34972d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 16:37:44 +0200 Subject: [PATCH 13/27] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4e282f1e..cdc0b9ccfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ releases may include breaking changes. ### Added - ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit - unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**]) + unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**], + [**@burgholzer**]) - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function From 7d117b558b60cfac7e3459da8cb213a94a905dc1 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 7 Jul 2026 16:57:47 +0200 Subject: [PATCH 14/27] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index 5c96d7851d..f8491d70b8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -17,7 +17,6 @@ #include "mlir/Dialect/QCO/Utils/Matrix.h" #include -#include #include #include #include From ce8011841b6eac36d9a379f952715e6e877c0458 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 13 Jul 2026 11:43:56 +0200 Subject: [PATCH 15/27] =?UTF-8?q?=F0=9F=94=80=20Merge=20main=20and=20adapt?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 12 +- .rumdl.toml | 2 +- CHANGELOG.md | 74 +- README.md | 4 +- UPGRADING.md | 39 +- bindings/fomac/fomac.cpp | 185 +- bindings/ir/register_quantum_computation.cpp | 6 +- bindings/na/register_fomac.cpp | 4 +- cmake/ExternalDependencies.cmake | 8 +- docs/qir/index.md | 12 +- include/mqt-core/fomac/FoMaC.hpp | 1093 +++--- include/mqt-core/ir/QuantumComputation.hpp | 3 +- include/mqt-core/na/fomac/Device.hpp | 10 +- include/mqt-core/qasm3/Parser.hpp | 2 +- include/mqt-core/qasm3/Statement.hpp | 9 +- include/mqt-core/qir/runtime/Runtime.hpp | 58 +- .../Conversion/QCToQIR/QIRCommon/QIRCommon.h | 33 + .../Dialect/QC/Builder/QCProgramBuilder.h | 111 +- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 10 +- .../Dialect/QCO/Builder/QCOProgramBuilder.h | 170 +- mlir/include/mlir/Dialect/QCO/IR/QCODialect.h | 34 - mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 267 +- mlir/include/mlir/Dialect/QCO/QCOUtils.h | 17 +- .../Dialect/QCO/Transforms/Mapping/Mapping.h | 6 +- .../mlir/Dialect/QCO/Utils/Algorithms.h | 38 - mlir/include/mlir/Dialect/QCO/Utils/Drivers.h | 58 - mlir/include/mlir/Dialect/QCO/Utils/Graph.h | 108 + mlir/include/mlir/Dialect/QCO/Utils/Layout.h | 79 + mlir/include/mlir/Dialect/QCO/Utils/Matrix.h | 93 +- mlir/include/mlir/Dialect/QCO/Utils/Qubits.h | 71 - .../Dialect/QIR/Builder/QIRProgramBuilder.h | 51 +- mlir/include/mlir/Dialect/Utils/Utils.h | 34 + mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp | 17 +- .../QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp | 20 +- .../QCToQIR/QIRBase/QCToQIRBase.cpp | 15 +- .../QCToQIR/QIRCommon/QIRCommon.cpp | 54 + .../Dialect/QC/Builder/QCProgramBuilder.cpp | 85 +- mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp | 53 +- mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp | 42 +- .../QC/Translation/TranslateQASM3ToQC.cpp | 52 +- .../TranslateQuantumComputationToQC.cpp | 18 +- .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 155 +- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 57 +- mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp | 49 +- mlir/lib/Dialect/QCO/IR/QCOUtils.cpp | 146 +- .../Dialect/QCO/IR/QubitManagement/SinkOp.cpp | 83 +- mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp | 44 +- .../QCO/Transforms/Decomposition/Weyl.cpp | 9 +- .../QCO/Transforms/Mapping/Mapping.cpp | 1425 +++++--- mlir/lib/Dialect/QCO/Utils/Algorithms.cpp | 46 - mlir/lib/Dialect/QCO/Utils/Graph.cpp | 148 + mlir/lib/Dialect/QCO/Utils/Layout.cpp | 70 + mlir/lib/Dialect/QCO/Utils/Matrix.cpp | 483 +-- mlir/lib/Dialect/QCO/Utils/Qubits.cpp | 77 - .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 68 +- .../Dialect/QTensor/Utils/TensorIterator.cpp | 6 +- .../Compiler/test_compiler_pipeline.cpp | 543 +-- .../JeffRoundTrip/test_jeff_round_trip.cpp | 11 +- .../Conversion/QCOToQC/test_qco_to_qc.cpp | 11 +- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 14 +- .../test_qc_to_qir_adaptive.cpp | 342 +- .../QCToQIRBase/test_qc_to_qir_base.cpp | 240 +- mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | 36 +- .../QC/Translation/test_qasm3_translation.cpp | 57 +- .../test_quantum_computation_translation.cpp | 10 +- mlir/unittests/Dialect/QCO/IR/CMakeLists.txt | 11 +- mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 132 +- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 234 +- .../test_euler_decomposition.cpp | 155 +- .../Decomposition/test_weyl_decomposition.cpp | 40 +- .../QCO/Transforms/Mapping/test_mapping.cpp | 646 +++- .../test_fuse_two_qubit_unitary_runs.cpp | 95 +- .../test_qco_hadamard_lifting.cpp | 34 +- .../Dialect/QCO/Utils/test_drivers.cpp | 53 - .../Dialect/QCO/Utils/test_unitary_matrix.cpp | 108 +- mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp | 404 +-- .../Dialect/QTensor/IR/test_qtensor_ir.cpp | 24 +- mlir/unittests/TestCaseUtils.h | 69 +- mlir/unittests/programs/qasm_programs.cpp | 119 +- mlir/unittests/programs/qc_programs.cpp | 993 ++++-- mlir/unittests/programs/qc_programs.h | 566 ++-- mlir/unittests/programs/qco_programs.cpp | 2941 +++++++++++------ mlir/unittests/programs/qco_programs.h | 733 ++-- mlir/unittests/programs/qir_programs.cpp | 748 ++++- mlir/unittests/programs/qir_programs.h | 351 +- .../programs/quantum_computation_programs.cpp | 113 +- pyproject.toml | 4 +- python/mqt/core/fomac.pyi | 13 +- python/mqt/core/ir/__init__.pyi | 4 +- src/fomac/FoMaC.cpp | 509 +-- src/ir/QuantumComputation.cpp | 6 +- src/na/fomac/Device.cpp | 23 +- src/qasm3/Parser.cpp | 13 +- src/qdmi/devices/dd/Device.cpp | 6 +- src/qdmi/driver/Driver.cpp | 13 +- src/qir/jit/Session.cpp | 29 + src/qir/runner/CMakeLists.txt | 2 +- src/qir/runner/Runner.cpp | 8 +- src/qir/runtime/QIR.cpp | 21 +- src/qir/runtime/Runtime.cpp | 72 +- test/fomac/test_fomac.cpp | 72 +- test/python/fomac/test_fomac.py | 14 + test/qdmi/driver/test_driver.cpp | 8 + test/qir/jit/test_jit_session.cpp | 39 +- test/qir/runtime/test_qir_runtime.cpp | 153 +- uv.lock | 588 ++-- 107 files changed, 11107 insertions(+), 6248 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/QCO/Utils/Algorithms.h create mode 100644 mlir/include/mlir/Dialect/QCO/Utils/Graph.h create mode 100644 mlir/include/mlir/Dialect/QCO/Utils/Layout.h delete mode 100644 mlir/include/mlir/Dialect/QCO/Utils/Qubits.h delete mode 100644 mlir/lib/Dialect/QCO/Utils/Algorithms.cpp create mode 100644 mlir/lib/Dialect/QCO/Utils/Graph.cpp create mode 100644 mlir/lib/Dialect/QCO/Utils/Layout.cpp delete mode 100644 mlir/lib/Dialect/QCO/Utils/Qubits.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4d9370843..04817ed5bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,7 +163,7 @@ jobs: build-project: true files-changed-only: true setup-python: true - install-pkgs: "nanobind==2.12.0" + install-pkgs: "nanobind==2.13.0" cpp-linter-extra-args: "-std=c++20" setup-mlir: true llvm-version: 22.1.7 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30dbb0025a..ab8290674d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: ## Check JSON schemata - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.3 + rev: 0.37.4 hooks: - id: check-github-workflows priority: 0 @@ -47,14 +47,14 @@ repos: ## Check pyproject.toml file - repo: https://github.com/henryiii/validate-pyproject-schema-store - rev: 2026.06.26 + rev: 2026.06.30 hooks: - id: validate-pyproject priority: 0 ## Ensure uv.lock is up to date - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.25 + rev: 0.11.26 hooks: - id: uv-lock priority: 0 @@ -71,7 +71,7 @@ repos: ## Check for typos - repo: https://github.com/adhtruong/mirrors-typos - rev: v1.47.2 + rev: v1.48.0 hooks: - id: typos priority: 3 @@ -104,7 +104,7 @@ repos: ## Format configuration files with prettier - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.9.1 + rev: v3.9.4 hooks: - id: prettier types_or: [yaml, html, css, scss, javascript, json, json5] @@ -156,7 +156,7 @@ repos: ## Check Python types with ty - repo: https://github.com/astral-sh/ty-pre-commit - rev: v0.0.55 + rev: v0.0.56 hooks: - id: ty args: [--only-dev] diff --git a/.rumdl.toml b/.rumdl.toml index 05e0273bbc..d16914050f 100644 --- a/.rumdl.toml +++ b/.rumdl.toml @@ -3,7 +3,7 @@ "**/pull_request_template.md" = ["MD013", "MD041"] # The docs files may include HTML and do not need to start with a top-level heading "docs/**" = ["MD033", "MD041"] -# The README may include HTML and dooes not need to start with a top-level heading +# The README may include HTML and does not need to start with a top-level heading "README.md" = ["MD033", "MD041"] [per-file-flavor] diff --git a/CHANGELOG.md b/CHANGELOG.md index cdc0b9ccfe..75357fe3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ releases may include breaking changes. ### Added +- ✨ Add support for custom job parameters to C++ and Python FoMaC library + ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) +- ✨ Add QIR Output Schemas support to the QIR runtime ([#1877]) + ([**@rturrado**]) - ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**], [**@burgholzer**]) @@ -23,15 +27,13 @@ releases may include breaking changes. environment ([#1786]) ([**@denialhaag**]) - ✨ Add two-qubit Weyl (KAK) decomposition and native-gateset synthesis support ([#1803], [#1832]) ([**@simon1hofmann**], [**@burgholzer**]) -- ✨ Extend the QCO unitary matrix library ([#1774], [#1802], [#1809], [#1814]) - ([**@simon1hofmann**], [**@burgholzer**]) +- ✨ Extend the QCO unitary matrix library ([#1774], [#1802], [#1809], [#1814], + [#1850]) ([**@simon1hofmann**], [**@burgholzer**]) - ✨ Add a `fuse-single-qubit-unitary-runs` pass for fusing compile-time single-qubit unitary runs via Euler resynthesis ([#1672]) ([**@simon1hofmann**], [**@burgholzer**]) - ✨ Add QIR program format support to the DDSIM QDMI Device ([#1766]) ([**@rturrado**]) -- 🚸 Add [CMake presets] to provide a standardized and reproducible way to - configure builds ([#1660]) ([**@denialhaag**]) - ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates @@ -43,24 +45,22 @@ releases may include breaking changes. [#1676], [#1706], [#1776]) ([**@denialhaag**], [**@burgholzer**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], - [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], - [**@burgholzer**]) + [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870]) + ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1710], - [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], - [#1781], [#1782], [#1787], [#1806], [#1807], [#1808], [#1823], [#1824], - [#1830]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], - [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], - [**@simon1hofmann**]) + [#1717], [#1728], [#1730], [#1749], [#1751], [#1755], [#1762], [#1765], + [#1780], [#1781], [#1782], [#1787], [#1806], [#1807], [#1808], [#1823], + [#1824], [#1830], [#1869], [#1872]) ([**@burgholzer**], [**@denialhaag**], + [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], + [**@MatthiasReumann**], [**@simon1hofmann**]) ### Changed -- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], - [#1737]) ([**@denialhaag**]) - ⬆️ Require LLVM 22.1 for C++ library builds ([#1549]) ([**@burgholzer**], [**@denialhaag**]) - 📦 Build MLIR by default for C++ library builds ([#1356]) ([**@burgholzer**], @@ -68,8 +68,6 @@ releases may include breaking changes. ### Removed -- 📝 Remove support for generating LaTeX documentation ([#1828]) - ([**@denialhaag**]) - 🔥 Remove the density matrix support from the MQT Core DD package ([#1466]) ([**@burgholzer**]) - 🔥 Remove `datastructures` (`ds`) (sub)library from MQT Core ([#1458]) @@ -78,6 +76,36 @@ releases may include breaking changes. ### Fixed - 🐛 Fix QIR function names for adjoint gates ([#1830]) ([**@denialhaag**]) + +## [3.7.0] - 2026-07-09 + +_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#370)._ + +### Added + +- ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter + ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) +- 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function + ([#1826]) ([**@ystade**]) +- 🚸 Add [CMake presets] to provide a standardized and reproducible way to + configure builds ([#1660]) ([**@denialhaag**]) + +### Changed + +- ⬆️ Update QDMI to version 1.3.2 ([#1873]) ([**@denialhaag**]) +- ♻️ Improve implementation and usability of FoMaC classes ([#1849]) + ([**@MatthiasReumann**]) +- ⬆️ Update `nanobind` to version 2.13.0 ([#1817]) +- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], + [#1737]) ([**@denialhaag**]) + +### Removed + +- 📝 Remove support for generating LaTeX documentation ([#1828]) + ([**@denialhaag**]) + +### Fixed + - 🐛 Fix invalid `prop_type` for `QDMI_DEVICE_PROPERTY_COUPLINGMAP` in QDMI SC Device ([#1842]) ([**@MatthiasReumann**]) @@ -146,7 +174,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#350)._ ### Changed - ⬆️ Update `nanobind` to version 2.12.0 ([#1528]) -- ⬆️ Update QDMI to `v1.3.0` ([#1652]) ([**@burgholzer**]) +- ⬆️ Update QDMI to version 1.3.0 ([#1652]) ([**@burgholzer**]) - 📦 Switch to component-based installation for the MQT Core Python package ([#1596]) ([**@burgholzer**]) - ⬆️ Update QDMI to latest version from stable `v1.2.x` branch ([#1593]) @@ -582,7 +610,8 @@ changelogs._ -[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.6.1...HEAD +[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.7.0...HEAD +[3.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.7.0 [3.6.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.1 [3.6.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.0 [3.5.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.1 @@ -603,7 +632,15 @@ changelogs._ +[#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 +[#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 +[#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 +[#1872]: https://github.com/munich-quantum-toolkit/core/pull/1872 +[#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 +[#1869]: https://github.com/munich-quantum-toolkit/core/pull/1869 [#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865 +[#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 +[#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 @@ -613,11 +650,13 @@ changelogs._ [#1826]: https://github.com/munich-quantum-toolkit/core/pull/1826 [#1824]: https://github.com/munich-quantum-toolkit/core/pull/1824 [#1823]: https://github.com/munich-quantum-toolkit/core/pull/1823 +[#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 [#1814]: https://github.com/munich-quantum-toolkit/core/pull/1814 [#1809]: https://github.com/munich-quantum-toolkit/core/pull/1809 [#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808 [#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807 [#1806]: https://github.com/munich-quantum-toolkit/core/pull/1806 +[#1805]: https://github.com/munich-quantum-toolkit/core/pull/1805 [#1803]: https://github.com/munich-quantum-toolkit/core/pull/1803 [#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802 [#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787 @@ -630,6 +669,7 @@ changelogs._ [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 +[#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 [#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 diff --git a/README.md b/README.md index 0e8196dbc7..deb61e0b17 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,13 @@ To support this endeavor, please consider: `mqt.core` is available via [PyPI](https://pypi.org/project/mqt.core/). ```console -(.venv) $ pip install mqt.core +uv pip install mqt.core ``` The following code gives an example on the usage: ```python3 -from mqt.core import QuantumComputation +from mqt.core.ir import QuantumComputation qc = QuantumComputation(2, 2) qc.h(0) diff --git a/UPGRADING.md b/UPGRADING.md index 56673a4530..e33dfb48b3 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -54,6 +54,33 @@ Its functionality has only ever been used in [MQT QMAP] since its inception. As a consequence, the code shall be moved to [MQT QMAP] once QMAP adopts an MQT Core version that includes this change. +### Dev container + +A [dev container](https://containers.dev/) configuration is available to provide +a consistent local development environment. Common IDEs like +[CLion](https://www.jetbrains.com/help/clion/dev-containers-starting-page.html) +and [VS Code](https://code.visualstudio.com/docs/devcontainers/containers) can +open the repository directly inside the container. If you are on Windows, we +recommend using Docker Desktop with the WSL 2 backend. + +## [3.7.0] + +The shared library ABI version (`SOVERSION`) is increased from `3.6` to `3.7`. +Thus, consuming libraries need to update their wheel repair configuration for +`cibuildwheel` to ensure the `mqt-core` libraries are properly skipped in the +wheel repair step. + +### `nanobind` updated to version 2.13.0 + +This release updates the `nanobind` dependency to version 2.13.0, which includes +an ABI bump. Any existing code that uses the `mqt-core` Python bindings will +need to be recompiled with the new `nanobind` version. + +### QDMI updated to version 1.3.2 + +While not a breaking change, this release updates the QDMI dependency to version +1.3.2 + ### CMake presets [CMake presets] have been added to provide a standardized and reproducible way @@ -75,15 +102,6 @@ preparation for a `clang-tidy` run. If you are on Windows, use the `debug-windows` and `release-windows` presets. -### Dev container - -A [dev container](https://containers.dev/) configuration is available to provide -a consistent local development environment. Common IDEs like -[CLion](https://www.jetbrains.com/help/clion/dev-containers-starting-page.html) -and [VS Code](https://code.visualstudio.com/docs/devcontainers/containers) can -open the repository directly inside the container. If you are on Windows, we -recommend using Docker Desktop with the WSL 2 backend. - ## [3.6.0] The shared library ABI version (`SOVERSION`) is increased from `3.5` to `3.6`. @@ -378,7 +396,8 @@ It also requires the `uv` library version 0.5.20 or higher. -[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.6.0...HEAD +[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.7.0...HEAD +[3.7.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.6.0...v3.7.0 [3.6.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.5.1...v3.6.0 [3.5.1]: https://github.com/munich-quantum-toolkit/core/compare/v3.5.0...v3.5.1 [3.5.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.4.0...v3.5.0 diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 3714d91c6a..4239d0d1bd 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -19,6 +19,7 @@ #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 @@ -117,13 +118,13 @@ All authentication parameters are optional and can be provided as keyword argume List of available devices.)pb"); // Job class - auto job = nb::class_( + auto job = nb::class_( m, "Job", "A job represents a submitted quantum program execution."); - job.def("check", &fomac::Session::Job::check, + job.def("check", &fomac::Job::check, "Returns the current status of the job."); - job.def("wait", &fomac::Session::Job::wait, "timeout"_a = 0, + job.def("wait", &fomac::Job::wait, "timeout"_a = 0, R"pb(Waits for the job to complete. Args: @@ -132,41 +133,38 @@ All authentication parameters are optional and can be provided as keyword argume Returns: True if the job completed within the timeout, False otherwise.)pb"); - job.def("cancel", &fomac::Session::Job::cancel, "Cancels the job."); + job.def("cancel", &fomac::Job::cancel, "Cancels the job."); - job.def("get_shots", &fomac::Session::Job::getShots, + job.def("get_shots", &fomac::Job::getShots, "Returns the raw shot results from the job."); - job.def("get_counts", &fomac::Session::Job::getCounts, + job.def("get_counts", &fomac::Job::getCounts, "Returns the measurement counts from the job."); - job.def("get_dense_statevector", &fomac::Session::Job::getDenseStateVector, + job.def("get_dense_statevector", &fomac::Job::getDenseStateVector, "Returns the dense statevector from the job (typically only " "available from simulator devices)."); - job.def("get_dense_probabilities", - &fomac::Session::Job::getDenseProbabilities, + job.def("get_dense_probabilities", &fomac::Job::getDenseProbabilities, "Returns the dense probabilities from the job (typically only " "available from simulator devices)."); - job.def("get_sparse_statevector", &fomac::Session::Job::getSparseStateVector, + job.def("get_sparse_statevector", &fomac::Job::getSparseStateVector, "Returns the sparse statevector from the job (typically only " "available from simulator devices)."); - job.def("get_sparse_probabilities", - &fomac::Session::Job::getSparseProbabilities, + job.def("get_sparse_probabilities", &fomac::Job::getSparseProbabilities, "Returns the sparse probabilities from the job (typically only " "available from simulator devices)."); - job.def_prop_ro("id", &fomac::Session::Job::getId, "The job ID."); + job.def_prop_ro("id", &fomac::Job::getId, "The job ID."); - job.def_prop_ro("program_format", &fomac::Session::Job::getProgramFormat, + job.def_prop_ro("program_format", &fomac::Job::getProgramFormat, "The format of the submitted program."); - job.def_prop_ro("program", &fomac::Session::Job::getProgram, - "The submitted program."); + job.def_prop_ro("program", &fomac::Job::getProgram, "The submitted program."); - job.def_prop_ro("num_shots", &fomac::Session::Job::getNumShots, + job.def_prop_ro("num_shots", &fomac::Job::getNumShots, "The number of shots."); job.def(nb::self == nb::self, @@ -203,7 +201,7 @@ All authentication parameters are optional and can be provided as keyword argume .value("CUSTOM5", QDMI_PROGRAM_FORMAT_CUSTOM5); // Device class - auto device = nb::class_( + auto device = nb::class_( m, "Device", "A device represents a quantum device with its properties and " "capabilities."); @@ -217,68 +215,69 @@ All authentication parameters are optional and can be provided as keyword argume .value("MAINTENANCE", QDMI_DEVICE_STATUS_MAINTENANCE) .value("CALIBRATION", QDMI_DEVICE_STATUS_CALIBRATION); - device.def("name", &fomac::Session::Device::getName, + device.def("name", &fomac::Device::getName, "Returns the name of the device."); - device.def("version", &fomac::Session::Device::getVersion, + device.def("version", &fomac::Device::getVersion, "Returns the version of the device."); - device.def("status", &fomac::Session::Device::getStatus, + device.def("status", &fomac::Device::getStatus, "Returns the current status of the device."); - device.def("library_version", &fomac::Session::Device::getLibraryVersion, + device.def("library_version", &fomac::Device::getLibraryVersion, "Returns the version of the library used to define the device."); - device.def("qubits_num", &fomac::Session::Device::getQubitsNum, + device.def("qubits_num", &fomac::Device::getQubitsNum, "Returns the number of qubits available on the device."); - device.def("sites", &fomac::Session::Device::getSites, + device.def("sites", &fomac::Device::getSites, "Returns the list of all sites (zone and regular sites) available " "on the device."); - device.def("regular_sites", &fomac::Session::Device::getRegularSites, + device.def("regular_sites", &fomac::Device::getRegularSites, "Returns the list of regular sites (without zone sites) available " "on the device."); - device.def("zones", &fomac::Session::Device::getZones, + device.def("zones", &fomac::Device::getZones, "Returns the list of zone sites (without regular sites) available " "on the device."); - device.def("operations", &fomac::Session::Device::getOperations, + device.def("operations", &fomac::Device::getOperations, "Returns the list of operations supported by the device."); - device.def("coupling_map", &fomac::Session::Device::getCouplingMap, + device.def("coupling_map", &fomac::Device::getCouplingMap, "Returns the coupling map of the device as a list of site pairs."); - device.def("needs_calibration", &fomac::Session::Device::getNeedsCalibration, + device.def("needs_calibration", &fomac::Device::getNeedsCalibration, "Returns whether the device needs calibration."); - device.def("length_unit", &fomac::Session::Device::getLengthUnit, + device.def("length_unit", &fomac::Device::getLengthUnit, "Returns the unit of length used by the device."); - device.def("length_scale_factor", - &fomac::Session::Device::getLengthScaleFactor, + device.def("length_scale_factor", &fomac::Device::getLengthScaleFactor, "Returns the scale factor for length used by the device."); - device.def("duration_unit", &fomac::Session::Device::getDurationUnit, + device.def("duration_unit", &fomac::Device::getDurationUnit, "Returns the unit of duration used by the device."); - device.def("duration_scale_factor", - &fomac::Session::Device::getDurationScaleFactor, + device.def("duration_scale_factor", &fomac::Device::getDurationScaleFactor, "Returns the scale factor for duration used by the device."); - device.def("min_atom_distance", &fomac::Session::Device::getMinAtomDistance, + device.def("min_atom_distance", &fomac::Device::getMinAtomDistance, "Returns the minimum atom distance on the device."); device.def("supported_program_formats", - &fomac::Session::Device::getSupportedProgramFormats, + &fomac::Device::getSupportedProgramFormats, "Returns the list of program formats supported by the device."); - device.def("submit_job", &fomac::Session::Device::submitJob, "program"_a, - "program_format"_a, "num_shots"_a, - nb::rv_policy::reference_internal, "Submits a job to the device."); + device.def("submit_job", &fomac::Device::submitJob, "program"_a, + "program_format"_a, "num_shots"_a, nb::kw_only(), + "custom1"_a = nb::none(), "custom2"_a = nb::none(), + "custom3"_a = nb::none(), "custom4"_a = nb::none(), + "custom5"_a = nb::none(), nb::rv_policy::reference_internal, + "Submits a job to the device."); - device.def("__repr__", [](const fomac::Session::Device& dev) { + device.def("__repr__", [](const fomac::Device& dev) { return ""; }); @@ -288,50 +287,48 @@ All authentication parameters are optional and can be provided as keyword argume nb::sig("def __ne__(self, arg: object, /) -> bool")); // Site class - auto site = nb::class_( + auto site = nb::class_( device, "Site", "A site represents a potential qubit location on a quantum device."); - site.def("index", &fomac::Session::Device::Site::getIndex, - "Returns the index of the site."); + site.def("index", &fomac::Site::getIndex, "Returns the index of the site."); - site.def("t1", &fomac::Session::Device::Site::getT1, + site.def("t1", &fomac::Site::getT1, "Returns the T1 coherence time of the site."); - site.def("t2", &fomac::Session::Device::Site::getT2, + site.def("t2", &fomac::Site::getT2, "Returns the T2 coherence time of the site."); - site.def("name", &fomac::Session::Device::Site::getName, - "Returns the name of the site."); + site.def("name", &fomac::Site::getName, "Returns the name of the site."); - site.def("x_coordinate", &fomac::Session::Device::Site::getXCoordinate, + site.def("x_coordinate", &fomac::Site::getXCoordinate, "Returns the x coordinate of the site."); - site.def("y_coordinate", &fomac::Session::Device::Site::getYCoordinate, + site.def("y_coordinate", &fomac::Site::getYCoordinate, "Returns the y coordinate of the site."); - site.def("z_coordinate", &fomac::Session::Device::Site::getZCoordinate, + site.def("z_coordinate", &fomac::Site::getZCoordinate, "Returns the z coordinate of the site."); - site.def("is_zone", &fomac::Session::Device::Site::isZone, + site.def("is_zone", &fomac::Site::isZone, "Returns whether the site is a zone."); - site.def("x_extent", &fomac::Session::Device::Site::getXExtent, + site.def("x_extent", &fomac::Site::getXExtent, "Returns the x extent of the site."); - site.def("y_extent", &fomac::Session::Device::Site::getYExtent, + site.def("y_extent", &fomac::Site::getYExtent, "Returns the y extent of the site."); - site.def("z_extent", &fomac::Session::Device::Site::getZExtent, + site.def("z_extent", &fomac::Site::getZExtent, "Returns the z extent of the site."); - site.def("module_index", &fomac::Session::Device::Site::getModuleIndex, + site.def("module_index", &fomac::Site::getModuleIndex, "Returns the index of the module the site belongs to."); - site.def("submodule_index", &fomac::Session::Device::Site::getSubmoduleIndex, + site.def("submodule_index", &fomac::Site::getSubmoduleIndex, "Returns the index of the submodule the site belongs to."); - site.def("__repr__", [](const fomac::Session::Device::Site& s) { + site.def("__repr__", [](const fomac::Site& s) { return ""; }); @@ -341,78 +338,68 @@ All authentication parameters are optional and can be provided as keyword argume nb::sig("def __ne__(self, arg: object, /) -> bool")); // Operation class - auto operation = nb::class_( + auto operation = nb::class_( device, "Operation", "An operation represents a quantum operation that can be performed on a " "quantum device."); - operation.def("name", &fomac::Session::Device::Operation::getName, - "sites"_a.sig("...") = - std::vector{}, + operation.def("name", &fomac::Operation::getName, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the name of the operation."); - operation.def("qubits_num", &fomac::Session::Device::Operation::getQubitsNum, - "sites"_a.sig("...") = - std::vector{}, + operation.def("qubits_num", &fomac::Operation::getQubitsNum, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the number of qubits the operation acts on."); - operation.def( - "parameters_num", &fomac::Session::Device::Operation::getParametersNum, - "sites"_a.sig("...") = std::vector{}, - "params"_a.sig("...") = std::vector{}, - "Returns the number of parameters the operation has."); + operation.def("parameters_num", &fomac::Operation::getParametersNum, + "sites"_a.sig("...") = std::vector{}, + "params"_a.sig("...") = std::vector{}, + "Returns the number of parameters the operation has."); - operation.def("duration", &fomac::Session::Device::Operation::getDuration, - "sites"_a.sig("...") = - std::vector{}, + operation.def("duration", &fomac::Operation::getDuration, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the duration of the operation."); - operation.def("fidelity", &fomac::Session::Device::Operation::getFidelity, - "sites"_a.sig("...") = - std::vector{}, + operation.def("fidelity", &fomac::Operation::getFidelity, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the fidelity of the operation."); - operation.def("interaction_radius", - &fomac::Session::Device::Operation::getInteractionRadius, - "sites"_a.sig("...") = - std::vector{}, + operation.def("interaction_radius", &fomac::Operation::getInteractionRadius, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the interaction radius of the operation."); - operation.def( - "blocking_radius", &fomac::Session::Device::Operation::getBlockingRadius, - "sites"_a.sig("...") = std::vector{}, - "params"_a.sig("...") = std::vector{}, - "Returns the blocking radius of the operation."); + operation.def("blocking_radius", &fomac::Operation::getBlockingRadius, + "sites"_a.sig("...") = std::vector{}, + "params"_a.sig("...") = std::vector{}, + "Returns the blocking radius of the operation."); - operation.def( - "idling_fidelity", &fomac::Session::Device::Operation::getIdlingFidelity, - "sites"_a.sig("...") = std::vector{}, - "params"_a.sig("...") = std::vector{}, - "Returns the idling fidelity of the operation."); + operation.def("idling_fidelity", &fomac::Operation::getIdlingFidelity, + "sites"_a.sig("...") = std::vector{}, + "params"_a.sig("...") = std::vector{}, + "Returns the idling fidelity of the operation."); - operation.def("is_zoned", &fomac::Session::Device::Operation::isZoned, + operation.def("is_zoned", &fomac::Operation::isZoned, "Returns whether the operation is zoned."); - operation.def("sites", &fomac::Session::Device::Operation::getSites, + operation.def("sites", &fomac::Operation::getSites, "Returns the list of sites the operation can be performed on."); - operation.def("site_pairs", &fomac::Session::Device::Operation::getSitePairs, + operation.def("site_pairs", &fomac::Operation::getSitePairs, "Returns the list of site pairs the local 2-qubit operation " "can be performed on."); operation.def("mean_shuttling_speed", - &fomac::Session::Device::Operation::getMeanShuttlingSpeed, - "sites"_a.sig("...") = - std::vector{}, + &fomac::Operation::getMeanShuttlingSpeed, + "sites"_a.sig("...") = std::vector{}, "params"_a.sig("...") = std::vector{}, "Returns the mean shuttling speed of the operation."); - operation.def("__repr__", [](const fomac::Session::Device::Operation& op) { + operation.def("__repr__", [](const fomac::Operation& op) { return ""; }); @@ -436,7 +423,7 @@ All authentication parameters are optional and can be provided as keyword argume const std::optional& custom3 = std::nullopt, const std::optional& custom4 = std::nullopt, const std::optional& custom5 = - std::nullopt) -> fomac::Session::Device { + std::nullopt) -> fomac::Device { const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, .token = token, .authFile = authFile, @@ -450,7 +437,7 @@ All authentication parameters are optional and can be provided as keyword argume .custom5 = custom5}; auto* const qdmiDevice = qdmi::Driver::get().addDynamicDeviceLibrary( libraryPath, prefix, config); - return fomac::Session::Device::fromQDMIDevice(qdmiDevice); + return fomac::Session::createSessionlessDevice(qdmiDevice); }, "library_path"_a, "prefix"_a, nb::kw_only(), "base_url"_a = std::nullopt, "token"_a = std::nullopt, "auth_file"_a = std::nullopt, diff --git a/bindings/ir/register_quantum_computation.cpp b/bindings/ir/register_quantum_computation.cpp index 618d52e7e4..3dc239b511 100644 --- a/bindings/ir/register_quantum_computation.cpp +++ b/bindings/ir/register_quantum_computation.cpp @@ -2003,15 +2003,17 @@ This method is equivalent to calling :meth:`measure` multiple times. 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_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)pb"); + 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. diff --git a/bindings/na/register_fomac.cpp b/bindings/na/register_fomac.cpp index d70bc696ba..a35059e772 100644 --- a/bindings/na/register_fomac.cpp +++ b/bindings/na/register_fomac.cpp @@ -41,7 +41,7 @@ void registerFomac(nb::module_& m) { nb::module_::import_("mqt.core.fomac"); - auto device = nb::class_( + auto device = nb::class_( m, "Device", "Represents a device with a lattice of traps."); auto lattice = nb::class_( @@ -122,7 +122,7 @@ void registerFomac(nb::module_& m) { return dev.getDecoherenceTimes().t2; }, "The T2 time of the device."); - device.def("__repr__", [](const fomac::Session::Device& dev) { + device.def("__repr__", [](const fomac::Device& dev) { return ""; }); device.def_static("try_create_from_device", diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 37e646cd0c..40596ed145 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -86,9 +86,11 @@ if(BUILD_MQT_CORE_TESTS) endif() # cmake-format: off -set(QDMI_VERSION 1.3.0 +set(QDMI_MINIMUM_VERSION 1.3.0 + CACHE STRING "Minimum QDMI version") +set(QDMI_VERSION 1.3.2 CACHE STRING "QDMI version") -set(QDMI_REV "0f7e08c58b72800d1022a01cfb618af67b9a9c30" # v1.3.0 +set(QDMI_REV "d05a0b418f42e54e9585d2e00af8ce23e745fd83" # v1.3.2 CACHE STRING "QDMI identifier (tag, branch or commit hash)") set(QDMI_REPO_OWNER "Munich-Quantum-Software-Stack" CACHE STRING "QDMI repository owner (change when using a fork)") @@ -98,7 +100,7 @@ FetchContent_Declare( qdmi GIT_REPOSITORY https://github.com/${QDMI_REPO_OWNER}/qdmi.git GIT_TAG ${QDMI_REV} - FIND_PACKAGE_ARGS ${QDMI_VERSION}) + FIND_PACKAGE_ARGS ${QDMI_MINIMUM_VERSION}) list(APPEND FETCH_PACKAGES qdmi) set(SPDLOG_VERSION diff --git a/docs/qir/index.md b/docs/qir/index.md index 4f04280af1..8ddac20831 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -42,8 +42,16 @@ The `mqt-core-qir-runner` can be used to execute a QIR file (typically with a ./build/bin/mqt-core-qir-runner bell.ll ``` -This will simulate the circuit and print the measurement results to the console. -The runner supports the QIR Base Profile. +The runner prints the program's outputs to the console in one of the two +[QIR Output Schemas][output-schemas] (Labeled or Ordered): the two `HEADER` +records announce the schema, and each shot is wrapped in `START` and `END` +records with a `METADATA\toutput_labeling_schema\t` line inside. + +The active schema is selected by the `output_labeling_schema` function attribute +on the entry-point function of the QIR program. The value `ordered` selects +Ordered; anything else, or a missing attribute, selects Labeled. + +[output-schemas]: https://github.com/qir-alliance/qir-spec/tree/main/specification/output_schemas ### QIR Support in the DDSIM QDMI Device diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 48dbfa815e..127004e822 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -11,6 +11,7 @@ #pragma once #include "qdmi/common/Common.hpp" +#include "qdmi/types.h" #include @@ -19,18 +20,20 @@ #include #include #include -#include #include #include -#include +#include #include #include #include #include #include +#include #include namespace fomac { +using CustomJobParameter = std::variant; + /** * @brief Concept for ranges that are contiguous in memory and can be * constructed with a size. @@ -40,8 +43,7 @@ namespace fomac { */ template concept size_constructible_contiguous_range = - std::ranges::contiguous_range && - std::constructible_from && + std::ranges::contiguous_range && std::constructible_from && requires { typename T::value_type; } && requires(T t) { { t.data() } -> std::same_as; }; @@ -54,8 +56,8 @@ concept size_constructible_contiguous_range = */ template concept value_or_string = - std::integral || std::floating_point || std::is_same_v || - std::is_same_v || std::is_same_v; + std::integral || std::floating_point || std::same_as || + std::same_as || std::same_as; /** * @brief Concept for types that are either value_or_string or @@ -76,7 +78,7 @@ concept value_or_string_or_vector = */ template concept is_optional = requires { typename T::value_type; } && - std::is_same_v>; + std::same_as>; /** * @brief Concept for types that are either std::string or std::optional of @@ -87,8 +89,8 @@ concept is_optional = requires { typename T::value_type; } && */ template concept string_or_optional_string = - std::is_same_v || - (is_optional && std::is_same_v); + std::same_as || + (is_optional && std::same_as); /// @see remove_optional_t template struct remove_optional { @@ -106,8 +108,7 @@ template struct remove_optional> { * with the underlying type of optional without caring about its optionality. * @tparam T The type to strip optional from. */ -template -using remove_optional_t = typename remove_optional::type; +template using remove_optional_t = remove_optional::type; /** * @brief Concept for types that are either size_constructible_contiguous_range @@ -175,6 +176,11 @@ struct SessionConfig { std::optional custom5; }; +class Job; +class Site; +class Device; +class Operation; + /** * @brief Class representing the Session library. * @details This class provides methods to query available devices and @@ -182,553 +188,592 @@ struct SessionConfig { * @see QDMI_Session */ class Session { +public: /** - * @brief Private token class. - * @details Only the Session class can create instances of this class. + * @brief Creates a Device object from a QDMI_Device handle. + * @param device The QDMI_Device handle to wrap. + * @return A Device object wrapping the given handle. + * @note This is a factory method for use in bindings where a + * session is not accessible. + */ + [[nodiscard]] static Device createSessionlessDevice(QDMI_Device device); + + /** + * @brief Constructs a new QDMI Session with optional authentication. + * @param config Optional session configuration containing authentication + * parameters. If not provided, uses default (no authentication). + * @details Creates, allocates, and initializes a new QDMI session. */ - class Token { - public: - Token() = default; - }; + explicit Session(const SessionConfig& config = {}); + + /// @see QDMI_SESSION_PROPERTY_DEVICES + [[nodiscard]] std::vector getDevices(); + +private: + /// Query a session property. + template + [[nodiscard]] T queryProperty(const QDMI_Session_Property prop) const { + using StrippedValueType = remove_optional_t::value_type; + + size_t size = 0; + qdmi::throwIfError(QDMI_session_query_session_property(session_.get(), prop, + 0, nullptr, &size), + std::string("Querying size ") + qdmi::toString(prop)); + remove_optional_t value(size / sizeof(StrippedValueType)); + qdmi::throwIfError(QDMI_session_query_session_property( + session_.get(), prop, size, value.data(), nullptr), + std::string("Querying ") + qdmi::toString(prop)); + return value; + } + + std::unique_ptr session_{ + nullptr, QDMI_session_free}; +}; + +static_assert(!std::is_copy_constructible()); +static_assert(!std::is_copy_assignable()); +static_assert(std::is_move_constructible()); +static_assert(std::is_move_assignable()); +/** + * @brief Class representing a quantum device. + * @details + * This class provides methods to query properties of the device, + * its sites, and its operations. + * + * The class can only be constructed by Session instances. + * + * @see QDMI_Device + */ +class Device { public: + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) + operator QDMI_Device() const { return device_; } + + /// @see QDMI_DEVICE_PROPERTY_NAME + [[nodiscard]] std::string getName() const; + + /// @see QDMI_DEVICE_PROPERTY_VERSION + [[nodiscard]] std::string getVersion() const; + + /// @see QDMI_DEVICE_PROPERTY_STATUS + [[nodiscard]] QDMI_Device_Status getStatus() const; + + /// @see QDMI_DEVICE_PROPERTY_LIBRARYVERSION + [[nodiscard]] std::string getLibraryVersion() const; + + /// @see QDMI_DEVICE_PROPERTY_QUBITSNUM + [[nodiscard]] size_t getQubitsNum() const; + + /// @see QDMI_DEVICE_PROPERTY_SITES + [[nodiscard]] std::vector getSites() const; + /** - * @brief Class representing a submitted job. - * @details This class provides methods to query job status and retrieve - * results. - * @see QDMI_Job + * @brief Returns the list of regular sites (without zone sites) available + * on the device. + * @details Filters all sites and only returns regular sites, i.e., where + * `isZone()` yields `false`. These represent actual potential physical + * qubit locations on the device lattice. + * @returns vector of regular sites + * @see QDMI_DEVICE_PROPERTY_SITES */ - class Job { - QDMI_Job job_; - - public: - /** - * @brief Constructs a Job object from a QDMI_Job handle. - * @param job The QDMI_Job handle to wrap. - */ - explicit Job(QDMI_Job job) : job_(job) {} - /** - * @brief Destructor that releases the underlying QDMI_Job resource. - */ - ~Job() { - if (job_ != nullptr) { - QDMI_job_free(job_); - } - } - // Delete copy constructor and copy assignment operator to prevent - // pointer duplication and double-free - Job(const Job&) = delete; - Job& operator=(const Job&) = delete; - // Default move constructor and move assignment operator to allow - // safe ownership transfer - Job(Job&& other) noexcept : job_(other.job_) { other.job_ = nullptr; } - Job& operator=(Job&& other) noexcept { - if (this != &other) { - if (job_ != nullptr) { - QDMI_job_free(job_); - } - job_ = other.job_; - other.job_ = nullptr; - } - return *this; - } - /// @returns the underlying QDMI_Job object. - [[nodiscard]] auto getQDMIJob() const -> QDMI_Job { return job_; } - // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) - operator QDMI_Job() const { return job_; } - /// @see QDMI_job_check - [[nodiscard]] auto check() const -> QDMI_Job_Status; - /** - * @brief @see QDMI_job_wait - * @param timeout The maximum time to wait in seconds. 0 (default) means - * wait indefinitely. - * @return true if the job completed successfully, false if it timed out - */ - [[nodiscard]] auto wait(size_t timeout = 0) const -> bool; - /// @see QDMI_job_cancel - auto cancel() const -> void; - /// Get the job ID - [[nodiscard]] auto getId() const -> std::string; - /// Get the program format - [[nodiscard]] auto getProgramFormat() const -> QDMI_Program_Format; - /// Get the program to be executed - [[nodiscard]] auto getProgram() const -> std::string; - /// Get the number of shots - [[nodiscard]] auto getNumShots() const -> size_t; - /** - * @brief Returns the measurement shots as a vector of bitstrings. - * @see QDMI_JOB_RESULT_SHOTS - */ - [[nodiscard]] auto getShots() const -> std::vector; - /** - * @brief Returns a map of measurement outcomes to their respective counts. - * @see QDMI_JOB_RESULT_HIST_KEYS - * @see QDMI_JOB_RESULT_HIST_VALUES - */ - [[nodiscard]] auto getCounts() const -> std::map; - /** - * @brief Returns the dense state vector as a vector of complex numbers. - * @see QDMI_JOB_RESULT_STATEVECTOR_DENSE - */ - [[nodiscard]] auto getDenseStateVector() const - -> std::vector>; - /** - * @brief Returns the dense probabilities as a vector of doubles. - * @see QDMI_JOB_RESULT_PROBABILITIES_DENSE - */ - [[nodiscard]] auto getDenseProbabilities() const -> std::vector; - /** - * @brief Returns the sparse state vector as a map of bitstrings to complex - * amplitudes. - * @see QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS - * @see QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES - */ - [[nodiscard]] auto getSparseStateVector() const - -> std::map>; - /** - * @brief Returns the sparse probabilities as a map of bitstrings to - * probabilities. - * @see QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS - * @see QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES - */ - [[nodiscard]] auto getSparseProbabilities() const - -> std::map; - }; + [[nodiscard]] std::vector getRegularSites() const; /** - * @brief Class representing a quantum device. - * @details This class provides methods to query properties of the device, - * its sites, and its operations. - * @see QDMI_Device + * @brief Returns the list of zone sites (without regular sites) available + * on the device. + * @details Filters all sites and only returns zone sites, i.e., where + * `isZone()` yields `true`. These represent a zone, i.e., an extent where + * zoned operations can be performed, not individual qubit locations. + * @returns a vector of zone sites + * @see QDMI_DEVICE_PROPERTY_SITES */ - class Device { - /** - * @brief Private token class. - * @details Only the Device class can create instances of this class. - */ - class Token { - public: - Token() = default; - }; + [[nodiscard]] std::vector getZones() const; + + /// @see QDMI_DEVICE_PROPERTY_OPERATIONS + [[nodiscard]] std::vector getOperations() const; + + /// @see QDMI_DEVICE_PROPERTY_COUPLINGMAP + [[nodiscard]] std::optional>> + getCouplingMap() const; + + /// @see QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION + [[nodiscard]] std::optional getNeedsCalibration() const; - public: - /** - * @brief Class representing a site (qubit) on the device. - * @details This class provides methods to query properties of the site. - * @see QDMI_Site - */ - class Site { - /// @brief The associated QDMI_Device object. - QDMI_Device device_; - /// @brief The underlying QDMI_Site object. - QDMI_Site site_; - - template - [[nodiscard]] auto queryProperty(const QDMI_Site_Property prop) const - -> T { - std::string msg = "Querying "; - msg += qdmi::toString(prop); - if constexpr (string_or_optional_string) { - size_t size = 0; - auto result = QDMI_device_query_site_property(device_, site_, prop, 0, - nullptr, &size); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - std::string value(size - 1, '\0'); - result = QDMI_device_query_site_property(device_, site_, prop, size, - value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } else { - remove_optional_t value{}; - const auto result = QDMI_device_query_site_property( - device_, site_, prop, sizeof(remove_optional_t), &value, - nullptr); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - return value; + /// @see QDMI_DEVICE_PROPERTY_LENGTHUNIT + [[nodiscard]] std::optional getLengthUnit() const; + + /// @see QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR + [[nodiscard]] std::optional getLengthScaleFactor() const; + + /// @see QDMI_DEVICE_PROPERTY_DURATIONUNIT + [[nodiscard]] std::optional getDurationUnit() const; + + /// @see QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR + [[nodiscard]] std::optional getDurationScaleFactor() const; + + /// @see QDMI_DEVICE_PROPERTY_MINATOMDISTANCE + [[nodiscard]] std::optional getMinAtomDistance() const; + + /// @see QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS + [[nodiscard]] std::vector + getSupportedProgramFormats() const; + + /// @see QDMI_job_submit + [[nodiscard]] Job submitJob( + const std::string& program, QDMI_Program_Format format, size_t numShots, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) const; + + auto operator<=>(const Device&) const noexcept = default; + +private: + /** + * @brief Constructs a Device object from a QDMI_Device handle. + * @param device The QDMI_Device handle to wrap. + */ + explicit Device(QDMI_Device device) : device_(device) {} + + /// Query a device property. + template + [[nodiscard]] T queryProperty(const QDMI_Device_Property prop) const { + std::string msg = "Querying "; + msg += qdmi::toString(prop); + + if constexpr (string_or_optional_string) { + size_t size = 0; + auto result = + QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); + + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; } } - public: - /** - * @brief Constructs a Site object from a QDMI_Site handle. - * @param device The associated QDMI_Device handle. - * @param site The QDMI_Site handle to wrap. - */ - Site(Token /* unused */, QDMI_Device device, QDMI_Site site) - : device_(device), site_(site) {} - /// @returns the underlying QDMI_Site object. - [[nodiscard]] auto getQDMISite() const -> QDMI_Site { return site_; } - // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) - operator QDMI_Site() const { return site_; } - auto operator<=>(const Site&) const = default; - /// @see QDMI_SITE_PROPERTY_INDEX - [[nodiscard]] auto getIndex() const -> size_t; - /// @see QDMI_SITE_PROPERTY_T1 - [[nodiscard]] auto getT1() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_T2 - [[nodiscard]] auto getT2() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_NAME - [[nodiscard]] auto getName() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_XCOORDINATE - [[nodiscard]] auto getXCoordinate() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_YCOORDINATE - [[nodiscard]] auto getYCoordinate() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_ZCOORDINATE - [[nodiscard]] auto getZCoordinate() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_ISZONE - [[nodiscard]] auto isZone() const -> bool; - /// @see QDMI_SITE_PROPERTY_XEXTENT - [[nodiscard]] auto getXExtent() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_YEXTENT - [[nodiscard]] auto getYExtent() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_ZEXTENT - [[nodiscard]] auto getZExtent() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_MODULEINDEX - [[nodiscard]] auto getModuleIndex() const -> std::optional; - /// @see QDMI_SITE_PROPERTY_SUBMODULEINDEX - [[nodiscard]] auto getSubmoduleIndex() const -> std::optional; - }; - /** - * @brief Class representing an operation (gate) supported by the device. - * @details This class provides methods to query properties of the - * operation. - * @see QDMI_Operation - */ - class Operation { - /// @brief The associated QDMI_Device object. - QDMI_Device device_; - /// @brief The underlying QDMI_Operation object. - QDMI_Operation operation_; - - template - [[nodiscard]] auto queryProperty(const QDMI_Operation_Property prop, - const std::vector& sites, - const std::vector& params) const - -> T { - std::string msg = "Querying "; - msg += qdmi::toString(prop); - std::vector qdmiSites; - qdmiSites.reserve(sites.size()); - std::ranges::transform( - sites, std::back_inserter(qdmiSites), - [](const Site& site) -> QDMI_Site { return site; }); - if constexpr (string_or_optional_string) { - size_t size = 0; - auto result = QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, 0, nullptr, &size); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - std::string value(size - 1, '\0'); - result = QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } else if constexpr (maybe_optional_size_constructible_contiguous_range< - T>) { - size_t size = 0; - auto result = QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, 0, nullptr, &size); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - remove_optional_t value( - size / sizeof(typename remove_optional_t::value_type)); - result = QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } else { - remove_optional_t value{}; - const auto result = QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, sizeof(remove_optional_t), - &value, nullptr); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - return value; + qdmi::throwIfError(result, msg); + std::string value(size - 1, '\0'); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); + return value; + } else if constexpr (maybe_optional_size_constructible_contiguous_range< + T>) { + size_t size = 0; + auto result = + QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); + + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; } } - public: - /** - * @brief Constructs an Operation object from a QDMI_Operation handle. - * @param device The associated QDMI_Device handle. - * @param operation The QDMI_Operation handle to wrap. - */ - Operation(Token /* unused */, QDMI_Device device, - QDMI_Operation operation) - : device_(device), operation_(operation) {} - /// @returns the underlying QDMI_Operation object. - [[nodiscard]] auto getQDMIOperation() const -> QDMI_Operation { - return operation_; + qdmi::throwIfError(result, msg); + remove_optional_t value( + size / sizeof(typename remove_optional_t::value_type)); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); + return value; + } else { + remove_optional_t value{}; + const auto result = QDMI_device_query_device_property( + device_, prop, sizeof(remove_optional_t), &value, nullptr); + + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; + } } - // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) - operator QDMI_Operation() const { return operation_; } - auto operator<=>(const Operation&) const = default; - /// @see QDMI_OPERATION_PROPERTY_NAME - [[nodiscard]] auto getName(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::string; - /// @see QDMI_OPERATION_PROPERTY_QUBITSNUM - [[nodiscard]] auto - getQubitsNum(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_PARAMETERSNUM - [[nodiscard]] auto - getParametersNum(const std::vector& sites = {}, - const std::vector& params = {}) const -> size_t; - /// @see QDMI_OPERATION_PROPERTY_DURATION - [[nodiscard]] auto - getDuration(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_FIDELITY - [[nodiscard]] auto - getFidelity(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS - [[nodiscard]] auto - getInteractionRadius(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS - [[nodiscard]] auto - getBlockingRadius(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_IDLINGFIDELITY - [[nodiscard]] auto - getIdlingFidelity(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - /// @see QDMI_OPERATION_PROPERTY_ISZONED - [[nodiscard]] auto isZoned() const -> bool; - /// @see QDMI_OPERATION_PROPERTY_SITES - [[nodiscard]] auto getSites() const -> std::optional>; - /** - * @brief Returns the list of site pairs the local 2-qubit operation can - * be performed on. - * @details For local 2-qubit operations, this function interprets the - * returned list of sites by QDMI as site pairs according to the QDMI - * specification. Hence, this function facilitates easier iteration over - * supported site pairs. - * @return Optional vector of site pairs if this is a local 2-qubit - * operation, std::nullopt otherwise. - * @see QDMI_OPERATION_PROPERTY_SITES - */ - [[nodiscard]] auto getSitePairs() const - -> std::optional>>; - /// @see QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED - [[nodiscard]] auto - getMeanShuttlingSpeed(const std::vector& sites = {}, - const std::vector& params = {}) const - -> std::optional; - }; - private: - /// @brief The underlying QDMI_Device object. - QDMI_Device device_; - - template - [[nodiscard]] auto queryProperty(const QDMI_Device_Property prop) const - -> T { - std::string msg = "Querying "; - msg += qdmi::toString(prop); - if constexpr (string_or_optional_string) { - size_t size = 0; - auto result = - QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } - } - qdmi::throwIfError(result, msg); - std::string value(size - 1, '\0'); - result = QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } else if constexpr (maybe_optional_size_constructible_contiguous_range< - T>) { - size_t size = 0; - auto result = - QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } + qdmi::throwIfError(result, msg); + return value; + } + } + + static void setCustomJobParam(QDMI_Job job, QDMI_Job_Parameter param, + const CustomJobParameter& value); + + /// @brief The underlying device pointer. + QDMI_Device device_; + + friend class Session; +}; + +/** + * @brief Class representing a submitted job. + * @details + * This class provides methods to query job status and retrieve + * results. + * + * The class can only be constructed by Device instances. + * + * @see QDMI_Job + */ +class Job { +public: + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) + operator QDMI_Job() const { return job_.get(); } + + /// @see QDMI_job_check + [[nodiscard]] QDMI_Job_Status check() const; + + /** + * @brief @see QDMI_job_wait + * @param timeout The maximum time to wait in seconds. 0 (default) means + * wait indefinitely. + * @return true if the job completed successfully, false if it timed out + */ + [[nodiscard]] bool wait(size_t timeout = 0) const; + + /// @see QDMI_job_cancel + void cancel() const; + + /// Get the job ID + [[nodiscard]] std::string getId() const; + + /// Get the program format + [[nodiscard]] QDMI_Program_Format getProgramFormat() const; + + /// Get the program to be executed + [[nodiscard]] std::string getProgram() const; + + /// Get the number of shots + [[nodiscard]] size_t getNumShots() const; + + /** + * @brief Returns the measurement shots as a vector of bitstrings. + * @see QDMI_JOB_RESULT_SHOTS + */ + [[nodiscard]] std::vector getShots() const; + + /** + * @brief Returns a map of measurement outcomes to their respective counts. + * @see QDMI_JOB_RESULT_HIST_KEYS + * @see QDMI_JOB_RESULT_HIST_VALUES + */ + [[nodiscard]] std::map getCounts() const; + + /** + * @brief Returns the dense state vector as a vector of complex numbers. + * @see QDMI_JOB_RESULT_STATEVECTOR_DENSE + */ + [[nodiscard]] std::vector> getDenseStateVector() const; + + /** + * @brief Returns the dense probabilities as a vector of doubles. + * @see QDMI_JOB_RESULT_PROBABILITIES_DENSE + */ + [[nodiscard]] std::vector getDenseProbabilities() const; + + /** + * @brief Returns the sparse state vector as a map of bitstrings to complex + * amplitudes. + * @see QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS + * @see QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES + */ + [[nodiscard]] std::map> + getSparseStateVector() const; + + /** + * @brief Returns the sparse probabilities as a map of bitstrings to + * probabilities. + * @see QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS + * @see QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES + */ + [[nodiscard]] std::map getSparseProbabilities() const; + + auto operator<=>(const Job&) const noexcept = default; + +private: + /** + * @brief Constructs a Job object from a QDMI_Job handle. + * @param job The QDMI_Job handle to wrap. + */ + explicit Job(QDMI_Job job) : job_(job, QDMI_job_free) {} + + std::unique_ptr job_{ + nullptr, QDMI_job_free}; + + friend class Device; +}; + +static_assert(!std::is_copy_constructible()); +static_assert(!std::is_copy_assignable()); +static_assert(std::is_move_constructible()); +static_assert(std::is_move_assignable()); + +/** + * @brief Class representing a site (qubit) on the device. + * @details + * This class provides methods to query properties of the site. + * + * The class can only be constructed by Device and Operation instances. + * + * @see QDMI_Site + */ +class Site { +public: + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) + operator QDMI_Site() const { return site_; } + + /// @see QDMI_SITE_PROPERTY_INDEX + [[nodiscard]] size_t getIndex() const; + + /// @see QDMI_SITE_PROPERTY_T1 + [[nodiscard]] std::optional getT1() const; + + /// @see QDMI_SITE_PROPERTY_T2 + [[nodiscard]] std::optional getT2() const; + + /// @see QDMI_SITE_PROPERTY_NAME + [[nodiscard]] std::optional getName() const; + + /// @see QDMI_SITE_PROPERTY_XCOORDINATE + [[nodiscard]] std::optional getXCoordinate() const; + + /// @see QDMI_SITE_PROPERTY_YCOORDINATE + [[nodiscard]] std::optional getYCoordinate() const; + + /// @see QDMI_SITE_PROPERTY_ZCOORDINATE + [[nodiscard]] std::optional getZCoordinate() const; + + /// @see QDMI_SITE_PROPERTY_ISZONE + [[nodiscard]] bool isZone() const; + + /// @see QDMI_SITE_PROPERTY_XEXTENT + [[nodiscard]] std::optional getXExtent() const; + + /// @see QDMI_SITE_PROPERTY_YEXTENT + [[nodiscard]] std::optional getYExtent() const; + + /// @see QDMI_SITE_PROPERTY_ZEXTENT + [[nodiscard]] std::optional getZExtent() const; + + /// @see QDMI_SITE_PROPERTY_MODULEINDEX + [[nodiscard]] std::optional getModuleIndex() const; + + /// @see QDMI_SITE_PROPERTY_SUBMODULEINDEX + [[nodiscard]] std::optional getSubmoduleIndex() const; + + auto operator<=>(const Site&) const noexcept = default; + +private: + /** + * @brief Constructs a Site object from a QDMI_Site handle. + * @param device The device that owns the site. + * @param site The QDMI_Site handle to wrap. + */ + Site(const Device* device, QDMI_Site site) : device_(device), site_(site) {} + + /// Query a site property. + template + [[nodiscard]] T queryProperty(const QDMI_Site_Property prop) const { + if constexpr (string_or_optional_string) { + size_t size = 0; + const auto result = QDMI_device_query_site_property(*device_, site_, prop, + 0, nullptr, &size); + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; } - qdmi::throwIfError(result, msg); - remove_optional_t value( - size / sizeof(typename remove_optional_t::value_type)); - result = QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } else { - remove_optional_t value{}; - const auto result = QDMI_device_query_device_property( - device_, prop, sizeof(remove_optional_t), &value, nullptr); - if constexpr (is_optional) { - if (result == QDMI_ERROR_NOTSUPPORTED) { - return std::nullopt; - } + } + qdmi::throwIfError(result, + std::string("Querying size") + qdmi::toString(prop)); + std::string value(size - 1, '\0'); + qdmi::throwIfError(QDMI_device_query_site_property(*device_, site_, prop, + size, value.data(), + nullptr), + std::string("Querying ") + qdmi::toString(prop)); + return value; + } else { + remove_optional_t value{}; + const auto result = QDMI_device_query_site_property( + *device_, site_, prop, sizeof(remove_optional_t), &value, nullptr); + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; } - qdmi::throwIfError(result, msg); - return value; } + qdmi::throwIfError(result, + std::string("Querying ") + qdmi::toString(prop)); + return value; } + } - public: - /** - * @brief Constructs a Device object from a QDMI_Device handle. - * @param device The QDMI_Device handle to wrap. - */ - Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {} - /** - * @brief Creates a Device object from a QDMI_Device handle. - * @param device The QDMI_Device handle to wrap. - * @return A Device object wrapping the given handle. - * @note This is a factory method for use in bindings where Token - * construction is not accessible. - */ - [[nodiscard]] static auto fromQDMIDevice(QDMI_Device device) -> Device { - return Device(Session::Token{}, device); - } - /// @returns the underlying QDMI_Device object. - [[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; } - // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) - operator QDMI_Device() const { return device_; } - auto operator<=>(const Device&) const = default; - /// @see QDMI_DEVICE_PROPERTY_NAME - [[nodiscard]] auto getName() const -> std::string; - /// @see QDMI_DEVICE_PROPERTY_VERSION - [[nodiscard]] auto getVersion() const -> std::string; - /// @see QDMI_DEVICE_PROPERTY_STATUS - [[nodiscard]] auto getStatus() const -> QDMI_Device_Status; - /// @see QDMI_DEVICE_PROPERTY_LIBRARYVERSION - [[nodiscard]] auto getLibraryVersion() const -> std::string; - /// @see QDMI_DEVICE_PROPERTY_QUBITSNUM - [[nodiscard]] auto getQubitsNum() const -> size_t; - /// @see QDMI_DEVICE_PROPERTY_SITES - [[nodiscard]] auto getSites() const -> std::vector; - /** - * @brief Returns the list of regular sites (without zone sites) available - * on the device. - * @details Filters all sites and only returns regular sites, i.e., where - * `isZone()` yields `false`. These represent actual potential physical - * qubit locations on the device lattice. - * @returns vector of regular sites - * @see QDMI_DEVICE_PROPERTY_SITES - */ - [[nodiscard]] auto getRegularSites() const -> std::vector; - /** - * @brief Returns the list of zone sites (without regular sites) available - * on the device. - * @details Filters all sites and only returns zone sites, i.e., where - * `isZone()` yields `true`. These represent a zone, i.e., an extent where - * zoned operations can be performed, not individual qubit locations. - * @returns a vector of zone sites - * @see QDMI_DEVICE_PROPERTY_SITES - */ - [[nodiscard]] auto getZones() const -> std::vector; - /// @see QDMI_DEVICE_PROPERTY_OPERATIONS - [[nodiscard]] auto getOperations() const -> std::vector; - /// @see QDMI_DEVICE_PROPERTY_COUPLINGMAP - [[nodiscard]] auto getCouplingMap() const - -> std::optional>>; - /// @see QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION - [[nodiscard]] auto getNeedsCalibration() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_LENGTHUNIT - [[nodiscard]] auto getLengthUnit() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR - [[nodiscard]] auto getLengthScaleFactor() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_DURATIONUNIT - [[nodiscard]] auto getDurationUnit() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR - [[nodiscard]] auto getDurationScaleFactor() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_MINATOMDISTANCE - [[nodiscard]] auto getMinAtomDistance() const -> std::optional; - /// @see QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS - [[nodiscard]] auto getSupportedProgramFormats() const - -> std::vector; - /// @see QDMI_job_submit - [[nodiscard]] auto submitJob(const std::string& program, - QDMI_Program_Format format, - size_t numShots) const -> Job; - }; + /// @brief A pointer to the device that owns the site. + Device const* device_; -private: - QDMI_Session session_ = nullptr; + /// @brief The underlying QDMI_Site object. + QDMI_Site site_; - template - [[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const - -> T { - std::string msg = "Querying "; - msg += qdmi::toString(prop); - size_t size = 0; - auto result = - QDMI_session_query_session_property(session_, prop, 0, nullptr, &size); - qdmi::throwIfError(result, msg); - remove_optional_t value( - size / sizeof(typename remove_optional_t::value_type)); - result = QDMI_session_query_session_property(session_, prop, size, - value.data(), nullptr); - qdmi::throwIfError(result, msg); - return value; - } + friend class Device; + friend class Operation; +}; +/** + * @brief Class representing an operation (gate) supported by the device. + * @details + * This class provides methods to query properties of the + * operation. + * + * The class can only be constructed by Device instances. + * + * @see QDMI_Operation + */ +class Operation { public: + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) + operator QDMI_Operation() const { return operation_; } + + /// @see QDMI_OPERATION_PROPERTY_NAME + [[nodiscard]] std::string + getName(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_QUBITSNUM + [[nodiscard]] std::optional + getQubitsNum(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_PARAMETERSNUM + [[nodiscard]] size_t + getParametersNum(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_DURATION + [[nodiscard]] std::optional + getDuration(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_FIDELITY + [[nodiscard]] std::optional + getFidelity(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS + [[nodiscard]] std::optional + getInteractionRadius(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS + [[nodiscard]] std::optional + getBlockingRadius(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_IDLINGFIDELITY + [[nodiscard]] std::optional + getIdlingFidelity(const std::vector& sites = {}, + const std::vector& params = {}) const; + + /// @see QDMI_OPERATION_PROPERTY_ISZONED + [[nodiscard]] bool isZoned() const; + + /// @see QDMI_OPERATION_PROPERTY_SITES + [[nodiscard]] std::optional> getSites() const; + /** - * @brief Constructs a new QDMI Session with optional authentication. - * @param config Optional session configuration containing authentication - * parameters. If not provided, uses default (no authentication). - * @details Creates, allocates, and initializes a new QDMI session. + * @brief Returns the list of site pairs the local 2-qubit operation can + * be performed on. + * @details For local 2-qubit operations, this function interprets the + * returned list of sites by QDMI as site pairs according to the QDMI + * specification. Hence, this function facilitates easier iteration over + * supported site pairs. + * @return Optional vector of site pairs if this is a local 2-qubit + * operation, std::nullopt otherwise. + * @see QDMI_OPERATION_PROPERTY_SITES */ - explicit Session(const SessionConfig& config = {}); + [[nodiscard]] std::optional>> + getSitePairs() const; + + /// @see QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED + [[nodiscard]] std::optional + getMeanShuttlingSpeed(const std::vector& sites = {}, + const std::vector& params = {}) const; + + auto operator<=>(const Operation&) const noexcept = default; +private: /** - * @brief Destructor that releases the QDMI session. + * @brief Constructs an Operation object from a QDMI_Operation handle. + * @param device The device that owns the site. + * @param operation The QDMI_Operation handle to wrap. */ - ~Session(); + Operation(const Device* device, QDMI_Operation operation) + : device_(device), operation_(operation) {} + + /// Query an operation property. + template + [[nodiscard]] T queryProperty(const QDMI_Operation_Property prop, + const std::vector& sites, + const std::vector& params) const { + std::string msg = "Querying "; + msg += qdmi::toString(prop); + std::vector qdmiSites; + qdmiSites.reserve(sites.size()); + std::ranges::transform(sites, std::back_inserter(qdmiSites), + [](const Site& site) -> QDMI_Site { return site; }); + if constexpr (string_or_optional_string) { + size_t size = 0; + auto result = QDMI_device_query_operation_property( + *device_, operation_, sites.size(), qdmiSites.data(), params.size(), + params.data(), prop, 0, nullptr, &size); + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; + } + } + qdmi::throwIfError(result, msg); + std::string value(size - 1, '\0'); + result = QDMI_device_query_operation_property( + *device_, operation_, sites.size(), qdmiSites.data(), params.size(), + params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); + return value; + } else if constexpr (maybe_optional_size_constructible_contiguous_range< + T>) { + size_t size = 0; + auto result = QDMI_device_query_operation_property( + *device_, operation_, sites.size(), qdmiSites.data(), params.size(), + params.data(), prop, 0, nullptr, &size); + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; + } + } + qdmi::throwIfError(result, msg); + remove_optional_t value( + size / sizeof(typename remove_optional_t::value_type)); + result = QDMI_device_query_operation_property( + *device_, operation_, sites.size(), qdmiSites.data(), params.size(), + params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); + return value; + } else { + remove_optional_t value{}; + const auto result = QDMI_device_query_operation_property( + *device_, operation_, sites.size(), qdmiSites.data(), params.size(), + params.data(), prop, sizeof(remove_optional_t), &value, nullptr); + if constexpr (is_optional) { + if (result == QDMI_ERROR_NOTSUPPORTED) { + return std::nullopt; + } + } + qdmi::throwIfError(result, msg); + return value; + } + } - // Delete copy constructors and assignment operators - Session(const Session&) = delete; - Session& operator=(const Session&) = delete; + /// @brief A pointer to the device that owns the operation. + Device const* device_; - // Allow move semantics - Session(Session&&) noexcept; - Session& operator=(Session&&) noexcept; + /// @brief The underlying QDMI_Operation object. + QDMI_Operation operation_; - /// @see QDMI_SESSION_PROPERTY_DEVICES - [[nodiscard]] auto getDevices() -> std::vector; + friend class Device; }; } // namespace fomac diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 68f2e295b1..92e426f982 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -322,11 +322,12 @@ class QuantumComputation { /** * @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); + void measureAll(bool addBits = true, bool addBarrier = true); void bridge(const Targets& targets); diff --git a/include/mqt-core/na/fomac/Device.hpp b/include/mqt-core/na/fomac/Device.hpp index 1a6a5e53ab..e5698661d4 100644 --- a/include/mqt-core/na/fomac/Device.hpp +++ b/include/mqt-core/na/fomac/Device.hpp @@ -24,7 +24,7 @@ namespace na { * @brief Class representing the Session library with neutral atom extensions. * @see fomac::Session */ -class Session : public fomac::Session { +class Session { public: /** * @brief Class representing a quantum device with neutral atom extensions. @@ -32,7 +32,7 @@ class Session : public fomac::Session { * @note Since it inherits from @ref na::Device, Device objects can be * converted to `nlohmann::json` objects. */ - class Device : public fomac::Session::Device, na::Device { + class Device : public fomac::Device, na::Device { /** * @brief Initializes the name from the underlying QDMI device. @@ -86,7 +86,8 @@ class Session : public fomac::Session { * class. For their initialization, the corresponding `init*FromDevice` * methods must be called, see @ref tryCreateFromDevice. */ - explicit Device(const fomac::Session::Device& device); + explicit Device(const fomac::Device& device) + : fomac::Device(device), na::Device() {}; public: /// @returns the length unit of the device. @@ -120,8 +121,7 @@ class Session : public fomac::Session { * @return An optional containing the instantiated device if compatible, * std::nullopt otherwise. */ - [[nodiscard]] static auto - tryCreateFromDevice(const fomac::Session::Device& device) + [[nodiscard]] static auto tryCreateFromDevice(const fomac::Device& device) -> std::optional { Device d(device); // The sequence of the following method calls does not matter. diff --git a/include/mqt-core/qasm3/Parser.hpp b/include/mqt-core/qasm3/Parser.hpp index b93f679945..72831862da 100644 --- a/include/mqt-core/qasm3/Parser.hpp +++ b/include/mqt-core/qasm3/Parser.hpp @@ -114,7 +114,7 @@ class Parser final { std::shared_ptr parseBarrierStatement(); - std::shared_ptr parseDeclaration(bool isConst); + std::shared_ptr parseDeclaration(bool isConst, bool isOutput); std::shared_ptr parseGateDefinition(); diff --git a/include/mqt-core/qasm3/Statement.hpp b/include/mqt-core/qasm3/Statement.hpp index 258c7a7900..31f09b5ccb 100644 --- a/include/mqt-core/qasm3/Statement.hpp +++ b/include/mqt-core/qasm3/Statement.hpp @@ -347,15 +347,18 @@ class DeclarationStatement final 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, - std::shared_ptr ty, std::string id, + bool declIsOutput, std::shared_ptr ty, + std::string id, std::shared_ptr expr) - : Statement(std::move(debug)), isConst(declIsConst), type(ty), - identifier(std::move(id)), expression(std::move(expr)) {} + : Statement(std::move(debug)), isConst(declIsConst), + isOutput(declIsOutput), type(ty), identifier(std::move(id)), + expression(std::move(expr)) {} void accept(InstVisitor* visitor) override; }; diff --git a/include/mqt-core/qir/runtime/Runtime.hpp b/include/mqt-core/qir/runtime/Runtime.hpp index 2ca9d5639a..756ff1fe1d 100644 --- a/include/mqt-core/qir/runtime/Runtime.hpp +++ b/include/mqt-core/qir/runtime/Runtime.hpp @@ -214,7 +214,7 @@ class Runtime { /// If @c dd is currently populated, the existing package's `decRef` plus /// `garbageCollect` path is used so the package (and its internal caches) /// is kept warm. - /// If @c dd was moved out (e.g. by @ref Runtime::takeState), a new package + /// If @c dd was moved out (e.g., by @ref Runtime::takeState), a new package /// is allocated. auto reset() -> void { if (dd) { @@ -228,6 +228,8 @@ class Runtime { } }; + enum class OutputSchema : uint8_t { Labeled, Ordered }; + private: static constexpr uintptr_t MIN_DYN_QUBIT_ADDRESS = 0x10000; enum class AddressMode : uint8_t { UNKNOWN, DYNAMIC, STATIC }; @@ -245,6 +247,9 @@ class Runtime { QState qState; std::mt19937_64 mt; std::ostream* os = &std::cout; + // The QIR spec does not define a default output schema. + // The runtime picks @c Labeled when a program doesn't declare one. + OutputSchema outputSchema = OutputSchema::Labeled; Runtime(); explicit Runtime(uint64_t randomSeed); @@ -290,6 +295,13 @@ class Runtime { return {targets, Op, paramVec}; } + // Helper function to output a type (bool, int...) to @c os, honoring the + // active @c outputSchema. + // The label is included only in Labeled mode. + // Tab separator between fields, newline at end. + void outputType(const char* type, std::string_view value, + const char* label) const; + public: [[nodiscard]] static auto generateRandomSeed() -> uint64_t; static Runtime& getInstance(); @@ -398,12 +410,6 @@ class Runtime { /// @returns the accumulated measurement string. auto getMeasurements() const -> const std::string&; - /// Emit `label:\n` to the output stream. - auto outputContainer(int64_t elementCount, const char* label) const -> void; - - /// Emit `label: valueStr\n` to the output stream. - auto outputValue(std::string_view valueStr, const char* label) const -> void; - /// Move the quantum state out of the runtime. /// Then reset the runtime to a clean state ready for the next job. /// Intended for use after a @c JitSession constructed with @@ -414,6 +420,44 @@ class Runtime { auto getOstream() const -> std::ostream&; auto setOstream(std::ostream& other) -> void; auto resetOstream() -> void; + + /// Emit `OUTPUT\tRESULT\t<0|1>[\tlabel]\n` to the output stream. + auto outputResult(bool value, const char* label) const -> void; + + /// Emit `OUTPUT\tBOOL\t[\tlabel]\n` to the output stream. + auto outputBool(bool value, const char* label) const -> void; + + /// Emit `OUTPUT\tINT\t[\tlabel]\n` to the output stream. + auto outputInt(int64_t value, const char* label) const -> void; + + /// Emit `OUTPUT\tDOUBLE\t[\tlabel]\n` to the output stream. + auto outputFloat(double value, const char* label) const -> void; + + /// Emit `OUTPUT\tTUPLE\t[\tlabel]\n` to the output stream. + auto outputTuple(int64_t elementCount, const char* label) const -> void; + + /// Emit `OUTPUT\tARRAY\t[\tlabel]\n` to the output stream. + auto outputArray(int64_t elementCount, const char* label) const -> void; + + /// Emit the HEADER records (once per submitted program): + /// `HEADER\tschema_id\t` + /// `HEADER\tschema_version\t2.1` + auto outputProgramHeader() const -> void; + + /// Emit `START\n` followed by + /// `METADATA\toutput_labeling_schema\t\n` (one per shot). + auto outputShotStart() const -> void; + + /// Emit `END\t0\n` (one per shot). + /// The trailing `0` is a spec literal, not a runtime exit code. + auto outputShotEnd() const -> void; + + [[nodiscard]] auto getOutputSchema() const -> OutputSchema; + auto setOutputSchema(OutputSchema schema) -> void; }; +/// Write the schema's spec-mandated literal (`labeled` or `ordered`). +auto operator<<(std::ostream& os, Runtime::OutputSchema schema) + -> std::ostream&; + } // namespace qir diff --git a/mlir/include/mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.h b/mlir/include/mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.h index 2434359f3a..ce1fcde8dc 100644 --- a/mlir/include/mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.h +++ b/mlir/include/mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.h @@ -67,6 +67,15 @@ struct LoweringState { Block* measurementsBlock{}; Block* outputBlock{}; + /// Set of MeasureOps whose results should be recorded in the output. + DenseSet returnedMeasurements; + + /// Set of array register names that should be recorded in the output. + DenseSet recordedArrays; + + /// Set of unnamed result indices that should be recorded in the output. + DenseSet recordedIndices; + /// The qubit allocation mode used in the module AllocationMode allocationMode = AllocationMode::Unset; @@ -152,4 +161,28 @@ void populateQCToQIRPatterns(RewritePatternSet& patterns, void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, LoweringState& state); +/** + * @brief Strips returned measurement results from function return statements + * + * @details + * Walks all `func::ReturnOp` operations in the module to identify operands + * that are directly defined by a `qc::MeasureOp`. For each such operand: + * - The defining `MeasureOp` is added to `state.returnedMeasurements` so that + * it will be included in the QIR output recording. + * - The operand is removed from the return statement. + * + * Non-measurement return values are preserved. After stripping, the enclosing + * `func::FuncOp` function type is updated to match the new return operands. + * + * This must be called **before** func-to-LLVM conversion, while + * `func::ReturnOp` and `qc::MeasureOp` are still in the IR. + * + * Return values that are indirectly computed from measurement outcomes remain + * unaffected. + * + * @param moduleOp The top-level module operation to walk + * @param state The lowering state; `returnedMeasurements` is populated + */ +void stripReturnedMeasurements(Operation* moduleOp, LoweringState& state); + } // namespace mlir diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index ed94af35bc..dee7248db0 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -70,7 +70,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { //===--------------------------------------------------------------------===// /** - * @brief Initialize the builder and prepare for program construction + * @brief Initialize the builder and prepare for program construction, with + * a default return type of i64. * * @details * Creates a main function with an entry_point attribute. Must be called @@ -78,6 +79,23 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { */ void initialize(); + /** + * @brief Initialize the builder and prepare for program construction + * with specified return types. + * @param returnTypes The return types for the main function + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(TypeRange returnTypes); + + /** + * @brief Modify the return types of the main function after initialization. + * @param returnTypes The new return types for the main function + */ + void retype(TypeRange returnTypes); + //===--------------------------------------------------------------------===// // Constants //===--------------------------------------------------------------------===// @@ -922,6 +940,42 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { QCProgramBuilder& ctrl(ValueRange controls, ValueRange targets, const function_ref& body); + /** + * @brief Apply a control modifier with a single target and one-qubit body. + * + * @param controls Control qubits + * @param target Target qubit + * @param body Function that builds the body containing the target operation + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctrl({q0_in, q1_in}, q2_in, [&](Value target) { + * builder.x(target); + * }); + * ``` + */ + QCProgramBuilder& ctrl(ValueRange controls, Value target, + const function_ref& body); + + /** + * @brief Apply a control modifier with one control and one target. + * + * @param control Control qubit + * @param target Target qubit + * @param body Function that builds the body containing the target operation + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctrl(q0_in, q1_in, [&](Value target) { + * builder.x(target); + * }); + * ``` + */ + QCProgramBuilder& ctrl(Value control, Value target, + const function_ref& body); + /** * @brief Apply an inverse (i.e., adjoint) operation. * @@ -944,6 +998,23 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { QCProgramBuilder& inv(ValueRange qubits, const function_ref& body); + /** + * @brief Apply an inverse modifier on a single qubit. + * + * @param qubit Qubit involved in the operation + * @param body Function that builds the body containing the operation to + * invert + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.inv(q0_in, [&](Value qubit) { + * builder.h(qubit); + * }); + * ``` + */ + QCProgramBuilder& inv(Value qubit, const function_ref& body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// @@ -1092,17 +1163,49 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { OwningOpRef finalize(); /** - * @brief Convenience method for building quantum programs + * @brief Finalize the program with the given return values and return the + * constructed module + * @param returnValues Values representing the return values of the main + * function. + * + * @details + * Automatically deallocates all remaining valid qubits and tensors of qubits, + * adds a return statement with the given return values, and + * transfers ownership of the module to the caller. The builder should not + * be used after calling this method. + * + * The return values must have the types indicated by the function signature + * of the main function, which returns an `i64` by default and can be + * modified by passing different arguments to the `initialize()` method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(ValueRange returnValues); + + /** + * @brief Convenience method for building quantum programs. * @param context The MLIR context to use for building the program * @param buildFunc A function that takes a reference to a QCProgramBuilder * and uses it to build the desired quantum program. The builder will be * properly initialized before calling this function, and the resulting module - * will be finalized and returned after this function completes. + * will be finalized using the returned Values after this function completes. + * @return The module containing the quantum program built by buildFunc. + */ + static OwningOpRef + build(MLIRContext* context, + const function_ref(QCProgramBuilder&)>& buildFunc); + + /** + * @brief Convenience method for building quantum programs with one return + * value. + * @param context The MLIR context to use for building the program + * @param buildFunc A function that takes a reference to a QCProgramBuilder + * and returns the single result value of the desired quantum program. * @return The module containing the quantum program built by buildFunc. */ static OwningOpRef build(MLIRContext* context, - const function_ref& buildFunc); + const function_ref& buildFunc); private: enum class AllocationMode : uint8_t { Unset, Static, Dynamic }; diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 88df7b5586..da46297e8f 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -968,7 +968,11 @@ def CtrlOp }]; let builders = [OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, - "const function_ref&":$body)>]; + "const function_ref&":$body)>, + OpBuilder<(ins "ValueRange":$controls, "Value":$target, + "const function_ref&":$bodyBuilder)>, + OpBuilder<(ins "Value":$control, "Value":$target, + "const function_ref&":$bodyBuilder)>]; let hasCanonicalizer = 1; let hasVerifier = 1; @@ -1021,7 +1025,9 @@ def InvOp : QCOp<"inv", }]; let builders = [OpBuilder<(ins "ValueRange":$qubits, - "const function_ref&":$body)>]; + "const function_ref&":$body)>, + OpBuilder<(ins "Value":$qubit, + "const function_ref&":$bodyBuilder)>]; let hasCanonicalizer = 1; let hasVerifier = 1; diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index 12d7ef73d1..d88e12dfa7 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -78,7 +78,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { //===--------------------------------------------------------------------===// /** - * @brief Initialize the builder and prepare for program construction + * @brief Initialize the builder and prepare for program construction, with + * a default return type of i64. * * @details * Creates a main function with an entry_point attribute. Must be called @@ -86,6 +87,23 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { */ void initialize(); + /** + * @brief Initialize the builder and prepare for program construction + * with specified return types. + * @param returnTypes The return types for the main function + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(TypeRange returnTypes); + + /** + * @brief Modify the return types of the main function after initialization. + * @param returnTypes The new return types for the main function + */ + void retype(TypeRange returnTypes); + //===--------------------------------------------------------------------===// // Constants //===--------------------------------------------------------------------===// @@ -387,7 +405,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * * @param qubit Input qubit (must be valid/unconsumed) * @param bit The classical bit to record the result - * @return Output qubit value + * @return Pair of (output_qubit, measurement_result) * * @par Example: * ```c++ @@ -397,7 +415,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * %q0_out, %r0 = qco.measure("c", 3, 0) %q0 : !qco.qubit * ``` */ - Value measure(Value qubit, const Bit& bit); + std::pair measure(Value qubit, const Bit& bit); /** * @brief Reset a qubit to |0⟩ state @@ -468,7 +486,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}); \ + * auto [q0_out, q1_out] = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.ctrl(%q0_in, %q1_in) { \ @@ -519,7 +537,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.c##OP_NAME(q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.c##OP_NAME(q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ @@ -542,7 +560,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, target_out} = builder.mc##OP_NAME({q0_in, q1_in}, q2_in); \ + * auto [controls_out, target_out] = builder.mc##OP_NAME({q0_in, q1_in}, \ + * q2_in); \ * ``` \ * ```mlir \ * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ @@ -605,7 +624,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.c##OP_NAME(PARAM, q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.c##OP_NAME(PARAM, q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ @@ -630,8 +649,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, target_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}, \ - * q2_in); \ + * auto [controls_out, target_out] = builder.mc##OP_NAME(PARAM, {q0_in, \ + * q1_in}, q2_in); \ * ``` \ * ```mlir \ * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ @@ -693,7 +712,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ @@ -722,8 +741,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, {q0_in, \ - * q1_in}, q2_in); \ + * auto [controls_out, target_out] = builder.mc##OP_NAME(PARAM1, PARAM2, \ + * {q0_in, q1_in}, q2_in); \ * ``` \ * ```mlir \ * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ @@ -789,7 +808,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0_in, \ + * auto [q0_out, q1_out] = builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0_in, \ * q1_in); \ * ``` \ * ```mlir \ @@ -821,8 +840,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, \ - * {q0_in, q1_in}, q2_in); \ + * auto [controls_out, target_out] = builder.mc##OP_NAME(PARAM1, PARAM2, \ + * PARAM3, {q0_in, q1_in}, q2_in); \ * ``` \ * ```mlir \ * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ @@ -859,7 +878,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.OP_NAME(q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.OP_NAME(q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ @@ -881,7 +900,8 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(q0_in, q1_in, q2_in); \ + * auto [q0_out, targets_out] = builder.c##OP_NAME(q0_in, q1_in, q2_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ @@ -908,8 +928,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME({q0_in, q1_in}, \ + * auto [controls_out, targets_out] = builder.mc##OP_NAME({q0_in, q1_in}, \ * q2_in, q3_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ @@ -948,7 +969,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.OP_NAME(PARAM, q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.OP_NAME(PARAM, q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ @@ -973,8 +994,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM, q0_in, q1_in, \ + * auto [q0_out, targets_out] = builder.c##OP_NAME(PARAM, q0_in, q1_in, \ * q2_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ @@ -1003,8 +1025,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM, {q0_in, \ + * auto [controls_out, targets_out] = builder.mc##OP_NAME(PARAM, {q0_in, \ * q1_in}, q2_in, q3_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ @@ -1045,7 +1068,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, q1_out} = builder.OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * auto [q0_out, q1_out] = builder.OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ * ``` \ * ```mlir \ * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ @@ -1071,8 +1094,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, \ + * auto [q0_out, targets_out] = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, \ * q1_in, q2_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ @@ -1103,8 +1127,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * \ * @par Example: \ * ```c++ \ - * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM1, PARAM2, \ + * auto [controls_out, targets_out] = builder.mc##OP_NAME(PARAM1, PARAM2, \ * {q0_in, q1_in}, q2_in, q3_in); \ + * auto [q1_out, q2_out] = targets_out; \ * ``` \ * ```mlir \ * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ @@ -1159,7 +1184,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * * @par Example: * ```c++ - * {controls_out, targets_out} = + * auto [controls_out, targets_out] = * builder.ctrl(q0_in, q1_in, * [&](ValueRange targets) -> SmallVector { * return {builder.x(targets[0])}; @@ -1176,6 +1201,44 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { ctrl(ValueRange controls, ValueRange targets, function_ref(ValueRange)> body); + /** + * @brief Apply a control modifier with a single target and one-qubit body. + * + * @param controls Control qubits + * @param target Target qubit + * @param body Function that builds the body containing the target operation + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * auto [controls_out, target_out] = + * builder.ctrl({q0_in, q1_in}, q2_in, [&](Value target) { + * return builder.x(target); + * }); + * ``` + */ + std::pair ctrl(ValueRange controls, Value target, + function_ref body); + + /** + * @brief Apply a control modifier with one control and one target. + * + * @param control Control qubit + * @param target Target qubit + * @param body Function that builds the body containing the target operation + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * auto [control_out, target_out] = + * builder.ctrl(q0_in, q1_in, [&](Value target) { + * return builder.x(target); + * }); + * ``` + */ + std::pair ctrl(Value control, Value target, + function_ref body); + /** * @brief Apply an inverse operation * @@ -1185,7 +1248,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * * @par Example: * ```c++ - * qubits_out = builder.inv(q0_in, + * auto qubits_out = builder.inv(q0_in, * [&](ValueRange qubits) -> SmallVector { * return {builder.s(qubits[0])}; * } @@ -1201,6 +1264,23 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { ValueRange inv(ValueRange qubits, function_ref(ValueRange)> body); + /** + * @brief Apply an inverse modifier on a single qubit. + * + * @param qubit Qubit involved in the operation + * @param body Function that builds the body containing the operation to + * invert + * @return Output qubit + * + * @par Example: + * ```c++ + * auto qubit_out = builder.inv(q0_in, [&](Value qubit) { + * return builder.s(qubit); + * }); + * ``` + */ + Value inv(Value qubit, function_ref body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// @@ -1391,17 +1471,49 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { OwningOpRef finalize(); /** - * @brief Convenience method for building quantum programs + * @brief Finalize the program with the given return values and return the + * constructed module + * @param returnValues Values representing the return values of the main + * function. + * + * @details + * Automatically deallocates all remaining valid qubits and tensors of qubits, + * adds a return statement with the given return values, and + * transfers ownership of the module to the caller. The builder should not + * be used after calling this method. + * + * The return values must have the types indicated by the function signature + * of the main function, which returns an `i64` by default and can be + * modified by passing different arguments to the `initialize()` method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(ValueRange returnValues); + + /** + * @brief Convenience method for building quantum programs. * @param context The MLIR context to use for building the program * @param buildFunc A function that takes a reference to a QCOProgramBuilder * and uses it to build the desired quantum program. The builder will be * properly initialized before calling this function, and the resulting module - * will be finalized and returned after this function completes. + * will be finalized using the returned Values after this function completes. + * @return The module containing the quantum program built by buildFunc. + */ + static OwningOpRef + build(MLIRContext* context, + const function_ref(QCOProgramBuilder&)>& buildFunc); + + /** + * @brief Convenience method for building quantum programs with one return + * value. + * @param context The MLIR context to use for building the program + * @param buildFunc A function that takes a reference to a QCOProgramBuilder + * and returns the single result value of the desired quantum program. * @return The module containing the quantum program built by buildFunc. */ static OwningOpRef build(MLIRContext* context, - const function_ref& buildFunc); + const function_ref& buildFunc); private: enum class AllocationMode : uint8_t { Unset, Static, Dynamic }; diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h index 1b08a1715a..7c0ea4fab2 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h +++ b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h @@ -13,9 +13,6 @@ #include "mlir/Dialect/Utils/Utils.h" #include -#include -#include -#include #include #include #include @@ -142,35 +139,4 @@ template class TargetAndParameterArityTrait { }; }; -/** - * @brief Find the entry point function with entry_point attribute - * - * @details - * Searches for the function marked with the "entry_point" attribute in - * the passthrough attributes. If multiple functions are marked, returns the - * first one encountered. - * - * @param op The module operation to search in. - * @returns the entry point function, or nullptr if not found. - */ -inline func::FuncOp getEntryPoint(ModuleOp op) { - static constexpr StringRef PASSTHROUGH_LABEL = "passthrough"; - static constexpr StringRef ENTRY_POINT_LABEL = "entry_point"; - - const auto isEntry = [](Attribute attr) { - const auto strAttr = dyn_cast(attr); - return strAttr && strAttr.getValue() == ENTRY_POINT_LABEL; - }; - - for (auto func : op.getOps()) { - if (const auto passthrough = - func->getAttrOfType(PASSTHROUGH_LABEL); - passthrough && llvm::any_of(passthrough, isEntry)) { - return func; - } - } - - return nullptr; -} - } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index f75e21e919..a3ee7b405c 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -96,7 +96,7 @@ def StaticOp : QCOOp<"static", [Pure]> { // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : QCOOp<"measure"> { +def MeasureOp : QCOOp<"measure", [Pure]> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -119,8 +119,7 @@ def MeasureOp : QCOOp<"measure"> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); @@ -138,7 +137,7 @@ def MeasureOp : QCOOp<"measure"> { let hasVerifier = 1; } -def ResetOp : QCOOp<"reset", [Idempotent, SameOperandsAndResultType]> { +def ResetOp : QCOOp<"reset", [Idempotent, SameOperandsAndResultType, Pure]> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state, @@ -150,8 +149,7 @@ def ResetOp : QCOOp<"reset", [Idempotent, SameOperandsAndResultType]> { ``` }]; - let arguments = - (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -209,7 +207,8 @@ def GPhaseOp let hasCanonicalizer = 1; } -def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def IdOp + : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit and returns the transformed qubit. @@ -220,7 +219,7 @@ def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -233,7 +232,8 @@ def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def XOp + : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. @@ -244,7 +244,7 @@ def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -257,7 +257,8 @@ def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def YOp + : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit and returns the transformed qubit. @@ -268,7 +269,7 @@ def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -281,7 +282,8 @@ def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def ZOp + : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit and returns the transformed qubit. @@ -292,7 +294,7 @@ def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -305,7 +307,8 @@ def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def HOp + : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply a H gate to a qubit"; let description = [{ Applies a H gate to a qubit and returns the transformed qubit. @@ -316,7 +319,7 @@ def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -329,7 +332,8 @@ def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SOp + : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit and returns the transformed qubit. @@ -340,7 +344,7 @@ def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -353,8 +357,8 @@ def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SdgOp - : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SdgOp : QCOOp<"sdg", + traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit and returns the transformed qubit. @@ -365,7 +369,7 @@ def SdgOp ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -378,7 +382,8 @@ def SdgOp let hasCanonicalizer = 1; } -def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TOp + : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit and returns the transformed qubit. @@ -389,7 +394,7 @@ def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -402,8 +407,8 @@ def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def TdgOp - : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TdgOp : QCOOp<"tdg", + traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit and returns the transformed qubit. @@ -414,7 +419,7 @@ def TdgOp ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -427,7 +432,8 @@ def TdgOp let hasCanonicalizer = 1; } -def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXOp + : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter, Pure]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit and returns the transformed qubit. @@ -438,7 +444,7 @@ def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -451,8 +457,8 @@ def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SXdgOp - : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, + Pure]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit and returns the transformed qubit. @@ -463,7 +469,7 @@ def SXdgOp ``` }]; - let arguments = (ins Arg:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -476,7 +482,8 @@ def SXdgOp let hasCanonicalizer = 1; } -def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RXOp + : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter, Pure]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. @@ -487,7 +494,7 @@ def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) " @@ -506,7 +513,8 @@ def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RYOp + : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter, Pure]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit and returns the transformed qubit. @@ -517,7 +525,7 @@ def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) " @@ -536,7 +544,8 @@ def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RZOp + : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter, Pure]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit and returns the transformed qubit. @@ -547,7 +556,7 @@ def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) " @@ -566,7 +575,8 @@ def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def POp + : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, Pure]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit and returns the transformed qubit. @@ -577,7 +587,7 @@ def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) " @@ -596,7 +606,8 @@ def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def ROp + : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, Pure]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit and returns the transformed qubit. @@ -607,7 +618,7 @@ def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta, Arg:$phi); let results = (outs QubitType:$qubit_out); @@ -627,7 +638,8 @@ def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def U2Op + : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter, Pure]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. @@ -638,7 +650,7 @@ def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$phi, Arg:$lambda); let results = (outs QubitType:$qubit_out); @@ -658,7 +670,8 @@ def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { +def UOp + : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, Pure]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit and returns the transformed qubit. @@ -669,7 +682,7 @@ def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { ``` }]; - let arguments = (ins Arg:$qubit_in, + let arguments = (ins Arg:$qubit_in, Arg:$theta, Arg:$phi, Arg:$lambda); @@ -692,8 +705,8 @@ def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let hasCanonicalizer = 1; } -def SWAPOp - : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, + Pure]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. @@ -704,9 +717,8 @@ def SWAPOp ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` " @@ -720,8 +732,8 @@ def SWAPOp let hasCanonicalizer = 1; } -def iSWAPOp - : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, + TwoTargetZeroParameter, Pure]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits and returns the transformed qubits. @@ -732,9 +744,8 @@ def iSWAPOp ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` " @@ -746,8 +757,8 @@ def iSWAPOp }]; } -def DCXOp - : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def DCXOp : QCOOp<"dcx", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, Pure]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits and returns the transformed qubits. @@ -758,9 +769,8 @@ def DCXOp ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` " @@ -774,8 +784,8 @@ def DCXOp let hasCanonicalizer = 1; } -def ECROp - : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def ECROp : QCOOp<"ecr", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, Pure]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits and returns the transformed qubits. @@ -786,9 +796,8 @@ def ECROp ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` " @@ -802,7 +811,8 @@ def ECROp let hasCanonicalizer = 1; } -def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RXXOp + : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter, Pure]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits and returns the transformed qubits. @@ -813,10 +823,9 @@ def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) " @@ -835,7 +844,8 @@ def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RYYOp + : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter, Pure]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits and returns the transformed qubits. @@ -846,10 +856,9 @@ def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) " @@ -868,7 +877,8 @@ def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZXOp + : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter, Pure]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits and returns the transformed qubits. @@ -879,10 +889,9 @@ def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) " @@ -901,7 +910,8 @@ def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZZOp + : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter, Pure]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits and returns the transformed qubits. @@ -912,10 +922,9 @@ def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) " @@ -934,8 +943,8 @@ def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def XXPlusYYOp : QCOOp<"xx_plus_yy", - traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, + TwoTargetTwoParameter, Pure]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits and returns the transformed qubits. @@ -946,11 +955,10 @@ def XXPlusYYOp : QCOOp<"xx_plus_yy", ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in " "attr-dict `:` type($qubit0_in) `,` type($qubit1_in) " @@ -970,8 +978,8 @@ def XXPlusYYOp : QCOOp<"xx_plus_yy", let hasCanonicalizer = 1; } -def XXMinusYYOp : QCOOp<"xx_minus_yy", - traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, + TwoTargetTwoParameter, Pure]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits and returns the transformed qubits. @@ -982,11 +990,10 @@ def XXMinusYYOp : QCOOp<"xx_minus_yy", ``` }]; - let arguments = - (ins Arg:$qubit0_in, - Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in " "attr-dict `:` type($qubit0_in) `,` type($qubit1_in) " @@ -1006,7 +1013,7 @@ def XXMinusYYOp : QCOOp<"xx_minus_yy", let hasCanonicalizer = 1; } -def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { +def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface, Pure]> { let summary = "Apply a barrier gate to a set of qubits"; let description = [{ Applies a barrier gate to a set of qubits and returns the transformed qubits. @@ -1018,7 +1025,7 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { }]; let arguments = - (ins Arg, "the target qubits", [MemRead]>:$qubits_in); + (ins Arg, "the target qubits">:$qubits_in); let results = (outs Variadic:$qubits_out); let assemblyFormat = "$qubits_in attr-dict `:` type($qubits_in) `->` type($qubits_out)"; @@ -1058,7 +1065,7 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { // Modifiers //===----------------------------------------------------------------------===// -def YieldOp : QCOOp<"yield", traits = [Terminator, ReturnLike]> { +def YieldOp : QCOOp<"yield", traits = [Terminator, ReturnLike, Pure]> { let summary = "Yield from a modifier region"; let description = [{ Terminates a modifier region, yielding the transformed target qubit and qtensor values back to the enclosing modifier operation. @@ -1081,7 +1088,7 @@ def CtrlOp : QCOOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments, SameOperandsAndResultType, SameOperandsAndResultShape, - SingleBlockImplicitTerminator<"YieldOp">, + SingleBlockImplicitTerminator<"YieldOp">, Pure, RecursiveMemoryEffects]> { let summary = "Add control qubits to a unitary operation"; let description = [{ @@ -1103,9 +1110,9 @@ def CtrlOp : QCOOp<"ctrl", ``` }]; - let arguments = (ins Arg, - "the control qubits", [MemRead]>:$controls_in, - Arg, "the target qubits", [MemRead]>:$targets_in); + let arguments = + (ins Arg, "the control qubits">:$controls_in, + Arg, "the target qubits">:$targets_in); let results = (outs Variadic:$controls_out, Variadic:$targets_out); let regions = (region SizedRegion<1>:$region); @@ -1147,13 +1154,17 @@ def CtrlOp : QCOOp<"ctrl", [[nodiscard]] std::optional getUnitaryMatrix(); }]; - let builders = [OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets), - [{ - build($_builder, $_state, controls.getTypes(), targets.getTypes(), controls, targets); - }]>, - OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, - "function_ref(ValueRange)" - ">":$bodyBuilder)>]; + let builders = + [OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets), [{ + build($_builder, $_state, controls.getTypes(), targets.getTypes(), + controls, targets); + }]>, + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, + "function_ref(ValueRange)>":$bodyBuilder)>, + OpBuilder<(ins "ValueRange":$controls, "Value":$target, + "function_ref":$bodyBuilder)>, + OpBuilder<(ins "Value":$control, "Value":$target, + "function_ref":$bodyBuilder)>]; let hasCanonicalizer = 1; let hasVerifier = 1; @@ -1161,7 +1172,7 @@ def CtrlOp : QCOOp<"ctrl", def InvOp : QCOOp<"inv", traits = [UnitaryOpInterface, SingleBlockImplicitTerminator<"YieldOp">, - RecursiveMemoryEffects]> { + Pure, RecursiveMemoryEffects]> { let summary = "Invert a unitary operation"; let description = [{ A modifier operation that inverts the unitary operation defined in its body region. @@ -1180,9 +1191,8 @@ def InvOp : QCOOp<"inv", traits = [UnitaryOpInterface, ``` }]; - let arguments = - (ins Arg, - "the qubits involved in the operation", [MemRead]>:$qubits_in); + let arguments = (ins Arg, + "the qubits involved in the operation">:$qubits_in); let results = (outs Variadic:$qubits_out); let regions = (region SizedRegion<1>:$region); let assemblyFormat = [{ @@ -1221,12 +1231,14 @@ def InvOp : QCOOp<"inv", traits = [UnitaryOpInterface, [[nodiscard]] std::optional getUnitaryMatrix(); }]; - let builders = [OpBuilder<(ins "ValueRange":$qubits), [{ - build($_builder, $_state, qubits.getTypes(), qubits); - }]>, - OpBuilder<(ins "ValueRange":$qubits, - "function_ref(ValueRange)" - ">":$bodyBuilder)>]; + let builders = + [OpBuilder<(ins "ValueRange":$qubits), [{ + build($_builder, $_state, qubits.getTypes(), qubits); + }]>, + OpBuilder<(ins "ValueRange":$qubits, + "function_ref(ValueRange)>":$bodyBuilder)>, + OpBuilder<(ins "Value":$qubit, + "function_ref":$bodyBuilder)>]; let hasCanonicalizer = 1; let hasVerifier = 1; @@ -1243,7 +1255,7 @@ def IfOp RegionBranchOpInterface, ["getNumRegionInvocations", "getRegionInvocationBounds", "getEntrySuccessorRegions"]>, - SingleBlock, SingleBlockImplicitTerminator<"YieldOp">, + SingleBlock, SingleBlockImplicitTerminator<"YieldOp">, Pure, RecursiveMemoryEffects]> { let summary = "If-then-else operation for linear (qubit) types"; @@ -1319,6 +1331,13 @@ def IfOp /// Return the yielded value that corresponds to the given argument /// for the else-block, or `nullptr` on failure. OpOperand* getTiedElseYieldedValue(BlockArgument bbArg); + + /// Append the specified additional "qubit" operands: replace this + /// if-op with a new if-op that has the additional qubit operands. + /// The operands can be of qubit or qtensor type. + /// The branch bodies of this if-op are moved over to the new if-op. + /// The newly added qubits are yielded from each branch. + IfOp replaceWithAdditionalQubits(RewriterBase& rewriter, ValueRange addons); }]; let extraClassDefinition = [{ diff --git a/mlir/include/mlir/Dialect/QCO/QCOUtils.h b/mlir/include/mlir/Dialect/QCO/QCOUtils.h index f08db99293..cf781c0b52 100644 --- a/mlir/include/mlir/Dialect/QCO/QCOUtils.h +++ b/mlir/include/mlir/Dialect/QCO/QCOUtils.h @@ -20,18 +20,25 @@ #include #include +#include #include namespace mlir::qco { +/// Maximum number of modifier targets supported by @ref +/// composeBodyMatrix. +inline constexpr size_t kMaxModifierTargetQubits = 10; + /** - * @brief Composes compile-time single-qubit unitaries in a modifier body. + * @brief Composes compile-time unitaries in a modifier body on @p numTargets + * wires. * - * @return The composed 2x2 target unitary in program order, or `std::nullopt` - * when the body cannot be composed. + * @details Block arguments map to wire indices `0..numTargets-1` (MSB-first, + * matching @ref Matrix2x2::embedInNqubit). Returns the composed unitary in + * program order, or `std::nullopt` when the body cannot be composed. */ -[[nodiscard]] std::optional -composeSingleQubitBodyMatrix(Block& block); +[[nodiscard]] std::optional composeBodyMatrix(Block& block, + size_t numTargets); /** * @brief Check whether two parameter values match. diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h index 0eead57bd9..544f1872c7 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h @@ -11,7 +11,6 @@ #pragma once #include "mlir/Dialect/QCO/Transforms/Passes.h" -#include "mlir/Dialect/QCO/Utils/Algorithms.h" #include #include @@ -25,7 +24,8 @@ namespace mlir::qco { * @brief Create a mapping pass instance with the given target architecture. * @returns a pass object. */ -std::unique_ptr createMappingPass(size_t nqubits, const Edges& coupling, - MappingPassOptions options); +std::unique_ptr +createMappingPass(const llvm::DenseSet>&, + MappingPassOptions); } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Algorithms.h b/mlir/include/mlir/Dialect/QCO/Utils/Algorithms.h deleted file mode 100644 index 77c57585f3..0000000000 --- a/mlir/include/mlir/Dialect/QCO/Utils/Algorithms.h +++ /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 - */ - -#pragma once - -#include -#include - -#include -#include - -namespace mlir::qco { - -using Matrix = SmallVector, 0>; -using Edges = llvm::DenseSet>; - -/** - * @brief Find all shortest paths between two nodes in a graph. - * @details Has a time complexity of O(n^3). - * - * @link Adapted from https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm - * - * @param n The number of nodes in the graph. - * @param edges The set of edges (i, j). - * - * @returns The distance matrix dist, where dist[i, j] is defined as the - * distance between node i and j. - */ -Matrix findAllShortestPaths(size_t n, const Edges& edges); - -} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h index 3d360ebb78..f359e3194c 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h @@ -12,7 +12,6 @@ #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include "mlir/Dialect/QCO/Utils/Qubits.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" @@ -35,63 +34,6 @@ namespace mlir::qco { -using WalkProgramFn = function_ref; - -/** - * @brief Perform top-down non-recursive walk of all operations within a - * region of a quantum program and apply a callback function. - * @details The signature of the callback function is: - * - * (Operation*, Qubits& q) -> WalkResult - * - * where the Qubits object tracks the front of qubit SSA values. - * Depending on the template parameter, the callback is executed before or after - * updating the Qubits state. - * @param region The targeted region. - * @param fn The callback function. - * @returns success(), if all operations have been visited. - */ -template -LogicalResult walkProgram(Region& region, const WalkProgramFn& fn) { - Qubits qubits; - for (Operation& curr : region.getOps()) { - if constexpr (Order == WalkOrder::PreOrder) { - if (fn(&curr, qubits).wasInterrupted()) { - return failure(); - } - } - - TypeSwitch(&curr) - .template Case( - [&](StaticOp op) { qubits.add(op.getQubit(), op.getIndex()); }) - .template Case([&](AllocOp op) { qubits.add(op.getResult()); }) - .template Case([&](UnitaryOpInterface& op) { - for (const auto& [prevV, nextV] : - llvm::zip(op.getInputQubits(), op.getOutputQubits())) { - const auto prevQ = cast>(prevV); - const auto nextQ = cast>(nextV); - qubits.remap(prevQ, nextQ); - } - }) - .template Case([&](ResetOp op) { - qubits.remap(op.getQubitIn(), op.getQubitOut()); - }) - .template Case([&](MeasureOp op) { - qubits.remap(op.getQubitIn(), op.getQubitOut()); - }) - .template Case( - [&](SinkOp op) { qubits.remove(op.getQubit()); }); - - if constexpr (Order == WalkOrder::PostOrder) { - if (fn(&curr, qubits).wasInterrupted()) { - return failure(); - } - } - } - - return success(); -} - using ReleasedOps = SmallVector; using PendingWiresMap = DenseMap>; diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h new file mode 100644 index 0000000000..6cd25c8144 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h @@ -0,0 +1,108 @@ +/* + * 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 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace mlir::qco { + +/// A directed graph. +class Graph { +public: + class DistanceMatrix { + SmallVector data_; + size_t n_{}; + + public: + /// Initialize distance matrix, where all entries are filled with `v`. + explicit DistanceMatrix(size_t n, size_t v) : n_(n), data_(n * n, v) {} + + /// Return the i-th row. + MutableArrayRef operator[](size_t i) { + assert(i < n_ && "row index out of bounds"); + return MutableArrayRef(data_).slice(i * n_, n_); + } + + /// Return the i-th row. + ArrayRef operator[](size_t i) const { + assert(i < n_ && "row index out of bounds"); + return ArrayRef(data_).slice(i * n_, n_); + } + }; + + /// Construct an empty graph. + Graph() = default; + + /// Construct graph from node identifiers. + explicit Graph(ArrayRef nodes) { + for_each(nodes, [this](const auto u) { std::ignore = adj_[u]; }); + } + + /// Construct graph from edge set. + explicit Graph(const llvm::DenseSet>& edges) { + for_each(edges, [this](const auto& e) { addEdge(e.first, e.second); }); + } + + /// Add a directed edge to the internal representation of the graph. + /// Implicitly adds nodes. + void addEdge(size_t u, size_t v); + + /// Return the neighbours of a node. + [[nodiscard]] ArrayRef getNeighbours(size_t id) const; + + /// Return the nodes. + [[nodiscard]] SmallVector getNodes() const; + + /// Return the number of nodes. + [[nodiscard]] size_t getNumNodes() const { return adj_.size(); } + + /// Return the degree of a node. + [[nodiscard]] size_t getDegree(const size_t id) const { + return adj_.at(id).size(); + } + + /// Return the max degree of the graph. + [[nodiscard]] size_t getMaxDegree() const; + + /// Return true if the graph has no nodes and edges. + [[nodiscard]] bool empty() const { return adj_.empty(); } + + /// Clear the graph. + void clear() { adj_.clear(); } + + /// Remove the edges from the graph. Keep the nodes. + void clearEdges(); + + /// Return the minimum distance matrix of the graph by implementing the + /// Floyd-Warshall Algorithm + /// (https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm) where dist[i][j] + /// denotes the distance between i and j. + [[nodiscard]] Graph::DistanceMatrix getDistMatrix() const; + + /// Return cycle in graph or `std::nullopt` if none exists. + /// Implements an iterative depth-first search inspired by LLVM's SCC + /// utilities. For a cycle [A, B, C, A], the function returns [A, B, C]. + [[nodiscard]] std::optional> findCycle() const; + +private: + llvm::DenseMap> adj_; +}; +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Layout.h b/mlir/include/mlir/Dialect/QCO/Utils/Layout.h new file mode 100644 index 0000000000..f4fc4c3530 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Utils/Layout.h @@ -0,0 +1,79 @@ +/* + * 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 + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace mlir::qco { + +/// A qubit layout that maps program and hardware indices without +/// storing Values. Used for efficient memory usage when Value tracking isn't +/// needed. +/// +/// Note that we use the terminology "hardware" and "program" qubits +/// here, because "virtual" (opposed to physical) and "static" (opposed to +/// dynamic) are C++ keywords. +class Layout { +public: + /// Construct and return a random layout with size `nqubits`. + static Layout random(size_t nqubits, size_t seed); + + /// Insert program:hardware index mapping. + void add(size_t prog, size_t hw); + + /// Lookup and return program index for a hardware index. + [[nodiscard]] size_t getProgramIndex(size_t hw) const; + + /// Lookup and return hardware index for a program index. + [[nodiscard]] size_t getHardwareIndex(size_t prog) const; + + /// Lookup and return multiple hardware indices at once. + template + requires(sizeof...(ProgIndices) > 0) && + ((std::is_convertible_v) && ...) + [[nodiscard]] auto getHardwareIndices(ProgIndices... progs) const { + return std::tuple{getHardwareIndex(static_cast(progs))...}; + } + + /// Lookup and return multiple program indices at once. + template + requires(sizeof...(HwIndices) > 0) && + ((std::is_convertible_v) && ...) + [[nodiscard]] auto getProgramIndices(HwIndices... hws) const { + return std::tuple{getProgramIndex(static_cast(hws))...}; + } + + /// Swap the mapping to program indices of two hardware indices. + void swap(size_t hwA, size_t hwB); + + /// Return the number of qubits managed by the layout. + [[nodiscard]] size_t nqubits() const; + + /// Return the program to hardware mapping. + [[nodiscard]] ArrayRef getProgramToHardware() const; + +protected: + /// Maps a program qubit index to its hardware index. + SmallVector programToHardware_; + /// Maps a hardware qubit index to its program index. + SmallVector hardwareToProgram_; + +private: + explicit Layout(const size_t nqubits) + : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} +}; +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index 38816a9c85..cb99ce6fce 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h @@ -62,7 +62,7 @@ struct Matrix1x1 { * @param col Column index (must be `0`). * @return Reference to the sole matrix entry. */ - [[nodiscard]] Complex& operator()(std::size_t row, std::size_t col); + [[nodiscard]] Complex& operator()(size_t row, size_t col); /** * @brief Const element access with `(row, col)` indexing. @@ -70,7 +70,7 @@ struct Matrix1x1 { * @param col Column index (must be `0`). * @return Copy of the sole matrix entry. */ - [[nodiscard]] Complex operator()(std::size_t row, std::size_t col) const; + [[nodiscard]] Complex operator()(size_t row, size_t col) const; /** * @brief Element-wise scaling by a complex scalar. @@ -125,11 +125,11 @@ struct Matrix1x1 { */ struct Matrix2x2 { /// Number of rows. - static constexpr std::size_t K_ROWS = 2; + static constexpr size_t K_ROWS = 2; /// Number of columns. - static constexpr std::size_t K_COLS = 2; + static constexpr size_t K_COLS = 2; /// Total number of stored elements. - static constexpr std::size_t K_SIZE_AT_COMPILE_TIME = 4; + static constexpr size_t K_SIZE_AT_COMPILE_TIME = 4; /// Flat row-major storage of all matrix entries. std::array data{}; @@ -161,7 +161,7 @@ struct Matrix2x2 { * @param col Column index in `[0, K_COLS)`. * @return Reference to the element at `(row, col)`. */ - [[nodiscard]] Complex& operator()(std::size_t row, std::size_t col); + [[nodiscard]] Complex& operator()(size_t row, size_t col); /** * @brief Const element access. @@ -169,7 +169,7 @@ struct Matrix2x2 { * @param col Column index in `[0, K_COLS)`. * @return Copy of the element at `(row, col)`. */ - [[nodiscard]] Complex operator()(std::size_t row, std::size_t col) const; + [[nodiscard]] Complex operator()(size_t row, size_t col) const; /** * @brief Matrix product `*this * rhs`. @@ -271,8 +271,8 @@ struct Matrix2x2 { * @param qubitIndex Wire index to act on. * @return Embedded unitary as a dynamic matrix. */ - [[nodiscard]] DynamicMatrix embedInNqubit(std::size_t numQubits, - std::size_t qubitIndex) const; + [[nodiscard]] DynamicMatrix embedInNqubit(size_t numQubits, + size_t qubitIndex) const; /** * @brief Embed this single-qubit matrix into a two-qubit Hilbert space. @@ -280,7 +280,7 @@ struct Matrix2x2 { * @param qubitIndex Wire index (`0` = high bit / MSB, `1` = low bit). * @return The `4x4` embedded unitary. */ - [[nodiscard]] Matrix4x4 embedInTwoQubit(std::size_t qubitIndex) const; + [[nodiscard]] Matrix4x4 embedInTwoQubit(size_t qubitIndex) const; }; /** @@ -291,11 +291,11 @@ struct Matrix2x2 { */ struct Matrix4x4 { /// Number of rows. - static constexpr std::size_t K_ROWS = 4; + static constexpr size_t K_ROWS = 4; /// Number of columns. - static constexpr std::size_t K_COLS = 4; + static constexpr size_t K_COLS = 4; /// Total number of stored elements. - static constexpr std::size_t K_SIZE_AT_COMPILE_TIME = 16; + static constexpr size_t K_SIZE_AT_COMPILE_TIME = 16; /// Flat row-major storage of all matrix entries. std::array data{}; @@ -350,7 +350,7 @@ struct Matrix4x4 { * @param col Column index in `[0, K_COLS)`. * @return Reference to the element at `(row, col)`. */ - [[nodiscard]] Complex& operator()(std::size_t row, std::size_t col); + [[nodiscard]] Complex& operator()(size_t row, size_t col); /** * @brief Const element access. @@ -358,7 +358,7 @@ struct Matrix4x4 { * @param col Column index in `[0, K_COLS)`. * @return Copy of the element at `(row, col)`. */ - [[nodiscard]] Complex operator()(std::size_t row, std::size_t col) const; + [[nodiscard]] Complex operator()(size_t row, size_t col) const; /** * @brief Matrix product `*this * rhs`. @@ -473,28 +473,28 @@ struct Matrix4x4 { * @param col Column index in `[0, K_COLS)`. * @return Array of the four column entries. */ - [[nodiscard]] std::array column(std::size_t col) const; + [[nodiscard]] std::array column(size_t col) const; /** * @brief Overwrites column @p col with @p values. * @param col Column index in `[0, K_COLS)`. * @param values New column entries, top to bottom; must have length `K_ROWS`. */ - void setColumn(std::size_t col, ArrayRef values); + void setColumn(size_t col, ArrayRef values); /** * @brief Returns the entries of row @p row, left to right. * @param row Row index in `[0, K_ROWS)`. * @return View over the four row entries. */ - [[nodiscard]] ArrayRef row(std::size_t row) const; + [[nodiscard]] ArrayRef row(size_t row) const; /** * @brief Overwrites row @p row with @p values. * @param row Row index in `[0, K_ROWS)`. * @param values New row entries, left to right; must have length `K_COLS`. */ - void setRow(std::size_t row, ArrayRef values); + void setRow(size_t row, ArrayRef values); /** * @brief Returns the element-wise real parts in row-major order. @@ -574,9 +574,8 @@ struct Matrix4x4 { * @param q1Index Wire index of operand 1. * @return Embedded unitary as a dynamic matrix. */ - [[nodiscard]] DynamicMatrix embedInNqubit(std::size_t numQubits, - std::size_t q0Index, - std::size_t q1Index) const; + [[nodiscard]] DynamicMatrix embedInNqubit(size_t numQubits, size_t q0Index, + size_t q1Index) const; /** * @brief Reorder this matrix to act on qubits `{0, 1}`. @@ -584,8 +583,8 @@ struct Matrix4x4 { * @param q0Index Wire index of operand 0; @p q1Index wire index of operand 1. * @return Reordered copy of this matrix. */ - [[nodiscard]] Matrix4x4 reorderForQubits(std::size_t q0Index, - std::size_t q1Index) const; + [[nodiscard]] Matrix4x4 reorderForQubits(size_t q0Index, + size_t q1Index) const; }; /** @@ -604,7 +603,7 @@ class DynamicMatrix { * @brief Creates a zero-initialized square matrix. * @param dim Side length of the square matrix. */ - explicit DynamicMatrix(std::int64_t dim); + explicit DynamicMatrix(int64_t dim); /** * @brief Creates a dynamic matrix from a fixed 2x2 matrix. @@ -634,7 +633,7 @@ class DynamicMatrix { * @param dim Side length of the identity matrix. * @return Identity matrix with ones on the diagonal. */ - [[nodiscard]] static DynamicMatrix identity(std::int64_t dim); + [[nodiscard]] static DynamicMatrix identity(int64_t dim); /** * @brief Creates a dynamic matrix holding the adjoint of a 2x2 matrix. @@ -647,13 +646,13 @@ class DynamicMatrix { * @brief Returns the number of rows. * @return Matrix dimension. */ - [[nodiscard]] std::int64_t rows() const; + [[nodiscard]] int64_t rows() const; /** * @brief Returns the number of columns. * @return Matrix dimension. */ - [[nodiscard]] std::int64_t cols() const; + [[nodiscard]] int64_t cols() const; /** * @brief Mutable element access. @@ -661,7 +660,7 @@ class DynamicMatrix { * @param col Column index in `[0, dim)`. * @return Reference to the element at `(row, col)`. */ - [[nodiscard]] Complex& operator()(std::int64_t row, std::int64_t col); + [[nodiscard]] Complex& operator()(int64_t row, int64_t col); /** * @brief Const element access. @@ -669,7 +668,7 @@ class DynamicMatrix { * @param col Column index in `[0, dim)`. * @return Copy of the element at `(row, col)`. */ - [[nodiscard]] Complex operator()(std::int64_t row, std::int64_t col) const; + [[nodiscard]] Complex operator()(int64_t row, int64_t col) const; /** * @brief Copies a 2x2 block into the bottom-right corner. @@ -784,6 +783,40 @@ class DynamicMatrix { */ [[nodiscard]] DynamicMatrix operator*(const DynamicMatrix& rhs) const; + /** + * @brief Premultiplies by a matrix: `*this = lhs * *this`. + * @param lhs Left-hand factor. + */ + void premultiplyBy(const DynamicMatrix& lhs); + + /** + * @brief Premultiplies by a single-qubit gate embedded on @p qubitIndex. + * + * Uses the same MSB-first wire convention as @ref Matrix2x2::embedInNqubit. + * + * @param gate Single-qubit unitary. + * @param numQubits Number of qubits in this matrix. + * @param qubitIndex Target wire index. + */ + void premultiplyByEmbedded1Q(const Matrix2x2& gate, size_t numQubits, + size_t qubitIndex); + + /** + * @brief Premultiplies by a two-qubit gate embedded on @p q0Index and @p + * q1Index. + * + * @p gate must already be reordered for wires @p q0Index and @p q1Index when + * @p numQubits is 2. Uses the same MSB-first convention as @ref + * Matrix4x4::embedInNqubit. + * + * @param gate Two-qubit unitary. + * @param numQubits Number of qubits in this matrix. + * @param q0Index First target wire index. + * @param q1Index Second target wire index. + */ + void premultiplyByEmbedded2Q(const Matrix4x4& gate, size_t numQubits, + size_t q0Index, size_t q1Index); + /** * @brief Element-wise scaling by a complex scalar. * @param scalar Factor applied to every matrix entry. diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Qubits.h b/mlir/include/mlir/Dialect/QCO/Utils/Qubits.h deleted file mode 100644 index b08952aa46..0000000000 --- a/mlir/include/mlir/Dialect/QCO/Utils/Qubits.h +++ /dev/null @@ -1,71 +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 - */ - -#pragma once - -#include "mlir/Dialect/QCO/IR/QCODialect.h" - -#include -#include - -#include -#include -#include - -namespace mlir::qco { -class Qubits { - /** - * @brief Specifies the qubit "location" (hardware or program). - */ - enum class QubitLocation : std::uint8_t { Hardware, Program }; - -public: - /** - * @brief Add qubit with automatically assigned dynamic index. - */ - void add(TypedValue q); - - /** - * @brief Add qubit with static index. - */ - void add(TypedValue q, std::size_t hw); - - /** - * @brief Remap the qubit value from prev to next. - */ - void remap(TypedValue prev, TypedValue next); - - /** - * @brief Remove the qubit value. - */ - void remove(TypedValue q); - - /** - * @returns the qubit value assigned to a program index. - */ - [[nodiscard]] TypedValue getProgramQubit(std::size_t index) const; - - /** - * @returns the qubit value assigned to a hardware index. - */ - [[nodiscard]] TypedValue getHardwareQubit(std::size_t index) const; - - /** - * @returns the index assigned to the qubit value. - */ - [[nodiscard]] std::size_t getIndex(TypedValue q) const; - -private: - DenseMap> programToValue_; - DenseMap> hardwareToValue_; - DenseMap, std::pair> - valueToIndex_; -}; -} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 65a4bf7dcf..3b0d025e82 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -100,6 +100,23 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { */ void initialize(); + /** + * @brief Initialize the builder and prepare for program construction + * with specified return types. + * @param returnType The return type for the main function + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(Type returnType); + + /** + * @brief Modify the return type of the main function after initialization. + * @param returnType The new return type for the main function + */ + void retype(Type returnType); + //===--------------------------------------------------------------------===// // Constants //===--------------------------------------------------------------------===// @@ -304,6 +321,7 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { * * @param qubit The qubit to measure * @param resultIndex The classical bit index for result pointer + * @param record Whether the measurement should be recorded in the output * @return An LLVM pointer to the measurement result * * @par Example: @@ -324,7 +342,7 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { * !llvm.ptr) -> () * ``` */ - Value measure(Value qubit, int64_t resultIndex); + Value measure(Value qubit, int64_t resultIndex, bool record = true); /** * @brief Measure a qubit into a classical register @@ -337,6 +355,7 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { * * @param qubit The qubit to measure * @param bit The classical bit to store the result + * @param record Whether the measurement should be recorded in the output * @return An LLVM pointer to the measurement result * * @par Example: @@ -360,7 +379,7 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { * : (i64, !llvm.ptr, !llvm.ptr) -> () * ``` */ - Value measure(Value qubit, const Bit& bit); + Value measure(Value qubit, const Bit& bit, bool record = true); /** * @brief Reset a qubit to |0⟩ state @@ -1078,6 +1097,25 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { */ OwningOpRef finalize(); + /** + * @brief Finalize the program with the given return value and return the + * constructed module + * @param returnValue The return value of the main function + * + * @details + * Automatically deallocates all remaining valid qubits and tensors of qubits, + * adds a return statement with the given return value, and + * transfers ownership of the module to the caller. The builder should not + * be used after calling this method. + * + * The return value must have the type indicated by the function signature + * of the main function, which returns an `i64` by default and can be + * modified by passing different arguments to the `initialize()` method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(Value returnValue); + /** * @brief Convenience method for building quantum programs * @param context The MLIR context to use for building the program @@ -1085,11 +1123,12 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { * and uses it to build the desired quantum program. The builder will be * properly initialized before calling this function, and the resulting module * will be finalized and returned after this function completes. + * @param profile The profile to use for the program. Defaults to Adaptive. * @return The module containing the quantum program built by buildFunc. */ static OwningOpRef build(MLIRContext* context, - const function_ref& buildFunc, + const function_ref& buildFunc, Profile profile = Profile::Adaptive); private: @@ -1135,6 +1174,12 @@ class QIRProgramBuilder final : public ImplicitLocOpBuilder { /// Map from result index to result pointer for non-register results DenseMap resultPtrs; + /// Set of array register names that should be recorded in the output. + DenseSet recordedArrays; + + /// Set of unnamed result indices that should be recorded in the output. + DenseSet recordedIndices; + /// Map from register to their loaded indices DenseMap> loadedQubits; diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 4135ca1769..970ec6d90c 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -13,8 +13,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -268,4 +271,35 @@ inline void inlineModifierBody(Operation* op, Block& body, rewriter.replaceOp(op, results); } +/** + * @brief Find the entry point function with the entry_point attribute + * + * @details + * Searches for the function marked with the "entry_point" attribute in + * the passthrough attributes. If multiple functions are marked, returns the + * first one encountered. + * + * @param op The module operation to search in. + * @returns the entry point function, or nullptr if not found. + */ +inline func::FuncOp getEntryPoint(ModuleOp op) { + static constexpr StringRef PASSTHROUGH_LABEL = "passthrough"; + static constexpr StringRef ENTRY_POINT_LABEL = "entry_point"; + + const auto isEntry = [](Attribute attr) { + const auto strAttr = dyn_cast(attr); + return strAttr && strAttr.getValue() == ENTRY_POINT_LABEL; + }; + + for (auto func : op.getOps()) { + if (const auto passthrough = + func->getAttrOfType(PASSTHROUGH_LABEL); + passthrough && llvm::any_of(passthrough, isEntry)) { + return func; + } + } + + return nullptr; +} + } // namespace mlir::utils diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index e18452a780..0bb434e9e3 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -1194,16 +1194,11 @@ struct ConvertSCFForOpToJeff final : StatefulOpConversionPattern { * * @par Example: * ```mlir - * func.func @main() -> i64 attributes {passthrough = ["entry_point"]} { - * %0 = arith.constant 0 : i64 - * return %0 - * } + * func.func @main() -> i64 attributes {passthrough = ["entry_point"]} { ... } * ``` * is converted to * ```mlir - * func.func @main() -> () { - * return - * } + * func.func @main() -> i64 { ... } * ``` */ struct ConvertQCOMainToJeff final : StatefulOpConversionPattern { @@ -1236,17 +1231,11 @@ struct ConvertQCOMainToJeff final : StatefulOpConversionPattern { getState().entryPointName = op.getSymName(); - // Update function signature and remove passthrough attribute + // Remove passthrough attribute from function signature rewriter.startOpModification(op); - op.setType(FunctionType::get(rewriter.getContext(), {}, {})); op->removeAttr("passthrough"); rewriter.finalizeOpModification(op); - // Replace return operation - rewriter.setInsertionPointToEnd(block); - func::ReturnOp::create(rewriter, returnOp->getLoc()); - rewriter.eraseOp(returnOp); - return success(); } }; diff --git a/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp b/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp index 9b03e4d722..80a30c19bd 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cpp @@ -373,6 +373,9 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { // Get result pointer Value result; + const bool shouldRecord = + state.returnedMeasurements.contains(op.getOperation()); + if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { const auto registerName = op.getRegisterName().value(); const auto registerSize = @@ -380,6 +383,10 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { const auto registerIndex = static_cast(op.getRegisterIndex().value()); + if (shouldRecord) { + state.recordedArrays.insert(state.stringSaver.save(registerName)); + } + // Create result register if it does not exist yet if (!resultArrays.contains(registerName)) { auto fnSig = LLVM::LLVMFunctionType::get( @@ -415,8 +422,12 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { result = loadedResults.at({registerName, registerIndex}); } else { rewriter.setInsertionPoint(state.entryBlock->getTerminator()); - result = createPointerFromIndex(rewriter, op.getLoc(), resultPtrs.size()); - resultPtrs.try_emplace(resultPtrs.size(), result); + auto index = resultPtrs.size(); + if (shouldRecord) { + state.recordedIndices.insert(index); + } + result = createPointerFromIndex(rewriter, op.getLoc(), index); + resultPtrs.try_emplace(index, result); } rewriter.restoreInsertionPoint(savedInsertionPoint); @@ -620,7 +631,10 @@ struct QCToQIRAdaptive final : impl::QCToQIRAdaptiveBase { } } - // Stage 2: Convert func dialect to LLVM + // Stage 2.0: Strip returned measurements from func::ReturnOp + stripReturnedMeasurements(moduleOp, state); + + // Stage 2.1: Convert func dialect to LLVM { RewritePatternSet funcPatterns(ctx); target.addIllegalDialect(); diff --git a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp index c7043422f1..1e5a392a92 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cpp @@ -220,6 +220,8 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { // Get result pointer Value result; int64_t resultIndex = 0; + const bool shouldRecord = + state.returnedMeasurements.contains(op.getOperation()); const auto nresults = resultPtrs.size(); if (op.getRegisterIndex() && op.getRegisterName() && op.getRegisterSize()) { @@ -234,6 +236,10 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { resultIndex = static_cast(nresults); } + if (shouldRecord) { + state.recordedIndices.insert(resultIndex); + } + if (resultPtrs.contains(resultIndex)) { result = resultPtrs.at(resultIndex); } else { @@ -406,7 +412,12 @@ struct QCToQIRBase final : impl::QCToQIRBaseBase { target.addLegalDialect(); - // Stage 1: Convert func dialect to LLVM + LoweringState state; + + // Stage 1.0: Strip returned measurements from func::ReturnOp + stripReturnedMeasurements(moduleOp, state); + + // Stage 1.1: Convert func dialect to LLVM { RewritePatternSet funcPatterns(ctx); target.addIllegalDialect(); @@ -426,8 +437,6 @@ struct QCToQIRBase final : impl::QCToQIRBaseBase { return; } - LoweringState state; - // Stage 2: Create block structure ensureBlocks(main, state); diff --git a/mlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cpp b/mlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cpp index aea4ae0a02..1c5c70005c 100644 --- a/mlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cpp +++ b/mlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cpp @@ -15,6 +15,7 @@ #include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QIR/Utils/QIRUtils.h" +#include #include #include #include @@ -22,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -426,6 +429,9 @@ void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, auto fnDec = getOrCreateFunctionDeclaration(builder, main, QIR_RECORD_OUTPUT, fnSig); for (const auto& [index, ptr] : resultPtrs) { + if (!state.recordedIndices.contains(index)) { + continue; + } auto label = createResultLabel(builder, main, "__unnamed__" + std::to_string(index)) .getResult(); @@ -440,6 +446,9 @@ void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, auto fnDec = getOrCreateFunctionDeclaration(builder, main, QIR_ARRAY_RECORD_OUTPUT, fnSig); for (const auto& [name, results] : resultArrays) { + if (!state.recordedArrays.contains(name)) { + continue; + } auto size = results.getDefiningOp().getArraySize(); auto label = createResultLabel(builder, main, name).getResult(); LLVM::CallOp::create(builder, main->getLoc(), fnDec, @@ -463,4 +472,49 @@ void populateQCToQIRPatterns(RewritePatternSet& patterns, &state); } +void stripReturnedMeasurements(Operation* moduleOp, LoweringState& state) { + moduleOp->walk([&](func::FuncOp funcOp) { + // First, check if the given function is the main entrypoint or not. + auto passthrough = funcOp->getAttrOfType("passthrough"); + bool isEntryPoint = false; + if (passthrough) { + isEntryPoint = llvm::any_of(passthrough, [](Attribute attr) { + auto strAttr = dyn_cast(attr); + return strAttr && strAttr.getValue() == "entry_point"; + }); + } + if (!isEntryPoint) { + return; + } + + funcOp.walk([&](func::ReturnOp returnOp) { + SmallVector keptOperands; + SmallVector keptReturnTypes; + + for (auto operand : returnOp.getOperands()) { + if (auto measureOp = operand.getDefiningOp()) { + state.returnedMeasurements.insert(measureOp.getOperation()); + } else { + keptOperands.push_back(operand); + keptReturnTypes.push_back(operand.getType()); + } + } + + if (keptOperands.empty() && !returnOp.getOperands().empty()) { + OpBuilder builder(returnOp); + auto zero = + arith::ConstantIntOp::create(builder, returnOp.getLoc(), 0, 64); + keptOperands.push_back(zero); + keptReturnTypes.push_back(zero.getType()); + } + + returnOp.getOperandsMutable().assign(keptOperands); + + funcOp.setFunctionType(FunctionType::get( + funcOp.getContext(), funcOp.getFunctionType().getInputs(), + keptReturnTypes)); + }); + }); +} + } // namespace mlir diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 03d7acb815..3a06d4b593 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -47,12 +47,14 @@ QCProgramBuilder::QCProgramBuilder(MLIRContext* context) ctx->loadDialect(); } -void QCProgramBuilder::initialize() { +void QCProgramBuilder::initialize() { initialize({getI64Type()}); } + +void QCProgramBuilder::initialize(TypeRange returnTypes) { // Set insertion point to the module body setInsertionPointToStart(cast(module).getBody()); // Create main function as entry point - auto funcType = getFunctionType({}, {getI64Type()}); + auto funcType = getFunctionType({}, returnTypes); auto mainFunc = func::FuncOp::create(*this, "main", funcType); // Add entry_point attribute to identify the main function @@ -65,6 +67,16 @@ void QCProgramBuilder::initialize() { regionStack.emplace_back(entryBlock.getParent()); } +void QCProgramBuilder::retype(TypeRange returnTypes) { + auto mainFunc = getEntryPoint(mlir::cast(module)); + if (!mainFunc) { + llvm::reportFatalUsageError("Main function not found for retyping"); + } + auto funcType = + getFunctionType(mainFunc.getFunctionType().getInputs(), returnTypes); + mainFunc.setType(funcType); +} + Value QCProgramBuilder::boolConstant(const bool value) { checkFinalized(); return arith::ConstantOp::create(*this, getBoolAttr(value)).getResult(); @@ -245,7 +257,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) QCProgramBuilder& QCProgramBuilder::mc##OP_NAME(ValueRange controls, \ Value target) { \ ctrl(controls, target, \ - [&](ValueRange targets) { OP_CLASS::create(*this, targets[0]); }); \ + [&](Value targetArg) { OP_CLASS::create(*this, targetArg); }); \ return *this; \ } @@ -281,9 +293,8 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) const std::variant&(PARAM), ValueRange controls, \ Value target) { \ auto param = variantToValue(*this, getLoc(), PARAM); \ - ctrl(controls, target, [&](ValueRange targets) { \ - OP_CLASS::create(*this, targets[0], param); \ - }); \ + ctrl(controls, target, \ + [&](Value targetArg) { OP_CLASS::create(*this, targetArg, param); }); \ return *this; \ } @@ -316,8 +327,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) Value target) { \ auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ - ctrl(controls, target, [&](ValueRange targets) { \ - OP_CLASS::create(*this, targets[0], param1, param2); \ + ctrl(controls, target, [&](Value targetArg) { \ + OP_CLASS::create(*this, targetArg, param1, param2); \ }); \ return *this; \ } @@ -354,8 +365,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ auto param3 = variantToValue(*this, getLoc(), PARAM3); \ - ctrl(controls, target, [&](ValueRange targets) { \ - OP_CLASS::create(*this, targets[0], param1, param2, param3); \ + ctrl(controls, target, [&](Value targetArg) { \ + OP_CLASS::create(*this, targetArg, param1, param2, param3); \ }); \ return *this; \ } @@ -476,6 +487,22 @@ QCProgramBuilder::ctrl(ValueRange controls, ValueRange targets, return *this; } +QCProgramBuilder& +QCProgramBuilder::ctrl(ValueRange controls, Value target, + const function_ref& body) { + checkFinalized(); + CtrlOp::create(*this, controls, target, body); + return *this; +} + +QCProgramBuilder& +QCProgramBuilder::ctrl(Value control, Value target, + const function_ref& body) { + checkFinalized(); + CtrlOp::create(*this, control, target, body); + return *this; +} + QCProgramBuilder& QCProgramBuilder::inv(ValueRange qubits, const function_ref& body) { @@ -484,6 +511,13 @@ QCProgramBuilder::inv(ValueRange qubits, return *this; } +QCProgramBuilder& QCProgramBuilder::inv(Value qubit, + const function_ref& body) { + checkFinalized(); + InvOp::create(*this, qubit, body); + return *this; +} + //===----------------------------------------------------------------------===// // SCF operations //===----------------------------------------------------------------------===// @@ -630,6 +664,13 @@ void QCProgramBuilder::ensureAllocationMode( OwningOpRef QCProgramBuilder::finalize() { checkFinalized(); + auto exitCode = intConstant(0); + return finalize({exitCode}); +} + +OwningOpRef QCProgramBuilder::finalize(ValueRange returnValues) { + checkFinalized(); + // Ensure that main function exists and insertion point is valid auto* insertionBlock = getInsertionBlock(); func::FuncOp mainFunc = nullptr; @@ -660,11 +701,8 @@ OwningOpRef QCProgramBuilder::finalize() { } allocatedMemrefs.clear(); - // Create constant 0 for successful exit code - auto exitCode = intConstant(0); - - // Add return statement with exit code 0 to the main function - func::ReturnOp::create(*this, exitCode); + // Add return statement with the given return values to the main function + func::ReturnOp::create(*this, returnValues); // Invalidate context to prevent use-after-finalize ctx = nullptr; @@ -675,11 +713,22 @@ OwningOpRef QCProgramBuilder::finalize() { OwningOpRef QCProgramBuilder::build( MLIRContext* context, - const function_ref& buildFunc) { + const function_ref(QCProgramBuilder&)>& buildFunc) { + QCProgramBuilder builder(context); + builder.initialize(); + auto result = buildFunc(builder); + builder.retype(ValueRange(result).getTypes()); + return builder.finalize(result); +} + +OwningOpRef QCProgramBuilder::build( + MLIRContext* context, + const function_ref& buildFunc) { QCProgramBuilder builder(context); builder.initialize(); - buildFunc(builder); - return builder.finalize(); + auto result = buildFunc(builder); + builder.retype(result.getType()); + return builder.finalize(result); } } // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp index 86dd60e8d1..7f5ea5de44 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,9 @@ #include #include +#include #include +#include using namespace mlir; using namespace mlir::qc; @@ -163,6 +166,21 @@ struct EraseEmptyCtrl final : OpRewritePattern { } // namespace +static void +buildModifierBody(OpBuilder& odsBuilder, OperationState& odsState, + const size_t numBlockArgs, + const function_ref& emitBody) { + auto& block = odsState.regions.front()->emplaceBlock(); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < numBlockArgs; ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + emitBody(odsBuilder, block); +} + size_t CtrlOp::getNumBodyUnitaries() { return utils::getNumBodyUnitaries(*getBody()); } @@ -175,17 +193,34 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, ValueRange targets, const function_ref& body) { build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); + buildModifierBody(odsBuilder, odsState, targets.size(), + [&](OpBuilder& builder, Block& block) { + body(block.getArguments()); + YieldOp::create(builder, odsState.location); + }); +} - auto qubitType = QubitType::get(odsBuilder.getContext()); - for (size_t i = 0; i < targets.size(); ++i) { - block.addArgument(qubitType, odsState.location); - } +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange controls, Value target, + const function_ref& bodyBuilder) { + odsState.addOperands(controls); + odsState.addOperands(target); + llvm::copy( + llvm::ArrayRef({static_cast(controls.size()), 1}), + odsState.getOrAddProperties() + .operandSegmentSizes.begin()); + odsState.addRegion(); + buildModifierBody(odsBuilder, odsState, 1, + [&](OpBuilder& builder, Block& block) { + bodyBuilder(block.getArgument(0)); + YieldOp::create(builder, odsState.location); + }); +} - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - body(block.getArguments()); - YieldOp::create(odsBuilder, odsState.location); +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + Value control, Value target, + const function_ref& bodyBuilder) { + build(odsBuilder, odsState, ValueRange{control}, target, bodyBuilder); } LogicalResult CtrlOp::verify() { diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp index b1caa45344..0a6bed0645 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include +#include #include #include @@ -300,6 +302,21 @@ struct EraseEmptyInv final : OpRewritePattern { } // namespace +static void +buildModifierBody(OpBuilder& odsBuilder, OperationState& odsState, + const size_t numBlockArgs, + const function_ref& emitBody) { + auto& block = odsState.regions.front()->emplaceBlock(); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < numBlockArgs; ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + emitBody(odsBuilder, block); +} + size_t InvOp::getNumBodyUnitaries() { return utils::getNumBodyUnitaries(*getBody()); } @@ -312,17 +329,22 @@ void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange qubits, const function_ref& body) { build(odsBuilder, odsState, qubits); - auto& block = odsState.regions.front()->emplaceBlock(); - - auto qubitType = QubitType::get(odsBuilder.getContext()); - for (size_t i = 0; i < qubits.size(); ++i) { - block.addArgument(qubitType, odsState.location); - } + buildModifierBody(odsBuilder, odsState, qubits.size(), + [&](OpBuilder& builder, Block& block) { + body(block.getArguments()); + YieldOp::create(builder, odsState.location); + }); +} - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - body(block.getArguments()); - YieldOp::create(odsBuilder, odsState.location); +void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, Value qubit, + const function_ref& bodyBuilder) { + odsState.addOperands(qubit); + odsState.addRegion(); + buildModifierBody(odsBuilder, odsState, 1, + [&](OpBuilder& builder, Block& block) { + bodyBuilder(block.getArgument(0)); + YieldOp::create(builder, odsState.location); + }); } LogicalResult InvOp::verify() { diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index bfc7bfe989..eab7843753 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -220,7 +220,44 @@ class MLIRQasmImporter final : public qasm3::InstVisitor { } } - OwningOpRef finalize() { return builder.finalize(); } + OwningOpRef finalize() { + if (outputRegisters.empty()) { + outputRegisters = allBitRegisters; + } + + if (outputRegisters.empty()) { + return builder.finalize(); + } + + // Collect measurement results for all output bit registers + SmallVector returnValues; + for (const auto& regName : outputRegisters) { + auto it = bitValues.find(regName); + if (it == bitValues.end()) { + llvm::errs() << "Output register '" << regName + << "' was never measured.\n"; + return nullptr; + } + + auto expectedSize = classicalRegisters[regName].size; + if (it->second.size() < expectedSize) { + llvm::errs() << "Not all bits of output register '" << regName + << "' have been measured.\n"; + return nullptr; + } + for (auto bit : it->second) { + if (!bit) { + llvm::errs() << "Not all bits of output register '" << regName + << "' have been measured.\n"; + return nullptr; + } + returnValues.push_back(bit); + } + } + + builder.retype(ValueRange(returnValues).getTypes()); + return builder.finalize(returnValues); + } private: QCProgramBuilder builder; @@ -238,6 +275,12 @@ class MLIRQasmImporter final : public qasm3::InstVisitor { /// Map from classical-register name to measurement results. llvm::StringMap> bitValues; + /// Names of all bit registers, in declaration order. + SmallVector allBitRegisters; + + /// Names of classical registers declared as output, in declaration order. + SmallVector outputRegisters; + /// Map from gate identifier to OpenQASM 3 definition. llvm::StringMap> gates; @@ -361,6 +404,13 @@ class MLIRQasmImporter final : public qasm3::InstVisitor { case qasm3::Int: case qasm3::Uint: { classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); + if (sizedType->type == qasm3::Bit) { + allBitRegisters.push_back(id); + if (stmt->isOutput || openQASM2CompatMode) { + // We return `output` bits in QASM3, or all named bits in QASM2. + outputRegisters.push_back(id); + } + } break; } default: diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index 7a2cf23651..4b798de54b 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -729,7 +730,7 @@ static LogicalResult addIfElseOp(QCProgramBuilder& builder, * @brief Translates an operation from QuantumComputation to QC dialect * * @param builder The QCProgramBuilder used to create operations - * @param quantumComputation The quantum computation to translate + * @param operation The operation to translate * @param state The translation state * @return Success if all supported operations were translated */ @@ -849,7 +850,13 @@ OwningOpRef translateQuantumComputationToQC( MLIRContext* context, const ::qc::QuantumComputation& quantumComputation) { // Create and initialize the builder (creates module and main function) QCProgramBuilder builder(context); - builder.initialize(); + SmallVector resultTypes(quantumComputation.getNcbits(), + builder.getI1Type()); + if (quantumComputation.getNcbits() == 0) { + // Without classical bits, we instead return an exit code 0. + resultTypes.push_back(builder.getI64Type()); + } + builder.initialize(resultTypes); // Allocate quantum registers using the builder const auto qregs = allocateQregs(builder, quantumComputation); @@ -876,6 +883,13 @@ OwningOpRef translateQuantumComputationToQC( // Finalize and return the module (adds return statement and transfers // ownership) + if (quantumComputation.getNcbits() > 0) { + if (llvm::any_of(state.results, [](Value v) { return !v; })) { + llvm::errs() << "Not all classical bits were measured.\n"; + return nullptr; + } + return builder.finalize(state.results); + } return builder.finalize(); } diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index da20f3bfa4..f766602a24 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -52,12 +53,14 @@ QCOProgramBuilder::QCOProgramBuilder(MLIRContext* context) ctx->loadDialect(); } -void QCOProgramBuilder::initialize() { +void QCOProgramBuilder::initialize() { initialize({getI64Type()}); } + +void QCOProgramBuilder::initialize(TypeRange returnTypes) { // Set insertion point to the module body setInsertionPointToStart(mlir::cast(module).getBody()); // Create main function as entry point - auto funcType = getFunctionType({}, {getI64Type()}); + auto funcType = getFunctionType({}, returnTypes); auto mainFunc = func::FuncOp::create(*this, "main", funcType); // Add entry_point attribute to identify the main function @@ -69,6 +72,16 @@ void QCOProgramBuilder::initialize() { setInsertionPointToStart(&entryBlock); } +void QCOProgramBuilder::retype(TypeRange returnTypes) { + auto mainFunc = getEntryPoint(mlir::cast(module)); + if (!mainFunc) { + llvm::reportFatalUsageError("Main function not found for retyping"); + } + auto funcType = + getFunctionType(mainFunc.getFunctionType().getInputs(), returnTypes); + mainFunc.setType(funcType); +} + Value QCOProgramBuilder::intConstant(const int64_t value) { checkFinalized(); return arith::ConstantOp::create(*this, getI64IntegerAttr(value)).getResult(); @@ -400,7 +413,8 @@ std::pair QCOProgramBuilder::measure(Value qubit) { return {qubitOut, result}; } -Value QCOProgramBuilder::measure(Value qubit, const Bit& bit) { +std::pair QCOProgramBuilder::measure(Value qubit, + const Bit& bit) { checkFinalized(); auto nameAttr = getStringAttr(bit.registerName); @@ -413,7 +427,7 @@ Value QCOProgramBuilder::measure(Value qubit, const Bit& bit) { // Update tracking updateQubitTracking(qubit, qubitOut); - return qubitOut; + return {qubitOut, measureOp.getResult()}; } Value QCOProgramBuilder::reset(Value qubit) { @@ -480,19 +494,15 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) Value target) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = \ - ctrl(control, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(targets[0])}; \ - }); \ - return {controlsOut[0], targetsOut[0]}; \ + ctrl(control, target, [&](Value target) { return OP_NAME(target); }); \ + return {controlsOut, targetsOut}; \ } \ std::pair QCOProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value target) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = \ - ctrl(controls, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(targets[0])}; \ - }); \ - return {controlsOut, targetsOut[0]}; \ + ctrl(controls, target, [&](Value target) { return OP_NAME(target); }); \ + return {controlsOut, targetsOut}; \ } DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) @@ -526,10 +536,9 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) checkFinalized(); \ auto param = variantToValue(*this, getLoc(), PARAM); \ const auto [controlsOut, targetsOut] = \ - ctrl(control, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param, targets[0])}; \ - }); \ - return {controlsOut[0], targetsOut[0]}; \ + ctrl(control, target, \ + [&](Value target) { return OP_NAME(param, target); }); \ + return {controlsOut, targetsOut}; \ } \ std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ @@ -537,10 +546,9 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) checkFinalized(); \ auto param = variantToValue(*this, getLoc(), PARAM); \ const auto [controlsOut, targetsOut] = \ - ctrl(controls, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param, targets[0])}; \ - }); \ - return {controlsOut, targetsOut[0]}; \ + ctrl(controls, target, \ + [&](Value target) { return OP_NAME(param, target); }); \ + return {controlsOut, targetsOut}; \ } DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) @@ -570,10 +578,9 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ const auto [controlsOut, targetsOut] = \ - ctrl(control, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param1, param2, targets[0])}; \ - }); \ - return {controlsOut[0], targetsOut[0]}; \ + ctrl(control, target, \ + [&](Value target) { return OP_NAME(param1, param2, target); }); \ + return {controlsOut, targetsOut}; \ } \ std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ @@ -583,10 +590,9 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ const auto [controlsOut, targetsOut] = \ - ctrl(controls, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param1, param2, targets[0])}; \ - }); \ - return {controlsOut, targetsOut[0]}; \ + ctrl(controls, target, \ + [&](Value target) { return OP_NAME(param1, param2, target); }); \ + return {controlsOut, targetsOut}; \ } DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) @@ -618,10 +624,10 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) auto param2 = variantToValue(*this, getLoc(), PARAM2); \ auto param3 = variantToValue(*this, getLoc(), PARAM3); \ const auto [controlsOut, targetsOut] = \ - ctrl(control, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param1, param2, param3, targets[0])}; \ + ctrl(control, target, [&](Value target) { \ + return OP_NAME(param1, param2, param3, target); \ }); \ - return {controlsOut[0], targetsOut[0]}; \ + return {controlsOut, targetsOut}; \ } \ std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ @@ -633,10 +639,10 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) auto param2 = variantToValue(*this, getLoc(), PARAM2); \ auto param3 = variantToValue(*this, getLoc(), PARAM3); \ const auto [controlsOut, targetsOut] = \ - ctrl(controls, target, [&](ValueRange targets) -> SmallVector { \ - return {OP_NAME(param1, param2, param3, targets[0])}; \ + ctrl(controls, target, [&](Value target) { \ + return OP_NAME(param1, param2, param3, target); \ }); \ - return {controlsOut, targetsOut[0]}; \ + return {controlsOut, targetsOut}; \ } DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) @@ -879,6 +885,56 @@ QCOProgramBuilder::inv(ValueRange qubits, return targetsOut; } +std::pair +QCOProgramBuilder::ctrl(ValueRange controls, Value target, + function_ref body) { + checkFinalized(); + + Value innerTargetOut; + auto ctrlOp = + CtrlOp::create(*this, controls, target, [&](Value targetArg) -> Value { + updateQubitTracking(target, targetArg); + innerTargetOut = body(targetArg); + return innerTargetOut; + }); + + const auto& controlsOut = ctrlOp.getControlsOut(); + for (const auto& [control, controlOut] : + llvm::zip_equal(controls, controlsOut)) { + updateQubitTracking(control, controlOut); + } + const auto& targetsOut = ctrlOp.getTargetsOut(); + assert(targetsOut.size() == 1); + updateQubitTracking(innerTargetOut, targetsOut.front()); + + return {controlsOut, targetsOut.front()}; +} + +std::pair +QCOProgramBuilder::ctrl(Value control, Value target, + function_ref body) { + const auto [controlsOut, targetOut] = ctrl(ValueRange{control}, target, body); + assert(controlsOut.size() == 1); + return {controlsOut.front(), targetOut}; +} + +Value QCOProgramBuilder::inv(Value qubit, function_ref body) { + checkFinalized(); + + Value innerQubitOut; + auto invOp = InvOp::create(*this, qubit, [&](Value qubitArg) -> Value { + updateQubitTracking(qubit, qubitArg); + innerQubitOut = body(qubitArg); + return innerQubitOut; + }); + + const auto& qubitsOut = invOp.getQubitsOut(); + assert(qubitsOut.size() == 1); + updateQubitTracking(innerQubitOut, qubitsOut.front()); + + return qubitsOut.front(); +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// @@ -1103,6 +1159,13 @@ void QCOProgramBuilder::ensureAllocationMode( OwningOpRef QCOProgramBuilder::finalize() { checkFinalized(); + auto exitCode = intConstant(0); + return finalize({exitCode}); +} + +OwningOpRef QCOProgramBuilder::finalize(ValueRange returnValues) { + checkFinalized(); + // Ensure that main function exists and insertion point is valid auto* insertionBlock = getInsertionBlock(); func::FuncOp mainFunc = nullptr; @@ -1151,11 +1214,8 @@ OwningOpRef QCOProgramBuilder::finalize() { validQubits.clear(); validTensors.clear(); - // Create constant 0 for successful exit code - auto exitCode = intConstant(0); - - // Add return statement with exit code 0 to the main function - func::ReturnOp::create(*this, exitCode); + // Add return statement with the given return values to the main function + func::ReturnOp::create(*this, returnValues); // Invalidate context to prevent use-after-finalize ctx = nullptr; @@ -1165,11 +1225,22 @@ OwningOpRef QCOProgramBuilder::finalize() { OwningOpRef QCOProgramBuilder::build( MLIRContext* context, - const function_ref& buildFunc) { + const function_ref(QCOProgramBuilder&)>& buildFunc) { + QCOProgramBuilder builder(context); + builder.initialize(); + auto result = buildFunc(builder); + builder.retype(ValueRange(result).getTypes()); + return builder.finalize(result); +} + +OwningOpRef QCOProgramBuilder::build( + MLIRContext* context, + const function_ref& buildFunc) { QCOProgramBuilder builder(context); builder.initialize(); - buildFunc(builder); - return builder.finalize(); + auto result = buildFunc(builder); + builder.retype(result.getType()); + return builder.finalize(result); } } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 657c90b8a1..f4b6f6b51f 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -195,6 +195,21 @@ struct EraseEmptyCtrl final : OpRewritePattern { } // namespace +static void +buildModifierBody(OpBuilder& odsBuilder, OperationState& odsState, + const size_t numBlockArgs, + const function_ref& emitBody) { + auto& block = odsState.regions.front()->emplaceBlock(); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < numBlockArgs; ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + emitBody(odsBuilder, block); +} + size_t CtrlOp::getNumBodyUnitaries() { return utils::getNumBodyUnitaries(*getBody()); } @@ -224,17 +239,29 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, ValueRange targets, function_ref(ValueRange)> bodyBuilder) { build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); + buildModifierBody(odsBuilder, odsState, targets.size(), + [&](OpBuilder& builder, Block& block) { + YieldOp::create(builder, odsState.location, + bodyBuilder(block.getArguments())); + }); +} - auto qubitType = QubitType::get(odsBuilder.getContext()); - for (size_t i = 0; i < targets.size(); ++i) { - block.addArgument(qubitType, odsState.location); - } +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange controls, Value target, + function_ref bodyBuilder) { + build(odsBuilder, odsState, controls.getTypes(), target.getType(), controls, + target); + buildModifierBody(odsBuilder, odsState, 1, + [&](OpBuilder& builder, Block& block) { + YieldOp::create(builder, odsState.location, + bodyBuilder(block.getArgument(0))); + }); +} - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - YieldOp::create(odsBuilder, odsState.location, - bodyBuilder(block.getArguments())); +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + Value control, Value target, + function_ref bodyBuilder) { + build(odsBuilder, odsState, ValueRange{control}, target, bodyBuilder); } LogicalResult CtrlOp::verify() { @@ -313,10 +340,10 @@ std::optional CtrlOp::getUnitaryMatrix() { // Build `I_{2^controls} ⊗ U` by placing the target block in the bottom-right // corner of a `2^controls * targetDim` identity. const auto controlledMatrix = - [numControls](const std::int64_t targetDim, + [numControls](const int64_t targetDim, const auto& targetBlock) -> DynamicMatrix { auto matrix = DynamicMatrix::identity(static_cast( - (1ULL << numControls) * static_cast(targetDim))); + (1ULL << numControls) * static_cast(targetDim))); matrix.setBottomRightCorner(targetBlock); return matrix; }; @@ -332,11 +359,9 @@ std::optional CtrlOp::getUnitaryMatrix() { return std::nullopt; } - // Composed single-qubit body (e.g. `ctrl { h; x }`); embed the 2x2 directly. - if (getNumTargets() == 1) { - if (const auto composed = composeSingleQubitBodyMatrix(*getBody())) { - return controlledMatrix(2, *composed); - } + // Composed body (e.g., `ctrl { h; x }` or `ctrl { swap; ry }`) + if (const auto composed = composeBodyMatrix(*getBody(), getNumTargets())) { + return controlledMatrix(composed->rows(), *composed); } return std::nullopt; diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index 675e835638..c58e63c564 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -318,6 +319,21 @@ struct EraseEmptyInv final : OpRewritePattern { } // namespace +static void +buildModifierBody(OpBuilder& odsBuilder, OperationState& odsState, + const size_t numBlockArgs, + const function_ref& emitBody) { + auto& block = odsState.regions.front()->emplaceBlock(); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < numBlockArgs; ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + emitBody(odsBuilder, block); +} + size_t InvOp::getNumBodyUnitaries() { return utils::getNumBodyUnitaries(*getBody()); } @@ -347,17 +363,21 @@ void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange qubits, function_ref(ValueRange)> bodyBuilder) { build(odsBuilder, odsState, qubits); - auto& block = odsState.regions.front()->emplaceBlock(); - - auto qubitType = QubitType::get(odsBuilder.getContext()); - for (size_t i = 0; i < qubits.size(); ++i) { - block.addArgument(qubitType, odsState.location); - } + buildModifierBody(odsBuilder, odsState, qubits.size(), + [&](OpBuilder& builder, Block& block) { + YieldOp::create(builder, odsState.location, + bodyBuilder(block.getArguments())); + }); +} - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - YieldOp::create(odsBuilder, odsState.location, - bodyBuilder(block.getArguments())); +void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, Value qubit, + function_ref bodyBuilder) { + build(odsBuilder, odsState, qubit.getType(), qubit); + buildModifierBody(odsBuilder, odsState, 1, + [&](OpBuilder& builder, Block& block) { + YieldOp::create(builder, odsState.location, + bodyBuilder(block.getArgument(0))); + }); } LogicalResult InvOp::verify() { @@ -427,12 +447,9 @@ std::optional InvOp::getUnitaryMatrix() { return std::nullopt; } - // Composed single-qubit body (e.g. `inv { h; t }`). - if (getNumTargets() != 1) { - return std::nullopt; - } - if (const auto composed = composeSingleQubitBodyMatrix(*getBody())) { - return DynamicMatrix::fromAdjoint(*composed); + // Composed body (e.g., `ctrl { h; x }` or `ctrl { swap; ry }`) + if (const auto composed = composeBodyMatrix(*getBody(), getNumTargets())) { + return composed->adjoint(); } return std::nullopt; } diff --git a/mlir/lib/Dialect/QCO/IR/QCOUtils.cpp b/mlir/lib/Dialect/QCO/IR/QCOUtils.cpp index 92f47d6d2b..3fcf3a5a73 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOUtils.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOUtils.cpp @@ -14,6 +14,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" +#include #include #include #include @@ -22,53 +23,130 @@ #include #include +#include +#include #include namespace mlir::qco { -std::optional composeSingleQubitBodyMatrix(Block& block) { - Matrix2x2 acc = Matrix2x2::identity(); +/// Returns the wire index for @p wire in @p wireIds, or `std::nullopt` if +/// untracked. +[[nodiscard]] static std::optional +lookupWireId(const DenseMap& wireIds, Value wire) { + if (const auto it = wireIds.find(wire); it != wireIds.end()) { + return it->second; + } + return std::nullopt; +} + +/// Propagates wire indices from unitary inputs to outputs via @p wireIds. +static void propagateWireIds(UnitaryOpInterface unitary, + DenseMap& wireIds) { + for (auto [input, output] : + llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { + if (const auto wire = lookupWireId(wireIds, input)) { + wireIds[output] = *wire; + } + } +} + +/// Returns the @p unitary embedded on @p numTargets modifier wires using @p +/// wireIds. +[[nodiscard]] static std::optional +embedUnitaryInBody(UnitaryOpInterface unitary, size_t numTargets, + const DenseMap& wireIds) { + const auto numOpQubits = unitary.getNumQubits(); + if (numOpQubits == 0 || numOpQubits > 2) { + return std::nullopt; + } + + if (numOpQubits == 1) { + const auto wire = lookupWireId(wireIds, unitary.getInputQubit(0)); + if (!wire.has_value()) { + return std::nullopt; + } + return unitary.getUnitaryMatrix()->embedInNqubit(numTargets, + *wire); + } + + const auto q0 = lookupWireId(wireIds, unitary.getInputQubit(0)); + const auto q1 = lookupWireId(wireIds, unitary.getInputQubit(1)); + if (!q0.has_value() || !q1.has_value()) { + return std::nullopt; + } + return unitary.getUnitaryMatrix()->embedInNqubit(numTargets, *q0, + *q1); +} + +std::optional composeBodyMatrix(Block& block, + size_t numTargets) { + if (numTargets == 0 || numTargets > kMaxModifierTargetQubits || + block.getNumArguments() != numTargets) { + return std::nullopt; + } + + std::optional acc; Complex global{1.0, 0.0}; bool found = false; + + DenseMap wireIds; + for (size_t i = 0; i < numTargets; ++i) { + wireIds[block.getArgument(i)] = i; + } + for (Operation& op : block.without_terminator()) { - if (!TypeSwitch(&op) - .Case([](auto) { return true; }) - .Case([&](GPhaseOp gphase) { - const auto matrix = gphase.getUnitaryMatrix(); - if (!matrix) { - return false; - } - global *= (*matrix)(0, 0); - found = true; - return true; - }) - .Case([&](UnitaryOpInterface unitary) { - if (!unitary.isSingleQubit() || - !unitary.hasCompileTimeKnownUnitaryMatrix()) { - return false; - } - Matrix2x2 matrix; - if (!unitary.getUnitaryMatrix2x2(matrix)) { - return false; - } - acc.premultiplyBy(matrix); - found = true; - return true; - }) - .Default([](Operation* operation) { - const auto usesQubit = [](Value value) { - return isa(value.getType()); - }; - return !llvm::any_of(operation->getOperands(), usesQubit) && - !llvm::any_of(operation->getResults(), usesQubit); - })) { + const bool handled = + TypeSwitch(&op) + .Case([&](BarrierOp barrier) { + propagateWireIds(barrier, wireIds); + return true; + }) + .Case([&](GPhaseOp gphase) { + const auto matrix = gphase.getUnitaryMatrix(); + if (!matrix) { + return false; + } + global *= matrix->value; + found = true; + return true; + }) + .Case([&](UnitaryOpInterface unitary) { + if (!unitary.hasCompileTimeKnownUnitaryMatrix()) { + return false; + } + auto embedded = embedUnitaryInBody(unitary, numTargets, wireIds); + if (!embedded.has_value()) { + return false; + } + if (!acc.has_value()) { + acc.swap(embedded); + } else { + acc->premultiplyBy(*embedded); + } + found = true; + propagateWireIds(unitary, wireIds); + return true; + }) + .Default([&](Operation* unknown) { + const auto usesQubit = [](Value value) { + return isa(value.getType()); + }; + return !llvm::any_of(unknown->getOperands(), usesQubit) && + !llvm::any_of(unknown->getResults(), usesQubit); + }); + + if (!handled) { return std::nullopt; } } + if (!found) { return std::nullopt; } - acc *= global; + if (!acc.has_value()) { + acc = DynamicMatrix::identity(static_cast(1ULL << numTargets)); + } + *acc *= global; return acc; } diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index ec8b33b833..c07fa324aa 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -8,15 +8,40 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include +#include +#include #include +#include #include +#include +#include #include +#include using namespace mlir; using namespace mlir::qco; +/** + * @brief Check if given quantum operation is unused (i.e., only used by + * sinks and no memory effects). + * + * @param op The operation to check. + * @return bool True if the operation is unused, false otherwise. + */ +static bool checkDeadGate(Operation* op) { + if (!isMemoryEffectFree(op)) { + // This ignores operations and regions that have children with memory + // effects, which should never be considered dead. + return false; + } + return llvm::all_of(op->getUsers(), + [](Operation* user) { return isa(user); }); +} + namespace { /** @@ -39,9 +64,65 @@ struct RemoveAllocSinkPair final : OpRewritePattern { } }; +/** + * @brief Remove dead gates. + */ +struct DeadGateElimination final : OpRewritePattern { + + explicit DeadGateElimination(MLIRContext* context) + : OpRewritePattern(context) {} + + LogicalResult matchAndRewrite(SinkOp op, + PatternRewriter& rewriter) const override { + Value currentValue = op.getQubit(); + auto* currentOp = currentValue.getDefiningOp(); + auto success = false; + while (currentOp != nullptr) { + if (!checkDeadGate(currentOp)) { + break; + } + + currentValue = + TypeSwitch(currentOp) + .Case([&](auto measureOp) { + auto newValue = measureOp.getQubitIn(); + rewriter.replaceAllUsesWith(measureOp.getQubitOut(), newValue); + rewriter.eraseOp(measureOp); + return newValue; + }) + .Case([&](auto ifOp) { + auto* tiedQubit = + ifOp.getTiedQubit(cast(currentValue)); + auto newValue = tiedQubit->get(); + rewriter.replaceOp(ifOp, ifOp.getQubits()); + return newValue; + }) + .Case([&](auto resetOp) { + auto newValue = resetOp.getQubitIn(); + rewriter.replaceOp(resetOp, resetOp->getOperands()); + return newValue; + }) + .Case([&](auto unitaryOp) { + auto newValue = unitaryOp.getInputForOutput(currentValue); + rewriter.replaceOp(unitaryOp, unitaryOp.getInputQubits()); + return newValue; + }) + .Default([&](auto) { return nullptr; }); + + if (currentValue == nullptr) { + break; + } + currentOp = currentValue.getDefiningOp(); + success = true; + } + + return llvm::success(success); + } +}; + } // namespace void SinkOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp index c377947153..c232728250 100644 --- a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp @@ -313,15 +313,55 @@ BlockArgument IfOp::getTiedElseBlockArgument(OpOperand* qubit) { } OpOperand* IfOp::getTiedThenYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &thenYield().getTargetsMutable()[bbArg.getArgNumber()]; } OpOperand* IfOp::getTiedElseYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &elseYield().getTargetsMutable()[bbArg.getArgNumber()]; } + +IfOp IfOp::replaceWithAdditionalQubits(RewriterBase& rewriter, + ValueRange addons) { + if (addons.empty()) { + return *this; + } + + SmallVector allQubits; + allQubits.reserve(getQubits().size() + addons.size()); + allQubits.append(getQubits().begin(), getQubits().end()); + allQubits.append(addons.begin(), addons.end()); + const auto allQubitTypes = ValueRange(allQubits).getTypes(); + + auto newIfOp = create(rewriter, getLoc(), getCondition(), allQubits); + + const auto rewriteRegion = [&rewriter, &allQubitTypes, + &addons](Region& oldRegion, Region& newRegion) { + auto* oldBlock = &oldRegion.front(); + const auto numOldArgs = oldBlock->getNumArguments(); + auto* newBlock = rewriter.createBlock(&newRegion, {}, allQubitTypes); + const auto oldArgs = newBlock->getArguments().take_front(numOldArgs); + const auto addonArgs = newBlock->getArguments().drop_front(numOldArgs); + + rewriter.mergeBlocks(oldBlock, newBlock, oldArgs); + + auto yield = cast(newBlock->getTerminator()); + SmallVector yieldedValues; + yieldedValues.reserve(yield.getTargets().size() + addons.size()); + yieldedValues.append(yield.getTargets().begin(), yield.getTargets().end()); + yieldedValues.append(addonArgs.begin(), addonArgs.end()); + rewriter.replaceOpWithNewOp(yield, yieldedValues); + }; + + rewriteRegion(getThenRegion(), newIfOp.getThenRegion()); + rewriteRegion(getElseRegion(), newIfOp.getElseRegion()); + + rewriter.eraseOp(*this); + + return newIfOp; +} diff --git a/mlir/lib/Dialect/QCO/Transforms/Decomposition/Weyl.cpp b/mlir/lib/Dialect/QCO/Transforms/Decomposition/Weyl.cpp index 2b2c9818e3..16d6332612 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Decomposition/Weyl.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Decomposition/Weyl.cpp @@ -705,13 +705,12 @@ LogicalResult synthesizeUnitary2QWeyl(OpBuilder& builder, Location loc, wire = *synthesized; }; const auto emitEntangler = [&]() { - auto ctrlOp = CtrlOp::create( - builder, loc, wire0, wire1, - [&](ValueRange targetArgs) -> SmallVector { + auto ctrlOp = + CtrlOp::create(builder, loc, wire0, wire1, [&](Value targetQubit) { if (emitCz) { - return {ZOp::create(builder, loc, targetArgs[0]).getOutputQubit(0)}; + return ZOp::create(builder, loc, targetQubit).getOutputQubit(0); } - return {XOp::create(builder, loc, targetArgs[0]).getOutputQubit(0)}; + return XOp::create(builder, loc, targetQubit).getOutputQubit(0); }); wire0 = ctrlOp.getOutputControl(0); wire1 = ctrlOp.getOutputTarget(0); diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 74a6d9e6db..8664675abb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -10,43 +10,50 @@ #include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" -#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include "mlir/Dialect/QCO/Utils/Algorithms.h" #include "mlir/Dialect/QCO/Utils/Drivers.h" +#include "mlir/Dialect/QCO/Utils/Graph.h" +#include "mlir/Dialect/QCO/Utils/Layout.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" +#include "mlir/Dialect/Utils/Utils.h" -#include #include #include #include +#include #include #include +#include #include #include +#include #include +#include #include #include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include -#include +#include #include #include -#include #include #include #include @@ -56,6 +63,7 @@ namespace mlir::qco { using namespace mlir::qtensor; +using namespace mlir::utils; #define GEN_PASS_DEF_MAPPINGPASS #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" @@ -64,235 +72,115 @@ namespace { struct MappingPass : impl::MappingPassBase { private: - using IndexType = size_t; - using IndexPairType = std::pair; + using IndexPairType = std::pair; using Window = SmallVector; - using Neighbours = SmallVector>; - - enum class RoutingMode : std::uint8_t { Cold, Hot }; - - /** - * @brief A qubit layout that maps program and hardware indices without - * storing Values. Used for efficient memory usage when Value tracking isn't - * needed. - * - * Note that we use the terminology "hardware" and "program" qubits here, - * because "virtual" (opposed to physical) and "static" (opposed to dynamic) - * are C++ keywords. - */ - class [[nodiscard]] Layout { - public: - /** - * @brief Constructs the identity (i->i) layout. - * @param nqubits The number of qubits. - * @return The identity layout. - */ - static Layout identity(const size_t nqubits) { - Layout layout(nqubits); - for (size_t i = 0; i < nqubits; ++i) { - layout.add(i, i); - } - return layout; - } - - /** - * @brief Constructs a random layout. - * @param nqubits The number of qubits. - * @param seed A seed for randomization. - * @return The random layout. - */ - static Layout random(const size_t nqubits, const size_t seed) { - SmallVector mapping(nqubits); - std::iota(mapping.begin(), mapping.end(), IndexType{0}); - std::ranges::shuffle(mapping, std::mt19937_64{seed}); - - Layout layout(nqubits); - for (const auto [prog, hw] : enumerate(mapping)) { - layout.add(prog, hw); - } - - return layout; - } - - /** - * @brief Insert program:hardware index mapping. - * @param prog The program index. - * @param hw The hardware index. - */ - void add(IndexType prog, IndexType hw) { - assert(prog < programToHardware_.size() && - "add: program index out of bounds"); - assert(hw < hardwareToProgram_.size() && - "add: hardware index out of bounds"); - programToHardware_[prog] = hw; - hardwareToProgram_[hw] = prog; - } - - /** - * @brief Look up program index for a hardware index. - * @param hw The hardware index. - * @return The program index of the respective hardware index. - */ - [[nodiscard]] IndexType getProgramIndex(const IndexType hw) const { - assert(hw < hardwareToProgram_.size() && - "getProgramIndex: hardware index out of bounds"); - return hardwareToProgram_[hw]; - } - - /** - * @brief Look up hardware index for a program index. - * @param prog The program index. - * @return The hardware index of the respective program index. - */ - [[nodiscard]] IndexType getHardwareIndex(const IndexType prog) const { - assert(prog < programToHardware_.size() && - "getHardwareIndex: program index out of bounds"); - return programToHardware_[prog]; - } - - /** - * @brief Convenience function to lookup multiple hardware indices at once. - * @param progs The program indices. - * @return A tuple of hardware indices. - */ - template - requires(sizeof...(ProgIndices) > 0) && - ((std::is_convertible_v) && ...) - [[nodiscard]] auto getHardwareIndices(ProgIndices... progs) const { - return std::tuple{getHardwareIndex(static_cast(progs))...}; - } - - /** - * @brief Convenience function to lookup multiple program indices at once. - * @param hws The hardware indices. - * @return A tuple of program indices. - */ - template - requires(sizeof...(HwIndices) > 0) && - ((std::is_convertible_v) && ...) - [[nodiscard]] auto getProgramIndices(HwIndices... hws) const { - return std::tuple{getProgramIndex(static_cast(hws))...}; - } - - /** - * @brief Swap the mapping to program indices of two hardware indices. - */ - void swap(const IndexType hw0, const IndexType hw1) { - const auto prog0 = hardwareToProgram_[hw0]; - const auto prog1 = hardwareToProgram_[hw1]; - - std::swap(hardwareToProgram_[hw0], hardwareToProgram_[hw1]); - std::swap(programToHardware_[prog0], programToHardware_[prog1]); - } - - /** - * @returns the number of qubits managed by the layout. - */ - [[nodiscard]] size_t nqubits() const { return programToHardware_.size(); } - - /** - * @returns the program to hardware mapping. - */ - [[nodiscard]] ArrayRef getProgramToHardware() const { - return programToHardware_; - } - - protected: - /** - * @brief Maps a program qubit index to its hardware index. - */ - SmallVector programToHardware_; - - /** - * @brief Maps a hardware qubit index to its program index. - */ - SmallVector hardwareToProgram_; + using Wires = SmallVector; + using RecursiveRoutingStackItem = std::pair>; + using RecursiveRoutingStack = SmallVector; - private: - explicit Layout(const size_t nqubits) - : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} - }; + enum class RoutingMode : bool { Cold, Hot }; - class [[nodiscard]] AugmentedDevice { + class AugmentedDevice { public: - AugmentedDevice() = default; - - AugmentedDevice(size_t nqubits, const Edges& coupling) - : nqubits_(nqubits), dist_(findAllShortestPaths(nqubits, coupling)), - coupling_(coupling), neighbours_(nqubits) { - for (const auto& [u, v] : coupling_) { - neighbours_[u].push_back(v); - } - } + explicit AugmentedDevice( + const DenseSet>& couplingSet) + : coupling_(couplingSet), dist_(coupling_.getDistMatrix()) {} - /** - * @returns the device's number of qubits. - */ - [[nodiscard]] size_t nqubits() const { return nqubits_; } + /// Return the device's number of qubits. + [[nodiscard]] size_t nqubits() const { return coupling_.getNumNodes(); } - /** - * @returns true if @p u and @p v are adjacent. - */ - [[nodiscard]] bool areAdjacent(size_t u, size_t v) const { - return coupling_.contains(std::make_pair(u, v)); + /// Return true if two qubits are adjacent. + [[nodiscard]] bool areAdjacent(const size_t u, const size_t v) const { + return dist_[u][v] == 1UL; } - /** - * @returns the length of the shortest path between @p u and @p v. - */ - [[nodiscard]] size_t distanceBetween(size_t u, size_t v) const { - if (dist_[u][v] == UINT64_MAX) { + /// Return the length of the shortest path between two qubits. + [[nodiscard]] size_t distanceBetween(const size_t u, const size_t v) const { + const auto dist = dist_[u][v]; + if (dist == UINT64_MAX) { report_fatal_error("Failed to compute the distance between qubits " + Twine(u) + " and " + Twine(v)); } - return dist_[u][v]; + return dist; } - /** - * @returns all neighbours of @p u. - */ - [[nodiscard]] ArrayRef neighboursOf(size_t u) const { - return neighbours_[u]; + /// Return the qubit identifiers. + [[nodiscard]] SmallVector qubits() const { + return coupling_.getNodes(); } - /** - * @returns the max degree (connectivity) of any qubit of the device. - */ - [[nodiscard]] size_t maxDegree() const { - size_t deg = 0; - for (const auto& nbrs : neighbours_) { - deg = std::max(deg, nbrs.size()); - } - return deg; + /// Return all neighbours of a qubit. + [[nodiscard]] ArrayRef neighboursOf(const size_t u) const { + return coupling_.getNeighbours(u); } + /// Return the max degree (connectivity) of any qubit of the device. + [[nodiscard]] size_t maxDegree() const { return coupling_.getMaxDegree(); } + private: - size_t nqubits_{}; - Matrix dist_; - Edges coupling_; - Neighbours neighbours_; + Graph coupling_; + Graph::DistanceMatrix dist_; }; - struct [[nodiscard]] Trial { - explicit Trial(Layout layout) : layout(std::move(layout)) {} + struct WireInfos { + /// Return the mapped wire index of a program index. + [[nodiscard]] size_t lookupIndex(const size_t prog) const { + return programToIndex_[prog]; + } + + /// Return the mapped program index of a wire index. + [[nodiscard]] size_t lookupProgram(const size_t index) const { + return indexToProgram_[index]; + } - Layout layout; - size_t nswaps{}; - bool success{false}; + /// Bidirectionally map a wire index to a program index. + /// Overwrites existing mappings. + void map(const size_t index, const size_t prog) { + if (index >= indexToProgram_.size()) { + indexToProgram_.resize(index + 1); + } + if (prog >= programToIndex_.size()) { + programToIndex_.resize(prog + 1); + } + indexToProgram_[index] = prog; + programToIndex_[prog] = index; + } + + /// Swap two program indices. + void swap(const size_t prog0, const size_t prog1) { + const auto i0 = lookupIndex(prog0); + const auto i1 = lookupIndex(prog1); + std::swap(programToIndex_[prog0], programToIndex_[prog1]); + std::swap(indexToProgram_[i0], indexToProgram_[i1]); + } + + private: + /// Maps the i-th wire index to a program index. + SmallVector indexToProgram_; + /// Maps a program index to the i-th wire index. + SmallVector programToIndex_; + }; + + /// Statistics collected while routing. + struct Statistics { + size_t nswaps{0}; }; - /** - * @brief Parameters influencing the behavior of the A* search algorithm. - */ - struct [[nodiscard]] Parameters { + /// Parameters influencing the behavior of the A* search algorithm. + struct Parameters { float alpha; float lambda; }; - /** - * @brief Describes a node in the A* search graph. - */ - struct [[nodiscard]] Node { + /// Utility-struct for routing functions. + struct RoutingBundle { + Wires wires; + WireInfos infos; + Layout layout; + }; + + /// Describes a node in the A* search graph. + struct Node { struct ComparePointer { bool operator()(const Node* lhs, const Node* rhs) const { return lhs->f > rhs->f; @@ -305,17 +193,13 @@ struct MappingPass : impl::MappingPassBase { size_t depth; float f; - /** - * @brief Construct a root node with the given layout. Initialize the - * sequence with an empty vector and set the cost to zero. - */ + /// Construct a root node with the given layout. Initialize the + /// sequence with an empty vector and set the cost to zero. explicit Node(Layout layout) : layout(std::move(layout)), parent(nullptr), depth(0), f(0) {} - /** - * @brief Construct a non-root node from its parent node. Apply the given - * swap to the layout of the parent node. - */ + /// Construct a non-root node from its parent node. Apply the given swap to + /// the layout of the parent node. Node(Node* parent, const IndexPairType& swap, const Window& window, const AugmentedDevice& device, const Parameters& params) : layout(parent->layout), swap(swap), parent(parent), @@ -324,36 +208,29 @@ struct MappingPass : impl::MappingPassBase { f = g(params.alpha) + h(window, device, params); // NOLINT } - /** - * @returns true if the current SWAP sequence makes all gates in the front - * executable. - */ + /// Return true, if the current SWAP sequence makes all gates in the front + /// executable. [[nodiscard]] bool isGoal(const IndexPairType& front, const AugmentedDevice& device) const { - return device.areAdjacent(layout.getHardwareIndex(front.first), - layout.getHardwareIndex(front.second)); + const auto [hw0, hw1] = + layout.getHardwareIndices(front.first, front.second); + return device.areAdjacent(hw0, hw1); } private: - /** - * @brief Calculate the path cost for the A* search algorithm. - * - * The path cost function is the weighted sum of the currently required - * SWAPs. - */ + /// Calculate the path cost for the A* search algorithm. + /// The path costs are the weighted sum of the currently required SWAPs. [[nodiscard]] float g(const float alpha) const { return alpha * static_cast(depth); } - /** - * @brief Calculate the heuristic cost for the A* search algorithm. - * - * Computes the minimal number of SWAPs required to route each gate in - * each layer. For each gate, this is determined by the shortest distance - * between its hardware qubits. Intuitively, this is the number of SWAPs - * that a naive router would insert to route the layers (with a constant - * layout). - */ + /// Calculate the heuristic cost for the A* search algorithm. + /// + /// Computes the minimal number of SWAPs required to route each gate in + /// each layer. For each gate, this is determined by the shortest distance + /// between its hardware qubits. Intuitively, this is the number of SWAPs + /// that a naive router would insert to route the layers (with a constant + /// layout). [[nodiscard]] float h(const Window& window, const AugmentedDevice& device, const Parameters& params) const { float costs{0}; @@ -370,26 +247,107 @@ struct MappingPass : impl::MappingPassBase { } }; + /// Describes the graph F of arXiv:1602.05150v3. + struct FGraph { + explicit FGraph(std::shared_ptr device) + : f_(device->qubits()), device_(std::move(device)) {}; + + /// Build F-graph: Add edges to F for each edge in the coupling graph. + /// Note that this assumes that the coupling graph is directed, but + /// symmetric (essentially: undirected). + void construct(const Layout& from, const Layout& to) { + for (const auto u : device_->qubits()) { + for (const auto v : device_->neighboursOf(u)) { + if (shouldAddEdge(u, v, from, to)) { + f_.addEdge(u, v); + } + } + } + } + + /// Try to find a directed cycle in the F graph. If there is one, + /// we can apply a happy swap chain. Note that this happy swap chain + /// does not include the final back edge closing the cycle because the + /// first SWAP changes the token (the qubit) on the target, invalidating + /// the edge in F. + [[nodiscard]] std::optional> + findHappySWAPChain() const { + const auto optCycle = f_.findCycle(); + if (!optCycle) { + return std::nullopt; + } + const auto& cycle = *optCycle; + + SmallVector swaps; + for (size_t i = cycle.size() - 1; i > 0; --i) { + swaps.emplace_back(cycle[i], cycle[i - 1]); + } + return swaps; + } + + /// Find an unhappy SWAP. That is, find an edge (u, v), where exchanging u + /// and v, reduces u's distance to its target location (by one) and + /// increases v's distance from 0 (already at the correct location) to one. + [[nodiscard]] std::optional findUnhappySWAP() const { + for (const auto u : f_.getNodes()) { + for (const auto v : f_.getNeighbours(u)) { + if (f_.getDegree(v) == 0) { + return {{u, v}}; + } + } + } + + return std::nullopt; + } + + /// Reset the F graph for rebuilding. + void reset() { f_.clearEdges(); } + + private: + /// Return true, if moving the program qubit on hardware qubit u to hardware + /// qubit v brings it closer to its destination hardware qubit. + [[nodiscard]] bool shouldAddEdge(const size_t u, const size_t v, + const Layout& from, + const Layout& to) const { + const auto dest = to.getHardwareIndex(from.getProgramIndex(u)); + return device_->distanceBetween(v, dest) < + device_->distanceBetween(u, dest); + } + + Graph f_; + std::shared_ptr device_; + }; + public: + /// Construct default mapping pass. MappingPass() = default; - explicit MappingPass(MappingPassOptions options) : MappingPassBase(options) {} - explicit MappingPass(size_t nqubits, const Edges& coupling, - MappingPassOptions options = {}) - : MappingPassBase(options), device(nqubits, coupling) {} + + /// Construct default mapping pass with options. + explicit MappingPass(const MappingPassOptions& options) + : MappingPassBase(options) {} + + /// Construct mapping from coupling set. + explicit MappingPass(const DenseSet>& couplingSet, + const MappingPassOptions& options) + : MappingPassBase(options), + device(std::make_shared(couplingSet)) {} protected: void runOnOperation() override { - assert(alpha > 0 && "runOnOperation: expected alpha > 0"); - assert(niterations > 0 && "runOnOperation: expected niterations > 0"); - assert(ntrials > 0 && "runOnOperation: expected ntrials > 0"); + assert(alpha > 0 && "expected alpha > 0"); + assert(niterations > 0 && "expected niterations > 0"); + assert(ntrials > 0 && "expected ntrials > 0"); + + if (!device) { + llvm::reportFatalUsageError("No device specified!"); + } - std::mt19937_64 rng{seed}; IRRewriter rewriter(&getContext()); - ModuleOp m = getOperation(); - auto func = getEntryPoint(m); + auto mod = getOperation(); + auto func = getEntryPoint(mod); if (!func) { - m.emitError() << "does not contain an entry point function"; + mod.emitError() << "does not contain an entry point function"; signalPassFailure(); return; } @@ -400,93 +358,125 @@ struct MappingPass : impl::MappingPassBase { return; } - if (comp->size() > device.nqubits()) { - m.emitError() << "requires " + Twine(comp.value().size()) + - " qubits. However, the architecture only supports " + - Twine(device.nqubits()) + "qubits."; + auto& body = func.getFunctionBody(); + auto& [wires, infos] = *comp; + + if (wires.size() > device->nqubits()) { + func.emitError() + << "requires " + Twine(wires.size()) + + " qubits. However, the architecture only supports " + + Twine(device->nqubits()) + "qubits."; signalPassFailure(); return; } - // Create trials for initial layout refining. Currently, this includes - // `ntrials` many random layouts. - SmallVector trials; - trials.reserve(ntrials); - for (size_t i = 0; i < ntrials; ++i) { - trials.emplace_back(Layout::random(device.nqubits(), rng())); - } - - // Execute each of the trials (possibly in parallel). Collect the results - // and find the one with the fewest SWAPs on the final backwards pass. - parallelForEach(&getContext(), trials, [&, this](Trial& trial) { - if (const auto res = refineLayout(*comp, trial.layout); succeeded(res)) { - trial.success = true; - trial.nswaps = *res; - } - }); - - Trial* best = findBestTrial(trials); - if (best == nullptr) { - func.emitError() << "failed to find the best layout trial"; + auto layout = generateLayout(wires, infos); + if (failed(layout)) { + func->emitError() << "failed to refine random initial layouts."; signalPassFailure(); return; } - // Perform placement and hot routing by inserting SWAPs into the IR. - auto placedWires = place(func, best->layout, rewriter); + std::tie(wires, infos) = std::move(place(body, *layout, rewriter)); + + Statistics stats; + RoutingBundle bundle{.wires = std::move(wires), + .infos = std::move(infos), + .layout = std::move(*layout)}; + const auto res = route( - placedWires, best->layout, &rewriter); - if (failed(res)) { - func.emitError() << "failed to map the " << func.getName() << " function"; + bundle, stats, &rewriter); + if (res.failed()) { + func.emitError() << "failed to map the function"; signalPassFailure(); return; } // Collect statistics. - numSwaps += *res; + numSwaps += stats.nswaps; // Fix SSA Dominance issues. - for_each(func.getFunctionBody().getBlocks(), - [](Block& b) { sortTopologically(&b); }); + for_each(body.getBlocks(), [](Block& b) { sortTopologically(&b); }); } private: - /** - * @brief Collect wires of the quantum computation before placement. - * @details - * The mapping pass currently assumes that the quantum computation allocates - * all tensors at the start of the function. The required qubits are extracted - * from these tensors and used for the computation. Finally, the qubits are - * inserted back into the tensors at the end of the function. - * Thus, a valid program has the following structure: - * - * T ⨉ [qtensor::AllocOp] - * → N ⨉ [qtensor::ExtractOp] - * → (Computation) - * → N ⨉ [qtensor::InsertOp] - * → T ⨉ [qtensor::DeallocOp] - * - * @returns a vector of wire iterator, or failure() if any of the above - * assumptions are violated. - */ - static FailureOr> + /// Extend the init arguments of an `scf::ForOp` by adding a given range of + /// additional SSA values. Replaces the existing operation and returns the + /// newly created one. + static scf::ForOp extend(scf::ForOp forOp, ValueRange addons, + IRRewriter& rewriter) { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(forOp); + + const auto res = + forOp.replaceWithAdditionalIterOperands(rewriter, addons, true); + assert(succeeded(res)); + auto newForOp = cast(*res); + + for (const auto [before, after] : llvm::zip_equal( + addons, newForOp.getResults().take_back(addons.size()))) { + rewriter.replaceAllUsesExcept(before, after, newForOp); + } + return newForOp; + } + + /// Extend the qubit arguments of an `IfOp` by adding a given range of + /// additional SSA values. Replaces the existing operation and returns the + /// newly created one. + static IfOp extend(IfOp ifOp, ValueRange addons, IRRewriter& rewriter) { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(ifOp); + + auto newIfOp = ifOp.replaceWithAdditionalQubits(rewriter, addons); + + for (const auto [before, after] : llvm::zip_equal( + addons, newIfOp->getResults().take_back(addons.size()))) { + rewriter.replaceAllUsesExcept(before, after, newIfOp); + } + + return newIfOp; + } + + /// Return the wires of a dynamic computation. + /// The mapping pass currently assumes that + /// - there are no `qco.alloc` operation + /// - there is an "extraction" and "insertion" phase, where the i-th extract + /// defines the i-th program qubit + /// Thus, supported programs have the following structure: + /// + /// T ⨉ [qtensor::AllocOp] + /// → N ⨉ [qtensor::ExtractOp] + /// → (Computation) + /// → N ⨉ [qtensor::InsertOp] + /// → T ⨉ [qtensor::DeallocOp] + /// + /// If any of the above assumptions are violated, the function returns + /// failure. + static FailureOr> getComputation(func::FuncOp func) { if (!func.getOps().empty()) { - func.emitError() << "must not contain qco.alloc operations"; - return failure(); + return func.emitError() << "must not contain qco.alloc operations"; } - SmallVector wires; - for (auto tensor : func.getOps()) { + Wires wires; + WireInfos infos; + + for (auto alloc : func.getOps()) { bool isInitPhase = true; - TensorIterator it(tensor.getResult()); + TensorIterator it(alloc.getResult()); for (; it != std::default_sentinel; ++it) { if (auto extract = dyn_cast(it.operation())) { if (!isInitPhase) { - func.emitError() << "must extract and insert all qubits at once."; - return failure(); + return func.emitError() + << "must extract and insert all qubits at once."; } - wires.emplace_back(extract.getResult()); + + const auto qubit = extract.getResult(); + const auto index = wires.size(); + + wires.emplace_back(qubit); + infos.map(index, index); + continue; } @@ -496,36 +486,36 @@ struct MappingPass : impl::MappingPassBase { } } } - return wires; + + return std::make_pair(wires, infos); } - /** - * @brief Perform placement by replacing dynamic with static qubits. - * @details - * Creates static qubits and replaces the extracted qubits with it. - * Moreover, the function extends the computation with as many static qubits - * as the architecture supports. - * @returns a vector of wire iterators, where the i-th wire points at the i-th - * static program qubit. - */ - static SmallVector - place(func::FuncOp func, const Layout& layout, IRRewriter& rewriter) { + /// Perform placement by + /// - initializing as many hardware qubits as the architecture supports + /// - replacing dynamic with static qubits + /// - extending the inputs of `scf::ForOp` to all hardware qubits. + /// + /// Analogously to the getComputation function, the i-th extract + /// operation defines the i-th program qubit. + static std::pair place(Region& body, const Layout& layout, + IRRewriter& rewriter) { SmallVector staticOps; staticOps.reserve(layout.nqubits()); // Create and save static qubit operations. - rewriter.setInsertionPointToStart(&func.getFunctionBody().front()); + rewriter.setInsertionPointToStart(&body.front()); for (size_t i = 0; i < layout.nqubits(); ++i) { - const auto op = StaticOp::create(rewriter, func.getLoc(), i); + const auto op = StaticOp::create(rewriter, body.getLoc(), i); staticOps.emplace_back(op); rewriter.setInsertionPointAfter(op); } // Replace extract ops and collect in program-qubit order. - SmallVector placedWires(layout.nqubits()); - size_t prog = 0UL; - for (auto alloc : make_early_inc_range(func.getOps())) { + Wires wires; + WireInfos infos; + + for (auto alloc : make_early_inc_range(body.getOps())) { TensorIterator it(alloc.getResult()); while (it != std::default_sentinel) { // Get the operation and early increment to avoid issues after erasure. @@ -534,6 +524,7 @@ struct MappingPass : impl::MappingPassBase { TypeSwitch(curr) .Case([&](auto op) { + const auto prog = wires.size(); const auto hw = layout.getHardwareIndex(prog); const auto qubit = staticOps[hw].getQubit(); @@ -541,8 +532,8 @@ struct MappingPass : impl::MappingPassBase { rewriter.replaceAllUsesWith(op.getOutTensor(), op.getTensor()); rewriter.eraseOp(op); - placedWires[prog] = WireIterator(qubit); - ++prog; + wires.emplace_back(qubit); + infos.map(prog, prog); }) .Case([&](auto op) { rewriter.setInsertionPointAfter(op); @@ -557,84 +548,162 @@ struct MappingPass : impl::MappingPassBase { } // Create sinks for remaining, unused, static qubits. - rewriter.setInsertionPoint(func.getFunctionBody().back().getTerminator()); - for (; prog < layout.nqubits(); ++prog) { + + rewriter.setInsertionPoint(body.back().getTerminator()); + for (size_t prog = wires.size(); prog < layout.nqubits(); ++prog) { const auto hw = layout.getHardwareIndex(prog); const auto qubit = staticOps[hw].getQubit(); - placedWires[prog] = WireIterator(qubit); - SinkOp::create(rewriter, func->getLoc(), qubit); - } - return placedWires; - } + wires.emplace_back(qubit); + infos.map(prog, prog); - /** - * @brief Find the best trial result in terms of the number of SWAPs. - * @returns the best trial result or nullptr if no result is valid. - */ - [[nodiscard]] static Trial* findBestTrial(MutableArrayRef trials) { - Trial* best = nullptr; - for (auto& trial : trials) { - if (!trial.success) { - continue; - } + SinkOp::create(rewriter, body.getLoc(), qubit); + } - if (best == nullptr || best->nswaps > trial.nswaps) { - best = &trial; + // Finally, update the SCF operations such that they take all static qubits + // as input. To handle recursively nested SCF operations, use a stack of + // (region, mapping) pairs. + + SmallVector>> stack; + stack.emplace_back(body, DenseSet{}); + + while (!stack.empty()) { + for (auto [region, qubits] = stack.pop_back_val(); + Operation& op : make_early_inc_range(region.getOps())) { + TypeSwitch(&op) + .Case( + [&](StaticOp staticOp) { qubits.insert(staticOp.getQubit()); }) + .Case([&](UnitaryOpInterface& uOp) { + for (const auto [pred, succ] : llvm::zip_equal( + uOp.getInputQubits(), uOp.getOutputQubits())) { + qubits.insert(succ); + qubits.erase(pred); + } + }) + .Case([&](scf::ForOp forOp) { + assert(qubits.size() == layout.nqubits()); + + llvm::for_each(forOp.getInits(), + [&](Value v) { qubits.erase(v); }); + + auto newForOp = extend(forOp, to_vector(qubits), rewriter); + for (const auto [init, result] : llvm::zip_equal( + newForOp.getInits(), *newForOp.getLoopResults())) { + qubits.insert(result); + qubits.erase(init); + } + + stack.emplace_back( + newForOp.getRegion(), + DenseSet(newForOp.getRegionIterArgs().begin(), + newForOp.getRegionIterArgs().end())); + }) + .Case([&](IfOp ifOp) { + assert(qubits.size() == layout.nqubits()); + + llvm::for_each(ifOp.getQubits(), + [&](Value v) { qubits.erase(v); }); + + auto newIfOp = extend(ifOp, to_vector(qubits), rewriter); + + for (const auto [qubit, result] : + llvm::zip_equal(newIfOp.getQubits(), newIfOp.getResults())) { + qubits.insert(result); + qubits.erase(qubit); + } + + const auto thenArgs = newIfOp.getThenRegion().getArguments(); + const auto elseArgs = newIfOp.getElseRegion().getArguments(); + stack.emplace_back( + newIfOp.getThenRegion(), + DenseSet(thenArgs.begin(), thenArgs.end())); + stack.emplace_back( + newIfOp.getElseRegion(), + DenseSet(elseArgs.begin(), elseArgs.end())); + }) + .Case([&](auto resetOp) { + qubits.insert(resetOp.getQubitOut()); + qubits.erase(resetOp.getQubitIn()); + }) + .Case([&](auto) { + llvm::reportFatalInternalError("unexpected dynamic qubit alloc"); + }); } } - return best; + return {wires, infos}; } - /** - * @brief Refine the trial's layout and count #swaps for the final backwards - * pass. - * @details Use the SABRE Approach to improve the initial layout: - * Traverse the layers from left-to-right-to-left and cold-route - * along the way. Repeat this procedure "niterations" times. - * @returns failure() if routing fails. - */ - FailureOr refineLayout(SmallVector wires, - Layout& layout) { - size_t nswaps{0}; - for (size_t i = 0; i < niterations; ++i) { - if (failed(route(wires, layout))) { - return failure(); + /// Execute `ntrials` many (parallel) initial layout refinement trials and + /// return the heuristically best one. + /// + /// The function uses the SABRE Approach to improve the initial layout: + /// Traverse the layers of the program from left-to-right-to-left and + /// cold-route along the way. Repeat this procedure "niterations" times and + /// finally find the trial with the fewest SWAPs on the final backwards pass + /// and return the respective layout. + FailureOr generateLayout(const Wires& wires, const WireInfos& infos) { + std::mt19937_64 rng{seed}; + + struct Trial { + RoutingBundle bundle; + Statistics stats{}; + bool success{false}; + }; + + SmallVector trials; + trials.reserve(ntrials); + for (size_t i = 0; i < ntrials; ++i) { + trials.emplace_back( + RoutingBundle{.wires = wires, + .infos = infos, + .layout = Layout::random(device->nqubits(), rng())}); + } + + parallelForEach(&getContext(), trials, [&, this](Trial& t) { + for (size_t i = 0; i < niterations; ++i) { + if (route(t.bundle, t.stats).failed()) { + return; + } + t.stats.nswaps = 0; + if (route(t.bundle, t.stats).failed()) { + return; + } } - const auto resB = route(wires, layout); - if (failed(resB)) { - return failure(); + t.success = true; + }); + + Trial* best = nullptr; + for (Trial& t : trials) { + if (t.success && + (best == nullptr || best->stats.nswaps > t.stats.nswaps)) { + best = &t; } - nswaps = *resB; } - return nswaps; + if (best == nullptr) { + return failure(); + } + + return best->bundle.layout; } - /** - * @brief Perform A* search to find a sequence of SWAPs that makes the - * two-qubit operations inside the first layer (the front) executable. - * @details - * The iteration budget is b^{3} node expansions, i.e. roughly a depth-3 - * search in a tree with branching factor b. A hard cap prevents impractical - * runtimes on larger architectures. - * - * The branching factor b of the A* search is the product of the - * architecture's maximum qubit degree and the maximum number of two-qubit - * gates in any layer: - * - * b = maxDegree × ⌈N/2⌉ - * - * @returns a vector of hardware-index pairs (each denoting a SWAP) or - * failure() if A* fails. - */ - [[nodiscard]] FailureOr> - search(const Window& window, const Layout& layout) { + /// Perform A* search to find a sequence of SWAPs that makes all two-qubit ops + /// inside the first layer executable. + /// + /// The iteration budget is b^{3} node expansions, i.e. roughly a depth-3 + /// search in a tree with branching factor b, where b is the product of the + /// architecture's maximum qubit degree and the maximum number of two-qubit + /// gates in any layer: `b = maxDegree × ⌈N/2⌉`. A hard cap prevents + /// impractical runtimes on larger architectures. + /// + /// Returns `failure`, if the A* search fails. + FailureOr> search(const Window& window, + const Layout& layout) const { constexpr size_t cap = 25'000'000UL; - const size_t b = device.maxDegree() * ((device.nqubits() + 1) / 2); + const size_t b = device->maxDegree() * ((device->nqubits() + 1) / 2); const size_t budget = std::min(b * b * b, cap); const Parameters params{.alpha = alpha, .lambda = lambda}; @@ -645,14 +714,14 @@ struct MappingPass : impl::MappingPassBase { // Early exit, if the root node is a goal node already. Node* root = std::construct_at(arena.Allocate(), layout); - if (root->isGoal(window.front(), device)) { + if (root->isGoal(window.front(), *device)) { return SmallVector{}; } frontier.emplace(root); - DenseMap, size_t> bestDepth; - DenseSet expansionSet; + DenseMap, size_t> bestDepth; + SmallVector expansionSet; size_t i = 0; while (!frontier.empty() && i < budget) { @@ -667,8 +736,8 @@ struct MappingPass : impl::MappingPassBase { const auto [it, inserted] = bestDepth.try_emplace( curr->layout.getProgramToHardware(), curr->depth); if (!inserted) { - const auto otherDepth = it->getSecond(); - if (curr->depth >= otherDepth) { + if (const auto otherDepth = it->getSecond(); + curr->depth >= otherDepth) { ++i; continue; } @@ -676,13 +745,13 @@ struct MappingPass : impl::MappingPassBase { it->second = curr->depth; } - // If the currently visited node is a goal node, reconstruct the sequence - // of SWAPs from this node to the root. + // If the currently visited node is a goal node, reconstruct the + // sequence of SWAPs from this node to the root. - if (curr->isGoal(window.front(), device)) { + if (curr->isGoal(window.front(), *device)) { SmallVector seq(curr->depth); size_t j = seq.size() - 1; - for (Node* n = curr; n->parent != nullptr; n = n->parent) { + for (const Node* n = curr; n->parent != nullptr; n = n->parent) { seq[j] = n->swap; --j; } @@ -691,21 +760,21 @@ struct MappingPass : impl::MappingPassBase { } // Given a layout, create child-nodes for each possible SWAP - // between two neighbouring hardware qubits. + // between two neighboring hardware qubits. expansionSet.clear(); - const auto& [q0, q1] = window.front(); - for (const auto prog : {q0, q1}) { + for (const auto& [q0, q1] = window.front(); const auto prog : {q0, q1}) { for (const auto hw0 = curr->layout.getHardwareIndex(prog); - const auto hw1 : device.neighboursOf(hw0)) { + const auto hw1 : device->neighboursOf(hw0)) { // Ensure consistent hashing/comparison. const IndexPairType swap = std::minmax(hw0, hw1); - if (!expansionSet.insert(swap).second) { + if (is_contained(expansionSet, swap)) { continue; } + expansionSet.push_back(swap); frontier.emplace(std::construct_at(arena.Allocate(), curr, swap, - window, device, params)); + window, *device, params)); } } @@ -715,85 +784,171 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - /** - * @brief Skip a qubit-pair block. - * @details Traverses the pair of wire iterators in tandem until a two-qubit - * operation is found. If the two-qubit operation is equivalent, continue. - * Otherwise stop. - */ - template - static void skipQubitPairBlock(WireIterator& w0, WireIterator& w1) { - using Traits = WireTraversalTraits; + /// Return the SWAP sequence to move from one layout to another. + /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. + [[nodiscard]] SmallVector restore(const Layout& from, + const Layout& to) const { + Layout curr(from); + FGraph f(device); + SmallVector swaps; - WireIterator curr0(w0); - WireIterator curr1(w1); while (true) { - while (Traits::isActive(curr0)) { - std::ranges::advance(curr0, Traits::stride()); + f.reset(); + f.construct(curr, to); + + if (const auto happy = f.findHappySWAPChain()) { + for (const auto& swap : *happy) { + swaps.emplace_back(swap); + curr.swap(swap.first, swap.second); + } + continue; } - if (curr0 == std::default_sentinel) { - return; + // If there are no happy or unhappy swaps anymore, + // the final placement of every token is reached. + + const auto unhappy = f.findUnhappySWAP(); + if (!unhappy) { + break; } - while (Traits::isActive(curr1)) { - std::ranges::advance(curr1, Traits::stride()); + swaps.emplace_back(*unhappy); + curr.swap(unhappy->first, unhappy->second); + } + + return swaps; + } + + /// Return a pair of SWAP sequences to transform two layouts into each other. + /// Inspired by the 4-Approximation algorithm described in arXiv:1602.05150v3, + /// with the key difference that the goal permutation is not static. + [[nodiscard]] std::pair, + SmallVector> + converge(const Layout& lhs, const Layout& rhs) const { + std::array layouts{Layout(lhs), Layout(rhs)}; + std::array graphs{FGraph(device), FGraph(device)}; + std::array, 2> swaps{}; + + std::mt19937 gen(seed); + std::uniform_int_distribution coin(0, 1); + + while (true) { + size_t i = 0; + for (; i < 2; ++i) { + FGraph& f = graphs[i]; + + f.reset(); + f.construct(layouts[i], layouts[(i + 1) % 2]); + + if (const auto happy = f.findHappySWAPChain()) { + for (const auto& swap : *happy) { + swaps[i].emplace_back(swap); + layouts[i].swap(swap.first, swap.second); + } + break; + } } - if (curr1 == std::default_sentinel) { - return; + // If we exit early from the loop, we've found a happy SWAP chain. + if (i != 2) { + continue; } - if (curr0.operation() != curr1.operation()) { - return; + // Otherwise, we randomly apply an unhappy SWAP to one of the layouts. + // If there is no happy or unhappy swaps anymore, the final placement of + // every token is reached. + + i = coin(gen); + + const auto unhappy = graphs[i].findUnhappySWAP(); + if (!unhappy) { + break; } - // Handle two-qubit barrier edge case explicitly. - if (auto barrier = dyn_cast(curr0.operation())) { - if (barrier.getNumQubits() != 2) { + swaps[i].emplace_back(*unhappy); + layouts[i].swap(unhappy->first, unhappy->second); + } + + return {std::move(swaps[0]), std::move(swaps[1])}; + } + + /// Skip to the end of the two-qubit block for both wire iterators, where + /// initially both must point at the same two-qubit operation. + template + static void skipQubitPairBlock(WireIterator& it0, WireIterator& it1) { + using Traits = WireTraversalTraits; + + // Traverses the pair of wire iterators in tandem until a two-qubit + // operation is found. If the two-qubit operation is equivalent, continue. + // Otherwise, stop. + + std::array block{it0, it1}; + while (true) { + for (auto& it : block) { + while (Traits::isActive(it)) { + std::ranges::advance(it, Traits::stride()); + + if (it.operation() == nullptr) { // isa + return; + } + + if (auto u = dyn_cast(it.operation()); + u && u.getNumQubits() > 1) { + // Handle two-qubit barrier edge case explicitly. + if (isa(u) && u.getNumQubits() != 2) { + return; + } + // Otherwise stop for subsequent two-qubit unitary comparison. + break; + } + } + + if (it == std::default_sentinel) { return; } } - w0 = curr0; - w1 = curr1; + if (block[0].operation() != block[1].operation()) { + return; + } + + it0 = block[0]; + it1 = block[1]; } } - /** - * @brief Build and return window of layers. - * @details Traverses the circuit-layers until the desired window sizes is - * reached. Assumes that wires[i] = i-th program qubit. The size of the window - * is 1 + nlookahead. - * @returns window of layers. - */ + /// Return a window of layers with a maximum size of `1 + nlookahead`. template - Window getWindow(ArrayRef baseWires) { + Window getWindow(Wires wires, const WireInfos& infos) { Window window; window.reserve(1 + nlookahead); - SmallVector wires(baseWires); - std::ignore = walkProgramGraph( + walkProgramGraph( wires, [&](const ReadyRange& ready, ReleasedOps& released) { if (ready.empty()) { return WalkResult::advance(); } - for (const auto& [op, progs] : ready) { - if (isa(op)) { - released.emplace_back(op); - continue; - } + for (const auto& [op, indices] : ready) { + if (auto u = dyn_cast(op)) { + const auto i0 = indices[0]; + const auto i1 = indices[1]; + + const auto prog0 = infos.lookupProgram(i0); + const auto prog1 = infos.lookupProgram(i1); - const auto p0 = progs[0]; - const auto p1 = progs[1]; - window.emplace_back(p0, p1); - if (window.size() == 1 + nlookahead) { - return WalkResult::interrupt(); + window.emplace_back(prog0, prog1); + if (window.size() == 1 + nlookahead) { + return WalkResult::interrupt(); + } + + skipQubitPairBlock(wires[i0], wires[i1]); + released.emplace_back(u); + return WalkResult::advance(); } - skipQubitPairBlock(wires[p0], wires[p1]); - released.emplace_back(wires[p0].operation()); + released.emplace_back(op); + return WalkResult::advance(); } return WalkResult::advance(); @@ -802,145 +957,333 @@ struct MappingPass : impl::MappingPassBase { return window; } - /** - * @brief Advance past all executable gates. - * @details Traverses the multi-qubit gates of the circuit until no more - * executable gates are found. - */ + /// Insert SWAP operations, exchanging two qubits, virtually + /// (`RoutingMode::Cold`) or into the IR (`RoutingMode::Hot`). The function + /// expects that each wire points at the correct insertion point. + template + static void insertSWAPs(ArrayRef swaps, RoutingBundle& bundle, + Statistics& stats, IRRewriter* rewriter) { + auto& [wires, infos, layout] = bundle; + for (const auto& [hw0, hw1] : swaps) { + if constexpr (Mode == RoutingMode::Hot) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + + const auto i0 = infos.lookupIndex(prog0); + const auto i1 = infos.lookupIndex(prog1); + + auto& w0 = wires[i0]; + auto& w1 = wires[i1]; + + const auto in0 = w0.qubit(); + const auto in1 = w1.qubit(); + + rewriter->setInsertionPointAfterValue(in0); // Valid bc. Hot => Forward. + auto swapOp = SWAPOp::create(*rewriter, in0.getLoc(), in0, in1); + + const auto out0 = swapOp.getQubit0Out(); + const auto out1 = swapOp.getQubit1Out(); + + rewriter->replaceAllUsesExcept(in0, out1, swapOp); + rewriter->replaceAllUsesExcept(in1, out0, swapOp); + + infos.swap(prog0, prog1); + + std::advance(w0, 1); // Move to SWAP. + std::advance(w1, 1); + } + + layout.swap(hw0, hw1); + } + + stats.nswaps += swaps.size(); + } + + /// Advance past all executable gates and return operations with nested + /// regions and the respective wire indices. Stops when no more executable + /// gates are found. After the function returns, the wires point at the + /// results of non-executable gates or operations with nested regions. template - void skipExecutableGates(MutableArrayRef wires, - Layout& layout) { - std::ignore = walkProgramGraph( + RecursiveRoutingStack advance(Wires& wires, const WireInfos& infos, + const Layout& layout) { + RecursiveRoutingStack stack; + + // Advance wires past all executable gates and push operations with + // nested regions and the respective wire indices of their inputs onto the + // result stack. + + walkProgramGraph( wires, [&](const ReadyRange& ready, ReleasedOps& released) { if (ready.empty()) { return WalkResult::advance(); } - for (const auto& [op, progs] : ready) { - if (isa(op)) { - released.emplace_back(op); - continue; - } - - const auto [hw0, hw1] = - layout.getHardwareIndices(progs[0], progs[1]); - - if (device.areAdjacent(hw0, hw1)) { - released.emplace_back(op); - } + for (const auto& [readyOp, indices] : ready) { + TypeSwitch(readyOp) + .template Case( + [&](BarrierOp op) { released.emplace_back(op); }) + .template Case([&](UnitaryOpInterface op) { + const auto prog0 = infos.lookupProgram(indices[0]); + const auto prog1 = infos.lookupProgram(indices[1]); + if (const auto [hw0, hw1] = + layout.getHardwareIndices(prog0, prog1); + device->areAdjacent(hw0, hw1)) { + released.emplace_back(op); + } + }) + .template Case( + [&](auto op) { stack.emplace_back(op, indices); }); } - // Stop, if there are no more ready AND executable gates. if (released.empty()) { return WalkResult::interrupt(); } return WalkResult::advance(); }); + + return stack; } - /** - * @brief Route via SWAP insertion. - * @details Iterates over a dynamically computed window of layers and uses A* - * search to find a sequence of SWAPs that makes that layer executable. - * Depending on the template parameter, this function only updates - * (and hence modifies) the layout or also inserts the SWAPs into the IR. - * @returns failure() if A* search isn't able to find a solution, the number - * of SWAPs otherwise. - */ - template - FailureOr route(SmallVector& wires, Layout& layout, - IRRewriter* rewriter = nullptr) { - using Traits = WireTraversalTraits; + /// Processes the recursive stack item by routing the nested operation and + /// inserting epilogue SWAPs. + template + requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) + LogicalResult dispatch(const RecursiveRoutingStackItem& item, + RoutingBundle& parent, Statistics& stats, + IRRewriter* rewriter = nullptr) { + const auto& [op, indices] = item; + return TypeSwitch(op) + .template Case([&](scf::ForOp forOp) { + RoutingBundle child{.layout = parent.layout}; + + // Map parent (results) to child values (iter args). Going + // forwards, the recursive routing starts at block + // arguments, while the backwards go starts at the yielded + // values. + + for (size_t i : indices) { + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); + const auto arg = forOp.getTiedLoopRegionIterArg(res); + const auto index = child.wires.size(); + + if constexpr (Direction == WireDirection::Forward) { + child.wires.emplace_back(arg); + } else { + const auto yield = forOp.getTiedLoopYieldedValue(arg)->get(); + child.wires.emplace_back(yield); + } + child.infos.map(index, prog); + } - size_t nswaps{0}; - while (true) { - skipExecutableGates(wires, layout); + if (failed(route(child, stats, rewriter))) { + return failure(); + } - const auto window = getWindow(wires); - if (window.empty()) { - break; - } + const auto swaps = restore(child.layout, parent.layout); - if constexpr (mode == RoutingMode::Hot) { + if constexpr (Mode == RoutingMode::Hot) { - // At this point the wire iterators either point to - // std::default_sentinel or a multi-qubit gate (including barriers) of - // the current or subsequent layers. The former must be decremented - // twice (sentinel -> sink -> unitary/static). For the latter we simply - // must ensure the insertion point is before the multi-qubit gates. + // After routing the loop body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). - for (auto& it : wires) { - std::ranges::advance(it, it == std::default_sentinel - ? -2 * Traits::stride() - : -Traits::stride()); + for_each(child.wires, [](auto& it) { std::advance(it, -2); }); + } + + insertSWAPs(swaps, child, stats, rewriter); + + if constexpr (Mode == RoutingMode::Hot) { + sortTopologically(forOp.getBody()); + } + + // Finally, move past the operation with nested regions by + // incrementing the respective global wires. + + for_each(indices, [&](size_t i) { + std::advance(parent.wires[i], + WireTraversalTraits::stride()); + }); + + return success(); + }) + .template Case([&](IfOp ifOp) { + std::array children{RoutingBundle{.layout = parent.layout}, + RoutingBundle{.layout = parent.layout}}; + + // Map parent (results) to child values (qubits). Going + // forwards, the recursive routing starts at block + // arguments, while the backwards go starts at the yielded + // values. + + for (size_t i : indices) { + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); + const auto index = children[0].wires.size(); + + OpOperand* qubit = ifOp.getTiedQubit(res); + const std::array args{ifOp.getTiedThenBlockArgument(qubit), + ifOp.getTiedElseBlockArgument(qubit)}; + + if constexpr (Direction == WireDirection::Forward) { + for (size_t j = 0; j < children.size(); ++j) { + children[j].wires.emplace_back(args[j]); + children[j].infos.map(index, prog); + } + } else { + const std::array yields{ + ifOp.getTiedThenYieldedValue(args[0])->get(), + ifOp.getTiedElseYieldedValue(args[1])->get()}; + for (size_t j = 0; j < children.size(); ++j) { + children[j].wires.emplace_back(yields[j]); + children[j].infos.map(index, prog); + } + } + } + + for (auto& child : children) { + if (failed(route(child, stats, rewriter))) { + return failure(); + } + } + + if constexpr (Mode == RoutingMode::Hot) { + + // After routing the branch body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). + + for_each(children[0].wires, [](auto& it) { std::advance(it, -2); }); + for_each(children[1].wires, [](auto& it) { std::advance(it, -2); }); + } + + const auto [fst, snd] = + converge(children[0].layout, children[1].layout); + + insertSWAPs(fst, children[0], stats, rewriter); + insertSWAPs(snd, children[1], stats, rewriter); + + if constexpr (Mode == RoutingMode::Hot) { + + // The IfOp implements the SingleBlockImplicitTerminator trait. + assert(ifOp.getThenRegion().hasOneBlock()); + assert(ifOp.getElseRegion().hasOneBlock()); + + sortTopologically(&ifOp.getThenRegion().getBlocks().front()); + sortTopologically(&ifOp.getElseRegion().getBlocks().front()); + } + + // Finally, move past the operation with nested regions by + // incrementing the respective global wires. + + for_each(indices, [&](size_t i) { + std::advance(parent.wires[i], + WireTraversalTraits::stride()); + }); + + return success(); + }) + .Default([](Operation*) { return failure(); }); + } + + /// Iterates over a dynamically computed window of layers and uses A* search + /// to find a SWAP sequence that makes each layer executable. Depending on + /// the template parameter, this function only updates the layout or also + /// inserts the SWAPs into the IR. The function returns `failure` if A* is + /// unable to find a solution. + template + requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) + LogicalResult route(RoutingBundle& bundle, Statistics& stats, + IRRewriter* rewriter = nullptr) { + auto& [wires, infos, layout] = bundle; + + while (true) { + + while (true) { + const auto stack = advance(wires, infos, layout); + if (stack.empty()) { + break; + } + for (const auto& item : stack) { + if (dispatch(item, bundle, stats, rewriter) + .failed()) { + return failure(); + } } } + const auto window = getWindow(wires, infos); + if (window.empty()) { + break; + } + const auto swaps = search(window, layout); if (failed(swaps)) { return failure(); } - for (const auto& [hw0, hw1] : *swaps) { - if constexpr (mode == RoutingMode::Hot) { - const auto& [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - const auto& w0 = wires[prog0]; - const auto& w1 = wires[prog1]; - - assert(!isa(w0.operation())); - assert(!isa(w1.operation())); - - const auto in0 = w0.qubit(); - const auto in1 = w1.qubit(); - - rewriter->setInsertionPointAfter(in0.getDefiningOp()); - auto swapOp = SWAPOp::create(*rewriter, in0.getLoc(), in0, in1); + if constexpr (Mode == RoutingMode::Hot) { - const auto out0 = swapOp.getQubit0Out(); - const auto out1 = swapOp.getQubit1Out(); - - rewriter->replaceAllUsesExcept(in0, out1, swapOp); - rewriter->replaceAllUsesExcept(in1, out0, swapOp); - - // Preserve program-indexed wire semantics. - wires[prog0] = WireIterator(out1); - wires[prog1] = WireIterator(out0); + // At this point the wire iterators either point to + // std::default_sentinel or a multi-qubit gate (incl. barriers) of + // the current or subsequent layers. The former must be decremented + // twice (sentinel → sink → unitary/static). For the latter, we + // must ensure the insertion point is before the multi-qubit gates. - assert(isa(w0.operation())); - assert(isa(w1.operation())); + for (auto& it : wires) { + std::advance(it, it == std::default_sentinel ? -2 : -1); } - layout.swap(hw0, hw1); } - if constexpr (mode == RoutingMode::Hot) { + insertSWAPs(*swaps, bundle, stats, rewriter); + + if constexpr (Mode == RoutingMode::Hot) { // After SWAP insertion, a wire is either untouched by the SWAP - // insertion or pointing at a SWAP operation. If the former is the case, - // incrementing the wire iterator will undo the previous decrement, - // leaving it at the same position as before the SWAP insertion. - // Otherwise, an increment will move the iterator to the multi-qubit op - // of the current or subsequent layer or to a sink (and thus - // std::default_sentinel). - - for_each(wires, - [](auto& it) { std::ranges::advance(it, Traits::stride()); }); + // insertion or pointing at a SWAP operation. If the former is the + // case, incrementing the wire iterator will undo the previous + // decrement, leaving it at the same position as before the SWAP + // insertion. Otherwise, an increment will move the iterator to the + // multi-qubit op of the current or subsequent layer or to a sink (and + // thus std::default_sentinel). + + for_each(wires, [](auto& it) { std::advance(it, 1); }); } - - nswaps += swaps->size(); } - return nswaps; + return success(); } - AugmentedDevice device; + std::shared_ptr device; }; } // namespace -std::unique_ptr createMappingPass(size_t nqubits, const Edges& coupling, - MappingPassOptions options) { - return std::make_unique(nqubits, coupling, options); +std::unique_ptr +createMappingPass(const DenseSet>& couplingSet, + MappingPassOptions options) { + + // Verify the assumption that the coupling set is symmetric: + // For every edge (u, v) in the set, (v, u) must also be present. + + for (const auto& [u, v] : couplingSet) { + if (u == v) { + llvm::reportFatalUsageError("Found an invalid (u, u) edge."); + } + + if (!couplingSet.contains({v, u})) { + llvm::reportFatalUsageError("Expected symmetric coupling set: edge (" + + Twine(u) + ", " + Twine(v) + + ") exists but (" + Twine(v) + ", " + + Twine(u) + ") does not."); + } + } + + return std::make_unique(couplingSet, options); } } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/Algorithms.cpp b/mlir/lib/Dialect/QCO/Utils/Algorithms.cpp deleted file mode 100644 index c8da5817f0..0000000000 --- a/mlir/lib/Dialect/QCO/Utils/Algorithms.cpp +++ /dev/null @@ -1,46 +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 "mlir/Dialect/QCO/Utils/Algorithms.h" - -#include -#include - -#include -#include -#include - -namespace mlir::qco { -Matrix findAllShortestPaths(size_t n, const Edges& edges) { - Matrix dist(n, SmallVector(n, UINT64_MAX)); - - for (const auto& [u, v] : edges) { - dist[u][v] = 1; - } - for (std::size_t v = 0; v < n; ++v) { - dist[v][v] = 0; - } - - for (std::size_t k = 0; k < n; ++k) { - for (std::size_t i = 0; i < n; ++i) { - for (std::size_t j = 0; j < n; ++j) { - if (dist[i][k] == UINT64_MAX || dist[k][j] == UINT64_MAX) { - continue; // Avoid overflow with "infinite" distances. - } - - const std::size_t sum = dist[i][k] + dist[k][j]; - dist[i][j] = std::min(dist[i][j], sum); - } - } - } - - return dist; -} -} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/Graph.cpp b/mlir/lib/Dialect/QCO/Utils/Graph.cpp new file mode 100644 index 0000000000..5e3de25add --- /dev/null +++ b/mlir/lib/Dialect/QCO/Utils/Graph.cpp @@ -0,0 +1,148 @@ +/* + * 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 "mlir/Dialect/QCO/Utils/Graph.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { +void Graph::addEdge(size_t u, size_t v) { + adj_[u].emplace_back(v); + std::ignore = adj_[v]; // Ensure v exists in the map. +} + +ArrayRef Graph::getNeighbours(size_t id) const { return adj_.at(id); } + +SmallVector Graph::getNodes() const { return to_vector(adj_.keys()); } + +size_t Graph::getMaxDegree() const { + size_t deg = 0; + for (const auto& [u, nbrs] : adj_) { + deg = std::ranges::max(deg, nbrs.size()); + } + return deg; +} + +void Graph::clearEdges() { + for_each(adj_, [](auto& kv) { kv.second.clear(); }); +} + +Graph::DistanceMatrix Graph::getDistMatrix() const { + const auto n = getNumNodes(); + + Graph::DistanceMatrix dist(n, std::numeric_limits::max()); + for (const auto& [u, nbrs] : adj_) { + for (const auto& v : nbrs) { + dist[u][v] = 1; + } + } + for (size_t v = 0; v < n; ++v) { + dist[v][v] = 0; + } + + for (size_t k = 0; k < n; ++k) { + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + if (dist[i][k] == std::numeric_limits::max() || + dist[k][j] == std::numeric_limits::max()) { + continue; // Avoid overflow with "infinite" distances. + } + + const size_t sum = dist[i][k] + dist[k][j]; + dist[i][j] = std::min(dist[i][j], sum); + } + } + } + + return dist; +} + +std::optional> Graph::findCycle() const { + enum struct State : uint8_t { Unseen, Seen, Finished }; + + struct Frame { + size_t id; + size_t neighbourIdx; + }; + + SmallVector stack; + llvm::DenseMap parents; + llvm::DenseMap states; + + // Preparation step: Mark all nodes as unseen. + llvm::for_each(adj_.keys(), [&](size_t id) { states[id] = State::Unseen; }); + + for (const auto initId : adj_.keys()) { + // Only start from unseen nodes. + if (states[initId] != State::Unseen) { + continue; + } + + stack.emplace_back(initId, 0); + + while (!stack.empty()) { + Frame& top = stack.back(); + + // If we haven't seen this node before, mark it as seen. + if (states[top.id] == State::Unseen) { + states[top.id] = State::Seen; + } + + auto it = adj_.find(top.id); + assert(it != adj_.end() && "expected node id in adjacency map"); + const auto nbrs = it->getSecond(); + + // Once all neighbours have been visited (indicated by the index + // exceeding the number of neighbours - 1), set the frame on node to + // finished and pop it from the stack. + if (top.neighbourIdx >= nbrs.size()) { + states[top.id] = State::Finished; + stack.pop_back(); + continue; + } + + // Collect the neighbour and advance the index on the + // frame for the next iteration. + const auto nbrId = nbrs[top.neighbourIdx]; + ++top.neighbourIdx; + + if (states[nbrId] == State::Unseen) { + parents[nbrId] = top.id; + stack.emplace_back(nbrId, 0); + } else if (states[nbrId] == State::Seen) { + SmallVector path; + for (auto curr = top.id; curr != nbrId; curr = parents[curr]) { + path.emplace_back(curr); + } + path.emplace_back(nbrId); + std::ranges::reverse(path); + return path; + } + } + + // Preparse stack for next iteration. + stack.clear(); + } + + return std::nullopt; +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/Layout.cpp b/mlir/lib/Dialect/QCO/Utils/Layout.cpp new file mode 100644 index 0000000000..9f0a3b4209 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Utils/Layout.cpp @@ -0,0 +1,70 @@ +/* + * 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 "mlir/Dialect/QCO/Utils/Layout.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mlir::qco { +Layout Layout::random(const size_t nqubits, const size_t seed) { + SmallVector mapping(nqubits); + std::iota(mapping.begin(), mapping.end(), size_t{0}); + std::ranges::shuffle(mapping, std::mt19937_64{seed}); + + Layout layout(nqubits); + for (const auto [prog, hw] : enumerate(mapping)) { + layout.add(prog, hw); + } + + return layout; +} + +void Layout::add(const size_t prog, const size_t hw) { + assert(prog < programToHardware_.size() && "program index out of bounds"); + assert(hw < hardwareToProgram_.size() && "hardware index out of bounds"); + programToHardware_[prog] = hw; + hardwareToProgram_[hw] = prog; +} + +size_t Layout::getProgramIndex(const size_t hw) const { + assert(hw < hardwareToProgram_.size() && "hardware index out of bounds"); + return hardwareToProgram_[hw]; +} + +size_t Layout::getHardwareIndex(const size_t prog) const { + assert(prog < programToHardware_.size() && "program index out of bounds"); + return programToHardware_[prog]; +} + +void Layout::swap(const size_t hwA, const size_t hwB) { + assert(hwA < hardwareToProgram_.size() && "hardware index out of bounds"); + assert(hwB < hardwareToProgram_.size() && "hardware index out of bounds"); + const auto progA = hardwareToProgram_[hwA]; + const auto progB = hardwareToProgram_[hwB]; + + std::swap(hardwareToProgram_[hwA], hardwareToProgram_[hwB]); + std::swap(programToHardware_[progA], programToHardware_[progB]); +} + +size_t Layout::nqubits() const { return programToHardware_.size(); } + +ArrayRef Layout::getProgramToHardware() const { + return programToHardware_; +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp index 7f7075cd22..e9911fd3d1 100644 --- a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp @@ -49,24 +49,24 @@ namespace mlir::qco { /// Writes the conjugate transpose of @p in into @p out (square, row-major). static void adjointInto(ArrayRef in, MutableArrayRef out, - const std::size_t dim) { - for (std::size_t row = 0; row < dim; ++row) { - for (std::size_t col = 0; col < dim; ++col) { + const size_t dim) { + for (size_t row = 0; row < dim; ++row) { + for (size_t col = 0; col < dim; ++col) { out[(row * dim) + col] = std::conj(in[(col * dim) + row]); } } } -template -static void assignFixedImpl(std::int64_t& dim, SmallVector& data, +template +static void assignFixedImpl(int64_t& dim, SmallVector& data, const std::array& src) { - dim = static_cast(Dim); + dim = static_cast(Dim); data.assign(src.begin(), src.end()); } -template +template [[nodiscard]] static bool -isApproxFixedImpl(const std::int64_t dim, ArrayRef data, +isApproxFixedImpl(const int64_t dim, ArrayRef data, const std::array& other, const double tol) { if (std::cmp_not_equal(dim, Dim)) { return false; @@ -74,18 +74,18 @@ isApproxFixedImpl(const std::int64_t dim, ArrayRef data, return entriesAreApprox(data, other, tol); } -template +template [[nodiscard]] static bool assignFromDynamicImpl(const DynamicMatrix& src, std::array& dst) { - if (src.rows() != static_cast(Dim) || - src.cols() != static_cast(Dim)) { + if (src.rows() != static_cast(Dim) || + src.cols() != static_cast(Dim)) { return false; } - for (std::size_t row = 0; row < Dim; ++row) { - for (std::size_t col = 0; col < Dim; ++col) { + for (size_t row = 0; row < Dim; ++row) { + for (size_t col = 0; col < Dim; ++col) { dst[(row * Dim) + col] = - src(static_cast(row), static_cast(col)); + src(static_cast(row), static_cast(col)); } } return true; @@ -111,8 +111,8 @@ static void multiply4x4(const ArrayRef lhs, assert(lhs.size() == Matrix4x4::K_SIZE_AT_COMPILE_TIME && rhs.size() == Matrix4x4::K_SIZE_AT_COMPILE_TIME && out.size() == Matrix4x4::K_SIZE_AT_COMPILE_TIME); - for (std::size_t row = 0; row < Matrix4x4::K_ROWS; ++row) { - const std::size_t rowBase = row * Matrix4x4::K_COLS; + for (size_t row = 0; row < Matrix4x4::K_ROWS; ++row) { + const size_t rowBase = row * Matrix4x4::K_COLS; const Complex& a0 = lhs[rowBase + 0]; const Complex& a1 = lhs[rowBase + 1]; const Complex& a2 = lhs[rowBase + 2]; @@ -124,14 +124,39 @@ static void multiply4x4(const ArrayRef lhs, } } +/// Left-applies a 2x2 gate to a row pair (`out = gate * [a; b]`). +static void apply2x2LeftToRowPair(const ArrayRef gate, Complex& a, + Complex& b) { + assert(gate.size() == Matrix2x2::K_SIZE_AT_COMPILE_TIME); + const Complex newA = gate[0] * a + gate[1] * b; + const Complex newB = gate[2] * a + gate[3] * b; + a = newA; + b = newB; +} + +/// Left-applies a 4x4 gate to a column 4-vector (`[a; b; c; d] = gate * +/// [a; b; c; d]`). +static void apply4x4LeftToColumn(const ArrayRef gate, Complex& a, + Complex& b, Complex& c, Complex& d) { + assert(gate.size() == Matrix4x4::K_SIZE_AT_COMPILE_TIME); + const Complex newA = gate[0] * a + gate[1] * b + gate[2] * c + gate[3] * d; + const Complex newB = gate[4] * a + gate[5] * b + gate[6] * c + gate[7] * d; + const Complex newC = gate[8] * a + gate[9] * b + gate[10] * c + gate[11] * d; + const Complex newD = + gate[12] * a + gate[13] * b + gate[14] * c + gate[15] * d; + a = newA; + b = newB; + c = newC; + d = newD; +} + /// Returns true if @p data is approximately the @p dim x @p dim identity /// matrix. -[[nodiscard]] static bool isIdentityEntries(ArrayRef data, - const std::size_t dim, - const double tol) { +[[nodiscard]] static bool +isIdentityEntries(ArrayRef data, const size_t dim, const double tol) { assert(data.size() >= dim * dim); - for (std::size_t row = 0; row < dim; ++row) { - for (std::size_t col = 0; col < dim; ++col) { + for (size_t row = 0; row < dim; ++row) { + for (size_t col = 0; col < dim; ++col) { const Complex& entry = data[(row * dim) + col]; if (row == col) { if (!entryIsApprox(entry, Complex{1.0, 0.0}, tol)) { @@ -146,65 +171,61 @@ static void multiply4x4(const ArrayRef lhs, } /// Returns @p dim as `size_t` after asserting it is non-negative and squarable. -[[nodiscard]] static std::size_t checkedDim(const std::int64_t dim) { +[[nodiscard]] static size_t checkedDim(const int64_t dim) { assert(dim >= 0 && "DynamicMatrix dimension must be non-negative"); - const auto udim = static_cast(dim); - assert( - (udim == 0 || udim <= std::numeric_limits::max() / udim) && - "DynamicMatrix dimension is too large to allocate storage"); + const auto udim = static_cast(dim); + assert((udim == 0 || udim <= std::numeric_limits::max() / udim) && + "DynamicMatrix dimension is too large to allocate storage"); return udim; } /// Returns the flat row-major index for `(row, col)` after bounds checking. -[[nodiscard]] static std::size_t checkedFlatIndex(const std::size_t row, - const std::size_t col, - const std::size_t dim) { +[[nodiscard]] static size_t checkedFlatIndex(const size_t row, const size_t col, + const size_t dim) { assert(row < dim && col < dim && "matrix index out of bounds"); return (row * dim) + col; } -[[nodiscard]] static std::int64_t checkedFlatIndex(const std::int64_t row, - const std::int64_t col, - const std::int64_t dim) { +[[nodiscard]] static int64_t +checkedFlatIndex(const int64_t row, const int64_t col, const int64_t dim) { assert(row >= 0 && col >= 0 && row < dim && col < dim && "matrix index out of bounds"); return (row * dim) + col; } -[[nodiscard]] static std::size_t checkedStorageSize(const std::int64_t dim) { +[[nodiscard]] static size_t checkedStorageSize(const int64_t dim) { const auto udim = checkedDim(dim); return udim * udim; } /// Returns `2^numQubits` as `int64_t` after checking it fits. -[[nodiscard]] static std::int64_t -checkedHilbertDim(const std::size_t numQubits) { - assert(numQubits < std::numeric_limits::digits && +[[nodiscard]] static int64_t checkedHilbertDim(const size_t numQubits) { + assert(numQubits < std::numeric_limits::digits && "Hilbert-space dimension must fit in int64_t"); - return std::int64_t{static_cast(std::uint64_t{1} << numQubits)}; + return static_cast(uint64_t{1} << numQubits); } -static void validateCornerDims(const std::int64_t matrixDim, - const std::int64_t blockDim) { +static void validateCornerDims(const int64_t matrixDim, + const int64_t blockDim) { assert(matrixDim >= 0 && blockDim >= 0 && blockDim <= matrixDim && "block must fit in the bottom-right corner of the matrix"); std::ignore = checkedDim(matrixDim); } /// Copies @p blockData into the bottom-right @p blockDim x @p blockDim corner. -static void copyBottomRightCorner(const std::int64_t matrixDim, +static void copyBottomRightCorner(const int64_t matrixDim, MutableArrayRef matrixData, - const std::int64_t blockDim, + const int64_t blockDim, ArrayRef blockData) { validateCornerDims(matrixDim, blockDim); assert(matrixData.size() >= checkedStorageSize(matrixDim)); assert(blockData.size() >= checkedStorageSize(blockDim)); - const std::int64_t offset = matrixDim - blockDim; - for (std::int64_t row = 0; row < blockDim; ++row) { - for (std::int64_t col = 0; col < blockDim; ++col) { - matrixData[static_cast(((offset + row) * matrixDim) + - offset + col)] = - blockData[static_cast((row * blockDim) + col)]; + const int64_t offset = matrixDim - blockDim; + for (int64_t row = 0; row < blockDim; ++row) { + for (int64_t col = 0; col < blockDim; ++col) { + matrixData[static_cast(((offset + row) * matrixDim) + offset + + col)] = + blockData[static_cast((row * blockDim) + col)]; } } } @@ -215,9 +236,9 @@ static void copyBottomRightCorner(const std::int64_t matrixDim, * Qubit 0 is the MSB of @p stateIndex, matching @ref Matrix4x4::kron and * @ref Matrix2x2::embedInNqubit. */ -[[nodiscard]] static std::size_t qubitBitAt(const std::size_t stateIndex, - const std::size_t numQubits, - const std::size_t qubitIndex) { +[[nodiscard]] static size_t qubitBitAt(const size_t stateIndex, + const size_t numQubits, + const size_t qubitIndex) { return (stateIndex >> (numQubits - 1 - qubitIndex)) & 1U; } @@ -229,12 +250,10 @@ static void copyBottomRightCorner(const std::int64_t matrixDim, * is zero. For a single-qubit embed, pass @p skipB = @p numQubits so only @p * skipA is skipped. */ -[[nodiscard]] static bool otherQubitBitsMatch(const std::size_t row, - const std::size_t col, - const std::size_t numQubits, - const std::size_t skipA, - const std::size_t skipB) { - for (std::size_t q = 0; q < numQubits; ++q) { +[[nodiscard]] static bool +otherQubitBitsMatch(const size_t row, const size_t col, const size_t numQubits, + const size_t skipA, const size_t skipB) { + for (size_t q = 0; q < numQubits; ++q) { if (q == skipA || q == skipB) { continue; } @@ -245,13 +264,12 @@ static void copyBottomRightCorner(const std::int64_t matrixDim, return true; } -Complex& Matrix1x1::operator()(const std::size_t row, const std::size_t col) { +Complex& Matrix1x1::operator()(const size_t row, const size_t col) { assert(row == 0 && col == 0 && "matrix index out of bounds"); return value; } -Complex Matrix1x1::operator()(const std::size_t row, - const std::size_t col) const { +Complex Matrix1x1::operator()(const size_t row, const size_t col) const { assert(row == 0 && col == 0 && "matrix index out of bounds"); return value; } @@ -279,12 +297,11 @@ Matrix1x1& Matrix1x1::operator*=(const Complex& scalar) { Matrix1x1 Matrix1x1::adjoint() const { return fromElements(std::conj(value)); } -Complex& Matrix2x2::operator()(const std::size_t row, const std::size_t col) { +Complex& Matrix2x2::operator()(const size_t row, const size_t col) { return data[checkedFlatIndex(row, col, K_COLS)]; } -Complex Matrix2x2::operator()(const std::size_t row, - const std::size_t col) const { +Complex Matrix2x2::operator()(const size_t row, const size_t col) const { return data[checkedFlatIndex(row, col, K_COLS)]; } @@ -336,8 +353,8 @@ bool Matrix2x2::assignFrom(const DynamicMatrix& src) { return assignFromDynamicImpl(src, data); } -DynamicMatrix Matrix2x2::embedInNqubit(const std::size_t numQubits, - const std::size_t qubitIndex) const { +DynamicMatrix Matrix2x2::embedInNqubit(const size_t numQubits, + const size_t qubitIndex) const { assert(qubitIndex < numQubits && "Invalid qubit index for single-qubit embed"); if (numQubits == 2) { @@ -345,22 +362,22 @@ DynamicMatrix Matrix2x2::embedInNqubit(const std::size_t numQubits, } const auto dim = checkedHilbertDim(numQubits); DynamicMatrix out(dim); - const auto udim = static_cast(dim); - for (std::size_t row = 0; row < udim; ++row) { - for (std::size_t col = 0; col < udim; ++col) { + const auto udim = static_cast(dim); + for (size_t row = 0; row < udim; ++row) { + for (size_t col = 0; col < udim; ++col) { if (!otherQubitBitsMatch(row, col, numQubits, qubitIndex, numQubits)) { continue; } - const std::size_t rowBit = qubitBitAt(row, numQubits, qubitIndex); - const std::size_t colBit = qubitBitAt(col, numQubits, qubitIndex); - out(static_cast(row), static_cast(col)) = + const size_t rowBit = qubitBitAt(row, numQubits, qubitIndex); + const size_t colBit = qubitBitAt(col, numQubits, qubitIndex); + out(static_cast(row), static_cast(col)) = (*this)(rowBit, colBit); } } return out; } -Matrix4x4 Matrix2x2::embedInTwoQubit(const std::size_t qubitIndex) const { +Matrix4x4 Matrix2x2::embedInTwoQubit(const size_t qubitIndex) const { if (qubitIndex == 0) { return Matrix4x4::kron(*this, Matrix2x2::identity()); } @@ -370,12 +387,11 @@ Matrix4x4 Matrix2x2::embedInTwoQubit(const std::size_t qubitIndex) const { llvm::reportFatalInternalError("Invalid qubit index for single-qubit embed"); } -Complex& Matrix4x4::operator()(const std::size_t row, const std::size_t col) { +Complex& Matrix4x4::operator()(const size_t row, const size_t col) { return data[checkedFlatIndex(row, col, K_COLS)]; } -Complex Matrix4x4::operator()(const std::size_t row, - const std::size_t col) const { +Complex Matrix4x4::operator()(const size_t row, const size_t col) const { return data[checkedFlatIndex(row, col, K_COLS)]; } @@ -451,14 +467,13 @@ Matrix4x4 Matrix4x4::kron(const Matrix2x2& lhs, const Matrix2x2& rhs) { } std::array -Matrix4x4::column(const std::size_t col) const { +Matrix4x4::column(const size_t col) const { assert(col < K_COLS && "matrix index out of bounds"); return {data[col], data[K_COLS + col], data[(2 * K_COLS) + col], data[(3 * K_COLS) + col]}; } -void Matrix4x4::setColumn(const std::size_t col, - const ArrayRef values) { +void Matrix4x4::setColumn(const size_t col, const ArrayRef values) { assert(col < K_COLS && "matrix index out of bounds"); assert(values.size() == K_ROWS && "setColumn requires exactly K_ROWS entries"); @@ -468,15 +483,15 @@ void Matrix4x4::setColumn(const std::size_t col, data[(3 * K_COLS) + col] = values[3]; } -ArrayRef Matrix4x4::row(const std::size_t row) const { +ArrayRef Matrix4x4::row(const size_t row) const { assert(row < K_ROWS && "matrix index out of bounds"); return ArrayRef(data).slice(row * K_COLS, K_COLS); } -void Matrix4x4::setRow(const std::size_t row, const ArrayRef values) { +void Matrix4x4::setRow(const size_t row, const ArrayRef values) { assert(row < K_ROWS && "matrix index out of bounds"); assert(values.size() == K_COLS && "setRow requires exactly K_COLS entries"); - const std::size_t rowBase = row * K_COLS; + const size_t rowBase = row * K_COLS; data[rowBase + 0] = values[0]; data[rowBase + 1] = values[1]; data[rowBase + 2] = values[2]; @@ -507,9 +522,9 @@ bool Matrix4x4::assignFrom(const DynamicMatrix& src) { return assignFromDynamicImpl(src, data); } -DynamicMatrix Matrix4x4::embedInNqubit(const std::size_t numQubits, - const std::size_t q0Index, - const std::size_t q1Index) const { +DynamicMatrix Matrix4x4::embedInNqubit(const size_t numQubits, + const size_t q0Index, + const size_t q1Index) const { assert(q0Index < numQubits && q1Index < numQubits && q0Index != q1Index && "Invalid qubit indices for two-qubit embed"); if (numQubits == 2) { @@ -517,25 +532,25 @@ DynamicMatrix Matrix4x4::embedInNqubit(const std::size_t numQubits, } const auto dim = checkedHilbertDim(numQubits); DynamicMatrix out(dim); - const auto udim = static_cast(dim); - for (std::size_t row = 0; row < udim; ++row) { - for (std::size_t col = 0; col < udim; ++col) { + const auto udim = static_cast(dim); + for (size_t row = 0; row < udim; ++row) { + for (size_t col = 0; col < udim; ++col) { if (!otherQubitBitsMatch(row, col, numQubits, q0Index, q1Index)) { continue; } - const std::size_t rowPair = (qubitBitAt(row, numQubits, q0Index) << 1) | - qubitBitAt(row, numQubits, q1Index); - const std::size_t colPair = (qubitBitAt(col, numQubits, q0Index) << 1) | - qubitBitAt(col, numQubits, q1Index); - out(static_cast(row), static_cast(col)) = + const size_t rowPair = (qubitBitAt(row, numQubits, q0Index) << 1) | + qubitBitAt(row, numQubits, q1Index); + const size_t colPair = (qubitBitAt(col, numQubits, q0Index) << 1) | + qubitBitAt(col, numQubits, q1Index); + out(static_cast(row), static_cast(col)) = (*this)(rowPair, colPair); } } return out; } -Matrix4x4 Matrix4x4::reorderForQubits(const std::size_t q0Index, - const std::size_t q1Index) const { +Matrix4x4 Matrix4x4::reorderForQubits(const size_t q0Index, + const size_t q1Index) const { if (q0Index == 0 && q1Index == 1) { return *this; } @@ -560,31 +575,30 @@ Matrix4x4 Matrix4x4::reorderForQubits(const std::size_t q0Index, static void symmetricTred24(ArrayRef input, std::array& z, std::array& diag, std::array& subdiag) { - constexpr std::size_t n = 4; - const auto zAt = [&z](const std::size_t row, - const std::size_t col) -> double& { + constexpr size_t n = 4; + const auto zAt = [&z](const size_t row, const size_t col) -> double& { return z[row + (col * n)]; }; double h = 0.0; - for (std::size_t col = 0; col < n; ++col) { - for (std::size_t row = col; row < n; ++row) { + for (size_t col = 0; col < n; ++col) { + for (size_t row = col; row < n; ++row) { zAt(row, col) = input[(row * n) + col]; } diag[col] = input[((n - 1) * n) + col]; } for (int i = static_cast(n) - 1; i >= 1; --i) { - const auto ui = static_cast(i); - const std::size_t l = ui - 1; + const auto ui = static_cast(i); + const size_t l = ui - 1; h = 0.0; double scale = 0.0; - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { scale += std::abs(diag[k]); } if (scale == 0.0) { subdiag[ui] = diag[l]; - for (std::size_t j = 0; j <= l; ++j) { + for (size_t j = 0; j <= l; ++j) { diag[j] = zAt(l, j); zAt(ui, j) = 0.0; zAt(j, ui) = 0.0; @@ -592,10 +606,10 @@ static void symmetricTred24(ArrayRef input, std::array& z, diag[ui] = 0.0; continue; } - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { diag[k] /= scale; } - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { h += diag[k] * diag[k]; } const double f = diag[l]; @@ -604,32 +618,32 @@ static void symmetricTred24(ArrayRef input, std::array& z, h -= f * g; diag[l] = f - g; - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { subdiag[k] = 0.0; } - for (std::size_t j = 0; j <= l; ++j) { + for (size_t j = 0; j <= l; ++j) { const double fj = diag[j]; zAt(j, ui) = fj; double gj = subdiag[j] + (zAt(j, j) * fj); - for (std::size_t k = j + 1; k <= l; ++k) { + for (size_t k = j + 1; k <= l; ++k) { gj += zAt(k, j) * diag[k]; subdiag[k] += zAt(k, j) * fj; } subdiag[j] = gj; } double ff = 0.0; - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { subdiag[k] /= h; ff += subdiag[k] * diag[k]; } const double hh = 0.5 * ff / h; - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { subdiag[k] -= hh * diag[k]; } - for (std::size_t j = 0; j <= l; ++j) { + for (size_t j = 0; j <= l; ++j) { const double fj = diag[j]; const double gj = subdiag[j]; - for (std::size_t k = j; k <= l; ++k) { + for (size_t k = j; k <= l; ++k) { zAt(k, j) -= (fj * subdiag[k]) + (gj * diag[k]); } diag[j] = zAt(l, j); @@ -638,34 +652,34 @@ static void symmetricTred24(ArrayRef input, std::array& z, diag[ui] = h; } - for (std::size_t i = 1; i < n; ++i) { - const std::size_t l = i - 1; + for (size_t i = 1; i < n; ++i) { + const size_t l = i - 1; zAt(n - 1, l) = zAt(l, l); zAt(l, l) = 1.0; h = diag[i]; if (h != 0.0) { - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { diag[k] = zAt(k, i) / h; } - for (std::size_t j = 0; j <= l; ++j) { + for (size_t j = 0; j <= l; ++j) { double g = 0.0; - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { g += zAt(k, i) * zAt(k, j); } - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { zAt(k, j) -= g * diag[k]; } } } - for (std::size_t k = 0; k <= l; ++k) { + for (size_t k = 0; k <= l; ++k) { zAt(k, i) = 0.0; } } - for (std::size_t j = 0; j < n; ++j) { + for (size_t j = 0; j < n; ++j) { diag[j] = zAt(n - 1, j); } - for (std::size_t j = 0; j < n - 1; ++j) { + for (size_t j = 0; j < n - 1; ++j) { zAt(n - 1, j) = 0.0; } zAt(n - 1, n - 1) = 1.0; @@ -676,25 +690,24 @@ static void symmetricTred24(ArrayRef input, std::array& z, static void symmetricTql24(std::array& diag, std::array& subdiag, std::array& z) { - constexpr std::size_t n = 4; - const auto zAt = [&z](const std::size_t row, - const std::size_t col) -> double& { + constexpr size_t n = 4; + const auto zAt = [&z](const size_t row, const size_t col) -> double& { return z[row + (col * n)]; }; - for (std::size_t i = 1; i < n; ++i) { + for (size_t i = 1; i < n; ++i) { subdiag[i - 1] = subdiag[i]; } double f = 0.0; double tst1 = 0.0; subdiag[n - 1] = 0.0; - for (std::size_t l = 0; l < n; ++l) { + for (size_t l = 0; l < n; ++l) { int j = 0; const double h = std::abs(diag[l]) + std::abs(subdiag[l]); tst1 = std::max(tst1, h); - std::size_t m = l; + size_t m = l; for (; m < n; ++m) { const double tst2 = tst1 + std::abs(subdiag[m]); if (tst2 == tst1) { @@ -709,8 +722,8 @@ static void symmetricTql24(std::array& diag, } ++j; - const std::size_t l1 = l + 1; - const std::size_t l2 = l1 + 1; + const size_t l1 = l + 1; + const size_t l2 = l1 + 1; const double g = diag[l]; const double p = (diag[l1] - g) / (2.0 * subdiag[l]); const double r = std::hypot(p, 1.0); @@ -718,7 +731,7 @@ static void symmetricTql24(std::array& diag, diag[l1] = subdiag[l] * (p + std::copysign(std::abs(r), p)); const double dl1 = diag[l1]; const double hh = g - diag[l]; - for (std::size_t i = l2; i < n; ++i) { + for (size_t i = l2; i < n; ++i) { diag[i] -= hh; } f += hh; @@ -730,12 +743,12 @@ static void symmetricTql24(std::array& diag, double s = 0.0; double c3 = 1.0; double s2 = 0.0; - const std::size_t mml = m - l; - for (std::size_t ii = 1; ii <= mml; ++ii) { + const size_t mml = m - l; + for (size_t ii = 1; ii <= mml; ++ii) { c3 = c2; c2 = c; s2 = s; - const std::size_t i = m - ii; + const size_t i = m - ii; const double gi = c * subdiag[i]; const double hi = c * pv; const double ri = std::hypot(pv, subdiag[i]); @@ -744,7 +757,7 @@ static void symmetricTql24(std::array& diag, c = pv / ri; pv = (c * diag[i]) - (s * gi); diag[i + 1] = hi + (s * ((c * gi) + (s * diag[i]))); - for (std::size_t k = 0; k < n; ++k) { + for (size_t k = 0; k < n; ++k) { const double zkI1 = zAt(k, i + 1); zAt(k, i + 1) = (s * zAt(k, i)) + (c * zkI1); zAt(k, i) = (c * zAt(k, i)) - (s * zkI1); @@ -763,11 +776,11 @@ static void symmetricTql24(std::array& diag, diag[l] += f; } - for (std::size_t ii = 1; ii < n; ++ii) { - const std::size_t i = ii - 1; - std::size_t k = i; + for (size_t ii = 1; ii < n; ++ii) { + const size_t i = ii - 1; + size_t k = i; double p = diag[i]; - for (std::size_t j = ii; j < n; ++j) { + for (size_t j = ii; j < n; ++j) { if (diag[j] < p) { k = j; p = diag[j]; @@ -778,7 +791,7 @@ static void symmetricTql24(std::array& diag, } diag[k] = diag[i]; diag[i] = p; - for (std::size_t j = 0; j < n; ++j) { + for (size_t j = 0; j < n; ++j) { const double tmp = zAt(j, i); zAt(j, i) = zAt(j, k); zAt(j, k) = tmp; @@ -810,7 +823,7 @@ symmetricEigenDecomposition4x4(const ArrayRef symmetric) { llvm::reportFatalInternalError( "symmetricEigenDecomposition4x4 expects 16 row-major entries"); } - constexpr std::size_t n = 4; + constexpr size_t n = 4; SymmetricEigenDecomposition4x4 result; std::array z{}; @@ -818,8 +831,8 @@ symmetricEigenDecomposition4x4(const ArrayRef symmetric) { symmetricTred24(symmetric, z, result.eigenvalues, subdiag); symmetricTql24(result.eigenvalues, subdiag, z); - for (std::size_t col = 0; col < n; ++col) { - for (std::size_t row = 0; row < n; ++row) { + for (size_t col = 0; col < n; ++col) { + for (size_t row = 0; row < n; ++row) { result.eigenvectors(row, col) = z[row + (col * n)]; } } @@ -865,10 +878,10 @@ class EispackMatrixView { EispackMatrixView(MutableArrayRef values, const int ld) : values_(values), ld_(ld) {} - [[nodiscard]] static std::size_t rowMajorIndex(const int row, const int col, - const int ld) { - return static_cast(row) + - (static_cast(col) * static_cast(ld)); + [[nodiscard]] static size_t rowMajorIndex(const int row, const int col, + const int ld) { + return static_cast(row) + + (static_cast(col) * static_cast(ld)); } [[nodiscard]] double& at(const int row, const int col) { @@ -965,11 +978,11 @@ static void eigenDecompositionReduceToHessenberg( EispackMatrixView matrixImag(matrixImagBuf, leadingDim); const auto householderRealAt = [&householderRealBuf](const int index) -> double& { - return householderRealBuf[static_cast(index)]; + return householderRealBuf[static_cast(index)]; }; const auto householderImagAt = [&householderImagBuf](const int index) -> double& { - return householderImagBuf[static_cast(index)]; + return householderImagBuf[static_cast(index)]; }; const int kp1 = rowLow + 1; @@ -1079,19 +1092,19 @@ eigenDecompositionQrSolve(const int leadingDim, const int order, EispackMatrixView eigenvectorImag(eigenvectorImagBuf, leadingDim); const auto householderRealAt = [&householderRealBuf](const int index) -> double& { - return householderRealBuf[static_cast(index)]; + return householderRealBuf[static_cast(index)]; }; const auto householderImagAt = [&householderImagBuf](const int index) -> double& { - return householderImagBuf[static_cast(index)]; + return householderImagBuf[static_cast(index)]; }; const auto eigenvalueRealAt = [&eigenvalueRealBuf](const int index) -> double& { - return eigenvalueRealBuf[static_cast(index)]; + return eigenvalueRealBuf[static_cast(index)]; }; const auto eigenvalueImagAt = [&eigenvalueImagBuf](const int index) -> double& { - return eigenvalueImagBuf[static_cast(index)]; + return eigenvalueImagBuf[static_cast(index)]; }; for (int j = 0; j < order; ++j) { @@ -1495,8 +1508,7 @@ eigenvectorColumnNorm(const int order, const int col, const int leadingDim, const ArrayRef eigenvectorImag) { double normSq = 0.0; for (int row = 0; row < order; ++row) { - const std::size_t idx = - EispackMatrixView::rowMajorIndex(row, col, leadingDim); + const size_t idx = EispackMatrixView::rowMajorIndex(row, col, leadingDim); normSq += (eigenvectorReal[idx] * eigenvectorReal[idx]) + (eigenvectorImag[idx] * eigenvectorImag[idx]); } @@ -1522,17 +1534,16 @@ eigenvectorColumnNorm(const int order, const int col, const int leadingDim, eigenvectorImag) { EigenDecomposition4x4 result; for (int col = 0; col < K_COMPLEX_EIGEN4_SIZE; ++col) { - result.eigenvalues[static_cast(col)] = - Complex(eigenvalueReal[static_cast(col)], - eigenvalueImag[static_cast(col)]); + result.eigenvalues[static_cast(col)] = + Complex(eigenvalueReal[static_cast(col)], + eigenvalueImag[static_cast(col)]); const double norm = eigenvectorColumnNorm(K_COMPLEX_EIGEN4_SIZE, col, K_COMPLEX_EIGEN4_SIZE, eigenvectorReal, eigenvectorImag); for (int row = 0; row < K_COMPLEX_EIGEN4_SIZE; ++row) { - const std::size_t idx = + const size_t idx = EispackMatrixView::rowMajorIndex(row, col, K_COMPLEX_EIGEN4_SIZE); - result.eigenvectors(static_cast(row), - static_cast(col)) = + result.eigenvectors(static_cast(row), static_cast(col)) = normalizedEigenvectorEntry(eigenvectorReal[idx], eigenvectorImag[idx], norm); } @@ -1544,10 +1555,10 @@ static void splitMatrix4x4ToRealImag( const Matrix4x4& matrix, std::array& matrixReal, std::array& matrixImag) { - for (std::size_t row = 0; row < Matrix4x4::K_ROWS; ++row) { - for (std::size_t col = 0; col < Matrix4x4::K_COLS; ++col) { + for (size_t row = 0; row < Matrix4x4::K_ROWS; ++row) { + for (size_t col = 0; col < Matrix4x4::K_COLS; ++col) { const Complex& value = matrix(row, col); - const std::size_t idx = row + (col * Matrix4x4::K_ROWS); + const size_t idx = row + (col * Matrix4x4::K_ROWS); matrixReal[idx] = std::real(value); matrixImag[idx] = std::imag(value); } @@ -1687,11 +1698,11 @@ eigenDecomposition2x2(const Matrix2x2& matrix) { */ [[nodiscard]] static std::optional eigenDecompositionDynamic(const DynamicMatrix& matrix) { - const std::int64_t dim = matrix.rows(); + const int64_t dim = matrix.rows(); if (dim != matrix.cols()) { return std::nullopt; } - if (dim > static_cast(std::numeric_limits::max())) { + if (dim > static_cast(std::numeric_limits::max())) { return std::nullopt; } assert(dim >= 3 && dim != 4); @@ -1700,25 +1711,24 @@ eigenDecompositionDynamic(const DynamicMatrix& matrix) { const int rowLow = 0; const int rowHigh = order - 1; - const std::size_t matrixStorage = - (static_cast(leadingDim) * static_cast(order)) + - static_cast(order) + 1U; + const size_t matrixStorage = + (static_cast(leadingDim) * static_cast(order)) + + static_cast(order) + 1U; SmallVector matrixReal(matrixStorage); SmallVector matrixImag(matrixStorage); for (int row = 0; row < order; ++row) { for (int col = 0; col < order; ++col) { const Complex value = matrix(row, col); - const std::size_t idx = - EispackMatrixView::rowMajorIndex(row, col, leadingDim); + const size_t idx = EispackMatrixView::rowMajorIndex(row, col, leadingDim); matrixReal[idx] = std::real(value); matrixImag[idx] = std::imag(value); } } - SmallVector householderReal(static_cast(order)); - SmallVector householderImag(static_cast(order)); - SmallVector eigenvalueReal(static_cast(order) + 1U); - SmallVector eigenvalueImag(static_cast(order) + 1U); + SmallVector householderReal(static_cast(order)); + SmallVector householderImag(static_cast(order)); + SmallVector eigenvalueReal(static_cast(order) + 1U); + SmallVector eigenvalueImag(static_cast(order) + 1U); SmallVector eigenvectorReal(matrixStorage); SmallVector eigenvectorImag(matrixStorage); @@ -1734,17 +1744,15 @@ eigenDecompositionDynamic(const DynamicMatrix& matrix) { } EigenDecomposition result; - result.eigenvalues.reserve(static_cast(order)); + result.eigenvalues.reserve(static_cast(order)); result.eigenvectors = DynamicMatrix(dim); for (int col = 0; col < order; ++col) { - result.eigenvalues.emplace_back( - eigenvalueReal[static_cast(col)], - eigenvalueImag[static_cast(col)]); + result.eigenvalues.emplace_back(eigenvalueReal[static_cast(col)], + eigenvalueImag[static_cast(col)]); const double norm = eigenvectorColumnNorm(order, col, leadingDim, eigenvectorReal, eigenvectorImag); for (int row = 0; row < order; ++row) { - const std::size_t idx = - EispackMatrixView::rowMajorIndex(row, col, leadingDim); + const size_t idx = EispackMatrixView::rowMajorIndex(row, col, leadingDim); result.eigenvectors(row, col) = normalizedEigenvectorEntry( eigenvectorReal[idx], eigenvectorImag[idx], norm); } @@ -1766,7 +1774,7 @@ Matrix4x4 Matrix4x4::fromRealRowMajor(const ArrayRef entries) { "Matrix4x4::fromRealRowMajor expects 16 row-major entries"); } Matrix4x4 result; - for (std::size_t i = 0; i < K_SIZE_AT_COMPILE_TIME; ++i) { + for (size_t i = 0; i < K_SIZE_AT_COMPILE_TIME; ++i) { result.data[i] = entries[i]; } return result; @@ -1781,13 +1789,13 @@ SymmetricEigenDecomposition4x4 Matrix4x4::symmetricEigenDecomposition() const { } struct DynamicMatrix::Impl { - std::int64_t dim = 0; + int64_t dim = 0; SmallVector data; }; DynamicMatrix::DynamicMatrix() : impl_(std::make_unique()) {} -DynamicMatrix::DynamicMatrix(const std::int64_t dim) +DynamicMatrix::DynamicMatrix(const int64_t dim) : impl_(std::make_unique()) { impl_->dim = dim; impl_->data.assign(checkedStorageSize(dim), Complex{}); @@ -1820,14 +1828,14 @@ DynamicMatrix::operator=(DynamicMatrix&& other) noexcept = default; DynamicMatrix::~DynamicMatrix() = default; -std::int64_t DynamicMatrix::rows() const { return impl_->dim; } +int64_t DynamicMatrix::rows() const { return impl_->dim; } -std::int64_t DynamicMatrix::cols() const { return impl_->dim; } +int64_t DynamicMatrix::cols() const { return impl_->dim; } -DynamicMatrix DynamicMatrix::identity(const std::int64_t dim) { +DynamicMatrix DynamicMatrix::identity(const int64_t dim) { DynamicMatrix matrix(dim); const auto udim = checkedDim(dim); - for (std::size_t i = 0; i < udim; ++i) { + for (size_t i = 0; i < udim; ++i) { matrix.impl_->data[(i * udim) + i] = 1.0; } return matrix; @@ -1837,28 +1845,24 @@ DynamicMatrix DynamicMatrix::fromAdjoint(const Matrix2x2& src) { return DynamicMatrix(src.adjoint()); } -Complex& DynamicMatrix::operator()(const std::int64_t row, - const std::int64_t col) { +Complex& DynamicMatrix::operator()(const int64_t row, const int64_t col) { return impl_ - ->data[static_cast(checkedFlatIndex(row, col, impl_->dim))]; + ->data[static_cast(checkedFlatIndex(row, col, impl_->dim))]; } -Complex DynamicMatrix::operator()(const std::int64_t row, - const std::int64_t col) const { +Complex DynamicMatrix::operator()(const int64_t row, const int64_t col) const { return impl_ - ->data[static_cast(checkedFlatIndex(row, col, impl_->dim))]; + ->data[static_cast(checkedFlatIndex(row, col, impl_->dim))]; } void DynamicMatrix::setBottomRightCorner(const Matrix2x2& block) { copyBottomRightCorner(impl_->dim, impl_->data, - static_cast(Matrix2x2::K_ROWS), - block.data); + static_cast(Matrix2x2::K_ROWS), block.data); } void DynamicMatrix::setBottomRightCorner(const Matrix4x4& block) { copyBottomRightCorner(impl_->dim, impl_->data, - static_cast(Matrix4x4::K_ROWS), - block.data); + static_cast(Matrix4x4::K_ROWS), block.data); } void DynamicMatrix::setBottomRightCorner(const DynamicMatrix& block) { @@ -1919,7 +1923,7 @@ Complex DynamicMatrix::trace() const { Complex sum{0.0, 0.0}; const auto udim = checkedDim(impl_->dim); const ArrayRef storage = impl_->data; - for (std::size_t i = 0; i < udim; ++i) { + for (size_t i = 0; i < udim; ++i) { sum += storage[(i * udim) + i]; } return sum; @@ -1939,10 +1943,10 @@ DynamicMatrix DynamicMatrix::operator*(const DynamicMatrix& rhs) const { } const auto udim = checkedDim(impl_->dim); - for (std::size_t row = 0; row < udim; ++row) { - for (std::size_t col = 0; col < udim; ++col) { + for (size_t row = 0; row < udim; ++row) { + for (size_t col = 0; col < udim; ++col) { Complex sum{0.0, 0.0}; - for (std::size_t k = 0; k < udim; ++k) { + for (size_t k = 0; k < udim; ++k) { sum += impl_->data[(row * udim) + k] * rhs.impl_->data[(k * udim) + col]; } @@ -1952,9 +1956,84 @@ DynamicMatrix DynamicMatrix::operator*(const DynamicMatrix& rhs) const { return out; } +void DynamicMatrix::premultiplyBy(const DynamicMatrix& lhs) { + *this = lhs * *this; +} + +void DynamicMatrix::premultiplyByEmbedded1Q(const Matrix2x2& gate, + const size_t numQubits, + const size_t qubitIndex) { + assert(qubitIndex < numQubits && + static_cast(impl_->dim) == (uint64_t{1} << numQubits) && + "Matrix dimension must match numQubits"); + if (std::cmp_equal(impl_->dim, Matrix2x2::K_ROWS)) { + assert(qubitIndex == 0); + std::array tmp{}; + multiply2x2(gate.data, impl_->data, tmp); + impl_->data.assign(tmp.begin(), tmp.end()); + return; + } + const auto udim = checkedDim(impl_->dim); + const size_t mask = size_t{1} << (numQubits - 1 - qubitIndex); + const size_t step = mask << 1; + auto& data = impl_->data; + for (size_t chunk = 0; chunk < udim; chunk += step) { + for (size_t inner = 0; inner < mask; ++inner) { + const size_t base = chunk | inner; + const size_t row1 = base | mask; + for (size_t col = 0; col < udim; ++col) { + const size_t idx0 = (base * udim) + col; + const size_t idx1 = (row1 * udim) + col; + apply2x2LeftToRowPair(gate.data, data[idx0], data[idx1]); + } + } + } +} + +void DynamicMatrix::premultiplyByEmbedded2Q(const Matrix4x4& gate, + const size_t numQubits, + const size_t q0Index, + const size_t q1Index) { + assert(q0Index < numQubits && q1Index < numQubits && q0Index != q1Index && + static_cast(impl_->dim) == (uint64_t{1} << numQubits) && + "Matrix dimension must match numQubits"); + if (std::cmp_equal(impl_->dim, Matrix4x4::K_ROWS)) { + assert(q0Index == 0 && q1Index == 1); + std::array tmp{}; + multiply4x4(gate.data, impl_->data, tmp); + impl_->data.assign(tmp.begin(), tmp.end()); + return; + } + const auto udim = checkedDim(impl_->dim); + const size_t mask0 = size_t{1} << (numQubits - 1 - q0Index); + const size_t mask1 = size_t{1} << (numQubits - 1 - q1Index); + auto& data = impl_->data; + for (size_t block = 0; block < (udim >> 2); ++block) { + size_t base = 0; + size_t rest = block; + for (size_t q = 0; q < numQubits; ++q) { + if (q == q0Index || q == q1Index) { + continue; + } + if ((rest & 1U) != 0) { + base |= size_t{1} << (numQubits - 1 - q); + } + rest >>= 1U; + } + const std::array rowIdx = { + base, base | mask1, base | mask0, base | mask0 | mask1}; + for (size_t col = 0; col < udim; ++col) { + apply4x4LeftToColumn(gate.data, data[(rowIdx[0] * udim) + col], + data[(rowIdx[1] * udim) + col], + data[(rowIdx[2] * udim) + col], + data[(rowIdx[3] * udim) + col]); + } + } +} + DynamicMatrix DynamicMatrix::operator*(const Complex& scalar) const { DynamicMatrix out(impl_->dim); - for (std::size_t i = 0; i < impl_->data.size(); ++i) { + for (size_t i = 0; i < impl_->data.size(); ++i) { out.impl_->data[i] = impl_->data[i] * scalar; } return out; @@ -2002,7 +2081,7 @@ EigenDecomposition::from(const EigenDecomposition4x4& eigen4) { } std::optional DynamicMatrix::eigenDecomposition() const { - const std::size_t dim = checkedDim(impl_->dim); + const size_t dim = checkedDim(impl_->dim); if (dim == 0) { return std::nullopt; } diff --git a/mlir/lib/Dialect/QCO/Utils/Qubits.cpp b/mlir/lib/Dialect/QCO/Utils/Qubits.cpp deleted file mode 100644 index c01187f625..0000000000 --- a/mlir/lib/Dialect/QCO/Utils/Qubits.cpp +++ /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 - */ - -#include "mlir/Dialect/QCO/Utils/Qubits.h" - -#include "mlir/Dialect/QCO/IR/QCODialect.h" - -#include - -#include -#include -#include - -namespace mlir::qco { -void Qubits::add(TypedValue q) { - const auto index = programToValue_.size(); - programToValue_.try_emplace(index, q); - valueToIndex_.try_emplace(q, std::make_pair(QubitLocation::Program, index)); -} - -void Qubits::add(TypedValue q, std::size_t hw) { - hardwareToValue_.try_emplace(hw, q); - valueToIndex_.try_emplace(q, std::make_pair(QubitLocation::Hardware, hw)); -} - -void Qubits::remap(TypedValue prev, TypedValue next) { - assert(valueToIndex_.contains(prev)); - const auto& [location, index] = valueToIndex_.lookup(prev); - - valueToIndex_.erase(prev); - valueToIndex_.try_emplace(next, std::make_pair(location, index)); - - if (location == QubitLocation::Program) { - programToValue_[index] = next; - return; - } - - hardwareToValue_[index] = next; -} - -void Qubits::remove(TypedValue q) { - assert(valueToIndex_.contains(q)); - const auto& [location, index] = valueToIndex_.lookup(q); - - valueToIndex_.erase(q); - - if (location == QubitLocation::Program) { - programToValue_.erase(index); - return; - } - - hardwareToValue_.erase(index); -} - -TypedValue Qubits::getProgramQubit(std::size_t index) const { - assert(programToValue_.contains(index)); - return programToValue_.lookup(index); -} - -TypedValue Qubits::getHardwareQubit(std::size_t index) const { - assert(hardwareToValue_.contains(index)); - return hardwareToValue_.lookup(index); -} - -std::size_t Qubits::getIndex(TypedValue q) const { - assert(valueToIndex_.contains(q)); - const auto& res = valueToIndex_.lookup(q); - return res.second; -} -} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 40b89cde52..0004d0ebfb 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -51,12 +51,14 @@ QIRProgramBuilder::QIRProgramBuilder(MLIRContext* context) getContext()->loadDialect(); } -void QIRProgramBuilder::initialize() { +void QIRProgramBuilder::initialize() { initialize(getI64Type()); } + +void QIRProgramBuilder::initialize(Type returnType) { // Set insertion point to the module body setInsertionPointToStart(cast(module).getBody()); // Create main function: () -> i64 - auto funcType = LLVM::LLVMFunctionType::get(getI64Type(), {}); + auto funcType = LLVM::LLVMFunctionType::get(returnType, {}); auto mainFuncOp = LLVM::LLVMFuncOp::create(*this, "main", funcType); mainFunc = mainFuncOp.getOperation(); @@ -87,7 +89,6 @@ void QIRProgramBuilder::initialize() { // Create exit code constant in entry block setInsertionPointToStart(entryBlock); - exitCode = intConstant(0); // Add initialize call auto initSig = LLVM::LLVMFunctionType::get(voidType, ptrType); @@ -99,14 +100,19 @@ void QIRProgramBuilder::initialize() { setInsertionPointToEnd(entryBlock); LLVM::BrOp::create(*this, bodyBlock); - // Return the exit code (success) in output block - setInsertionPointToEnd(outputBlock); - LLVM::ReturnOp::create(*this, exitCode); - // Set insertion point to body block for user operations setInsertionPointToStart(bodyBlock); } +void QIRProgramBuilder::retype(Type returnType) { + auto mainFn = dyn_cast(mainFunc); + if (!mainFn) { + llvm::reportFatalUsageError("Main function not found for retyping"); + } + auto funcType = LLVM::LLVMFunctionType::get(returnType, {}); + mainFn.setType(funcType); +} + Value QIRProgramBuilder::resolveIntVariant( const std::variant& variant) { if (std::holds_alternative(variant)) { @@ -349,7 +355,8 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, return {.name = name, .size = size}; } -Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { +Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex, + bool record) { checkFinalized(); if (resultIndex < 0) { @@ -378,10 +385,14 @@ Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { getOrCreateFunctionDeclaration(*this, module, QIR_MEASURE, mzSig); LLVM::CallOp::create(*this, mzDec, ValueRange{qubit, result}); + if (record) { + recordedIndices.insert(resultIndex); + } + return result; } -Value QIRProgramBuilder::measure(Value qubit, const Bit& bit) { +Value QIRProgramBuilder::measure(Value qubit, const Bit& bit, bool record) { checkFinalized(); const InsertionGuard guard(*this); @@ -400,6 +411,21 @@ Value QIRProgramBuilder::measure(Value qubit, const Bit& bit) { getOrCreateFunctionDeclaration(*this, module, QIR_MEASURE, fnSig); LLVM::CallOp::create(*this, fnDec, ValueRange{qubit, result}); + if (record) { + if (profile == Profile::Adaptive) { + recordedArrays.insert(stringSaver.save(bit.registerName)); + } else { + // In the base profile we don't have recorded arrays, so we need to + // find the index of the result and record that instead. + for (const auto& [index, ptr] : resultPtrs) { + if (ptr == result) { + recordedIndices.insert(index); + break; + } + } + } + } + return result; } @@ -951,6 +977,9 @@ void QIRProgramBuilder::generateOutputRecording() { getOrCreateFunctionDeclaration(*this, module, QIR_RECORD_OUTPUT, fnSig); // Create output recording for each result pointer for (const auto& [index, ptr] : resultPtrs) { + if (!recordedIndices.contains(index)) { + continue; + } auto label = createResultLabel(*this, module, "__unnamed__" + std::to_string(index)) .getResult(); @@ -965,6 +994,9 @@ void QIRProgramBuilder::generateOutputRecording() { QIR_ARRAY_RECORD_OUTPUT, fnSig); // Create output recording for each register for (const auto& [name, results] : resultArrays) { + if (!recordedArrays.contains(name)) { + continue; + } auto size = results.getDefiningOp().getArraySize(); auto label = createResultLabel(*this, module, name).getResult(); LLVM::CallOp::create(*this, fnDec, ValueRange{size, results, label}); @@ -975,10 +1007,21 @@ void QIRProgramBuilder::generateOutputRecording() { OwningOpRef QIRProgramBuilder::finalize() { checkFinalized(); + auto exitCode = intConstant(0); + return finalize(exitCode); +} + +OwningOpRef QIRProgramBuilder::finalize(Value returnValue) { + checkFinalized(); + // Save current insertion point const InsertionGuard guard(*this); const bool isAdaptive = (profile == Profile::Adaptive); + // Add return statement with the given return values to the main function + setInsertionPointToEnd(outputBlock); + LLVM::ReturnOp::create(*this, returnValue); + // Release resources in output block setInsertionPoint(outputBlock->getTerminator()); @@ -1034,12 +1077,13 @@ OwningOpRef QIRProgramBuilder::finalize() { OwningOpRef QIRProgramBuilder::build( MLIRContext* context, - const function_ref& buildFunc, Profile profile) { + const function_ref& buildFunc, Profile profile) { QIRProgramBuilder builder(context); builder.profile = profile; builder.initialize(); - buildFunc(builder); - return builder.finalize(); + auto result = buildFunc(builder); + builder.retype(result.getType()); + return builder.finalize(result); } } // namespace mlir::qir diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 064cdbd624..6af8c5bff7 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -26,12 +26,8 @@ namespace mlir::qtensor { TypedValue TensorIterator::tensor() const { - if (op_ == nullptr) { - return tensor_; - } - // The following operations don't have an OpResult. - if (isa(op_)) { + if (op_ != nullptr && isa(op_)) { return nullptr; } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index ddc3e4ce4d..b67f8c7f96 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -34,8 +34,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -44,8 +46,13 @@ namespace mqt::test::compiler { -using QCProgramBuilderFn = NamedBuilder; -using QIRProgramBuilderFn = NamedBuilder; +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::qco; +using namespace mlir::qir; + +using QCProgramBuilderFn = NamedMLIRBuilder; +using QIRProgramBuilderFn = NamedMLIRBuilder; using QuantumComputationBuilderFn = NamedBuilder<::qc::QuantumComputation>; namespace { @@ -82,46 +89,44 @@ std::ostream& operator<<(std::ostream& os, class CompilerPipelineTest : public testing::TestWithParam { protected: - std::unique_ptr context; + std::unique_ptr context; void SetUp() override { - mlir::DialectRegistry registry; - registry.insert(); - context = std::make_unique(); + DialectRegistry registry; + registry + .insert(); + context = std::make_unique(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); } - [[nodiscard]] mlir::OwningOpRef + [[nodiscard]] OwningOpRef buildQCReference(const QCProgramBuilderFn builder) const { - auto module = mlir::qc::QCProgramBuilder::build(context.get(), builder.fn); + auto module = mqt::test::buildMLIRProgram(context.get(), builder); EXPECT_TRUE(runQCCleanupPipeline(module.get()).succeeded()); return module; } - [[nodiscard]] mlir::OwningOpRef + [[nodiscard]] OwningOpRef buildQIRReference(const QIRProgramBuilderFn builder) const { - auto module = mlir::qir::QIRProgramBuilder::build( - context.get(), builder.fn, - mlir::qir::QIRProgramBuilder::Profile::Adaptive); + auto module = mqt::test::buildMLIRProgram( + context.get(), builder, QIRProgramBuilder::Profile::Adaptive); EXPECT_TRUE(runQIRCleanupPipeline(module.get(), true).succeeded()); return module; } - [[nodiscard]] mlir::OwningOpRef + [[nodiscard]] OwningOpRef parseRecordedModule(const std::string& ir) const { - return mlir::parseSourceString(ir, context.get()); + return parseSourceString(ir, context.get()); } - static void runPipeline(const mlir::ModuleOp module, const bool convertToQIR, + static void runPipeline(const ModuleOp module, const bool convertToQIR, const bool disableMergeSingleQubitRotationGates, const bool enableHadamardLifting, - mlir::CompilationRecord& record) { - mlir::QuantumCompilerConfig config; + CompilationRecord& record) { + QuantumCompilerConfig config; config.convertToQIRAdaptive = convertToQIR; config.disableMergeSingleQubitRotationGates = disableMergeSingleQubitRotationGates; @@ -129,16 +134,16 @@ class CompilerPipelineTest config.recordIntermediates = true; config.printIRAfterAllStages = true; - mlir::QuantumCompilerPipeline pipeline(config); + QuantumCompilerPipeline pipeline(config); ASSERT_TRUE(pipeline.runPipeline(module, &record).succeeded()); } void expectEquivalent(const std::string& stage, const std::string& ir, - const mlir::ModuleOp expected) const { + const ModuleOp expected) const { auto actual = parseRecordedModule(ir); ASSERT_TRUE(actual) << stage << " failed to parse"; - EXPECT_TRUE(mlir::verify(*actual).succeeded()); - EXPECT_TRUE(mlir::verify(expected).succeeded()); + EXPECT_TRUE(verify(*actual).succeeded()); + EXPECT_TRUE(verify(expected).succeeded()); EXPECT_TRUE(areModulesEquivalentWithPermutations(actual.get(), expected)); } }; @@ -150,25 +155,25 @@ TEST_P(CompilerPipelineTest, EndToEndPipeline) { const auto name = " (" + testCase.name + ")"; DeferredPrinter printer; - mlir::OwningOpRef module; + OwningOpRef module; if (testCase.startFromQuantumComputation) { ASSERT_TRUE(testCase.quantumComputationBuilder); ::qc::QuantumComputation comp; testCase.quantumComputationBuilder.fn(comp); - module = mlir::translateQuantumComputationToQC(context.get(), comp); + module = translateQuantumComputationToQC(context.get(), comp); ASSERT_TRUE(module); printer.record(module.get(), "QC Import" + name); } else { ASSERT_TRUE(testCase.qcProgramBuilder); - module = mlir::qc::QCProgramBuilder::build(context.get(), - testCase.qcProgramBuilder.fn); + module = + mqt::test::buildMLIRProgram(context.get(), testCase.qcProgramBuilder); ASSERT_TRUE(module); printer.record(module.get(), "QC Input" + name); } - EXPECT_TRUE(mlir::verify(*module).succeeded()); + EXPECT_TRUE(verify(*module).succeeded()); - mlir::CompilationRecord record; + CompilationRecord record; runPipeline(module.get(), testCase.convertToQIR, false, false, record); ASSERT_TRUE(testCase.qcReferenceBuilder); @@ -207,15 +212,16 @@ TEST_P(CompilerPipelineTest, EndToEndPipeline) { * Correctness of the pass is tested in a dedicated test. */ TEST_F(CompilerPipelineTest, RotationGateMergingPass) { - auto module = mlir::qc::QCProgramBuilder::build( - context.get(), [&](mlir::qc::QCProgramBuilder& b) { + auto module = + QCProgramBuilder::build(context.get(), [&](QCProgramBuilder& b) { auto q = b.allocQubit(); b.rz(1.0, q); b.rx(1.0, q); + return b.measure(q); }); ASSERT_TRUE(module); - mlir::CompilationRecord record; + CompilationRecord record; runPipeline(module.get(), false, false, false, record); // The outputs must differ, proving the pass ran and transformed the IR @@ -230,15 +236,16 @@ TEST_F(CompilerPipelineTest, RotationGateMergingPass) { * Correctness of the pass is tested in a dedicated test. */ TEST_F(CompilerPipelineTest, HadamardLiftingPass) { - auto module = mlir::qc::QCProgramBuilder::build( - context.get(), [&](mlir::qc::QCProgramBuilder& b) { + auto module = + QCProgramBuilder::build(context.get(), [&](QCProgramBuilder& b) { auto q = b.allocQubit(); b.x(q); b.h(q); + return b.measure(q); }); ASSERT_TRUE(module); - mlir::CompilationRecord record; + CompilationRecord record; runPipeline(module.get(), false, true, true, record); // The outputs must differ, proving the pass ran and transformed the IR @@ -277,419 +284,431 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithInv), false}, - CompilerPipelineTestCase{"AllocQubit", - MQT_NAMED_BUILDER(qc::allocQubit), nullptr, - MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, - CompilerPipelineTestCase{"AllocQubitRegister", - MQT_NAMED_BUILDER(qc::allocQubitRegister), - nullptr, MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, + CompilerPipelineTestCase{ + "AllocQubit", MQT_NAMED_BUILDER(::qc::allocQubit), nullptr, + MQT_NAMED_BUILDER(mlir::qc::alloc1QubitRegister), + MQT_NAMED_BUILDER(mlir::qir::alloc1QubitRegister)}, + CompilerPipelineTestCase{ + "AllocQubitRegister", MQT_NAMED_BUILDER(::qc::allocQubitRegister), + nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubitRegister), + MQT_NAMED_BUILDER(mlir::qir::allocQubitRegister)}, CompilerPipelineTestCase{ "AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(qc::allocMultipleQubitRegisters), nullptr, - MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, - CompilerPipelineTestCase{"AllocLargeRegister", - MQT_NAMED_BUILDER(qc::allocLargeRegister), - nullptr, MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, + MQT_NAMED_BUILDER(::qc::allocMultipleQubitRegisters), nullptr, + MQT_NAMED_BUILDER(mlir::qc::allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(mlir::qir::allocMultipleQubitRegisters)}, + CompilerPipelineTestCase{ + "AllocLargeRegister", MQT_NAMED_BUILDER(::qc::allocLargeRegister), + nullptr, MQT_NAMED_BUILDER(mlir::qc::allocLargeRegister), + MQT_NAMED_BUILDER(mlir::qir::allocQubitRegister)}, CompilerPipelineTestCase{ "SingleMeasurementToSingleBit", - MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit), nullptr, + MQT_NAMED_BUILDER(::qc::singleMeasurementToSingleBit), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(mlir::qir::singleMeasurementToSingleBit)}, + MQT_NAMED_BUILDER(mlir::qir::singleMeasurementToSingleBit)}, CompilerPipelineTestCase{ "RepeatedMeasurementToSameBit", - MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit), nullptr, + MQT_NAMED_BUILDER(::qc::repeatedMeasurementToSameBit), nullptr, MQT_NAMED_BUILDER(mlir::qc::repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(mlir::qir::repeatedMeasurementToSameBit)}, + MQT_NAMED_BUILDER(mlir::qir::repeatedMeasurementToSameBit)}, CompilerPipelineTestCase{ "RepeatedMeasurementToDifferentBits", - MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits), nullptr, + MQT_NAMED_BUILDER(::qc::repeatedMeasurementToDifferentBits), + nullptr, MQT_NAMED_BUILDER(mlir::qc::repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(mlir::qir::repeatedMeasurementToDifferentBits)}, + MQT_NAMED_BUILDER( + mlir::qir::repeatedMeasurementToDifferentBits)}, CompilerPipelineTestCase{ "MultipleClassicalRegistersAndMeasurements", - MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements), + MQT_NAMED_BUILDER(::qc::multipleClassicalRegistersAndMeasurements), nullptr, MQT_NAMED_BUILDER( mlir::qc::multipleClassicalRegistersAndMeasurements), MQT_NAMED_BUILDER( - mlir::qir::multipleClassicalRegistersAndMeasurements)}, + mlir::qir::multipleClassicalRegistersAndMeasurements)}, CompilerPipelineTestCase{ "MeasurementWithoutRegisters", nullptr, MQT_NAMED_BUILDER(mlir::qc::measurementWithoutRegisters), MQT_NAMED_BUILDER(mlir::qc::measurementWithoutRegisters), - MQT_NAMED_BUILDER(mlir::qir::measurementWithoutRegisters), false}, + MQT_NAMED_BUILDER(mlir::qir::measurementWithoutRegisters), + false}, CompilerPipelineTestCase{ "ResetQubitAfterSingleOp", - MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp), nullptr, + MQT_NAMED_BUILDER(::qc::resetQubitAfterSingleOp), nullptr, MQT_NAMED_BUILDER(mlir::qc::resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(mlir::qir::resetQubitAfterSingleOp)}, + MQT_NAMED_BUILDER(mlir::qir::resetQubitAfterSingleOp)}, CompilerPipelineTestCase{ "ResetMultipleQubitsAfterSingleOp", - MQT_NAMED_BUILDER(qc::resetMultipleQubitsAfterSingleOp), nullptr, + MQT_NAMED_BUILDER(::qc::resetMultipleQubitsAfterSingleOp), nullptr, MQT_NAMED_BUILDER(mlir::qc::resetMultipleQubitsAfterSingleOp), - MQT_NAMED_BUILDER(mlir::qir::resetMultipleQubitsAfterSingleOp)}, + MQT_NAMED_BUILDER( + mlir::qir::resetMultipleQubitsAfterSingleOp)}, CompilerPipelineTestCase{ "RepeatedResetAfterSingleOp", - MQT_NAMED_BUILDER(qc::repeatedResetAfterSingleOp), nullptr, + MQT_NAMED_BUILDER(::qc::repeatedResetAfterSingleOp), nullptr, MQT_NAMED_BUILDER(mlir::qc::resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(mlir::qir::resetQubitAfterSingleOp)}, - CompilerPipelineTestCase{"GlobalPhase", - MQT_NAMED_BUILDER(qc::globalPhase), nullptr, - MQT_NAMED_BUILDER(mlir::qc::globalPhase), - MQT_NAMED_BUILDER(mlir::qir::globalPhase)}, - CompilerPipelineTestCase{"Identity", MQT_NAMED_BUILDER(qc::identity), - nullptr, MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, + MQT_NAMED_BUILDER(mlir::qir::resetQubitAfterSingleOp)}, + CompilerPipelineTestCase{ + "GlobalPhase", MQT_NAMED_BUILDER(::qc::globalPhase), nullptr, + MQT_NAMED_BUILDER(mlir::qc::globalPhase), + MQT_NAMED_BUILDER(mlir::qir::globalPhase)}, + CompilerPipelineTestCase{ + "Identity", MQT_NAMED_BUILDER(::qc::identity), nullptr, + MQT_NAMED_BUILDER(mlir::qc::alloc1QubitRegister), + MQT_NAMED_BUILDER(mlir::qir::alloc1QubitRegister)}, CompilerPipelineTestCase{ "SingleControlledIdentity", - MQT_NAMED_BUILDER(qc::singleControlledIdentity), nullptr, - MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, + MQT_NAMED_BUILDER(::qc::singleControlledIdentity), nullptr, + MQT_NAMED_BUILDER(mlir::qc::allocQubitRegister), + MQT_NAMED_BUILDER(mlir::qir::allocQubitRegister)}, CompilerPipelineTestCase{ "MultipleControlledIdentity", - MQT_NAMED_BUILDER(qc::multipleControlledIdentity), nullptr, - MQT_NAMED_BUILDER(mlir::qc::emptyQC), - MQT_NAMED_BUILDER(mlir::qir::emptyQIR)}, - CompilerPipelineTestCase{"X", MQT_NAMED_BUILDER(qc::x), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledIdentity), nullptr, + MQT_NAMED_BUILDER(mlir::qc::alloc3QubitRegister), + MQT_NAMED_BUILDER(mlir::qir::alloc3QubitRegister)}, + CompilerPipelineTestCase{"X", MQT_NAMED_BUILDER(::qc::x), nullptr, MQT_NAMED_BUILDER(mlir::qc::x), - MQT_NAMED_BUILDER(mlir::qir::x)}, + MQT_NAMED_BUILDER(mlir::qir::x)}, CompilerPipelineTestCase{ - "SingleControlledX", MQT_NAMED_BUILDER(qc::singleControlledX), + "SingleControlledX", MQT_NAMED_BUILDER(::qc::singleControlledX), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledX), - MQT_NAMED_BUILDER(mlir::qir::singleControlledX)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledX)}, CompilerPipelineTestCase{ - "MultipleControlledX", MQT_NAMED_BUILDER(qc::multipleControlledX), + "MultipleControlledX", MQT_NAMED_BUILDER(::qc::multipleControlledX), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledX), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledX)}, - CompilerPipelineTestCase{"Y", MQT_NAMED_BUILDER(qc::y), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledX)}, + CompilerPipelineTestCase{"Y", MQT_NAMED_BUILDER(::qc::y), nullptr, MQT_NAMED_BUILDER(mlir::qc::y), - MQT_NAMED_BUILDER(mlir::qir::y)}, + MQT_NAMED_BUILDER(mlir::qir::y)}, CompilerPipelineTestCase{ - "SingleControlledY", MQT_NAMED_BUILDER(qc::singleControlledY), + "SingleControlledY", MQT_NAMED_BUILDER(::qc::singleControlledY), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledY), - MQT_NAMED_BUILDER(mlir::qir::singleControlledY)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledY)}, CompilerPipelineTestCase{ - "MultipleControlledY", MQT_NAMED_BUILDER(qc::multipleControlledY), + "MultipleControlledY", MQT_NAMED_BUILDER(::qc::multipleControlledY), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledY), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledY)}, - CompilerPipelineTestCase{"Z", MQT_NAMED_BUILDER(qc::z), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledY)}, + CompilerPipelineTestCase{"Z", MQT_NAMED_BUILDER(::qc::z), nullptr, MQT_NAMED_BUILDER(mlir::qc::z), - MQT_NAMED_BUILDER(mlir::qir::z)}, + MQT_NAMED_BUILDER(mlir::qir::z)}, CompilerPipelineTestCase{ - "SingleControlledZ", MQT_NAMED_BUILDER(qc::singleControlledZ), + "SingleControlledZ", MQT_NAMED_BUILDER(::qc::singleControlledZ), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledZ), - MQT_NAMED_BUILDER(mlir::qir::singleControlledZ)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledZ)}, CompilerPipelineTestCase{ - "MultipleControlledZ", MQT_NAMED_BUILDER(qc::multipleControlledZ), + "MultipleControlledZ", MQT_NAMED_BUILDER(::qc::multipleControlledZ), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledZ), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledZ)}, - CompilerPipelineTestCase{"H", MQT_NAMED_BUILDER(qc::h), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledZ)}, + CompilerPipelineTestCase{"H", MQT_NAMED_BUILDER(::qc::h), nullptr, MQT_NAMED_BUILDER(mlir::qc::h), - MQT_NAMED_BUILDER(mlir::qir::h)}, + MQT_NAMED_BUILDER(mlir::qir::h)}, CompilerPipelineTestCase{ - "SingleControlledH", MQT_NAMED_BUILDER(qc::singleControlledH), + "SingleControlledH", MQT_NAMED_BUILDER(::qc::singleControlledH), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledH), - MQT_NAMED_BUILDER(mlir::qir::singleControlledH)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledH)}, CompilerPipelineTestCase{ - "MultipleControlledH", MQT_NAMED_BUILDER(qc::multipleControlledH), + "MultipleControlledH", MQT_NAMED_BUILDER(::qc::multipleControlledH), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledH), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledH)}, - CompilerPipelineTestCase{"HWithoutRegister", nullptr, - MQT_NAMED_BUILDER(mlir::qc::hWithoutRegister), - MQT_NAMED_BUILDER(mlir::qc::hWithoutRegister), - MQT_NAMED_BUILDER(mlir::qir::hWithoutRegister), - false}, - CompilerPipelineTestCase{"S", MQT_NAMED_BUILDER(qc::s), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledH)}, + CompilerPipelineTestCase{ + "HWithoutRegister", nullptr, + MQT_NAMED_BUILDER(mlir::qc::hWithoutRegister), + MQT_NAMED_BUILDER(mlir::qc::hWithoutRegister), + MQT_NAMED_BUILDER(mlir::qir::hWithoutRegister), false}, + CompilerPipelineTestCase{"S", MQT_NAMED_BUILDER(::qc::s), nullptr, MQT_NAMED_BUILDER(mlir::qc::s), - MQT_NAMED_BUILDER(mlir::qir::s)}, + MQT_NAMED_BUILDER(mlir::qir::s)}, CompilerPipelineTestCase{ - "SingleControlledS", MQT_NAMED_BUILDER(qc::singleControlledS), + "SingleControlledS", MQT_NAMED_BUILDER(::qc::singleControlledS), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledS), - MQT_NAMED_BUILDER(mlir::qir::singleControlledS)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledS)}, CompilerPipelineTestCase{ - "MultipleControlledS", MQT_NAMED_BUILDER(qc::multipleControlledS), + "MultipleControlledS", MQT_NAMED_BUILDER(::qc::multipleControlledS), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledS), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledS)}, - CompilerPipelineTestCase{"Sdg", MQT_NAMED_BUILDER(qc::sdg), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledS)}, + CompilerPipelineTestCase{"Sdg", MQT_NAMED_BUILDER(::qc::sdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::sdg), - MQT_NAMED_BUILDER(mlir::qir::sdg)}, + MQT_NAMED_BUILDER(mlir::qir::sdg)}, CompilerPipelineTestCase{ - "SingleControlledSdg", MQT_NAMED_BUILDER(qc::singleControlledSdg), + "SingleControlledSdg", MQT_NAMED_BUILDER(::qc::singleControlledSdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledSdg), - MQT_NAMED_BUILDER(mlir::qir::singleControlledSdg)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledSdg)}, CompilerPipelineTestCase{ "MultipleControlledSdg", - MQT_NAMED_BUILDER(qc::multipleControlledSdg), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledSdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledSdg), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledSdg)}, - CompilerPipelineTestCase{"T", MQT_NAMED_BUILDER(qc::t_), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledSdg)}, + CompilerPipelineTestCase{"T", MQT_NAMED_BUILDER(::qc::t_), nullptr, MQT_NAMED_BUILDER(mlir::qc::t_), - MQT_NAMED_BUILDER(mlir::qir::t_)}, + MQT_NAMED_BUILDER(mlir::qir::t_)}, CompilerPipelineTestCase{ - "SingleControlledT", MQT_NAMED_BUILDER(qc::singleControlledT), + "SingleControlledT", MQT_NAMED_BUILDER(::qc::singleControlledT), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledT), - MQT_NAMED_BUILDER(mlir::qir::singleControlledT)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledT)}, CompilerPipelineTestCase{ - "MultipleControlledT", MQT_NAMED_BUILDER(qc::multipleControlledT), + "MultipleControlledT", MQT_NAMED_BUILDER(::qc::multipleControlledT), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledT), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledT)}, - CompilerPipelineTestCase{"Tdg", MQT_NAMED_BUILDER(qc::tdg), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledT)}, + CompilerPipelineTestCase{"Tdg", MQT_NAMED_BUILDER(::qc::tdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::tdg), - MQT_NAMED_BUILDER(mlir::qir::tdg)}, + MQT_NAMED_BUILDER(mlir::qir::tdg)}, CompilerPipelineTestCase{ - "SingleControlledTdg", MQT_NAMED_BUILDER(qc::singleControlledTdg), + "SingleControlledTdg", MQT_NAMED_BUILDER(::qc::singleControlledTdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledTdg), - MQT_NAMED_BUILDER(mlir::qir::singleControlledTdg)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledTdg)}, CompilerPipelineTestCase{ "MultipleControlledTdg", - MQT_NAMED_BUILDER(qc::multipleControlledTdg), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledTdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledTdg), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledTdg)}, - CompilerPipelineTestCase{"SX", MQT_NAMED_BUILDER(qc::sx), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledTdg)}, + CompilerPipelineTestCase{"SX", MQT_NAMED_BUILDER(::qc::sx), nullptr, MQT_NAMED_BUILDER(mlir::qc::sx), - MQT_NAMED_BUILDER(mlir::qir::sx)}, + MQT_NAMED_BUILDER(mlir::qir::sx)}, CompilerPipelineTestCase{ - "SingleControlledSX", MQT_NAMED_BUILDER(qc::singleControlledSx), + "SingleControlledSX", MQT_NAMED_BUILDER(::qc::singleControlledSx), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledSx), - MQT_NAMED_BUILDER(mlir::qir::singleControlledSx)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledSx)}, CompilerPipelineTestCase{ - "MultipleControlledSX", MQT_NAMED_BUILDER(qc::multipleControlledSx), - nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledSx), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledSx)}, - CompilerPipelineTestCase{"SXdg", MQT_NAMED_BUILDER(qc::sxdg), nullptr, + "MultipleControlledSX", + MQT_NAMED_BUILDER(::qc::multipleControlledSx), nullptr, + MQT_NAMED_BUILDER(mlir::qc::multipleControlledSx), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledSx)}, + CompilerPipelineTestCase{"SXdg", MQT_NAMED_BUILDER(::qc::sxdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::sxdg), - MQT_NAMED_BUILDER(mlir::qir::sxdg)}, + MQT_NAMED_BUILDER(mlir::qir::sxdg)}, CompilerPipelineTestCase{ - "SingleControlledSXdg", MQT_NAMED_BUILDER(qc::singleControlledSxdg), - nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledSxdg), - MQT_NAMED_BUILDER(mlir::qir::singleControlledSxdg)}, + "SingleControlledSXdg", + MQT_NAMED_BUILDER(::qc::singleControlledSxdg), nullptr, + MQT_NAMED_BUILDER(mlir::qc::singleControlledSxdg), + MQT_NAMED_BUILDER(mlir::qir::singleControlledSxdg)}, CompilerPipelineTestCase{ "MultipleControlledSXdg", - MQT_NAMED_BUILDER(qc::multipleControlledSxdg), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledSxdg), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledSxdg), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledSxdg)}, - CompilerPipelineTestCase{"RX", MQT_NAMED_BUILDER(qc::rx), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledSxdg)}, + CompilerPipelineTestCase{"RX", MQT_NAMED_BUILDER(::qc::rx), nullptr, MQT_NAMED_BUILDER(mlir::qc::rx), - MQT_NAMED_BUILDER(mlir::qir::rx)}, + MQT_NAMED_BUILDER(mlir::qir::rx)}, CompilerPipelineTestCase{ - "SingleControlledRX", MQT_NAMED_BUILDER(qc::singleControlledRx), + "SingleControlledRX", MQT_NAMED_BUILDER(::qc::singleControlledRx), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRx), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRx)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRx)}, CompilerPipelineTestCase{ - "MultipleControlledRX", MQT_NAMED_BUILDER(qc::multipleControlledRx), - nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRx), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRx)}, - CompilerPipelineTestCase{"RY", MQT_NAMED_BUILDER(qc::ry), nullptr, + "MultipleControlledRX", + MQT_NAMED_BUILDER(::qc::multipleControlledRx), nullptr, + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRx), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRx)}, + CompilerPipelineTestCase{"RY", MQT_NAMED_BUILDER(::qc::ry), nullptr, MQT_NAMED_BUILDER(mlir::qc::ry), - MQT_NAMED_BUILDER(mlir::qir::ry)}, + MQT_NAMED_BUILDER(mlir::qir::ry)}, CompilerPipelineTestCase{ - "SingleControlledRY", MQT_NAMED_BUILDER(qc::singleControlledRy), + "SingleControlledRY", MQT_NAMED_BUILDER(::qc::singleControlledRy), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRy), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRy)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRy)}, CompilerPipelineTestCase{ - "MultipleControlledRY", MQT_NAMED_BUILDER(qc::multipleControlledRy), - nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRy), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRy)}, - CompilerPipelineTestCase{"RZ", MQT_NAMED_BUILDER(qc::rz), nullptr, + "MultipleControlledRY", + MQT_NAMED_BUILDER(::qc::multipleControlledRy), nullptr, + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRy), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRy)}, + CompilerPipelineTestCase{"RZ", MQT_NAMED_BUILDER(::qc::rz), nullptr, MQT_NAMED_BUILDER(mlir::qc::rz), - MQT_NAMED_BUILDER(mlir::qir::rz)}, + MQT_NAMED_BUILDER(mlir::qir::rz)}, CompilerPipelineTestCase{ - "SingleControlledRZ", MQT_NAMED_BUILDER(qc::singleControlledRz), + "SingleControlledRZ", MQT_NAMED_BUILDER(::qc::singleControlledRz), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRz), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRz)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRz)}, CompilerPipelineTestCase{ - "MultipleControlledRZ", MQT_NAMED_BUILDER(qc::multipleControlledRz), - nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRz), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRz)}, - CompilerPipelineTestCase{"P", MQT_NAMED_BUILDER(qc::p), nullptr, + "MultipleControlledRZ", + MQT_NAMED_BUILDER(::qc::multipleControlledRz), nullptr, + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRz), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRz)}, + CompilerPipelineTestCase{"P", MQT_NAMED_BUILDER(::qc::p), nullptr, MQT_NAMED_BUILDER(mlir::qc::p), - MQT_NAMED_BUILDER(mlir::qir::p)}, + MQT_NAMED_BUILDER(mlir::qir::p)}, CompilerPipelineTestCase{ "SingleControlledP", - MQT_NAMED_BUILDER(qc::singleControlledP), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledP), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledP), - MQT_NAMED_BUILDER(mlir::qir::singleControlledP)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledP)}, CompilerPipelineTestCase{ - "MultipleControlledP", MQT_NAMED_BUILDER(qc::multipleControlledP), + "MultipleControlledP", MQT_NAMED_BUILDER(::qc::multipleControlledP), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledP), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledP)}, - CompilerPipelineTestCase{"R", MQT_NAMED_BUILDER(qc::r), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledP)}, + CompilerPipelineTestCase{"R", MQT_NAMED_BUILDER(::qc::r), nullptr, MQT_NAMED_BUILDER(mlir::qc::r), - MQT_NAMED_BUILDER(mlir::qir::r)}, + MQT_NAMED_BUILDER(mlir::qir::r)}, CompilerPipelineTestCase{ "SingleControlledR", - MQT_NAMED_BUILDER(qc::singleControlledR), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledR), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledR), - MQT_NAMED_BUILDER(mlir::qir::singleControlledR)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledR)}, CompilerPipelineTestCase{ - "MultipleControlledR", MQT_NAMED_BUILDER(qc::multipleControlledR), + "MultipleControlledR", MQT_NAMED_BUILDER(::qc::multipleControlledR), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledR), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledR)}, - CompilerPipelineTestCase{"U2", MQT_NAMED_BUILDER(qc::u2), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledR)}, + CompilerPipelineTestCase{"U2", MQT_NAMED_BUILDER(::qc::u2), nullptr, MQT_NAMED_BUILDER(mlir::qc::u2), - MQT_NAMED_BUILDER(mlir::qir::u2)}, + MQT_NAMED_BUILDER(mlir::qir::u2)}, CompilerPipelineTestCase{ - "SingleControlledU2", MQT_NAMED_BUILDER(qc::singleControlledU2), + "SingleControlledU2", MQT_NAMED_BUILDER(::qc::singleControlledU2), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledU2), - MQT_NAMED_BUILDER(mlir::qir::singleControlledU2)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledU2)}, CompilerPipelineTestCase{ - "MultipleControlledU2", MQT_NAMED_BUILDER(qc::multipleControlledU2), - nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledU2), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledU2)}, - CompilerPipelineTestCase{"U", MQT_NAMED_BUILDER(qc::u), nullptr, + "MultipleControlledU2", + MQT_NAMED_BUILDER(::qc::multipleControlledU2), nullptr, + MQT_NAMED_BUILDER(mlir::qc::multipleControlledU2), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledU2)}, + CompilerPipelineTestCase{"U", MQT_NAMED_BUILDER(::qc::u), nullptr, MQT_NAMED_BUILDER(mlir::qc::u), - MQT_NAMED_BUILDER(mlir::qir::u)}, + MQT_NAMED_BUILDER(mlir::qir::u)}, CompilerPipelineTestCase{ "SingleControlledU", - MQT_NAMED_BUILDER(qc::singleControlledU), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledU), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledU), - MQT_NAMED_BUILDER(mlir::qir::singleControlledU)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledU)}, CompilerPipelineTestCase{ - "MultipleControlledU", MQT_NAMED_BUILDER(qc::multipleControlledU), + "MultipleControlledU", MQT_NAMED_BUILDER(::qc::multipleControlledU), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledU), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledU)}, - CompilerPipelineTestCase{"SWAP", MQT_NAMED_BUILDER(qc::swap), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledU)}, + CompilerPipelineTestCase{"SWAP", MQT_NAMED_BUILDER(::qc::swap), nullptr, MQT_NAMED_BUILDER(mlir::qc::swap), - MQT_NAMED_BUILDER(mlir::qir::swap)}, + MQT_NAMED_BUILDER(mlir::qir::swap)}, CompilerPipelineTestCase{ - "SingleControlledSWAP", MQT_NAMED_BUILDER(qc::singleControlledSwap), - nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledSwap), - MQT_NAMED_BUILDER(mlir::qir::singleControlledSwap)}, + "SingleControlledSWAP", + MQT_NAMED_BUILDER(::qc::singleControlledSwap), nullptr, + MQT_NAMED_BUILDER(mlir::qc::singleControlledSwap), + MQT_NAMED_BUILDER(mlir::qir::singleControlledSwap)}, CompilerPipelineTestCase{ "MultipleControlledSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledSwap), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledSwap), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledSwap), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledSwap)}, - CompilerPipelineTestCase{"iSWAP", MQT_NAMED_BUILDER(qc::iswap), nullptr, - MQT_NAMED_BUILDER(mlir::qc::iswap), - MQT_NAMED_BUILDER(mlir::qir::iswap)}, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledSwap)}, + CompilerPipelineTestCase{"iSWAP", MQT_NAMED_BUILDER(::qc::iswap), + nullptr, MQT_NAMED_BUILDER(mlir::qc::iswap), + MQT_NAMED_BUILDER(mlir::qir::iswap)}, CompilerPipelineTestCase{ "SingleControllediSWAP", - MQT_NAMED_BUILDER(qc::singleControlledIswap), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledIswap), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledIswap), - MQT_NAMED_BUILDER(mlir::qir::singleControlledIswap)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledIswap)}, CompilerPipelineTestCase{ "MultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledIswap), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledIswap), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledIswap), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledIswap)}, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledIswap)}, CompilerPipelineTestCase{ - "InverseISWAP", MQT_NAMED_BUILDER(qc::inverseIswap), nullptr, + "InverseISWAP", MQT_NAMED_BUILDER(::qc::inverseIswap), nullptr, MQT_NAMED_BUILDER(mlir::qc::inverseIswap), nullptr, true, false}, CompilerPipelineTestCase{ "InverseMultiControlledISWAP", - MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap), nullptr, + MQT_NAMED_BUILDER(::qc::inverseMultipleControlledIswap), nullptr, MQT_NAMED_BUILDER(mlir::qc::inverseMultipleControlledIswap), nullptr, true, false}, - CompilerPipelineTestCase{"DCX", MQT_NAMED_BUILDER(qc::dcx), nullptr, + CompilerPipelineTestCase{"DCX", MQT_NAMED_BUILDER(::qc::dcx), nullptr, MQT_NAMED_BUILDER(mlir::qc::dcx), - MQT_NAMED_BUILDER(mlir::qir::dcx)}, + MQT_NAMED_BUILDER(mlir::qir::dcx)}, CompilerPipelineTestCase{ - "SingleControlledDCX", MQT_NAMED_BUILDER(qc::singleControlledDcx), + "SingleControlledDCX", MQT_NAMED_BUILDER(::qc::singleControlledDcx), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledDcx), - MQT_NAMED_BUILDER(mlir::qir::singleControlledDcx)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledDcx)}, CompilerPipelineTestCase{ "MultipleControlledDCX", - MQT_NAMED_BUILDER(qc::multipleControlledDcx), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledDcx), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledDcx), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledDcx)}, - CompilerPipelineTestCase{"ECR", MQT_NAMED_BUILDER(qc::ecr), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledDcx)}, + CompilerPipelineTestCase{"ECR", MQT_NAMED_BUILDER(::qc::ecr), nullptr, MQT_NAMED_BUILDER(mlir::qc::ecr), - MQT_NAMED_BUILDER(mlir::qir::ecr)}, + MQT_NAMED_BUILDER(mlir::qir::ecr)}, CompilerPipelineTestCase{ - "SingleControlledECR", MQT_NAMED_BUILDER(qc::singleControlledEcr), + "SingleControlledECR", MQT_NAMED_BUILDER(::qc::singleControlledEcr), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledEcr), - MQT_NAMED_BUILDER(mlir::qir::singleControlledEcr)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledEcr)}, CompilerPipelineTestCase{ "MultipleControlledECR", - MQT_NAMED_BUILDER(qc::multipleControlledEcr), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledEcr), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledEcr), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledEcr)}, - CompilerPipelineTestCase{"RXX", MQT_NAMED_BUILDER(qc::rxx), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledEcr)}, + CompilerPipelineTestCase{"RXX", MQT_NAMED_BUILDER(::qc::rxx), nullptr, MQT_NAMED_BUILDER(mlir::qc::rxx), - MQT_NAMED_BUILDER(mlir::qir::rxx)}, + MQT_NAMED_BUILDER(mlir::qir::rxx)}, CompilerPipelineTestCase{ - "SingleControlledRXX", MQT_NAMED_BUILDER(qc::singleControlledRxx), + "SingleControlledRXX", MQT_NAMED_BUILDER(::qc::singleControlledRxx), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRxx), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRxx)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRxx)}, CompilerPipelineTestCase{ "MultipleControlledRXX", - MQT_NAMED_BUILDER(qc::multipleControlledRxx), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledRxx), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRxx), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRxx)}, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRxx)}, CompilerPipelineTestCase{ - "TripleControlledRXX", MQT_NAMED_BUILDER(qc::tripleControlledRxx), + "TripleControlledRXX", MQT_NAMED_BUILDER(::qc::tripleControlledRxx), nullptr, MQT_NAMED_BUILDER(mlir::qc::tripleControlledRxx), - MQT_NAMED_BUILDER(mlir::qir::tripleControlledRxx)}, - CompilerPipelineTestCase{"RYY", MQT_NAMED_BUILDER(qc::ryy), nullptr, + MQT_NAMED_BUILDER(mlir::qir::tripleControlledRxx)}, + CompilerPipelineTestCase{"RYY", MQT_NAMED_BUILDER(::qc::ryy), nullptr, MQT_NAMED_BUILDER(mlir::qc::ryy), - MQT_NAMED_BUILDER(mlir::qir::ryy)}, + MQT_NAMED_BUILDER(mlir::qir::ryy)}, CompilerPipelineTestCase{ - "SingleControlledRYY", MQT_NAMED_BUILDER(qc::singleControlledRyy), + "SingleControlledRYY", MQT_NAMED_BUILDER(::qc::singleControlledRyy), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRyy), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRyy)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRyy)}, CompilerPipelineTestCase{ "MultipleControlledRYY", - MQT_NAMED_BUILDER(qc::multipleControlledRyy), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledRyy), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRyy), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRyy)}, - CompilerPipelineTestCase{"RZX", MQT_NAMED_BUILDER(qc::rzx), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRyy)}, + CompilerPipelineTestCase{"RZX", MQT_NAMED_BUILDER(::qc::rzx), nullptr, MQT_NAMED_BUILDER(mlir::qc::rzx), - MQT_NAMED_BUILDER(mlir::qir::rzx)}, + MQT_NAMED_BUILDER(mlir::qir::rzx)}, CompilerPipelineTestCase{ - "SingleControlledRZX", MQT_NAMED_BUILDER(qc::singleControlledRzx), + "SingleControlledRZX", MQT_NAMED_BUILDER(::qc::singleControlledRzx), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRzx), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRzx)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRzx)}, CompilerPipelineTestCase{ "MultipleControlledRZX", - MQT_NAMED_BUILDER(qc::multipleControlledRzx), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledRzx), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRzx), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRzx)}, - CompilerPipelineTestCase{"RZZ", MQT_NAMED_BUILDER(qc::rzz), nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRzx)}, + CompilerPipelineTestCase{"RZZ", MQT_NAMED_BUILDER(::qc::rzz), nullptr, MQT_NAMED_BUILDER(mlir::qc::rzz), - MQT_NAMED_BUILDER(mlir::qir::rzz)}, + MQT_NAMED_BUILDER(mlir::qir::rzz)}, CompilerPipelineTestCase{ - "SingleControlledRZZ", MQT_NAMED_BUILDER(qc::singleControlledRzz), + "SingleControlledRZZ", MQT_NAMED_BUILDER(::qc::singleControlledRzz), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledRzz), - MQT_NAMED_BUILDER(mlir::qir::singleControlledRzz)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledRzz)}, CompilerPipelineTestCase{ "MultipleControlledRZZ", - MQT_NAMED_BUILDER(qc::multipleControlledRzz), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledRzz), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledRzz), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledRzz)}, - CompilerPipelineTestCase{"XXPlusYY", MQT_NAMED_BUILDER(qc::xxPlusYY), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledRzz)}, + CompilerPipelineTestCase{"XXPlusYY", MQT_NAMED_BUILDER(::qc::xxPlusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::xxPlusYY), - MQT_NAMED_BUILDER(mlir::qir::xxPlusYY)}, + MQT_NAMED_BUILDER(mlir::qir::xxPlusYY)}, CompilerPipelineTestCase{ "SingleControlledXXPlusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledXxPlusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledXxPlusYY), - MQT_NAMED_BUILDER(mlir::qir::singleControlledXxPlusYY)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledXxPlusYY)}, CompilerPipelineTestCase{ "MultipleControlledXXPlusYY", - MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledXxPlusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxPlusYY)}, - CompilerPipelineTestCase{"XXMinusYY", MQT_NAMED_BUILDER(qc::xxMinusYY), - nullptr, + MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxPlusYY)}, + CompilerPipelineTestCase{"XXMinusYY", + MQT_NAMED_BUILDER(::qc::xxMinusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::xxMinusYY), - MQT_NAMED_BUILDER(mlir::qir::xxMinusYY)}, + MQT_NAMED_BUILDER(mlir::qir::xxMinusYY)}, CompilerPipelineTestCase{ "SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), nullptr, + MQT_NAMED_BUILDER(::qc::singleControlledXxMinusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledXxMinusYY), - MQT_NAMED_BUILDER(mlir::qir::singleControlledXxMinusYY)}, + MQT_NAMED_BUILDER(mlir::qir::singleControlledXxMinusYY)}, CompilerPipelineTestCase{ "MultipleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), nullptr, + MQT_NAMED_BUILDER(::qc::multipleControlledXxMinusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxMinusYY)}, - CompilerPipelineTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), + MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxMinusYY)}, + CompilerPipelineTestCase{"CtrlTwo", MQT_NAMED_BUILDER(::qc::ctrlTwo), nullptr, MQT_NAMED_BUILDER(mlir::qc::ctrlTwo), - MQT_NAMED_BUILDER(mlir::qir::ctrlTwo)})); + MQT_NAMED_BUILDER(mlir::qir::ctrlTwo)})); } // namespace mqt::test::compiler diff --git a/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp b/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp index 0f09b2b274..cd4856ebac 100644 --- a/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp +++ b/mlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -42,8 +43,8 @@ namespace { struct JeffRoundTripTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const JeffRoundTripTestCase& info); @@ -91,8 +92,7 @@ TEST_P(JeffRoundTripTest, ProgramEquivalence) { const auto name = " (" + nameStr + ")"; mqt::test::DeferredPrinter printer; - auto program = - qco::QCOProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -126,8 +126,7 @@ TEST_P(JeffRoundTripTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized Converted QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = - qco::QCOProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QCO IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index f25ce73b6f..3cab63449f 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -41,8 +42,8 @@ namespace { struct QCOToQCTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCOToQCTestCase& info); @@ -85,8 +86,7 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { const auto name = " (" + nameStr + ")"; mqt::test::DeferredPrinter printer; - auto program = - qco::QCOProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -103,8 +103,7 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized Converted QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = - qc::QCProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QC IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 9e66a0b655..0b78783834 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -41,8 +42,8 @@ namespace { struct QCToQCOTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCToQCOTestCase& info); @@ -85,7 +86,7 @@ TEST_P(QCToQCOTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = qc::QCProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -102,8 +103,7 @@ TEST_P(QCToQCOTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized Converted QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = - qco::QCOProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QCO IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -140,7 +140,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qco::staticQubitsWithInv)}, QCToQCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), - MQT_NAMED_BUILDER(qco::allocSinkPair)})); + MQT_NAMED_BUILDER(qco::emptyQCO)})); /// @} /// \name QCToQCO/Modifiers/CtrlOp.cpp @@ -248,7 +248,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(QCIDOpTest, QCToQCOTest, testing::Values(QCToQCOTestCase{ "Identity", MQT_NAMED_BUILDER(qc::identity), - MQT_NAMED_BUILDER(qco::emptyQCO)})); + MQT_NAMED_BUILDER(qco::alloc1QubitRegister)})); /// @} /// \name QCToQCO/Operations/StandardGates/IswapOp.cpp diff --git a/mlir/unittests/Conversion/QCToQIR/QCToQIRAdaptive/test_qc_to_qir_adaptive.cpp b/mlir/unittests/Conversion/QCToQIR/QCToQIRAdaptive/test_qc_to_qir_adaptive.cpp index 509f1ff321..e386a4e579 100644 --- a/mlir/unittests/Conversion/QCToQIR/QCToQIRAdaptive/test_qc_to_qir_adaptive.cpp +++ b/mlir/unittests/Conversion/QCToQIR/QCToQIRAdaptive/test_qc_to_qir_adaptive.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -42,8 +43,8 @@ namespace { struct QCToQIRAdaptiveTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCToQIRAdaptiveTestCase& info); @@ -87,7 +88,7 @@ TEST_P(QCToQIRAdaptiveTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = qc::QCProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -105,8 +106,8 @@ TEST_P(QCToQIRAdaptiveTest, ProgramEquivalence) { EXPECT_TRUE(verify(*program).succeeded()); auto reference = - qir::QIRProgramBuilder::build(context.get(), referenceBuilder.fn, - qir::QIRProgramBuilder::Profile::Adaptive); + mqt::test::buildMLIRProgram(context.get(), referenceBuilder, + qir::QIRProgramBuilder::Profile::Adaptive); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QIR IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -125,16 +126,16 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveBarrierOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"Barrier", MQT_NAMED_BUILDER(qc::barrier), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::alloc1QubitRegister<>)}, QCToQIRAdaptiveTestCase{"BarrierTwoQubits", MQT_NAMED_BUILDER(qc::barrierTwoQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, QCToQIRAdaptiveTestCase{"BarrierMultipleQubits", MQT_NAMED_BUILDER(qc::barrierMultipleQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::alloc3QubitRegister<>)}, QCToQIRAdaptiveTestCase{"SingleControlledBarrier", MQT_NAMED_BUILDER(qc::singleControlledBarrier), - MQT_NAMED_BUILDER(qir::emptyQIR)})); + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/DcxOp.cpp @@ -142,15 +143,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveDCXOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"DCX", MQT_NAMED_BUILDER(qc::dcx), - MQT_NAMED_BUILDER(qir::dcx)}, + MQT_NAMED_BUILDER(qir::dcx<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledDCX", MQT_NAMED_BUILDER(qc::singleControlledDcx), - MQT_NAMED_BUILDER(qir::singleControlledDcx)}, + MQT_NAMED_BUILDER(qir::singleControlledDcx<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledDCX", MQT_NAMED_BUILDER(qc::multipleControlledDcx), - MQT_NAMED_BUILDER(qir::multipleControlledDcx)})); + MQT_NAMED_BUILDER(qir::multipleControlledDcx<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/EcrOp.cpp @@ -158,15 +159,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveECROpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"ECR", MQT_NAMED_BUILDER(qc::ecr), - MQT_NAMED_BUILDER(qir::ecr)}, + MQT_NAMED_BUILDER(qir::ecr<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledECR", MQT_NAMED_BUILDER(qc::singleControlledEcr), - MQT_NAMED_BUILDER(qir::singleControlledEcr)}, + MQT_NAMED_BUILDER(qir::singleControlledEcr<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledECR", MQT_NAMED_BUILDER(qc::multipleControlledEcr), - MQT_NAMED_BUILDER(qir::multipleControlledEcr)})); + MQT_NAMED_BUILDER(qir::multipleControlledEcr<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/GphaseOp.cpp @@ -174,7 +175,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(QCToQIRAdaptiveGPhaseOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{ "GlobalPhase", MQT_NAMED_BUILDER(qc::globalPhase), - MQT_NAMED_BUILDER(qir::globalPhase)})); + MQT_NAMED_BUILDER(qir::globalPhase<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/HOp.cpp @@ -183,16 +184,16 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveHOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"H", MQT_NAMED_BUILDER(qc::h), - MQT_NAMED_BUILDER(qir::h)}, + MQT_NAMED_BUILDER(qir::h<>)}, QCToQIRAdaptiveTestCase{"SingleControlledH", MQT_NAMED_BUILDER(qc::singleControlledH), - MQT_NAMED_BUILDER(qir::singleControlledH)}, + MQT_NAMED_BUILDER(qir::singleControlledH<>)}, QCToQIRAdaptiveTestCase{"MultipleControlledH", MQT_NAMED_BUILDER(qc::multipleControlledH), - MQT_NAMED_BUILDER(qir::multipleControlledH)}, + MQT_NAMED_BUILDER(qir::multipleControlledH<>)}, QCToQIRAdaptiveTestCase{"HWithoutRegister", MQT_NAMED_BUILDER(qc::hWithoutRegister), - MQT_NAMED_BUILDER(qir::hWithoutRegister)})); + MQT_NAMED_BUILDER(qir::hWithoutRegister<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/IdOp.cpp @@ -201,30 +202,31 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveIDOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"Identity", MQT_NAMED_BUILDER(qc::identity), - MQT_NAMED_BUILDER(qir::identity)}, + MQT_NAMED_BUILDER(qir::identity<>)}, QCToQIRAdaptiveTestCase{"SingleControlledIdentity", MQT_NAMED_BUILDER(qc::singleControlledIdentity), - MQT_NAMED_BUILDER(qir::identity)}, + MQT_NAMED_BUILDER(qir::twoQubitsOneIdentity<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledIdentity", MQT_NAMED_BUILDER(qc::multipleControlledIdentity), - MQT_NAMED_BUILDER(qir::identity)})); + MQT_NAMED_BUILDER(qir::threeQubitsOneIdentity<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/IswapOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveiSWAPOpTest, QCToQIRAdaptiveTest, - testing::Values( - QCToQIRAdaptiveTestCase{"iSWAP", MQT_NAMED_BUILDER(qc::iswap), - MQT_NAMED_BUILDER(qir::iswap)}, - QCToQIRAdaptiveTestCase{"SingleControllediSWAP", - MQT_NAMED_BUILDER(qc::singleControlledIswap), - MQT_NAMED_BUILDER(qir::singleControlledIswap)}, - QCToQIRAdaptiveTestCase{ - "MultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledIswap), - MQT_NAMED_BUILDER(qir::multipleControlledIswap)})); + testing::Values(QCToQIRAdaptiveTestCase{"iSWAP", + MQT_NAMED_BUILDER(qc::iswap), + MQT_NAMED_BUILDER(qir::iswap<>)}, + QCToQIRAdaptiveTestCase{ + "SingleControllediSWAP", + MQT_NAMED_BUILDER(qc::singleControlledIswap), + MQT_NAMED_BUILDER(qir::singleControlledIswap<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControllediSWAP", + MQT_NAMED_BUILDER(qc::multipleControlledIswap), + MQT_NAMED_BUILDER(qir::multipleControlledIswap<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/POp.cpp @@ -233,13 +235,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptivePOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"P", MQT_NAMED_BUILDER(qc::p), - MQT_NAMED_BUILDER(qir::p)}, + MQT_NAMED_BUILDER(qir::p<>)}, QCToQIRAdaptiveTestCase{"SingleControlledP", MQT_NAMED_BUILDER(qc::singleControlledP), - MQT_NAMED_BUILDER(qir::singleControlledP)}, - QCToQIRAdaptiveTestCase{"MultipleControlledP", - MQT_NAMED_BUILDER(qc::multipleControlledP), - MQT_NAMED_BUILDER(qir::multipleControlledP)})); + MQT_NAMED_BUILDER(qir::singleControlledP<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledP", MQT_NAMED_BUILDER(qc::multipleControlledP), + MQT_NAMED_BUILDER(qir::multipleControlledP<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/ROp.cpp @@ -248,13 +250,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveROpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"R", MQT_NAMED_BUILDER(qc::r), - MQT_NAMED_BUILDER(qir::r)}, + MQT_NAMED_BUILDER(qir::r<>)}, QCToQIRAdaptiveTestCase{"SingleControlledR", MQT_NAMED_BUILDER(qc::singleControlledR), - MQT_NAMED_BUILDER(qir::singleControlledR)}, - QCToQIRAdaptiveTestCase{"MultipleControlledR", - MQT_NAMED_BUILDER(qc::multipleControlledR), - MQT_NAMED_BUILDER(qir::multipleControlledR)})); + MQT_NAMED_BUILDER(qir::singleControlledR<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledR", MQT_NAMED_BUILDER(qc::multipleControlledR), + MQT_NAMED_BUILDER(qir::multipleControlledR<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RxOp.cpp @@ -263,13 +265,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRXOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"RX", MQT_NAMED_BUILDER(qc::rx), - MQT_NAMED_BUILDER(qir::rx)}, + MQT_NAMED_BUILDER(qir::rx<>)}, QCToQIRAdaptiveTestCase{"SingleControlledRX", MQT_NAMED_BUILDER(qc::singleControlledRx), - MQT_NAMED_BUILDER(qir::singleControlledRx)}, - QCToQIRAdaptiveTestCase{"MultipleControlledRX", - MQT_NAMED_BUILDER(qc::multipleControlledRx), - MQT_NAMED_BUILDER(qir::multipleControlledRx)})); + MQT_NAMED_BUILDER(qir::singleControlledRx<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledRX", MQT_NAMED_BUILDER(qc::multipleControlledRx), + MQT_NAMED_BUILDER(qir::multipleControlledRx<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RxxOp.cpp @@ -277,15 +279,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRXXOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"RXX", MQT_NAMED_BUILDER(qc::rxx), - MQT_NAMED_BUILDER(qir::rxx)}, + MQT_NAMED_BUILDER(qir::rxx<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledRXX", MQT_NAMED_BUILDER(qc::singleControlledRxx), - MQT_NAMED_BUILDER(qir::singleControlledRxx)}, + MQT_NAMED_BUILDER(qir::singleControlledRxx<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledRXX", MQT_NAMED_BUILDER(qc::multipleControlledRxx), - MQT_NAMED_BUILDER(qir::multipleControlledRxx)})); + MQT_NAMED_BUILDER(qir::multipleControlledRxx<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RyOp.cpp @@ -294,13 +296,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRYOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"RY", MQT_NAMED_BUILDER(qc::ry), - MQT_NAMED_BUILDER(qir::ry)}, + MQT_NAMED_BUILDER(qir::ry<>)}, QCToQIRAdaptiveTestCase{"SingleControlledRY", MQT_NAMED_BUILDER(qc::singleControlledRy), - MQT_NAMED_BUILDER(qir::singleControlledRy)}, - QCToQIRAdaptiveTestCase{"MultipleControlledRY", - MQT_NAMED_BUILDER(qc::multipleControlledRy), - MQT_NAMED_BUILDER(qir::multipleControlledRy)})); + MQT_NAMED_BUILDER(qir::singleControlledRy<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledRY", MQT_NAMED_BUILDER(qc::multipleControlledRy), + MQT_NAMED_BUILDER(qir::multipleControlledRy<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RyyOp.cpp @@ -308,15 +310,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRYYOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"RYY", MQT_NAMED_BUILDER(qc::ryy), - MQT_NAMED_BUILDER(qir::ryy)}, + MQT_NAMED_BUILDER(qir::ryy<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledRYY", MQT_NAMED_BUILDER(qc::singleControlledRyy), - MQT_NAMED_BUILDER(qir::singleControlledRyy)}, + MQT_NAMED_BUILDER(qir::singleControlledRyy<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledRYY", MQT_NAMED_BUILDER(qc::multipleControlledRyy), - MQT_NAMED_BUILDER(qir::multipleControlledRyy)})); + MQT_NAMED_BUILDER(qir::multipleControlledRyy<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RzOp.cpp @@ -325,13 +327,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRZOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"RZ", MQT_NAMED_BUILDER(qc::rz), - MQT_NAMED_BUILDER(qir::rz)}, + MQT_NAMED_BUILDER(qir::rz<>)}, QCToQIRAdaptiveTestCase{"SingleControlledRZ", MQT_NAMED_BUILDER(qc::singleControlledRz), - MQT_NAMED_BUILDER(qir::singleControlledRz)}, - QCToQIRAdaptiveTestCase{"MultipleControlledRZ", - MQT_NAMED_BUILDER(qc::multipleControlledRz), - MQT_NAMED_BUILDER(qir::multipleControlledRz)})); + MQT_NAMED_BUILDER(qir::singleControlledRz<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledRZ", MQT_NAMED_BUILDER(qc::multipleControlledRz), + MQT_NAMED_BUILDER(qir::multipleControlledRz<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RzxOp.cpp @@ -339,15 +341,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRZXOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"RZX", MQT_NAMED_BUILDER(qc::rzx), - MQT_NAMED_BUILDER(qir::rzx)}, + MQT_NAMED_BUILDER(qir::rzx<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledRZX", MQT_NAMED_BUILDER(qc::singleControlledRzx), - MQT_NAMED_BUILDER(qir::singleControlledRzx)}, + MQT_NAMED_BUILDER(qir::singleControlledRzx<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledRZX", MQT_NAMED_BUILDER(qc::multipleControlledRzx), - MQT_NAMED_BUILDER(qir::multipleControlledRzx)})); + MQT_NAMED_BUILDER(qir::multipleControlledRzx<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/RzzOp.cpp @@ -355,15 +357,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveRZZOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"RZZ", MQT_NAMED_BUILDER(qc::rzz), - MQT_NAMED_BUILDER(qir::rzz)}, + MQT_NAMED_BUILDER(qir::rzz<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledRZZ", MQT_NAMED_BUILDER(qc::singleControlledRzz), - MQT_NAMED_BUILDER(qir::singleControlledRzz)}, + MQT_NAMED_BUILDER(qir::singleControlledRzz<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledRZZ", MQT_NAMED_BUILDER(qc::multipleControlledRzz), - MQT_NAMED_BUILDER(qir::multipleControlledRzz)})); + MQT_NAMED_BUILDER(qir::multipleControlledRzz<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/SOp.cpp @@ -372,13 +374,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveSOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"S", MQT_NAMED_BUILDER(qc::s), - MQT_NAMED_BUILDER(qir::s)}, + MQT_NAMED_BUILDER(qir::s<>)}, QCToQIRAdaptiveTestCase{"SingleControlledS", MQT_NAMED_BUILDER(qc::singleControlledS), - MQT_NAMED_BUILDER(qir::singleControlledS)}, - QCToQIRAdaptiveTestCase{"MultipleControlledS", - MQT_NAMED_BUILDER(qc::multipleControlledS), - MQT_NAMED_BUILDER(qir::multipleControlledS)})); + MQT_NAMED_BUILDER(qir::singleControlledS<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledS", MQT_NAMED_BUILDER(qc::multipleControlledS), + MQT_NAMED_BUILDER(qir::multipleControlledS<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/SdgOp.cpp @@ -386,15 +388,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveSdgOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"Sdg", MQT_NAMED_BUILDER(qc::sdg), - MQT_NAMED_BUILDER(qir::sdg)}, + MQT_NAMED_BUILDER(qir::sdg<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledSdg", MQT_NAMED_BUILDER(qc::singleControlledSdg), - MQT_NAMED_BUILDER(qir::singleControlledSdg)}, + MQT_NAMED_BUILDER(qir::singleControlledSdg<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledSdg", MQT_NAMED_BUILDER(qc::multipleControlledSdg), - MQT_NAMED_BUILDER(qir::multipleControlledSdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledSdg<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/SwapOp.cpp @@ -402,15 +404,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveSWAPOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"SWAP", MQT_NAMED_BUILDER(qc::swap), - MQT_NAMED_BUILDER(qir::swap)}, + MQT_NAMED_BUILDER(qir::swap<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledSWAP", MQT_NAMED_BUILDER(qc::singleControlledSwap), - MQT_NAMED_BUILDER(qir::singleControlledSwap)}, + MQT_NAMED_BUILDER(qir::singleControlledSwap<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledSWAP", MQT_NAMED_BUILDER(qc::multipleControlledSwap), - MQT_NAMED_BUILDER(qir::multipleControlledSwap)})); + MQT_NAMED_BUILDER(qir::multipleControlledSwap<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/SxOp.cpp @@ -419,13 +421,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveSXOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"SX", MQT_NAMED_BUILDER(qc::sx), - MQT_NAMED_BUILDER(qir::sx)}, + MQT_NAMED_BUILDER(qir::sx<>)}, QCToQIRAdaptiveTestCase{"SingleControlledSX", MQT_NAMED_BUILDER(qc::singleControlledSx), - MQT_NAMED_BUILDER(qir::singleControlledSx)}, - QCToQIRAdaptiveTestCase{"MultipleControlledSX", - MQT_NAMED_BUILDER(qc::multipleControlledSx), - MQT_NAMED_BUILDER(qir::multipleControlledSx)})); + MQT_NAMED_BUILDER(qir::singleControlledSx<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledSX", MQT_NAMED_BUILDER(qc::multipleControlledSx), + MQT_NAMED_BUILDER(qir::multipleControlledSx<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/SxdgOp.cpp @@ -433,15 +435,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveSXdgOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"SXdg", MQT_NAMED_BUILDER(qc::sxdg), - MQT_NAMED_BUILDER(qir::sxdg)}, + MQT_NAMED_BUILDER(qir::sxdg<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledSXdg", MQT_NAMED_BUILDER(qc::singleControlledSxdg), - MQT_NAMED_BUILDER(qir::singleControlledSxdg)}, + MQT_NAMED_BUILDER(qir::singleControlledSxdg<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledSXdg", MQT_NAMED_BUILDER(qc::multipleControlledSxdg), - MQT_NAMED_BUILDER(qir::multipleControlledSxdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledSxdg<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/TOp.cpp @@ -450,13 +452,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveTOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"T", MQT_NAMED_BUILDER(qc::t_), - MQT_NAMED_BUILDER(qir::t_)}, + MQT_NAMED_BUILDER(qir::t_<>)}, QCToQIRAdaptiveTestCase{"SingleControlledT", MQT_NAMED_BUILDER(qc::singleControlledT), - MQT_NAMED_BUILDER(qir::singleControlledT)}, - QCToQIRAdaptiveTestCase{"MultipleControlledT", - MQT_NAMED_BUILDER(qc::multipleControlledT), - MQT_NAMED_BUILDER(qir::multipleControlledT)})); + MQT_NAMED_BUILDER(qir::singleControlledT<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledT", MQT_NAMED_BUILDER(qc::multipleControlledT), + MQT_NAMED_BUILDER(qir::multipleControlledT<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/TdgOp.cpp @@ -464,15 +466,15 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveTdgOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"Tdg", MQT_NAMED_BUILDER(qc::tdg), - MQT_NAMED_BUILDER(qir::tdg)}, + MQT_NAMED_BUILDER(qir::tdg<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledTdg", MQT_NAMED_BUILDER(qc::singleControlledTdg), - MQT_NAMED_BUILDER(qir::singleControlledTdg)}, + MQT_NAMED_BUILDER(qir::singleControlledTdg<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledTdg", MQT_NAMED_BUILDER(qc::multipleControlledTdg), - MQT_NAMED_BUILDER(qir::multipleControlledTdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledTdg<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/U2Op.cpp @@ -481,13 +483,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveU2OpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"U2", MQT_NAMED_BUILDER(qc::u2), - MQT_NAMED_BUILDER(qir::u2)}, + MQT_NAMED_BUILDER(qir::u2<>)}, QCToQIRAdaptiveTestCase{"SingleControlledU2", MQT_NAMED_BUILDER(qc::singleControlledU2), - MQT_NAMED_BUILDER(qir::singleControlledU2)}, - QCToQIRAdaptiveTestCase{"MultipleControlledU2", - MQT_NAMED_BUILDER(qc::multipleControlledU2), - MQT_NAMED_BUILDER(qir::multipleControlledU2)})); + MQT_NAMED_BUILDER(qir::singleControlledU2<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledU2", MQT_NAMED_BUILDER(qc::multipleControlledU2), + MQT_NAMED_BUILDER(qir::multipleControlledU2<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/UOp.cpp @@ -496,13 +498,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveUOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"U", MQT_NAMED_BUILDER(qc::u), - MQT_NAMED_BUILDER(qir::u)}, + MQT_NAMED_BUILDER(qir::u<>)}, QCToQIRAdaptiveTestCase{"SingleControlledU", MQT_NAMED_BUILDER(qc::singleControlledU), - MQT_NAMED_BUILDER(qir::singleControlledU)}, - QCToQIRAdaptiveTestCase{"MultipleControlledU", - MQT_NAMED_BUILDER(qc::multipleControlledU), - MQT_NAMED_BUILDER(qir::multipleControlledU)})); + MQT_NAMED_BUILDER(qir::singleControlledU<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledU", MQT_NAMED_BUILDER(qc::multipleControlledU), + MQT_NAMED_BUILDER(qir::multipleControlledU<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/XOp.cpp @@ -511,30 +513,30 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveXOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"X", MQT_NAMED_BUILDER(qc::x), - MQT_NAMED_BUILDER(qir::x)}, + MQT_NAMED_BUILDER(qir::x<>)}, QCToQIRAdaptiveTestCase{"SingleControlledX", MQT_NAMED_BUILDER(qc::singleControlledX), - MQT_NAMED_BUILDER(qir::singleControlledX)}, - QCToQIRAdaptiveTestCase{"MultipleControlledX", - MQT_NAMED_BUILDER(qc::multipleControlledX), - MQT_NAMED_BUILDER(qir::multipleControlledX)})); + MQT_NAMED_BUILDER(qir::singleControlledX<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledX", MQT_NAMED_BUILDER(qc::multipleControlledX), + MQT_NAMED_BUILDER(qir::multipleControlledX<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/XxMinusYyOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveXXMinusYYOpTest, QCToQIRAdaptiveTest, - testing::Values(QCToQIRAdaptiveTestCase{"XXMinusYY", - MQT_NAMED_BUILDER(qc::xxMinusYY), - MQT_NAMED_BUILDER(qir::xxMinusYY)}, - QCToQIRAdaptiveTestCase{ - "SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), - MQT_NAMED_BUILDER(qir::singleControlledXxMinusYY)}, - QCToQIRAdaptiveTestCase{ - "MultipleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(qir::multipleControlledXxMinusYY)})); + testing::Values( + QCToQIRAdaptiveTestCase{"XXMinusYY", MQT_NAMED_BUILDER(qc::xxMinusYY), + MQT_NAMED_BUILDER(qir::xxMinusYY<>)}, + QCToQIRAdaptiveTestCase{ + "SingleControlledXXMinusYY", + MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), + MQT_NAMED_BUILDER(qir::singleControlledXxMinusYY<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledXXMinusYY", + MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), + MQT_NAMED_BUILDER(qir::multipleControlledXxMinusYY<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/XxPlusYyOp.cpp @@ -543,15 +545,15 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveXXPlusYYOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{"XXPlusYY", MQT_NAMED_BUILDER(qc::xxPlusYY), - MQT_NAMED_BUILDER(qir::xxPlusYY)}, + MQT_NAMED_BUILDER(qir::xxPlusYY<>)}, QCToQIRAdaptiveTestCase{ "SingleControlledXXPlusYY", MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY), - MQT_NAMED_BUILDER(qir::singleControlledXxPlusYY)}, + MQT_NAMED_BUILDER(qir::singleControlledXxPlusYY<>)}, QCToQIRAdaptiveTestCase{ "MultipleControlledXXPlusYY", MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(qir::multipleControlledXxPlusYY)})); + MQT_NAMED_BUILDER(qir::multipleControlledXxPlusYY<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/YOp.cpp @@ -560,13 +562,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveYOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"Y", MQT_NAMED_BUILDER(qc::y), - MQT_NAMED_BUILDER(qir::y)}, + MQT_NAMED_BUILDER(qir::y<>)}, QCToQIRAdaptiveTestCase{"SingleControlledY", MQT_NAMED_BUILDER(qc::singleControlledY), - MQT_NAMED_BUILDER(qir::singleControlledY)}, - QCToQIRAdaptiveTestCase{"MultipleControlledY", - MQT_NAMED_BUILDER(qc::multipleControlledY), - MQT_NAMED_BUILDER(qir::multipleControlledY)})); + MQT_NAMED_BUILDER(qir::singleControlledY<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledY", MQT_NAMED_BUILDER(qc::multipleControlledY), + MQT_NAMED_BUILDER(qir::multipleControlledY<>)})); /// @} /// \name QCToQIRAdaptive/Operations/StandardGates/ZOp.cpp @@ -575,13 +577,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveZOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"Z", MQT_NAMED_BUILDER(qc::z), - MQT_NAMED_BUILDER(qir::z)}, + MQT_NAMED_BUILDER(qir::z<>)}, QCToQIRAdaptiveTestCase{"SingleControlledZ", MQT_NAMED_BUILDER(qc::singleControlledZ), - MQT_NAMED_BUILDER(qir::singleControlledZ)}, - QCToQIRAdaptiveTestCase{"MultipleControlledZ", - MQT_NAMED_BUILDER(qc::multipleControlledZ), - MQT_NAMED_BUILDER(qir::multipleControlledZ)})); + MQT_NAMED_BUILDER(qir::singleControlledZ<>)}, + QCToQIRAdaptiveTestCase{ + "MultipleControlledZ", MQT_NAMED_BUILDER(qc::multipleControlledZ), + MQT_NAMED_BUILDER(qir::multipleControlledZ<>)})); /// @} /// \name QCToQIRAdaptive/Operations/MeasureOp.cpp @@ -592,23 +594,24 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveTestCase{ "SingleMeasurementToSingleBit", MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(qir::singleMeasurementToSingleBit)}, + MQT_NAMED_BUILDER(qir::singleMeasurementToSingleBit<>)}, QCToQIRAdaptiveTestCase{ "RepeatedMeasurementToSameBit", MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(qir::repeatedMeasurementToSameBit)}, + MQT_NAMED_BUILDER(qir::repeatedMeasurementToSameBit<>)}, QCToQIRAdaptiveTestCase{ "RepeatedMeasurementToDifferentBits", MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(qir::repeatedMeasurementToDifferentBits)}, + MQT_NAMED_BUILDER(qir::repeatedMeasurementToDifferentBits<>)}, QCToQIRAdaptiveTestCase{ "MultipleClassicalRegistersAndMeasurements", MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements), - MQT_NAMED_BUILDER(qir::multipleClassicalRegistersAndMeasurements)}, + MQT_NAMED_BUILDER( + qir::multipleClassicalRegistersAndMeasurements)}, QCToQIRAdaptiveTestCase{ "MeasurementWithoutRegisters", MQT_NAMED_BUILDER(qc::measurementWithoutRegisters), - MQT_NAMED_BUILDER(qir::measurementWithoutRegisters)})); + MQT_NAMED_BUILDER(qir::measurementWithoutRegisters<>)})); /// @} /// \name QCToQIRAdaptive/Operations/ResetOp.cpp @@ -618,26 +621,27 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QCToQIRAdaptiveTestCase{"ResetQubitWithoutOp", MQT_NAMED_BUILDER(qc::resetQubitWithoutOp), - MQT_NAMED_BUILDER(qir::resetQubitWithoutOp)}, + MQT_NAMED_BUILDER(qir::resetQubitWithoutOp<>)}, QCToQIRAdaptiveTestCase{ "ResetMultipleQubitsWithoutOp", MQT_NAMED_BUILDER(qc::resetMultipleQubitsWithoutOp), - MQT_NAMED_BUILDER(qir::resetMultipleQubitsWithoutOp)}, - QCToQIRAdaptiveTestCase{"RepeatedResetWithoutOp", - MQT_NAMED_BUILDER(qc::repeatedResetWithoutOp), - MQT_NAMED_BUILDER(qir::repeatedResetWithoutOp)}, + MQT_NAMED_BUILDER(qir::resetMultipleQubitsWithoutOp<>)}, + QCToQIRAdaptiveTestCase{ + "RepeatedResetWithoutOp", + MQT_NAMED_BUILDER(qc::repeatedResetWithoutOp), + MQT_NAMED_BUILDER(qir::repeatedResetWithoutOp<>)}, QCToQIRAdaptiveTestCase{ "ResetQubitAfterSingleOp", MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(qir::resetQubitAfterSingleOp)}, + MQT_NAMED_BUILDER(qir::resetQubitAfterSingleOp<>)}, QCToQIRAdaptiveTestCase{ "ResetMultipleQubitsAfterSingleOp", MQT_NAMED_BUILDER(qc::resetMultipleQubitsAfterSingleOp), - MQT_NAMED_BUILDER(qir::resetMultipleQubitsAfterSingleOp)}, + MQT_NAMED_BUILDER(qir::resetMultipleQubitsAfterSingleOp<>)}, QCToQIRAdaptiveTestCase{ "RepeatedResetAfterSingleOp", MQT_NAMED_BUILDER(qc::repeatedResetAfterSingleOp), - MQT_NAMED_BUILDER(qir::repeatedResetAfterSingleOp)})); + MQT_NAMED_BUILDER(qir::repeatedResetAfterSingleOp<>)})); /// @} /// \name QCToQIRAdaptive/QubitManagement/QubitManagement.cpp @@ -646,24 +650,24 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRAdaptiveQubitManagementTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubit<>)}, QCToQIRAdaptiveTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(qc::allocQubitRegister), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, QCToQIRAdaptiveTestCase{ "AllocMultipleQubitRegisters", MQT_NAMED_BUILDER(qc::allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocMultipleQubitRegisters<>)}, QCToQIRAdaptiveTestCase{ "AllocMultipleQubitRegistersWithOps", MQT_NAMED_BUILDER(qc::allocMultipleQubitRegistersWithOps), - MQT_NAMED_BUILDER(qir::allocMultipleQubitRegistersWithOps)}, + MQT_NAMED_BUILDER(qir::allocMultipleQubitRegistersWithOps<>)}, QCToQIRAdaptiveTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(qc::allocLargeRegister), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, QCToQIRAdaptiveTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::staticQubits)}, QCToQIRAdaptiveTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(qc::staticQubitsWithOps), MQT_NAMED_BUILDER(qir::staticQubitsWithOps)}, @@ -683,7 +687,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qir::staticQubitsWithInv)}, QCToQIRAdaptiveTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), - MQT_NAMED_BUILDER(qir::emptyQIR)})); + MQT_NAMED_BUILDER(qir::emptyQIR<>)})); /// @} /// \name QCToQIRAdaptive/Operations/IfOp.cpp @@ -692,15 +696,15 @@ INSTANTIATE_TEST_SUITE_P( SCFIfOpTest, QCToQIRAdaptiveTest, testing::Values( QCToQIRAdaptiveTestCase{"SimpleIfOp", MQT_NAMED_BUILDER(qc::simpleIf), - MQT_NAMED_BUILDER(qir::simpleIf)}, + MQT_NAMED_BUILDER(qir::simpleIf<>)}, QCToQIRAdaptiveTestCase{"IfTwoQubits", MQT_NAMED_BUILDER(qc::ifTwoQubits), - MQT_NAMED_BUILDER(qir::ifTwoQubits)}, + MQT_NAMED_BUILDER(qir::ifTwoQubits<>)}, QCToQIRAdaptiveTestCase{"IfElse", MQT_NAMED_BUILDER(qc::ifElse), - MQT_NAMED_BUILDER(qir::ifElse)}, + MQT_NAMED_BUILDER(qir::ifElse<>)}, QCToQIRAdaptiveTestCase{"NestedIfOpForLoop", MQT_NAMED_BUILDER(qc::nestedIfOpForLoop), - MQT_NAMED_BUILDER(qir::nestedIfOpForLoop)})); + MQT_NAMED_BUILDER(qir::nestedIfOpForLoop<>)})); /// @} /// \name QCToQIRAdaptive/Operations/WhileOp.cpp @@ -710,10 +714,10 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QCToQIRAdaptiveTestCase{"SimpleWhile", MQT_NAMED_BUILDER(qc::simpleWhileReset), - MQT_NAMED_BUILDER(qir::simpleWhileReset)}, + MQT_NAMED_BUILDER(qir::simpleWhileReset<>)}, QCToQIRAdaptiveTestCase{"SimpleDoWhile", MQT_NAMED_BUILDER(qc::simpleDoWhileReset), - MQT_NAMED_BUILDER(qir::simpleDoWhileReset)})); + MQT_NAMED_BUILDER(qir::simpleDoWhileReset<>)})); /// \name QCToQIRAdaptive/Operations/ForOp.cpp /// @{ @@ -722,26 +726,28 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QCToQIRAdaptiveTestCase{"SimpleForLoop", MQT_NAMED_BUILDER(qc::simpleForLoop), - MQT_NAMED_BUILDER(qir::simpleForLoop)}, + MQT_NAMED_BUILDER(qir::simpleForLoop<>)}, QCToQIRAdaptiveTestCase{"NestedForLoopIfOp", MQT_NAMED_BUILDER(qc::nestedForLoopIfOp), - MQT_NAMED_BUILDER(qir::nestedForLoopIfOp)}, + MQT_NAMED_BUILDER(qir::nestedForLoopIfOp<>)}, QCToQIRAdaptiveTestCase{"NestedForLoopWhileOp", MQT_NAMED_BUILDER(qc::nestedForLoopWhileOp), - MQT_NAMED_BUILDER(qir::nestedForLoopWhileOp)}, + MQT_NAMED_BUILDER(qir::nestedForLoopWhileOp<>)}, QCToQIRAdaptiveTestCase{ "nestedForLoopCtrlOpWithSeparateQubit", MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithSeparateQubit), - MQT_NAMED_BUILDER(qir::nestedForLoopCtrlOpWithSeparateQubit)}, + MQT_NAMED_BUILDER( + qir::nestedForLoopCtrlOpWithSeparateQubit)}, QCToQIRAdaptiveTestCase{ "nestedForLoopCtrlOpWithExtractedQubit", MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit), - MQT_NAMED_BUILDER(qir::nestedForLoopCtrlOpWithExtractedQubit)})); + MQT_NAMED_BUILDER( + qir::nestedForLoopCtrlOpWithExtractedQubit)})); /// \name QCToQIRAdaptive/Modifiers/CtrlOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P(QCToQIRCtrlOpTest, QCToQIRAdaptiveTest, testing::Values(QCToQIRAdaptiveTestCase{ "NestedCtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), - MQT_NAMED_BUILDER(qir::ctrlTwo)})); + MQT_NAMED_BUILDER(qir::ctrlTwo<>)})); /// @} diff --git a/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp b/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp index b637247132..4319c3b4d3 100644 --- a/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp +++ b/mlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -42,8 +43,8 @@ namespace { struct QCToQIRBaseTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCToQIRBaseTestCase& info); @@ -85,7 +86,7 @@ TEST_P(QCToQIRBaseTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = qc::QCProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -102,9 +103,8 @@ TEST_P(QCToQIRBaseTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized Converted QIR IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = - qir::QIRProgramBuilder::build(context.get(), referenceBuilder.fn, - qir::QIRProgramBuilder::Profile::Base); + auto reference = mqt::test::buildMLIRProgram( + context.get(), referenceBuilder, qir::QIRProgramBuilder::Profile::Base); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QIR IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -123,16 +123,16 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseBarrierOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Barrier", MQT_NAMED_BUILDER(qc::barrier), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::alloc1QubitRegister<>)}, QCToQIRBaseTestCase{"BarrierTwoQubits", MQT_NAMED_BUILDER(qc::barrierTwoQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, QCToQIRBaseTestCase{"BarrierMultipleQubits", MQT_NAMED_BUILDER(qc::barrierMultipleQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::alloc3QubitRegister<>)}, QCToQIRBaseTestCase{"SingleControlledBarrier", MQT_NAMED_BUILDER(qc::singleControlledBarrier), - MQT_NAMED_BUILDER(qir::emptyQIR)})); + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/DcxOp.cpp @@ -141,13 +141,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseDCXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"DCX", MQT_NAMED_BUILDER(qc::dcx), - MQT_NAMED_BUILDER(qir::dcx)}, + MQT_NAMED_BUILDER(qir::dcx<>)}, QCToQIRBaseTestCase{"SingleControlledDCX", MQT_NAMED_BUILDER(qc::singleControlledDcx), - MQT_NAMED_BUILDER(qir::singleControlledDcx)}, + MQT_NAMED_BUILDER(qir::singleControlledDcx<>)}, QCToQIRBaseTestCase{"MultipleControlledDCX", MQT_NAMED_BUILDER(qc::multipleControlledDcx), - MQT_NAMED_BUILDER(qir::multipleControlledDcx)})); + MQT_NAMED_BUILDER(qir::multipleControlledDcx<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/EcrOp.cpp @@ -156,13 +156,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseECROpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"ECR", MQT_NAMED_BUILDER(qc::ecr), - MQT_NAMED_BUILDER(qir::ecr)}, + MQT_NAMED_BUILDER(qir::ecr<>)}, QCToQIRBaseTestCase{"SingleControlledECR", MQT_NAMED_BUILDER(qc::singleControlledEcr), - MQT_NAMED_BUILDER(qir::singleControlledEcr)}, + MQT_NAMED_BUILDER(qir::singleControlledEcr<>)}, QCToQIRBaseTestCase{"MultipleControlledECR", MQT_NAMED_BUILDER(qc::multipleControlledEcr), - MQT_NAMED_BUILDER(qir::multipleControlledEcr)})); + MQT_NAMED_BUILDER(qir::multipleControlledEcr<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/GphaseOp.cpp @@ -170,7 +170,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(QCToQIRBaseGPhaseOpTest, QCToQIRBaseTest, testing::Values(QCToQIRBaseTestCase{ "GlobalPhase", MQT_NAMED_BUILDER(qc::globalPhase), - MQT_NAMED_BUILDER(qir::globalPhase)})); + MQT_NAMED_BUILDER(qir::globalPhase<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/HOp.cpp @@ -179,16 +179,16 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseHOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"H", MQT_NAMED_BUILDER(qc::h), - MQT_NAMED_BUILDER(qir::h)}, + MQT_NAMED_BUILDER(qir::h<>)}, QCToQIRBaseTestCase{"SingleControlledH", MQT_NAMED_BUILDER(qc::singleControlledH), - MQT_NAMED_BUILDER(qir::singleControlledH)}, + MQT_NAMED_BUILDER(qir::singleControlledH<>)}, QCToQIRBaseTestCase{"MultipleControlledH", MQT_NAMED_BUILDER(qc::multipleControlledH), - MQT_NAMED_BUILDER(qir::multipleControlledH)}, + MQT_NAMED_BUILDER(qir::multipleControlledH<>)}, QCToQIRBaseTestCase{"HWithoutRegister", MQT_NAMED_BUILDER(qc::hWithoutRegister), - MQT_NAMED_BUILDER(qir::hWithoutRegister)})); + MQT_NAMED_BUILDER(qir::hWithoutRegister<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/IdOp.cpp @@ -197,28 +197,29 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseIDOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Identity", MQT_NAMED_BUILDER(qc::identity), - MQT_NAMED_BUILDER(qir::identity)}, + MQT_NAMED_BUILDER(qir::identity<>)}, QCToQIRBaseTestCase{"SingleControlledIdentity", MQT_NAMED_BUILDER(qc::singleControlledIdentity), - MQT_NAMED_BUILDER(qir::identity)}, + MQT_NAMED_BUILDER(qir::twoQubitsOneIdentity<>)}, QCToQIRBaseTestCase{"MultipleControlledIdentity", MQT_NAMED_BUILDER(qc::multipleControlledIdentity), - MQT_NAMED_BUILDER(qir::identity)})); + MQT_NAMED_BUILDER(qir::threeQubitsOneIdentity<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/IswapOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseiSWAPOpTest, QCToQIRBaseTest, - testing::Values( - QCToQIRBaseTestCase{"iSWAP", MQT_NAMED_BUILDER(qc::iswap), - MQT_NAMED_BUILDER(qir::iswap)}, - QCToQIRBaseTestCase{"SingleControllediSWAP", - MQT_NAMED_BUILDER(qc::singleControlledIswap), - MQT_NAMED_BUILDER(qir::singleControlledIswap)}, - QCToQIRBaseTestCase{"MultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledIswap), - MQT_NAMED_BUILDER(qir::multipleControlledIswap)})); + testing::Values(QCToQIRBaseTestCase{"iSWAP", MQT_NAMED_BUILDER(qc::iswap), + MQT_NAMED_BUILDER(qir::iswap<>)}, + QCToQIRBaseTestCase{ + "SingleControllediSWAP", + MQT_NAMED_BUILDER(qc::singleControlledIswap), + MQT_NAMED_BUILDER(qir::singleControlledIswap<>)}, + QCToQIRBaseTestCase{ + "MultipleControllediSWAP", + MQT_NAMED_BUILDER(qc::multipleControlledIswap), + MQT_NAMED_BUILDER(qir::multipleControlledIswap<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/POp.cpp @@ -227,13 +228,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBasePOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"P", MQT_NAMED_BUILDER(qc::p), - MQT_NAMED_BUILDER(qir::p)}, + MQT_NAMED_BUILDER(qir::p<>)}, QCToQIRBaseTestCase{"SingleControlledP", MQT_NAMED_BUILDER(qc::singleControlledP), - MQT_NAMED_BUILDER(qir::singleControlledP)}, + MQT_NAMED_BUILDER(qir::singleControlledP<>)}, QCToQIRBaseTestCase{"MultipleControlledP", MQT_NAMED_BUILDER(qc::multipleControlledP), - MQT_NAMED_BUILDER(qir::multipleControlledP)})); + MQT_NAMED_BUILDER(qir::multipleControlledP<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/ROp.cpp @@ -242,13 +243,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseROpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"R", MQT_NAMED_BUILDER(qc::r), - MQT_NAMED_BUILDER(qir::r)}, + MQT_NAMED_BUILDER(qir::r<>)}, QCToQIRBaseTestCase{"SingleControlledR", MQT_NAMED_BUILDER(qc::singleControlledR), - MQT_NAMED_BUILDER(qir::singleControlledR)}, + MQT_NAMED_BUILDER(qir::singleControlledR<>)}, QCToQIRBaseTestCase{"MultipleControlledR", MQT_NAMED_BUILDER(qc::multipleControlledR), - MQT_NAMED_BUILDER(qir::multipleControlledR)})); + MQT_NAMED_BUILDER(qir::multipleControlledR<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RxOp.cpp @@ -257,13 +258,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RX", MQT_NAMED_BUILDER(qc::rx), - MQT_NAMED_BUILDER(qir::rx)}, + MQT_NAMED_BUILDER(qir::rx<>)}, QCToQIRBaseTestCase{"SingleControlledRX", MQT_NAMED_BUILDER(qc::singleControlledRx), - MQT_NAMED_BUILDER(qir::singleControlledRx)}, + MQT_NAMED_BUILDER(qir::singleControlledRx<>)}, QCToQIRBaseTestCase{"MultipleControlledRX", MQT_NAMED_BUILDER(qc::multipleControlledRx), - MQT_NAMED_BUILDER(qir::multipleControlledRx)})); + MQT_NAMED_BUILDER(qir::multipleControlledRx<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RxxOp.cpp @@ -272,13 +273,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRXXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RXX", MQT_NAMED_BUILDER(qc::rxx), - MQT_NAMED_BUILDER(qir::rxx)}, + MQT_NAMED_BUILDER(qir::rxx<>)}, QCToQIRBaseTestCase{"SingleControlledRXX", MQT_NAMED_BUILDER(qc::singleControlledRxx), - MQT_NAMED_BUILDER(qir::singleControlledRxx)}, + MQT_NAMED_BUILDER(qir::singleControlledRxx<>)}, QCToQIRBaseTestCase{"MultipleControlledRXX", MQT_NAMED_BUILDER(qc::multipleControlledRxx), - MQT_NAMED_BUILDER(qir::multipleControlledRxx)})); + MQT_NAMED_BUILDER(qir::multipleControlledRxx<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RyOp.cpp @@ -287,13 +288,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRYOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RY", MQT_NAMED_BUILDER(qc::ry), - MQT_NAMED_BUILDER(qir::ry)}, + MQT_NAMED_BUILDER(qir::ry<>)}, QCToQIRBaseTestCase{"SingleControlledRY", MQT_NAMED_BUILDER(qc::singleControlledRy), - MQT_NAMED_BUILDER(qir::singleControlledRy)}, + MQT_NAMED_BUILDER(qir::singleControlledRy<>)}, QCToQIRBaseTestCase{"MultipleControlledRY", MQT_NAMED_BUILDER(qc::multipleControlledRy), - MQT_NAMED_BUILDER(qir::multipleControlledRy)})); + MQT_NAMED_BUILDER(qir::multipleControlledRy<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RyyOp.cpp @@ -302,13 +303,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRYYOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RYY", MQT_NAMED_BUILDER(qc::ryy), - MQT_NAMED_BUILDER(qir::ryy)}, + MQT_NAMED_BUILDER(qir::ryy<>)}, QCToQIRBaseTestCase{"SingleControlledRYY", MQT_NAMED_BUILDER(qc::singleControlledRyy), - MQT_NAMED_BUILDER(qir::singleControlledRyy)}, + MQT_NAMED_BUILDER(qir::singleControlledRyy<>)}, QCToQIRBaseTestCase{"MultipleControlledRYY", MQT_NAMED_BUILDER(qc::multipleControlledRyy), - MQT_NAMED_BUILDER(qir::multipleControlledRyy)})); + MQT_NAMED_BUILDER(qir::multipleControlledRyy<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RzOp.cpp @@ -317,13 +318,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRZOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RZ", MQT_NAMED_BUILDER(qc::rz), - MQT_NAMED_BUILDER(qir::rz)}, + MQT_NAMED_BUILDER(qir::rz<>)}, QCToQIRBaseTestCase{"SingleControlledRZ", MQT_NAMED_BUILDER(qc::singleControlledRz), - MQT_NAMED_BUILDER(qir::singleControlledRz)}, + MQT_NAMED_BUILDER(qir::singleControlledRz<>)}, QCToQIRBaseTestCase{"MultipleControlledRZ", MQT_NAMED_BUILDER(qc::multipleControlledRz), - MQT_NAMED_BUILDER(qir::multipleControlledRz)})); + MQT_NAMED_BUILDER(qir::multipleControlledRz<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RzxOp.cpp @@ -332,13 +333,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRZXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RZX", MQT_NAMED_BUILDER(qc::rzx), - MQT_NAMED_BUILDER(qir::rzx)}, + MQT_NAMED_BUILDER(qir::rzx<>)}, QCToQIRBaseTestCase{"SingleControlledRZX", MQT_NAMED_BUILDER(qc::singleControlledRzx), - MQT_NAMED_BUILDER(qir::singleControlledRzx)}, + MQT_NAMED_BUILDER(qir::singleControlledRzx<>)}, QCToQIRBaseTestCase{"MultipleControlledRZX", MQT_NAMED_BUILDER(qc::multipleControlledRzx), - MQT_NAMED_BUILDER(qir::multipleControlledRzx)})); + MQT_NAMED_BUILDER(qir::multipleControlledRzx<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/RzzOp.cpp @@ -347,13 +348,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseRZZOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"RZZ", MQT_NAMED_BUILDER(qc::rzz), - MQT_NAMED_BUILDER(qir::rzz)}, + MQT_NAMED_BUILDER(qir::rzz<>)}, QCToQIRBaseTestCase{"SingleControlledRZZ", MQT_NAMED_BUILDER(qc::singleControlledRzz), - MQT_NAMED_BUILDER(qir::singleControlledRzz)}, + MQT_NAMED_BUILDER(qir::singleControlledRzz<>)}, QCToQIRBaseTestCase{"MultipleControlledRZZ", MQT_NAMED_BUILDER(qc::multipleControlledRzz), - MQT_NAMED_BUILDER(qir::multipleControlledRzz)})); + MQT_NAMED_BUILDER(qir::multipleControlledRzz<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/SOp.cpp @@ -362,13 +363,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseSOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"S", MQT_NAMED_BUILDER(qc::s), - MQT_NAMED_BUILDER(qir::s)}, + MQT_NAMED_BUILDER(qir::s<>)}, QCToQIRBaseTestCase{"SingleControlledS", MQT_NAMED_BUILDER(qc::singleControlledS), - MQT_NAMED_BUILDER(qir::singleControlledS)}, + MQT_NAMED_BUILDER(qir::singleControlledS<>)}, QCToQIRBaseTestCase{"MultipleControlledS", MQT_NAMED_BUILDER(qc::multipleControlledS), - MQT_NAMED_BUILDER(qir::multipleControlledS)})); + MQT_NAMED_BUILDER(qir::multipleControlledS<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/SdgOp.cpp @@ -377,13 +378,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseSdgOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Sdg", MQT_NAMED_BUILDER(qc::sdg), - MQT_NAMED_BUILDER(qir::sdg)}, + MQT_NAMED_BUILDER(qir::sdg<>)}, QCToQIRBaseTestCase{"SingleControlledSdg", MQT_NAMED_BUILDER(qc::singleControlledSdg), - MQT_NAMED_BUILDER(qir::singleControlledSdg)}, + MQT_NAMED_BUILDER(qir::singleControlledSdg<>)}, QCToQIRBaseTestCase{"MultipleControlledSdg", MQT_NAMED_BUILDER(qc::multipleControlledSdg), - MQT_NAMED_BUILDER(qir::multipleControlledSdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledSdg<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/SwapOp.cpp @@ -392,13 +393,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseSWAPOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"SWAP", MQT_NAMED_BUILDER(qc::swap), - MQT_NAMED_BUILDER(qir::swap)}, + MQT_NAMED_BUILDER(qir::swap<>)}, QCToQIRBaseTestCase{"SingleControlledSWAP", MQT_NAMED_BUILDER(qc::singleControlledSwap), - MQT_NAMED_BUILDER(qir::singleControlledSwap)}, + MQT_NAMED_BUILDER(qir::singleControlledSwap<>)}, QCToQIRBaseTestCase{"MultipleControlledSWAP", MQT_NAMED_BUILDER(qc::multipleControlledSwap), - MQT_NAMED_BUILDER(qir::multipleControlledSwap)})); + MQT_NAMED_BUILDER(qir::multipleControlledSwap<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/SxOp.cpp @@ -407,13 +408,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseSXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"SX", MQT_NAMED_BUILDER(qc::sx), - MQT_NAMED_BUILDER(qir::sx)}, + MQT_NAMED_BUILDER(qir::sx<>)}, QCToQIRBaseTestCase{"SingleControlledSX", MQT_NAMED_BUILDER(qc::singleControlledSx), - MQT_NAMED_BUILDER(qir::singleControlledSx)}, + MQT_NAMED_BUILDER(qir::singleControlledSx<>)}, QCToQIRBaseTestCase{"MultipleControlledSX", MQT_NAMED_BUILDER(qc::multipleControlledSx), - MQT_NAMED_BUILDER(qir::multipleControlledSx)})); + MQT_NAMED_BUILDER(qir::multipleControlledSx<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/SxdgOp.cpp @@ -422,13 +423,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseSXdgOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"SXdg", MQT_NAMED_BUILDER(qc::sxdg), - MQT_NAMED_BUILDER(qir::sxdg)}, + MQT_NAMED_BUILDER(qir::sxdg<>)}, QCToQIRBaseTestCase{"SingleControlledSXdg", MQT_NAMED_BUILDER(qc::singleControlledSxdg), - MQT_NAMED_BUILDER(qir::singleControlledSxdg)}, + MQT_NAMED_BUILDER(qir::singleControlledSxdg<>)}, QCToQIRBaseTestCase{"MultipleControlledSXdg", MQT_NAMED_BUILDER(qc::multipleControlledSxdg), - MQT_NAMED_BUILDER(qir::multipleControlledSxdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledSxdg<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/TOp.cpp @@ -437,13 +438,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseTOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"T", MQT_NAMED_BUILDER(qc::t_), - MQT_NAMED_BUILDER(qir::t_)}, + MQT_NAMED_BUILDER(qir::t_<>)}, QCToQIRBaseTestCase{"SingleControlledT", MQT_NAMED_BUILDER(qc::singleControlledT), - MQT_NAMED_BUILDER(qir::singleControlledT)}, + MQT_NAMED_BUILDER(qir::singleControlledT<>)}, QCToQIRBaseTestCase{"MultipleControlledT", MQT_NAMED_BUILDER(qc::multipleControlledT), - MQT_NAMED_BUILDER(qir::multipleControlledT)})); + MQT_NAMED_BUILDER(qir::multipleControlledT<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/TdgOp.cpp @@ -452,13 +453,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseTdgOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Tdg", MQT_NAMED_BUILDER(qc::tdg), - MQT_NAMED_BUILDER(qir::tdg)}, + MQT_NAMED_BUILDER(qir::tdg<>)}, QCToQIRBaseTestCase{"SingleControlledTdg", MQT_NAMED_BUILDER(qc::singleControlledTdg), - MQT_NAMED_BUILDER(qir::singleControlledTdg)}, + MQT_NAMED_BUILDER(qir::singleControlledTdg<>)}, QCToQIRBaseTestCase{"MultipleControlledTdg", MQT_NAMED_BUILDER(qc::multipleControlledTdg), - MQT_NAMED_BUILDER(qir::multipleControlledTdg)})); + MQT_NAMED_BUILDER(qir::multipleControlledTdg<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/U2Op.cpp @@ -467,13 +468,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseU2OpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"U2", MQT_NAMED_BUILDER(qc::u2), - MQT_NAMED_BUILDER(qir::u2)}, + MQT_NAMED_BUILDER(qir::u2<>)}, QCToQIRBaseTestCase{"SingleControlledU2", MQT_NAMED_BUILDER(qc::singleControlledU2), - MQT_NAMED_BUILDER(qir::singleControlledU2)}, + MQT_NAMED_BUILDER(qir::singleControlledU2<>)}, QCToQIRBaseTestCase{"MultipleControlledU2", MQT_NAMED_BUILDER(qc::multipleControlledU2), - MQT_NAMED_BUILDER(qir::multipleControlledU2)})); + MQT_NAMED_BUILDER(qir::multipleControlledU2<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/UOp.cpp @@ -482,13 +483,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseUOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"U", MQT_NAMED_BUILDER(qc::u), - MQT_NAMED_BUILDER(qir::u)}, + MQT_NAMED_BUILDER(qir::u<>)}, QCToQIRBaseTestCase{"SingleControlledU", MQT_NAMED_BUILDER(qc::singleControlledU), - MQT_NAMED_BUILDER(qir::singleControlledU)}, + MQT_NAMED_BUILDER(qir::singleControlledU<>)}, QCToQIRBaseTestCase{"MultipleControlledU", MQT_NAMED_BUILDER(qc::multipleControlledU), - MQT_NAMED_BUILDER(qir::multipleControlledU)})); + MQT_NAMED_BUILDER(qir::multipleControlledU<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/XOp.cpp @@ -497,13 +498,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseXOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"X", MQT_NAMED_BUILDER(qc::x), - MQT_NAMED_BUILDER(qir::x)}, + MQT_NAMED_BUILDER(qir::x<>)}, QCToQIRBaseTestCase{"SingleControlledX", MQT_NAMED_BUILDER(qc::singleControlledX), - MQT_NAMED_BUILDER(qir::singleControlledX)}, + MQT_NAMED_BUILDER(qir::singleControlledX<>)}, QCToQIRBaseTestCase{"MultipleControlledX", MQT_NAMED_BUILDER(qc::multipleControlledX), - MQT_NAMED_BUILDER(qir::multipleControlledX)})); + MQT_NAMED_BUILDER(qir::multipleControlledX<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/XxMinusYyOp.cpp @@ -512,14 +513,15 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseXXMinusYYOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"XXMinusYY", MQT_NAMED_BUILDER(qc::xxMinusYY), - MQT_NAMED_BUILDER(qir::xxMinusYY)}, - QCToQIRBaseTestCase{"SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), - MQT_NAMED_BUILDER(qir::singleControlledXxMinusYY)}, + MQT_NAMED_BUILDER(qir::xxMinusYY<>)}, + QCToQIRBaseTestCase{ + "SingleControlledXXMinusYY", + MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), + MQT_NAMED_BUILDER(qir::singleControlledXxMinusYY<>)}, QCToQIRBaseTestCase{ "MultipleControlledXXMinusYY", MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(qir::multipleControlledXxMinusYY)})); + MQT_NAMED_BUILDER(qir::multipleControlledXxMinusYY<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/XxPlusYyOp.cpp @@ -528,14 +530,14 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseXXPlusYYOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"XXPlusYY", MQT_NAMED_BUILDER(qc::xxPlusYY), - MQT_NAMED_BUILDER(qir::xxPlusYY)}, + MQT_NAMED_BUILDER(qir::xxPlusYY<>)}, QCToQIRBaseTestCase{"SingleControlledXXPlusYY", MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY), - MQT_NAMED_BUILDER(qir::singleControlledXxPlusYY)}, + MQT_NAMED_BUILDER(qir::singleControlledXxPlusYY<>)}, QCToQIRBaseTestCase{ "MultipleControlledXXPlusYY", MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(qir::multipleControlledXxPlusYY)})); + MQT_NAMED_BUILDER(qir::multipleControlledXxPlusYY<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/YOp.cpp @@ -544,13 +546,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseYOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Y", MQT_NAMED_BUILDER(qc::y), - MQT_NAMED_BUILDER(qir::y)}, + MQT_NAMED_BUILDER(qir::y<>)}, QCToQIRBaseTestCase{"SingleControlledY", MQT_NAMED_BUILDER(qc::singleControlledY), - MQT_NAMED_BUILDER(qir::singleControlledY)}, + MQT_NAMED_BUILDER(qir::singleControlledY<>)}, QCToQIRBaseTestCase{"MultipleControlledY", MQT_NAMED_BUILDER(qc::multipleControlledY), - MQT_NAMED_BUILDER(qir::multipleControlledY)})); + MQT_NAMED_BUILDER(qir::multipleControlledY<>)})); /// @} /// \name QCToQIRBase/Operations/StandardGates/ZOp.cpp @@ -559,13 +561,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseZOpTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"Z", MQT_NAMED_BUILDER(qc::z), - MQT_NAMED_BUILDER(qir::z)}, + MQT_NAMED_BUILDER(qir::z<>)}, QCToQIRBaseTestCase{"SingleControlledZ", MQT_NAMED_BUILDER(qc::singleControlledZ), - MQT_NAMED_BUILDER(qir::singleControlledZ)}, + MQT_NAMED_BUILDER(qir::singleControlledZ<>)}, QCToQIRBaseTestCase{"MultipleControlledZ", MQT_NAMED_BUILDER(qc::multipleControlledZ), - MQT_NAMED_BUILDER(qir::multipleControlledZ)})); + MQT_NAMED_BUILDER(qir::multipleControlledZ<>)})); /// @} /// \name QCToQIRBase/Operations/MeasureOp.cpp @@ -576,23 +578,24 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseTestCase{ "SingleMeasurementToSingleBit", MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(qir::singleMeasurementToSingleBit)}, + MQT_NAMED_BUILDER(qir::singleMeasurementToSingleBit<>)}, QCToQIRBaseTestCase{ "RepeatedMeasurementToSameBit", MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(qir::repeatedMeasurementToSameBit)}, + MQT_NAMED_BUILDER(qir::repeatedMeasurementToSameBit<>)}, QCToQIRBaseTestCase{ "RepeatedMeasurementToDifferentBits", MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(qir::repeatedMeasurementToDifferentBits)}, + MQT_NAMED_BUILDER(qir::repeatedMeasurementToDifferentBits<>)}, QCToQIRBaseTestCase{ "MultipleClassicalRegistersAndMeasurements", MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements), - MQT_NAMED_BUILDER(qir::multipleClassicalRegistersAndMeasurements)}, + MQT_NAMED_BUILDER( + qir::multipleClassicalRegistersAndMeasurements)}, QCToQIRBaseTestCase{ "MeasurementWithoutRegisters", MQT_NAMED_BUILDER(qc::measurementWithoutRegisters), - MQT_NAMED_BUILDER(qir::measurementWithoutRegisters)})); + MQT_NAMED_BUILDER(qir::measurementWithoutRegisters<>)})); /// @} /// \name QCToQIRBase/QubitManagement/QubitManagement.cpp @@ -601,22 +604,23 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRBaseQubitManagementTest, QCToQIRBaseTest, testing::Values( QCToQIRBaseTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubit<>)}, QCToQIRBaseTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(qc::allocQubitRegister), - MQT_NAMED_BUILDER(qir::emptyQIR)}, - QCToQIRBaseTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(qc::allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, + QCToQIRBaseTestCase{ + "AllocMultipleQubitRegisters", + MQT_NAMED_BUILDER(qc::allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(qir::allocMultipleQubitRegisters<>)}, QCToQIRBaseTestCase{ "AllocMultipleQubitRegistersWithOps", MQT_NAMED_BUILDER(qc::allocMultipleQubitRegistersWithOps), - MQT_NAMED_BUILDER(qir::allocMultipleQubitRegistersWithOps)}, + MQT_NAMED_BUILDER(qir::allocMultipleQubitRegistersWithOps<>)}, QCToQIRBaseTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(qc::allocLargeRegister), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::allocQubitRegister<>)}, QCToQIRBaseTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), - MQT_NAMED_BUILDER(qir::emptyQIR)}, + MQT_NAMED_BUILDER(qir::staticQubits)}, QCToQIRBaseTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(qc::staticQubitsWithOps), MQT_NAMED_BUILDER(qir::staticQubitsWithOps)}, @@ -636,5 +640,5 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qir::staticQubitsWithInv)}, QCToQIRBaseTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), - MQT_NAMED_BUILDER(qir::emptyQIR)})); + MQT_NAMED_BUILDER(qir::emptyQIR<>)})); /// @} diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 4d0f56912b..08d56935ed 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,8 +36,8 @@ namespace { struct QCTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCTestCase& info); }; @@ -73,7 +74,7 @@ TEST_P(QCTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = QCProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -82,7 +83,7 @@ TEST_P(QCTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized QC IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = QCProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QC IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -148,7 +149,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(rxx)}, QCTestCase{"InvControlSandwich", MQT_NAMED_BUILDER(invCtrlSandwich), - MQT_NAMED_BUILDER(singleControlledRxx)})); + MQT_NAMED_BUILDER(singleControlledRxx)}, + QCTestCase{"InverseT", MQT_NAMED_BUILDER(inverseT), + MQT_NAMED_BUILDER(tdg)})); /// @} /// \name QC/Operations/MeasureOp.cpp @@ -210,7 +213,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(barrierMultipleQubits)}, QCTestCase{"SingleControlledBarrier", MQT_NAMED_BUILDER(singleControlledBarrier), - MQT_NAMED_BUILDER(barrier)}, + MQT_NAMED_BUILDER(twoQubitsOneBarrier)}, QCTestCase{"InverseBarrier", MQT_NAMED_BUILDER(inverseBarrier), MQT_NAMED_BUILDER(barrier)})); @@ -284,7 +287,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(singleControlledP)}, QCTestCase{"TrivialControlledGlobalPhase", MQT_NAMED_BUILDER(trivialControlledGlobalPhase), - MQT_NAMED_BUILDER(globalPhase)}, + MQT_NAMED_BUILDER(globalPhaseAndMeasure)}, QCTestCase{"InverseGlobalPhase", MQT_NAMED_BUILDER(inverseGlobalPhase), MQT_NAMED_BUILDER(globalPhase)}, QCTestCase{"InverseMultipleControlledGlobalPhase", @@ -323,13 +326,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(identity)}, QCTestCase{"SingleControlledIdentity", MQT_NAMED_BUILDER(singleControlledIdentity), - MQT_NAMED_BUILDER(identity)}, + MQT_NAMED_BUILDER(twoQubitsOneIdentity)}, QCTestCase{"MultipleControlledIdentity", MQT_NAMED_BUILDER(multipleControlledIdentity), - MQT_NAMED_BUILDER(identity)}, + MQT_NAMED_BUILDER(threeQubitsOneIdentity)}, QCTestCase{"NestedControlledIdentity", MQT_NAMED_BUILDER(nestedControlledIdentity), - MQT_NAMED_BUILDER(identity)}, + MQT_NAMED_BUILDER(threeQubitsOneIdentity)}, QCTestCase{"TrivialControlledIdentity", MQT_NAMED_BUILDER(trivialControlledIdentity), MQT_NAMED_BUILDER(identity)}, @@ -337,7 +340,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(identity)}, QCTestCase{"InverseMultipleControlledIdentity", MQT_NAMED_BUILDER(inverseMultipleControlledIdentity), - MQT_NAMED_BUILDER(identity)})); + MQT_NAMED_BUILDER(threeQubitsOneIdentity)})); /// @} /// \name QC/Operations/StandardGates/IswapOp.cpp @@ -913,16 +916,11 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCQubitManagementTest, QCTest, testing::Values( - QCTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(emptyQC)}, - QCTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(emptyQC)}, - QCTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), + QCTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubitNoMeasure), MQT_NAMED_BUILDER(emptyQC)}, QCTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(emptyQC)}, - QCTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + MQT_NAMED_BUILDER(allocQubitRegister)}, + QCTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubitsNoMeasure), MQT_NAMED_BUILDER(emptyQC)}, QCTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(staticQubitsWithOps), diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index b18c858b2d..c99059aa77 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -24,7 +24,9 @@ #include #include #include +#include #include +#include #include #include @@ -37,7 +39,7 @@ namespace { struct QASM3TranslationTestCase { std::string name; std::string source; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QASM3TranslationTestCase& test); @@ -67,32 +69,47 @@ class QASM3TranslationTest } // namespace -static void twoX(qc::QCProgramBuilder& b) { +static SmallVector twoX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.x(q[0]); b.x(q[1]); + auto c0 = b.measure(q[0]); + auto c1 = b.measure(q[1]); + return {c0, c1}; } -static void singleNegControlledX(qc::QCProgramBuilder& b) { +static SmallVector singleNegControlledX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.x(q[0]); b.cx(q[0], q[1]); b.x(q[0]); + auto c0 = b.measure(q[0]); + auto c1 = b.measure(q[1]); + return {c0, c1}; } -static void tripleControlledX(qc::QCProgramBuilder& b) { +static SmallVector tripleControlledX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcx({q[0], q[1], q[2]}, q[3]); + auto c0 = b.measure(q[0]); + auto c1 = b.measure(q[1]); + auto c2 = b.measure(q[2]); + auto c3 = b.measure(q[3]); + return {c0, c1, c2, c3}; } -static void mixedControlledX(qc::QCProgramBuilder& b) { +static SmallVector mixedControlledX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.x(q[1]); b.mcx({q[0], q[1]}, q[2]); b.x(q[1]); + auto c0 = b.measure(q[0]); + auto c1 = b.measure(q[1]); + auto c2 = b.measure(q[2]); + return {c0, c1, c2}; } -static void twoMixedControlledX(qc::QCProgramBuilder& b) { +static SmallVector twoMixedControlledX(qc::QCProgramBuilder& b) { auto q1 = b.allocQubitRegister(2); auto q2 = b.allocQubitRegister(2); auto q3 = b.allocQubitRegister(2); @@ -102,15 +119,24 @@ static void twoMixedControlledX(qc::QCProgramBuilder& b) { b.x(q2[1]); b.mcx({q1[1], q2[1]}, q3[1]); b.x(q2[1]); + auto c0 = b.measure(q1[0]); + auto c1 = b.measure(q1[1]); + auto c2 = b.measure(q2[0]); + auto c3 = b.measure(q2[1]); + auto c4 = b.measure(q3[0]); + auto c5 = b.measure(q3[1]); + return {c0, c1, c2, c3, c4, c5}; } -static void ifNot(qc::QCProgramBuilder& b) { +static Value ifNot(qc::QCProgramBuilder& b) { auto trueValue = b.boolConstant(true); auto q = b.allocQubitRegister(1); b.h(q[0]); auto c = b.measure(q[0]); auto cond = arith::XOrIOp::create(b, c, trueValue).getResult(); b.scfIf(cond, [&] { b.x(q[0]); }); + auto out = b.measure(q[0]); + return out; } TEST_P(QASM3TranslationTest, ProgramEquivalence) { @@ -128,8 +154,7 @@ TEST_P(QASM3TranslationTest, ProgramEquivalence) { printer.record(translated.get(), "Canonicalized Translated QC IR" + name); EXPECT_TRUE(verify(*translated).succeeded()); - auto reference = - qc::QCProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QC IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -147,7 +172,7 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QASM3TranslationTestCase{"AllocQubit", qasm::allocQubit, - MQT_NAMED_BUILDER(qc::allocQubit)}, + MQT_NAMED_BUILDER(qc::alloc1QubitRegister)}, QASM3TranslationTestCase{"AllocQubitRegister", qasm::allocQubitRegister, MQT_NAMED_BUILDER(qc::allocQubitRegister)}, QASM3TranslationTestCase{ @@ -185,12 +210,12 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::inverseGlobalPhase)}, QASM3TranslationTestCase{"Identity", qasm::identity, MQT_NAMED_BUILDER(qc::identity)}, - QASM3TranslationTestCase{ - "SingleControlledIdentity", qasm::singleControlledIdentity, - MQT_NAMED_BUILDER(qc::singleControlledIdentity)}, - QASM3TranslationTestCase{ - "MultipleControlledIdentity", qasm::multipleControlledIdentity, - MQT_NAMED_BUILDER(qc::multipleControlledIdentity)}, + QASM3TranslationTestCase{"SingleControlledIdentity", + qasm::singleControlledIdentity, + MQT_NAMED_BUILDER(qc::twoQubitsOneIdentity)}, + QASM3TranslationTestCase{"MultipleControlledIdentity", + qasm::multipleControlledIdentity, + MQT_NAMED_BUILDER(qc::threeQubitsOneIdentity)}, QASM3TranslationTestCase{"X", qasm::x, MQT_NAMED_BUILDER(qc::x)}, QASM3TranslationTestCase{"TwoX", qasm::twoX, MQT_NAMED_BUILDER(twoX)}, QASM3TranslationTestCase{"SingleControlledX", qasm::singleControlledX, diff --git a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp index b47c9f97a7..b94e3f082c 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -36,7 +37,7 @@ namespace { struct QuantumComputationTranslationTestCase { std::string name; mqt::test::NamedBuilder<::qc::QuantumComputation> programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, @@ -87,8 +88,7 @@ TEST_P(QuantumComputationTranslationTest, ProgramEquivalence) { printer.record(translated.get(), "Canonicalized Translated QC IR" + name); EXPECT_TRUE(mlir::verify(*translated).succeeded()); - auto reference = - mlir::qc::QCProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QC IR" + name); EXPECT_TRUE(mlir::verify(*reference).succeeded()); @@ -107,7 +107,7 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QuantumComputationTranslationTestCase{ "AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), - MQT_NAMED_BUILDER(mlir::qc::allocQubit)}, + MQT_NAMED_BUILDER(mlir::qc::alloc1QubitRegister)}, QuantumComputationTranslationTestCase{ "AllocQubitRegister", MQT_NAMED_BUILDER(qc::allocQubitRegister), MQT_NAMED_BUILDER(mlir::qc::allocQubitRegister)}, @@ -160,7 +160,7 @@ INSTANTIATE_TEST_SUITE_P( QuantumComputationTranslationTestCase{ "MultipleControlledIdentity", MQT_NAMED_BUILDER(qc::multipleControlledIdentity), - MQT_NAMED_BUILDER(mlir::qc::multipleControlledIdentity)}, + MQT_NAMED_BUILDER(mlir::qc::threeQubitsOneIdentity)}, QuantumComputationTranslationTestCase{"X", MQT_NAMED_BUILDER(qc::x), MQT_NAMED_BUILDER(mlir::qc::x)}, QuantumComputationTranslationTestCase{ diff --git a/mlir/unittests/Dialect/QCO/IR/CMakeLists.txt b/mlir/unittests/Dialect/QCO/IR/CMakeLists.txt index eabfef0087..40f522e459 100644 --- a/mlir/unittests/Dialect/QCO/IR/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/IR/CMakeLists.txt @@ -8,8 +8,15 @@ set(qco_ir_target mqt-core-mlir-unittest-qco-ir) add_executable(${qco_ir_target} test_qco_ir.cpp test_qco_ir_matrix.cpp) -target_link_libraries(${qco_ir_target} PRIVATE MLIRParser MLIRSupportMQT GTest::gtest_main - MLIRQCOProgramBuilder MLIRQCOPrograms MQT::CoreDD) +target_link_libraries( + ${qco_ir_target} + PRIVATE MLIRParser + MLIRSupportMQT + GTest::gtest_main + MLIRQCOProgramBuilder + MLIRQCOPrograms + MLIRQCOUtils + MQT::CoreDD) mqt_mlir_configure_unittest_target(${qco_ir_target}) gtest_discover_tests(${qco_ir_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 26fca1733f..5d829b6284 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -39,8 +40,8 @@ namespace { struct QCOTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QCOTestCase& info); }; @@ -74,7 +75,7 @@ TEST_P(QCOTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = QCOProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -83,7 +84,7 @@ TEST_P(QCOTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = QCOProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QCO IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -117,7 +118,7 @@ TEST_F(QCOTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) { TEST_F(QCOTest, DirectIfBuilder) { // Test If construction directly QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize({builder.getI1Type(), builder.getI1Type()}); auto c0 = arith::ConstantIndexOp::create(builder, 0); auto c1 = arith::ConstantIndexOp::create(builder, 1); auto r0 = qtensor::AllocOp::create(builder, c1); @@ -130,18 +131,20 @@ TEST_F(QCOTest, DirectIfBuilder) { auto innerQubit = XOp::create(builder, qubits[0]); return SmallVector{innerQubit}; }); - auto r2 = qtensor::InsertOp::create(builder, ifOp.getResult(0), + auto finalMeasureOp = MeasureOp::create(builder, ifOp.getResult(0)); + auto r2 = qtensor::InsertOp::create(builder, finalMeasureOp.getQubitOut(), extractOp.getOutTensor(), c0); qtensor::DeallocOp::create(builder, r2); - auto directBuilder = builder.finalize(); + auto directBuilder = + builder.finalize({measureOp.getResult(), finalMeasureOp.getResult()}); ASSERT_TRUE(directBuilder); EXPECT_TRUE(verify(*directBuilder).succeeded()); EXPECT_TRUE(runQCOCleanupPipeline(directBuilder.get()).succeeded()); EXPECT_TRUE(verify(*directBuilder).succeeded()); auto refBuilder = - QCOProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(simpleIf).fn); + mqt::test::buildMLIRProgram(context.get(), MQT_NAMED_BUILDER(simpleIf)); ASSERT_TRUE(refBuilder); EXPECT_TRUE(verify(*refBuilder).succeeded()); EXPECT_TRUE(runQCOCleanupPipeline(refBuilder.get()).succeeded()); @@ -155,10 +158,9 @@ TEST_F(QCOTest, IfOpParser) { // Test IfOp parser const char* mlirCode = R"( module { - func.func @main() -> i64 attributes {passthrough = ["entry_point"]} { + func.func @main() -> i1 attributes {passthrough = ["entry_point"]} { %c0 = arith.constant 0 : index %c1 = arith.constant 1 : index - %c0_i64 = arith.constant 0 : i64 %q0_0 = qco.alloc : !qco.qubit %t0 = qtensor.alloc(%c1) : tensor<1x!qco.qubit> %q0_1 = qco.h %q0_0 : !qco.qubit -> !qco.qubit @@ -172,9 +174,10 @@ TEST_F(QCOTest, IfOpParser) { } else args(%arg0 = %q0_2, %arg1 = %t0) { qco.yield %arg0, %arg1 : !qco.qubit, tensor<1x!qco.qubit> } - qco.sink %q0_4 : !qco.qubit + %q0_5, %c = qco.measure %q0_4 : !qco.qubit + qco.sink %q0_5 : !qco.qubit qtensor.dealloc %t3 : tensor<1x!qco.qubit> - return %c0_i64 : i64 + return %c : i1 } })"; @@ -185,8 +188,8 @@ TEST_F(QCOTest, IfOpParser) { EXPECT_TRUE(runQCOCleanupPipeline(parsedSourceModule.get()).succeeded()); EXPECT_TRUE(verify(*parsedSourceModule).succeeded()); - auto refBuilder = QCOProgramBuilder::build( - context.get(), MQT_NAMED_BUILDER(ifOneQubitOneTensor).fn); + auto refBuilder = mqt::test::buildMLIRProgram( + context.get(), MQT_NAMED_BUILDER(ifOneQubitOneTensor)); ASSERT_TRUE(refBuilder); EXPECT_TRUE(verify(*refBuilder).succeeded()); EXPECT_TRUE(runQCOCleanupPipeline(refBuilder.get()).succeeded()); @@ -254,7 +257,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(invCtrlSandwich), MQT_NAMED_BUILDER(singleControlledRxx)}, QCOTestCase{"InvCtrlTwo", MQT_NAMED_BUILDER(invCtrlTwo), - MQT_NAMED_BUILDER(ctrlInvTwo)})); + MQT_NAMED_BUILDER(ctrlInvTwo)}, + QCOTestCase{"InverseT", MQT_NAMED_BUILDER(inverseT), + MQT_NAMED_BUILDER(tdg)})); /// @} /// \name QCO/Operations/StandardGates/BarrierOp.cpp @@ -306,7 +311,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(twoDcx)}, QCOTestCase{"TwoDCXSwappedTargets", MQT_NAMED_BUILDER(twoDcxSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/EcrOp.cpp @@ -333,7 +338,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledEcr), MQT_NAMED_BUILDER(multipleControlledEcr)}, QCOTestCase{"TwoECR", MQT_NAMED_BUILDER(twoEcr), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/GphaseOp.cpp @@ -377,7 +382,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledH), MQT_NAMED_BUILDER(multipleControlledH)}, QCOTestCase{"TwoH", MQT_NAMED_BUILDER(twoH), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(allocQubit)})); /// @} /// \name QCO/Operations/StandardGates/IdOp.cpp @@ -386,24 +391,24 @@ INSTANTIATE_TEST_SUITE_P( QCOIDOpTest, QCOTest, testing::Values( QCOTestCase{"Identity", MQT_NAMED_BUILDER(identity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(allocQubit)}, QCOTestCase{"SingleControlledIdentity", MQT_NAMED_BUILDER(singleControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"MultipleControlledIdentity", MQT_NAMED_BUILDER(multipleControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc3QubitRegister)}, QCOTestCase{"NestedControlledIdentity", MQT_NAMED_BUILDER(nestedControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc3QubitRegister)}, QCOTestCase{"TrivialControlledIdentity", MQT_NAMED_BUILDER(trivialControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(allocQubit)}, QCOTestCase{"InverseIdentity", MQT_NAMED_BUILDER(inverseIdentity), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"InverseMultipleControlledIdentity", MQT_NAMED_BUILDER(inverseMultipleControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc3QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/IswapOp.cpp @@ -453,7 +458,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledP), MQT_NAMED_BUILDER(multipleControlledP)}, QCOTestCase{"TwoPOppositePhase", MQT_NAMED_BUILDER(twoPOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(allocQubit)})); /// @} /// \name QCO/Operations/StandardGates/ROp.cpp @@ -505,7 +510,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledRx), MQT_NAMED_BUILDER(multipleControlledRx)}, QCOTestCase{"TwoRXOppositePhase", MQT_NAMED_BUILDER(twoRxOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RxxOp.cpp @@ -538,10 +543,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(rxx)}, QCOTestCase{"TwoRXXOppositePhase", MQT_NAMED_BUILDER(twoRxxOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoRXXOppositePhaseSwappedTargets", MQT_NAMED_BUILDER(twoRxxOppositePhaseSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RyOp.cpp @@ -566,7 +571,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledRy), MQT_NAMED_BUILDER(multipleControlledRy)}, QCOTestCase{"TwoRYOppositePhase", MQT_NAMED_BUILDER(twoRyOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RyyOp.cpp @@ -599,10 +604,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ryy)}, QCOTestCase{"TwoRYYOppositePhaseSwappedTargets", MQT_NAMED_BUILDER(twoRyyOppositePhaseSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoRYYOppositePhase", MQT_NAMED_BUILDER(twoRyyOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RzOp.cpp @@ -627,7 +632,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledRz), MQT_NAMED_BUILDER(multipleControlledRz)}, QCOTestCase{"TwoRZOppositePhase", MQT_NAMED_BUILDER(twoRzOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RzxOp.cpp @@ -655,7 +660,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledRzx)}, QCOTestCase{"TwoRZXOppositePhase", MQT_NAMED_BUILDER(twoRzxOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/RzzOp.cpp @@ -688,10 +693,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(rzz)}, QCOTestCase{"TwoRZZOppositePhaseSwappedTargets", MQT_NAMED_BUILDER(twoRzzOppositePhaseSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoRZZOppositePhase", MQT_NAMED_BUILDER(twoRzzOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/SOp.cpp @@ -715,7 +720,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledS), MQT_NAMED_BUILDER(multipleControlledSdg)}, QCOTestCase{"SThenSdg", MQT_NAMED_BUILDER(sThenSdg), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoS", MQT_NAMED_BUILDER(twoS), MQT_NAMED_BUILDER(z)})); /// @} @@ -743,7 +748,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledSdg), MQT_NAMED_BUILDER(multipleControlledS)}, QCOTestCase{"SdgThenS", MQT_NAMED_BUILDER(sdgThenS), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoSdg", MQT_NAMED_BUILDER(twoSdg), MQT_NAMED_BUILDER(z)})); /// @} @@ -772,10 +777,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledSwap), MQT_NAMED_BUILDER(multipleControlledSwap)}, QCOTestCase{"TwoSWAP", MQT_NAMED_BUILDER(twoSwap), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoSWAPSwappedTargets", MQT_NAMED_BUILDER(twoSwapSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc2QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/SxOp.cpp @@ -800,7 +805,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledSx), MQT_NAMED_BUILDER(multipleControlledSxdg)}, QCOTestCase{"SXThenSXdg", MQT_NAMED_BUILDER(sxThenSxdg), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoSX", MQT_NAMED_BUILDER(twoSx), MQT_NAMED_BUILDER(x)})); /// @} @@ -828,7 +833,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledSxdg), MQT_NAMED_BUILDER(multipleControlledSx)}, QCOTestCase{"SXdgThenSX", MQT_NAMED_BUILDER(sxdgThenSx), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoSXdg", MQT_NAMED_BUILDER(twoSxdg), MQT_NAMED_BUILDER(x)})); /// @} @@ -854,7 +859,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledT), MQT_NAMED_BUILDER(multipleControlledTdg)}, QCOTestCase{"TThenTdg", MQT_NAMED_BUILDER(tThenTdg), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoT", MQT_NAMED_BUILDER(twoT), MQT_NAMED_BUILDER(s)})); /// @} @@ -882,7 +887,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledTdg), MQT_NAMED_BUILDER(multipleControlledT)}, QCOTestCase{"TdgThenS", MQT_NAMED_BUILDER(tdgThenT), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"TwoTdg", MQT_NAMED_BUILDER(twoTdg), MQT_NAMED_BUILDER(sdg)})); /// @} @@ -967,11 +972,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledX), MQT_NAMED_BUILDER(multipleControlledX)}, QCOTestCase{"TwoX", MQT_NAMED_BUILDER(twoX), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc1QubitRegister)}, QCOTestCase{"ControlledTwoX", MQT_NAMED_BUILDER(controlledTwoX), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"InverseTwoX", MQT_NAMED_BUILDER(inverseTwoX), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/XxMinusYyOp.cpp @@ -1000,7 +1005,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledXxMinusYY)}, QCOTestCase{"TwoXXMinusYYOppositePhase", MQT_NAMED_BUILDER(twoXxMinusYYOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoXXMinusYYSwappedTargets", MQT_NAMED_BUILDER(twoXxMinusYYSwappedTargets), MQT_NAMED_BUILDER(xxMinusYY)})); @@ -1032,7 +1037,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledXxPlusYY)}, QCOTestCase{"TwoXXPlusYYOppositePhase", MQT_NAMED_BUILDER(twoXxPlusYYOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"TwoXXPlusYYSwappedTargets", MQT_NAMED_BUILDER(twoXxPlusYYSwappedTargets), MQT_NAMED_BUILDER(xxPlusYY)})); @@ -1059,7 +1064,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledY), MQT_NAMED_BUILDER(multipleControlledY)}, QCOTestCase{"TwoY", MQT_NAMED_BUILDER(twoY), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/StandardGates/ZOp.cpp @@ -1083,7 +1088,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledZ), MQT_NAMED_BUILDER(multipleControlledZ)}, QCOTestCase{"TwoZ", MQT_NAMED_BUILDER(twoZ), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(alloc1QubitRegister)})); /// @} /// \name QCO/Operations/MeasureOp.cpp @@ -1112,13 +1117,13 @@ INSTANTIATE_TEST_SUITE_P( QCOResetOpTest, QCOTest, testing::Values(QCOTestCase{"ResetQubitWithoutOp", MQT_NAMED_BUILDER(resetQubitWithoutOp), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(allocQubit)}, QCOTestCase{"ResetMultipleQubitsWithoutOp", MQT_NAMED_BUILDER(resetMultipleQubitsWithoutOp), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(alloc2QubitRegister)}, QCOTestCase{"RepeatedResetWithoutOp", MQT_NAMED_BUILDER(repeatedResetWithoutOp), - MQT_NAMED_BUILDER(emptyQCO)}, + MQT_NAMED_BUILDER(allocQubit)}, QCOTestCase{"ResetQubitAfterSingleOp", MQT_NAMED_BUILDER(resetQubitAfterSingleOp), MQT_NAMED_BUILDER(resetQubitAfterSingleOp)}, @@ -1136,16 +1141,10 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QCOQubitManagementTest, QCOTest, testing::Values( - QCOTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), + QCOTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubitNoMeasure), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + QCOTestCase{"StaticQubitsNoMeasure", + MQT_NAMED_BUILDER(staticQubitsNoMeasure), MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(staticQubitsWithOps), @@ -1162,6 +1161,11 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(staticQubitsWithInv), MQT_NAMED_BUILDER(staticQubitsWithInv)}, + QCOTestCase{"DeadGateElimination", MQT_NAMED_BUILDER(deadGatesProgram), + MQT_NAMED_BUILDER(alloc2Qubits)}, + QCOTestCase{"DeadGateEliminationIfOp", + MQT_NAMED_BUILDER(deadGatesWithIfOpProgram), + MQT_NAMED_BUILDER(deadGatesWithIfOpSimplified)}, QCOTestCase{"AllocSinkPair", MQT_NAMED_BUILDER(allocSinkPair), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(allocQubitNoMeasure)})); /// @} 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 7e0cd58d31..49233d5e4e 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -20,6 +20,7 @@ #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" #include "qco_programs.h" @@ -28,13 +29,17 @@ #include #include #include +#include #include #include +#include #include #include #include +#include #include +#include using namespace mlir; using namespace qco; @@ -55,8 +60,7 @@ matrix4FromDefinition(const Definition& definition) { template [[nodiscard]] static Matrix4x4 -expectedMatrixFromComputation(const Fn& build, - const std::size_t numQubits = 2) { +expectedMatrixFromComputation(const Fn& build, const size_t numQubits = 2) { qc::QuantumComputation comp; build(comp); const auto package = std::make_unique(numQubits); @@ -64,33 +68,45 @@ expectedMatrixFromComputation(const Fn& build, dd::buildFunctionality(comp, *package).getMatrix(numQubits)); } -static void controlledXH(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(2); - b.ctrl(q[0], q[1], [&](ValueRange targets) { - auto wire = b.x(targets[0]); - wire = b.h(wire); - return SmallVector{wire}; - }); +[[nodiscard]] static InvOp firstInvOp(ModuleOp module) { + auto funcOp = cast(module.getBody()->front()); + return *funcOp.getBody().getOps().begin(); } -static void controlledInverseHT(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(2); - b.ctrl(q[0], q[1], [&](ValueRange targets) { - auto wire = b.inv({targets[0]}, [&](ValueRange innerTargets) { - auto inner = b.h(innerTargets[0]); - inner = b.t(inner); - return SmallVector{inner}; - })[0]; - return SmallVector{wire}; - }); +[[nodiscard]] static CtrlOp firstCtrlOp(ModuleOp module) { + auto funcOp = cast(module.getBody()->front()); + return *funcOp.getBody().getOps().begin(); +} + +[[nodiscard]] static std::optional invMatrix(ModuleOp module) { + return firstInvOp(module).getUnitaryMatrix(); +} + +template +static void assertInvBodyAdjoint(MLIRContext* ctx, Builder&& build, + const DynamicMatrix& body) { + auto moduleOp = QCOProgramBuilder::build(ctx, std::forward(build)); + ASSERT_TRUE(moduleOp); + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + ASSERT_TRUE(matrix->isApprox(body.adjoint())); +} + +template +static void expectComposeNTargetFails(MLIRContext* ctx, Builder&& build, + size_t numTargets) { + auto moduleOp = QCOProgramBuilder::build(ctx, std::forward(build)); + ASSERT_TRUE(moduleOp); + EXPECT_FALSE(composeBodyMatrix(*firstInvOp(*moduleOp).getBody(), numTargets) + .has_value()); } namespace { struct QCOMatrixTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; }; class QCOMatrixTest : public testing::TestWithParam { @@ -114,10 +130,8 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), singleControlledX); ASSERT_TRUE(moduleOp); - // Get the operation from the module - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto ctrlOp = *funcOp.getBody().getOps().begin(); - auto matrix = ctrlOp.getUnitaryMatrix(); + const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); + ASSERT_TRUE(matrix); const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { @@ -132,9 +146,7 @@ TEST_F(QCOMatrixTest, ControlledHOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), singleControlledH); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto ctrlOp = *funcOp.getBody().getOps().begin(); - auto matrix = ctrlOp.getUnitaryMatrix(); + const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); const Matrix4x4 expected = @@ -150,9 +162,7 @@ TEST_F(QCOMatrixTest, ControlledXHOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), controlledXH); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto ctrlOp = *funcOp.getBody().getOps().begin(); - auto matrix = ctrlOp.getUnitaryMatrix(); + const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); const Matrix4x4 expected = @@ -169,9 +179,7 @@ TEST_F(QCOMatrixTest, ControlledInverseHTOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), controlledInverseHT); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto ctrlOp = *funcOp.getBody().getOps().begin(); - auto matrix = ctrlOp.getUnitaryMatrix(); + const auto matrix = firstCtrlOp(*moduleOp).getUnitaryMatrix(); ASSERT_TRUE(matrix); const Matrix4x4 expected = @@ -186,6 +194,118 @@ TEST_F(QCOMatrixTest, ControlledInverseHTOpMatrix) { ASSERT_TRUE(matrix->isApprox(expected)); } + +TEST_F(QCOMatrixTest, InverseTwoRxRyOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), inverseTwoRxRy); + ASSERT_TRUE(moduleOp); + + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + + const DynamicMatrix body = RYOp::unitaryMatrix(0.3).embedInNqubit(2, 1) * + RXOp::unitaryMatrix(0.2).embedInNqubit(2, 0); + ASSERT_TRUE(matrix->isApprox(body.adjoint())); +} + +TEST_F(QCOMatrixTest, InverseCxThenRzOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), inverseCxThenRz); + ASSERT_TRUE(moduleOp); + + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + + const Matrix4x4 cx = Matrix4x4::fromElements(1.0, 0.0, 0.0, 0.0, // + 0.0, 1.0, 0.0, 0.0, // + 0.0, 0.0, 0.0, 1.0, // + 0.0, 0.0, 1.0, 0.0); + const DynamicMatrix body = + RZOp::unitaryMatrix(0.4).embedInNqubit(2, 1) * cx.embedInNqubit(2, 0, 1); + ASSERT_TRUE(matrix->isApprox(body.adjoint())); +} + +TEST_F(QCOMatrixTest, InverseDcxThenRzOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), inverseDcxThenRz); + ASSERT_TRUE(moduleOp); + + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + + const DynamicMatrix body = RZOp::unitaryMatrix(0.4).embedInNqubit(2, 1) * + DCXOp::getUnitaryMatrix().embedInNqubit(2, 0, 1); + ASSERT_TRUE(matrix->isApprox(body.adjoint())); +} + +TEST_F(QCOMatrixTest, InvCtrlTwoOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), invCtrlTwo); + ASSERT_TRUE(moduleOp); + + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + + DynamicMatrix body = RXXOp::unitaryMatrix(0.123).embedInNqubit(2, 0, 1) * + XOp::getUnitaryMatrix().embedInNqubit(2, 0); + DynamicMatrix ctrl = DynamicMatrix::identity(8); + ctrl.setBottomRightCorner(body); + ASSERT_TRUE(matrix->isApprox(ctrl.adjoint())); +} + +TEST_F(QCOMatrixTest, InverseGphaseBarrierXOpMatrix) { + DynamicMatrix body; + body.assignFrom(XOp::getUnitaryMatrix()); + body *= std::exp(Complex{0.0, 0.25}); + assertInvBodyAdjoint(context.get(), inverseGphaseBarrierX, body); +} + +TEST_F(QCOMatrixTest, InverseModifierWiresOpMatrix) { + assertInvBodyAdjoint( + context.get(), inverseNestedInvHAndT, + DynamicMatrix(TOp::getUnitaryMatrix() * HOp::getUnitaryMatrix())); + assertInvBodyAdjoint(context.get(), inverseNestedInvHAndX, + XOp::getUnitaryMatrix().embedInNqubit(2, 1) * + HOp::getUnitaryMatrix().embedInNqubit(2, 0)); + assertInvBodyAdjoint(context.get(), inverseThreeWireRxRyRz, + RZOp::unitaryMatrix(0.4).embedInNqubit(3, 2) * + RYOp::unitaryMatrix(0.3).embedInNqubit(3, 1) * + RXOp::unitaryMatrix(0.2).embedInNqubit(3, 0)); + assertInvBodyAdjoint(context.get(), inverseThreeWireNestedTwoInv, + RZOp::unitaryMatrix(0.4).embedInNqubit(3, 2) * + (RYOp::unitaryMatrix(0.3).embedInNqubit(3, 1) * + RXOp::unitaryMatrix(0.2).embedInNqubit(3, 0)) + .adjoint()); +} + +TEST_F(QCOMatrixTest, ComposeNTargetRejectsExcessiveTargets) { + auto moduleOp = QCOProgramBuilder::build(context.get(), inverseTwoRxRy); + ASSERT_TRUE(moduleOp); + EXPECT_FALSE(composeBodyMatrix(*firstInvOp(*moduleOp).getBody(), + kMaxModifierTargetQubits + 1) + .has_value()); +} + +TEST_F(QCOMatrixTest, ComposeNTargetRejectsThreeQubitOp) { + expectComposeNTargetFails(context.get(), inverseWithThreeQubitOpInBody, 3); +} + +TEST_F(QCOMatrixTest, ComposeNTargetRejectsRuntimeGphase) { + constexpr auto mlirCode = R"( + module { + func.func @test(%theta: f64) -> !qco.qubit { + %q_in = qco.alloc : !qco.qubit + %q_out = qco.inv (%q = %q_in) { + qco.gphase(%theta) + %q_1 = qco.x %q : !qco.qubit -> !qco.qubit + qco.yield %q_1 : !qco.qubit + } : {!qco.qubit} -> {!qco.qubit} + return %q_out : !qco.qubit + } + } + )"; + + auto moduleOp = parseSourceString(mlirCode, context.get()); + ASSERT_TRUE(moduleOp); + EXPECT_FALSE( + composeBodyMatrix(*firstInvOp(*moduleOp).getBody(), 1).has_value()); +} /// @} /// \name QCO/Modifiers/InvOp.cpp @@ -194,10 +314,8 @@ TEST_F(QCOMatrixTest, InverseIswapOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseIswap); ASSERT_TRUE(moduleOp); - // Get the operation from the module - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { @@ -212,9 +330,7 @@ TEST_F(QCOMatrixTest, InverseTwoXOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseTwoX); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - const auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); DynamicMatrix expected; @@ -226,9 +342,7 @@ TEST_F(QCOMatrixTest, InverseXOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseX); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - const auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); DynamicMatrix expected; @@ -240,9 +354,7 @@ TEST_F(QCOMatrixTest, InverseSxOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseSx); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - const auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); DynamicMatrix expected; @@ -254,9 +366,7 @@ TEST_F(QCOMatrixTest, InverseGphaseXOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseGphaseX); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - const auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); const auto composeGlobal = std::polar(1.0, -0.123); @@ -269,9 +379,7 @@ TEST_F(QCOMatrixTest, InverseGphaseBarrierOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseGphaseBarrier); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - const auto matrix = invOp.getUnitaryMatrix(); + const auto matrix = invMatrix(*moduleOp); ASSERT_TRUE(matrix); const auto global = std::conj(std::polar(1.0, 0.123)); @@ -284,19 +392,20 @@ TEST_F(QCOMatrixTest, InverseTwoBarriersInInvOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), inverseTwoBarriersInInv); ASSERT_TRUE(moduleOp); - - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - EXPECT_FALSE(invOp.getUnitaryMatrix()); + EXPECT_FALSE(invMatrix(*moduleOp).has_value()); } TEST_F(QCOMatrixTest, InvTwoOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), invTwo); ASSERT_TRUE(moduleOp); - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - EXPECT_FALSE(invOp.getUnitaryMatrix()); + const auto matrix = invMatrix(*moduleOp); + ASSERT_TRUE(matrix); + + const DynamicMatrix body = + RXXOp::unitaryMatrix(0.123).embedInNqubit(2, 0, 1) * + XOp::getUnitaryMatrix().embedInNqubit(2, 0); + ASSERT_TRUE(matrix->isApprox(body.adjoint())); } TEST_F(QCOMatrixTest, InverseDynamicRzXOpMatrix) { @@ -316,10 +425,7 @@ TEST_F(QCOMatrixTest, InverseDynamicRzXOpMatrix) { auto moduleOp = parseSourceString(mlirCode, context.get()); ASSERT_TRUE(moduleOp); - - auto funcOp = *moduleOp->getBody()->getOps().begin(); - auto invOp = *funcOp.getBody().getOps().begin(); - EXPECT_FALSE(invOp.getUnitaryMatrix()); + EXPECT_FALSE(invMatrix(*moduleOp).has_value()); } /// @} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp index 90041fbccd..f1413aed70 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cpp @@ -18,6 +18,8 @@ #include "mlir/Dialect/Utils/Utils.h" #include +#include +#include #include #include #include @@ -70,7 +72,7 @@ struct TestFixture { void setUp() { DialectRegistry registry; - registry.insert(); context = std::make_unique(); context->appendDialectRegistry(registry); @@ -101,6 +103,18 @@ class EulerSynthesisExactTest } // namespace +/** + * @brief Measures the given qubits and returns the measurement outcomes. + * @param b The `ProgramBuilder` used to perform the measurements. + * @param qubits The qubits to be measured. + * @return The result values. + */ +static SmallVector measureAndReturn(QCOProgramBuilder& b, + ValueRange qubits) { + return to_vector( + llvm::map_range(qubits, [&](Value q) { return b.measure(q).second; })); +} + //===----------------------------------------------------------------------===// // Euler synthesis support //===----------------------------------------------------------------------===// @@ -134,8 +148,8 @@ template } template static void forEachBasis(Fn fn) { - const std::array bases = {"zyz", "zxz", "xzx", "xyx", - "u", "zsxx", "r"}; + constexpr std::array bases = {"zyz", "zxz", "xzx", "xyx", + "u", "zsxx", "r"}; for (const char* basis : bases) { fn(StringRef{basis}); } @@ -361,7 +375,7 @@ INSTANTIATE_TEST_SUITE_P( ZSXXShortcutCase{"ZYZNearZeroTheta", [](MLIRContext*) -> Matrix2x2 { constexpr double tol = - 0.5 * mlir::utils::TOLERANCE; + 0.5 * utils::TOLERANCE; return RZOp::unitaryMatrix(0.4) * RYOp::unitaryMatrix(tol) * RZOp::unitaryMatrix(0.3); @@ -376,23 +390,21 @@ INSTANTIATE_TEST_SUITE_P( ZSXXShortcutCase{"RYNearHalfPi", [](MLIRContext* ctx) -> Matrix2x2 { return rotationMatrix( - ctx, - (std::numbers::pi / 2.0) + - (0.5 * mlir::utils::TOLERANCE)); + ctx, (std::numbers::pi / 2.0) + + (0.5 * utils::TOLERANCE)); }, 2, 1, 0}, ZSXXShortcutCase{"RYNearZero", [](MLIRContext* ctx) -> Matrix2x2 { return rotationMatrix( - ctx, 0.5 * mlir::utils::TOLERANCE); + ctx, 0.5 * utils::TOLERANCE); }, 0, 0, 0}, ZSXXShortcutCase{"RYNearPi", [](MLIRContext* ctx) -> Matrix2x2 { return rotationMatrix( - ctx, - std::numbers::pi - - (0.5 * mlir::utils::TOLERANCE)); + ctx, std::numbers::pi - + (0.5 * utils::TOLERANCE)); }, 1, 0, 1}), [](const testing::TestParamInfo& info) { @@ -456,7 +468,7 @@ TEST(EulerSynthesisTest, RandomReconstructionAllBases) { TEST(EulerAnglesCoverageTest, ParamsZYZUsesOffDiagonal01When10IsNearZero) { Matrix2x2 matrix = RXOp::unitaryMatrix(0.4); matrix(1, 0) = Complex{0.0, 0.0}; - ASSERT_GT(std::abs(matrix(0, 1)), mlir::utils::TOLERANCE); + ASSERT_GT(std::abs(matrix(0, 1)), utils::TOLERANCE); const EulerAngles angles = anglesFromUnitary(matrix, ZYZ); EXPECT_TRUE(std::isfinite(angles.theta)); EXPECT_TRUE(std::isfinite(angles.phi)); @@ -470,9 +482,9 @@ TEST(EulerAnglesCoverageTest, PhaseOnlyDecompositionSkipsRotationGates) { const Matrix2x2 matrix = Matrix2x2::fromElements(scale, 0, 0, scale); ASSERT_FALSE(matrix.isApprox(Matrix2x2::identity())); const EulerAngles angles = anglesFromUnitary(matrix, ZYZ); - EXPECT_LE(std::abs(angles.theta), mlir::utils::TOLERANCE); - EXPECT_LE(std::abs(angles.phi), mlir::utils::TOLERANCE); - EXPECT_LE(std::abs(angles.lambda), mlir::utils::TOLERANCE); + EXPECT_LE(std::abs(angles.theta), utils::TOLERANCE); + EXPECT_LE(std::abs(angles.phi), utils::TOLERANCE); + EXPECT_LE(std::abs(angles.lambda), utils::TOLERANCE); const auto circuit = synthesizeMatrix(fx.ctx(), matrix, ZYZ); ASSERT_TRUE(succeeded(verify(*circuit.mlirModule))); EXPECT_EQ(countZYZGates(circuit.func), 0U); @@ -494,7 +506,7 @@ TEST(EulerAnglesCoverageTest, UBasisNonzeroThetaEmitsSingleUGate) { fx.setUp(); const Matrix2x2 matrix = RYOp::unitaryMatrix(1.2); const EulerAngles angles = anglesFromUnitary(matrix, U); - ASSERT_GT(std::abs(angles.theta), mlir::utils::TOLERANCE); + ASSERT_GT(std::abs(angles.theta), utils::TOLERANCE); expectSynthesizedMatrix(fx.ctx(), matrix, U, [](func::FuncOp funcOp, const Matrix2x2& /*matrix*/) { EXPECT_EQ(countOps(funcOp), 1U); @@ -507,7 +519,7 @@ TEST(EulerAnglesCoverageTest, RBasisNonzeroThetaEmitsThreeRGates) { fx.setUp(); const Matrix2x2 matrix = HOp::getUnitaryMatrix(); const EulerAngles angles = anglesFromUnitary(matrix, R); - ASSERT_GT(std::abs(angles.theta), mlir::utils::TOLERANCE); + ASSERT_GT(std::abs(angles.theta), utils::TOLERANCE); expectSynthesizedMatrix(fx.ctx(), matrix, R, [](func::FuncOp funcOp, const Matrix2x2& /*matrix*/) { EXPECT_EQ(countOps(funcOp), 3U); @@ -517,7 +529,7 @@ TEST(EulerAnglesCoverageTest, RBasisNonzeroThetaEmitsThreeRGates) { TEST(EulerAnglesCoverageTest, Mod2PiMapsPiBoundaryThroughSynthesis) { TestFixture fx; fx.setUp(); - constexpr double eps = 0.5 * mlir::utils::TOLERANCE; + constexpr double eps = 0.5 * utils::TOLERANCE; const Complex global = std::polar(1.0, std::numbers::pi - eps); const Matrix2x2 matrix = Matrix2x2::fromElements(global, 0, 0, global); expectSynthesizedMatrix(fx.ctx(), matrix, U, @@ -532,7 +544,8 @@ TEST(EulerAnglesCoverageTest, Mod2PiPreservesNonFinitePhase) { fx.setUp(); const Matrix2x2 matrix = Matrix2x2::fromElements( Complex{std::numeric_limits::quiet_NaN(), 0}, 0, 0, 1); - EXPECT_NO_FATAL_FAILURE(synthesizeMatrix(fx.ctx(), matrix, ZYZ)); + EXPECT_NO_FATAL_FAILURE(std::ignore = + synthesizeMatrix(fx.ctx(), matrix, ZYZ)); } //===----------------------------------------------------------------------===// @@ -765,53 +778,58 @@ static void runFuseInParent(MLIRContext* ctx, ProgramT program, // --- Fuse program fixtures --- // -static void singleQubitRunWithSingleQubitGate(QCOProgramBuilder& b) { +static SmallVector +singleQubitRunWithSingleQubitGate(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); q[0] = b.t(q[0]); q[0] = b.rz(0.123, q[0]); - q[0] = b.inv({q[0]}, [&b](ValueRange targets) -> SmallVector { - return {b.sx(targets[0])}; - })[0]; + q[0] = b.inv(q[0], [&b](Value qubit) { return b.sx(qubit); }); q[0] = b.ry(-0.456, q[0]); + return measureAndReturn(b, q.qubits); } -static void singleQubitRunsSplitByTwoQGate(QCOProgramBuilder& b) { +static SmallVector singleQubitRunsSplitByTwoQGate(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); q[0] = b.h(q[0]); q[0] = b.t(q[0]); std::tie(q[0], q[1]) = b.swap(q[0], q[1]); q[0] = b.rz(0.321, q[0]); q[0] = b.sx(q[0]); + return measureAndReturn(b, q.qubits); } -static void singleQubitRunsSplitByBarrier(QCOProgramBuilder& b) { +static SmallVector singleQubitRunsSplitByBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); q[0] = b.t(q[0]); q[0] = b.barrier({q[0]})[0]; q[0] = b.rz(0.321, q[0]); q[0] = b.sx(q[0]); + return measureAndReturn(b, q.qubits); } -static void singleNonBasisGate(QCOProgramBuilder& b) { +static SmallVector singleNonBasisGate(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); + return measureAndReturn(b, q.qubits); } -static void singlePauliX(QCOProgramBuilder& b) { +static SmallVector singlePauliX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.x(q[0]); + return measureAndReturn(b, q.qubits); } -static void canonicalZYZRun(QCOProgramBuilder& b) { +static SmallVector canonicalZYZRun(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.rz(0.3, q[0]); q[0] = b.ry(0.5, q[0]); q[0] = b.rz(0.7, q[0]); + return measureAndReturn(b, q.qubits); } -static void overlongZYZRun(QCOProgramBuilder& b) { +static SmallVector overlongZYZRun(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.rz(0.3, q[0]); q[0] = b.ry(0.5, q[0]); @@ -819,38 +837,43 @@ static void overlongZYZRun(QCOProgramBuilder& b) { q[0] = b.ry(0.9, q[0]); q[0] = b.rz(1.1, q[0]); q[0] = b.ry(1.3, q[0]); + return measureAndReturn(b, q.qubits); } -static void overlongZSXXMixedPureZRun(QCOProgramBuilder& b) { +static SmallVector overlongZSXXMixedPureZRun(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sx(q[0]); q[0] = b.rz(std::numbers::pi, q[0]); q[0] = b.sx(q[0]); + return measureAndReturn(b, q.qubits); } -static void singleQubitRunInScfFor(QCOProgramBuilder& b) { +static SmallVector singleQubitRunInScfFor(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.scfFor(0, 1, 1, ValueRange{q[0]}, [&b](Value, ValueRange iterArgs) { - Value wire = iterArgs[0]; - wire = b.h(wire); - wire = b.t(wire); - wire = b.rz(0.123, wire); - return SmallVector{wire}; - }); + auto res = + b.scfFor(0, 1, 1, ValueRange{q[0]}, [&b](Value, ValueRange iterArgs) { + Value wire = iterArgs[0]; + wire = b.h(wire); + wire = b.t(wire); + wire = b.rz(0.123, wire); + return SmallVector{wire}; + }); + return measureAndReturn(b, res); } -static void xInverseTwoX(QCOProgramBuilder& b) { +static SmallVector xInverseTwoX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.x(q[0]); - q[0] = b.inv({q[0]}, [&b](ValueRange targets) { - Value wire = b.x(targets[0]); - wire = b.x(wire); - return SmallVector{wire}; - })[0]; + q[0] = b.inv(q[0], [&b](Value qubit) { + qubit = b.x(qubit); + return b.x(qubit); + }); q[0] = b.x(q[0]); + return measureAndReturn(b, q.qubits); } -static void inverseMultiQubitBodySingleQubitRun(QCOProgramBuilder& b) { +static SmallVector +inverseMultiQubitBodySingleQubitRun(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); auto outs = b.inv({q[0], q[1]}, [&b](ValueRange targets) -> SmallVector { @@ -860,36 +883,38 @@ static void inverseMultiQubitBodySingleQubitRun(QCOProgramBuilder& b) { }); q[0] = outs[0]; q[1] = outs[1]; + return measureAndReturn(b, q.qubits); } -static void controlledInverseHT(QCOProgramBuilder& b) { +static SmallVector controlledInverseHT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl(q[0], q[1], [&b](ValueRange targets) { - auto wire = b.inv({targets[0]}, [&b](ValueRange innerTargets) { - auto inner = b.h(innerTargets[0]); - inner = b.t(inner); - return SmallVector{inner}; - })[0]; - return SmallVector{wire}; + auto res = b.ctrl(q[0], q[1], [&b](Value target) { + return b.inv(target, [&b](Value qubit) { + qubit = b.h(qubit); + return b.t(qubit); + }); }); + return measureAndReturn(b, {res.second}); } -static void controlledH(QCOProgramBuilder& b) { +static SmallVector controlledH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl(q[0], q[1], - [&b](ValueRange targets) { return SmallVector{b.h(targets[0])}; }); + auto res = b.ctrl(q[0], q[1], [&b](Value target) { return b.h(target); }); + return measureAndReturn(b, {res.second}); } -static void singleQubitRunsSplitByScfFor(QCOProgramBuilder& b) { +static SmallVector singleQubitRunsSplitByScfFor(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); q[0] = b.t(q[0]); - b.scfFor(0, 1, 1, ValueRange{q[0]}, [&b](Value, ValueRange iterArgs) { - Value wire = iterArgs[0]; - wire = b.rz(0.321, wire); - wire = b.sx(wire); - return SmallVector{wire}; - }); + auto res = + b.scfFor(0, 1, 1, ValueRange{q[0]}, [&b](Value, ValueRange iterArgs) { + Value wire = iterArgs[0]; + wire = b.rz(0.321, wire); + wire = b.sx(wire); + return SmallVector{wire}; + }); + return measureAndReturn(b, res); } //===----------------------------------------------------------------------===// @@ -910,7 +935,7 @@ TEST(FuseSingleQubitUnitaryRunsTest, FusesProgramsAllBases) { fx.setUp(); struct Case { - void (*program)(QCOProgramBuilder&); + SmallVector (*program)(QCOProgramBuilder&); void (*extra)(func::FuncOp, StringRef); }; const std::array cases = {{ @@ -990,7 +1015,7 @@ TEST(FuseSingleQubitUnitaryRunsTest, DoesNotFuseAcrossBoundariesAllBases) { fx.setUp(); struct Case { - void (*program)(QCOProgramBuilder&); + SmallVector (*program)(QCOProgramBuilder&); void (*check)(func::FuncOp, StringRef, MLIRContext*); }; const std::array cases = {{ diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index 37c175edc8..2b822c6cba 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -790,18 +790,15 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { EXPECT_TRUE(spec->allowsOp( UOp::create(builder, loc, q0, 0.1, 0.2, 0.3).getOperation())); - auto cx = CtrlOp::create( - builder, loc, ValueRange{q0}, ValueRange{q1}, - [&builder, &loc](ValueRange targets) -> SmallVector { - return {XOp::create(builder, loc, targets[0]).getOutputQubit(0)}; - }); + auto cx = CtrlOp::create(builder, loc, q0, q1, [&](Value target) { + return XOp::create(builder, loc, target).getOutputQubit(0); + }); EXPECT_TRUE(spec->allowsOp(cx.getOperation())); - auto cxWithInterleavedH = CtrlOp::create( - builder, loc, ValueRange{q0}, ValueRange{q1}, - [&builder, &loc](ValueRange targets) -> SmallVector { - auto wire = XOp::create(builder, loc, targets[0]).getOutputQubit(0); - return {HOp::create(builder, loc, wire).getOutputQubit(0)}; + auto cxWithInterleavedH = + CtrlOp::create(builder, loc, q0, q1, [&](Value target) { + auto wire = XOp::create(builder, loc, target).getOutputQubit(0); + return HOp::create(builder, loc, wire).getOutputQubit(0); }); EXPECT_FALSE(spec->allowsOp(cxWithInterleavedH.getOperation())); @@ -816,11 +813,9 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { EXPECT_FALSE( rzSpec->allowsOp(POp::create(builder, loc, q0, 0.3).getOperation())); - auto hCtrl = CtrlOp::create( - builder, loc, ValueRange{q0}, ValueRange{q1}, - [&builder, &loc](ValueRange targets) -> SmallVector { - return {HOp::create(builder, loc, targets[0]).getOutputQubit(0)}; - }); + auto hCtrl = CtrlOp::create(builder, loc, q0, q1, [&](Value target) { + return HOp::create(builder, loc, target).getOutputQubit(0); + }); EXPECT_FALSE(spec->allowsOp(hCtrl.getOperation())); const auto funcTy3 = builder.getFunctionType({qubitTy, qubitTy, qubitTy}, @@ -831,20 +826,17 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { Value c0 = entry3->getArgument(0); Value c1 = entry3->getArgument(1); Value target = entry3->getArgument(2); - auto ccx = CtrlOp::create( - builder, loc, ValueRange{c0, c1}, ValueRange{target}, - [&builder, &loc](ValueRange targets) -> SmallVector { - return {XOp::create(builder, loc, targets[0]).getOutputQubit(0)}; + auto ccx = + CtrlOp::create(builder, loc, ValueRange{c0, c1}, target, [&](Value t) { + return XOp::create(builder, loc, t).getOutputQubit(0); }); EXPECT_FALSE(spec->allowsOp(ccx.getOperation())); const auto czSpec = NativeGateset::parse("u,cz"); ASSERT_TRUE(czSpec); - auto cz = CtrlOp::create( - builder, loc, ValueRange{q0}, ValueRange{q1}, - [&builder, &loc](ValueRange targets) -> SmallVector { - return {ZOp::create(builder, loc, targets[0]).getOutputQubit(0)}; - }); + auto cz = CtrlOp::create(builder, loc, q0, q1, [&](Value t) { + return ZOp::create(builder, loc, t).getOutputQubit(0); + }); EXPECT_TRUE(czSpec->allowsOp(cz.getOperation())); EXPECT_FALSE(czSpec->allowsOp(cx.getOperation())); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index f209d23716..3153febf45 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -14,23 +14,27 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" -#include "mlir/Dialect/QCO/Utils/Algorithms.h" -#include "mlir/Dialect/QCO/Utils/Drivers.h" -#include "mlir/Dialect/QCO/Utils/Qubits.h" +#include "mlir/Dialect/Utils/Utils.h" #include +#include +#include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include -#include +#include +#include #include #include #include @@ -40,67 +44,175 @@ using namespace mlir; using namespace mlir::qco; +using namespace mlir::utils; -using DeviceSpec = std::pair; - -/** - * @returns llvm::success() if all two-qubit gates inside @p region - * fulfill the given coupling constraints. llvm::failure(), otherwise. - */ -static LogicalResult isExecutable(Region& region, const Edges& coupling) { - return walkProgram(region, [&](Operation* curr, const Qubits& qubits) { - if (auto op = dyn_cast(curr)) { - if (isa(op)) { - return WalkResult::advance(); - } - - assert(op.getNumQubits() <= 2 && - "isExecutable: expected two-qubit gate decomposition"); - - if (op.getNumQubits() > 1) { - const auto q0 = cast>(op.getInputQubit(0)); - const auto q1 = cast>(op.getInputQubit(1)); - const auto i0 = qubits.getIndex(q0); - const auto i1 = qubits.getIndex(q1); - - if (!coupling.contains(std::make_pair(i0, i1))) { - return WalkResult::interrupt(); - } - } +namespace { +struct Device { + size_t nqubits{}; + DenseSet> couplingSet; +}; +} // namespace + +/// Return true, if the operations within a region fulfill the given coupling +/// constraints. +static bool +isExecutable(Region& body, DenseMap& m, + const DenseSet>& couplingSet) { + for (Operation& rop : body.getOps()) { + bool executable = true; + TypeSwitch(&rop) + .Case( + [&](StaticOp op) { m.try_emplace(op.getQubit(), op.getIndex()); }) + .Case([&](BarrierOp op) { + for (const auto [pred, succ] : + llvm::zip_equal(op.getInputQubits(), op.getOutputQubits())) { + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + }) + .Case([&](UnitaryOpInterface& op) { + assert(op.getNumQubits() <= 2 && "expected two-qubit decomp."); + + if (op.getNumQubits() > 1) { + const auto hwA = m.at(op.getInputQubit(0)); + const auto hwB = m.at(op.getInputQubit(1)); + if (!couplingSet.contains(std::make_pair(hwA, hwB))) { + llvm::dbgs() << "(" << hwA << ", " << hwB << ") " + << "not executable: \n"; + op->dump(); + executable = false; + } + } + + for (const auto [pred, succ] : + llvm::zip_equal(op.getInputQubits(), op.getOutputQubits())) { + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + }) + .Case([&](scf::ForOp forOp) { + DenseMap loopM; + for (const auto [init, arg] : + llvm::zip_equal(forOp.getInits(), forOp.getRegionIterArgs())) { + const auto hw = m.at(init); + loopM.try_emplace(arg, hw); + } + + for (OpOperand& operand : forOp.getInitsMutable()) { + const auto pred = operand.get(); + const auto succ = forOp.getTiedLoopResult(&operand); + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + + if (!isExecutable(forOp.getRegion(), loopM, couplingSet)) { + executable = false; + return; + } + + for (const auto& [arg, yielded] : llvm::zip_equal( + forOp.getRegionIterArgs(), forOp.getYieldedValues())) { + if (loopM.at(arg) != loopM.at(yielded)) { + llvm::dbgs() << "scf::forOp: layout not restored!\n"; + executable = false; + return; + } + } + }) + .Case([&](qco::IfOp ifOp) { + std::array mappings{DenseMap{}, + DenseMap{}}; + + const std::array regions{&ifOp.getThenRegion(), + &ifOp.getElseRegion()}; + + for (size_t i = 0; i < 2; ++i) { + for (const auto [init, arg] : llvm::zip_equal( + ifOp.getQubits(), regions[i]->getArguments())) { + const auto hw = m.at(init); + mappings[i].try_emplace(arg, hw); + } + } + + for (OpOperand& operand : ifOp.getQubitsMutable()) { + const auto pred = operand.get(); + const auto succ = ifOp.getTiedResult(&operand); + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + + std::array, 2> finalPermutation{}; + + for (size_t i = 0; i < 2; ++i) { + Region* body = regions[i]; + if (!isExecutable(*body, mappings[i], couplingSet)) { + executable = false; + return; + } + + Block& block = body->getBlocks().front(); + auto yield = cast(block.getTerminator()); + for (const auto v : yield.getTargets()) { + finalPermutation[i].emplace_back(mappings[i].at(v)); + } + } + + if (finalPermutation[0] != finalPermutation[1]) { + executable = false; + return; + } + }) + .Case([&](auto op) { + const auto pred = op.getQubitIn(); + const auto succ = op.getQubitOut(); + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + }); + + if (!executable) { + return false; } + } - return WalkResult::advance(); - }); + return true; } -/** - * @returns a 9x9 square-grid device. - */ -static DeviceSpec getNineQubitSquareGrid() { - const static Edges COUPLING{{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, - {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, - {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, - {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}; - return std::make_pair(9, COUPLING); +/// Return true, if the entry point fulfills the given coupling constraints. +static bool +isExecutable(func::FuncOp entry, + const DenseSet>& couplingSet) { + DenseMap m; + return isExecutable(entry.getFunctionBody(), m, couplingSet); +} + +/// Return a 9x9 square-grid coupling set. +static Device getNineQubitSquareGrid() { + return {.nqubits = 9, + .couplingSet = {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, + {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, + {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, + {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}}; } namespace { class MappingPassTest : public testing::Test, - public testing::WithParamInterface { + public testing::WithParamInterface { protected: void SetUp() override { DialectRegistry registry; - registry.insert(); + registry.insert(); context = std::make_unique(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); } - static LogicalResult runPass(ModuleOp m, const DeviceSpec& device, - const MappingPassOptions& options) { + static LogicalResult + runPass(ModuleOp m, const DenseSet>& couplingSet, + const MappingPassOptions& options) { PassManager pm(m->getContext()); - pm.addPass(createMappingPass(device.first, device.second, options)); + pm.addPass(createMappingPass(couplingSet, options)); return pm.run(m); } @@ -113,9 +225,7 @@ TEST_P(MappingPassTest, NoEntryPoint) { const auto& device = GetParam(); OwningOpRef m = ModuleOp::create(UnknownLoc::get(context.get())); - - auto res = runPass(m.get(), device, MappingPassOptions{}); - + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); ASSERT_TRUE(res.failed()); } @@ -123,14 +233,17 @@ TEST_P(MappingPassTest, NoQubitAllocations) { const auto& device = GetParam(); QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize({builder.getI1Type()}); - Value q0 = builder.allocQubit(); + Value q0; + Value c0; + q0 = builder.allocQubit(); q0 = builder.h(q0); + std::tie(q0, c0) = builder.measure(q0); builder.sink(q0); - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); + auto m = builder.finalize(c0); + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); ASSERT_TRUE(res.failed()); } @@ -139,181 +252,424 @@ TEST_P(MappingPassTest, NoExtractAfterInsert) { const auto& device = GetParam(); QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize({builder.getI1Type()}); Value tensor0 = builder.qtensorAlloc(1); Value q0; + Value c0; std::tie(tensor0, q0) = builder.qtensorExtract(tensor0, 0); q0 = builder.h(q0); tensor0 = builder.qtensorInsert(q0, tensor0, 0); std::tie(tensor0, q0) = builder.qtensorExtract(tensor0, 0); q0 = builder.x(q0); + std::tie(q0, c0) = builder.measure(q0); tensor0 = builder.qtensorInsert(q0, tensor0, 0); builder.qtensorDealloc(tensor0); - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); + auto m = builder.finalize(c0); + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); ASSERT_TRUE(res.failed()); } TEST_P(MappingPassTest, TooManyQubitsForArch) { const auto& device = GetParam(); + const auto size = static_cast(device.nqubits) + 1; + + SmallVector bits(size); + SmallVector qubits(size); QCOProgramBuilder builder(context.get()); - builder.initialize(); - - int64_t nqubits = static_cast(device.first) + 1; - Value tensor = builder.qtensorAlloc(nqubits); - SmallVector qubits(nqubits); - for (int64_t i = 0; i < nqubits; ++i) { - Value qi; - std::tie(tensor, qi) = builder.qtensorExtract(tensor, i); - qi = builder.h(qi); - qubits[i] = qi; + builder.initialize(SmallVector(size, builder.getI1Type())); + + Value tensor = builder.qtensorAlloc(size); + + for (int64_t i = 0; i < size; ++i) { + std::tie(tensor, qubits[i]) = builder.qtensorExtract(tensor, i); + qubits[i] = builder.h(qubits[i]); + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); } - for (int64_t i = 0; i < nqubits; ++i) { + for (int64_t i = 0; i < size; ++i) { tensor = builder.qtensorInsert(qubits[i], tensor, i); } builder.qtensorDealloc(tensor); - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); + auto m = builder.finalize(bits); + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); ASSERT_TRUE(res.failed()); } TEST_P(MappingPassTest, GHZ) { const auto& device = GetParam(); + const int64_t size = 3; + + SmallVector qubits(size); + SmallVector bits(size); QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize(SmallVector(3, builder.getI1Type())); Value tensor = builder.qtensorAlloc(3); + std::tie(tensor, qubits[0]) = builder.qtensorExtract(tensor, 0); + std::tie(tensor, qubits[1]) = builder.qtensorExtract(tensor, 1); + std::tie(tensor, qubits[2]) = builder.qtensorExtract(tensor, 2); + + qubits[0] = builder.h(qubits[0]); + std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); + std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); + + std::tie(qubits[0], bits[0]) = builder.measure(qubits[0]); + std::tie(qubits[1], bits[1]) = builder.measure(qubits[1]); + std::tie(qubits[2], bits[2]) = builder.measure(qubits[2]); + + tensor = builder.qtensorInsert(qubits[0], tensor, 0); + tensor = builder.qtensorInsert(qubits[1], tensor, 1); + tensor = builder.qtensorInsert(qubits[2], tensor, 2); + + builder.qtensorDealloc(tensor); + + auto m = builder.finalize(bits); + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); + auto entry = getEntryPoint(m.get()); + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} + +TEST_P(MappingPassTest, GHZUnrolled) { + const auto& device = GetParam(); + const auto size = static_cast(device.nqubits); + + SmallVector bits(size); + + PassManager pm(context.get()); + pm.addNestedPass(createQuantumLoopUnroll()); + pm.addPass(createCSEPass()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); + + Value tensor = builder.qtensorAlloc(size); Value q0; std::tie(tensor, q0) = builder.qtensorExtract(tensor, 0); + q0 = builder.h(q0); + tensor = builder.qtensorInsert(q0, tensor, 0); + tensor = builder.scfFor( + 1, size, 1, {tensor}, [&builder](Value iv, ValueRange iterArgs) { + Value loopTensor = iterArgs[0]; + Value ctrl; + Value targ; - Value q1; - std::tie(tensor, q1) = builder.qtensorExtract(tensor, 1); + std::tie(loopTensor, ctrl) = builder.qtensorExtract(loopTensor, 0); + std::tie(loopTensor, targ) = builder.qtensorExtract(loopTensor, iv); - Value q2; - std::tie(tensor, q2) = builder.qtensorExtract(tensor, 2); + std::tie(ctrl, targ) = builder.cx(ctrl, targ); - q0 = builder.h(q0); - std::tie(q0, q1) = builder.cx(q0, q1); - std::tie(q0, q2) = builder.cx(q0, q2); + loopTensor = builder.qtensorInsert(ctrl, loopTensor, 0); + loopTensor = builder.qtensorInsert(targ, loopTensor, iv); + + return SmallVector{loopTensor}; + })[0]; + + for (int64_t i = 0; i < size; ++i) { + Value q; + std::tie(tensor, q) = builder.qtensorExtract(tensor, i); + std::tie(q, bits[i]) = builder.measure(q); + tensor = builder.qtensorInsert(q, tensor, i); + } + + builder.qtensorDealloc(tensor); + + auto m = builder.finalize(bits); + auto res = pm.run(m.get()); + auto entry = getEntryPoint(m.get()); + + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} + +TEST_P(MappingPassTest, GroverLike) { + const auto& device = GetParam(); + const int64_t size = 5; + + SmallVector qubits(size); + SmallVector bits(size); + + PassManager pm(context.get()); + pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(5, builder.getI1Type())); + + Value tensor = builder.qtensorAlloc(4); + Value flagTensor = builder.qtensorAlloc(1); + + std::tie(tensor, qubits[0]) = builder.qtensorExtract(tensor, 0); + std::tie(tensor, qubits[1]) = builder.qtensorExtract(tensor, 1); + std::tie(tensor, qubits[2]) = builder.qtensorExtract(tensor, 2); + std::tie(tensor, qubits[3]) = builder.qtensorExtract(tensor, 3); + std::tie(flagTensor, qubits[4]) = builder.qtensorExtract(flagTensor, 0); + + qubits[0] = builder.h(qubits[0]); + qubits[1] = builder.h(qubits[1]); + qubits[2] = builder.h(qubits[2]); + qubits[3] = builder.h(qubits[3]); + qubits[4] = builder.x(qubits[4]); + + qubits = + builder.scfFor(1, 3, 1, qubits, [&builder](Value, ValueRange iterArgs) { + Value iterQ0 = iterArgs[0]; + Value iterQ1 = iterArgs[1]; + Value iterQ2 = iterArgs[2]; + Value iterQ3 = iterArgs[3]; + Value iterFlag = iterArgs[4]; + + std::tie(iterQ0, iterQ2) = builder.cx(iterQ0, iterQ2); + std::tie(iterQ2, iterQ3) = builder.cx(iterQ2, iterQ3); + std::tie(iterQ3, iterQ0) = builder.cx(iterQ3, iterQ0); + std::tie(iterQ0, iterFlag) = builder.cx(iterQ0, iterFlag); + + return SmallVector{iterQ0, iterQ1, iterQ2, iterQ3, iterFlag}; + }); + qubits = builder.barrier(qubits); + + for (int64_t i = 0; i < size; ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + } + + tensor = builder.qtensorInsert(qubits[0], tensor, 0); + tensor = builder.qtensorInsert(qubits[1], tensor, 1); + tensor = builder.qtensorInsert(qubits[2], tensor, 2); + tensor = builder.qtensorInsert(qubits[3], tensor, 3); + flagTensor = builder.qtensorInsert(qubits[4], flagTensor, 0); - tensor = builder.qtensorInsert(q0, tensor, 0); - tensor = builder.qtensorInsert(q1, tensor, 1); - tensor = builder.qtensorInsert(q2, tensor, 2); builder.qtensorDealloc(tensor); + builder.qtensorDealloc(flagTensor); - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); + auto m = builder.finalize(bits); + auto res = pm.run(m.get()); auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry.getFunctionBody(), device.second).succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} + +TEST_P(MappingPassTest, ParallelLoops) { + const auto& device = GetParam(); + constexpr int64_t size = 6; + + SmallVector qubits(size); + SmallVector bits(size); + + PassManager pm(context.get()); + pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); + + Value tensor = builder.qtensorAlloc(size); + for (int64_t i = 0; i < size; ++i) { + std::tie(tensor, qubits[i]) = builder.qtensorExtract(tensor, i); + qubits[i] = builder.h(qubits[i]); + } + + const auto upForResults = + builder.scfFor(1, 3, 1, {qubits[0], qubits[1], qubits[2]}, + [&builder](Value, ValueRange iterArgs) { + Value iterQ0 = iterArgs[0]; + Value iterQ1 = iterArgs[1]; + Value iterQ2 = iterArgs[2]; + + std::tie(iterQ0, iterQ1) = builder.cx(iterQ0, iterQ1); + iterQ0 = builder.h(iterQ0); + std::tie(iterQ0, iterQ1) = builder.cz(iterQ0, iterQ1); + std::tie(iterQ1, iterQ2) = builder.cz(iterQ1, iterQ2); + std::tie(iterQ0, iterQ2) = builder.cx(iterQ0, iterQ2); + + return SmallVector{iterQ0, iterQ1, iterQ2}; + }); + + qubits[0] = upForResults[0]; + qubits[1] = upForResults[1]; + qubits[2] = upForResults[2]; + + const auto downForResults = + builder.scfFor(1, 3, 1, {qubits[3], qubits[4], qubits[5]}, + [&builder](Value, ValueRange iterArgs) { + Value iterQ0 = iterArgs[0]; + Value iterQ1 = iterArgs[1]; + Value iterQ2 = iterArgs[2]; + + std::tie(iterQ0, iterQ1) = builder.cx(iterQ0, iterQ1); + iterQ0 = builder.h(iterQ0); + std::tie(iterQ1, iterQ2) = builder.cz(iterQ1, iterQ2); + std::tie(iterQ0, iterQ1) = builder.cz(iterQ0, iterQ1); + std::tie(iterQ0, iterQ2) = builder.cx(iterQ0, iterQ2); + + return SmallVector{iterQ0, iterQ1, iterQ2}; + }); + + qubits[3] = downForResults[0]; + qubits[4] = downForResults[1]; + qubits[5] = downForResults[2]; + + qubits = builder.barrier(qubits); + + for (int64_t i = 0; i < size; ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + qubits[i] = builder.h(qubits[i]); + } + + for (int64_t i = 0; i < size; ++i) { + tensor = builder.qtensorInsert(qubits[i], tensor, i); + } + + builder.qtensorDealloc(tensor); + + auto m = builder.finalize(bits); + auto res = pm.run(m.get()); + auto entry = getEntryPoint(m.get()); + + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } TEST_P(MappingPassTest, Sabre) { const auto& device = GetParam(); + constexpr int64_t size = 6; + + SmallVector qubits(size); + SmallVector bits(size); QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize(SmallVector(6, builder.getI1Type())); Value tensorUp = builder.qtensorAlloc(4); Value tensorDown = builder.qtensorAlloc(2); - Value q0; - std::tie(tensorUp, q0) = builder.qtensorExtract(tensorUp, 0); + std::tie(tensorUp, qubits[0]) = builder.qtensorExtract(tensorUp, 0); + std::tie(tensorUp, qubits[1]) = builder.qtensorExtract(tensorUp, 1); + std::tie(tensorUp, qubits[2]) = builder.qtensorExtract(tensorUp, 2); + std::tie(tensorUp, qubits[3]) = builder.qtensorExtract(tensorUp, 3); + std::tie(tensorDown, qubits[4]) = builder.qtensorExtract(tensorDown, 0); + std::tie(tensorDown, qubits[5]) = builder.qtensorExtract(tensorDown, 1); - Value q1; - std::tie(tensorUp, q1) = builder.qtensorExtract(tensorUp, 1); + qubits[0] = builder.h(qubits[0]); + qubits[1] = builder.h(qubits[1]); + qubits[4] = builder.h(qubits[4]); - Value q2; - std::tie(tensorUp, q2) = builder.qtensorExtract(tensorUp, 2); + qubits[0] = builder.z(qubits[0]); + std::tie(qubits[1], qubits[2]) = builder.cx(qubits[1], qubits[2]); + std::tie(qubits[4], qubits[5]) = builder.cx(qubits[4], qubits[5]); - Value q3; - std::tie(tensorUp, q3) = builder.qtensorExtract(tensorUp, 3); + std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); - Value q4; - std::tie(tensorDown, q4) = builder.qtensorExtract(tensorDown, 0); + qubits[0] = builder.h(qubits[0]); + qubits[1] = builder.y(qubits[1]); + std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); - Value q5; - std::tie(tensorDown, q5) = builder.qtensorExtract(tensorDown, 1); + std::tie(qubits[2], qubits[3]) = builder.cx(qubits[2], qubits[3]); - q0 = builder.h(q0); - q1 = builder.h(q1); - q4 = builder.h(q4); + qubits[2] = builder.h(qubits[2]); + qubits[3] = builder.h(qubits[3]); - q0 = builder.z(q0); - std::tie(q1, q2) = builder.cx(q1, q2); - std::tie(q4, q5) = builder.cx(q4, q5); + std::tie(qubits[1], qubits[2]) = builder.cx(qubits[1], qubits[2]); + std::tie(qubits[3], qubits[5]) = builder.cx(qubits[3], qubits[5]); - std::tie(q0, q1) = builder.cx(q0, q1); + qubits[3] = builder.z(qubits[3]); - q0 = builder.h(q0); - q1 = builder.y(q1); - std::tie(q0, q1) = builder.cx(q0, q1); + std::tie(qubits[3], qubits[4]) = builder.cx(qubits[3], qubits[4]); - std::tie(q2, q3) = builder.cx(q2, q3); + std::tie(qubits[3], qubits[0]) = builder.cx(qubits[3], qubits[0]); - q2 = builder.h(q2); - q3 = builder.h(q3); + qubits = builder.barrier(qubits); - std::tie(q1, q2) = builder.cx(q1, q2); - std::tie(q3, q5) = builder.cx(q3, q5); + for (int64_t i = 0; i < size; ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + } - q3 = builder.z(q3); + tensorUp = builder.qtensorInsert(qubits[0], tensorUp, 0); + tensorUp = builder.qtensorInsert(qubits[1], tensorUp, 1); + tensorUp = builder.qtensorInsert(qubits[2], tensorUp, 2); + tensorUp = builder.qtensorInsert(qubits[3], tensorUp, 3); + tensorDown = builder.qtensorInsert(qubits[4], tensorDown, 0); + tensorDown = builder.qtensorInsert(qubits[5], tensorDown, 1); + builder.qtensorDealloc(tensorUp); + builder.qtensorDealloc(tensorDown); - std::tie(q3, q4) = builder.cx(q3, q4); + auto m = builder.finalize(bits); + auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); + auto entry = getEntryPoint(m.get()); + + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} - std::tie(q3, q0) = builder.cx(q3, q0); +TEST_P(MappingPassTest, RandomOrderGHZ) { + const auto& device = GetParam(); + constexpr int64_t size = 9; - ValueRange out = builder.barrier({q0, q1, q2, q3, q4, q5}); - q0 = out[0]; - q1 = out[1]; - q2 = out[2]; - q3 = out[3]; - q4 = out[4]; - q5 = out[5]; + SmallVector qubits(size); + SmallVector bits(size); - Value c0; - Value c1; - Value c2; - Value c3; - Value c4; - Value c5; + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); - std::tie(q0, c0) = builder.measure(q0); - std::tie(q1, c1) = builder.measure(q1); - std::tie(q2, c2) = builder.measure(q2); - std::tie(q3, c3) = builder.measure(q3); - std::tie(q4, c4) = builder.measure(q4); - std::tie(q5, c5) = builder.measure(q5); - - tensorUp = builder.qtensorInsert(q0, tensorUp, 0); - tensorUp = builder.qtensorInsert(q1, tensorUp, 1); - tensorUp = builder.qtensorInsert(q2, tensorUp, 2); - tensorUp = builder.qtensorInsert(q3, tensorUp, 3); - tensorDown = builder.qtensorInsert(q4, tensorDown, 0); - tensorDown = builder.qtensorInsert(q5, tensorDown, 1); - builder.qtensorDealloc(tensorUp); - builder.qtensorDealloc(tensorDown); + Value tensor = builder.qtensorAlloc(size); + for (int64_t i = 0; i < size; ++i) { + std::tie(tensor, qubits[i]) = builder.qtensorExtract(tensor, i); + } + + qubits[0] = builder.h(qubits[0]); + std::tie(qubits[0], bits[0]) = builder.measure(qubits[0]); + + qubits = builder.qcoIf( + bits[0], qubits, + [&](ValueRange args) { + SmallVector values(args); + values[0] = builder.h(values[0]); + for (size_t i = 1; i < size; ++i) { + std::tie(values[0], values[i]) = builder.cx(values[0], values[i]); + } + return values; + }, + [&](ValueRange args) { + SmallVector values(args); + values[size - 1] = builder.h(values[size - 1]); + for (size_t i = size - 1; i > 0; --i) { + std::tie(values[8], values[i - 1]) = + builder.cx(values[8], values[i - 1]); + } + return values; + }); + + qubits = builder.barrier(qubits); + + for (int64_t i = 0; i < size; ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + } + + for (int64_t i = 0; i < size; ++i) { + tensor = builder.qtensorInsert(qubits[i], tensor, i); + } + + builder.qtensorDealloc(tensor); - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); + auto m = builder.finalize(bits); + auto res = + runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry.getFunctionBody(), device.second).succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 207363c98b..e96e6ccfd1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -48,7 +49,13 @@ using namespace mlir; using namespace mlir::qco; -using ProgramFn = void (*)(mlir::qc::QCProgramBuilder&); +using ProgramFn = SmallVector (*)(mlir::qc::QCProgramBuilder&); + +static SmallVector measureAndReturn(mlir::qc::QCProgramBuilder& b, + ValueRange qubits) { + return llvm::to_vector( + llvm::map_range(qubits, [&](Value q) { return b.measure(q); })); +} // --- Native-gateset membership check ------------------------------------- // @@ -246,14 +253,15 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { // A handful of circuits, which are crossed with the gateset table below. /// A bare SWAP (three-entangler class), the canonical two-qubit decomposition. -static void swapTwoQ(mlir::qc::QCProgramBuilder& b) { +static SmallVector swapTwoQ(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.swap(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// Rich single-qubit variety on both wires, followed by a two-qubit entangler. -static void broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { +static SmallVector broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.x(q0); @@ -264,10 +272,11 @@ static void broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { b.ry(-0.47, q1); b.rz(0.29, q0); b.cz(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// Long single-qubit run on one wire, then an entangler (Euler-run fusion). -static void hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { +static SmallVector hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.h(q0); @@ -275,10 +284,11 @@ static void hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { b.t(q0); b.y(q0); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// Zero-angle rotations that must canonicalize away before the entangler. -static void zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { +static SmallVector zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.rx(0.0, q0); @@ -286,44 +296,49 @@ static void zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { b.rz(0.0, q0); b.p(0.0, q1); b.cz(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// Single-qubit gates surrounding an entangler on both sides. -static void hCxSq1(mlir::qc::QCProgramBuilder& b) { +static SmallVector hCxSq1(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.h(q0); b.cx(q0, q1); b.s(q1); + return measureAndReturn(b, {q0, q1}); } /// Three-qubit program with chained entanglers on overlapping pairs. -static void threeQGhz(mlir::qc::QCProgramBuilder& b) { +static SmallVector threeQGhz(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); const auto q2 = b.allocQubit(); b.h(q0); b.cx(q0, q1); b.cx(q1, q2); + return measureAndReturn(b, {q0, q1, q2}); } /// Single-qubit gates wrapped in an inverse modifier (no entangler). -static void inverseTwoX(mlir::qc::QCProgramBuilder& b) { +static SmallVector inverseTwoX(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); - b.inv({q0}, [&](mlir::ValueRange qubits) { - b.x(qubits[0]); - b.x(qubits[0]); + b.inv(q0, [&](Value qubit) { + b.x(qubit); + b.x(qubit); }); + return measureAndReturn(b, {q0}); } /// A controlled two-gate body that must be synthesized as a two-qubit unitary. -static void controlledXH(mlir::qc::QCProgramBuilder& b) { +static SmallVector controlledXH(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); - b.ctrl(q0, {q1}, [&](ValueRange targets) { - b.x(targets[0]); - b.h(targets[0]); + b.ctrl(q0, q1, [&](Value target) { + b.x(target); + b.h(target); }); + return measureAndReturn(b, {q0, q1}); } // --- Fusion-window circuits ---------------------------------------------- // @@ -331,14 +346,16 @@ static void controlledXH(mlir::qc::QCProgramBuilder& b) { // These probe window geometry (where fusion starts/stops), so they run on a // single fixed gateset rather than the full table. -static void fusionCxCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector fusionCxCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -static void fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.h(q0); @@ -346,88 +363,103 @@ static void fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { b.t(q1); b.s(q0); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -static void fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); const auto q2 = b.allocQubit(); b.cx(q0, q1); b.cx(q1, q2); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1, q2}); } -static void fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); const auto q2 = b.allocQubit(); b.cx(q0, q1); b.rz(0.17, q1); b.cx(q1, q2); + return measureAndReturn(b, {q0, q1, q2}); } -static void fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.barrier({q0, q1}); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// A single-wire barrier between two entanglers: a non-walkable single-qubit /// shell terminates the run scan on wire A (and breaks the run-start chain of /// the second entangler), so neither entangler fuses. -static void fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.barrier({q0}); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } /// An entangler followed by a three-qubit gate sharing both run wires: the run /// scan must stop at the wider gate (it is neither a single- nor a two-qubit /// run member) and leave it untouched, since gates on more than two qubits are /// out of scope for this pass rather than a failure. -static void fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); const auto q2 = b.allocQubit(); b.cx(q0, q1); b.mcx({q0, q1}, q2); + return measureAndReturn(b, {q0, q1, q2}); } -static void fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { +static SmallVector fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.cx(q1, q0); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -static void fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.h(q0); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -static void fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { +static SmallVector +fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.cx(q0, q1); b.rz(0.11, q0); b.ry(0.22, q1); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -static void determinismSwap(mlir::qc::QCProgramBuilder& b) { +static Value determinismSwap(mlir::qc::QCProgramBuilder& b) { const auto q0 = b.allocQubit(); const auto q1 = b.allocQubit(); b.swap(q0, q1); b.dealloc(q0); b.dealloc(q1); + return b.intConstant(0); } namespace { @@ -537,6 +569,17 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { EXPECT_TRUE(failed(pm.run(*moduleOp))); } + void expectSynthesisFailure(Value (*program)(mlir::qc::QCProgramBuilder&), + StringRef nativeGates) { + auto moduleOp = mlir::qc::QCProgramBuilder::build(context.get(), program); + PassManager pm(moduleOp->getContext()); + pm.addPass(createQCToQCO()); + pm.addPass(createFuseTwoQubitUnitaryRuns(FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = nativeGates.str(), + })); + EXPECT_TRUE(failed(pm.run(*moduleOp))); + } + void expectTwoQFusePreservesUnitary(ProgramFn program, StringRef nativeGates) { auto expected = mlir::qc::QCProgramBuilder::build(context.get(), program); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp index 616acd3a58..5653f4f36c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp @@ -311,11 +311,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { auto q = programBuilder.allocQubitRegister(3); const auto b = programBuilder.allocClassicalBitRegister(1); - auto [q12, q0] = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; - }); - q[1] = programBuilder.h(q0[0]); + auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, q[0], [&](Value target) { + return programBuilder.x(target); + }); + q[1] = programBuilder.h(q0); programBuilder.measure(q[1], b[0]); module = programBuilder.finalize(); @@ -323,11 +322,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { const auto bRef = referenceBuilder.allocClassicalBitRegister(1); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); - auto [q02Ref, q1Ref] = referenceBuilder.ctrl( - {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; + auto [q02Ref, q1Ref] = + referenceBuilder.ctrl({qRef[0], qRef[2]}, qRef[1], [&](Value target) { + return referenceBuilder.x(target); }); - referenceBuilder.h(q1Ref[0]); + referenceBuilder.h(q1Ref); referenceBuilder.measure(q02Ref[0], bRef[0]); reference = referenceBuilder.finalize(); @@ -388,11 +387,10 @@ TEST_F(QCOHadamardLiftingTest, q[0] = programBuilder.h(q0); programBuilder.measure(q[0], b[0]); programBuilder.measure(q1, b[1]); - auto [q34, q2] = - programBuilder.ctrl({q[3], q[4]}, {q[2]}, [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; - }); - q[2] = programBuilder.h(q2[0]); + auto [q34, q2] = programBuilder.ctrl({q[3], q[4]}, q[2], [&](Value target) { + return programBuilder.x(target); + }); + q[2] = programBuilder.h(q2); programBuilder.measure(q[2], b[2]); programBuilder.measure(q34[0], b[3]); programBuilder.s(q34[1]); @@ -406,11 +404,11 @@ TEST_F(QCOHadamardLiftingTest, referenceBuilder.measure(qRef1, bRef[1]); qRef[2] = referenceBuilder.h(qRef[2]); qRef[4] = referenceBuilder.h(qRef[4]); - auto [qRef32, qRef4] = referenceBuilder.ctrl( - {qRef[3], qRef[2]}, {qRef[4]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; + auto [qRef32, qRef4] = + referenceBuilder.ctrl({qRef[3], qRef[2]}, qRef[4], [&](Value target) { + return referenceBuilder.x(target); }); - qRef[4] = referenceBuilder.h(qRef4[0]); + qRef[4] = referenceBuilder.h(qRef4); referenceBuilder.measure(qRef32[1], bRef[2]); referenceBuilder.measure(qRef32[0], bRef[3]); referenceBuilder.s(qRef[4]); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp index c9f84fefd0..7bbc2437dd 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp @@ -12,7 +12,6 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Utils/Drivers.h" -#include "mlir/Dialect/QCO/Utils/Qubits.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include @@ -50,58 +49,6 @@ class DriversTest : public testing::Test { }; } // namespace -TEST_F(DriversTest, ProgramWalk) { - qco::QCOProgramBuilder builder(context.get()); - builder.initialize(); - const auto q00 = builder.allocQubit(); - const auto q10 = builder.allocQubit(); - const auto q20 = builder.allocQubit(); - const auto q30 = builder.allocQubit(); - - const auto q01 = builder.h(q00); - const auto [q02, q11] = builder.cx(q01, q10); - const auto [q21, q31] = builder.cx(q20, q30); - - const auto [q03, c0] = builder.measure(q02); - const auto [q12, c1] = builder.measure(q11); - const auto [q22, c2] = builder.measure(q21); - const auto [q32, c3] = builder.measure(q31); - - builder.sink(q03); - builder.sink(q12); - builder.sink(q22); - builder.sink(q32); - - auto mod = builder.finalize(); - auto func = *(mod->getOps().begin()); - - Value ex0 = nullptr; - Value ex1 = nullptr; - Value ex2 = nullptr; - Value ex3 = nullptr; - - // Walk until the first measurement operation is encountered and stop. - // Since WalkOrder::PreOrder is used here, the state of the qubits is not yet - // updated with the SSA values of the measurement op. - // Consequently, the program qubits point at the outputs of the controlled-Xs. - std::ignore = qco::walkProgram(func.getBody(), - [&](Operation* op, const qco::Qubits& qubits) { - if (op == q03.getDefiningOp()) { - ex0 = qubits.getProgramQubit(0); - ex1 = qubits.getProgramQubit(1); - ex2 = qubits.getProgramQubit(2); - ex3 = qubits.getProgramQubit(3); - return WalkResult::interrupt(); - } - return WalkResult::advance(); - }); - - ASSERT_EQ(ex0, q02); - ASSERT_EQ(ex1, q11); - ASSERT_EQ(ex2, q21); - ASSERT_EQ(ex3, q31); -} - TEST_F(DriversTest, ProgramGraphWalkTooFewWires) { qco::QCOProgramBuilder builder(context.get()); builder.initialize(); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index 618daff0db..4e5e1e4857 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -43,9 +43,9 @@ static_assert(!SupportedMatrix); 0, 0, 0, 1); // row 3 } -[[nodiscard]] static DynamicMatrix cyclicPermutation(const std::int64_t dim) { +[[nodiscard]] static DynamicMatrix cyclicPermutation(const int64_t dim) { DynamicMatrix matrix(dim); - for (std::int64_t row = 0; row < dim; ++row) { + for (int64_t row = 0; row < dim; ++row) { matrix(row, (row + 1) % dim) = 1.0; } return matrix; @@ -66,7 +66,7 @@ static void verifyMatrix2x2FixedMatchesDynamic() { ASSERT_TRUE(dynamic.has_value()); EXPECT_TRUE(DynamicMatrix(fixed->eigenvectors) .isApprox(dynamic->eigenvectors, MATRIX_TOLERANCE)); - for (std::size_t i = 0; i < 2; ++i) { + for (size_t i = 0; i < 2; ++i) { EXPECT_TRUE(complexesAreApprox(fixed->eigenvalues[i], dynamic->eigenvalues[i], MATRIX_TOLERANCE)); } @@ -83,7 +83,7 @@ static void verifyMatrix4x4FixedMatchesDynamic() { ASSERT_TRUE(dynamic.has_value()); EXPECT_TRUE(DynamicMatrix(fixed->eigenvectors) .isApprox(dynamic->eigenvectors, MATRIX_TOLERANCE)); - for (std::size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < 4; ++i) { EXPECT_TRUE(complexesAreApprox(fixed->eigenvalues[i], dynamic->eigenvalues[i], MATRIX_TOLERANCE)); } @@ -93,17 +93,16 @@ static void verifyMatrix4x4FixedMatchesDynamic() { eigenpairsSatisfy(const DynamicMatrix& matrix, const EigenDecomposition& decomposition, const double tol = MATRIX_TOLERANCE) { - const std::int64_t dim = matrix.rows(); - for (std::size_t colIdx = 0; colIdx < decomposition.eigenvalues.size(); - ++colIdx) { + const int64_t dim = matrix.rows(); + for (size_t colIdx = 0; colIdx < decomposition.eigenvalues.size(); ++colIdx) { DynamicMatrix eigenvector(dim); - for (std::int64_t row = 0; row < dim; ++row) { + for (int64_t row = 0; row < dim; ++row) { eigenvector(row, 0) = - decomposition.eigenvectors(row, static_cast(colIdx)); + decomposition.eigenvectors(row, static_cast(colIdx)); } const DynamicMatrix image = matrix * eigenvector; const Complex lambda = decomposition.eigenvalues[colIdx]; - for (std::int64_t row = 0; row < dim; ++row) { + for (int64_t row = 0; row < dim; ++row) { if (!complexesAreApprox(image(row, 0), lambda * eigenvector(row, 0), tol)) { return false; @@ -115,9 +114,9 @@ eigenpairsSatisfy(const DynamicMatrix& matrix, [[nodiscard]] static double matrixFrobeniusNorm(const DynamicMatrix& matrix) { double sumSq = 0.0; - const std::int64_t dim = matrix.rows(); - for (std::int64_t row = 0; row < dim; ++row) { - for (std::int64_t col = 0; col < dim; ++col) { + const int64_t dim = matrix.rows(); + for (int64_t row = 0; row < dim; ++row) { + for (int64_t col = 0; col < dim; ++col) { sumSq += std::norm(matrix(row, col)); } } @@ -127,18 +126,17 @@ eigenpairsSatisfy(const DynamicMatrix& matrix, [[nodiscard]] static double maxEigenpairResidual(const DynamicMatrix& matrix, const EigenDecomposition& decomposition) { - const std::int64_t dim = matrix.rows(); + const int64_t dim = matrix.rows(); double worst = 0.0; - for (std::size_t colIdx = 0; colIdx < decomposition.eigenvalues.size(); - ++colIdx) { + for (size_t colIdx = 0; colIdx < decomposition.eigenvalues.size(); ++colIdx) { DynamicMatrix eigenvector(dim); - for (std::int64_t row = 0; row < dim; ++row) { + for (int64_t row = 0; row < dim; ++row) { eigenvector(row, 0) = - decomposition.eigenvectors(row, static_cast(colIdx)); + decomposition.eigenvectors(row, static_cast(colIdx)); } const DynamicMatrix image = matrix * eigenvector; const Complex lambda = decomposition.eigenvalues[colIdx]; - for (std::int64_t row = 0; row < dim; ++row) { + for (int64_t row = 0; row < dim; ++row) { worst = std::max(worst, std::abs(image(row, 0) - lambda * eigenvector(row, 0))); } @@ -148,8 +146,7 @@ maxEigenpairResidual(const DynamicMatrix& matrix, [[nodiscard]] static double generalEigenpairTolerance(const DynamicMatrix& matrix) { - const auto dim = - static_cast(std::max(matrix.rows(), 1)); + const auto dim = static_cast(std::max(matrix.rows(), 1)); const double scale = std::max(1.0, matrixFrobeniusNorm(matrix)); // EISPACK backward error scales with dimension and ||A||_F. return std::max(1e-10, 1e-11 * dim * scale); @@ -162,7 +159,7 @@ static void expectEigenDecomposition(const DynamicMatrix& matrix) { << "eigenDecomposition failed for " << matrix.rows() << "x" << matrix.cols(); ASSERT_EQ(decomposition->eigenvalues.size(), - static_cast(matrix.rows())); + static_cast(matrix.rows())); ASSERT_EQ(decomposition->eigenvectors.rows(), matrix.rows()); ASSERT_EQ(decomposition->eigenvectors.cols(), matrix.cols()); for (const Complex& eigenvalue : decomposition->eigenvalues) { @@ -178,7 +175,7 @@ static void expectGeneralEigenDecomposition(const DynamicMatrix& matrix) { << "eigenDecomposition failed for " << matrix.rows() << "x" << matrix.cols(); ASSERT_EQ(decomposition->eigenvalues.size(), - static_cast(matrix.rows())); + static_cast(matrix.rows())); ASSERT_EQ(decomposition->eigenvectors.rows(), matrix.rows()); ASSERT_EQ(decomposition->eigenvectors.cols(), matrix.cols()); const double tol = generalEigenpairTolerance(matrix); @@ -193,21 +190,21 @@ static void expectGeneralEigenDecomposition(const DynamicMatrix& matrix) { EXPECT_TRUE(complexesAreApprox(matrix.trace(), eigenTrace, tol)); } -[[nodiscard]] static DynamicMatrix randomComplexMatrix(const std::int64_t dim, +[[nodiscard]] static DynamicMatrix randomComplexMatrix(const int64_t dim, std::mt19937& rng) { std::uniform_real_distribution dist(-3.0, 3.0); DynamicMatrix matrix(dim); - for (std::int64_t row = 0; row < dim; ++row) { - for (std::int64_t col = 0; col < dim; ++col) { + for (int64_t row = 0; row < dim; ++row) { + for (int64_t col = 0; col < dim; ++col) { matrix(row, col) = Complex{dist(rng), dist(rng)}; } } return matrix; } -[[nodiscard]] static DynamicMatrix diagonalUnitary(const std::int64_t dim) { +[[nodiscard]] static DynamicMatrix diagonalUnitary(const int64_t dim) { DynamicMatrix matrix(dim); - for (std::int64_t i = 0; i < dim; ++i) { + for (int64_t i = 0; i < dim; ++i) { matrix(i, i) = std::exp(1i * (0.1 * static_cast(i))); } return matrix; @@ -417,6 +414,49 @@ TEST(DynamicMatrix, ScalarMultiplyAssign) { std::exp(Complex{0.0, 0.5}))); } +TEST(DynamicMatrix, PremultiplyBy) { + const DynamicMatrix x(pauliX()); + const auto y = + DynamicMatrix(Matrix2x2::fromElements(1, 0, 0, std::exp(1i * 0.5))); + DynamicMatrix acc = DynamicMatrix::identity(2); + acc.premultiplyBy(x); + acc.premultiplyBy(y); + EXPECT_TRUE(acc.isApprox(y * x)); +} + +TEST(DynamicMatrix, PremultiplyByEmbeddedMatchesDense) { + const Matrix2x2 x = pauliX(); + const Matrix4x4 swap = swapMatrix(); + for (const size_t numQubits : {2U, 3U}) { + for (size_t wire = 0; wire < numQubits; ++wire) { + DynamicMatrix dense = + DynamicMatrix::identity(static_cast(1) << numQubits); + dense.premultiplyBy(x.embedInNqubit(numQubits, wire)); + DynamicMatrix structured = + DynamicMatrix::identity(static_cast(1) << numQubits); + structured.premultiplyByEmbedded1Q(x, numQubits, wire); + EXPECT_TRUE(dense.isApprox(structured)); + } + } + for (const std::array wires : + {std::array{0, 1}, std::array{0, 2}, + std::array{1, 2}}) { + constexpr size_t numQubits = 3; + DynamicMatrix dense = + DynamicMatrix::identity(static_cast(1) << numQubits); + dense.premultiplyBy(swap.embedInNqubit(numQubits, wires[0], wires[1])); + DynamicMatrix structured = + DynamicMatrix::identity(static_cast(1) << numQubits); + structured.premultiplyByEmbedded2Q(swap, numQubits, wires[0], wires[1]); + EXPECT_TRUE(dense.isApprox(structured)); + } + DynamicMatrix dense2 = DynamicMatrix::identity(4); + dense2.premultiplyBy(swap.embedInNqubit(2, 1, 0)); + DynamicMatrix structured2 = DynamicMatrix::identity(4); + structured2.premultiplyByEmbedded2Q(swap.reorderForQubits(1, 0), 2, 0, 1); + EXPECT_TRUE(dense2.isApprox(structured2)); +} + TEST(DynamicMatrix, AssignFrom) { DynamicMatrix dynamic; @@ -744,7 +784,7 @@ TEST(SymmetricEigensolver, Matrix4x4Overload) { Matrix4x4::fromRealRowMajor(a).symmetricEigenDecomposition(); const SymmetricEigenDecomposition4x4 fromMatrix = matrix.symmetricEigenDecomposition(); - for (std::size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < 4; ++i) { EXPECT_NEAR(fromMatrix.eigenvalues[i], fromArray.eigenvalues[i], MATRIX_TOLERANCE); } @@ -756,8 +796,8 @@ TEST(SymmetricEigensolver, ReconstructsRandomSymmetric) { std::uniform_real_distribution dist(-2.0, 2.0); for (int trial = 0; trial < 50; ++trial) { std::array a{}; - for (std::size_t i = 0; i < 4; ++i) { - for (std::size_t j = i; j < 4; ++j) { + for (size_t i = 0; i < 4; ++i) { + for (size_t j = i; j < 4; ++j) { const double value = dist(rng); a[(i * 4) + j] = value; a[(j * 4) + i] = value; @@ -767,7 +807,7 @@ TEST(SymmetricEigensolver, ReconstructsRandomSymmetric) { Matrix4x4::fromRealRowMajor(a).symmetricEigenDecomposition(); // Eigenvalues are ascending. - for (std::size_t i = 0; i + 1 < 4; ++i) { + for (size_t i = 0; i + 1 < 4; ++i) { EXPECT_LE(result.eigenvalues[i], result.eigenvalues[i + 1] + MATRIX_TOLERANCE); } @@ -794,7 +834,7 @@ TEST(SymmetricEigensolver, HandlesDegenerateSpectrum) { // A scalar multiple of the identity: every vector is an eigenvector, but the // returned basis must still be orthonormal. std::array a{}; - for (std::size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < 4; ++i) { a[(i * 4) + i] = 2.5; } const SymmetricEigenDecomposition4x4 result = @@ -951,7 +991,7 @@ TEST(SymmetricEigensolver, SparseCornerElement) { const SymmetricEigenDecomposition4x4 fromArray = Matrix4x4::fromRealRowMajor(sparse).symmetricEigenDecomposition(); EXPECT_NEAR(fromArray.eigenvalues[3], 7.0, MATRIX_TOLERANCE); - for (std::size_t i = 0; i + 1 < 4; ++i) { + for (size_t i = 0; i + 1 < 4; ++i) { EXPECT_NEAR(fromArray.eigenvalues[i], 0.0, MATRIX_TOLERANCE); } } diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index 765b249a41..da6b6e25d0 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,8 @@ namespace { struct QIRTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QIRTestCase& info); }; @@ -66,8 +67,8 @@ TEST_P(QIRTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = QIRProgramBuilder::build(context.get(), programBuilder.fn, - QIRProgramBuilder::Profile::Adaptive); + auto program = mqt::test::buildMLIRProgram( + context.get(), programBuilder, QIRProgramBuilder::Profile::Adaptive); ASSERT_TRUE(program); printer.record(program.get(), "Original QIR IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -76,8 +77,8 @@ TEST_P(QIRTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized QIR IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = QIRProgramBuilder::build( - context.get(), referenceBuilder.fn, QIRProgramBuilder::Profile::Adaptive); + auto reference = mqt::test::buildMLIRProgram( + context.get(), referenceBuilder, QIRProgramBuilder::Profile::Adaptive); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QIR IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); @@ -112,36 +113,36 @@ TEST_F(QIRTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) { /// @{ INSTANTIATE_TEST_SUITE_P( QIRDCXOpTest, QIRTest, - testing::Values(QIRTestCase{"DCX", MQT_NAMED_BUILDER(dcx), - MQT_NAMED_BUILDER(dcx)}, + testing::Values(QIRTestCase{"DCX", MQT_NAMED_BUILDER(dcx<>), + MQT_NAMED_BUILDER(dcx<>)}, QIRTestCase{"SingleControlledDCX", - MQT_NAMED_BUILDER(singleControlledDcx), - MQT_NAMED_BUILDER(singleControlledDcx)}, + MQT_NAMED_BUILDER(singleControlledDcx<>), + MQT_NAMED_BUILDER(singleControlledDcx<>)}, QIRTestCase{"MultipleControlledDCX", - MQT_NAMED_BUILDER(multipleControlledDcx), - MQT_NAMED_BUILDER(multipleControlledDcx)})); + MQT_NAMED_BUILDER(multipleControlledDcx<>), + MQT_NAMED_BUILDER(multipleControlledDcx<>)})); /// @} /// \name QIR/Operations/StandardGates/EcrOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRECROpTest, QIRTest, - testing::Values(QIRTestCase{"ECR", MQT_NAMED_BUILDER(ecr), - MQT_NAMED_BUILDER(ecr)}, + testing::Values(QIRTestCase{"ECR", MQT_NAMED_BUILDER(ecr<>), + MQT_NAMED_BUILDER(ecr<>)}, QIRTestCase{"SingleControlledECR", - MQT_NAMED_BUILDER(singleControlledEcr), - MQT_NAMED_BUILDER(singleControlledEcr)}, + MQT_NAMED_BUILDER(singleControlledEcr<>), + MQT_NAMED_BUILDER(singleControlledEcr<>)}, QIRTestCase{"MultipleControlledECR", - MQT_NAMED_BUILDER(multipleControlledEcr), - MQT_NAMED_BUILDER(multipleControlledEcr)})); + MQT_NAMED_BUILDER(multipleControlledEcr<>), + MQT_NAMED_BUILDER(multipleControlledEcr<>)})); /// @} /// \name QIR/Operations/StandardGates/GphaseOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P(QIRGPhaseOpTest, QIRTest, testing::Values(QIRTestCase{ - "GlobalPhase", MQT_NAMED_BUILDER(globalPhase), - MQT_NAMED_BUILDER(globalPhase)})); + "GlobalPhase", MQT_NAMED_BUILDER(globalPhase<>), + MQT_NAMED_BUILDER(globalPhase<>)})); /// @} /// \name QIR/Operations/StandardGates/HOp.cpp @@ -149,41 +150,41 @@ INSTANTIATE_TEST_SUITE_P(QIRGPhaseOpTest, QIRTest, INSTANTIATE_TEST_SUITE_P( QIRHOpTest, QIRTest, testing::Values( - QIRTestCase{"H", MQT_NAMED_BUILDER(h), MQT_NAMED_BUILDER(h)}, - QIRTestCase{"SingleControlledH", MQT_NAMED_BUILDER(singleControlledH), - MQT_NAMED_BUILDER(singleControlledH)}, + QIRTestCase{"H", MQT_NAMED_BUILDER(h<>), MQT_NAMED_BUILDER(h<>)}, + QIRTestCase{"SingleControlledH", MQT_NAMED_BUILDER(singleControlledH<>), + MQT_NAMED_BUILDER(singleControlledH<>)}, QIRTestCase{"MultipleControlledH", - MQT_NAMED_BUILDER(multipleControlledH), - MQT_NAMED_BUILDER(multipleControlledH)})); + MQT_NAMED_BUILDER(multipleControlledH<>), + MQT_NAMED_BUILDER(multipleControlledH<>)})); /// @} /// \name QIR/Operations/StandardGates/IdOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRIDOpTest, QIRTest, - testing::Values(QIRTestCase{"Identity", MQT_NAMED_BUILDER(identity), - MQT_NAMED_BUILDER(identity)}, + testing::Values(QIRTestCase{"Identity", MQT_NAMED_BUILDER(identity<>), + MQT_NAMED_BUILDER(identity<>)}, QIRTestCase{"SingleControlledIdentity", - MQT_NAMED_BUILDER(singleControlledIdentity), - MQT_NAMED_BUILDER(singleControlledIdentity)}, + MQT_NAMED_BUILDER(singleControlledIdentity<>), + MQT_NAMED_BUILDER(singleControlledIdentity<>)}, QIRTestCase{ "MultipleControlledIdentity", - MQT_NAMED_BUILDER(multipleControlledIdentity), - MQT_NAMED_BUILDER(multipleControlledIdentity)})); + MQT_NAMED_BUILDER(multipleControlledIdentity<>), + MQT_NAMED_BUILDER(multipleControlledIdentity<>)})); /// @} /// \name QIR/Operations/StandardGates/IswapOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRiSWAPOpTest, QIRTest, - testing::Values(QIRTestCase{"iSWAP", MQT_NAMED_BUILDER(iswap), - MQT_NAMED_BUILDER(iswap)}, + testing::Values(QIRTestCase{"iSWAP", MQT_NAMED_BUILDER(iswap<>), + MQT_NAMED_BUILDER(iswap<>)}, QIRTestCase{"SingleControllediSWAP", - MQT_NAMED_BUILDER(singleControlledIswap), - MQT_NAMED_BUILDER(singleControlledIswap)}, + MQT_NAMED_BUILDER(singleControlledIswap<>), + MQT_NAMED_BUILDER(singleControlledIswap<>)}, QIRTestCase{"MultipleControllediSWAP", - MQT_NAMED_BUILDER(multipleControlledIswap), - MQT_NAMED_BUILDER(multipleControlledIswap)})); + MQT_NAMED_BUILDER(multipleControlledIswap<>), + MQT_NAMED_BUILDER(multipleControlledIswap<>)})); /// @} /// \name QIR/Operations/StandardGates/POp.cpp @@ -191,12 +192,12 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRPOpTest, QIRTest, testing::Values( - QIRTestCase{"P", MQT_NAMED_BUILDER(p), MQT_NAMED_BUILDER(p)}, - QIRTestCase{"SingleControlledP", MQT_NAMED_BUILDER(singleControlledP), - MQT_NAMED_BUILDER(singleControlledP)}, + QIRTestCase{"P", MQT_NAMED_BUILDER(p<>), MQT_NAMED_BUILDER(p<>)}, + QIRTestCase{"SingleControlledP", MQT_NAMED_BUILDER(singleControlledP<>), + MQT_NAMED_BUILDER(singleControlledP<>)}, QIRTestCase{"MultipleControlledP", - MQT_NAMED_BUILDER(multipleControlledP), - MQT_NAMED_BUILDER(multipleControlledP)})); + MQT_NAMED_BUILDER(multipleControlledP<>), + MQT_NAMED_BUILDER(multipleControlledP<>)})); /// @} /// \name QIR/Operations/StandardGates/ROp.cpp @@ -204,107 +205,110 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRROpTest, QIRTest, testing::Values( - QIRTestCase{"R", MQT_NAMED_BUILDER(r), MQT_NAMED_BUILDER(r)}, - QIRTestCase{"SingleControlledR", MQT_NAMED_BUILDER(singleControlledR), - MQT_NAMED_BUILDER(singleControlledR)}, + QIRTestCase{"R", MQT_NAMED_BUILDER(r<>), MQT_NAMED_BUILDER(r<>)}, + QIRTestCase{"SingleControlledR", MQT_NAMED_BUILDER(singleControlledR<>), + MQT_NAMED_BUILDER(singleControlledR<>)}, QIRTestCase{"MultipleControlledR", - MQT_NAMED_BUILDER(multipleControlledR), - MQT_NAMED_BUILDER(multipleControlledR)})); + MQT_NAMED_BUILDER(multipleControlledR<>), + MQT_NAMED_BUILDER(multipleControlledR<>)})); /// @} /// \name QIR/Operations/StandardGates/RxOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRXOpTest, QIRTest, - testing::Values( - QIRTestCase{"RX", MQT_NAMED_BUILDER(rx), MQT_NAMED_BUILDER(rx)}, - QIRTestCase{"SingleControlledRX", MQT_NAMED_BUILDER(singleControlledRx), - MQT_NAMED_BUILDER(singleControlledRx)}, - QIRTestCase{"MultipleControlledRX", - MQT_NAMED_BUILDER(multipleControlledRx), - MQT_NAMED_BUILDER(multipleControlledRx)})); + testing::Values(QIRTestCase{"RX", MQT_NAMED_BUILDER(rx<>), + MQT_NAMED_BUILDER(rx<>)}, + QIRTestCase{"SingleControlledRX", + MQT_NAMED_BUILDER(singleControlledRx<>), + MQT_NAMED_BUILDER(singleControlledRx<>)}, + QIRTestCase{"MultipleControlledRX", + MQT_NAMED_BUILDER(multipleControlledRx<>), + MQT_NAMED_BUILDER(multipleControlledRx<>)})); /// @} /// \name QIR/Operations/StandardGates/RxxOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRXXOpTest, QIRTest, - testing::Values(QIRTestCase{"RXX", MQT_NAMED_BUILDER(rxx), - MQT_NAMED_BUILDER(rxx)}, + testing::Values(QIRTestCase{"RXX", MQT_NAMED_BUILDER(rxx<>), + MQT_NAMED_BUILDER(rxx<>)}, QIRTestCase{"SingleControlledRXX", - MQT_NAMED_BUILDER(singleControlledRxx), - MQT_NAMED_BUILDER(singleControlledRxx)}, + MQT_NAMED_BUILDER(singleControlledRxx<>), + MQT_NAMED_BUILDER(singleControlledRxx<>)}, QIRTestCase{"MultipleControlledRXX", - MQT_NAMED_BUILDER(multipleControlledRxx), - MQT_NAMED_BUILDER(multipleControlledRxx)})); + MQT_NAMED_BUILDER(multipleControlledRxx<>), + MQT_NAMED_BUILDER(multipleControlledRxx<>)})); /// @} /// \name QIR/Operations/StandardGates/RyOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRYOpTest, QIRTest, - testing::Values( - QIRTestCase{"RY", MQT_NAMED_BUILDER(ry), MQT_NAMED_BUILDER(ry)}, - QIRTestCase{"SingleControlledRY", MQT_NAMED_BUILDER(singleControlledRy), - MQT_NAMED_BUILDER(singleControlledRy)}, - QIRTestCase{"MultipleControlledRY", - MQT_NAMED_BUILDER(multipleControlledRy), - MQT_NAMED_BUILDER(multipleControlledRy)})); + testing::Values(QIRTestCase{"RY", MQT_NAMED_BUILDER(ry<>), + MQT_NAMED_BUILDER(ry<>)}, + QIRTestCase{"SingleControlledRY", + MQT_NAMED_BUILDER(singleControlledRy<>), + MQT_NAMED_BUILDER(singleControlledRy<>)}, + QIRTestCase{"MultipleControlledRY", + MQT_NAMED_BUILDER(multipleControlledRy<>), + MQT_NAMED_BUILDER(multipleControlledRy<>)})); /// @} /// \name QIR/Operations/StandardGates/RyyOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRYYOpTest, QIRTest, - testing::Values(QIRTestCase{"RYY", MQT_NAMED_BUILDER(ryy), - MQT_NAMED_BUILDER(ryy)}, + testing::Values(QIRTestCase{"RYY", MQT_NAMED_BUILDER(ryy<>), + MQT_NAMED_BUILDER(ryy<>)}, QIRTestCase{"SingleControlledRYY", - MQT_NAMED_BUILDER(singleControlledRyy), - MQT_NAMED_BUILDER(singleControlledRyy)}, + MQT_NAMED_BUILDER(singleControlledRyy<>), + MQT_NAMED_BUILDER(singleControlledRyy<>)}, QIRTestCase{"MultipleControlledRYY", - MQT_NAMED_BUILDER(multipleControlledRyy), - MQT_NAMED_BUILDER(multipleControlledRyy)})); + MQT_NAMED_BUILDER(multipleControlledRyy<>), + MQT_NAMED_BUILDER(multipleControlledRyy<>)})); /// @} /// \name QIR/Operations/StandardGates/RzOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRZOpTest, QIRTest, - testing::Values( - QIRTestCase{"RZ", MQT_NAMED_BUILDER(rz), MQT_NAMED_BUILDER(rz)}, - QIRTestCase{"SingleControlledRZ", MQT_NAMED_BUILDER(singleControlledRz), - MQT_NAMED_BUILDER(singleControlledRz)}, - QIRTestCase{"MultipleControlledRZ", - MQT_NAMED_BUILDER(multipleControlledRz), - MQT_NAMED_BUILDER(multipleControlledRz)})); + testing::Values(QIRTestCase{"RZ", MQT_NAMED_BUILDER(rz<>), + MQT_NAMED_BUILDER(rz<>)}, + QIRTestCase{"SingleControlledRZ", + MQT_NAMED_BUILDER(singleControlledRz<>), + MQT_NAMED_BUILDER(singleControlledRz<>)}, + QIRTestCase{"MultipleControlledRZ", + MQT_NAMED_BUILDER(multipleControlledRz<>), + MQT_NAMED_BUILDER(multipleControlledRz<>)})); /// @} /// \name QIR/Operations/StandardGates/RzxOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRZXOpTest, QIRTest, - testing::Values(QIRTestCase{"RZX", MQT_NAMED_BUILDER(rzx), - MQT_NAMED_BUILDER(rzx)}, + testing::Values(QIRTestCase{"RZX", MQT_NAMED_BUILDER(rzx<>), + MQT_NAMED_BUILDER(rzx<>)}, QIRTestCase{"SingleControlledRZX", - MQT_NAMED_BUILDER(singleControlledRzx), - MQT_NAMED_BUILDER(singleControlledRzx)}, + MQT_NAMED_BUILDER(singleControlledRzx<>), + MQT_NAMED_BUILDER(singleControlledRzx<>)}, QIRTestCase{"MultipleControlledRZX", - MQT_NAMED_BUILDER(multipleControlledRzx), - MQT_NAMED_BUILDER(multipleControlledRzx)})); + MQT_NAMED_BUILDER(multipleControlledRzx<>), + MQT_NAMED_BUILDER(multipleControlledRzx<>)})); /// @} /// \name QIR/Operations/StandardGates/RzzOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRRZZOpTest, QIRTest, - testing::Values(QIRTestCase{"RZZ", MQT_NAMED_BUILDER(rzz), - MQT_NAMED_BUILDER(rzz)}, + testing::Values(QIRTestCase{"RZZ", MQT_NAMED_BUILDER(rzz<>), + MQT_NAMED_BUILDER(rzz<>)}, QIRTestCase{"SingleControlledRZZ", - MQT_NAMED_BUILDER(singleControlledRzz), - MQT_NAMED_BUILDER(singleControlledRzz)}, + MQT_NAMED_BUILDER(singleControlledRzz<>), + MQT_NAMED_BUILDER(singleControlledRzz<>)}, QIRTestCase{"MultipleControlledRZZ", - MQT_NAMED_BUILDER(multipleControlledRzz), - MQT_NAMED_BUILDER(multipleControlledRzz)})); + MQT_NAMED_BUILDER(multipleControlledRzz<>), + MQT_NAMED_BUILDER(multipleControlledRzz<>)})); /// @} /// \name QIR/Operations/StandardGates/SOp.cpp @@ -312,67 +316,68 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRSOpTest, QIRTest, testing::Values( - QIRTestCase{"S", MQT_NAMED_BUILDER(s), MQT_NAMED_BUILDER(s)}, - QIRTestCase{"SingleControlledS", MQT_NAMED_BUILDER(singleControlledS), - MQT_NAMED_BUILDER(singleControlledS)}, + QIRTestCase{"S", MQT_NAMED_BUILDER(s<>), MQT_NAMED_BUILDER(s<>)}, + QIRTestCase{"SingleControlledS", MQT_NAMED_BUILDER(singleControlledS<>), + MQT_NAMED_BUILDER(singleControlledS<>)}, QIRTestCase{"MultipleControlledS", - MQT_NAMED_BUILDER(multipleControlledS), - MQT_NAMED_BUILDER(multipleControlledS)})); + MQT_NAMED_BUILDER(multipleControlledS<>), + MQT_NAMED_BUILDER(multipleControlledS<>)})); /// @} /// \name QIR/Operations/StandardGates/SdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRSdgOpTest, QIRTest, - testing::Values(QIRTestCase{"Sdg", MQT_NAMED_BUILDER(sdg), - MQT_NAMED_BUILDER(sdg)}, + testing::Values(QIRTestCase{"Sdg", MQT_NAMED_BUILDER(sdg<>), + MQT_NAMED_BUILDER(sdg<>)}, QIRTestCase{"SingleControlledSdg", - MQT_NAMED_BUILDER(singleControlledSdg), - MQT_NAMED_BUILDER(singleControlledSdg)}, + MQT_NAMED_BUILDER(singleControlledSdg<>), + MQT_NAMED_BUILDER(singleControlledSdg<>)}, QIRTestCase{"MultipleControlledSdg", - MQT_NAMED_BUILDER(multipleControlledSdg), - MQT_NAMED_BUILDER(multipleControlledSdg)})); + MQT_NAMED_BUILDER(multipleControlledSdg<>), + MQT_NAMED_BUILDER(multipleControlledSdg<>)})); /// @} /// \name QIR/Operations/StandardGates/SwapOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRSWAPOpTest, QIRTest, - testing::Values(QIRTestCase{"SWAP", MQT_NAMED_BUILDER(swap), - MQT_NAMED_BUILDER(swap)}, + testing::Values(QIRTestCase{"SWAP", MQT_NAMED_BUILDER(swap<>), + MQT_NAMED_BUILDER(swap<>)}, QIRTestCase{"SingleControlledSWAP", - MQT_NAMED_BUILDER(singleControlledSwap), - MQT_NAMED_BUILDER(singleControlledSwap)}, + MQT_NAMED_BUILDER(singleControlledSwap<>), + MQT_NAMED_BUILDER(singleControlledSwap<>)}, QIRTestCase{"MultipleControlledSWAP", - MQT_NAMED_BUILDER(multipleControlledSwap), - MQT_NAMED_BUILDER(multipleControlledSwap)})); + MQT_NAMED_BUILDER(multipleControlledSwap<>), + MQT_NAMED_BUILDER(multipleControlledSwap<>)})); /// @} /// \name QIR/Operations/StandardGates/SxOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRSXOpTest, QIRTest, - testing::Values( - QIRTestCase{"SX", MQT_NAMED_BUILDER(sx), MQT_NAMED_BUILDER(sx)}, - QIRTestCase{"SingleControlledSX", MQT_NAMED_BUILDER(singleControlledSx), - MQT_NAMED_BUILDER(singleControlledSx)}, - QIRTestCase{"MultipleControlledSX", - MQT_NAMED_BUILDER(multipleControlledSx), - MQT_NAMED_BUILDER(multipleControlledSx)})); + testing::Values(QIRTestCase{"SX", MQT_NAMED_BUILDER(sx<>), + MQT_NAMED_BUILDER(sx<>)}, + QIRTestCase{"SingleControlledSX", + MQT_NAMED_BUILDER(singleControlledSx<>), + MQT_NAMED_BUILDER(singleControlledSx<>)}, + QIRTestCase{"MultipleControlledSX", + MQT_NAMED_BUILDER(multipleControlledSx<>), + MQT_NAMED_BUILDER(multipleControlledSx<>)})); /// @} /// \name QIR/Operations/StandardGates/SxdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRSXdgOpTest, QIRTest, - testing::Values(QIRTestCase{"SXdg", MQT_NAMED_BUILDER(sxdg), - MQT_NAMED_BUILDER(sxdg)}, + testing::Values(QIRTestCase{"SXdg", MQT_NAMED_BUILDER(sxdg<>), + MQT_NAMED_BUILDER(sxdg<>)}, QIRTestCase{"SingleControlledSXdg", - MQT_NAMED_BUILDER(singleControlledSxdg), - MQT_NAMED_BUILDER(singleControlledSxdg)}, + MQT_NAMED_BUILDER(singleControlledSxdg<>), + MQT_NAMED_BUILDER(singleControlledSxdg<>)}, QIRTestCase{"MultipleControlledSXdg", - MQT_NAMED_BUILDER(multipleControlledSxdg), - MQT_NAMED_BUILDER(multipleControlledSxdg)})); + MQT_NAMED_BUILDER(multipleControlledSxdg<>), + MQT_NAMED_BUILDER(multipleControlledSxdg<>)})); /// @} /// \name QIR/Operations/StandardGates/TOp.cpp @@ -380,39 +385,40 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRTOpTest, QIRTest, testing::Values( - QIRTestCase{"T", MQT_NAMED_BUILDER(t_), MQT_NAMED_BUILDER(t_)}, - QIRTestCase{"SingleControlledT", MQT_NAMED_BUILDER(singleControlledT), - MQT_NAMED_BUILDER(singleControlledT)}, + QIRTestCase{"T", MQT_NAMED_BUILDER(t_<>), MQT_NAMED_BUILDER(t_<>)}, + QIRTestCase{"SingleControlledT", MQT_NAMED_BUILDER(singleControlledT<>), + MQT_NAMED_BUILDER(singleControlledT<>)}, QIRTestCase{"MultipleControlledT", - MQT_NAMED_BUILDER(multipleControlledT), - MQT_NAMED_BUILDER(multipleControlledT)})); + MQT_NAMED_BUILDER(multipleControlledT<>), + MQT_NAMED_BUILDER(multipleControlledT<>)})); /// @} /// \name QIR/Operations/StandardGates/TdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRTdgOpTest, QIRTest, - testing::Values(QIRTestCase{"Tdg", MQT_NAMED_BUILDER(tdg), - MQT_NAMED_BUILDER(tdg)}, + testing::Values(QIRTestCase{"Tdg", MQT_NAMED_BUILDER(tdg<>), + MQT_NAMED_BUILDER(tdg<>)}, QIRTestCase{"SingleControlledTdg", - MQT_NAMED_BUILDER(singleControlledTdg), - MQT_NAMED_BUILDER(singleControlledTdg)}, + MQT_NAMED_BUILDER(singleControlledTdg<>), + MQT_NAMED_BUILDER(singleControlledTdg<>)}, QIRTestCase{"MultipleControlledTdg", - MQT_NAMED_BUILDER(multipleControlledTdg), - MQT_NAMED_BUILDER(multipleControlledTdg)})); + MQT_NAMED_BUILDER(multipleControlledTdg<>), + MQT_NAMED_BUILDER(multipleControlledTdg<>)})); /// @} /// \name QIR/Operations/StandardGates/U2Op.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRU2OpTest, QIRTest, - testing::Values( - QIRTestCase{"U2", MQT_NAMED_BUILDER(u2), MQT_NAMED_BUILDER(u2)}, - QIRTestCase{"SingleControlledU2", MQT_NAMED_BUILDER(singleControlledU2), - MQT_NAMED_BUILDER(singleControlledU2)}, - QIRTestCase{"MultipleControlledU2", - MQT_NAMED_BUILDER(multipleControlledU2), - MQT_NAMED_BUILDER(multipleControlledU2)})); + testing::Values(QIRTestCase{"U2", MQT_NAMED_BUILDER(u2<>), + MQT_NAMED_BUILDER(u2<>)}, + QIRTestCase{"SingleControlledU2", + MQT_NAMED_BUILDER(singleControlledU2<>), + MQT_NAMED_BUILDER(singleControlledU2<>)}, + QIRTestCase{"MultipleControlledU2", + MQT_NAMED_BUILDER(multipleControlledU2<>), + MQT_NAMED_BUILDER(multipleControlledU2<>)})); /// @} /// \name QIR/Operations/StandardGates/UOp.cpp @@ -420,12 +426,12 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRUOpTest, QIRTest, testing::Values( - QIRTestCase{"U", MQT_NAMED_BUILDER(u), MQT_NAMED_BUILDER(u)}, - QIRTestCase{"SingleControlledU", MQT_NAMED_BUILDER(singleControlledU), - MQT_NAMED_BUILDER(singleControlledU)}, + QIRTestCase{"U", MQT_NAMED_BUILDER(u<>), MQT_NAMED_BUILDER(u<>)}, + QIRTestCase{"SingleControlledU", MQT_NAMED_BUILDER(singleControlledU<>), + MQT_NAMED_BUILDER(singleControlledU<>)}, QIRTestCase{"MultipleControlledU", - MQT_NAMED_BUILDER(multipleControlledU), - MQT_NAMED_BUILDER(multipleControlledU)})); + MQT_NAMED_BUILDER(multipleControlledU<>), + MQT_NAMED_BUILDER(multipleControlledU<>)})); /// @} /// \name QIR/Operations/StandardGates/XOp.cpp @@ -433,42 +439,42 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRXOpTest, QIRTest, testing::Values( - QIRTestCase{"X", MQT_NAMED_BUILDER(x), MQT_NAMED_BUILDER(x)}, - QIRTestCase{"SingleControlledX", MQT_NAMED_BUILDER(singleControlledX), - MQT_NAMED_BUILDER(singleControlledX)}, + QIRTestCase{"X", MQT_NAMED_BUILDER(x<>), MQT_NAMED_BUILDER(x<>)}, + QIRTestCase{"SingleControlledX", MQT_NAMED_BUILDER(singleControlledX<>), + MQT_NAMED_BUILDER(singleControlledX<>)}, QIRTestCase{"MultipleControlledX", - MQT_NAMED_BUILDER(multipleControlledX), - MQT_NAMED_BUILDER(multipleControlledX)})); + MQT_NAMED_BUILDER(multipleControlledX<>), + MQT_NAMED_BUILDER(multipleControlledX<>)})); /// @} /// \name QIR/Operations/StandardGates/XxMinusYyOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRXXMinusYYOpTest, QIRTest, - testing::Values(QIRTestCase{"XXMinusYY", MQT_NAMED_BUILDER(xxMinusYY), - MQT_NAMED_BUILDER(xxMinusYY)}, + testing::Values(QIRTestCase{"XXMinusYY", MQT_NAMED_BUILDER(xxMinusYY<>), + MQT_NAMED_BUILDER(xxMinusYY<>)}, QIRTestCase{"SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(singleControlledXxMinusYY), - MQT_NAMED_BUILDER(singleControlledXxMinusYY)}, + MQT_NAMED_BUILDER(singleControlledXxMinusYY<>), + MQT_NAMED_BUILDER(singleControlledXxMinusYY<>)}, QIRTestCase{ "MultipleControlledXXMinusYY", - MQT_NAMED_BUILDER(multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(multipleControlledXxMinusYY)})); + MQT_NAMED_BUILDER(multipleControlledXxMinusYY<>), + MQT_NAMED_BUILDER(multipleControlledXxMinusYY<>)})); /// @} /// \name QIR/Operations/StandardGates/XxPlusYyOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QIRXXPlusYYOpTest, QIRTest, - testing::Values(QIRTestCase{"XXPlusYY", MQT_NAMED_BUILDER(xxPlusYY), - MQT_NAMED_BUILDER(xxPlusYY)}, + testing::Values(QIRTestCase{"XXPlusYY", MQT_NAMED_BUILDER(xxPlusYY<>), + MQT_NAMED_BUILDER(xxPlusYY<>)}, QIRTestCase{"SingleControlledXXPlusYY", - MQT_NAMED_BUILDER(singleControlledXxPlusYY), - MQT_NAMED_BUILDER(singleControlledXxPlusYY)}, + MQT_NAMED_BUILDER(singleControlledXxPlusYY<>), + MQT_NAMED_BUILDER(singleControlledXxPlusYY<>)}, QIRTestCase{ "MultipleControlledXXPlusYY", - MQT_NAMED_BUILDER(multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(multipleControlledXxPlusYY)})); + MQT_NAMED_BUILDER(multipleControlledXxPlusYY<>), + MQT_NAMED_BUILDER(multipleControlledXxPlusYY<>)})); /// @} /// \name QIR/Operations/StandardGates/YOp.cpp @@ -476,12 +482,12 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRYOpTest, QIRTest, testing::Values( - QIRTestCase{"Y", MQT_NAMED_BUILDER(y), MQT_NAMED_BUILDER(y)}, - QIRTestCase{"SingleControlledY", MQT_NAMED_BUILDER(singleControlledY), - MQT_NAMED_BUILDER(singleControlledY)}, + QIRTestCase{"Y", MQT_NAMED_BUILDER(y<>), MQT_NAMED_BUILDER(y<>)}, + QIRTestCase{"SingleControlledY", MQT_NAMED_BUILDER(singleControlledY<>), + MQT_NAMED_BUILDER(singleControlledY<>)}, QIRTestCase{"MultipleControlledY", - MQT_NAMED_BUILDER(multipleControlledY), - MQT_NAMED_BUILDER(multipleControlledY)})); + MQT_NAMED_BUILDER(multipleControlledY<>), + MQT_NAMED_BUILDER(multipleControlledY<>)})); /// @} /// \name QIR/Operations/StandardGates/ZOp.cpp @@ -489,12 +495,12 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRZOpTest, QIRTest, testing::Values( - QIRTestCase{"Z", MQT_NAMED_BUILDER(z), MQT_NAMED_BUILDER(z)}, - QIRTestCase{"SingleControlledZ", MQT_NAMED_BUILDER(singleControlledZ), - MQT_NAMED_BUILDER(singleControlledZ)}, + QIRTestCase{"Z", MQT_NAMED_BUILDER(z<>), MQT_NAMED_BUILDER(z<>)}, + QIRTestCase{"SingleControlledZ", MQT_NAMED_BUILDER(singleControlledZ<>), + MQT_NAMED_BUILDER(singleControlledZ<>)}, QIRTestCase{"MultipleControlledZ", - MQT_NAMED_BUILDER(multipleControlledZ), - MQT_NAMED_BUILDER(multipleControlledZ)})); + MQT_NAMED_BUILDER(multipleControlledZ<>), + MQT_NAMED_BUILDER(multipleControlledZ<>)})); /// @} /// \name QIR/Operations/MeasureOp.cpp @@ -503,14 +509,14 @@ INSTANTIATE_TEST_SUITE_P( QIRMeasureOpTest, QIRTest, testing::Values( QIRTestCase{"SingleMeasurementToSingleBit", - MQT_NAMED_BUILDER(singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(singleMeasurementToSingleBit)}, + MQT_NAMED_BUILDER(singleMeasurementToSingleBit<>), + MQT_NAMED_BUILDER(singleMeasurementToSingleBit<>)}, QIRTestCase{"RepeatedMeasurementToSameBit", - MQT_NAMED_BUILDER(repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(repeatedMeasurementToSameBit)}, + MQT_NAMED_BUILDER(repeatedMeasurementToSameBit<>), + MQT_NAMED_BUILDER(repeatedMeasurementToSameBit<>)}, QIRTestCase{"RepeatedMeasurementToDifferentBits", - MQT_NAMED_BUILDER(repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(repeatedMeasurementToDifferentBits)})); + MQT_NAMED_BUILDER(repeatedMeasurementToDifferentBits<>), + MQT_NAMED_BUILDER(repeatedMeasurementToDifferentBits<>)})); /// @} /// \name QIR/Operations/ResetOp.cpp @@ -519,23 +525,23 @@ INSTANTIATE_TEST_SUITE_P( QIRResetOpTest, QIRTest, testing::Values( QIRTestCase{"ResetQubitWithoutOp", - MQT_NAMED_BUILDER(resetQubitWithoutOp), - MQT_NAMED_BUILDER(resetQubitWithoutOp)}, + MQT_NAMED_BUILDER(resetQubitWithoutOp<>), + MQT_NAMED_BUILDER(resetQubitWithoutOp<>)}, QIRTestCase{"ResetMultipleQubitsWithoutOp", - MQT_NAMED_BUILDER(resetMultipleQubitsWithoutOp), - MQT_NAMED_BUILDER(resetMultipleQubitsWithoutOp)}, + MQT_NAMED_BUILDER(resetMultipleQubitsWithoutOp<>), + MQT_NAMED_BUILDER(resetMultipleQubitsWithoutOp<>)}, QIRTestCase{"RepeatedResetWithoutOp", - MQT_NAMED_BUILDER(repeatedResetWithoutOp), - MQT_NAMED_BUILDER(repeatedResetWithoutOp)}, + MQT_NAMED_BUILDER(repeatedResetWithoutOp<>), + MQT_NAMED_BUILDER(repeatedResetWithoutOp<>)}, QIRTestCase{"ResetQubitAfterSingleOp", - MQT_NAMED_BUILDER(resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(resetQubitAfterSingleOp)}, + MQT_NAMED_BUILDER(resetQubitAfterSingleOp<>), + MQT_NAMED_BUILDER(resetQubitAfterSingleOp<>)}, QIRTestCase{"ResetMultipleQubitsAfterSingleOp", - MQT_NAMED_BUILDER(resetMultipleQubitsAfterSingleOp), - MQT_NAMED_BUILDER(resetMultipleQubitsAfterSingleOp)}, + MQT_NAMED_BUILDER(resetMultipleQubitsAfterSingleOp<>), + MQT_NAMED_BUILDER(resetMultipleQubitsAfterSingleOp<>)}, QIRTestCase{"RepeatedResetAfterSingleOp", - MQT_NAMED_BUILDER(repeatedResetAfterSingleOp), - MQT_NAMED_BUILDER(repeatedResetAfterSingleOp)})); + MQT_NAMED_BUILDER(repeatedResetAfterSingleOp<>), + MQT_NAMED_BUILDER(repeatedResetAfterSingleOp<>)})); /// @} /// \name QIR/QubitManagement/QubitManagement.cpp @@ -543,15 +549,17 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( QIRQubitManagementTest, QIRTest, testing::Values( - QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(allocQubit)}, - QIRTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(allocQubitRegister)}, + QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit<>), + MQT_NAMED_BUILDER(allocQubit<>)}, + QIRTestCase{"AllocQubitRegister", + MQT_NAMED_BUILDER(allocQubitRegister<>), + MQT_NAMED_BUILDER(allocQubitRegister<>)}, QIRTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, - QIRTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(allocLargeRegister)}, + MQT_NAMED_BUILDER(allocMultipleQubitRegisters<>), + MQT_NAMED_BUILDER(allocMultipleQubitRegisters<>)}, + QIRTestCase{"AllocLargeRegister", + MQT_NAMED_BUILDER(allocLargeRegister<>), + MQT_NAMED_BUILDER(allocLargeRegister<>)}, QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(staticQubits)}, QIRTestCase{"StaticQubitsWithOps", diff --git a/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp b/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp index a76e20cc1a..a94ad944c0 100644 --- a/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp +++ b/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp @@ -33,7 +33,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -41,6 +44,7 @@ #include #include #include +#include using namespace mlir; using namespace mlir::qtensor; @@ -62,9 +66,11 @@ class QTensorTest : public ::testing::Test { } /// Build a module using the QCOProgramBuilder and run the cleanup pipeline. + template [[nodiscard]] OwningOpRef - buildAndCanonicalize(void (*buildFn)(QCOProgramBuilder&)) const { - auto module = QCOProgramBuilder::build(context.get(), buildFn); + buildAndCanonicalize(BuildFn&& buildFn) const { + auto module = + QCOProgramBuilder::build(context.get(), std::forward(buildFn)); if (!module) { return {}; } @@ -189,8 +195,10 @@ TEST_F(QTensorTest, AllocOpStaticTypeWithDynamicSizeOperandFailsVerification) { /// An alloc immediately followed by dealloc should be eliminated entirely. TEST_F(QTensorTest, DeallocOpAllocDeallocPairIsRemoved) { - auto canonicalized = - buildAndCanonicalize([](QCOProgramBuilder& b) { b.qtensorAlloc(3); }); + auto canonicalized = buildAndCanonicalize([](QCOProgramBuilder& b) { + b.qtensorAlloc(3); + return b.intConstant(0); + }); ASSERT_TRUE(canonicalized); EXPECT_TRUE(verify(*canonicalized).succeeded()); // Both AllocOp and DeallocOp should have been erased. @@ -416,8 +424,8 @@ TEST_F(QTensorTest, ResetAfterExtractThroughSameIndexInsertIsNotEliminated) { struct QTensorIntegrationTestCase { std::string name; - mqt::test::NamedBuilder programBuilder; - mqt::test::NamedBuilder referenceBuilder; + mqt::test::NamedMLIRBuilder programBuilder; + mqt::test::NamedMLIRBuilder referenceBuilder; friend std::ostream& operator<<(std::ostream& os, const QTensorIntegrationTestCase& info); @@ -449,7 +457,7 @@ TEST_P(QTensorIntegrationTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; mqt::test::DeferredPrinter printer; - auto program = QCOProgramBuilder::build(context.get(), programBuilder.fn); + auto program = mqt::test::buildMLIRProgram(context.get(), programBuilder); ASSERT_TRUE(program); printer.record(program.get(), "Original QTensor IR" + name); EXPECT_TRUE(verify(*program).succeeded()); @@ -458,7 +466,7 @@ TEST_P(QTensorIntegrationTest, ProgramEquivalence) { printer.record(program.get(), "Canonicalized QTensor IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - auto reference = QCOProgramBuilder::build(context.get(), referenceBuilder.fn); + auto reference = mqt::test::buildMLIRProgram(context.get(), referenceBuilder); ASSERT_TRUE(reference); printer.record(reference.get(), "Reference QTensor IR" + name); EXPECT_TRUE(verify(*reference).succeeded()); diff --git a/mlir/unittests/TestCaseUtils.h b/mlir/unittests/TestCaseUtils.h index 570c86c87f..2646b3207a 100644 --- a/mlir/unittests/TestCaseUtils.h +++ b/mlir/unittests/TestCaseUtils.h @@ -17,20 +17,25 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include +#include #include namespace mqt::test { -template struct NamedBuilder { +template struct NamedBuilder { const char* name = nullptr; - void (*fn)(BuilderT&) = nullptr; + RetT (*fn)(BuilderT&) = nullptr; - constexpr NamedBuilder(const char* nameIn, void (*fnIn)(BuilderT&)) noexcept + constexpr NamedBuilder(const char* nameIn, RetT (*fnIn)(BuilderT&)) noexcept : name(nameIn), fn(fnIn) {} // NOLINTNEXTLINE(*-explicit-constructor) @@ -41,10 +46,62 @@ template struct NamedBuilder { } }; +template +[[nodiscard]] constexpr NamedBuilder +namedBuilder(const char* name, RetT (*fn)(BuilderT&)) noexcept { + return NamedBuilder{name, fn}; +} + +template struct NamedMLIRBuilder { + using SingleFn = mlir::Value (*)(BuilderT&); + using MultiFn = mlir::SmallVector (*)(BuilderT&); + + const char* name = nullptr; + std::variant fn; + + constexpr NamedMLIRBuilder(const char* nameIn, SingleFn fnIn) noexcept + : name(nameIn), fn(fnIn) {} + + constexpr NamedMLIRBuilder(const char* nameIn, MultiFn fnIn) noexcept + : name(nameIn), fn(fnIn) {} + + // NOLINTNEXTLINE(*-explicit-constructor) + constexpr NamedMLIRBuilder(std::nullptr_t) noexcept : fn(std::monostate{}) {} + + [[nodiscard]] constexpr explicit operator bool() const noexcept { + return !std::holds_alternative(fn); + } +}; + template -[[nodiscard]] constexpr NamedBuilder -namedBuilder(const char* name, void (*fn)(BuilderT&)) noexcept { - return NamedBuilder{name, fn}; +[[nodiscard]] constexpr NamedMLIRBuilder +namedBuilder(const char* name, mlir::Value (*fn)(BuilderT&)) noexcept { + return NamedMLIRBuilder{name, fn}; +} + +template +[[nodiscard]] constexpr NamedMLIRBuilder +namedBuilder(const char* name, + mlir::SmallVector (*fn)(BuilderT&)) noexcept { + return NamedMLIRBuilder{name, fn}; +} + +template +[[nodiscard]] mlir::OwningOpRef +buildMLIRProgram(mlir::MLIRContext* context, + const NamedMLIRBuilder& builder, Args&&... args) { + return std::visit( + [&](T fn) -> mlir::OwningOpRef { + if constexpr (requires { + BuilderT::build(context, fn, + std::forward(args)...); + }) { + return BuilderT::build(context, fn, std::forward(args)...); + } else { + return {}; + } + }, + builder.fn); } [[nodiscard]] constexpr const char* displayName(const char* name) noexcept { diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index 2386d7e8c7..97cf133e2a 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -18,29 +18,35 @@ namespace mlir::qasm { const std::string allocQubit = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit q; +bit c = measure q; )qasm"; const std::string allocQubitRegister = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; +bit[2] c = measure q; )qasm"; const std::string allocMultipleQubitRegisters = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q0; qubit[3] q1; +bit[2] c0 = measure q0; +bit[3] c1 = measure q1; )qasm"; const std::string allocLargeRegister = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[100] q; +bit[2] c; +c[0] = measure q[0]; +c[1] = measure q[99]; )qasm"; const std::string singleMeasurementToSingleBit = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; -bit[1] c; -measure q[0] -> c[0]; +bit[1] c = measure q[0]; )qasm"; const std::string repeatedMeasurementToSameBit = R"qasm(OPENQASM 3.0; @@ -77,6 +83,7 @@ include "stdgates.inc"; qubit[1] q; h q[0]; reset q[0]; +bit[1] c = measure q; )qasm"; const std::string resetMultipleQubitsAfterSingleOp = R"qasm(OPENQASM 3.0; @@ -86,6 +93,7 @@ h q[0]; reset q[0]; h q[1]; reset q[1]; +bit[2] c = measure q; )qasm"; const std::string repeatedResetAfterSingleOp = R"qasm(OPENQASM 3.0; @@ -95,6 +103,7 @@ h q[0]; reset q[0]; reset q[0]; reset q[0]; +bit[1] c = measure q; )qasm"; const std::string globalPhase = R"qasm(OPENQASM 3.0; @@ -111,60 +120,70 @@ const std::string identity = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; id q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledIdentity = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ id q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledIdentity = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ id q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string x = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; x q[0]; +bit[1] c = measure q; )qasm"; const std::string twoX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; x q; +bit[2] c = measure q; )qasm"; const std::string singleControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ x q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleNegControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; negctrl @ x q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ x q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string tripleControlledXOpenQASM2 = R"qasm(OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; cccx q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string mixedControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ negctrl @ x q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string twoMixedControlledX = R"qasm(OPENQASM 3.0; @@ -173,522 +192,611 @@ qubit[2] q1; qubit[2] q2; qubit[2] q3; ctrl @ negctrl @ x q1, q2, q3; +bit[2] c1 = measure q1; +bit[2] c2 = measure q2; +bit[2] c3 = measure q3; )qasm"; const std::string inverseX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; inv @ x q[0]; +bit[1] c = measure q; )qasm"; const std::string inverseMultipleControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; inv @ ctrl(2) @ x q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string y = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; y q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ y q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ y q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string z = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; z q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledZ = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ z q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledZ = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ z q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string h = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; h q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledH = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ h q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledH = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ h q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string s = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; s q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledS = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ s q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledS = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ s q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string sdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; sdg q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledSdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ sdg q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledSdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ sdg q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string t_ = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; t q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledT = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ t q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledT = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ t q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string tdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; tdg q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledTdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ tdg q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledTdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ tdg q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string sx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; sx q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledSx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ sx q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledSx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ sx q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string sxdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; sxdg q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledSxdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ sxdg q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledSxdg = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ sxdg q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string rx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; rx(0.123) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledRx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ rx(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledRx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ rx(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string ry = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; ry(0.456) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledRy = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ ry(0.456) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledRy = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ ry(0.456) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string rz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; rz(0.789) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledRz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ rz(0.789) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledRz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ rz(0.789) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string p = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; p(0.123) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledP = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ p(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledP = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ p(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string r = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; r(0.123, 0.456) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledR = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ r(0.123, 0.456) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledR = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ r(0.123, 0.456) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string u2 = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; u2(0.234, 0.567) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledU2 = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ u2(0.234, 0.567) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledU2 = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ u2(0.234, 0.567) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string u = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; u(0.1, 0.2, 0.3) q[0]; +bit[1] c = measure q; )qasm"; const std::string singleControlledU = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ctrl @ u(0.1, 0.2, 0.3) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string multipleControlledU = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl(2) @ u(0.1, 0.2, 0.3) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string swap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; swap q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledSwap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ swap q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledSwap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ swap q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string iswap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; iswap q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledIswap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ iswap q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledIswap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ iswap q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string inverseIswap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; inv @ iswap q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string inverseMultipleControlledIswap = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; inv @ ctrl(2) @ iswap q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string dcx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; dcx q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledDcx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ dcx q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledDcx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ dcx q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string ecr = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ecr q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledEcr = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ ecr q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledEcr = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ ecr q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string rxx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; rxx(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledRxx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ rxx(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledRxx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ rxx(0.123) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string tripleControlledRxx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[5] q; ctrl(3) @ rxx(0.123) q[0], q[1], q[2], q[3], q[4]; +bit[5] c = measure q; )qasm"; const std::string ryy = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; ryy(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledRyy = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ ryy(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledRyy = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ ryy(0.123) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string rzx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; rzx(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledRzx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ rzx(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledRzx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ rzx(0.123) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string rzz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; rzz(0.123) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledRzz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ rzz(0.123) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledRzz = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ rzz(0.123) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string xxPlusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; xx_plus_yy(0.123, 0.456) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledXxPlusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ xx_plus_yy(0.123, 0.456) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledXxPlusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ xx_plus_yy(0.123, 0.456) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string xxMinusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; xx_minus_yy(0.123, 0.456) q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string singleControlledXxMinusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; ctrl @ xx_minus_yy(0.123, 0.456) q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string multipleControlledXxMinusYY = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; ctrl(2) @ xx_minus_yy(0.123, 0.456) q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string barrier = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; barrier q[0]; +bit[1] c = measure q; )qasm"; const std::string barrierTwoQubits = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; barrier q[0], q[1]; +bit[2] c = measure q; )qasm"; const std::string barrierMultipleQubits = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; barrier q[0], q[1], q[2]; +bit[3] c = measure q; )qasm"; const std::string ctrlTwo = R"qasm(OPENQASM 3.0; @@ -699,6 +807,7 @@ gate compound q0, q1 { rxx(0.123) q0, q1; } ctrl(2) @ compound q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string ctrlTwoMixed = R"qasm(OPENQASM 3.0; @@ -709,6 +818,7 @@ gate compound q0, q1 { rxx(0.123) q0, q1; } ctrl(2) @ compound q[0], q[1], q[2], q[3]; +bit[4] c = measure q; )qasm"; const std::string simpleIf = R"qasm(OPENQASM 3.0; @@ -719,6 +829,7 @@ bit c = measure q[0]; if (c) { x q[0]; } +bit[1] out = measure q; )qasm"; const std::string ifNot = R"qasm(OPENQASM 3.0; @@ -729,6 +840,7 @@ bit c = measure q[0]; if (!c) { x q[0]; } +output bit[1] out = measure q; )qasm"; const std::string ifTwoQubits = R"qasm(OPENQASM 3.0; @@ -740,6 +852,7 @@ if (c) { x q[0]; x q[1]; } +bit[2] out = measure q; )qasm"; const std::string ifEmptyThen = R"qasm(OPENQASM 3.0; @@ -751,6 +864,7 @@ if (c) { } else { x q[0]; } +output bit[1] out = measure q; )qasm"; const std::string ifElse = R"qasm(OPENQASM 3.0; @@ -763,6 +877,7 @@ if (c) { } else { z q[0]; } +bit[1] out = measure q; )qasm"; } // namespace mlir::qasm diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 8410afd7a8..c54cc9c2d9 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -12,22 +12,40 @@ #include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include +#include +#include +#include + #include namespace mlir::qc { -void emptyQC([[maybe_unused]] QCProgramBuilder& builder) {} +/** + * @brief Measures the given qubits and returns the measurement outcomes. + * @param b The `ProgramBuilder` used to perform the measurements. + * @param qubits The qubits to be measured. + * @return The result values. + */ +static SmallVector measureAndReturn(QCProgramBuilder& b, + ValueRange qubits) { + return llvm::to_vector( + llvm::map_range(qubits, [&](Value q) { return b.measure(q); })); +} -void allocQubit(QCProgramBuilder& b) { b.allocQubit(); } +Value emptyQC(QCProgramBuilder& b) { return b.intConstant(0); } -void allocQubitRegister(QCProgramBuilder& b) { b.allocQubitRegister(2); } +Value allocQubit(QCProgramBuilder& b) { + auto q = b.allocQubit(); + return b.measure(q); +} -void allocMultipleQubitRegisters(QCProgramBuilder& b) { - b.allocQubitRegister(2); - b.allocQubitRegister(3); +Value allocQubitNoMeasure(QCProgramBuilder& b) { + b.allocQubit(); + return b.intConstant(0); } -void allocMultipleQubitRegistersWithOps(QCProgramBuilder& b) { +SmallVector allocMultipleQubitRegistersWithOps(QCProgramBuilder& b) { auto q0 = b.allocQubitRegister(2); auto q1 = b.allocQubitRegister(3); b.h(q0[0]); @@ -35,47 +53,84 @@ void allocMultipleQubitRegistersWithOps(QCProgramBuilder& b) { b.h(q1[0]); b.h(q1[1]); b.h(q1[2]); + return measureAndReturn(b, {q0[0], q0[1], q1[0], q1[1], q1[2]}); +} + +Value alloc1QubitRegister(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + return b.measure(q[0]); +} + +SmallVector allocQubitRegister(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + return measureAndReturn(b, q.qubits); +} + +SmallVector alloc3QubitRegister(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + return measureAndReturn(b, q.qubits); +} + +SmallVector allocMultipleQubitRegisters(QCProgramBuilder& b) { + auto q0 = b.allocQubitRegister(2); + auto q1 = b.allocQubitRegister(3); + return measureAndReturn(b, {q0[0], q0[1], q1[0], q1[1], q1[2]}); +} + +SmallVector allocLargeRegister(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(100); + return measureAndReturn(b, {q[0], q[99]}); } -void allocLargeRegister(QCProgramBuilder& b) { b.allocQubitRegister(100); } +SmallVector staticQubits(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + return measureAndReturn(b, {q0, q1}); +} -void staticQubits(QCProgramBuilder& b) { +Value staticQubitsNoMeasure(QCProgramBuilder& b) { b.staticQubit(0); b.staticQubit(1); + return b.intConstant(0); } -void staticQubitsWithOps(QCProgramBuilder& b) { +SmallVector staticQubitsWithOps(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.h(q0); b.h(q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithParametricOps(QCProgramBuilder& b) { +SmallVector staticQubitsWithParametricOps(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.rx(std::numbers::pi / 4., q0); b.p(std::numbers::pi / 2., q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithTwoTargetOps(QCProgramBuilder& b) { +SmallVector staticQubitsWithTwoTargetOps(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.rzz(0.123, q0, q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithCtrl(QCProgramBuilder& b) { +SmallVector staticQubitsWithCtrl(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithInv(QCProgramBuilder& b) { +Value staticQubitsWithInv(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); - b.inv({q0}, [&](ValueRange qubits) { b.t(qubits[0]); }); + b.inv(q0, [&](Value qubit) { b.t(qubit); }); + return b.measure(q0); } -void staticQubitsWithDuplicates(QCProgramBuilder& b) { +SmallVector staticQubitsWithDuplicates(QCProgramBuilder& b) { const auto q0a = b.staticQubit(0); const auto q1a = b.staticQubit(1); const auto q0b = b.staticQubit(0); @@ -85,10 +140,11 @@ void staticQubitsWithDuplicates(QCProgramBuilder& b) { b.p(std::numbers::pi / 2., q1a); b.rzz(0.123, q0b, q1b); b.cx(q0b, q1b); - b.inv({q0a}, [&](ValueRange qubits) { b.t(qubits[0]); }); + b.inv(q0a, [&](Value qubit) { b.t(qubit); }); + return measureAndReturn(b, {q0b, q1b}); } -void staticQubitsCanonical(QCProgramBuilder& b) { +SmallVector staticQubitsCanonical(QCProgramBuilder& b) { const auto q0 = b.staticQubit(0); const auto q1 = b.staticQubit(1); @@ -96,1283 +152,1542 @@ void staticQubitsCanonical(QCProgramBuilder& b) { b.p(std::numbers::pi / 2., q1); b.rzz(0.123, q0, q1); b.cx(q0, q1); - b.inv({q0}, [&](ValueRange qubits) { b.t(qubits[0]); }); + b.inv(q0, [&](Value qubit) { b.t(qubit); }); + return measureAndReturn(b, {q0, q1}); } -void allocDeallocPair(QCProgramBuilder& b) { +Value allocDeallocPair(QCProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); + return b.intConstant(0); } -void mixedStaticThenDynamicQubit(QCProgramBuilder& b) { - b.staticQubit(0); - b.allocQubit(); +SmallVector mixedStaticThenDynamicQubit(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.allocQubit(); + return measureAndReturn(b, {q0, q1}); } -void mixedDynamicRegisterThenStaticQubit(QCProgramBuilder& b) { - b.allocQubitRegister(2); - b.staticQubit(0); +SmallVector mixedDynamicRegisterThenStaticQubit(QCProgramBuilder& b) { + auto q0 = b.allocQubitRegister(2); + auto q1 = b.staticQubit(0); + return measureAndReturn(b, {q0[0], q0[1], q1}); } -void singleMeasurementToSingleBit(QCProgramBuilder& b) { +Value singleMeasurementToSingleBit(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); - b.measure(q[0], c[0]); + const auto outcome = b.measure(q[0], c[0]); + return outcome; } -void repeatedMeasurementToSameBit(QCProgramBuilder& b) { +Value repeatedMeasurementToSameBit(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); b.measure(q[0], c[0]); - b.measure(q[0], c[0]); + auto c3 = b.measure(q[0], c[0]); + return c3; } -void repeatedMeasurementToDifferentBits(QCProgramBuilder& b) { +SmallVector repeatedMeasurementToDifferentBits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(3); - b.measure(q[0], c[0]); - b.measure(q[0], c[1]); - b.measure(q[0], c[2]); + auto c1 = b.measure(q[0], c[0]); + auto c2 = b.measure(q[0], c[1]); + auto c3 = b.measure(q[0], c[2]); + return {c1, c2, c3}; } -void multipleClassicalRegistersAndMeasurements(QCProgramBuilder& b) { +SmallVector +multipleClassicalRegistersAndMeasurements(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); const auto& c0 = b.allocClassicalBitRegister(1, "c0"); const auto& c1 = b.allocClassicalBitRegister(2, "c1"); - b.measure(q[0], c0[0]); - b.measure(q[1], c1[0]); - b.measure(q[2], c1[1]); + const auto b1 = b.measure(q[0], c0[0]); + const auto b2 = b.measure(q[1], c1[0]); + const auto b3 = b.measure(q[2], c1[1]); + return {b1, b2, b3}; } -void measurementWithoutRegisters(QCProgramBuilder& b) { +Value measurementWithoutRegisters(QCProgramBuilder& b) { auto q = b.allocQubit(); - b.measure(q); + auto c = b.measure(q); + return c; } -void resetQubitWithoutOp(QCProgramBuilder& b) { +Value resetQubitWithoutOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.reset(q[0]); + return b.measure(q[0]); } -void resetMultipleQubitsWithoutOp(QCProgramBuilder& b) { +SmallVector resetMultipleQubitsWithoutOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.reset(q[0]); b.reset(q[1]); + return measureAndReturn(b, q.qubits); } -void repeatedResetWithoutOp(QCProgramBuilder& b) { +Value repeatedResetWithoutOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); + return b.measure(q[0]); } -void resetQubitAfterSingleOp(QCProgramBuilder& b) { +Value resetQubitAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); b.reset(q[0]); + return b.measure(q[0]); } -void resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b) { +SmallVector resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); b.reset(q[0]); b.h(q[1]); b.reset(q[1]); + return measureAndReturn(b, q.qubits); } -void repeatedResetAfterSingleOp(QCProgramBuilder& b) { +Value repeatedResetAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); + return b.measure(q[0]); } -void globalPhase(QCProgramBuilder& b) { b.gphase(0.123); } +Value globalPhase(QCProgramBuilder& b) { + b.gphase(0.123); + return b.intConstant(0); +} + +Value globalPhaseAndMeasure(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(0.123); + return b.measure(q[0]); +} -void singleControlledGlobalPhase(QCProgramBuilder& b) { +Value singleControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.cgphase(0.123, q[0]); + return b.measure(q[0]); } -void multipleControlledGlobalPhase(QCProgramBuilder& b) { +SmallVector multipleControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcgphase(0.123, {q[0], q[1], q[2]}); + return measureAndReturn(b, q.qubits); } -void nestedControlledGlobalPhase(QCProgramBuilder& b) { - auto q = b.allocQubitRegister(3); - b.ctrl(q[0], {q[1]}, - [&](ValueRange targets) { b.cgphase(0.123, targets[0]); }); +SmallVector nestedControlledGlobalPhase(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl(q[0], q[1], [&](Value target) { b.cgphase(0.123, target); }); + return measureAndReturn(b, q.qubits); } -void trivialControlledGlobalPhase(QCProgramBuilder& b) { +Value trivialControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcgphase(0.123, {}); + return b.measure(q[0]); } -void inverseGlobalPhase(QCProgramBuilder& b) { - b.inv({}, [&](ValueRange /*qubits*/) { b.gphase(-0.123); }); +Value inverseGlobalPhase(QCProgramBuilder& b) { + b.inv(ValueRange{}, [&](ValueRange /*qubits*/) { b.gphase(-0.123); }); + return b.intConstant(0); } -void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcgphase(-0.123, qubits); }); + return measureAndReturn(b, q.qubits); } -void identity(QCProgramBuilder& b) { +Value identity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.id(q[0]); + return b.measure(q[0]); +} + +SmallVector singleControlledIdentity(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.cid(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledIdentity(QCProgramBuilder& b) { +SmallVector twoQubitsOneIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cid(q[1], q[0]); + b.id(q[1]); + return measureAndReturn(b, q.qubits); +} + +SmallVector threeQubitsOneIdentity(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.id(q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledIdentity(QCProgramBuilder& b) { +SmallVector multipleControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcid({q[2], q[1]}, q[0]); + b.mcid({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); +} + +SmallVector twoQubitsOneBarrier(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.barrier(q[0]); + return measureAndReturn(b, q.qubits); } -void nestedControlledIdentity(QCProgramBuilder& b) { +SmallVector nestedControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.ctrl(q[2], {q[0], q[1]}, - [&](ValueRange targets) { b.cid(targets[1], targets[0]); }); + b.ctrl(q[0], {q[1], q[2]}, + [&](ValueRange targets) { b.cid(targets[0], targets[1]); }); + return measureAndReturn(b, q.qubits); } -void trivialControlledIdentity(QCProgramBuilder& b) { +Value trivialControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcid({}, q[0]); + return b.measure(q[0]); } -void inverseIdentity(QCProgramBuilder& b) { +Value inverseIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.id(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.id(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledIdentity(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcid({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void x(QCProgramBuilder& b) { +Value x(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.x(q[0]); + return b.measure(q[0]); } -void singleControlledX(QCProgramBuilder& b) { +SmallVector singleControlledX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledX(QCProgramBuilder& b) { +SmallVector multipleControlledX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcx({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledX(QCProgramBuilder& b) { +SmallVector nestedControlledX(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cx(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledX(QCProgramBuilder& b) { +Value trivialControlledX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcx({}, q[0]); + return b.measure(q[0]); } -void repeatedControlledX(QCProgramBuilder& b) { - auto q = b.allocQubitRegister(64); - b.h(q[0]); - for (auto i = 1; i < 64; i++) { - b.cx(q[0], q[i]); +SmallVector repeatedControlledX(QCProgramBuilder& b) { + auto control = b.allocQubit(); + b.h(control); + SmallVector qubits; + for (auto i = 0; i < 50; i++) { + auto qubit = b.allocQubit(); + b.cx(control, qubit); + qubits.push_back(qubit); } + qubits.push_back(control); + return measureAndReturn(b, qubits); } -void inverseX(QCProgramBuilder& b) { +Value inverseX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.x(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.x(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledX(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcx({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void y(QCProgramBuilder& b) { +Value y(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.y(q[0]); + return b.measure(q[0]); } -void singleControlledY(QCProgramBuilder& b) { +SmallVector singleControlledY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cy(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledY(QCProgramBuilder& b) { +SmallVector multipleControlledY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcy({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledY(QCProgramBuilder& b) { +SmallVector nestedControlledY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cy(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledY(QCProgramBuilder& b) { +Value trivialControlledY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcy({}, q[0]); + return b.measure(q[0]); } -void inverseY(QCProgramBuilder& b) { +Value inverseY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.y(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.y(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledY(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcy({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void z(QCProgramBuilder& b) { +Value z(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.z(q[0]); + return b.measure(q[0]); } -void singleControlledZ(QCProgramBuilder& b) { +SmallVector singleControlledZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cz(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledZ(QCProgramBuilder& b) { +SmallVector multipleControlledZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcz({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledZ(QCProgramBuilder& b) { +SmallVector nestedControlledZ(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cz(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledZ(QCProgramBuilder& b) { +Value trivialControlledZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcz({}, q[0]); + return b.measure(q[0]); } -void inverseZ(QCProgramBuilder& b) { +Value inverseZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.z(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.z(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledZ(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcz({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void h(QCProgramBuilder& b) { +Value h(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); + return b.measure(q[0]); } -void singleControlledH(QCProgramBuilder& b) { +SmallVector singleControlledH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ch(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledH(QCProgramBuilder& b) { +SmallVector multipleControlledH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mch({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledH(QCProgramBuilder& b) { +SmallVector nestedControlledH(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.ch(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledH(QCProgramBuilder& b) { +Value trivialControlledH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mch({}, q[0]); + return b.measure(q[0]); } -void inverseH(QCProgramBuilder& b) { +Value inverseH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.h(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledH(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mch({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void hWithoutRegister(QCProgramBuilder& b) { +Value hWithoutRegister(QCProgramBuilder& b) { auto q = b.allocQubit(); b.h(q); + return b.measure(q); } -void s(QCProgramBuilder& b) { +Value s(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.s(q[0]); + return b.measure(q[0]); } -void singleControlledS(QCProgramBuilder& b) { +SmallVector singleControlledS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cs(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledS(QCProgramBuilder& b) { +SmallVector multipleControlledS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcs({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledS(QCProgramBuilder& b) { +SmallVector nestedControlledS(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cs(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledS(QCProgramBuilder& b) { +Value trivialControlledS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcs({}, q[0]); + return b.measure(q[0]); } -void inverseS(QCProgramBuilder& b) { +Value inverseS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.s(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledS(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcs({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void sdg(QCProgramBuilder& b) { +Value sdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sdg(q[0]); + return b.measure(q[0]); } -void singleControlledSdg(QCProgramBuilder& b) { +SmallVector singleControlledSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csdg(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledSdg(QCProgramBuilder& b) { +SmallVector multipleControlledSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsdg({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledSdg(QCProgramBuilder& b) { +SmallVector nestedControlledSdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.csdg(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledSdg(QCProgramBuilder& b) { +Value trivialControlledSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcsdg({}, q[0]); + return b.measure(q[0]); } -void inverseSdg(QCProgramBuilder& b) { +Value inverseSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.sdg(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.sdg(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledSdg(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcsdg({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void t_(QCProgramBuilder& b) { +Value t_(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.t(q[0]); + return b.measure(q[0]); } -void singleControlledT(QCProgramBuilder& b) { +SmallVector singleControlledT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ct(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledT(QCProgramBuilder& b) { +SmallVector multipleControlledT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mct({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledT(QCProgramBuilder& b) { +SmallVector nestedControlledT(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.ct(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledT(QCProgramBuilder& b) { +Value trivialControlledT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mct({}, q[0]); + return b.measure(q[0]); } -void inverseT(QCProgramBuilder& b) { +Value inverseT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.t(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.t(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledT(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mct({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void tdg(QCProgramBuilder& b) { +Value tdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.tdg(q[0]); + return b.measure(q[0]); } -void singleControlledTdg(QCProgramBuilder& b) { +SmallVector singleControlledTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ctdg(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledTdg(QCProgramBuilder& b) { +SmallVector multipleControlledTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mctdg({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledTdg(QCProgramBuilder& b) { +SmallVector nestedControlledTdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.ctdg(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledTdg(QCProgramBuilder& b) { +Value trivialControlledTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mctdg({}, q[0]); + return b.measure(q[0]); } -void inverseTdg(QCProgramBuilder& b) { +Value inverseTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.tdg(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.tdg(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledTdg(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mctdg({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void sx(QCProgramBuilder& b) { +Value sx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sx(q[0]); + return b.measure(q[0]); } -void singleControlledSx(QCProgramBuilder& b) { +SmallVector singleControlledSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledSx(QCProgramBuilder& b) { +SmallVector multipleControlledSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsx({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledSx(QCProgramBuilder& b) { +SmallVector nestedControlledSx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.csx(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledSx(QCProgramBuilder& b) { +Value trivialControlledSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcsx({}, q[0]); + return b.measure(q[0]); } -void inverseSx(QCProgramBuilder& b) { +Value inverseSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.sx(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.sx(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledSx(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcsx({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void sxdg(QCProgramBuilder& b) { +Value sxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sxdg(q[0]); + return b.measure(q[0]); } -void singleControlledSxdg(QCProgramBuilder& b) { +SmallVector singleControlledSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csxdg(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledSxdg(QCProgramBuilder& b) { +SmallVector multipleControlledSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsxdg({q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledSxdg(QCProgramBuilder& b) { +SmallVector nestedControlledSxdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.csxdg(targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledSxdg(QCProgramBuilder& b) { +Value trivialControlledSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcsxdg({}, q[0]); + return b.measure(q[0]); } -void inverseSxdg(QCProgramBuilder& b) { +Value inverseSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.sxdg(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.sxdg(qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledSxdg(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcsxdg({qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void rx(QCProgramBuilder& b) { +Value rx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rx(0.123, q[0]); + return b.measure(q[0]); } -void singleControlledRx(QCProgramBuilder& b) { +SmallVector singleControlledRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.crx(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRx(QCProgramBuilder& b) { +SmallVector multipleControlledRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcrx(0.123, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRx(QCProgramBuilder& b) { +SmallVector nestedControlledRx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.crx(0.123, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRx(QCProgramBuilder& b) { +Value trivialControlledRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcrx(0.123, {}, q[0]); + return b.measure(q[0]); } -void inverseRx(QCProgramBuilder& b) { +Value inverseRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.rx(-0.123, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.rx(-0.123, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledRx(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcrx(-0.123, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void ry(QCProgramBuilder& b) { +Value ry(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.ry(0.456, q[0]); + return b.measure(q[0]); } -void singleControlledRy(QCProgramBuilder& b) { +SmallVector singleControlledRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cry(0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRy(QCProgramBuilder& b) { +SmallVector multipleControlledRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcry(0.456, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRy(QCProgramBuilder& b) { +SmallVector nestedControlledRy(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cry(0.456, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRy(QCProgramBuilder& b) { +Value trivialControlledRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcry(0.456, {}, q[0]); + return b.measure(q[0]); } -void inverseRy(QCProgramBuilder& b) { +Value inverseRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.ry(-0.456, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.ry(-0.456, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledRy(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcry(-0.456, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void rz(QCProgramBuilder& b) { +Value rz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rz(0.789, q[0]); + return b.measure(q[0]); } -void singleControlledRz(QCProgramBuilder& b) { +SmallVector singleControlledRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.crz(0.789, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRz(QCProgramBuilder& b) { +SmallVector multipleControlledRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcrz(0.789, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRz(QCProgramBuilder& b) { +SmallVector nestedControlledRz(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.crz(0.789, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRz(QCProgramBuilder& b) { +Value trivialControlledRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcrz(0.789, {}, q[0]); + return b.measure(q[0]); } -void inverseRz(QCProgramBuilder& b) { +Value inverseRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.rz(-0.789, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.rz(-0.789, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledRz(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcrz(-0.789, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void p(QCProgramBuilder& b) { +Value p(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.p(0.123, q[0]); + return b.measure(q[0]); } -void singleControlledP(QCProgramBuilder& b) { +SmallVector singleControlledP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cp(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledP(QCProgramBuilder& b) { +SmallVector multipleControlledP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcp(0.123, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledP(QCProgramBuilder& b) { +SmallVector nestedControlledP(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cp(0.123, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledP(QCProgramBuilder& b) { +Value trivialControlledP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcp(0.123, {}, q[0]); + return b.measure(q[0]); } -void inverseP(QCProgramBuilder& b) { +Value inverseP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.p(-0.123, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.p(-0.123, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledP(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcp(-0.123, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void r(QCProgramBuilder& b) { +Value r(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.r(0.123, 0.456, q[0]); + return b.measure(q[0]); } -void singleControlledR(QCProgramBuilder& b) { +SmallVector singleControlledR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cr(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledR(QCProgramBuilder& b) { +SmallVector multipleControlledR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcr(0.123, 0.456, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledR(QCProgramBuilder& b) { +SmallVector nestedControlledR(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cr(0.123, 0.456, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledR(QCProgramBuilder& b) { +Value trivialControlledR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcr(0.123, 0.456, {}, q[0]); + return b.measure(q[0]); } -void inverseR(QCProgramBuilder& b) { +Value inverseR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.r(-0.123, 0.456, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.r(-0.123, 0.456, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledR(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcr(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void u2(QCProgramBuilder& b) { +Value u2(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.u2(0.234, 0.567, q[0]); + return b.measure(q[0]); } -void singleControlledU2(QCProgramBuilder& b) { +SmallVector singleControlledU2(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cu2(0.234, 0.567, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledU2(QCProgramBuilder& b) { +SmallVector multipleControlledU2(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcu2(0.234, 0.567, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledU2(QCProgramBuilder& b) { +SmallVector nestedControlledU2(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cu2(0.234, 0.567, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledU2(QCProgramBuilder& b) { +Value trivialControlledU2(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcu2(0.234, 0.567, {}, q[0]); + return b.measure(q[0]); } -void inverseU2(QCProgramBuilder& b) { +Value inverseU2(QCProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(1); - b.inv(q[0], - [&](ValueRange qubits) { b.u2(-0.567 + pi, -0.234 - pi, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.u2(-0.567 + pi, -0.234 - pi, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledU2(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledU2(QCProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcu2(-0.567 + pi, -0.234 - pi, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void u(QCProgramBuilder& b) { +Value u(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.u(0.1, 0.2, 0.3, q[0]); + return b.measure(q[0]); } -void singleControlledU(QCProgramBuilder& b) { +SmallVector singleControlledU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cu(0.1, 0.2, 0.3, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledU(QCProgramBuilder& b) { +SmallVector multipleControlledU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcu(0.1, 0.2, 0.3, {q[0], q[1]}, q[2]); + return measureAndReturn(b, q.qubits); } -void nestedControlledU(QCProgramBuilder& b) { +SmallVector nestedControlledU(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { b.cu(0.1, 0.2, 0.3, targets[0], targets[1]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledU(QCProgramBuilder& b) { +Value trivialControlledU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.mcu(0.1, 0.2, 0.3, {}, q[0]); + return b.measure(q[0]); } -void inverseU(QCProgramBuilder& b) { +Value inverseU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.u(-0.1, -0.3, -0.2, qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.u(-0.1, -0.3, -0.2, qubit); }); + return b.measure(q[0]); } -void inverseMultipleControlledU(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.mcu(-0.1, -0.3, -0.2, {qubits[0], qubits[1]}, qubits[2]); }); + return measureAndReturn(b, q.qubits); } -void swap(QCProgramBuilder& b) { +SmallVector swap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.swap(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledSwap(QCProgramBuilder& b) { +SmallVector singleControlledSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cswap(q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledSwap(QCProgramBuilder& b) { +SmallVector multipleControlledSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcswap({q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledSwap(QCProgramBuilder& b) { +SmallVector nestedControlledSwap(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cswap(targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledSwap(QCProgramBuilder& b) { +SmallVector trivialControlledSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcswap({}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseSwap(QCProgramBuilder& b) { +SmallVector inverseSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.swap(qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledSwap(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void iswap(QCProgramBuilder& b) { +SmallVector iswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.iswap(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledIswap(QCProgramBuilder& b) { +SmallVector singleControlledIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.ciswap(q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledIswap(QCProgramBuilder& b) { +SmallVector multipleControlledIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mciswap({q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledIswap(QCProgramBuilder& b) { +SmallVector nestedControlledIswap(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.ciswap(targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledIswap(QCProgramBuilder& b) { +SmallVector trivialControlledIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mciswap({}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseIswap(QCProgramBuilder& b) { +SmallVector inverseIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.iswap(qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledIswap(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mciswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void dcx(QCProgramBuilder& b) { +SmallVector dcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.dcx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledDcx(QCProgramBuilder& b) { +SmallVector singleControlledDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cdcx(q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledDcx(QCProgramBuilder& b) { +SmallVector multipleControlledDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcdcx({q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledDcx(QCProgramBuilder& b) { +SmallVector nestedControlledDcx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cdcx(targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledDcx(QCProgramBuilder& b) { +SmallVector trivialControlledDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcdcx({}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseDcx(QCProgramBuilder& b) { +SmallVector inverseDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.dcx(qubits[1], qubits[0]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledDcx(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[3], q[2]}, [&](ValueRange qubits) { b.mcdcx({qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void ecr(QCProgramBuilder& b) { +SmallVector ecr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ecr(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledEcr(QCProgramBuilder& b) { +SmallVector singleControlledEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cecr(q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledEcr(QCProgramBuilder& b) { +SmallVector multipleControlledEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcecr({q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledEcr(QCProgramBuilder& b) { +SmallVector nestedControlledEcr(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cecr(targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledEcr(QCProgramBuilder& b) { +SmallVector trivialControlledEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcecr({}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseEcr(QCProgramBuilder& b) { +SmallVector inverseEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.ecr(qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledEcr(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcecr({qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void rxx(QCProgramBuilder& b) { +SmallVector rxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRxx(QCProgramBuilder& b) { +SmallVector singleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crxx(0.123, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRxx(QCProgramBuilder& b) { +SmallVector multipleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRxx(QCProgramBuilder& b) { +SmallVector nestedControlledRxx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.crxx(0.123, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRxx(QCProgramBuilder& b) { +SmallVector trivialControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcrxx(0.123, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseRxx(QCProgramBuilder& b) { +SmallVector inverseRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.rxx(-0.123, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRxx(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcrxx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void tripleControlledRxx(QCProgramBuilder& b) { +SmallVector tripleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(5); b.mcrxx(0.123, {q[0], q[1], q[2]}, q[3], q[4]); + return measureAndReturn(b, q.qubits); } -void fourControlledRxx(QCProgramBuilder& b) { +SmallVector fourControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(6); b.mcrxx(0.123, {q[0], q[1], q[2], q[3]}, q[4], q[5]); + return measureAndReturn(b, q.qubits); } -void ryy(QCProgramBuilder& b) { +SmallVector ryy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ryy(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRyy(QCProgramBuilder& b) { +SmallVector singleControlledRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cryy(0.123, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRyy(QCProgramBuilder& b) { +SmallVector multipleControlledRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcryy(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRyy(QCProgramBuilder& b) { +SmallVector nestedControlledRyy(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cryy(0.123, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRyy(QCProgramBuilder& b) { +SmallVector trivialControlledRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcryy(0.123, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseRyy(QCProgramBuilder& b) { +SmallVector inverseRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.ryy(-0.123, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRyy(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcryy(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void rzx(QCProgramBuilder& b) { +SmallVector rzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rzx(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRzx(QCProgramBuilder& b) { +SmallVector singleControlledRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crzx(0.123, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRzx(QCProgramBuilder& b) { +SmallVector multipleControlledRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrzx(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRzx(QCProgramBuilder& b) { +SmallVector nestedControlledRzx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.crzx(0.123, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRzx(QCProgramBuilder& b) { +SmallVector trivialControlledRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcrzx(0.123, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseRzx(QCProgramBuilder& b) { +SmallVector inverseRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.rzx(-0.123, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRzx(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcrzx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void rzz(QCProgramBuilder& b) { +SmallVector rzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rzz(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRzz(QCProgramBuilder& b) { +SmallVector singleControlledRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crzz(0.123, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRzz(QCProgramBuilder& b) { +SmallVector multipleControlledRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrzz(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledRzz(QCProgramBuilder& b) { +SmallVector nestedControlledRzz(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.crzz(0.123, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledRzz(QCProgramBuilder& b) { +SmallVector trivialControlledRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcrzz(0.123, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseRzz(QCProgramBuilder& b) { +SmallVector inverseRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.rzz(-0.123, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRzz(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcrzz(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void xxPlusYY(QCProgramBuilder& b) { +SmallVector xxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.xx_plus_yy(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledXxPlusYY(QCProgramBuilder& b) { +SmallVector singleControlledXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cxx_plus_yy(0.123, 0.456, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledXxPlusYY(QCProgramBuilder& b) { +SmallVector multipleControlledXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcxx_plus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledXxPlusYY(QCProgramBuilder& b) { +SmallVector nestedControlledXxPlusYY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cxx_plus_yy(0.123, 0.456, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledXxPlusYY(QCProgramBuilder& b) { +SmallVector trivialControlledXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcxx_plus_yy(0.123, 0.456, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseXxPlusYY(QCProgramBuilder& b) { +SmallVector inverseXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.xx_plus_yy(-0.123, 0.456, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcxx_plus_yy(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void xxMinusYY(QCProgramBuilder& b) { +SmallVector xxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.xx_minus_yy(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledXxMinusYY(QCProgramBuilder& b) { +SmallVector singleControlledXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cxx_minus_yy(0.123, 0.456, q[0], q[1], q[2]); + return measureAndReturn(b, q.qubits); } -void multipleControlledXxMinusYY(QCProgramBuilder& b) { +SmallVector multipleControlledXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcxx_minus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + return measureAndReturn(b, q.qubits); } -void nestedControlledXxMinusYY(QCProgramBuilder& b) { +SmallVector nestedControlledXxMinusYY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { b.cxx_minus_yy(0.123, 0.456, targets[0], targets[1], targets[2]); }); + return measureAndReturn(b, reg.qubits); } -void trivialControlledXxMinusYY(QCProgramBuilder& b) { +SmallVector trivialControlledXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.mcxx_minus_yy(0.123, 0.456, {}, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void inverseXxMinusYY(QCProgramBuilder& b) { +SmallVector inverseXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.xx_minus_yy(-0.123, 0.456, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b) { +SmallVector inverseMultipleControlledXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { b.mcxx_minus_yy(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], qubits[3]); }); + return measureAndReturn(b, q.qubits); } -void barrier(QCProgramBuilder& b) { +Value barrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.barrier(q[0]); + return b.measure(q[0]); } -void barrierTwoQubits(QCProgramBuilder& b) { +SmallVector barrierTwoQubits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.barrier({q[0], q[1]}); + return measureAndReturn(b, q.qubits); } -void barrierMultipleQubits(QCProgramBuilder& b) { +SmallVector barrierMultipleQubits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.barrier({q[0], q[1], q[2]}); + return measureAndReturn(b, q.qubits); } -void singleControlledBarrier(QCProgramBuilder& b) { +SmallVector singleControlledBarrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl(q[1], q[0], [&](ValueRange targets) { b.barrier(targets[0]); }); + b.ctrl(q[1], q[0], [&](Value target) { b.barrier({target}); }); + return measureAndReturn(b, q.qubits); } -void inverseBarrier(QCProgramBuilder& b) { +Value inverseBarrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { b.barrier(qubits[0]); }); + b.inv(q[0], [&](Value qubit) { b.barrier(qubit); }); + return b.measure(q[0]); } -void trivialCtrl(QCProgramBuilder& b) { +SmallVector trivialCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ctrl({}, {q[0], q[1]}, [&](ValueRange targets) { b.rxx(0.123, targets[0], targets[1]); }); + return measureAndReturn(b, q.qubits); } -void emptyCtrl(QCProgramBuilder& b) { +SmallVector emptyCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); - b.ctrl({q[0]}, {q[1]}, [&](ValueRange /*targets*/) {}); + b.ctrl(q[0], q[1], [&](Value /*target*/) {}); + return measureAndReturn(b, q.qubits); } -void nestedCtrl(QCProgramBuilder& b) { +SmallVector nestedCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { b.ctrl(targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { b.rxx(0.123, innerTargets[0], innerTargets[1]); }); }); + return measureAndReturn(b, q.qubits); } -void tripleNestedCtrl(QCProgramBuilder& b) { +SmallVector tripleNestedCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(5); b.ctrl(q[0], {q[1], q[2], q[3], q[4]}, [&](ValueRange targets) { b.ctrl(targets[0], {targets[1], targets[2], targets[3]}, @@ -1383,9 +1698,10 @@ void tripleNestedCtrl(QCProgramBuilder& b) { }); }); }); + return measureAndReturn(b, q.qubits); } -void doubleNestedCtrlTwoQubits(QCProgramBuilder& b) { +SmallVector doubleNestedCtrlTwoQubits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(6); b.ctrl({q[0], q[1]}, {q[2], q[3], q[4], q[5]}, [&](ValueRange targets) { b.ctrl({targets[0], targets[1]}, {targets[2], targets[3]}, @@ -1393,9 +1709,10 @@ void doubleNestedCtrlTwoQubits(QCProgramBuilder& b) { b.rxx(0.123, innerTargets[0], innerTargets[1]); }); }); + return measureAndReturn(b, q.qubits); } -void ctrlInvSandwich(QCProgramBuilder& b) { +SmallVector ctrlInvSandwich(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { b.inv(targets, [&](ValueRange qubits) { @@ -1404,25 +1721,28 @@ void ctrlInvSandwich(QCProgramBuilder& b) { }); }); }); + return measureAndReturn(b, q.qubits); } -void ctrlTwo(QCProgramBuilder& b) { +SmallVector ctrlTwo(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { b.x(targets[0]); b.rxx(0.123, targets[0], targets[1]); }); + return measureAndReturn(b, q.qubits); } -void ctrlTwoMixed(QCProgramBuilder& b) { +SmallVector ctrlTwoMixed(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { b.cx(targets[0], targets[1]); b.rxx(0.123, targets[0], targets[1]); }); + return measureAndReturn(b, q.qubits); } -void nestedCtrlTwo(QCProgramBuilder& b) { +SmallVector nestedCtrlTwo(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { b.ctrl(targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { @@ -1430,9 +1750,10 @@ void nestedCtrlTwo(QCProgramBuilder& b) { b.rxx(0.123, innerTargets[0], innerTargets[1]); }); }); + return measureAndReturn(b, q.qubits); } -void ctrlInvTwo(QCProgramBuilder& b) { +SmallVector ctrlInvTwo(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.ctrl(q[0], {q[1], q[2]}, [&](ValueRange targets) { b.inv(targets, [&](ValueRange qubits) { @@ -1440,24 +1761,27 @@ void ctrlInvTwo(QCProgramBuilder& b) { b.rxx(0.123, qubits[0], qubits[1]); }); }); + return measureAndReturn(b, q.qubits); } -void emptyInv(QCProgramBuilder& b) { +SmallVector emptyInv(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); b.inv({q[0], q[1]}, [&](ValueRange /*targets*/) {}); + return measureAndReturn(b, q.qubits); } -void nestedInv(QCProgramBuilder& b) { +SmallVector nestedInv(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.inv(qubits, [&](ValueRange innerQubits) { b.rxx(0.123, innerQubits[0], innerQubits[1]); }); }); + return measureAndReturn(b, q.qubits); } -void tripleNestedInv(QCProgramBuilder& b) { +SmallVector tripleNestedInv(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.inv(qubits, [&](ValueRange innerQubits) { @@ -1466,9 +1790,10 @@ void tripleNestedInv(QCProgramBuilder& b) { }); }); }); + return measureAndReturn(b, q.qubits); } -void invCtrlSandwich(QCProgramBuilder& b) { +SmallVector invCtrlSandwich(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.ctrl(qubits[0], {qubits[1], qubits[2]}, [&](ValueRange targets) { @@ -1477,17 +1802,19 @@ void invCtrlSandwich(QCProgramBuilder& b) { }); }); }); + return measureAndReturn(b, q.qubits); } -void invTwo(QCProgramBuilder& b) { +SmallVector invTwo(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.x(qubits[0]); b.rxx(0.123, qubits[0], qubits[1]); }); + return measureAndReturn(b, q.qubits); } -void invCtrlTwo(QCProgramBuilder& b) { +SmallVector invCtrlTwo(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { b.ctrl(qubits[0], {qubits[1], qubits[2]}, [&](ValueRange targets) { @@ -1495,16 +1822,28 @@ void invCtrlTwo(QCProgramBuilder& b) { b.rxx(0.123, targets[0], targets[1]); }); }); + return measureAndReturn(b, q.qubits); } -void simpleIf(QCProgramBuilder& b) { +SmallVector simpleIf(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); auto cond = b.measure(q[0]); b.scfIf(cond, [&] { b.x(q[0]); }); + auto res = b.measure(q[0]); + return {cond, res}; } -void ifTwoQubits(QCProgramBuilder& b) { +SmallVector ifElse(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.h(q[0]); + auto cond = b.measure(q[0]); + b.scfIf(cond, [&] { b.x(q[0]); }, [&] { b.z(q[0]); }); + auto bit = b.measure(q[0]); + return {cond, bit}; +} + +SmallVector ifTwoQubits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); auto cond = b.measure(q[0]); @@ -1512,16 +1851,12 @@ void ifTwoQubits(QCProgramBuilder& b) { b.x(q[0]); b.x(q[1]); }); + auto c0 = b.measure(q[0]); + auto c1 = b.measure(q[1]); + return {cond, c0, c1}; } -void ifElse(QCProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.h(q[0]); - auto cond = b.measure(q[0]); - b.scfIf(cond, [&] { b.x(q[0]); }, [&] { b.z(q[0]); }); -} - -void nestedIfOpForLoop(QCProgramBuilder& b) { +Value nestedIfOpForLoop(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto q0 = b.allocQubit(); b.h(q0); @@ -1534,9 +1869,10 @@ void nestedIfOpForLoop(QCProgramBuilder& b) { b.h(q1); }); }); + return b.measure(q0); } -void simpleWhileReset(QCProgramBuilder& b) { +Value simpleWhileReset(QCProgramBuilder& b) { auto q = b.allocQubit(); b.h(q); b.scfWhile( @@ -1545,9 +1881,10 @@ void simpleWhileReset(QCProgramBuilder& b) { b.scfCondition(measureResult); }, [&] { b.h(q); }); + return b.measure(q); } -void simpleDoWhileReset(QCProgramBuilder& b) { +Value simpleDoWhileReset(QCProgramBuilder& b) { auto q = b.allocQubit(); b.scfWhile( [&] { @@ -1556,17 +1893,19 @@ void simpleDoWhileReset(QCProgramBuilder& b) { b.scfCondition(measureResult); }, [&] {}); + return b.measure(q); } -void simpleForLoop(QCProgramBuilder& b) { +SmallVector simpleForLoop(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.scfFor(0, 2, 1, [&](Value iv) { auto q = b.memrefLoad(reg.value, iv); b.h(q); }); + return measureAndReturn(b, reg.qubits); }; -void nestedForLoopIfOp(QCProgramBuilder& b) { +Value nestedForLoopIfOp(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2); auto qCond = b.allocQubit(); b.scfFor(0, 2, 1, [&](Value iv) { @@ -1577,9 +1916,10 @@ void nestedForLoopIfOp(QCProgramBuilder& b) { b.h(q); }); }); + return b.measure(qCond); } -void nestedForLoopWhileOp(QCProgramBuilder& b) { +SmallVector nestedForLoopWhileOp(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.scfFor(0, 2, 1, [&](Value iv) { auto q = b.memrefLoad(reg.value, iv); @@ -1594,9 +1934,10 @@ void nestedForLoopWhileOp(QCProgramBuilder& b) { }, [&] { b.h(q); }); }); + return measureAndReturn(b, reg.qubits); } -void nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b) { +Value nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto control = b.allocQubit(); b.h(control); @@ -1605,9 +1946,10 @@ void nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b) { b.h(q0); b.cx(control, q0); }); + return b.measure(control); } -void nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b) { +Value nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.h(reg[0]); b.scfFor(1, 4, 1, [&](Value iv) { @@ -1615,6 +1957,7 @@ void nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b) { b.h(q0); b.cx(reg[0], q0); }); + return b.measure(reg[0]); } } // namespace mlir::qc diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 25d76718cc..4849d861c7 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -10,906 +10,934 @@ #pragma once +#include +#include + namespace mlir::qc { class QCProgramBuilder; /// Creates an empty QC Program. -void emptyQC(QCProgramBuilder& builder); +Value emptyQC(QCProgramBuilder& b); // --- Qubit Management ----------------------------------------------------- // /// Allocates a single qubit. -void allocQubit(QCProgramBuilder& b); +Value allocQubit(QCProgramBuilder& b); + +/// Allocates a single qubit without measuring it. +Value allocQubitNoMeasure(QCProgramBuilder& b); + +/// Allocates a qubit register of size `1`. +Value alloc1QubitRegister(QCProgramBuilder& b); /// Allocates a qubit register of size `2`. -void allocQubitRegister(QCProgramBuilder& b); +SmallVector allocQubitRegister(QCProgramBuilder& b); + +/// Allocates a qubit register of size `3`. +SmallVector alloc3QubitRegister(QCProgramBuilder& b); /// Allocates two qubit registers of size `2` and `3`. -void allocMultipleQubitRegisters(QCProgramBuilder& b); +SmallVector allocMultipleQubitRegisters(QCProgramBuilder& b); /// Allocates two qubit registers of size `2` and `3` and applies operations. -void allocMultipleQubitRegistersWithOps(QCProgramBuilder& b); +SmallVector allocMultipleQubitRegistersWithOps(QCProgramBuilder& b); /// Allocates a large qubit register. -void allocLargeRegister(QCProgramBuilder& b); +SmallVector allocLargeRegister(QCProgramBuilder& b); /// Allocates two inline qubits. -void staticQubits(QCProgramBuilder& b); +SmallVector staticQubits(QCProgramBuilder& b); + +/// Allocates two inline qubits without measuring them. +Value staticQubitsNoMeasure(QCProgramBuilder& b); /// Allocates two static qubits and applies operations. -void staticQubitsWithOps(QCProgramBuilder& b); +SmallVector staticQubitsWithOps(QCProgramBuilder& b); /// Allocates two static qubits and applies parametric gates. -void staticQubitsWithParametricOps(QCProgramBuilder& b); +SmallVector staticQubitsWithParametricOps(QCProgramBuilder& b); /// Allocates two static qubits and applies a two-target gate. -void staticQubitsWithTwoTargetOps(QCProgramBuilder& b); +SmallVector staticQubitsWithTwoTargetOps(QCProgramBuilder& b); /// Allocates two static qubits and applies a controlled gate. -void staticQubitsWithCtrl(QCProgramBuilder& b); +SmallVector staticQubitsWithCtrl(QCProgramBuilder& b); /// Allocates a static qubit and applies an inverse modifier. -void staticQubitsWithInv(QCProgramBuilder& b); +Value staticQubitsWithInv(QCProgramBuilder& b); /// Allocates duplicate static qubits and applies operations on both. -void staticQubitsWithDuplicates(QCProgramBuilder& b); +SmallVector staticQubitsWithDuplicates(QCProgramBuilder& b); /// Same as `staticQubitsWithDuplicates`, but with canonical static qubit /// retrievals. -void staticQubitsCanonical(QCProgramBuilder& b); +SmallVector staticQubitsCanonical(QCProgramBuilder& b); /// Allocates and explicitly deallocates a single qubit. -void allocDeallocPair(QCProgramBuilder& b); +Value allocDeallocPair(QCProgramBuilder& b); // --- Invalid / mixed addressing (unit tests) -------------------------------- /// @pre `builder.initialize()`. Fatal mixed addressing: static then dynamic /// alloc. -void mixedStaticThenDynamicQubit(QCProgramBuilder& b); +SmallVector mixedStaticThenDynamicQubit(QCProgramBuilder& b); /// @pre `builder.initialize()`. Fatal mixed addressing: dynamic register then /// static. -void mixedDynamicRegisterThenStaticQubit(QCProgramBuilder& b); +SmallVector mixedDynamicRegisterThenStaticQubit(QCProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. -void singleMeasurementToSingleBit(QCProgramBuilder& b); +Value singleMeasurementToSingleBit(QCProgramBuilder& b); /// Repeatedly measures a single qubit into the same classical bit. -void repeatedMeasurementToSameBit(QCProgramBuilder& b); +Value repeatedMeasurementToSameBit(QCProgramBuilder& b); /// Repeatedly measures a single qubit into different classical bits. -void repeatedMeasurementToDifferentBits(QCProgramBuilder& b); +SmallVector repeatedMeasurementToDifferentBits(QCProgramBuilder& b); /// Measures multiple qubits into multiple classical bits. -void multipleClassicalRegistersAndMeasurements(QCProgramBuilder& b); +SmallVector +multipleClassicalRegistersAndMeasurements(QCProgramBuilder& b); /// Measures a single qubit into a single classical bit, without explicitly /// allocating a quantum or classical register. -void measurementWithoutRegisters(QCProgramBuilder& b); +Value measurementWithoutRegisters(QCProgramBuilder& b); // --- ResetOp -------------------------------------------------------------- // /// Resets a single qubit without any operations being applied. -void resetQubitWithoutOp(QCProgramBuilder& b); +Value resetQubitWithoutOp(QCProgramBuilder& b); /// Resets multiple qubits without any operations being applied. -void resetMultipleQubitsWithoutOp(QCProgramBuilder& b); +SmallVector resetMultipleQubitsWithoutOp(QCProgramBuilder& b); /// Repeatedly resets a single qubit without any operations being applied. -void repeatedResetWithoutOp(QCProgramBuilder& b); +Value repeatedResetWithoutOp(QCProgramBuilder& b); /// Resets a single qubit after a single operation. -void resetQubitAfterSingleOp(QCProgramBuilder& b); +Value resetQubitAfterSingleOp(QCProgramBuilder& b); /// Resets multiple qubits after a single operation. -void resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b); +SmallVector resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b); /// Repeatedly resets a single qubit after a single operation. -void repeatedResetAfterSingleOp(QCProgramBuilder& b); +Value repeatedResetAfterSingleOp(QCProgramBuilder& b); // --- GPhaseOp ------------------------------------------------------------- // /// Creates a circuit with just a global phase. -void globalPhase(QCProgramBuilder& b); +Value globalPhase(QCProgramBuilder& b); + +/// Creates a circuit with just a global phase and a single measured qubit. +Value globalPhaseAndMeasure(QCProgramBuilder& b); /// Creates a controlled global phase gate with a single control qubit. -void singleControlledGlobalPhase(QCProgramBuilder& b); +Value singleControlledGlobalPhase(QCProgramBuilder& b); /// Creates a multi-controlled global phase gate with multiple control qubits. -void multipleControlledGlobalPhase(QCProgramBuilder& b); +SmallVector multipleControlledGlobalPhase(QCProgramBuilder& b); /// Creates a circuit with a nested controlled global phase gate. -void nestedControlledGlobalPhase(QCProgramBuilder& b); +SmallVector nestedControlledGlobalPhase(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled global phase gate. -void trivialControlledGlobalPhase(QCProgramBuilder& b); +Value trivialControlledGlobalPhase(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a global phase gate. -void inverseGlobalPhase(QCProgramBuilder& b); +Value inverseGlobalPhase(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled global /// phase gate. -void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b); +SmallVector inverseMultipleControlledGlobalPhase(QCProgramBuilder& b); // --- IdOp ----------------------------------------------------------------- // /// Creates a circuit with just an identity gate. -void identity(QCProgramBuilder& b); +Value identity(QCProgramBuilder& b); /// Creates a controlled identity gate with a single control qubit. -void singleControlledIdentity(QCProgramBuilder& b); +SmallVector singleControlledIdentity(QCProgramBuilder& b); + +/// Creates an identity gate on a single qubit in a two-qubit register. +SmallVector twoQubitsOneIdentity(QCProgramBuilder& b); + +/// Creates an identity gate on a single qubit in a three-qubit register. +SmallVector threeQubitsOneIdentity(QCProgramBuilder& b); /// Creates a multi-controlled identity gate with multiple control qubits. -void multipleControlledIdentity(QCProgramBuilder& b); +SmallVector multipleControlledIdentity(QCProgramBuilder& b); + +/// Creates an barrier gate on a single qubit in a two-qubit register. +SmallVector twoQubitsOneBarrier(QCProgramBuilder& b); /// Creates a circuit with a nested controlled identity gate. -void nestedControlledIdentity(QCProgramBuilder& b); +SmallVector nestedControlledIdentity(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled identity gate. -void trivialControlledIdentity(QCProgramBuilder& b); +Value trivialControlledIdentity(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an identity gate. -void inverseIdentity(QCProgramBuilder& b); +Value inverseIdentity(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled identity /// gate. -void inverseMultipleControlledIdentity(QCProgramBuilder& b); +SmallVector inverseMultipleControlledIdentity(QCProgramBuilder& b); // --- XOp ------------------------------------------------------------------ // /// Creates a circuit with just an X gate. -void x(QCProgramBuilder& b); +Value x(QCProgramBuilder& b); /// Creates a circuit with a single controlled X gate. -void singleControlledX(QCProgramBuilder& b); +SmallVector singleControlledX(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled X gate. -void multipleControlledX(QCProgramBuilder& b); +SmallVector multipleControlledX(QCProgramBuilder& b); /// Creates a circuit with a nested controlled X gate. -void nestedControlledX(QCProgramBuilder& b); +SmallVector nestedControlledX(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled X gate. -void trivialControlledX(QCProgramBuilder& b); +Value trivialControlledX(QCProgramBuilder& b); /// Creates a circuit with repeated controlled X gates. -void repeatedControlledX(QCProgramBuilder& b); +SmallVector repeatedControlledX(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an X gate. -void inverseX(QCProgramBuilder& b); +Value inverseX(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled X gate. -void inverseMultipleControlledX(QCProgramBuilder& b); +SmallVector inverseMultipleControlledX(QCProgramBuilder& b); // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. -void y(QCProgramBuilder& b); +Value y(QCProgramBuilder& b); /// Creates a circuit with a single controlled Y gate. -void singleControlledY(QCProgramBuilder& b); +SmallVector singleControlledY(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled Y gate. -void multipleControlledY(QCProgramBuilder& b); +SmallVector multipleControlledY(QCProgramBuilder& b); /// Creates a circuit with a nested controlled Y gate. -void nestedControlledY(QCProgramBuilder& b); +SmallVector nestedControlledY(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled Y gate. -void trivialControlledY(QCProgramBuilder& b); +Value trivialControlledY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Y gate. -void inverseY(QCProgramBuilder& b); +Value inverseY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Y gate. -void inverseMultipleControlledY(QCProgramBuilder& b); +SmallVector inverseMultipleControlledY(QCProgramBuilder& b); // --- ZOp ------------------------------------------------------------------ // /// Creates a circuit with just a Z gate. -void z(QCProgramBuilder& b); +Value z(QCProgramBuilder& b); /// Creates a circuit with a single controlled Z gate. -void singleControlledZ(QCProgramBuilder& b); +SmallVector singleControlledZ(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled Z gate. -void multipleControlledZ(QCProgramBuilder& b); +SmallVector multipleControlledZ(QCProgramBuilder& b); /// Creates a circuit with a nested controlled Z gate. -void nestedControlledZ(QCProgramBuilder& b); +SmallVector nestedControlledZ(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled Z gate. -void trivialControlledZ(QCProgramBuilder& b); +Value trivialControlledZ(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Z gate. -void inverseZ(QCProgramBuilder& b); +Value inverseZ(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Z gate. -void inverseMultipleControlledZ(QCProgramBuilder& b); +SmallVector inverseMultipleControlledZ(QCProgramBuilder& b); // --- HOp ------------------------------------------------------------------ // /// Creates a circuit with just an H gate. -void h(QCProgramBuilder& b); +Value h(QCProgramBuilder& b); /// Creates a circuit with a single controlled H gate. -void singleControlledH(QCProgramBuilder& b); +SmallVector singleControlledH(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled H gate. -void multipleControlledH(QCProgramBuilder& b); +SmallVector multipleControlledH(QCProgramBuilder& b); /// Creates a circuit with a nested controlled H gate. -void nestedControlledH(QCProgramBuilder& b); +SmallVector nestedControlledH(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled H gate. -void trivialControlledH(QCProgramBuilder& b); +Value trivialControlledH(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an H gate. -void inverseH(QCProgramBuilder& b); +Value inverseH(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled H gate. -void inverseMultipleControlledH(QCProgramBuilder& b); +SmallVector inverseMultipleControlledH(QCProgramBuilder& b); /// Creates a circuit with just an H gate and no qubit register. -void hWithoutRegister(QCProgramBuilder& b); +Value hWithoutRegister(QCProgramBuilder& b); // --- SOp ------------------------------------------------------------------ // /// Creates a circuit with just an S gate. -void s(QCProgramBuilder& b); +Value s(QCProgramBuilder& b); /// Creates a circuit with a single controlled S gate. -void singleControlledS(QCProgramBuilder& b); +SmallVector singleControlledS(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled S gate. -void multipleControlledS(QCProgramBuilder& b); +SmallVector multipleControlledS(QCProgramBuilder& b); /// Creates a circuit with a nested controlled S gate. -void nestedControlledS(QCProgramBuilder& b); +SmallVector nestedControlledS(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled S gate. -void trivialControlledS(QCProgramBuilder& b); +Value trivialControlledS(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an S gate. -void inverseS(QCProgramBuilder& b); +Value inverseS(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled S gate. -void inverseMultipleControlledS(QCProgramBuilder& b); +SmallVector inverseMultipleControlledS(QCProgramBuilder& b); // --- SdgOp ---------------------------------------------------------------- // /// Creates a circuit with just an Sdg gate. -void sdg(QCProgramBuilder& b); +Value sdg(QCProgramBuilder& b); /// Creates a circuit with a single controlled Sdg gate. -void singleControlledSdg(QCProgramBuilder& b); +SmallVector singleControlledSdg(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled Sdg gate. -void multipleControlledSdg(QCProgramBuilder& b); +SmallVector multipleControlledSdg(QCProgramBuilder& b); /// Creates a circuit with a nested controlled Sdg gate. -void nestedControlledSdg(QCProgramBuilder& b); +SmallVector nestedControlledSdg(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled Sdg gate. -void trivialControlledSdg(QCProgramBuilder& b); +Value trivialControlledSdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an Sdg gate. -void inverseSdg(QCProgramBuilder& b); +Value inverseSdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Sdg gate. -void inverseMultipleControlledSdg(QCProgramBuilder& b); +SmallVector inverseMultipleControlledSdg(QCProgramBuilder& b); // --- TOp ------------------------------------------------------------------ // /// Creates a circuit with just a T gate. -void t_(QCProgramBuilder& b); // NOLINT(*-identifier-naming) +Value t_(QCProgramBuilder& b); // NOLINT(*-identifier-naming) /// Creates a circuit with a single controlled T gate. -void singleControlledT(QCProgramBuilder& b); +SmallVector singleControlledT(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled T gate. -void multipleControlledT(QCProgramBuilder& b); +SmallVector multipleControlledT(QCProgramBuilder& b); /// Creates a circuit with a nested controlled T gate. -void nestedControlledT(QCProgramBuilder& b); +SmallVector nestedControlledT(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled T gate. -void trivialControlledT(QCProgramBuilder& b); +Value trivialControlledT(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a T gate. -void inverseT(QCProgramBuilder& b); +Value inverseT(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled T gate. -void inverseMultipleControlledT(QCProgramBuilder& b); +SmallVector inverseMultipleControlledT(QCProgramBuilder& b); // --- TdgOp ---------------------------------------------------------------- // /// Creates a circuit with just a Tdg gate. -void tdg(QCProgramBuilder& b); +Value tdg(QCProgramBuilder& b); /// Creates a circuit with a single controlled Tdg gate. -void singleControlledTdg(QCProgramBuilder& b); +SmallVector singleControlledTdg(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled Tdg gate. -void multipleControlledTdg(QCProgramBuilder& b); +SmallVector multipleControlledTdg(QCProgramBuilder& b); /// Creates a circuit with a nested controlled Tdg gate. -void nestedControlledTdg(QCProgramBuilder& b); +SmallVector nestedControlledTdg(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled Tdg gate. -void trivialControlledTdg(QCProgramBuilder& b); +Value trivialControlledTdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Tdg gate. -void inverseTdg(QCProgramBuilder& b); +Value inverseTdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Tdg gate. -void inverseMultipleControlledTdg(QCProgramBuilder& b); +SmallVector inverseMultipleControlledTdg(QCProgramBuilder& b); // --- SXOp ----------------------------------------------------------------- // /// Creates a circuit with just an SX gate. -void sx(QCProgramBuilder& b); +Value sx(QCProgramBuilder& b); /// Creates a circuit with a single controlled SX gate. -void singleControlledSx(QCProgramBuilder& b); +SmallVector singleControlledSx(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled SX gate. -void multipleControlledSx(QCProgramBuilder& b); +SmallVector multipleControlledSx(QCProgramBuilder& b); /// Creates a circuit with a nested controlled SX gate. -void nestedControlledSx(QCProgramBuilder& b); +SmallVector nestedControlledSx(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled SX gate. -void trivialControlledSx(QCProgramBuilder& b); +Value trivialControlledSx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an SX gate. -void inverseSx(QCProgramBuilder& b); +Value inverseSx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SX gate. -void inverseMultipleControlledSx(QCProgramBuilder& b); +SmallVector inverseMultipleControlledSx(QCProgramBuilder& b); // --- SXdgOp --------------------------------------------------------------- // /// Creates a circuit with just an SXdg gate. -void sxdg(QCProgramBuilder& b); +Value sxdg(QCProgramBuilder& b); /// Creates a circuit with a single controlled SXdg gate. -void singleControlledSxdg(QCProgramBuilder& b); +SmallVector singleControlledSxdg(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled SXdg gate. -void multipleControlledSxdg(QCProgramBuilder& b); +SmallVector multipleControlledSxdg(QCProgramBuilder& b); /// Creates a circuit with a nested controlled SXdg gate. -void nestedControlledSxdg(QCProgramBuilder& b); +SmallVector nestedControlledSxdg(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled SXdg gate. -void trivialControlledSxdg(QCProgramBuilder& b); +Value trivialControlledSxdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an SXdg gate. -void inverseSxdg(QCProgramBuilder& b); +Value inverseSxdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SXdg /// gate. -void inverseMultipleControlledSxdg(QCProgramBuilder& b); +SmallVector inverseMultipleControlledSxdg(QCProgramBuilder& b); // --- RXOp ----------------------------------------------------------------- // /// Creates a circuit with just an RX gate. -void rx(QCProgramBuilder& b); +Value rx(QCProgramBuilder& b); /// Creates a circuit with a single controlled RX gate. -void singleControlledRx(QCProgramBuilder& b); +SmallVector singleControlledRx(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RX gate. -void multipleControlledRx(QCProgramBuilder& b); +SmallVector multipleControlledRx(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RX gate. -void nestedControlledRx(QCProgramBuilder& b); +SmallVector nestedControlledRx(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RX gate. -void trivialControlledRx(QCProgramBuilder& b); +Value trivialControlledRx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RX gate. -void inverseRx(QCProgramBuilder& b); +Value inverseRx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RX gate. -void inverseMultipleControlledRx(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRx(QCProgramBuilder& b); // --- RYOp ----------------------------------------------------------------- // /// Creates a circuit with just an RY gate. -void ry(QCProgramBuilder& b); +Value ry(QCProgramBuilder& b); /// Creates a circuit with a single controlled RY gate. -void singleControlledRy(QCProgramBuilder& b); +SmallVector singleControlledRy(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RY gate. -void multipleControlledRy(QCProgramBuilder& b); +SmallVector multipleControlledRy(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RY gate. -void nestedControlledRy(QCProgramBuilder& b); +SmallVector nestedControlledRy(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RY gate. -void trivialControlledRy(QCProgramBuilder& b); +Value trivialControlledRy(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RY gate. -void inverseRy(QCProgramBuilder& b); +Value inverseRy(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RY gate. -void inverseMultipleControlledRy(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRy(QCProgramBuilder& b); // --- RZOp ----------------------------------------------------------------- // /// Creates a circuit with just an RZ gate. -void rz(QCProgramBuilder& b); +Value rz(QCProgramBuilder& b); /// Creates a circuit with a single controlled RZ gate. -void singleControlledRz(QCProgramBuilder& b); +SmallVector singleControlledRz(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RZ gate. -void multipleControlledRz(QCProgramBuilder& b); +SmallVector multipleControlledRz(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RZ gate. -void nestedControlledRz(QCProgramBuilder& b); +SmallVector nestedControlledRz(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RZ gate. -void trivialControlledRz(QCProgramBuilder& b); +Value trivialControlledRz(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZ gate. -void inverseRz(QCProgramBuilder& b); +Value inverseRz(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZ gate. -void inverseMultipleControlledRz(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRz(QCProgramBuilder& b); // --- POp ------------------------------------------------------------------ // /// Creates a circuit with just a P gate. -void p(QCProgramBuilder& b); +Value p(QCProgramBuilder& b); /// Creates a circuit with a single controlled P gate. -void singleControlledP(QCProgramBuilder& b); +SmallVector singleControlledP(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled P gate. -void multipleControlledP(QCProgramBuilder& b); +SmallVector multipleControlledP(QCProgramBuilder& b); /// Creates a circuit with a nested controlled P gate. -void nestedControlledP(QCProgramBuilder& b); +SmallVector nestedControlledP(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled P gate. -void trivialControlledP(QCProgramBuilder& b); +Value trivialControlledP(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a P gate. -void inverseP(QCProgramBuilder& b); +Value inverseP(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled P gate. -void inverseMultipleControlledP(QCProgramBuilder& b); +SmallVector inverseMultipleControlledP(QCProgramBuilder& b); // --- ROp ------------------------------------------------------------------ // /// Creates a circuit with just an R gate. -void r(QCProgramBuilder& b); +Value r(QCProgramBuilder& b); /// Creates a circuit with a single controlled R gate. -void singleControlledR(QCProgramBuilder& b); +SmallVector singleControlledR(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled R gate. -void multipleControlledR(QCProgramBuilder& b); +SmallVector multipleControlledR(QCProgramBuilder& b); /// Creates a circuit with a nested controlled R gate. -void nestedControlledR(QCProgramBuilder& b); +SmallVector nestedControlledR(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled R gate. -void trivialControlledR(QCProgramBuilder& b); +Value trivialControlledR(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an R gate. -void inverseR(QCProgramBuilder& b); +Value inverseR(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled R gate. -void inverseMultipleControlledR(QCProgramBuilder& b); +SmallVector inverseMultipleControlledR(QCProgramBuilder& b); // --- U2Op ----------------------------------------------------------------- // /// Creates a circuit with just a U2 gate. -void u2(QCProgramBuilder& b); +Value u2(QCProgramBuilder& b); /// Creates a circuit with a single controlled U2 gate. -void singleControlledU2(QCProgramBuilder& b); +SmallVector singleControlledU2(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled U2 gate. -void multipleControlledU2(QCProgramBuilder& b); +SmallVector multipleControlledU2(QCProgramBuilder& b); /// Creates a circuit with a nested controlled U2 gate. -void nestedControlledU2(QCProgramBuilder& b); +SmallVector nestedControlledU2(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled U2 gate. -void trivialControlledU2(QCProgramBuilder& b); +Value trivialControlledU2(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a U2 gate. -void inverseU2(QCProgramBuilder& b); +Value inverseU2(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled U2 gate. -void inverseMultipleControlledU2(QCProgramBuilder& b); +SmallVector inverseMultipleControlledU2(QCProgramBuilder& b); // --- UOp ------------------------------------------------------------------ // /// Creates a circuit with just a U gate. -void u(QCProgramBuilder& b); +Value u(QCProgramBuilder& b); /// Creates a circuit with a single controlled U gate. -void singleControlledU(QCProgramBuilder& b); +SmallVector singleControlledU(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled U gate. -void multipleControlledU(QCProgramBuilder& b); +SmallVector multipleControlledU(QCProgramBuilder& b); /// Creates a circuit with a nested controlled U gate. -void nestedControlledU(QCProgramBuilder& b); +SmallVector nestedControlledU(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled U gate. -void trivialControlledU(QCProgramBuilder& b); +Value trivialControlledU(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a U gate. -void inverseU(QCProgramBuilder& b); +Value inverseU(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled U gate. -void inverseMultipleControlledU(QCProgramBuilder& b); +SmallVector inverseMultipleControlledU(QCProgramBuilder& b); // --- SWAPOp --------------------------------------------------------------- // /// Creates a circuit with just a SWAP gate. -void swap(QCProgramBuilder& b); +SmallVector swap(QCProgramBuilder& b); /// Creates a circuit with a single controlled SWAP gate. -void singleControlledSwap(QCProgramBuilder& b); +SmallVector singleControlledSwap(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled SWAP gate. -void multipleControlledSwap(QCProgramBuilder& b); +SmallVector multipleControlledSwap(QCProgramBuilder& b); /// Creates a circuit with a nested controlled SWAP gate. -void nestedControlledSwap(QCProgramBuilder& b); +SmallVector nestedControlledSwap(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled SWAP gate. -void trivialControlledSwap(QCProgramBuilder& b); +SmallVector trivialControlledSwap(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a SWAP gate. -void inverseSwap(QCProgramBuilder& b); +SmallVector inverseSwap(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SWAP /// gate. -void inverseMultipleControlledSwap(QCProgramBuilder& b); +SmallVector inverseMultipleControlledSwap(QCProgramBuilder& b); // --- iSWAPOp -------------------------------------------------------------- // /// Creates a circuit with just an iSWAP gate. -void iswap(QCProgramBuilder& b); +SmallVector iswap(QCProgramBuilder& b); /// Creates a circuit with a single controlled iSWAP gate. -void singleControlledIswap(QCProgramBuilder& b); +SmallVector singleControlledIswap(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled iSWAP gate. -void multipleControlledIswap(QCProgramBuilder& b); +SmallVector multipleControlledIswap(QCProgramBuilder& b); /// Creates a circuit with a nested controlled iSWAP gate. -void nestedControlledIswap(QCProgramBuilder& b); +SmallVector nestedControlledIswap(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled iSWAP gate. -void trivialControlledIswap(QCProgramBuilder& b); +SmallVector trivialControlledIswap(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an iSWAP gate. -void inverseIswap(QCProgramBuilder& b); +SmallVector inverseIswap(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled iSWAP /// gate. -void inverseMultipleControlledIswap(QCProgramBuilder& b); +SmallVector inverseMultipleControlledIswap(QCProgramBuilder& b); // --- DCXOp ---------------------------------------------------------------- // /// Creates a circuit with just a DCX gate. -void dcx(QCProgramBuilder& b); +SmallVector dcx(QCProgramBuilder& b); /// Creates a circuit with a single controlled DCX gate. -void singleControlledDcx(QCProgramBuilder& b); +SmallVector singleControlledDcx(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled DCX gate. -void multipleControlledDcx(QCProgramBuilder& b); +SmallVector multipleControlledDcx(QCProgramBuilder& b); /// Creates a circuit with a nested controlled DCX gate. -void nestedControlledDcx(QCProgramBuilder& b); +SmallVector nestedControlledDcx(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled DCX gate. -void trivialControlledDcx(QCProgramBuilder& b); +SmallVector trivialControlledDcx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a DCX gate. -void inverseDcx(QCProgramBuilder& b); +SmallVector inverseDcx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled DCX gate. -void inverseMultipleControlledDcx(QCProgramBuilder& b); +SmallVector inverseMultipleControlledDcx(QCProgramBuilder& b); // --- ECROp ---------------------------------------------------------------- // /// Creates a circuit with just an ECR gate. -void ecr(QCProgramBuilder& b); +SmallVector ecr(QCProgramBuilder& b); /// Creates a circuit with a single controlled ECR gate. -void singleControlledEcr(QCProgramBuilder& b); +SmallVector singleControlledEcr(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled ECR gate. -void multipleControlledEcr(QCProgramBuilder& b); +SmallVector multipleControlledEcr(QCProgramBuilder& b); /// Creates a circuit with a nested controlled ECR gate. -void nestedControlledEcr(QCProgramBuilder& b); +SmallVector nestedControlledEcr(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled ECR gate. -void trivialControlledEcr(QCProgramBuilder& b); +SmallVector trivialControlledEcr(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an ECR gate. -void inverseEcr(QCProgramBuilder& b); +SmallVector inverseEcr(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled ECR gate. -void inverseMultipleControlledEcr(QCProgramBuilder& b); +SmallVector inverseMultipleControlledEcr(QCProgramBuilder& b); // --- RXXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RXX gate. -void rxx(QCProgramBuilder& b); +SmallVector rxx(QCProgramBuilder& b); /// Creates a circuit with a single controlled RXX gate. -void singleControlledRxx(QCProgramBuilder& b); +SmallVector singleControlledRxx(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RXX gate. -void multipleControlledRxx(QCProgramBuilder& b); +SmallVector multipleControlledRxx(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RXX gate. -void nestedControlledRxx(QCProgramBuilder& b); +SmallVector nestedControlledRxx(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RXX gate. -void trivialControlledRxx(QCProgramBuilder& b); +SmallVector trivialControlledRxx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RXX gate. -void inverseRxx(QCProgramBuilder& b); +SmallVector inverseRxx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RXX gate. -void inverseMultipleControlledRxx(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRxx(QCProgramBuilder& b); /// Creates a circuit with a triple-controlled RXX gate. -void tripleControlledRxx(QCProgramBuilder& b); +SmallVector tripleControlledRxx(QCProgramBuilder& b); /// Creates a circuit with a four-controlled RXX gate. -void fourControlledRxx(QCProgramBuilder& b); +SmallVector fourControlledRxx(QCProgramBuilder& b); // --- RYYOp ---------------------------------------------------------------- // /// Creates a circuit with just an RYY gate. -void ryy(QCProgramBuilder& b); +SmallVector ryy(QCProgramBuilder& b); /// Creates a circuit with a single controlled RYY gate. -void singleControlledRyy(QCProgramBuilder& b); +SmallVector singleControlledRyy(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RYY gate. -void multipleControlledRyy(QCProgramBuilder& b); +SmallVector multipleControlledRyy(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RYY gate. -void nestedControlledRyy(QCProgramBuilder& b); +SmallVector nestedControlledRyy(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RYY gate. -void trivialControlledRyy(QCProgramBuilder& b); +SmallVector trivialControlledRyy(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RYY gate. -void inverseRyy(QCProgramBuilder& b); +SmallVector inverseRyy(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RYY gate. -void inverseMultipleControlledRyy(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRyy(QCProgramBuilder& b); // --- RZXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZX gate. -void rzx(QCProgramBuilder& b); +SmallVector rzx(QCProgramBuilder& b); /// Creates a circuit with a single controlled RZX gate. -void singleControlledRzx(QCProgramBuilder& b); +SmallVector singleControlledRzx(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RZX gate. -void multipleControlledRzx(QCProgramBuilder& b); +SmallVector multipleControlledRzx(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RZX gate. -void nestedControlledRzx(QCProgramBuilder& b); +SmallVector nestedControlledRzx(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RZX gate. -void trivialControlledRzx(QCProgramBuilder& b); +SmallVector trivialControlledRzx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZX gate. -void inverseRzx(QCProgramBuilder& b); +SmallVector inverseRzx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZX gate. -void inverseMultipleControlledRzx(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRzx(QCProgramBuilder& b); // --- RZZOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZZ gate. -void rzz(QCProgramBuilder& b); +SmallVector rzz(QCProgramBuilder& b); /// Creates a circuit with a single controlled RZZ gate. -void singleControlledRzz(QCProgramBuilder& b); +SmallVector singleControlledRzz(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled RZZ gate. -void multipleControlledRzz(QCProgramBuilder& b); +SmallVector multipleControlledRzz(QCProgramBuilder& b); /// Creates a circuit with a nested controlled RZZ gate. -void nestedControlledRzz(QCProgramBuilder& b); +SmallVector nestedControlledRzz(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled RZZ gate. -void trivialControlledRzz(QCProgramBuilder& b); +SmallVector trivialControlledRzz(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZZ gate. -void inverseRzz(QCProgramBuilder& b); +SmallVector inverseRzz(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZZ gate. -void inverseMultipleControlledRzz(QCProgramBuilder& b); +SmallVector inverseMultipleControlledRzz(QCProgramBuilder& b); // --- XXPlusYYOp ----------------------------------------------------------- // /// Creates a circuit with just an XXPlusYY gate. -void xxPlusYY(QCProgramBuilder& b); +SmallVector xxPlusYY(QCProgramBuilder& b); /// Creates a circuit with a single controlled XXPlusYY gate. -void singleControlledXxPlusYY(QCProgramBuilder& b); +SmallVector singleControlledXxPlusYY(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled XXPlusYY gate. -void multipleControlledXxPlusYY(QCProgramBuilder& b); +SmallVector multipleControlledXxPlusYY(QCProgramBuilder& b); /// Creates a circuit with a nested controlled XXPlusYY gate. -void nestedControlledXxPlusYY(QCProgramBuilder& b); +SmallVector nestedControlledXxPlusYY(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled XXPlusYY gate. -void trivialControlledXxPlusYY(QCProgramBuilder& b); +SmallVector trivialControlledXxPlusYY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an XXPlusYY gate. -void inverseXxPlusYY(QCProgramBuilder& b); +SmallVector inverseXxPlusYY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled XXPlusYY /// gate. -void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b); +SmallVector inverseMultipleControlledXxPlusYY(QCProgramBuilder& b); // --- XXMinusYYOp ---------------------------------------------------------- // /// Creates a circuit with just an XXMinusYY gate. -void xxMinusYY(QCProgramBuilder& b); +SmallVector xxMinusYY(QCProgramBuilder& b); /// Creates a circuit with a single controlled XXMinusYY gate. -void singleControlledXxMinusYY(QCProgramBuilder& b); +SmallVector singleControlledXxMinusYY(QCProgramBuilder& b); /// Creates a circuit with a multi-controlled XXMinusYY gate. -void multipleControlledXxMinusYY(QCProgramBuilder& b); +SmallVector multipleControlledXxMinusYY(QCProgramBuilder& b); /// Creates a circuit with a nested controlled XXMinusYY gate. -void nestedControlledXxMinusYY(QCProgramBuilder& b); +SmallVector nestedControlledXxMinusYY(QCProgramBuilder& b); /// Creates a circuit with a trivial controlled XXMinusYY gate. -void trivialControlledXxMinusYY(QCProgramBuilder& b); +SmallVector trivialControlledXxMinusYY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an XXMinusYY gate. -void inverseXxMinusYY(QCProgramBuilder& b); +SmallVector inverseXxMinusYY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled XXMinusYY /// gate. -void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b); +SmallVector inverseMultipleControlledXxMinusYY(QCProgramBuilder& b); // --- BarrierOp ------------------------------------------------------------ // /// Creates a circuit with a barrier. -void barrier(QCProgramBuilder& b); +Value barrier(QCProgramBuilder& b); /// Creates a circuit with a barrier on two qubits. -void barrierTwoQubits(QCProgramBuilder& b); +SmallVector barrierTwoQubits(QCProgramBuilder& b); /// Creates a circuit with a barrier on multiple qubits. -void barrierMultipleQubits(QCProgramBuilder& b); +SmallVector barrierMultipleQubits(QCProgramBuilder& b); /// Creates a circuit with a single controlled barrier. -void singleControlledBarrier(QCProgramBuilder& b); +SmallVector singleControlledBarrier(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a barrier. -void inverseBarrier(QCProgramBuilder& b); +Value inverseBarrier(QCProgramBuilder& b); // --- CtrlOp --------------------------------------------------------------- // /// Creates a circuit with a trivial ctrl modifier. -void trivialCtrl(QCProgramBuilder& b); +SmallVector trivialCtrl(QCProgramBuilder& b); /// Creates a circuit with an empty ctrl modifier. -void emptyCtrl(QCProgramBuilder& b); +SmallVector emptyCtrl(QCProgramBuilder& b); /// Creates a circuit with nested ctrl modifiers. -void nestedCtrl(QCProgramBuilder& b); +SmallVector nestedCtrl(QCProgramBuilder& b); /// Creates a circuit with triple nested ctrl modifiers. -void tripleNestedCtrl(QCProgramBuilder& b); +SmallVector tripleNestedCtrl(QCProgramBuilder& b); /// Creates a circuit with double nested ctrl modifiers with two qubits each. -void doubleNestedCtrlTwoQubits(QCProgramBuilder& b); +SmallVector doubleNestedCtrlTwoQubits(QCProgramBuilder& b); /// Creates a circuit with control modifiers interleaved by an inverse modifier. -void ctrlInvSandwich(QCProgramBuilder& b); +SmallVector ctrlInvSandwich(QCProgramBuilder& b); /// Creates a circuit with a control modifier applied to two gates. -void ctrlTwo(QCProgramBuilder& b); +SmallVector ctrlTwo(QCProgramBuilder& b); /// Creates a circuit with a control modifier applied to a controlled and a /// non-controlled gate. -void ctrlTwoMixed(QCProgramBuilder& b); +SmallVector ctrlTwoMixed(QCProgramBuilder& b); /// Creates a circuit with nested control modifiers applied to two gates. -void nestedCtrlTwo(QCProgramBuilder& b); +SmallVector nestedCtrlTwo(QCProgramBuilder& b); /// Creates a circuit with a control modifier applied to a inverse modifier /// applied to two gates. -void ctrlInvTwo(QCProgramBuilder& b); +SmallVector ctrlInvTwo(QCProgramBuilder& b); // --- InvOp ---------------------------------------------------------------- // /// Creates a circuit with an empty inverse modifier. -void emptyInv(QCProgramBuilder& b); +SmallVector emptyInv(QCProgramBuilder& b); /// Creates a circuit with nested inverse modifiers. -void nestedInv(QCProgramBuilder& b); +SmallVector nestedInv(QCProgramBuilder& b); /// Creates a circuit with triple nested inverse modifiers. -void tripleNestedInv(QCProgramBuilder& b); +SmallVector tripleNestedInv(QCProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. -void invCtrlSandwich(QCProgramBuilder& b); +SmallVector invCtrlSandwich(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to two gates. -void invTwo(QCProgramBuilder& b); +SmallVector invTwo(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a control modifier /// applied to two gates. -void invCtrlTwo(QCProgramBuilder& b); +SmallVector invCtrlTwo(QCProgramBuilder& b); // --- IfOp ----------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. -void simpleIf(QCProgramBuilder& b); - -/// Creates a circuit with an if operation with two qubits. -void ifTwoQubits(QCProgramBuilder& b); +SmallVector simpleIf(QCProgramBuilder& b); /// Creates a circuit with an if operation with an else branch. -void ifElse(QCProgramBuilder& b); +SmallVector ifElse(QCProgramBuilder& b); + +/// Creates a circuit with an if operation with two qubits. +SmallVector ifTwoQubits(QCProgramBuilder& b); /// Creates a circuit with an if operation with a nested for operation with /// a register. -void nestedIfOpForLoop(QCProgramBuilder& b); +Value nestedIfOpForLoop(QCProgramBuilder& b); // --- WhileOp -------------------------------------------------------------- // /// Creates a circuit with a while operation using a while loop. -void simpleWhileReset(QCProgramBuilder& b); +Value simpleWhileReset(QCProgramBuilder& b); /// Creates a circuit with a while operation using a do-while loop. -void simpleDoWhileReset(QCProgramBuilder& b); +Value simpleDoWhileReset(QCProgramBuilder& b); // --- ForOp ---------------------------------------------------------------- // /// Creates a circuit with a simple for operation with a register. -void simpleForLoop(QCProgramBuilder& b); +SmallVector simpleForLoop(QCProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested if operation. -void nestedForLoopIfOp(QCProgramBuilder& b); +Value nestedForLoopIfOp(QCProgramBuilder& b); /// Creates a circuit with a for operation with a register and a nested while /// operation. -void nestedForLoopWhileOp(QCProgramBuilder& b); +SmallVector nestedForLoopWhileOp(QCProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is separately allocated from the /// register. -void nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b); +Value nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is extracted from the register. -void nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b); +Value nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b); } // namespace mlir::qc diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index b384aa20a1..4cafe6572d 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -14,2007 +14,2804 @@ #include #include +#include #include +#include #include #include +#include namespace mlir::qco { -void emptyQCO([[maybe_unused]] QCOProgramBuilder& builder) {} +/** + * @brief Measures the given `qtensor` and returns the measurement outcomes. + * @param b The `ProgramBuilder` used to perform the measurements. + * @param qTensor The `qtensor` to be measured. + * @param size The number of qubits in the `qtensor`. + * @return The result values. + */ +static SmallVector measureAndReturnQTensor(QCOProgramBuilder& b, + Value qTensor, + const int64_t size) { + SmallVector bits; + for (auto i = 0; i < size; ++i) { + auto [qTensorOut, qubit] = b.qtensorExtract(qTensor, i); + auto [q2, bit] = b.measure(qubit); + bits.push_back(bit); + qTensor = b.qtensorInsert(q2, qTensorOut, i); + } + return bits; +} + +/** + * @brief Measures the given qubits and returns the measurement outcomes. + * @param b The `ProgramBuilder` used to perform the measurements. + * @param qubits The qubits to be measured. + * @return The result values. + */ +static SmallVector measureAndReturn(QCOProgramBuilder& b, + ValueRange qubits) { + return llvm::to_vector( + llvm::map_range(qubits, [&](Value q) { return b.measure(q).second; })); +} + +Value emptyQCO(QCOProgramBuilder& b) { return b.intConstant(0); } + +Value allocQubit(QCOProgramBuilder& b) { + auto q = b.allocQubit(); + return b.measure(q).second; +} + +SmallVector alloc2Qubits(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto q1 = b.allocQubit(); + return measureAndReturn(b, {q0, q1}); +} -void allocQubit(QCOProgramBuilder& b) { b.allocQubit(); } +Value allocQubitNoMeasure(QCOProgramBuilder& b) { + (void)b.allocQubit(); + return b.intConstant(0); +} + +Value alloc1QubitRegister(QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + return b.measure(reg[0]).second; +} + +SmallVector alloc2QubitRegister(QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + return measureAndReturn(b, reg.qubits); +} + +SmallVector alloc3QubitRegister(QCOProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + return measureAndReturn(b, reg.qubits); +} -void allocQubitRegister(QCOProgramBuilder& b) { b.allocQubitRegister(2); } +SmallVector allocMultipleQubitRegisters(QCOProgramBuilder& b) { + auto r1 = b.allocQubitRegister(2); + auto r2 = b.allocQubitRegister(3); + return measureAndReturn(b, {r1[0], r1[1], r2[0], r2[1], r2[2]}); +} -void allocMultipleQubitRegisters(QCOProgramBuilder& b) { - b.allocQubitRegister(2); - b.allocQubitRegister(3); +Value allocLargeRegister(QCOProgramBuilder& b) { + auto r = b.allocQubitRegister(100); + return b.measure(r[0]).second; } -void allocLargeRegister(QCOProgramBuilder& b) { b.allocQubitRegister(100); } +Value staticQubitsNoMeasure(QCOProgramBuilder& b) { + (void)b.staticQubit(0); + (void)b.staticQubit(1); + return b.intConstant(0); +} -void staticQubits(QCOProgramBuilder& b) { - b.staticQubit(0); - b.staticQubit(1); +SmallVector staticQubits(QCOProgramBuilder& b) { + auto q1 = b.staticQubit(0); + auto q2 = b.staticQubit(1); + return measureAndReturn(b, {q1, q2}); } -void staticQubitsWithOps(QCOProgramBuilder& b) { +SmallVector staticQubitsWithOps(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); q0 = b.h(q0); q1 = b.h(q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithParametricOps(QCOProgramBuilder& b) { +SmallVector staticQubitsWithParametricOps(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); q0 = b.rx(std::numbers::pi / 4., q0); q1 = b.p(std::numbers::pi / 2., q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b) { +SmallVector staticQubitsWithTwoTargetOps(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); std::tie(q0, q1) = b.rzz(0.123, q0, q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithCtrl(QCOProgramBuilder& b) { +SmallVector staticQubitsWithCtrl(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); std::tie(q0, q1) = b.cx(q0, q1); + return measureAndReturn(b, {q0, q1}); } -void staticQubitsWithInv(QCOProgramBuilder& b) { +Value staticQubitsWithInv(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); - q0 = b.inv({q0}, [&](auto targets) -> SmallVector { - return {b.t(targets[0])}; - })[0]; + q0 = b.inv(q0, [&](Value qubit) { return b.t(qubit); }); + return b.measure(q0).second; } -void allocSinkPair(QCOProgramBuilder& b) { +Value allocSinkPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); b.sink(q); + return b.intConstant(0); +} + +SmallVector deadGatesProgram(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto q1 = b.allocQubit(); + + auto [q0M, m0] = b.measure(q0); + auto [q1M, m1] = b.measure(q1); + + q0 = b.h(q0M); + auto [res0, res1] = b.cx(q0, q1M); + auto [_, c1] = b.measure(res1); + q0 = b.reset(res0); + + return {m0, m1}; } -void mixedStaticThenDynamicQubit(QCOProgramBuilder& b) { - b.staticQubit(0); - b.allocQubit(); +Value deadGatesWithIfOpProgram(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto q1 = b.allocQubit(); + q0 = b.h(q0); + auto [r0, c0] = b.measure(q0); + q0 = r0; + + // This is an `if` with memory effects - it can't be removed. + q1 = b.qcoIf( + c0, {q1}, + [&](ValueRange qubits) -> SmallVector { + auto q1Then = b.x(qubits[0]); + b.gphase(0.5); // This adds memory effects to the `IfOp`. + return {q1Then}; + }, + [&](ValueRange qubits) -> SmallVector { + auto q1Else = b.h(qubits[0]); + return {q1Else}; + })[0]; + + // This is an `if` without memory effects - it can be removed. + q1 = b.qcoIf( + c0, {q1}, + [&](ValueRange qubits) -> SmallVector { + auto q1Then = b.x(qubits[0]); + return {q1Then}; + }, + [&](ValueRange qubits) -> SmallVector { + auto q1Else = b.h(qubits[0]); + return {q1Else}; + })[0]; + + return c0; } -void mixedDynamicRegisterThenStaticQubit(QCOProgramBuilder& b) { +Value deadGatesWithIfOpSimplified(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto q1 = b.allocQubit(); + q0 = b.h(q0); + auto [r0, c0] = b.measure(q0); + q0 = r0; + + // This is an `if` with memory effects - it can't be removed. + q1 = b.qcoIf( + c0, {q1}, + [&](ValueRange qubits) -> SmallVector { + auto q1Then = b.x(qubits[0]); + b.gphase(0.5); // Due to memory effect, the `IfOp` stays. + return {q1Then}; + }, + [&](ValueRange qubits) -> SmallVector { + auto q1Else = b.h(qubits[0]); + return {q1Else}; + })[0]; + + return c0; +} + +SmallVector mixedStaticThenDynamicQubit(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.allocQubit(); + return measureAndReturn(b, {q0, q1}); +} + +Value mixedDynamicRegisterThenStaticQubit(QCOProgramBuilder& b) { b.qtensorAlloc(2); - b.staticQubit(0); + auto q1 = b.staticQubit(0); + return b.measure(q1).second; } -void singleMeasurementToSingleBit(QCOProgramBuilder& b) { +Value singleMeasurementToSingleBit(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); - q[0] = b.measure(q[0], c[0]); + const auto [q1, bit] = b.measure(q[0], c[0]); + return bit; } -void repeatedMeasurementToSameBit(QCOProgramBuilder& b) { +Value repeatedMeasurementToSameBit(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); - q[0] = b.measure(q[0], c[0]); - q[0] = b.measure(q[0], c[0]); - q[0] = b.measure(q[0], c[0]); + auto [q1, _c1] = b.measure(q[0], c[0]); + auto [q2, _c2] = b.measure(q1, c[0]); + auto [q3, c3] = b.measure(q2, c[0]); + return c3; } -void repeatedMeasurementToDifferentBits(QCOProgramBuilder& b) { +SmallVector repeatedMeasurementToDifferentBits(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(3); - q[0] = b.measure(q[0], c[0]); - q[0] = b.measure(q[0], c[1]); - q[0] = b.measure(q[0], c[2]); + auto [q1, c1] = b.measure(q[0], c[0]); + auto [q2, c2] = b.measure(q1, c[1]); + auto [q3, c3] = b.measure(q2, c[2]); + return {c1, c2, c3}; } -void multipleClassicalRegistersAndMeasurements(QCOProgramBuilder& b) { +SmallVector +multipleClassicalRegistersAndMeasurements(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); const auto& c0 = b.allocClassicalBitRegister(1, "c0"); const auto& c1 = b.allocClassicalBitRegister(2, "c1"); - b.measure(q[0], c0[0]); - b.measure(q[1], c1[0]); - b.measure(q[2], c1[1]); + auto [q0, bit1] = b.measure(q[0], c0[0]); + auto [q1, bit2] = b.measure(q[1], c1[0]); + auto [q2, bit3] = b.measure(q[2], c1[1]); + return {bit1, bit2, bit3}; } -void measurementWithoutRegisters(QCOProgramBuilder& b) { +Value measurementWithoutRegisters(QCOProgramBuilder& b) { auto q = b.allocQubit(); - b.measure(q); + auto [q1, c] = b.measure(q); + return c; } -void resetQubitWithoutOp(QCOProgramBuilder& b) { +Value resetQubitWithoutOp(QCOProgramBuilder& b) { auto q = b.allocQubit(); q = b.reset(q); + return b.measure(q).second; } -void resetMultipleQubitsWithoutOp(QCOProgramBuilder& b) { +SmallVector resetMultipleQubitsWithoutOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); q[0] = b.reset(q[0]); q[1] = b.reset(q[1]); + return measureAndReturn(b, q.qubits); } -void repeatedResetWithoutOp(QCOProgramBuilder& b) { +Value repeatedResetWithoutOp(QCOProgramBuilder& b) { auto q = b.allocQubit(); q = b.reset(q); q = b.reset(q); q = b.reset(q); + return b.measure(q).second; } -void resetQubitAfterSingleOp(QCOProgramBuilder& b) { +Value resetQubitAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); q[0] = b.reset(q[0]); + return b.measure(q[0]).second; } -void resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b) { +SmallVector resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); q[0] = b.h(q[0]); q[0] = b.reset(q[0]); q[1] = b.h(q[1]); q[1] = b.reset(q[1]); + return measureAndReturn(b, q.qubits); } -void repeatedResetAfterSingleOp(QCOProgramBuilder& b) { +Value repeatedResetAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); q[0] = b.reset(q[0]); q[0] = b.reset(q[0]); q[0] = b.reset(q[0]); + return b.measure(q[0]).second; } -void globalPhase(QCOProgramBuilder& b) { b.gphase(0.123); } +Value globalPhase(QCOProgramBuilder& b) { + b.gphase(0.123); + return b.intConstant(0); +} -void singleControlledGlobalPhase(QCOProgramBuilder& b) { +Value singleControlledGlobalPhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.cgphase(0.123, q[0]); + q[0] = b.cgphase(0.123, q[0]); + return b.measure(q[0]).second; } -void multipleControlledGlobalPhase(QCOProgramBuilder& b) { +SmallVector multipleControlledGlobalPhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcgphase(0.123, {q[0], q[1], q[2]}); + auto qs = b.mcgphase(0.123, {q[0], q[1], q[2]}); + return measureAndReturn(b, qs); } -void inverseGlobalPhase(QCOProgramBuilder& b) { - b.inv({}, [&](ValueRange /*qubits*/) { +Value inverseGlobalPhase(QCOProgramBuilder& b) { + b.inv(ValueRange{}, [&](ValueRange /*qubits*/) { b.gphase(-0.123); return SmallVector{}; }); + return b.intConstant(0); } -void inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto qs = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { SmallVector controls{qubits[0], qubits[1], qubits[2]}; auto controlsOut = b.mcgphase(-0.123, controls); return SmallVector(controlsOut.begin(), controlsOut.end()); }); + return measureAndReturn(b, qs); } -void identity(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.id(q[0]); +Value identity(QCOProgramBuilder& b) { + auto q = b.allocQubit(); + q = b.id(q); + return b.measure(q).second; } -void singleControlledIdentity(QCOProgramBuilder& b) { +SmallVector singleControlledIdentity(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cid(q[1], q[0]); + std::tie(q[1], q[0]) = b.cid(q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void multipleControlledIdentity(QCOProgramBuilder& b) { +SmallVector multipleControlledIdentity(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcid({q[2], q[1]}, q[0]); + auto res = b.mcid({q[2], q[1]}, q[0]); + q[2] = res.first[0]; + q[1] = res.first[1]; + q[0] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledIdentity(QCOProgramBuilder& b) { +SmallVector nestedControlledIdentity(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.id(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.id(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledIdentity(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.mcid({}, q[0]); +Value trivialControlledIdentity(QCOProgramBuilder& b) { + auto q = b.allocQubit(); + auto res = b.mcid({}, q); + q = res.second; + return b.measure(q).second; } -void inverseIdentity(QCOProgramBuilder& b) { +Value inverseIdentity(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.id(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.id(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledIdentity(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledIdentity(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcid({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void x(QCOProgramBuilder& b) { +Value x(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.x(q[0]); + q[0] = b.x(q[0]); + return b.measure(q[0]).second; } -void singleControlledX(QCOProgramBuilder& b) { +SmallVector singleControlledX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cx(q[0], q[1]); + std::tie(q[0], q[1]) = b.cx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledX(QCOProgramBuilder& b) { +SmallVector multipleControlledX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcx({q[0], q[1]}, q[2]); + auto res = b.mcx({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledX(QCOProgramBuilder& b) { +SmallVector nestedControlledX(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.x(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.x(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledX(QCOProgramBuilder& b) { +Value trivialControlledX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcx({}, q[0]); + auto res = b.mcx({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void repeatedControlledX(QCOProgramBuilder& b) { - auto tensor = b.qtensorAlloc(64); - - Value q0; - std::tie(tensor, q0) = b.qtensorExtract(tensor, 0); - - SmallVector values(63); - for (auto i = 1; i < 64; i++) { - Value qi; - std::tie(tensor, qi) = b.qtensorExtract(tensor, i); - values[i - 1] = qi; - } - - q0 = b.h(q0); - for (auto i = 1; i < 64; i++) { - std::tie(q0, values[i - 1]) = b.cx(q0, values[i - 1]); +SmallVector repeatedControlledX(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto control = b.h(q0); + std::vector targets; + for (auto i = 0; i < 50; i++) { + auto qubit = b.allocQubit(); + auto res = b.cx(control, qubit); + control = res.first; + targets.push_back(res.second); } + targets.push_back(control); + return measureAndReturn(b, + SmallVector(targets.begin(), targets.end())); } -void inverseX(QCOProgramBuilder& b) { +Value inverseX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.x(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.x(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledX(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcx({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoX(QCOProgramBuilder& b) { +Value twoX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.x(q[0]); q[0] = b.x(q[0]); + return b.measure(q[0]).second; } -void controlledTwoX(QCOProgramBuilder& b) { +SmallVector controlledTwoX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl(q[0], q[1], [&](ValueRange targets) { - auto q = b.x(targets[0]); - q = b.x(q); - return SmallVector{q}; + auto res = b.ctrl(q[0], q[1], [&](Value target) { + target = b.x(target); + return b.x(target); }); + return measureAndReturn(b, {res.first, res.second}); } -void inverseTwoX(QCOProgramBuilder& b) { +Value inverseTwoX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { - auto q = b.x(qubits[0]); - q = b.x(q); - return SmallVector{q}; + auto res = b.inv(q[0], [&](Value qubit) { + qubit = b.x(qubit); + qubit = b.x(qubit); + return qubit; }); + return b.measure(res).second; } -void inverseGphaseX(QCOProgramBuilder& b) { +Value inverseGphaseX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) { + auto res = b.inv(q[0], [&](Value qubit) { b.gphase(-0.123); - return SmallVector{b.x(qubits[0])}; + return b.x(qubit); }); + return b.measure(res).second; } -void inverseGphaseBarrier(QCOProgramBuilder& b) { +Value inverseGphaseBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) -> SmallVector { + auto res = b.inv(q[0], [&](Value qubit) { b.gphase(0.123); - return {b.barrier({qubits[0]})[0]}; + return b.barrier({qubit})[0]; }); + return b.measure(res).second; } -void inverseTwoBarriersInInv(QCOProgramBuilder& b) { +Value inverseTwoBarriersInInv(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv(q[0], [&](ValueRange qubits) -> SmallVector { - auto q0 = b.barrier({qubits[0]})[0]; - return {b.barrier({q0})[0]}; + auto res = b.inv(q[0], [&](Value qubit) { + qubit = b.barrier({qubit})[0]; + return b.barrier({qubit})[0]; }); + return b.measure(res).second; } -void y(QCOProgramBuilder& b) { +Value y(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.y(q[0]); + q[0] = b.y(q[0]); + return b.measure(q[0]).second; } -void singleControlledY(QCOProgramBuilder& b) { +SmallVector singleControlledY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cy(q[0], q[1]); + std::tie(q[0], q[1]) = b.cy(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledY(QCOProgramBuilder& b) { +SmallVector multipleControlledY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcy({q[0], q[1]}, q[2]); + auto res = b.mcy({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledY(QCOProgramBuilder& b) { +SmallVector nestedControlledY(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.y(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.y(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledY(QCOProgramBuilder& b) { +Value trivialControlledY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcy({}, q[0]); + auto res = b.mcy({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseY(QCOProgramBuilder& b) { +Value inverseY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.y(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.y(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledY(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcy({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoY(QCOProgramBuilder& b) { +Value twoY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.y(q[0]); q[0] = b.y(q[0]); + return b.measure(q[0]).second; } -void z(QCOProgramBuilder& b) { +Value z(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.z(q[0]); + q[0] = b.z(q[0]); + return b.measure(q[0]).second; } -void singleControlledZ(QCOProgramBuilder& b) { +SmallVector singleControlledZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cz(q[0], q[1]); + std::tie(q[0], q[1]) = b.cz(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledZ(QCOProgramBuilder& b) { +SmallVector multipleControlledZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcz({q[0], q[1]}, q[2]); + auto res = b.mcz({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledZ(QCOProgramBuilder& b) { +SmallVector nestedControlledZ(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.z(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.z(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledZ(QCOProgramBuilder& b) { +Value trivialControlledZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcz({}, q[0]); + auto res = b.mcz({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseZ(QCOProgramBuilder& b) { +Value inverseZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.z(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.z(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledZ(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcz({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoZ(QCOProgramBuilder& b) { +Value twoZ(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.z(q[0]); q[0] = b.z(q[0]); + return b.measure(q[0]).second; } -void h(QCOProgramBuilder& b) { +Value h(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.h(q[0]); + q[0] = b.h(q[0]); + return b.measure(q[0]).second; } -void singleControlledH(QCOProgramBuilder& b) { +SmallVector singleControlledH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ch(q[0], q[1]); + std::tie(q[0], q[1]) = b.ch(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledH(QCOProgramBuilder& b) { +SmallVector multipleControlledH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mch({q[0], q[1]}, q[2]); + auto res = b.mch({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledH(QCOProgramBuilder& b) { +SmallVector nestedControlledH(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.h(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.h(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledH(QCOProgramBuilder& b) { +Value trivialControlledH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mch({}, q[0]); + auto res = b.mch({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseH(QCOProgramBuilder& b) { +Value inverseH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.h(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.h(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledH(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mch({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoH(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - q[0] = b.h(q[0]); - q[0] = b.h(q[0]); +Value twoH(QCOProgramBuilder& b) { + auto q = b.allocQubit(); + q = b.h(q); + q = b.h(q); + return b.measure(q).second; } -void hWithoutRegister(QCOProgramBuilder& b) { +Value hWithoutRegister(QCOProgramBuilder& b) { auto q = b.allocQubit(); - b.h(q); + q = b.h(q); + return b.measure(q).second; } -void s(QCOProgramBuilder& b) { +Value s(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.s(q[0]); + q[0] = b.s(q[0]); + return b.measure(q[0]).second; } -void singleControlledS(QCOProgramBuilder& b) { +SmallVector singleControlledS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cs(q[0], q[1]); + auto res = b.cs(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledS(QCOProgramBuilder& b) { +SmallVector multipleControlledS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcs({q[0], q[1]}, q[2]); + auto res = b.mcs({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledS(QCOProgramBuilder& b) { +SmallVector nestedControlledS(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.s(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.s(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledS(QCOProgramBuilder& b) { +Value trivialControlledS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcs({}, q[0]); + auto res = b.mcs({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseS(QCOProgramBuilder& b) { +Value inverseS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.s(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.s(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledS(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcs({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void sThenSdg(QCOProgramBuilder& b) { +Value sThenSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.s(q[0]); q[0] = b.sdg(q[0]); + return b.measure(q[0]).second; } -void twoS(QCOProgramBuilder& b) { +Value twoS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.s(q[0]); q[0] = b.s(q[0]); + return b.measure(q[0]).second; } -void sdg(QCOProgramBuilder& b) { +Value sdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.sdg(q[0]); + q[0] = b.sdg(q[0]); + return b.measure(q[0]).second; } -void singleControlledSdg(QCOProgramBuilder& b) { +SmallVector singleControlledSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.csdg(q[0], q[1]); + auto res = b.csdg(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledSdg(QCOProgramBuilder& b) { +SmallVector multipleControlledSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcsdg({q[0], q[1]}, q[2]); + auto res = b.mcsdg({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledSdg(QCOProgramBuilder& b) { +SmallVector nestedControlledSdg(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.sdg(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.sdg(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledSdg(QCOProgramBuilder& b) { +Value trivialControlledSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcsdg({}, q[0]); + auto res = b.mcsdg({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseSdg(QCOProgramBuilder& b) { +Value inverseSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.sdg(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.sdg(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledSdg(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcsdg({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void sdgThenS(QCOProgramBuilder& b) { +Value sdgThenS(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sdg(q[0]); q[0] = b.s(q[0]); + return b.measure(q[0]).second; } -void twoSdg(QCOProgramBuilder& b) { +Value twoSdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sdg(q[0]); q[0] = b.sdg(q[0]); + return b.measure(q[0]).second; } -void t_(QCOProgramBuilder& b) { +Value t_(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.t(q[0]); + q[0] = b.t(q[0]); + return b.measure(q[0]).second; } -void singleControlledT(QCOProgramBuilder& b) { +SmallVector singleControlledT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ct(q[0], q[1]); + auto res = b.ct(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledT(QCOProgramBuilder& b) { +SmallVector multipleControlledT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mct({q[0], q[1]}, q[2]); + auto res = b.mct({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledT(QCOProgramBuilder& b) { +SmallVector nestedControlledT(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.t(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.t(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledT(QCOProgramBuilder& b) { +Value trivialControlledT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mct({}, q[0]); + auto res = b.mct({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseT(QCOProgramBuilder& b) { +Value inverseT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { return SmallVector{b.t(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.t(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledT(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mct({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void tThenTdg(QCOProgramBuilder& b) { +Value tThenTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.t(q[0]); q[0] = b.tdg(q[0]); + return b.measure(q[0]).second; } -void twoT(QCOProgramBuilder& b) { +Value twoT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.t(q[0]); q[0] = b.t(q[0]); + return b.measure(q[0]).second; } -void tdg(QCOProgramBuilder& b) { +Value tdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.tdg(q[0]); + q[0] = b.tdg(q[0]); + return b.measure(q[0]).second; } -void singleControlledTdg(QCOProgramBuilder& b) { +SmallVector singleControlledTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctdg(q[0], q[1]); + auto res = b.ctdg(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledTdg(QCOProgramBuilder& b) { +SmallVector multipleControlledTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mctdg({q[0], q[1]}, q[2]); + auto res = b.mctdg({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledTdg(QCOProgramBuilder& b) { +SmallVector nestedControlledTdg(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.tdg(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.tdg(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledTdg(QCOProgramBuilder& b) { +Value trivialControlledTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mctdg({}, q[0]); + auto res = b.mctdg({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseTdg(QCOProgramBuilder& b) { +Value inverseTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.tdg(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.tdg(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledTdg(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mctdg({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void tdgThenT(QCOProgramBuilder& b) { +Value tdgThenT(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.tdg(q[0]); q[0] = b.t(q[0]); + return b.measure(q[0]).second; } -void twoTdg(QCOProgramBuilder& b) { +Value twoTdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.tdg(q[0]); q[0] = b.tdg(q[0]); + return b.measure(q[0]).second; } -void sx(QCOProgramBuilder& b) { +Value sx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.sx(q[0]); + q[0] = b.sx(q[0]); + return b.measure(q[0]).second; } -void singleControlledSx(QCOProgramBuilder& b) { +SmallVector singleControlledSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.csx(q[0], q[1]); + auto res = b.csx(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledSx(QCOProgramBuilder& b) { +SmallVector multipleControlledSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcsx({q[0], q[1]}, q[2]); + auto res = b.mcsx({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledSx(QCOProgramBuilder& b) { +SmallVector nestedControlledSx(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.sx(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.sx(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledSx(QCOProgramBuilder& b) { +Value trivialControlledSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcsx({}, q[0]); + auto res = b.mcsx({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseSx(QCOProgramBuilder& b) { +Value inverseSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.sx(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.sx(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledSx(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcsx({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void sxThenSxdg(QCOProgramBuilder& b) { +Value sxThenSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sx(q[0]); q[0] = b.sxdg(q[0]); + return b.measure(q[0]).second; } -void twoSx(QCOProgramBuilder& b) { +Value twoSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sx(q[0]); q[0] = b.sx(q[0]); + return b.measure(q[0]).second; } -void sxdg(QCOProgramBuilder& b) { +Value sxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.sxdg(q[0]); + q[0] = b.sxdg(q[0]); + return b.measure(q[0]).second; } -void singleControlledSxdg(QCOProgramBuilder& b) { +SmallVector singleControlledSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.csxdg(q[0], q[1]); + auto res = b.csxdg(q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledSxdg(QCOProgramBuilder& b) { +SmallVector multipleControlledSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcsxdg({q[0], q[1]}, q[2]); + auto res = b.mcsxdg({q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledSxdg(QCOProgramBuilder& b) { +SmallVector nestedControlledSxdg(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.sxdg(innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( + targets[0], targets[1], [&](Value target) { return b.sxdg(target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledSxdg(QCOProgramBuilder& b) { +Value trivialControlledSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcsxdg({}, q[0]); + auto res = b.mcsxdg({}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseSxdg(QCOProgramBuilder& b) { +Value inverseSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.sxdg(qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.sxdg(qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledSxdg(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcsxdg({qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void sxdgThenSx(QCOProgramBuilder& b) { +Value sxdgThenSx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sxdg(q[0]); q[0] = b.sx(q[0]); + return b.measure(q[0]).second; } -void twoSxdg(QCOProgramBuilder& b) { +Value twoSxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.sxdg(q[0]); q[0] = b.sxdg(q[0]); + return b.measure(q[0]).second; } -void rx(QCOProgramBuilder& b) { +Value rx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.rx(0.123, q[0]); + q[0] = b.rx(0.123, q[0]); + return b.measure(q[0]).second; } -void singleControlledRx(QCOProgramBuilder& b) { +SmallVector singleControlledRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.crx(0.123, q[0], q[1]); + auto res = b.crx(0.123, q[0], q[1]); + q[0] = res.first; + q[1] = res.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledRx(QCOProgramBuilder& b) { +SmallVector multipleControlledRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcrx(0.123, {q[0], q[1]}, q[2]); + auto res = b.mcrx(0.123, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRx(QCOProgramBuilder& b) { +SmallVector nestedControlledRx(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.rx(0.123, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.rx(0.123, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRx(QCOProgramBuilder& b) { +Value trivialControlledRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcrx(0.123, {}, q[0]); + auto res = b.mcrx(0.123, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseRx(QCOProgramBuilder& b) { +Value inverseRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.rx(-0.123, qubits[0])}; - }); + auto res = b.inv(q[0], [&](Value qubit) { return b.rx(-0.123, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledRx(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcrx(-0.123, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoRxOppositePhase(QCOProgramBuilder& b) { +Value twoRxOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.rx(0.123, q[0]); q[0] = b.rx(-0.123, q[0]); + return b.measure(q[0]).second; } -void rxPiOver2(QCOProgramBuilder& b) { +Value rxPiOver2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.rx(std::numbers::pi / 2, q[0]); + q[0] = b.rx(std::numbers::pi / 2, q[0]); + return b.measure(q[0]).second; } -void ry(QCOProgramBuilder& b) { +Value ry(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.ry(0.456, q[0]); + q[0] = b.ry(0.456, q[0]); + return b.measure(q[0]).second; } -void singleControlledRy(QCOProgramBuilder& b) { +SmallVector singleControlledRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cry(0.456, q[0], q[1]); + std::tie(q[0], q[1]) = b.cry(0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRy(QCOProgramBuilder& b) { +SmallVector multipleControlledRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcry(0.456, {q[0], q[1]}, q[2]); + auto res = b.mcry(0.456, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRy(QCOProgramBuilder& b) { +SmallVector nestedControlledRy(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.ry(0.456, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.ry(0.456, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRy(QCOProgramBuilder& b) { +Value trivialControlledRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcry(0.456, {}, q[0]); + auto res = b.mcry(0.456, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseRy(QCOProgramBuilder& b) { +Value inverseRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.ry(-0.456, qubits[0])}; - }); + auto res = b.inv(q[0], [&](Value qubit) { return b.ry(-0.456, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledRy(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcry(-0.456, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoRyOppositePhase(QCOProgramBuilder& b) { + +Value twoRyOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.ry(0.456, q[0]); q[0] = b.ry(-0.456, q[0]); + return b.measure(q[0]).second; } -void ryPiOver2(QCOProgramBuilder& b) { +Value ryPiOver2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.ry(std::numbers::pi / 2, q[0]); + q[0] = b.ry(std::numbers::pi / 2, q[0]); + return b.measure(q[0]).second; } -void rz(QCOProgramBuilder& b) { +Value rz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.rz(0.789, q[0]); + q[0] = b.rz(0.789, q[0]); + return b.measure(q[0]).second; } -void singleControlledRz(QCOProgramBuilder& b) { +SmallVector singleControlledRz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.crz(0.789, q[0], q[1]); + std::tie(q[0], q[1]) = b.crz(0.789, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledRz(QCOProgramBuilder& b) { +SmallVector multipleControlledRz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcrz(0.789, {q[0], q[1]}, q[2]); + auto res = b.mcrz(0.789, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRz(QCOProgramBuilder& b) { +SmallVector nestedControlledRz(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.rz(0.789, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.rz(0.789, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRz(QCOProgramBuilder& b) { +Value trivialControlledRz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcrz(0.789, {}, q[0]); + auto res = b.mcrz(0.789, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseRz(QCOProgramBuilder& b) { +Value inverseRz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.rz(-0.789, qubits[0])}; - }); + auto res = b.inv(q[0], [&](Value qubit) { return b.rz(-0.789, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledRz(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcrz(-0.789, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoRzOppositePhase(QCOProgramBuilder& b) { +Value twoRzOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.rz(0.789, q[0]); q[0] = b.rz(-0.789, q[0]); + return b.measure(q[0]).second; } -void p(QCOProgramBuilder& b) { +Value p(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.p(0.123, q[0]); + q[0] = b.p(0.123, q[0]); + return b.measure(q[0]).second; } -void singleControlledP(QCOProgramBuilder& b) { +SmallVector singleControlledP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cp(0.123, q[0], q[1]); + std::tie(q[0], q[1]) = b.cp(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledP(QCOProgramBuilder& b) { +SmallVector multipleControlledP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcp(0.123, {q[0], q[1]}, q[2]); + auto res = b.mcp(0.123, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledP(QCOProgramBuilder& b) { +SmallVector nestedControlledP(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.p(0.123, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.p(0.123, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledP(QCOProgramBuilder& b) { +Value trivialControlledP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcp(0.123, {}, q[0]); + auto res = b.mcp(0.123, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseP(QCOProgramBuilder& b) { +Value inverseP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, - [&](ValueRange qubits) { return SmallVector{b.p(-0.123, qubits[0])}; }); + auto res = b.inv(q[0], [&](Value qubit) { return b.p(-0.123, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledP(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcp(-0.123, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void twoPOppositePhase(QCOProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - q[0] = b.p(0.123, q[0]); - q[0] = b.p(-0.123, q[0]); +Value twoPOppositePhase(QCOProgramBuilder& b) { + auto q = b.allocQubit(); + q = b.p(0.123, q); + q = b.p(-0.123, q); + return b.measure(q).second; } -void r(QCOProgramBuilder& b) { +Value r(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.r(0.123, 0.456, q[0]); + q[0] = b.r(0.123, 0.456, q[0]); + return b.measure(q[0]).second; } -void singleControlledR(QCOProgramBuilder& b) { +SmallVector singleControlledR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cr(0.123, 0.456, q[0], q[1]); + std::tie(q[0], q[1]) = b.cr(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledR(QCOProgramBuilder& b) { +SmallVector multipleControlledR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcr(0.123, 0.456, {q[0], q[1]}, q[2]); + auto res = b.mcr(0.123, 0.456, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledR(QCOProgramBuilder& b) { +SmallVector nestedControlledR(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.r(0.123, 0.456, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.r(0.123, 0.456, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledR(QCOProgramBuilder& b) { +Value trivialControlledR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcr(0.123, 0.456, {}, q[0]); + auto res = b.mcr(0.123, 0.456, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseR(QCOProgramBuilder& b) { +Value inverseR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.r(-0.123, 0.456, qubits[0])}; - }); + auto res = + b.inv(q[0], [&](Value qubit) { return b.r(-0.123, 0.456, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledR(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcr(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void canonicalizeRToRx(QCOProgramBuilder& b) { +Value canonicalizeRToRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.r(0.123, 0., q[0]); + return b.measure(q[0]).second; } -void canonicalizeRToRy(QCOProgramBuilder& b) { +Value canonicalizeRToRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.r(0.456, std::numbers::pi / 2, q[0]); + return b.measure(q[0]).second; } -void twoR(QCOProgramBuilder& b) { +Value twoR(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.r(0.045, 0.456, q[0]); q[0] = b.r(0.078, 0.456, q[0]); + return b.measure(q[0]).second; } -void u2(QCOProgramBuilder& b) { +Value u2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u2(0.234, 0.567, q[0]); + q[0] = b.u2(0.234, 0.567, q[0]); + return b.measure(q[0]).second; } -void singleControlledU2(QCOProgramBuilder& b) { +SmallVector singleControlledU2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cu2(0.234, 0.567, q[0], q[1]); + std::tie(q[0], q[1]) = b.cu2(0.234, 0.567, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledU2(QCOProgramBuilder& b) { +SmallVector multipleControlledU2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcu2(0.234, 0.567, {q[0], q[1]}, q[2]); + auto res = b.mcu2(0.234, 0.567, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledU2(QCOProgramBuilder& b) { +SmallVector nestedControlledU2(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.u2(0.234, 0.567, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.u2(0.234, 0.567, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledU2(QCOProgramBuilder& b) { +Value trivialControlledU2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcu2(0.234, 0.567, {}, q[0]); + auto res = b.mcu2(0.234, 0.567, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseU2(QCOProgramBuilder& b) { +Value inverseU2(QCOProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.u2(-0.567 + pi, -0.234 - pi, qubits[0])}; - }); + auto res = b.inv( + q[0], [&](Value qubit) { return b.u2(-0.567 + pi, -0.234 - pi, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledU2(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledU2(QCOProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcu2(-0.567 + pi, -0.234 - pi, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void canonicalizeU2ToH(QCOProgramBuilder& b) { + +Value canonicalizeU2ToH(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u2(0., std::numbers::pi, q[0]); + q[0] = b.u2(0., std::numbers::pi, q[0]); + return b.measure(q[0]).second; } -void canonicalizeU2ToRx(QCOProgramBuilder& b) { +Value canonicalizeU2ToRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u2(-std::numbers::pi / 2, std::numbers::pi / 2, q[0]); + q[0] = b.u2(-std::numbers::pi / 2, std::numbers::pi / 2, q[0]); + return b.measure(q[0]).second; } -void canonicalizeU2ToRy(QCOProgramBuilder& b) { + +Value canonicalizeU2ToRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u2(0., 0., q[0]); + q[0] = b.u2(0., 0., q[0]); + return b.measure(q[0]).second; } -void u(QCOProgramBuilder& b) { +Value u(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u(0.1, 0.2, 0.3, q[0]); + q[0] = b.u(0.1, 0.2, 0.3, q[0]); + return b.measure(q[0]).second; } -void singleControlledU(QCOProgramBuilder& b) { +SmallVector singleControlledU(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.cu(0.1, 0.2, 0.3, q[0], q[1]); + std::tie(q[0], q[1]) = b.cu(0.1, 0.2, 0.3, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void multipleControlledU(QCOProgramBuilder& b) { +SmallVector multipleControlledU(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.mcu(0.1, 0.2, 0.3, {q[0], q[1]}, q[2]); + auto res = b.mcu(0.1, 0.2, 0.3, {q[0], q[1]}, q[2]); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledU(QCOProgramBuilder& b) { +SmallVector nestedControlledU(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { + auto res = b.ctrl({reg[0]}, {reg[1], reg[2]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0]}, {targets[1]}, [&](ValueRange innerTargets) { - return SmallVector{b.u(0.1, 0.2, 0.3, innerTargets[0])}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); + b.ctrl(targets[0], targets[1], + [&](Value target) { return b.u(0.1, 0.2, 0.3, target); }); + return SmallVector{innerControlsOut, innerTargetsOut}; }); + reg[0] = res.first[0]; + reg[1] = res.second[0]; + reg[2] = res.second[1]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledU(QCOProgramBuilder& b) { +Value trivialControlledU(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.mcu(0.1, 0.2, 0.3, {}, q[0]); + auto res = b.mcu(0.1, 0.2, 0.3, {}, q[0]); + q[0] = res.second; + return b.measure(q[0]).second; } -void inverseU(QCOProgramBuilder& b) { +Value inverseU(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.u(-0.1, -0.3, -0.2, qubits[0])}; - }); + auto res = + b.inv(q[0], [&](Value qubit) { return b.u(-0.1, -0.3, -0.2, qubit); }); + return b.measure(res).second; } -void inverseMultipleControlledU(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledU(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetOut] = b.mcu(-0.1, -0.3, -0.2, {qubits[0], qubits[1]}, qubits[2]); return llvm::to_vector( llvm::concat(controlsOut, ValueRange{targetOut})); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void canonicalizeUToP(QCOProgramBuilder& b) { +Value canonicalizeUToP(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u(0., 0., 0.123, q[0]); + q[0] = b.u(0., 0., 0.123, q[0]); + return b.measure(q[0]).second; } -void canonicalizeUToRx(QCOProgramBuilder& b) { +Value canonicalizeUToRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u(0.123, -std::numbers::pi / 2, std::numbers::pi / 2, q[0]); + q[0] = b.u(0.123, -std::numbers::pi / 2, std::numbers::pi / 2, q[0]); + return b.measure(q[0]).second; } -void canonicalizeUToRy(QCOProgramBuilder& b) { +Value canonicalizeUToRy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u(0.456, 0., 0., q[0]); + q[0] = b.u(0.456, 0., 0., q[0]); + return b.measure(q[0]).second; } -void canonicalizeUToU2(QCOProgramBuilder& b) { +Value canonicalizeUToU2(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.u(std::numbers::pi / 2, 0.234, 0.567, q[0]); + q[0] = b.u(std::numbers::pi / 2, 0.234, 0.567, q[0]); + return b.measure(q[0]).second; } -void swap(QCOProgramBuilder& b) { +SmallVector swap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.swap(q[0], q[1]); + std::tie(q[0], q[1]) = b.swap(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledSwap(QCOProgramBuilder& b) { +SmallVector singleControlledSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cswap(q[0], q[1], q[2]); + auto res = b.cswap(q[0], q[1], q[2]); + q[0] = res.first; + q[1] = res.second.first; + q[2] = res.second.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledSwap(QCOProgramBuilder& b) { +SmallVector multipleControlledSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcswap({q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcswap({q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledSwap(QCOProgramBuilder& b) { +SmallVector nestedControlledSwap(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.swap(innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = b.swap(innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledSwap(QCOProgramBuilder& b) { +SmallVector trivialControlledSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcswap({}, q[0], q[1]); + auto [c, t] = b.mcswap({}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseSwap(QCOProgramBuilder& b) { +SmallVector inverseSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.swap(qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.swap(qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledSwap(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoSwap(QCOProgramBuilder& b) { +SmallVector twoSwap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.swap(q[0], q[1]); std::tie(q[0], q[1]) = b.swap(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoSwapSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoSwapSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.swap(q[0], q[1]); std::tie(q[1], q[0]) = b.swap(q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void iswap(QCOProgramBuilder& b) { +SmallVector iswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.iswap(q[0], q[1]); + std::tie(q[0], q[1]) = b.iswap(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledIswap(QCOProgramBuilder& b) { +SmallVector singleControlledIswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.ciswap(q[0], q[1], q[2]); + auto [c, t] = b.ciswap(q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledIswap(QCOProgramBuilder& b) { +SmallVector multipleControlledIswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mciswap({q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mciswap({q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledIswap(QCOProgramBuilder& b) { +SmallVector nestedControlledIswap(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.iswap(innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = + b.iswap(innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledIswap(QCOProgramBuilder& b) { +SmallVector trivialControlledIswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mciswap({}, q[0], q[1]); + auto [c, t] = b.mciswap({}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseIswap(QCOProgramBuilder& b) { +SmallVector inverseIswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.iswap(qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.iswap(qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledIswap(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledIswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mciswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void dcx(QCOProgramBuilder& b) { +SmallVector dcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.dcx(q[0], q[1]); + std::tie(q[0], q[1]) = b.dcx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledDcx(QCOProgramBuilder& b) { +SmallVector singleControlledDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cdcx(q[0], q[1], q[2]); + auto [c, t] = b.cdcx(q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledDcx(QCOProgramBuilder& b) { +SmallVector multipleControlledDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcdcx({q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcdcx({q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledDcx(QCOProgramBuilder& b) { +SmallVector nestedControlledDcx(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.dcx(innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = b.dcx(innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledDcx(QCOProgramBuilder& b) { +SmallVector trivialControlledDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcdcx({}, q[0], q[1]); + auto [c, t] = b.mcdcx({}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseDcx(QCOProgramBuilder& b) { +SmallVector inverseDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[1], q[0]}, [&](ValueRange qubits) { - auto res = b.dcx(qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[1], q[0]}, [&](ValueRange qubits) { + auto [fst, snd] = b.dcx(qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[1] = res[0]; + q[0] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledDcx(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[3], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[3], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcdcx({qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[3] = res[2]; + q[2] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoDcx(QCOProgramBuilder& b) { +SmallVector twoDcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.dcx(q[0], q[1]); std::tie(q[0], q[1]) = b.dcx(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoDcxSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoDcxSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.dcx(q[0], q[1]); std::tie(q[1], q[0]) = b.dcx(q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void ecr(QCOProgramBuilder& b) { +SmallVector ecr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ecr(q[0], q[1]); + std::tie(q[0], q[1]) = b.ecr(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledEcr(QCOProgramBuilder& b) { +SmallVector singleControlledEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cecr(q[0], q[1], q[2]); + auto [c, t] = b.cecr(q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledEcr(QCOProgramBuilder& b) { +SmallVector multipleControlledEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcecr({q[0], q[1]}, q[2], q[3]); + auto [fst, snd] = b.mcecr({q[0], q[1]}, q[2], q[3]); + q[0] = fst[0]; + q[1] = fst[1]; + q[2] = snd.first; + q[3] = snd.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledEcr(QCOProgramBuilder& b) { +SmallVector nestedControlledEcr(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.ecr(innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = b.ecr(innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledEcr(QCOProgramBuilder& b) { +SmallVector trivialControlledEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcecr({}, q[0], q[1]); + auto [c, t] = b.mcecr({}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseEcr(QCOProgramBuilder& b) { +SmallVector inverseEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.ecr(qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.ecr(qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledEcr(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcecr({qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoEcr(QCOProgramBuilder& b) { +SmallVector twoEcr(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.ecr(q[0], q[1]); std::tie(q[0], q[1]) = b.ecr(q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void rxx(QCOProgramBuilder& b) { +SmallVector rxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.rxx(0.123, q[0], q[1]); + std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRxx(QCOProgramBuilder& b) { +SmallVector singleControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.crxx(0.123, q[0], q[1], q[2]); + auto [c, t] = b.crxx(0.123, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledRxx(QCOProgramBuilder& b) { +SmallVector multipleControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRxx(QCOProgramBuilder& b) { +SmallVector nestedControlledRxx(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.rxx(0.123, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = + b.rxx(0.123, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRxx(QCOProgramBuilder& b) { +SmallVector trivialControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcrxx(0.123, {}, q[0], q[1]); + auto [c, t] = b.mcrxx(0.123, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseRxx(QCOProgramBuilder& b) { +SmallVector inverseRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.rxx(-0.123, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.rxx(-0.123, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRxx(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcrxx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void tripleControlledRxx(QCOProgramBuilder& b) { +SmallVector tripleControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(5); - b.mcrxx(0.123, {q[0], q[1], q[2]}, q[3], q[4]); + auto [c, t] = b.mcrxx(0.123, {q[0], q[1], q[2]}, q[3], q[4]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = c[2]; + q[3] = t.first; + q[4] = t.second; + return measureAndReturn(b, q.qubits); } -void fourControlledRxx(QCOProgramBuilder& b) { +SmallVector fourControlledRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(6); - b.mcrxx(0.123, {q[0], q[1], q[2], q[3]}, q[4], q[5]); + auto [c, t] = b.mcrxx(0.123, {q[0], q[1], q[2], q[3]}, q[4], q[5]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = c[2]; + q[3] = c[3]; + q[4] = t.first; + q[5] = t.second; + return measureAndReturn(b, q.qubits); } -void twoRxx(QCOProgramBuilder& b) { +SmallVector twoRxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.rxx(0.045, q[0], q[1]); std::tie(q[0], q[1]) = b.rxx(0.078, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRxxSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRxxSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.rxx(0.045, q[0], q[1]); std::tie(q[1], q[0]) = b.rxx(0.078, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void twoRxxOppositePhase(QCOProgramBuilder& b) { +SmallVector twoRxxOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); std::tie(q[0], q[1]) = b.rxx(-0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRxxOppositePhaseSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRxxOppositePhaseSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); std::tie(q[1], q[0]) = b.rxx(-0.123, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void ryy(QCOProgramBuilder& b) { +SmallVector ryy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ryy(0.123, q[0], q[1]); + std::tie(q[0], q[1]) = b.ryy(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRyy(QCOProgramBuilder& b) { +SmallVector singleControlledRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cryy(0.123, q[0], q[1], q[2]); + auto [c, t] = b.cryy(0.123, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledRyy(QCOProgramBuilder& b) { +SmallVector multipleControlledRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcryy(0.123, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcryy(0.123, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRyy(QCOProgramBuilder& b) { +SmallVector nestedControlledRyy(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.ryy(0.123, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = + b.ryy(0.123, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRyy(QCOProgramBuilder& b) { +SmallVector trivialControlledRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcryy(0.123, {}, q[0], q[1]); + auto [c, t] = b.mcryy(0.123, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseRyy(QCOProgramBuilder& b) { +SmallVector inverseRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.ryy(-0.123, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.ryy(-0.123, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRyy(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcryy(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoRyy(QCOProgramBuilder& b) { +SmallVector twoRyy(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.ryy(0.045, q[0], q[1]); std::tie(q[0], q[1]) = b.ryy(0.078, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRyyOppositePhaseSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRyyOppositePhaseSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.ryy(0.123, q[0], q[1]); std::tie(q[1], q[0]) = b.ryy(-0.123, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void twoRyyOppositePhase(QCOProgramBuilder& b) { +SmallVector twoRyyOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.ryy(0.123, q[0], q[1]); std::tie(q[0], q[1]) = b.ryy(-0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRyySwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRyySwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.ryy(0.045, q[0], q[1]); std::tie(q[1], q[0]) = b.ryy(0.078, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void rzx(QCOProgramBuilder& b) { +SmallVector rzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.rzx(0.123, q[0], q[1]); + std::tie(q[0], q[1]) = b.rzx(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRzx(QCOProgramBuilder& b) { +SmallVector singleControlledRzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.crzx(0.123, q[0], q[1], q[2]); + auto [c, t] = b.crzx(0.123, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledRzx(QCOProgramBuilder& b) { +SmallVector multipleControlledRzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcrzx(0.123, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcrzx(0.123, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRzx(QCOProgramBuilder& b) { +SmallVector nestedControlledRzx(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.rzx(0.123, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = + b.rzx(0.123, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRzx(QCOProgramBuilder& b) { +SmallVector trivialControlledRzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcrzx(0.123, {}, q[0], q[1]); + auto [c, t] = b.mcrzx(0.123, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseRzx(QCOProgramBuilder& b) { +SmallVector inverseRzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.rzx(-0.123, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.rzx(-0.123, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRzx(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRzx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcrzx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoRzxOppositePhase(QCOProgramBuilder& b) { +SmallVector twoRzxOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rzx(0.123, q[0], q[1]); std::tie(q[0], q[1]) = b.rzx(-0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void rzz(QCOProgramBuilder& b) { +SmallVector rzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.rzz(0.123, q[0], q[1]); + std::tie(q[0], q[1]) = b.rzz(0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledRzz(QCOProgramBuilder& b) { +SmallVector singleControlledRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.crzz(0.123, q[0], q[1], q[2]); + auto [c, t] = b.crzz(0.123, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledRzz(QCOProgramBuilder& b) { +SmallVector multipleControlledRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcrzz(0.123, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcrzz(0.123, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledRzz(QCOProgramBuilder& b) { +SmallVector nestedControlledRzz(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = b.rzz(0.123, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = + b.rzz(0.123, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledRzz(QCOProgramBuilder& b) { +SmallVector trivialControlledRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcrzz(0.123, {}, q[0], q[1]); + auto [c, t] = b.mcrzz(0.123, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseRzz(QCOProgramBuilder& b) { +SmallVector inverseRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.rzz(-0.123, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.rzz(-0.123, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledRzz(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcrzz(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoRzz(QCOProgramBuilder& b) { +SmallVector twoRzz(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.rzz(0.045, q[0], q[1]); std::tie(q[0], q[1]) = b.rzz(0.078, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRzzSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRzzSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); // 0.045 + 0.078 = 0.123 std::tie(q[0], q[1]) = b.rzz(0.045, q[0], q[1]); std::tie(q[1], q[0]) = b.rzz(0.078, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void twoRzzOppositePhase(QCOProgramBuilder& b) { +SmallVector twoRzzOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rzz(0.123, q[0], q[1]); std::tie(q[0], q[1]) = b.rzz(-0.123, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoRzzOppositePhaseSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoRzzOppositePhaseSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rzz(0.123, q[0], q[1]); std::tie(q[1], q[0]) = b.rzz(-0.123, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void xxPlusYY(QCOProgramBuilder& b) { +SmallVector xxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.xx_plus_yy(0.123, 0.456, q[0], q[1]); + std::tie(q[0], q[1]) = b.xx_plus_yy(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledXxPlusYY(QCOProgramBuilder& b) { +SmallVector singleControlledXxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cxx_plus_yy(0.123, 0.456, q[0], q[1], q[2]); + auto [c, t] = b.cxx_plus_yy(0.123, 0.456, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledXxPlusYY(QCOProgramBuilder& b) { +SmallVector multipleControlledXxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcxx_plus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcxx_plus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledXxPlusYY(QCOProgramBuilder& b) { +SmallVector nestedControlledXxPlusYY(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = - b.xx_plus_yy(0.123, 0.456, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = b.xx_plus_yy( + 0.123, 0.456, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledXxPlusYY(QCOProgramBuilder& b) { +SmallVector trivialControlledXxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcxx_plus_yy(0.123, 0.456, {}, q[0], q[1]); + auto [c, t] = b.mcxx_plus_yy(0.123, 0.456, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseXxPlusYY(QCOProgramBuilder& b) { +SmallVector inverseXxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.xx_plus_yy(-0.123, 0.456, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.xx_plus_yy(-0.123, 0.456, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcxx_plus_yy( -0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoXxPlusYYOppositePhase(QCOProgramBuilder& b) { +SmallVector twoXxPlusYYOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_plus_yy(0.123, 0.456, q[0], q[1]); std::tie(q[0], q[1]) = b.xx_plus_yy(-0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoXxPlusYYSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoXxPlusYYSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_plus_yy(0.045, 0.456, q[0], q[1]); std::tie(q[1], q[0]) = b.xx_plus_yy(0.078, 0.456, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void xxMinusYY(QCOProgramBuilder& b) { +SmallVector xxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.xx_minus_yy(0.123, 0.456, q[0], q[1]); + std::tie(q[0], q[1]) = b.xx_minus_yy(0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void singleControlledXxMinusYY(QCOProgramBuilder& b) { +SmallVector singleControlledXxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.cxx_minus_yy(0.123, 0.456, q[0], q[1], q[2]); + auto [c, t] = b.cxx_minus_yy(0.123, 0.456, q[0], q[1], q[2]); + q[0] = c; + q[1] = t.first; + q[2] = t.second; + return measureAndReturn(b, q.qubits); } -void multipleControlledXxMinusYY(QCOProgramBuilder& b) { +SmallVector multipleControlledXxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.mcxx_minus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + auto [c, t] = b.mcxx_minus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + q[0] = c[0]; + q[1] = c[1]; + q[2] = t.first; + q[3] = t.second; + return measureAndReturn(b, q.qubits); } -void nestedControlledXxMinusYY(QCOProgramBuilder& b) { +SmallVector nestedControlledXxMinusYY(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( - {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { - auto res = - b.xx_minus_yy(0.123, 0.456, innerTargets[0], innerTargets[1]); - return SmallVector{res.first, res.second}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto [c, t] = + b.ctrl({reg[0]}, {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0]}, {targets[1], targets[2]}, + [&](ValueRange innerTargets) { + auto [fst, snd] = b.xx_minus_yy( + 0.123, 0.456, innerTargets[0], innerTargets[1]); + return SmallVector{fst, snd}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + reg[0] = c[0]; + reg[1] = t[0]; + reg[2] = t[1]; + reg[3] = t[2]; + return measureAndReturn(b, reg.qubits); } -void trivialControlledXxMinusYY(QCOProgramBuilder& b) { +SmallVector trivialControlledXxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.mcxx_minus_yy(0.123, 0.456, {}, q[0], q[1]); + auto [c, t] = b.mcxx_minus_yy(0.123, 0.456, {}, q[0], q[1]); + q[0] = t.first; + q[1] = t.second; + return measureAndReturn(b, q.qubits); } -void inverseXxMinusYY(QCOProgramBuilder& b) { +SmallVector inverseXxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { - auto res = b.xx_minus_yy(-0.123, 0.456, qubits[0], qubits[1]); - return SmallVector{res.first, res.second}; + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto [fst, snd] = b.xx_minus_yy(-0.123, 0.456, qubits[0], qubits[1]); + return SmallVector{fst, snd}; }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b) { +SmallVector inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.mcxx_minus_yy( -0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], qubits[3]); SmallVector targets{targetsOut.first, targetsOut.second}; return llvm::to_vector(llvm::concat(controlsOut, targets)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + q[3] = res[3]; + return measureAndReturn(b, q.qubits); } -void twoXxMinusYYOppositePhase(QCOProgramBuilder& b) { +SmallVector twoXxMinusYYOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_minus_yy(0.123, 0.456, q[0], q[1]); std::tie(q[0], q[1]) = b.xx_minus_yy(-0.123, 0.456, q[0], q[1]); + return measureAndReturn(b, q.qubits); } -void twoXxMinusYYSwappedTargets(QCOProgramBuilder& b) { +SmallVector twoXxMinusYYSwappedTargets(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_minus_yy(0.045, 0.456, q[0], q[1]); std::tie(q[1], q[0]) = b.xx_minus_yy(0.078, 0.456, q[1], q[0]); + return measureAndReturn(b, q.qubits); } -void barrier(QCOProgramBuilder& b) { +Value barrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.barrier(q[0]); + q[0] = b.barrier(q[0])[0]; + return b.measure(q[0]).second; } -void barrierTwoQubits(QCOProgramBuilder& b) { +SmallVector barrierTwoQubits(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.barrier({q[0], q[1]}); + auto res = b.barrier({q[0], q[1]}); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void barrierMultipleQubits(QCOProgramBuilder& b) { +SmallVector barrierMultipleQubits(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.barrier({q[0], q[1], q[2]}); + auto res = b.barrier({q[0], q[1], q[2]}); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void singleControlledBarrier(QCOProgramBuilder& b) { +Value singleControlledBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl({q[1]}, {q[0]}, [&](ValueRange targets) { - return SmallVector{b.barrier(targets[0])}; - }); + auto res = + b.ctrl(q[1], q[0], [&](Value target) { return b.barrier({target})[0]; }); + return b.measure(res.second).second; } -void inverseBarrier(QCOProgramBuilder& b) { +Value inverseBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv({q[0]}, [&](ValueRange qubits) { - return SmallVector{b.barrier(qubits[0])}; - }); + auto res = b.inv(q[0], [&](Value qubit) { return b.barrier({qubit})[0]; }); + return b.measure(res).second; } -void twoBarrier(QCOProgramBuilder& b) { +SmallVector twoBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); auto b1 = b.barrier({q[0], q[1]}); q[0] = b1[0]; q[1] = b1[1]; - b.barrier({q[0], q[1]}); + auto b2 = b.barrier({q[0], q[1]}); + q[0] = b2[0]; + q[1] = b2[1]; + return measureAndReturn(b, q.qubits); } -void trivialCtrl(QCOProgramBuilder& b) { +SmallVector trivialCtrl(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl({}, {q[0], q[1]}, [&](ValueRange targets) { + auto [_, q01] = b.ctrl({}, {q[0], q[1]}, [&](ValueRange targets) { auto [q0, q1] = b.rxx(0.123, targets[0], targets[1]); return SmallVector{q0, q1}; }); + return measureAndReturn(b, q01); } -void emptyCtrl(QCOProgramBuilder& b) { +SmallVector emptyCtrl(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); - b.ctrl(q[0], q[1], [&](ValueRange targets) { return targets; }); + auto [res0, res1] = b.ctrl(q[0], q[1], [&](Value target) { return target; }); + return measureAndReturn(b, {res0, res1}); } -void nestedCtrl(QCOProgramBuilder& b) { +SmallVector nestedCtrl(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl({q[0]}, {q[1], q[2], q[3]}, [&](ValueRange targets) { + auto res = b.ctrl({q[0]}, {q[1], q[2], q[3]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( {targets[0]}, {targets[1], targets[2]}, [&](ValueRange innerTargets) { auto [q0, q1] = b.rxx(0.123, innerTargets[0], innerTargets[1]); @@ -2023,11 +2820,16 @@ void nestedCtrl(QCOProgramBuilder& b) { return llvm::to_vector( llvm::concat(innerControlsOut, innerTargetsOut)); }); + q[0] = res.first[0]; + q[1] = res.second[0]; + q[2] = res.second[1]; + q[3] = res.second[2]; + return measureAndReturn(b, q.qubits); } -void tripleNestedCtrl(QCOProgramBuilder& b) { +SmallVector tripleNestedCtrl(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(5); - b.ctrl({q[0]}, {q[1], q[2], q[3], q[4]}, [&](ValueRange targets) { + auto res = b.ctrl({q[0]}, {q[1], q[2], q[3], q[4]}, [&](ValueRange targets) { const auto& [innerControlsOut, innerTargetsOut] = b.ctrl( {targets[0]}, {targets[1], targets[2], targets[3]}, [&](ValueRange innerTargets) { @@ -2044,25 +2846,40 @@ void tripleNestedCtrl(QCOProgramBuilder& b) { return llvm::to_vector( llvm::concat(innerControlsOut, innerTargetsOut)); }); + q[0] = res.first[0]; + q[1] = res.second[0]; + q[2] = res.second[1]; + q[3] = res.second[2]; + q[4] = res.second[3]; + return measureAndReturn(b, q.qubits); } -void doubleNestedCtrlTwoQubits(QCOProgramBuilder& b) { +SmallVector doubleNestedCtrlTwoQubits(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(6); - b.ctrl({q[0], q[1]}, {q[2], q[3], q[4], q[5]}, [&](ValueRange targets) { - const auto& [innerControlsOut, innerTargetsOut] = - b.ctrl({targets[0], targets[1]}, {targets[2], targets[3]}, - [&](ValueRange innerTargets) { - auto [q0, q1] = b.rxx(0.123, innerTargets[0], innerTargets[1]); - return SmallVector{q0, q1}; - }); - return llvm::to_vector( - llvm::concat(innerControlsOut, innerTargetsOut)); - }); + auto res = + b.ctrl({q[0], q[1]}, {q[2], q[3], q[4], q[5]}, [&](ValueRange targets) { + const auto& [innerControlsOut, innerTargetsOut] = + b.ctrl({targets[0], targets[1]}, {targets[2], targets[3]}, + [&](ValueRange innerTargets) { + auto [q0, q1] = + b.rxx(0.123, innerTargets[0], innerTargets[1]); + return SmallVector{q0, q1}; + }); + return llvm::to_vector( + llvm::concat(innerControlsOut, innerTargetsOut)); + }); + q[0] = res.first[0]; + q[1] = res.first[1]; + q[2] = res.second[0]; + q[3] = res.second[1]; + q[4] = res.second[2]; + q[5] = res.second[3]; + return measureAndReturn(b, q.qubits); } -void ctrlInvSandwich(QCOProgramBuilder& b) { +SmallVector ctrlInvSandwich(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl({q[0]}, {q[1], q[2], q[3]}, [&](ValueRange targets) { + auto res = b.ctrl({q[0]}, {q[1], q[2], q[3]}, [&](ValueRange targets) { auto inner = b.inv( {targets[0], targets[1], targets[2]}, [&](ValueRange innerTargets) { auto [innerControlsOut, innerTargetsOut] = @@ -2075,35 +2892,44 @@ void ctrlInvSandwich(QCOProgramBuilder& b) { return llvm::to_vector( llvm::concat(innerControlsOut, innerTargetsOut)); }); - return SmallVector{inner}; + return llvm::to_vector(inner); }); + q[0] = res.first[0]; + q[1] = res.second[0]; + q[2] = res.second[1]; + q[3] = res.second[2]; + return measureAndReturn(b, q.qubits); } -void ctrlTwo(QCOProgramBuilder& b) { +SmallVector ctrlTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + auto res = b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { auto i0 = targets[0]; auto i1 = targets[1]; i0 = b.x(i0); std::tie(i0, i1) = b.rxx(0.123, i0, i1); return SmallVector{i0, i1}; }); + return measureAndReturn( + b, {res.first[0], res.first[1], res.second[0], res.second[1]}); } -void ctrlTwoMixed(QCOProgramBuilder& b) { +SmallVector ctrlTwoMixed(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + auto res = b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { auto i0 = targets[0]; auto i1 = targets[1]; std::tie(i0, i1) = b.cx(i0, i1); std::tie(i0, i1) = b.rxx(0.123, i0, i1); return SmallVector{i0, i1}; }); + return measureAndReturn( + b, {res.first[0], res.first[1], res.second[0], res.second[1]}); } -void nestedCtrlTwo(QCOProgramBuilder& b) { +SmallVector nestedCtrlTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { + auto res = b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { const auto& [controlsOut, targetsOut] = b.ctrl( targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { auto i0 = innerTargets[0]; @@ -2114,11 +2940,13 @@ void nestedCtrlTwo(QCOProgramBuilder& b) { }); return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); }); + return measureAndReturn( + b, {res.first[0], res.second[0], res.second[1], res.second[2]}); } -void ctrlInvTwo(QCOProgramBuilder& b) { +SmallVector ctrlInvTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.ctrl(q[0], {q[1], q[2]}, [&](ValueRange targets) { + auto res = b.ctrl(q[0], {q[1], q[2]}, [&](ValueRange targets) { auto inner = b.inv(targets, [&](ValueRange qubits) { auto i0 = qubits[0]; auto i1 = qubits[1]; @@ -2128,28 +2956,33 @@ void ctrlInvTwo(QCOProgramBuilder& b) { }); return llvm::to_vector(inner); }); + return measureAndReturn(b, {res.first[0], res.second[0], res.second[1]}); } -void emptyInv(QCOProgramBuilder& b) { +SmallVector emptyInv(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { return qubits; }); + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { return qubits; }); + return measureAndReturn(b, res); } -void nestedInv(QCOProgramBuilder& b) { +SmallVector nestedInv(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { auto inner = b.inv({qubits[0], qubits[1]}, [&](ValueRange innerQubits) { auto [q0, q1] = b.rxx(0.123, innerQubits[0], innerQubits[1]); return SmallVector{q0, q1}; }); - return SmallVector{inner}; + return llvm::to_vector(inner); }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void tripleNestedInv(QCOProgramBuilder& b) { +SmallVector tripleNestedInv(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { auto inner1 = b.inv({qubits[0], qubits[1]}, [&](ValueRange innerQubits) { auto inner2 = b.inv( {innerQubits[0], innerQubits[1]}, [&](ValueRange innerInnerQubits) { @@ -2157,15 +2990,18 @@ void tripleNestedInv(QCOProgramBuilder& b) { b.rxx(-0.123, innerInnerQubits[0], innerInnerQubits[1]); return SmallVector{q0, q1}; }); - return SmallVector{inner2}; + return llvm::to_vector(inner2); }); - return SmallVector{inner1}; + return llvm::to_vector(inner1); }); + q[0] = res[0]; + q[1] = res[1]; + return measureAndReturn(b, q.qubits); } -void invCtrlSandwich(QCOProgramBuilder& b) { +SmallVector invCtrlSandwich(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.ctrl({qubits[0]}, {qubits[1], qubits[2]}, [&](ValueRange targets) { auto inner = @@ -2173,26 +3009,31 @@ void invCtrlSandwich(QCOProgramBuilder& b) { auto [q0, q1] = b.rxx(0.123, innerQubits[0], innerQubits[1]); return SmallVector{q0, q1}; }); - return SmallVector{inner}; + return llvm::to_vector(inner); }); return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); }); + q[0] = res[0]; + q[1] = res[1]; + q[2] = res[2]; + return measureAndReturn(b, q.qubits); } -void invTwo(QCOProgramBuilder& b) { +SmallVector invTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1]}, [&](ValueRange qubits) { auto i0 = qubits[0]; auto i1 = qubits[1]; i0 = b.x(i0); std::tie(i0, i1) = b.rxx(0.123, i0, i1); return SmallVector{i0, i1}; }); + return measureAndReturn(b, res); } -void invCtrlTwo(QCOProgramBuilder& b) { +SmallVector invCtrlTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { const auto& [controlsOut, targetsOut] = b.ctrl({qubits[0]}, {qubits[1], qubits[2]}, [&](ValueRange targets) { auto i0 = targets[0]; @@ -2203,45 +3044,56 @@ void invCtrlTwo(QCOProgramBuilder& b) { }); return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); }); + return measureAndReturn(b, res); } -void simpleIf(QCOProgramBuilder& b) { +SmallVector simpleIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf(measureResult, measuredQubit, [&](ValueRange args) { + auto res = b.qcoIf(measureResult, measuredQubit, [&](ValueRange args) { auto innerQubit = b.x(args[0]); return SmallVector{innerQubit}; }); + q[0] = res[0]; + auto [q1, bit] = b.measure(q[0]); + return {measureResult, bit}; } -void ifWithAngle(QCOProgramBuilder& b) { +Value ifWithAngle(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto theta = b.floatConstant(0.123); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf(measureResult, measuredQubit, [&](ValueRange args) { + q0 = b.qcoIf(measureResult, measuredQubit, [&](ValueRange args) { auto innerQubit = b.rx(theta, args[0]); return SmallVector{innerQubit}; - }); + })[0]; + return b.measure(q0).second; } -void ifTwoQubits(QCOProgramBuilder& b) { +SmallVector ifTwoQubits(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf(measureResult, {measuredQubit, q[1]}, [&](ValueRange args) { - auto innerQubit0 = b.x(args[0]); - auto innerQubit1 = b.x(args[1]); - return SmallVector{innerQubit0, innerQubit1}; - }); + auto res = + b.qcoIf(measureResult, {measuredQubit, q[1]}, [&](ValueRange args) { + auto innerQubit0 = b.x(args[0]); + auto innerQubit1 = b.x(args[1]); + return SmallVector{innerQubit0, innerQubit1}; + }); + q[0] = res[0]; + q[1] = res[1]; + auto [q0_, c0] = b.measure(q[0]); + auto [q1, c1] = b.measure(q[1]); + return {measureResult, c0, c1}; } -void ifElse(QCOProgramBuilder& b) { +SmallVector ifElse(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf( + auto res = b.qcoIf( measureResult, {measuredQubit}, [&](ValueRange args) { auto innerQubit = b.x(args[0]); @@ -2251,25 +3103,30 @@ void ifElse(QCOProgramBuilder& b) { auto innerQubit = b.z(args[0]); return SmallVector{innerQubit}; }); + q[0] = res[0]; + auto [q0_, c0] = b.measure(q[0]); + return {measureResult, c0}; } -void ifOneQubitOneTensor(QCOProgramBuilder& b) { +Value ifOneQubitOneTensor(QCOProgramBuilder& b) { auto q0 = b.allocQubit(); auto t0 = b.allocQubitRegister(1); auto q1 = b.h(q0); auto [measuredQubit, measureResult] = b.measure(q1); - b.qcoIf(measureResult, {measuredQubit, t0.value}, [&](ValueRange args) { - auto innerQubit0 = b.x(args[0]); - auto [t1, innerQubit1] = b.qtensorExtract(args[1], 0); - auto innerQubit2 = b.x(innerQubit1); - auto t2 = b.qtensorInsert(innerQubit2, t1, 0); - return SmallVector{innerQubit0, t2}; - }); + auto ifRes = + b.qcoIf(measureResult, {measuredQubit, t0.value}, [&](ValueRange args) { + auto innerQubit0 = b.x(args[0]); + auto [t1, innerQubit1] = b.qtensorExtract(args[1], 0); + auto innerQubit2 = b.x(innerQubit1); + auto t2 = b.qtensorInsert(innerQubit2, t1, 0); + return SmallVector{innerQubit0, t2}; + }); + return b.measure(ifRes[0]).second; } -void constantTrueIf(QCOProgramBuilder& b) { +Value constantTrueIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.qcoIf( + auto ifRes = b.qcoIf( true, q.qubits, [&](ValueRange args) { auto innerQubit = b.x(args[0]); @@ -2279,11 +3136,12 @@ void constantTrueIf(QCOProgramBuilder& b) { auto innerQubit = b.z(args[0]); return SmallVector{innerQubit}; }); + return b.measure(ifRes[0]).second; } -void constantFalseIf(QCOProgramBuilder& b) { +Value constantFalseIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.qcoIf( + auto ifRes = b.qcoIf( false, q.qubits, [&](ValueRange args) { auto innerQubit = b.x(args[0]); @@ -2293,13 +3151,14 @@ void constantFalseIf(QCOProgramBuilder& b) { auto innerQubit = b.z(args[0]); return SmallVector{innerQubit}; }); + return b.measure(ifRes[0]).second; } -void nestedTrueIf(QCOProgramBuilder& b) { +SmallVector nestedTrueIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf(measureResult, measuredQubit, [&](ValueRange outerArgs) { + auto ifRes = b.qcoIf(measureResult, measuredQubit, [&](ValueRange outerArgs) { auto innerResult = b.qcoIf(measureResult, outerArgs, [&](ValueRange innerArgs) { auto innerQubit = b.x(innerArgs[0]); @@ -2307,13 +3166,15 @@ void nestedTrueIf(QCOProgramBuilder& b) { }); return llvm::to_vector(innerResult); }); + auto [q1, c] = b.measure(ifRes[0]); + return {measureResult, c}; } -void nestedFalseIf(QCOProgramBuilder& b) { +SmallVector nestedFalseIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - b.qcoIf( + auto ifRes = b.qcoIf( measureResult, measuredQubit, [&](ValueRange args) { auto innerQubit = b.x(args[0]); @@ -2329,65 +3190,78 @@ void nestedFalseIf(QCOProgramBuilder& b) { }); return llvm::to_vector(innerResult); }); + auto [q1, c] = b.measure(ifRes[0]); + return {measureResult, c}; } -void qtensorAlloc(QCOProgramBuilder& b) { b.qtensorAlloc(3); } +SmallVector qtensorAlloc(QCOProgramBuilder& b) { + (void)b.qtensorAlloc(3); + return measureAndReturn(b, {}); +} -void qtensorDealloc(QCOProgramBuilder& b) { +SmallVector qtensorDealloc(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); b.qtensorDealloc(qtensor); + return measureAndReturn(b, {}); } -void qtensorFromElements(QCOProgramBuilder& b) { +SmallVector qtensorFromElements(QCOProgramBuilder& b) { auto q0 = b.allocQubit(); auto q1 = b.allocQubit(); auto q2 = b.allocQubit(); - b.qtensorFromElements({q0, q1, q2}); + (void)b.qtensorFromElements({q0, q1, q2}); + return measureAndReturn(b, {}); } -void qtensorExtract(QCOProgramBuilder& b) { +Value qtensorExtract(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); - b.qtensorExtract(qtensor, 0); + auto [t, q] = b.qtensorExtract(qtensor, 0); + return b.measure(q).second; } -void qtensorInsert(QCOProgramBuilder& b) { +SmallVector qtensorInsert(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); auto q1 = b.h(q0); - b.qtensorInsert(q1, extractOutTensor, 0); + (void)b.qtensorInsert(q1, extractOutTensor, 0); + return measureAndReturn(b, {}); } -void qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b) { +SmallVector qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); - b.qtensorInsert(q0, extractOutTensor, 1); + (void)b.qtensorInsert(q0, extractOutTensor, 1); + return measureAndReturn(b, {}); } -void qtensorExtractInsertSameIndex(QCOProgramBuilder& b) { +SmallVector qtensorExtractInsertSameIndex(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); - b.qtensorInsert(q0, extractOutTensor, 0); + (void)b.qtensorInsert(q0, extractOutTensor, 0); + return measureAndReturn(b, {}); } -void qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b) { +SmallVector qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); auto q1 = b.h(q0); auto insertOutTensor = b.qtensorInsert(q1, extractOutTensor, 0); auto [extractOutTensor1, q2] = b.qtensorExtract(insertOutTensor, 1); - b.qtensorInsert(q2, extractOutTensor1, 0); + (void)b.qtensorInsert(q2, extractOutTensor1, 0); + return measureAndReturn(b, {}); } -void qtensorInsertExtractSameIndex(QCOProgramBuilder& b) { +SmallVector qtensorInsertExtractSameIndex(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); auto q1 = b.h(q0); auto insertOutTensor = b.qtensorInsert(q1, extractOutTensor, 0); auto [extractOutTensor1, q2] = b.qtensorExtract(insertOutTensor, 0); - b.qtensorInsert(q2, extractOutTensor1, 0); + (void)b.qtensorInsert(q2, extractOutTensor1, 0); + return measureAndReturn(b, {}); } -void qtensorChain(QCOProgramBuilder& b) { +SmallVector qtensorChain(QCOProgramBuilder& b) { Value q0; Value q1; Value q2; @@ -2403,9 +3277,11 @@ void qtensorChain(QCOProgramBuilder& b) { qtensor = b.qtensorInsert(q1, qtensor, 1); qtensor = b.qtensorInsert(q0, qtensor, 0); b.qtensorDealloc(qtensor); + + return measureAndReturn(b, {}); } -void qtensorAlternativeChain(QCOProgramBuilder& b) { +SmallVector qtensorAlternativeChain(QCOProgramBuilder& b) { Value q0; Value q1; Value q2; @@ -2421,12 +3297,14 @@ void qtensorAlternativeChain(QCOProgramBuilder& b) { qtensor = b.qtensorInsert(q1, qtensor, 1); qtensor = b.qtensorInsert(q2, qtensor, 2); b.qtensorDealloc(qtensor); + + return measureAndReturn(b, {}); } -void simpleWhileReset(QCOProgramBuilder& b) { +Value simpleWhileReset(QCOProgramBuilder& b) { auto q0 = b.allocQubit(); auto q1 = b.h(q0); - b.scfWhile( + auto scfWhile = b.scfWhile( ValueRange{q1}, [&](ValueRange iterArgs) { auto [q2, measureResult] = b.measure(iterArgs[0]); @@ -2437,11 +3315,12 @@ void simpleWhileReset(QCOProgramBuilder& b) { auto q3 = b.h(iterArgs[0]); return SmallVector{q3}; }); + return b.measure(scfWhile[0]).second; } -void simpleDoWhileReset(QCOProgramBuilder& b) { +Value simpleDoWhileReset(QCOProgramBuilder& b) { auto q0 = b.allocQubit(); - b.scfWhile( + auto scfWhile = b.scfWhile( ValueRange{q0}, [&](ValueRange iterArgs) { auto q1 = b.h(iterArgs[0]); @@ -2450,46 +3329,54 @@ void simpleDoWhileReset(QCOProgramBuilder& b) { return SmallVector{q2}; }, [&](ValueRange iterArgs) { return llvm::to_vector(iterArgs); }); + return b.measure(scfWhile[0]).second; } -void simpleForLoop(QCOProgramBuilder& b) { +SmallVector simpleForLoop(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.scfFor(0, 2, 1, {reg.value}, [&](Value iv, ValueRange iterArgs) { - auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); - auto q1 = b.h(q0); - auto insert = b.qtensorInsert(q1, t0, iv); - return SmallVector{insert}; - }); + auto scfFor = + b.scfFor(0, 2, 1, {reg.value}, [&](Value iv, ValueRange iterArgs) { + auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); + auto q1 = b.h(q0); + auto insert = b.qtensorInsert(q1, t0, iv); + return SmallVector{insert}; + }); + return measureAndReturnQTensor(b, scfFor[0], 2); }; -void forLoopWithAngle(QCOProgramBuilder& b) { +Value forLoopWithAngle(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2); auto theta = b.floatConstant(0.123); - b.scfFor(0, 2, 1, {reg.value}, [&](Value iv, ValueRange iterArgs) { - auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); - auto q1 = b.rx(theta, q0); - auto insert = b.qtensorInsert(q1, t0, iv); - return SmallVector{insert}; - }); -}; + auto scfFor = + b.scfFor(0, 2, 1, {reg.value}, [&](Value iv, ValueRange iterArgs) { + auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); + auto q1 = b.rx(theta, q0); + auto insert = b.qtensorInsert(q1, t0, iv); + return SmallVector{insert}; + }); + auto [newReg, q] = b.qtensorExtract(scfFor[0], 0); + return b.measure(q).second; +} -void nestedForLoopIfOp(QCOProgramBuilder& b) { +Value nestedForLoopIfOp(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2); auto q0 = b.allocQubit(); - b.scfFor(0, 2, 1, {reg.value, q0}, [&](Value iv, ValueRange iterArgs) { - auto q1 = b.h(iterArgs[1]); - auto [q2, cond] = b.measure(q1); - auto ifOp = b.qcoIf(cond, iterArgs[0], [&](ValueRange args) { - auto [t0, q3] = b.qtensorExtract(args[0], iv); - auto q4 = b.h(q3); - auto insert = b.qtensorInsert(q4, t0, iv); - return SmallVector{insert}; - }); - return SmallVector{ifOp[0], q2}; - }); + auto scfFor = + b.scfFor(0, 2, 1, {reg.value, q0}, [&](Value iv, ValueRange iterArgs) { + auto q1 = b.h(iterArgs[1]); + auto [q2, cond] = b.measure(q1); + auto ifOp = b.qcoIf(cond, iterArgs[0], [&](ValueRange args) { + auto [t0, q3] = b.qtensorExtract(args[0], iv); + auto q4 = b.h(q3); + auto insert = b.qtensorInsert(q4, t0, iv); + return SmallVector{insert}; + }); + return SmallVector{ifOp[0], q2}; + }); + return b.measure(scfFor[1]).second; } -void nestedForLoopWhileOp(QCOProgramBuilder& b) { +SmallVector nestedForLoopWhileOp(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2); auto loopResult = b.scfFor(0, 2, 1, {reg.value}, [&](Value iv, ValueRange iterArgs) { @@ -2498,61 +3385,63 @@ void nestedForLoopWhileOp(QCOProgramBuilder& b) { auto insert = b.qtensorInsert(q1, t0, iv); return SmallVector{insert}; }); - b.scfFor(0, 2, 1, loopResult, [&](Value iv, ValueRange iterArgs) { - auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); - auto whileResult = b.scfWhile( - q0, - [&](ValueRange iterArgs) { - auto [q1, measureResult] = b.measure(iterArgs[0]); - b.scfCondition(measureResult, q1); - return SmallVector{q1}; - }, - [&](ValueRange iterArgs) { - auto q2 = b.h(iterArgs[0]); - return SmallVector{q2}; - }); - auto insert = b.qtensorInsert(whileResult[0], t0, iv); - return SmallVector{insert}; - }); + auto scfFor = + b.scfFor(0, 2, 1, loopResult, [&](Value iv, ValueRange iterArgs) { + auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); + auto whileResult = b.scfWhile( + q0, + [&](ValueRange innerIterArgs) { + auto [q1, measureResult] = b.measure(innerIterArgs[0]); + b.scfCondition(measureResult, q1); + return SmallVector{q1}; + }, + [&](ValueRange innerIterArgs) { + auto q2 = b.h(innerIterArgs[0]); + return SmallVector{q2}; + }); + auto insert = b.qtensorInsert(whileResult[0], t0, iv); + return SmallVector{insert}; + }); + return measureAndReturnQTensor(b, scfFor[0], 2); } -void nestedForLoopCtrlOpWithSeparateQubit(QCOProgramBuilder& b) { +Value nestedForLoopCtrlOpWithSeparateQubit(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto control0 = b.allocQubit(); auto control1 = b.h(control0); - b.scfFor(0, 3, 1, {reg.value, control1}, [&](Value iv, ValueRange iterArgs) { - auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); - auto q1 = b.h(q0); - auto [controls, targets] = b.ctrl(iterArgs[1], q1, [&](ValueRange args) { - auto q2 = b.x(args[0]); - return SmallVector{q2}; - }); - auto insert = b.qtensorInsert(targets[0], t0, iv); - return SmallVector{insert, controls[0]}; - }); + auto scfFor = b.scfFor( + 0, 3, 1, {reg.value, control1}, [&](Value iv, ValueRange iterArgs) { + auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); + auto q1 = b.h(q0); + auto [controlOut, targetOut] = + b.ctrl(iterArgs[1], q1, [&](Value target) { return b.x(target); }); + auto insert = b.qtensorInsert(targetOut, t0, iv); + return SmallVector{insert, controlOut}; + }); + return b.measure(scfFor[1]).second; } -void nestedForLoopCtrlOpWithExtractedQubit(QCOProgramBuilder& b) { +Value nestedForLoopCtrlOpWithExtractedQubit(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4); auto control = b.h(reg[0]); - b.scfFor(1, 4, 1, {reg.value, control}, [&](Value iv, ValueRange iterArgs) { - auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); - auto q1 = b.h(q0); - auto [controls, targets] = b.ctrl(iterArgs[1], q1, [&](ValueRange args) { - auto q2 = b.x(args[0]); - return SmallVector{q2}; - }); - auto insert = b.qtensorInsert(targets[0], t0, iv); - return SmallVector{insert, controls[0]}; - }); + auto scfFor = b.scfFor( + 1, 4, 1, {reg.value, control}, [&](Value iv, ValueRange iterArgs) { + auto [t0, q0] = b.qtensorExtract(iterArgs[0], iv); + auto q1 = b.h(q0); + auto [controlOut, targetOut] = + b.ctrl(iterArgs[1], q1, [&](Value target) { return b.x(target); }); + auto insert = b.qtensorInsert(targetOut, t0, iv); + return SmallVector{insert, controlOut}; + }); + return b.measure(scfFor[1]).second; } -void nestedIfOpForLoop(QCOProgramBuilder& b) { +Value nestedIfOpForLoop(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto q0 = b.allocQubit(); auto q1 = b.h(q0); auto [q2, cond] = b.measure(q1); - b.qcoIf( + auto ifRes = b.qcoIf( cond, {reg.value, q2}, [&](ValueRange args) { auto q3 = b.h(args[1]); @@ -2568,16 +3457,17 @@ void nestedIfOpForLoop(QCOProgramBuilder& b) { }); return SmallVector{scfFor[0], args[1]}; }); + return b.measure(ifRes[1]).second; } -void nestedIfOpForLoopWithAngle(QCOProgramBuilder& b) { +Value nestedIfOpForLoopWithAngle(QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto q0 = b.allocQubit(); auto theta1 = b.floatConstant(0.123); auto theta2 = b.floatConstant(0.456); auto q1 = b.h(q0); auto [q2, cond] = b.measure(q1); - b.qcoIf( + auto res = b.qcoIf( cond, {reg.value, q2}, [&](ValueRange args) { auto q3 = b.rx(theta1, args[1]); @@ -2593,6 +3483,129 @@ void nestedIfOpForLoopWithAngle(QCOProgramBuilder& b) { }); return SmallVector{scfFor[0], args[1]}; }); + return b.measure(res[1]).second; +} + +SmallVector controlledXH(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto [ctrl, targ] = b.ctrl(q[0], q[1], [&](Value target) { + target = b.x(target); + return b.h(target); + }); + return measureAndReturn(b, {ctrl, targ}); +} + +SmallVector controlledInverseHT(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto [ctrl, targ] = b.ctrl(q[0], q[1], [&](ValueRange targets) { + auto wire = b.inv({targets[0]}, [&](ValueRange innerTargets) { + auto inner = b.h(innerTargets[0]); + inner = b.t(inner); + return SmallVector{inner}; + })[0]; + return SmallVector{wire}; + }); + return measureAndReturn(b, {ctrl[0], targ[0]}); +} + +SmallVector inverseTwoRxRy(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto res = b.inv({q[0], q[1]}, [&](ValueRange targets) { + auto w0 = b.rx(0.2, targets[0]); + auto w1 = b.ry(0.3, targets[1]); + return SmallVector{w0, w1}; + }); + return measureAndReturn(b, res); +} + +SmallVector inverseCxThenRz(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto res = b.inv({q[0], q[1]}, [&](ValueRange targets) { + auto w0 = targets[0]; + auto w1 = targets[1]; + std::tie(w0, w1) = b.cx(w0, w1); + w1 = b.rz(0.4, w1); + return SmallVector{w0, w1}; + }); + return measureAndReturn(b, res); +} + +SmallVector inverseDcxThenRz(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto res = b.inv({q[0], q[1]}, [&](ValueRange targets) { + auto w0 = targets[0]; + auto w1 = targets[1]; + std::tie(w0, w1) = b.dcx(w0, w1); + w1 = b.rz(0.4, w1); + return SmallVector{w0, w1}; + }); + return measureAndReturn(b, res); +} + +Value inverseGphaseBarrierX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + auto res = b.inv(q[0], [&](Value target) { + b.gphase(0.25); + auto wire = b.barrier({target})[0]; + wire = b.x(wire); + return wire; + }); + return b.measure(res).second; +} + +Value inverseNestedInvHAndT(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + auto res = b.inv(q[0], [&](Value target) { + auto wire = b.inv(target, [&](Value inner) { return b.h(inner); }); + return b.t(wire); + }); + return b.measure(res).second; +} + +SmallVector inverseNestedInvHAndX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + auto res = b.inv({q[0], q[1]}, [&](ValueRange targets) { + auto w0 = b.inv(targets[0], [&](Value inner) { return b.h(inner); }); + auto w1 = b.x(targets[1]); + return SmallVector{w0, w1}; + }); + return measureAndReturn(b, res); +} + +SmallVector inverseThreeWireRxRyRz(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange targets) { + auto w0 = b.rx(0.2, targets[0]); + auto w1 = b.ry(0.3, targets[1]); + auto w2 = b.rz(0.4, targets[2]); + return SmallVector{w0, w1, w2}; + }); + return measureAndReturn(b, res); +} + +SmallVector inverseThreeWireNestedTwoInv(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange targets) { + auto inner = b.inv({targets[0], targets[1]}, [&](ValueRange innerTargets) { + auto w0 = b.rx(0.2, innerTargets[0]); + auto w1 = b.ry(0.3, innerTargets[1]); + return SmallVector{w0, w1}; + }); + auto w2 = b.rz(0.4, targets[2]); + return SmallVector{inner[0], inner[1], w2}; + }); + return measureAndReturn(b, res); +} + +SmallVector inverseWithThreeQubitOpInBody(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + auto res = b.inv({q[0], q[1], q[2]}, [&](ValueRange targets) { + auto [controls, innerTarget] = + b.ctrl({targets[0], targets[1]}, targets[2], + [&](Value inner) { return b.x(inner); }); + return SmallVector{controls[0], controls[1], innerTarget}; + }); + return measureAndReturn(b, res); } } // namespace mlir::qco diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index b568b4b02f..f764e7ab09 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -10,1146 +10,1197 @@ #pragma once +#include +#include + namespace mlir::qco { class QCOProgramBuilder; /// Creates an empty QCO program. -void emptyQCO(QCOProgramBuilder& builder); +Value emptyQCO(QCOProgramBuilder& builder); // --- Qubit Management ----------------------------------------------------- // /// Allocates a single qubit. -void allocQubit(QCOProgramBuilder& b); +Value allocQubit(QCOProgramBuilder& b); + +/// Allocates two individual qubits. +SmallVector alloc2Qubits(QCOProgramBuilder& b); + +/// Allocates a single qubit that is not measured. +Value allocQubitNoMeasure(QCOProgramBuilder& b); + +/// Allocates a qubit register of size `1`. +Value alloc1QubitRegister(QCOProgramBuilder& b); /// Allocates a qubit register of size `2`. -void allocQubitRegister(QCOProgramBuilder& b); +SmallVector alloc2QubitRegister(QCOProgramBuilder& b); + +/// Allocates a qubit register of size `3`. +SmallVector alloc3QubitRegister(QCOProgramBuilder& b); /// Allocates two qubit registers of size `2` and `3`. -void allocMultipleQubitRegisters(QCOProgramBuilder& b); +SmallVector allocMultipleQubitRegisters(QCOProgramBuilder& b); /// Allocates a large qubit register. -void allocLargeRegister(QCOProgramBuilder& b); +Value allocLargeRegister(QCOProgramBuilder& b); /// Allocates two inline qubits. -void staticQubits(QCOProgramBuilder& b); +SmallVector staticQubits(QCOProgramBuilder& b); + +/// Allocates two inline qubits without measuring them. +Value staticQubitsNoMeasure(QCOProgramBuilder& b); /// Allocates two static qubits and applies operations. -void staticQubitsWithOps(QCOProgramBuilder& b); +SmallVector staticQubitsWithOps(QCOProgramBuilder& b); /// Allocates two static qubits and applies parametric gates. -void staticQubitsWithParametricOps(QCOProgramBuilder& b); +SmallVector staticQubitsWithParametricOps(QCOProgramBuilder& b); /// Allocates two static qubits and applies a two-target gate. -void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b); +SmallVector staticQubitsWithTwoTargetOps(QCOProgramBuilder& b); /// Allocates two static qubits and applies a controlled gate. -void staticQubitsWithCtrl(QCOProgramBuilder& b); +SmallVector staticQubitsWithCtrl(QCOProgramBuilder& b); /// Allocates a static qubit and applies an inverse modifier. -void staticQubitsWithInv(QCOProgramBuilder& b); +Value staticQubitsWithInv(QCOProgramBuilder& b); /// Allocates and explicitly sinks a single qubit. -void allocSinkPair(QCOProgramBuilder& b); +Value allocSinkPair(QCOProgramBuilder& b); + +/// Allocates two qubits and performs a set of dead gates on them. +SmallVector deadGatesProgram(QCOProgramBuilder& b); + +/// Allocates two qubits and performs a set of dead gates on them, including +/// `if` operations. +Value deadGatesWithIfOpProgram(QCOProgramBuilder& b); + +/// Allocates two qubits and performs only non-dead `if` operations. +Value deadGatesWithIfOpSimplified(QCOProgramBuilder& b); // --- Invalid / mixed addressing (unit tests) -------------------------------- /// @pre `builder.initialize()`. Fatal mixed addressing: static then dynamic /// alloc. -void mixedStaticThenDynamicQubit(QCOProgramBuilder& b); +SmallVector mixedStaticThenDynamicQubit(QCOProgramBuilder& b); /// @pre `builder.initialize()`. Fatal mixed addressing: `qtensor` alloc then /// static. -void mixedDynamicRegisterThenStaticQubit(QCOProgramBuilder& b); +Value mixedDynamicRegisterThenStaticQubit(QCOProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. -void singleMeasurementToSingleBit(QCOProgramBuilder& b); +Value singleMeasurementToSingleBit(QCOProgramBuilder& b); /// Repeatedly measures a single qubit into the same classical bit. -void repeatedMeasurementToSameBit(QCOProgramBuilder& b); +Value repeatedMeasurementToSameBit(QCOProgramBuilder& b); /// Repeatedly measures a single qubit into different classical bits. -void repeatedMeasurementToDifferentBits(QCOProgramBuilder& b); +SmallVector repeatedMeasurementToDifferentBits(QCOProgramBuilder& b); /// Measures multiple qubits into multiple classical bits. -void multipleClassicalRegistersAndMeasurements(QCOProgramBuilder& b); +SmallVector +multipleClassicalRegistersAndMeasurements(QCOProgramBuilder& b); /// Measures a single qubit into a single classical bit, without explicitly /// allocating a quantum or classical register. -void measurementWithoutRegisters(QCOProgramBuilder& b); +Value measurementWithoutRegisters(QCOProgramBuilder& b); // --- ResetOp -------------------------------------------------------------- // /// Resets a single qubit without any operations being applied. -void resetQubitWithoutOp(QCOProgramBuilder& b); +Value resetQubitWithoutOp(QCOProgramBuilder& b); /// Resets multiple qubits without any operations being applied. -void resetMultipleQubitsWithoutOp(QCOProgramBuilder& b); +SmallVector resetMultipleQubitsWithoutOp(QCOProgramBuilder& b); /// Repeatedly resets a single qubit without any operations being applied. -void repeatedResetWithoutOp(QCOProgramBuilder& b); +Value repeatedResetWithoutOp(QCOProgramBuilder& b); /// Resets a single qubit after a single operation. -void resetQubitAfterSingleOp(QCOProgramBuilder& b); +Value resetQubitAfterSingleOp(QCOProgramBuilder& b); /// Resets multiple qubits after a single operation. -void resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b); +SmallVector resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b); /// Repeatedly resets a single qubit after a single operation. -void repeatedResetAfterSingleOp(QCOProgramBuilder& b); +Value repeatedResetAfterSingleOp(QCOProgramBuilder& b); // --- GPhaseOp ------------------------------------------------------------- // /// Creates a circuit with just a global phase. -void globalPhase(QCOProgramBuilder& b); +Value globalPhase(QCOProgramBuilder& b); /// Creates a controlled global phase gate with a single control qubit. -void singleControlledGlobalPhase(QCOProgramBuilder& b); +Value singleControlledGlobalPhase(QCOProgramBuilder& b); /// Creates a multi-controlled global phase gate with multiple control qubits. -void multipleControlledGlobalPhase(QCOProgramBuilder& b); +SmallVector multipleControlledGlobalPhase(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a global phase gate. -void inverseGlobalPhase(QCOProgramBuilder& b); +Value inverseGlobalPhase(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled global /// phase gate. -void inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b); // --- IdOp ----------------------------------------------------------------- // /// Creates a circuit with just an identity gate. -void identity(QCOProgramBuilder& b); +Value identity(QCOProgramBuilder& b); /// Creates a controlled identity gate with a single control qubit. -void singleControlledIdentity(QCOProgramBuilder& b); +SmallVector singleControlledIdentity(QCOProgramBuilder& b); /// Creates a multi-controlled identity gate with multiple control qubits. -void multipleControlledIdentity(QCOProgramBuilder& b); +SmallVector multipleControlledIdentity(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled identity gate. -void nestedControlledIdentity(QCOProgramBuilder& b); +SmallVector nestedControlledIdentity(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled identity gate. -void trivialControlledIdentity(QCOProgramBuilder& b); +Value trivialControlledIdentity(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an identity gate. -void inverseIdentity(QCOProgramBuilder& b); +Value inverseIdentity(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled identity /// gate. -void inverseMultipleControlledIdentity(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledIdentity(QCOProgramBuilder& b); // --- XOp ------------------------------------------------------------------ // /// Creates a circuit with just an X gate. -void x(QCOProgramBuilder& b); +Value x(QCOProgramBuilder& b); /// Creates a circuit with a single controlled X gate. -void singleControlledX(QCOProgramBuilder& b); +SmallVector singleControlledX(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled X gate. -void multipleControlledX(QCOProgramBuilder& b); +SmallVector multipleControlledX(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled X gate. -void nestedControlledX(QCOProgramBuilder& b); +SmallVector nestedControlledX(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled X gate. -void trivialControlledX(QCOProgramBuilder& b); +Value trivialControlledX(QCOProgramBuilder& b); /// Creates a circuit with repeated controlled X gates. -void repeatedControlledX(QCOProgramBuilder& b); +SmallVector repeatedControlledX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an X gate. -void inverseX(QCOProgramBuilder& b); +Value inverseX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled X gate. -void inverseMultipleControlledX(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledX(QCOProgramBuilder& b); /// Creates a circuit with two subsequent X gates. -void twoX(QCOProgramBuilder& b); +Value twoX(QCOProgramBuilder& b); /// Creates a circuit with a control modifier applied to two subsequent X gates. -void controlledTwoX(QCOProgramBuilder& b); +SmallVector controlledTwoX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to two subsequent X /// gates. -void inverseTwoX(QCOProgramBuilder& b); +Value inverseTwoX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a global phase and an /// X gate. -void inverseGphaseX(QCOProgramBuilder& b); +Value inverseGphaseX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a global phase and a /// barrier. -void inverseGphaseBarrier(QCOProgramBuilder& b); +Value inverseGphaseBarrier(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to two consecutive /// barriers. -void inverseTwoBarriersInInv(QCOProgramBuilder& b); +Value inverseTwoBarriersInInv(QCOProgramBuilder& b); // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. -void y(QCOProgramBuilder& b); +Value y(QCOProgramBuilder& b); /// Creates a circuit with a single controlled Y gate. -void singleControlledY(QCOProgramBuilder& b); +SmallVector singleControlledY(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled Y gate. -void multipleControlledY(QCOProgramBuilder& b); +SmallVector multipleControlledY(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled Y gate. -void nestedControlledY(QCOProgramBuilder& b); +SmallVector nestedControlledY(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled Y gate. -void trivialControlledY(QCOProgramBuilder& b); +Value trivialControlledY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Y gate. -void inverseY(QCOProgramBuilder& b); +Value inverseY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Y gate. -void inverseMultipleControlledY(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledY(QCOProgramBuilder& b); /// Creates a circuit with two Y gates in a row. -void twoY(QCOProgramBuilder& b); +Value twoY(QCOProgramBuilder& b); // --- ZOp ------------------------------------------------------------------ // /// Creates a circuit with just a Z gate. -void z(QCOProgramBuilder& b); +Value z(QCOProgramBuilder& b); /// Creates a circuit with a single controlled Z gate. -void singleControlledZ(QCOProgramBuilder& b); +SmallVector singleControlledZ(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled Z gate. -void multipleControlledZ(QCOProgramBuilder& b); +SmallVector multipleControlledZ(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled Z gate. -void nestedControlledZ(QCOProgramBuilder& b); +SmallVector nestedControlledZ(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled Z gate. -void trivialControlledZ(QCOProgramBuilder& b); +Value trivialControlledZ(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Z gate. -void inverseZ(QCOProgramBuilder& b); +Value inverseZ(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Z gate. -void inverseMultipleControlledZ(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledZ(QCOProgramBuilder& b); /// Creates a circuit with two Z gates in a row. -void twoZ(QCOProgramBuilder& b); +Value twoZ(QCOProgramBuilder& b); // --- HOp ------------------------------------------------------------------ // /// Creates a circuit with just an H gate. -void h(QCOProgramBuilder& b); +Value h(QCOProgramBuilder& b); /// Creates a circuit with a single controlled H gate. -void singleControlledH(QCOProgramBuilder& b); +SmallVector singleControlledH(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled H gate. -void multipleControlledH(QCOProgramBuilder& b); +SmallVector multipleControlledH(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled H gate. -void nestedControlledH(QCOProgramBuilder& b); +SmallVector nestedControlledH(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled H gate. -void trivialControlledH(QCOProgramBuilder& b); +Value trivialControlledH(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an H gate. -void inverseH(QCOProgramBuilder& b); +Value inverseH(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled H gate. -void inverseMultipleControlledH(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledH(QCOProgramBuilder& b); /// Creates a circuit with two H gates in a row. -void twoH(QCOProgramBuilder& b); +Value twoH(QCOProgramBuilder& b); /// Creates a circuit with just an H gate and no qubit register. -void hWithoutRegister(QCOProgramBuilder& b); +Value hWithoutRegister(QCOProgramBuilder& b); // --- SOp ------------------------------------------------------------------ // /// Creates a circuit with just an S gate. -void s(QCOProgramBuilder& b); +Value s(QCOProgramBuilder& b); /// Creates a circuit with a single controlled S gate. -void singleControlledS(QCOProgramBuilder& b); +SmallVector singleControlledS(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled S gate. -void multipleControlledS(QCOProgramBuilder& b); +SmallVector multipleControlledS(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled S gate. -void nestedControlledS(QCOProgramBuilder& b); +SmallVector nestedControlledS(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled S gate. -void trivialControlledS(QCOProgramBuilder& b); +Value trivialControlledS(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an S gate. -void inverseS(QCOProgramBuilder& b); +Value inverseS(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled S gate. -void inverseMultipleControlledS(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledS(QCOProgramBuilder& b); /// Creates a circuit with an S gate followed by an Sdg gate. -void sThenSdg(QCOProgramBuilder& b); +Value sThenSdg(QCOProgramBuilder& b); /// Creates a circuit with two S gates in a row. -void twoS(QCOProgramBuilder& b); +Value twoS(QCOProgramBuilder& b); // --- SdgOp ---------------------------------------------------------------- // /// Creates a circuit with just an Sdg gate. -void sdg(QCOProgramBuilder& b); +Value sdg(QCOProgramBuilder& b); /// Creates a circuit with a single controlled Sdg gate. -void singleControlledSdg(QCOProgramBuilder& b); +SmallVector singleControlledSdg(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled Sdg gate. -void multipleControlledSdg(QCOProgramBuilder& b); +SmallVector multipleControlledSdg(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled Sdg gate. -void nestedControlledSdg(QCOProgramBuilder& b); +SmallVector nestedControlledSdg(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled Sdg gate. -void trivialControlledSdg(QCOProgramBuilder& b); +Value trivialControlledSdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an Sdg gate. -void inverseSdg(QCOProgramBuilder& b); +Value inverseSdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Sdg gate. -void inverseMultipleControlledSdg(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledSdg(QCOProgramBuilder& b); /// Creates a circuit with an Sdg gate followed an S gate. -void sdgThenS(QCOProgramBuilder& b); +Value sdgThenS(QCOProgramBuilder& b); /// Creates a circuit with two Sdg gates in a row. -void twoSdg(QCOProgramBuilder& b); +Value twoSdg(QCOProgramBuilder& b); // --- TOp ------------------------------------------------------------------ // /// Creates a circuit with just a T gate. -void t_(QCOProgramBuilder& b); // NOLINT(*-identifier-naming) +Value t_(QCOProgramBuilder& b); // NOLINT(*-identifier-naming) /// Creates a circuit with a single controlled T gate. -void singleControlledT(QCOProgramBuilder& b); +SmallVector singleControlledT(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled T gate. -void multipleControlledT(QCOProgramBuilder& b); +SmallVector multipleControlledT(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled T gate. -void nestedControlledT(QCOProgramBuilder& b); +SmallVector nestedControlledT(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled T gate. -void trivialControlledT(QCOProgramBuilder& b); +Value trivialControlledT(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a T gate. -void inverseT(QCOProgramBuilder& b); +Value inverseT(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled T gate. -void inverseMultipleControlledT(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledT(QCOProgramBuilder& b); /// Creates a circuit with a T gate followed by a Tdg gate. -void tThenTdg(QCOProgramBuilder& b); +Value tThenTdg(QCOProgramBuilder& b); /// Creates a circuit with two T gates in a row. -void twoT(QCOProgramBuilder& b); +Value twoT(QCOProgramBuilder& b); // --- TdgOp ---------------------------------------------------------------- // /// Creates a circuit with just a Tdg gate. -void tdg(QCOProgramBuilder& b); +Value tdg(QCOProgramBuilder& b); /// Creates a circuit with a single controlled Tdg gate. -void singleControlledTdg(QCOProgramBuilder& b); +SmallVector singleControlledTdg(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled Tdg gate. -void multipleControlledTdg(QCOProgramBuilder& b); +SmallVector multipleControlledTdg(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled Tdg gate. -void nestedControlledTdg(QCOProgramBuilder& b); +SmallVector nestedControlledTdg(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled Tdg gate. -void trivialControlledTdg(QCOProgramBuilder& b); +Value trivialControlledTdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a Tdg gate. -void inverseTdg(QCOProgramBuilder& b); +Value inverseTdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Tdg gate. -void inverseMultipleControlledTdg(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledTdg(QCOProgramBuilder& b); /// Creates a circuit with a Tdg gate followed by a T gate. -void tdgThenT(QCOProgramBuilder& b); +Value tdgThenT(QCOProgramBuilder& b); /// Creates a circuit with two Tdg gates in a row. -void twoTdg(QCOProgramBuilder& b); +Value twoTdg(QCOProgramBuilder& b); // --- SXOp ----------------------------------------------------------------- // /// Creates a circuit with just an SX gate. -void sx(QCOProgramBuilder& b); +Value sx(QCOProgramBuilder& b); /// Creates a circuit with a single controlled SX gate. -void singleControlledSx(QCOProgramBuilder& b); +SmallVector singleControlledSx(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled SX gate. -void multipleControlledSx(QCOProgramBuilder& b); +SmallVector multipleControlledSx(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled SX gate. -void nestedControlledSx(QCOProgramBuilder& b); +SmallVector nestedControlledSx(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled SX gate. -void trivialControlledSx(QCOProgramBuilder& b); +Value trivialControlledSx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an SX gate. -void inverseSx(QCOProgramBuilder& b); +Value inverseSx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SX gate. -void inverseMultipleControlledSx(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledSx(QCOProgramBuilder& b); /// Creates a circuit with an SX gate followed by an SXdg gate. -void sxThenSxdg(QCOProgramBuilder& b); +Value sxThenSxdg(QCOProgramBuilder& b); /// Creates a circuit with two SX gates in a row. -void twoSx(QCOProgramBuilder& b); +Value twoSx(QCOProgramBuilder& b); // --- SXdgOp --------------------------------------------------------------- // /// Creates a circuit with just an SXdg gate. -void sxdg(QCOProgramBuilder& b); +Value sxdg(QCOProgramBuilder& b); /// Creates a circuit with a single controlled SXdg gate. -void singleControlledSxdg(QCOProgramBuilder& b); +SmallVector singleControlledSxdg(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled SXdg gate. -void multipleControlledSxdg(QCOProgramBuilder& b); +SmallVector multipleControlledSxdg(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled SXdg gate. -void nestedControlledSxdg(QCOProgramBuilder& b); +SmallVector nestedControlledSxdg(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled SXdg gate. -void trivialControlledSxdg(QCOProgramBuilder& b); +Value trivialControlledSxdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an SXdg gate. -void inverseSxdg(QCOProgramBuilder& b); +Value inverseSxdg(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SXdg /// gate. -void inverseMultipleControlledSxdg(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledSxdg(QCOProgramBuilder& b); /// Creates a circuit with an SXdg gate followed by an SX gate. -void sxdgThenSx(QCOProgramBuilder& b); +Value sxdgThenSx(QCOProgramBuilder& b); /// Creates a circuit with two SXdg gates in a row. -void twoSxdg(QCOProgramBuilder& b); +Value twoSxdg(QCOProgramBuilder& b); // --- RXOp ----------------------------------------------------------------- // /// Creates a circuit with just an RX gate. -void rx(QCOProgramBuilder& b); +Value rx(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RX gate. -void singleControlledRx(QCOProgramBuilder& b); +SmallVector singleControlledRx(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RX gate. -void multipleControlledRx(QCOProgramBuilder& b); +SmallVector multipleControlledRx(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RX gate. -void nestedControlledRx(QCOProgramBuilder& b); +SmallVector nestedControlledRx(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RX gate. -void trivialControlledRx(QCOProgramBuilder& b); +Value trivialControlledRx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RX gate. -void inverseRx(QCOProgramBuilder& b); +Value inverseRx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RX gate. -void inverseMultipleControlledRx(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRx(QCOProgramBuilder& b); /// Creates a circuit with two RX gates in a row with opposite phases. -void twoRxOppositePhase(QCOProgramBuilder& b); +Value twoRxOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with an RX gate with an angle of pi/2. -void rxPiOver2(QCOProgramBuilder& b); +Value rxPiOver2(QCOProgramBuilder& b); // --- RYOp ----------------------------------------------------------------- // /// Creates a circuit with just an RY gate. -void ry(QCOProgramBuilder& b); +Value ry(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RY gate. -void singleControlledRy(QCOProgramBuilder& b); +SmallVector singleControlledRy(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RY gate. -void multipleControlledRy(QCOProgramBuilder& b); +SmallVector multipleControlledRy(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RY gate. -void nestedControlledRy(QCOProgramBuilder& b); +SmallVector nestedControlledRy(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RY gate. -void trivialControlledRy(QCOProgramBuilder& b); +Value trivialControlledRy(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RY gate. -void inverseRy(QCOProgramBuilder& b); +Value inverseRy(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RY gate. -void inverseMultipleControlledRy(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRy(QCOProgramBuilder& b); /// Creates a circuit with two RY gates in a row with opposite phases. -void twoRyOppositePhase(QCOProgramBuilder& b); +Value twoRyOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with an RY gate with an angle of pi/2. -void ryPiOver2(QCOProgramBuilder& b); +Value ryPiOver2(QCOProgramBuilder& b); // --- RZOp ----------------------------------------------------------------- // /// Creates a circuit with just an RZ gate. -void rz(QCOProgramBuilder& b); +Value rz(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RZ gate. -void singleControlledRz(QCOProgramBuilder& b); +SmallVector singleControlledRz(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RZ gate. -void multipleControlledRz(QCOProgramBuilder& b); +SmallVector multipleControlledRz(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RZ gate. -void nestedControlledRz(QCOProgramBuilder& b); +SmallVector nestedControlledRz(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RZ gate. -void trivialControlledRz(QCOProgramBuilder& b); +Value trivialControlledRz(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZ gate. -void inverseRz(QCOProgramBuilder& b); +Value inverseRz(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZ gate. -void inverseMultipleControlledRz(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRz(QCOProgramBuilder& b); /// Creates a circuit with two RZ gates in a row with opposite phases. -void twoRzOppositePhase(QCOProgramBuilder& b); +Value twoRzOppositePhase(QCOProgramBuilder& b); // --- POp ------------------------------------------------------------------ // /// Creates a circuit with just a P gate. -void p(QCOProgramBuilder& b); +Value p(QCOProgramBuilder& b); /// Creates a circuit with a single controlled P gate. -void singleControlledP(QCOProgramBuilder& b); +SmallVector singleControlledP(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled P gate. -void multipleControlledP(QCOProgramBuilder& b); +SmallVector multipleControlledP(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled P gate. -void nestedControlledP(QCOProgramBuilder& b); +SmallVector nestedControlledP(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled P gate. -void trivialControlledP(QCOProgramBuilder& b); +Value trivialControlledP(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a P gate. -void inverseP(QCOProgramBuilder& b); +Value inverseP(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled P gate. -void inverseMultipleControlledP(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledP(QCOProgramBuilder& b); /// Creates a circuit with two P gates in a row with opposite phases. -void twoPOppositePhase(QCOProgramBuilder& b); +Value twoPOppositePhase(QCOProgramBuilder& b); // --- ROp ------------------------------------------------------------------ // /// Creates a circuit with just an R gate. -void r(QCOProgramBuilder& b); +Value r(QCOProgramBuilder& b); /// Creates a circuit with a single controlled R gate. -void singleControlledR(QCOProgramBuilder& b); +SmallVector singleControlledR(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled R gate. -void multipleControlledR(QCOProgramBuilder& b); +SmallVector multipleControlledR(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled R gate. -void nestedControlledR(QCOProgramBuilder& b); +SmallVector nestedControlledR(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled R gate. -void trivialControlledR(QCOProgramBuilder& b); +Value trivialControlledR(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an R gate. -void inverseR(QCOProgramBuilder& b); +Value inverseR(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled R gate. -void inverseMultipleControlledR(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledR(QCOProgramBuilder& b); /// Creates a circuit with an R gate that can be canonicalized to an RX gate. -void canonicalizeRToRx(QCOProgramBuilder& b); +Value canonicalizeRToRx(QCOProgramBuilder& b); /// Creates a circuit with an R gate that can be canonicalized to an RY gate. -void canonicalizeRToRy(QCOProgramBuilder& b); +Value canonicalizeRToRy(QCOProgramBuilder& b); /// Creates a circuit with two R gates in a row with the same `phi`. -void twoR(QCOProgramBuilder& b); +Value twoR(QCOProgramBuilder& b); // --- U2Op ----------------------------------------------------------------- // /// Creates a circuit with just a U2 gate. -void u2(QCOProgramBuilder& b); +Value u2(QCOProgramBuilder& b); /// Creates a circuit with a single controlled U2 gate. -void singleControlledU2(QCOProgramBuilder& b); +SmallVector singleControlledU2(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled U2 gate. -void multipleControlledU2(QCOProgramBuilder& b); +SmallVector multipleControlledU2(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled U2 gate. -void nestedControlledU2(QCOProgramBuilder& b); +SmallVector nestedControlledU2(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled U2 gate. -void trivialControlledU2(QCOProgramBuilder& b); +Value trivialControlledU2(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a U2 gate. -void inverseU2(QCOProgramBuilder& b); +Value inverseU2(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled U2 gate. -void inverseMultipleControlledU2(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledU2(QCOProgramBuilder& b); /// Creates a circuit with a U2 gate that can be canonicalized to an H gate. -void canonicalizeU2ToH(QCOProgramBuilder& b); +Value canonicalizeU2ToH(QCOProgramBuilder& b); /// Creates a circuit with a U2 gate that can be canonicalized to an RX gate. -void canonicalizeU2ToRx(QCOProgramBuilder& b); +Value canonicalizeU2ToRx(QCOProgramBuilder& b); /// Creates a circuit with a U2 gate that can be canonicalized to an RY gate. -void canonicalizeU2ToRy(QCOProgramBuilder& b); +Value canonicalizeU2ToRy(QCOProgramBuilder& b); // --- UOp ------------------------------------------------------------------ // /// Creates a circuit with just a U gate. -void u(QCOProgramBuilder& b); +Value u(QCOProgramBuilder& b); /// Creates a circuit with a single controlled U gate. -void singleControlledU(QCOProgramBuilder& b); +SmallVector singleControlledU(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled U gate. -void multipleControlledU(QCOProgramBuilder& b); +SmallVector multipleControlledU(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled U gate. -void nestedControlledU(QCOProgramBuilder& b); +SmallVector nestedControlledU(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled U gate. -void trivialControlledU(QCOProgramBuilder& b); +Value trivialControlledU(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a U gate. -void inverseU(QCOProgramBuilder& b); +Value inverseU(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled U gate. -void inverseMultipleControlledU(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledU(QCOProgramBuilder& b); /// Creates a circuit with a U gate that can be canonicalized to a P gate. -void canonicalizeUToP(QCOProgramBuilder& b); +Value canonicalizeUToP(QCOProgramBuilder& b); /// Creates a circuit with a U gate that can be canonicalized to an RX gate. -void canonicalizeUToRx(QCOProgramBuilder& b); +Value canonicalizeUToRx(QCOProgramBuilder& b); /// Creates a circuit with a U gate that can be canonicalized to an RY gate. -void canonicalizeUToRy(QCOProgramBuilder& b); +Value canonicalizeUToRy(QCOProgramBuilder& b); /// Creates a circuit with a U gate that can be canonicalized to a U2 gate. -void canonicalizeUToU2(QCOProgramBuilder& b); +Value canonicalizeUToU2(QCOProgramBuilder& b); // --- SWAPOp --------------------------------------------------------------- // /// Creates a circuit with just a SWAP gate. -void swap(QCOProgramBuilder& b); +SmallVector swap(QCOProgramBuilder& b); /// Creates a circuit with a single controlled SWAP gate. -void singleControlledSwap(QCOProgramBuilder& b); +SmallVector singleControlledSwap(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled SWAP gate. -void multipleControlledSwap(QCOProgramBuilder& b); +SmallVector multipleControlledSwap(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled SWAP gate. -void nestedControlledSwap(QCOProgramBuilder& b); +SmallVector nestedControlledSwap(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled SWAP gate. -void trivialControlledSwap(QCOProgramBuilder& b); +SmallVector trivialControlledSwap(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a SWAP gate. -void inverseSwap(QCOProgramBuilder& b); +SmallVector inverseSwap(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SWAP /// gate. -void inverseMultipleControlledSwap(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledSwap(QCOProgramBuilder& b); /// Creates a circuit with two SWAP gates in a row. -void twoSwap(QCOProgramBuilder& b); +SmallVector twoSwap(QCOProgramBuilder& b); /// Creates a circuit with two SWAP gates in a row with swapped targets. -void twoSwapSwappedTargets(QCOProgramBuilder& b); +SmallVector twoSwapSwappedTargets(QCOProgramBuilder& b); // --- iSWAPOp -------------------------------------------------------------- // /// Creates a circuit with just an iSWAP gate. -void iswap(QCOProgramBuilder& b); +SmallVector iswap(QCOProgramBuilder& b); /// Creates a circuit with a single controlled iSWAP gate. -void singleControlledIswap(QCOProgramBuilder& b); +SmallVector singleControlledIswap(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled iSWAP gate. -void multipleControlledIswap(QCOProgramBuilder& b); +SmallVector multipleControlledIswap(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled iSWAP gate. -void nestedControlledIswap(QCOProgramBuilder& b); +SmallVector nestedControlledIswap(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled iSWAP gate. -void trivialControlledIswap(QCOProgramBuilder& b); +SmallVector trivialControlledIswap(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an iSWAP gate. -void inverseIswap(QCOProgramBuilder& b); +SmallVector inverseIswap(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled iSWAP /// gate. -void inverseMultipleControlledIswap(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledIswap(QCOProgramBuilder& b); // --- DCXOp ---------------------------------------------------------------- // /// Creates a circuit with just a DCX gate. -void dcx(QCOProgramBuilder& b); +SmallVector dcx(QCOProgramBuilder& b); /// Creates a circuit with a single controlled DCX gate. -void singleControlledDcx(QCOProgramBuilder& b); +SmallVector singleControlledDcx(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled DCX gate. -void multipleControlledDcx(QCOProgramBuilder& b); +SmallVector multipleControlledDcx(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled DCX gate. -void nestedControlledDcx(QCOProgramBuilder& b); +SmallVector nestedControlledDcx(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled DCX gate. -void trivialControlledDcx(QCOProgramBuilder& b); +SmallVector trivialControlledDcx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a DCX gate. -void inverseDcx(QCOProgramBuilder& b); +SmallVector inverseDcx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled DCX gate. -void inverseMultipleControlledDcx(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledDcx(QCOProgramBuilder& b); /// Creates a circuit with two DCX gates in a row with identical targets. -void twoDcx(QCOProgramBuilder& b); +SmallVector twoDcx(QCOProgramBuilder& b); /// Creates a circuit with two DCX gates in a row with swapped targets. -void twoDcxSwappedTargets(QCOProgramBuilder& b); +SmallVector twoDcxSwappedTargets(QCOProgramBuilder& b); // --- ECROp ---------------------------------------------------------------- // /// Creates a circuit with just an ECR gate. -void ecr(QCOProgramBuilder& b); +SmallVector ecr(QCOProgramBuilder& b); /// Creates a circuit with a single controlled ECR gate. -void singleControlledEcr(QCOProgramBuilder& b); +SmallVector singleControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled ECR gate. -void multipleControlledEcr(QCOProgramBuilder& b); +SmallVector multipleControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled ECR gate. -void nestedControlledEcr(QCOProgramBuilder& b); +SmallVector nestedControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled ECR gate. -void trivialControlledEcr(QCOProgramBuilder& b); +SmallVector trivialControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an ECR gate. -void inverseEcr(QCOProgramBuilder& b); +SmallVector inverseEcr(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled ECR gate. -void inverseMultipleControlledEcr(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with two ECR gates in a row. -void twoEcr(QCOProgramBuilder& b); +SmallVector twoEcr(QCOProgramBuilder& b); // --- RXXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RXX gate. -void rxx(QCOProgramBuilder& b); +SmallVector rxx(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RXX gate. -void singleControlledRxx(QCOProgramBuilder& b); +SmallVector singleControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RXX gate. -void multipleControlledRxx(QCOProgramBuilder& b); +SmallVector multipleControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RXX gate. -void nestedControlledRxx(QCOProgramBuilder& b); +SmallVector nestedControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RXX gate. -void trivialControlledRxx(QCOProgramBuilder& b); +SmallVector trivialControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RXX gate. -void inverseRxx(QCOProgramBuilder& b); +SmallVector inverseRxx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RXX gate. -void inverseMultipleControlledRxx(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with a triple-controlled RXX gate. -void tripleControlledRxx(QCOProgramBuilder& b); +SmallVector tripleControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with a four-controlled RXX gate. -void fourControlledRxx(QCOProgramBuilder& b); +SmallVector fourControlledRxx(QCOProgramBuilder& b); /// Creates a circuit with two RXX gates in a row. -void twoRxx(QCOProgramBuilder& b); +SmallVector twoRxx(QCOProgramBuilder& b); /// Creates a circuit with two RXX gates in a row with swapped targets. -void twoRxxSwappedTargets(QCOProgramBuilder& b); +SmallVector twoRxxSwappedTargets(QCOProgramBuilder& b); /// Creates a circuit with two RXX gates in a row with opposite phases. -void twoRxxOppositePhase(QCOProgramBuilder& b); +SmallVector twoRxxOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with two RXX gates in a row with opposite phases and /// swapped targets. -void twoRxxOppositePhaseSwappedTargets(QCOProgramBuilder& b); +SmallVector twoRxxOppositePhaseSwappedTargets(QCOProgramBuilder& b); // --- RYYOp ---------------------------------------------------------------- // /// Creates a circuit with just an RYY gate. -void ryy(QCOProgramBuilder& b); +SmallVector ryy(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RYY gate. -void singleControlledRyy(QCOProgramBuilder& b); +SmallVector singleControlledRyy(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RYY gate. -void multipleControlledRyy(QCOProgramBuilder& b); +SmallVector multipleControlledRyy(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RYY gate. -void nestedControlledRyy(QCOProgramBuilder& b); +SmallVector nestedControlledRyy(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RYY gate. -void trivialControlledRyy(QCOProgramBuilder& b); +SmallVector trivialControlledRyy(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RYY gate. -void inverseRyy(QCOProgramBuilder& b); +SmallVector inverseRyy(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RYY gate. -void inverseMultipleControlledRyy(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRyy(QCOProgramBuilder& b); /// Creates a circuit with two RYY gates in a row. -void twoRyy(QCOProgramBuilder& b); +SmallVector twoRyy(QCOProgramBuilder& b); /// Creates a circuit with two RYY gates in a row with swapped targets. -void twoRyySwappedTargets(QCOProgramBuilder& b); +SmallVector twoRyySwappedTargets(QCOProgramBuilder& b); /// Creates a circuit with two RYY gates in a row with opposite phases. -void twoRyyOppositePhase(QCOProgramBuilder& b); +SmallVector twoRyyOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with two RYY gates in a row with opposite phases and /// swapped targets. -void twoRyyOppositePhaseSwappedTargets(QCOProgramBuilder& b); +SmallVector twoRyyOppositePhaseSwappedTargets(QCOProgramBuilder& b); // --- RZXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZX gate. -void rzx(QCOProgramBuilder& b); +SmallVector rzx(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RZX gate. -void singleControlledRzx(QCOProgramBuilder& b); +SmallVector singleControlledRzx(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RZX gate. -void multipleControlledRzx(QCOProgramBuilder& b); +SmallVector multipleControlledRzx(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RZX gate. -void nestedControlledRzx(QCOProgramBuilder& b); +SmallVector nestedControlledRzx(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RZX gate. -void trivialControlledRzx(QCOProgramBuilder& b); +SmallVector trivialControlledRzx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZX gate. -void inverseRzx(QCOProgramBuilder& b); +SmallVector inverseRzx(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZX gate. -void inverseMultipleControlledRzx(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRzx(QCOProgramBuilder& b); /// Creates a circuit with two RZX gates in a row with opposite phases. -void twoRzxOppositePhase(QCOProgramBuilder& b); +SmallVector twoRzxOppositePhase(QCOProgramBuilder& b); // --- RZZOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZZ gate. -void rzz(QCOProgramBuilder& b); +SmallVector rzz(QCOProgramBuilder& b); /// Creates a circuit with a single controlled RZZ gate. -void singleControlledRzz(QCOProgramBuilder& b); +SmallVector singleControlledRzz(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled RZZ gate. -void multipleControlledRzz(QCOProgramBuilder& b); +SmallVector multipleControlledRzz(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled RZZ gate. -void nestedControlledRzz(QCOProgramBuilder& b); +SmallVector nestedControlledRzz(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled RZZ gate. -void trivialControlledRzz(QCOProgramBuilder& b); +SmallVector trivialControlledRzz(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an RZZ gate. -void inverseRzz(QCOProgramBuilder& b); +SmallVector inverseRzz(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RZZ gate. -void inverseMultipleControlledRzz(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledRzz(QCOProgramBuilder& b); /// Creates a circuit with two RZZ gates in a row. -void twoRzz(QCOProgramBuilder& b); +SmallVector twoRzz(QCOProgramBuilder& b); /// Creates a circuit with two RZZ gates in a row with swapped targets. -void twoRzzSwappedTargets(QCOProgramBuilder& b); +SmallVector twoRzzSwappedTargets(QCOProgramBuilder& b); /// Creates a circuit with two RZZ gates in a row with opposite phases. -void twoRzzOppositePhase(QCOProgramBuilder& b); +SmallVector twoRzzOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with two RZZ gates in a row with opposite phases and /// swapped targets. -void twoRzzOppositePhaseSwappedTargets(QCOProgramBuilder& b); +SmallVector twoRzzOppositePhaseSwappedTargets(QCOProgramBuilder& b); // --- XXPlusYYOp ----------------------------------------------------------- // /// Creates a circuit with just an XXPlusYY gate. -void xxPlusYY(QCOProgramBuilder& b); +SmallVector xxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with a single controlled XXPlusYY gate. -void singleControlledXxPlusYY(QCOProgramBuilder& b); +SmallVector singleControlledXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled XXPlusYY gate. -void multipleControlledXxPlusYY(QCOProgramBuilder& b); +SmallVector multipleControlledXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled XXPlusYY gate. -void nestedControlledXxPlusYY(QCOProgramBuilder& b); +SmallVector nestedControlledXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled XXPlusYY gate. -void trivialControlledXxPlusYY(QCOProgramBuilder& b); +SmallVector trivialControlledXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an XXPlusYY gate. -void inverseXxPlusYY(QCOProgramBuilder& b); +SmallVector inverseXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled XXPlusYY /// gate. -void inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b); /// Creates a circuit with two XXPlusYY gates in a row with opposite phases. -void twoXxPlusYYOppositePhase(QCOProgramBuilder& b); +SmallVector twoXxPlusYYOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with two XXPlusYY gates in a row with swapped targets. -void twoXxPlusYYSwappedTargets(QCOProgramBuilder& b); +SmallVector twoXxPlusYYSwappedTargets(QCOProgramBuilder& b); // --- XXMinusYYOp ---------------------------------------------------------- // /// Creates a circuit with just an XXMinusYY gate. -void xxMinusYY(QCOProgramBuilder& b); +SmallVector xxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with a single controlled XXMinusYY gate. -void singleControlledXxMinusYY(QCOProgramBuilder& b); +SmallVector singleControlledXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with a multi-controlled XXMinusYY gate. -void multipleControlledXxMinusYY(QCOProgramBuilder& b); +SmallVector multipleControlledXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with a nested controlled XXMinusYY gate. -void nestedControlledXxMinusYY(QCOProgramBuilder& b); +SmallVector nestedControlledXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with a trivial controlled XXMinusYY gate. -void trivialControlledXxMinusYY(QCOProgramBuilder& b); +SmallVector trivialControlledXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to an XXMinusYY gate. -void inverseXxMinusYY(QCOProgramBuilder& b); +SmallVector inverseXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled XXMinusYY /// gate. -void inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b); +SmallVector inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b); /// Creates a circuit with two XXMinusYY gates in a row with opposite phases. -void twoXxMinusYYOppositePhase(QCOProgramBuilder& b); +SmallVector twoXxMinusYYOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with two XXMinusYY gates in a row with swapped targets. -void twoXxMinusYYSwappedTargets(QCOProgramBuilder& b); +SmallVector twoXxMinusYYSwappedTargets(QCOProgramBuilder& b); // --- BarrierOp ------------------------------------------------------------ // /// Creates a circuit with a barrier. -void barrier(QCOProgramBuilder& b); +Value barrier(QCOProgramBuilder& b); /// Creates a circuit with a barrier on two qubits. -void barrierTwoQubits(QCOProgramBuilder& b); +SmallVector barrierTwoQubits(QCOProgramBuilder& b); /// Creates a circuit with a barrier on multiple qubits. -void barrierMultipleQubits(QCOProgramBuilder& b); +SmallVector barrierMultipleQubits(QCOProgramBuilder& b); /// Creates a circuit with a single controlled barrier. -void singleControlledBarrier(QCOProgramBuilder& b); +Value singleControlledBarrier(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a barrier. -void inverseBarrier(QCOProgramBuilder& b); +Value inverseBarrier(QCOProgramBuilder& b); /// Creates a circuit with two barriers in a row with overlapping qubits. -void twoBarrier(QCOProgramBuilder& b); +SmallVector twoBarrier(QCOProgramBuilder& b); // --- CtrlOp --------------------------------------------------------------- // /// Creates a circuit with a trivial ctrl modifier. -void trivialCtrl(QCOProgramBuilder& b); +SmallVector trivialCtrl(QCOProgramBuilder& b); /// Creates a circuit with an empty ctrl modifier. -void emptyCtrl(QCOProgramBuilder& b); +SmallVector emptyCtrl(QCOProgramBuilder& b); /// Creates a circuit with nested ctrl modifiers. -void nestedCtrl(QCOProgramBuilder& b); +SmallVector nestedCtrl(QCOProgramBuilder& b); /// Creates a circuit with triple nested ctrl modifiers. -void tripleNestedCtrl(QCOProgramBuilder& b); +SmallVector tripleNestedCtrl(QCOProgramBuilder& b); /// Creates a circuit with double nested ctrl modifiers with two qubits each. -void doubleNestedCtrlTwoQubits(QCOProgramBuilder& b); +SmallVector doubleNestedCtrlTwoQubits(QCOProgramBuilder& b); /// Creates a circuit with control modifiers interleaved by an inverse modifier. -void ctrlInvSandwich(QCOProgramBuilder& b); +SmallVector ctrlInvSandwich(QCOProgramBuilder& b); /// Creates a circuit with a control modifier applied to two gates. -void ctrlTwo(QCOProgramBuilder& b); +SmallVector ctrlTwo(QCOProgramBuilder& b); /// Creates a circuit with a control modifier applied to a controlled and a /// non-controlled gate. -void ctrlTwoMixed(QCOProgramBuilder& b); +SmallVector ctrlTwoMixed(QCOProgramBuilder& b); /// Creates a circuit with nested control modifiers applied to two gates. -void nestedCtrlTwo(QCOProgramBuilder& b); +SmallVector nestedCtrlTwo(QCOProgramBuilder& b); /// Creates a circuit with a control modifier applied to an inverse modifier /// applied to two gates. -void ctrlInvTwo(QCOProgramBuilder& b); +SmallVector ctrlInvTwo(QCOProgramBuilder& b); // --- InvOp ---------------------------------------------------------------- // /// Creates a circuit with an empty inverse modifier. -void emptyInv(QCOProgramBuilder& b); +SmallVector emptyInv(QCOProgramBuilder& b); /// Creates a circuit with nested inverse modifiers. -void nestedInv(QCOProgramBuilder& b); +SmallVector nestedInv(QCOProgramBuilder& b); /// Creates a circuit with triple nested inverse modifiers. -void tripleNestedInv(QCOProgramBuilder& b); +SmallVector tripleNestedInv(QCOProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. -void invCtrlSandwich(QCOProgramBuilder& b); +SmallVector invCtrlSandwich(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to two gates. -void invTwo(QCOProgramBuilder& b); +SmallVector invTwo(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a control modifier /// applied to two gates. -void invCtrlTwo(QCOProgramBuilder& b); +SmallVector invCtrlTwo(QCOProgramBuilder& b); // --- IfOp ---------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. -void simpleIf(QCOProgramBuilder& b); +SmallVector simpleIf(QCOProgramBuilder& b); /// Creates a circuit with an if operation with a parameterized gate. -void ifWithAngle(QCOProgramBuilder& b); +Value ifWithAngle(QCOProgramBuilder& b); /// Creates a circuit with an if operation with two qubits. -void ifTwoQubits(QCOProgramBuilder& b); +SmallVector ifTwoQubits(QCOProgramBuilder& b); /// Creates a circuit with an if operation with an else branch. -void ifElse(QCOProgramBuilder& b); +SmallVector ifElse(QCOProgramBuilder& b); /// Creates a circuit with an if operation with one qubit and one register. -void ifOneQubitOneTensor(QCOProgramBuilder& b); +Value ifOneQubitOneTensor(QCOProgramBuilder& b); /// Creates a circuit with an if operation that uses a constant true as /// condition. -void constantTrueIf(QCOProgramBuilder& b); +Value constantTrueIf(QCOProgramBuilder& b); /// Creates a circuit with an if operation that uses a constant false as /// condition. -void constantFalseIf(QCOProgramBuilder& b); +Value constantFalseIf(QCOProgramBuilder& b); /// Creates a circuit with a nested if operation in the then branch that uses /// the same condition. -void nestedTrueIf(QCOProgramBuilder& b); +SmallVector nestedTrueIf(QCOProgramBuilder& b); /// Creates a circuit with a nested if operation in the else branch that uses /// the same condition. -void nestedFalseIf(QCOProgramBuilder& b); +SmallVector nestedFalseIf(QCOProgramBuilder& b); /// Creates a circuit with an if operation with a nested for operation with /// a register. -void nestedIfOpForLoop(QCOProgramBuilder& b); +Value nestedIfOpForLoop(QCOProgramBuilder& b); /// Creates a circuit with an if operation with a nested for operation and /// parameterized gates. -void nestedIfOpForLoopWithAngle(QCOProgramBuilder& b); +Value nestedIfOpForLoopWithAngle(QCOProgramBuilder& b); // --- WhileOp -------------------------------------------------------------- // /// Creates a circuit with a while operation using a while loop. -void simpleWhileReset(QCOProgramBuilder& b); +Value simpleWhileReset(QCOProgramBuilder& b); /// Creates a circuit with a while operation using a do-while loop. -void simpleDoWhileReset(QCOProgramBuilder& b); +Value simpleDoWhileReset(QCOProgramBuilder& b); // --- ForOp ---------------------------------------------------------------- // /// Creates a circuit with a simple for operation with a register. -void simpleForLoop(QCOProgramBuilder& b); +SmallVector simpleForLoop(QCOProgramBuilder& b); /// Creates a circuit with a for operation with a parameterized gate. -void forLoopWithAngle(QCOProgramBuilder& b); +Value forLoopWithAngle(QCOProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested if operation. -void nestedForLoopIfOp(QCOProgramBuilder& b); +Value nestedForLoopIfOp(QCOProgramBuilder& b); /// Creates a circuit with a for operation with a register and a nested while /// operation. -void nestedForLoopWhileOp(QCOProgramBuilder& b); +SmallVector nestedForLoopWhileOp(QCOProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is separately allocated from the /// register. -void nestedForLoopCtrlOpWithSeparateQubit(QCOProgramBuilder& b); +Value nestedForLoopCtrlOpWithSeparateQubit(QCOProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is extracted from the register. -void nestedForLoopCtrlOpWithExtractedQubit(QCOProgramBuilder& b); +Value nestedForLoopCtrlOpWithExtractedQubit(QCOProgramBuilder& b); // --- QTensor Operations -------------------------------------------------- // /// Allocates a tensor of size `3`. -void qtensorAlloc(QCOProgramBuilder& b); +SmallVector qtensorAlloc(QCOProgramBuilder& b); /// Allocates and explicitly deallocates a tensor. -void qtensorDealloc(QCOProgramBuilder& b); +SmallVector qtensorDealloc(QCOProgramBuilder& b); /// Constructs a tensor with from_elements. -void qtensorFromElements(QCOProgramBuilder& b); +SmallVector qtensorFromElements(QCOProgramBuilder& b); /// Extracts a qubit from a tensor. -void qtensorExtract(QCOProgramBuilder& b); +Value qtensorExtract(QCOProgramBuilder& b); /// Inserts a qubit into a tensor. -void qtensorInsert(QCOProgramBuilder& b); +SmallVector qtensorInsert(QCOProgramBuilder& b); /// Extracts a qubit from a tensor and inserts it immediately at a different /// index. -void qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b); +SmallVector qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b); /// Extracts a qubit from a tensor and inserts it immediately at the same index. -void qtensorExtractInsertSameIndex(QCOProgramBuilder& b); +SmallVector qtensorExtractInsertSameIndex(QCOProgramBuilder& b); /// Inserts a qubit into a tensor and extracts it immediately at a different /// index. -void qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b); +SmallVector qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b); /// Inserts a qubit into a tensor and extracts it immediately at the same index. -void qtensorInsertExtractSameIndex(QCOProgramBuilder& b); +SmallVector qtensorInsertExtractSameIndex(QCOProgramBuilder& b); /// Extracts three qubits with ascending index (0, 1, 2), performs a /// computation, and finally inserts the qubits in ascending order (0, 1, 2). -void qtensorChain(QCOProgramBuilder& b); +SmallVector qtensorChain(QCOProgramBuilder& b); /// Performs the same computation as the `qtensorChain` function, but uses /// qubits immediately after the extract and inserts the qubits in descending /// order (2, 1, 0). -void qtensorAlternativeChain(QCOProgramBuilder& b); +SmallVector qtensorAlternativeChain(QCOProgramBuilder& b); + +SmallVector controlledXH(QCOProgramBuilder& b); + +SmallVector controlledInverseHT(QCOProgramBuilder& b); + +SmallVector inverseTwoRxRy(QCOProgramBuilder& b); + +SmallVector inverseCxThenRz(QCOProgramBuilder& b); + +SmallVector inverseDcxThenRz(QCOProgramBuilder& b); + +Value inverseGphaseBarrierX(QCOProgramBuilder& b); + +Value inverseNestedInvHAndT(QCOProgramBuilder& b); + +SmallVector inverseNestedInvHAndX(QCOProgramBuilder& b); + +SmallVector inverseThreeWireRxRyRz(QCOProgramBuilder& b); + +SmallVector inverseThreeWireNestedTwoInv(QCOProgramBuilder& b); + +SmallVector inverseWithThreeQubitOpInBody(QCOProgramBuilder& b); } // namespace mlir::qco diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 948a74b889..1eb9efae9e 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -12,22 +12,77 @@ #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" +#include + +#include #include namespace mlir::qir { -void emptyQIR([[maybe_unused]] QIRProgramBuilder& builder) {} +/** + * @brief Measures the given qubits, records the outcomes and returns + * a single `i64` exit code with the value 0. + * @param b The QIRProgramBuilder used to perform the measurements and create + * the struct. + * @param qubits The qubits to be measured. + * @param inRegister Whether to store the results in a classical result array or + * not. + * @param startIndex The starting index for measurement outcomes. + * @return The result value. + */ +static Value measureAndRecord(QIRProgramBuilder& b, ValueRange qubits, + bool inRegister, int64_t startIndex = 0) { + + if (qubits.empty()) { + return b.intConstant(0); + } + QIRProgramBuilder::ClassicalRegister resultArray; + if (inRegister) { + resultArray = b.allocClassicalBitRegister( + static_cast(qubits.size()), "meas"); + } -void allocQubit(QIRProgramBuilder& b) { b.allocQubitRegister(1); } + for (auto i = 0L; i < qubits.size(); ++i) { + inRegister ? b.measure(qubits[i], resultArray[i]) + : b.measure(qubits[i], startIndex + i); + } + + return b.intConstant(0); +} -void allocQubitRegister(QIRProgramBuilder& b) { b.allocQubitRegister(2); } +template Value emptyQIR(QIRProgramBuilder& b) { + return measureAndRecord(b, {}, IntoRegister); +} + +template Value allocQubit(QIRProgramBuilder& b) { + auto q = b.allocQubit(); + return measureAndRecord(b, {q}, IntoRegister); +} + +template Value alloc1QubitRegister(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + return measureAndRecord(b, q.qubits, IntoRegister); +} + +template Value allocQubitRegister(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + return measureAndRecord(b, q.qubits, IntoRegister); +} + +template Value alloc3QubitRegister(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + return measureAndRecord(b, q.qubits, IntoRegister); +} -void allocMultipleQubitRegisters(QIRProgramBuilder& b) { - b.allocQubitRegister(2); - b.allocQubitRegister(3); +template +Value allocMultipleQubitRegisters(QIRProgramBuilder& b) { + auto q0 = b.allocQubitRegister(2); + auto q1 = b.allocQubitRegister(3); + return measureAndRecord(b, {q0[0], q0[1], q1[0], q1[1], q1[2]}, IntoRegister); } -void allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b) { +template +Value allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b) { auto q0 = b.allocQubitRegister(2); auto q1 = b.allocQubitRegister(3); b.h(q0[0]); @@ -35,47 +90,57 @@ void allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b) { b.h(q1[0]); b.h(q1[1]); b.h(q1[2]); + return measureAndRecord(b, {q0[0], q0[1], q1[0], q1[1], q1[2]}, IntoRegister); } -void allocLargeRegister(QIRProgramBuilder& b) { b.allocQubitRegister(100); } +template Value allocLargeRegister(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(100); + return measureAndRecord(b, {q[0], q[99]}, IntoRegister); +} -void staticQubits(QIRProgramBuilder& b) { - b.staticQubit(0); - b.staticQubit(1); +Value staticQubits(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + return measureAndRecord(b, {q0, q1}, false); } -void staticQubitsWithOps(QIRProgramBuilder& b) { +Value staticQubitsWithOps(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.h(q0); b.h(q1); + return measureAndRecord(b, {q0, q1}, false); } -void staticQubitsWithParametricOps(QIRProgramBuilder& b) { +Value staticQubitsWithParametricOps(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.rx(std::numbers::pi / 4., q0); b.p(std::numbers::pi / 2., q1); + return measureAndRecord(b, {q0, q1}, false); } -void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b) { +Value staticQubitsWithTwoTargetOps(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.rzz(0.123, q0, q1); + return measureAndRecord(b, {q0, q1}, false); } -void staticQubitsWithCtrl(QIRProgramBuilder& b) { +Value staticQubitsWithCtrl(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.cx(q0, q1); + return measureAndRecord(b, {q0, q1}, false); } -void staticQubitsWithInv(QIRProgramBuilder& b) { +Value staticQubitsWithInv(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); b.tdg(q0); + return measureAndRecord(b, {q0}, false); } -void staticQubitsWithDuplicates(QIRProgramBuilder& b) { +Value staticQubitsWithDuplicates(QIRProgramBuilder& b) { auto q0a = b.staticQubit(0); auto q1a = b.staticQubit(1); auto q0b = b.staticQubit(0); @@ -85,9 +150,10 @@ void staticQubitsWithDuplicates(QIRProgramBuilder& b) { b.rzz(0.123, q0b, q1b); b.cx(q0b, q1b); b.tdg(q0a); + return measureAndRecord(b, {q0b, q1b}, false); } -void staticQubitsCanonical(QIRProgramBuilder& b) { +Value staticQubitsCanonical(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); b.rx(std::numbers::pi / 4., q0); @@ -95,541 +161,679 @@ void staticQubitsCanonical(QIRProgramBuilder& b) { b.rzz(0.123, q0, q1); b.cx(q0, q1); b.tdg(q0); + return measureAndRecord(b, {q0, q1}, false); } -void mixedStaticThenDynamicQubit(QIRProgramBuilder& b) { - b.staticQubit(0); - b.allocQubit(); +Value mixedStaticThenDynamicQubit(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.allocQubit(); + return measureAndRecord(b, {q0, q1}, false); } -void mixedDynamicRegisterThenStaticQubit(QIRProgramBuilder& b) { - b.allocQubitRegister(2); - b.staticQubit(0); +Value mixedDynamicRegisterThenStaticQubit(QIRProgramBuilder& b) { + auto q0 = b.allocQubitRegister(2); + auto q1 = b.staticQubit(0); + return measureAndRecord(b, {q0[0], q0[1], q1}, false); } -void singleMeasurementToSingleBit(QIRProgramBuilder& b) { +template +Value singleMeasurementToSingleBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); + return b.intConstant(0); } -void repeatedMeasurementToSameBit(QIRProgramBuilder& b) { +template +Value repeatedMeasurementToSameBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); b.measure(q[0], c[0]); b.measure(q[0], c[0]); + return b.intConstant(0); } -void repeatedMeasurementToDifferentBits(QIRProgramBuilder& b) { +template +Value repeatedMeasurementToDifferentBits(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(3); b.measure(q[0], c[0]); b.measure(q[0], c[1]); b.measure(q[0], c[2]); + return b.intConstant(0); } -void multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b) { +template +Value multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); const auto& c0 = b.allocClassicalBitRegister(1, "c0"); const auto& c1 = b.allocClassicalBitRegister(2, "c1"); b.measure(q[0], c0[0]); b.measure(q[1], c1[0]); b.measure(q[2], c1[1]); + return b.intConstant(0); } -void measurementWithoutRegisters(QIRProgramBuilder& b) { +template +Value measurementWithoutRegisters(QIRProgramBuilder& b) { auto q = b.allocQubit(); - b.measure(q, 0); + auto bit = b.measure(q, 0); + return b.intConstant(0); } -void resetQubitWithoutOp(QIRProgramBuilder& b) { +template Value resetQubitWithoutOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.reset(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void resetMultipleQubitsWithoutOp(QIRProgramBuilder& b) { +template +Value resetMultipleQubitsWithoutOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.reset(q[0]); b.reset(q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void repeatedResetWithoutOp(QIRProgramBuilder& b) { +template +Value repeatedResetWithoutOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void resetQubitAfterSingleOp(QIRProgramBuilder& b) { +template +Value resetQubitAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); b.reset(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b) { +template +Value resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); b.reset(q[0]); b.h(q[1]); b.reset(q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void repeatedResetAfterSingleOp(QIRProgramBuilder& b) { +template +Value repeatedResetAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void globalPhase(QIRProgramBuilder& b) { b.gphase(0.123); } +template Value globalPhase(QIRProgramBuilder& b) { + b.gphase(0.123); + return measureAndRecord(b, {}, IntoRegister); +} -void identity(QIRProgramBuilder& b) { +template Value identity(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.id(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledIdentity(QIRProgramBuilder& b) { +template +Value singleControlledIdentity(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cid(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledIdentity(QIRProgramBuilder& b) { +template Value twoQubitsOneIdentity(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.id(q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); +} + +template +Value threeQubitsOneIdentity(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.id(q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); +} + +template +Value multipleControlledIdentity(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcid({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void x(QIRProgramBuilder& b) { +template Value x(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.x(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledX(QIRProgramBuilder& b) { +template Value singleControlledX(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cx(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledX(QIRProgramBuilder& b) { +template Value multipleControlledX(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcx({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void y(QIRProgramBuilder& b) { +template Value y(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.y(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledY(QIRProgramBuilder& b) { +template Value singleControlledY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cy(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledY(QIRProgramBuilder& b) { +template Value multipleControlledY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcy({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void z(QIRProgramBuilder& b) { +template Value z(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.z(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledZ(QIRProgramBuilder& b) { +template Value singleControlledZ(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cz(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledZ(QIRProgramBuilder& b) { +template Value multipleControlledZ(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcz({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void h(QIRProgramBuilder& b) { +template Value h(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledH(QIRProgramBuilder& b) { +template Value singleControlledH(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ch(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledH(QIRProgramBuilder& b) { +template Value multipleControlledH(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mch({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void hWithoutRegister(QIRProgramBuilder& b) { +template Value hWithoutRegister(QIRProgramBuilder& b) { auto q = b.allocQubit(); b.h(q); + return measureAndRecord(b, {q}, false); } -void s(QIRProgramBuilder& b) { +template Value s(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.s(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledS(QIRProgramBuilder& b) { +template Value singleControlledS(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cs(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledS(QIRProgramBuilder& b) { +template Value multipleControlledS(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcs({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void sdg(QIRProgramBuilder& b) { +template Value sdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sdg(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledSdg(QIRProgramBuilder& b) { +template Value singleControlledSdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csdg(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledSdg(QIRProgramBuilder& b) { +template Value multipleControlledSdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsdg({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void t_(QIRProgramBuilder& b) { +template +Value t_(QIRProgramBuilder& b) { // NOLINT(*-identifier-naming) auto q = b.allocQubitRegister(1); b.t(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledT(QIRProgramBuilder& b) { +template Value singleControlledT(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ct(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledT(QIRProgramBuilder& b) { +template Value multipleControlledT(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mct({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void tdg(QIRProgramBuilder& b) { +template Value tdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.tdg(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledTdg(QIRProgramBuilder& b) { +template Value singleControlledTdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ctdg(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledTdg(QIRProgramBuilder& b) { +template Value multipleControlledTdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mctdg({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void sx(QIRProgramBuilder& b) { +template Value sx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sx(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledSx(QIRProgramBuilder& b) { +template Value singleControlledSx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csx(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledSx(QIRProgramBuilder& b) { +template Value multipleControlledSx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsx({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void sxdg(QIRProgramBuilder& b) { +template Value sxdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sxdg(q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledSxdg(QIRProgramBuilder& b) { +template Value singleControlledSxdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.csxdg(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledSxdg(QIRProgramBuilder& b) { +template +Value multipleControlledSxdg(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcsxdg({q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void rx(QIRProgramBuilder& b) { +template Value rx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rx(0.123, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRx(QIRProgramBuilder& b) { +template Value singleControlledRx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.crx(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRx(QIRProgramBuilder& b) { +template Value multipleControlledRx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcrx(0.123, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void ry(QIRProgramBuilder& b) { +template Value ry(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.ry(0.456, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRy(QIRProgramBuilder& b) { +template Value singleControlledRy(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cry(0.456, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRy(QIRProgramBuilder& b) { +template Value multipleControlledRy(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcry(0.456, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void rz(QIRProgramBuilder& b) { +template Value rz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rz(0.789, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRz(QIRProgramBuilder& b) { +template Value singleControlledRz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.crz(0.789, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRz(QIRProgramBuilder& b) { +template Value multipleControlledRz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcrz(0.789, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void p(QIRProgramBuilder& b) { +template Value p(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.p(0.123, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledP(QIRProgramBuilder& b) { +template Value singleControlledP(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cp(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledP(QIRProgramBuilder& b) { +template Value multipleControlledP(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcp(0.123, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void r(QIRProgramBuilder& b) { +template Value r(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.r(0.123, 0.456, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledR(QIRProgramBuilder& b) { +template Value singleControlledR(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cr(0.123, 0.456, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledR(QIRProgramBuilder& b) { +template Value multipleControlledR(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcr(0.123, 0.456, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void u2(QIRProgramBuilder& b) { +template Value u2(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.u2(0.234, 0.567, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledU2(QIRProgramBuilder& b) { +template Value singleControlledU2(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cu2(0.234, 0.567, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledU2(QIRProgramBuilder& b) { +template Value multipleControlledU2(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcu2(0.234, 0.567, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void u(QIRProgramBuilder& b) { +template Value u(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.u(0.1, 0.2, 0.3, q[0]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledU(QIRProgramBuilder& b) { +template Value singleControlledU(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.cu(0.1, 0.2, 0.3, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledU(QIRProgramBuilder& b) { +template Value multipleControlledU(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.mcu(0.1, 0.2, 0.3, {q[0], q[1]}, q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void swap(QIRProgramBuilder& b) { +template Value swap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.swap(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledSwap(QIRProgramBuilder& b) { +template Value singleControlledSwap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cswap(q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledSwap(QIRProgramBuilder& b) { +template +Value multipleControlledSwap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcswap({q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void iswap(QIRProgramBuilder& b) { +template Value iswap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.iswap(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledIswap(QIRProgramBuilder& b) { +template Value singleControlledIswap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.ciswap(q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledIswap(QIRProgramBuilder& b) { +template +Value multipleControlledIswap(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mciswap({q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void dcx(QIRProgramBuilder& b) { +template Value dcx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.dcx(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledDcx(QIRProgramBuilder& b) { +template Value singleControlledDcx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cdcx(q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledDcx(QIRProgramBuilder& b) { +template Value multipleControlledDcx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcdcx({q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void ecr(QIRProgramBuilder& b) { +template Value ecr(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ecr(q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledEcr(QIRProgramBuilder& b) { +template Value singleControlledEcr(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cecr(q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledEcr(QIRProgramBuilder& b) { +template Value multipleControlledEcr(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcecr({q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void rxx(QIRProgramBuilder& b) { +template Value rxx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRxx(QIRProgramBuilder& b) { +template Value singleControlledRxx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crxx(0.123, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRxx(QIRProgramBuilder& b) { +template Value multipleControlledRxx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void tripleControlledRxx(QIRProgramBuilder& b) { +template Value tripleControlledRxx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(5); b.mcrxx(0.123, {q[0], q[1], q[2]}, q[3], q[4]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void ryy(QIRProgramBuilder& b) { +template Value ryy(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ryy(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRyy(QIRProgramBuilder& b) { +template Value singleControlledRyy(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cryy(0.123, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRyy(QIRProgramBuilder& b) { +template Value multipleControlledRyy(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcryy(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void rzx(QIRProgramBuilder& b) { +template Value rzx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rzx(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRzx(QIRProgramBuilder& b) { +template Value singleControlledRzx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crzx(0.123, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRzx(QIRProgramBuilder& b) { +template Value multipleControlledRzx(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrzx(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void rzz(QIRProgramBuilder& b) { +template Value rzz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rzz(0.123, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledRzz(QIRProgramBuilder& b) { +template Value singleControlledRzz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.crzz(0.123, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledRzz(QIRProgramBuilder& b) { +template Value multipleControlledRzz(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcrzz(0.123, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void xxPlusYY(QIRProgramBuilder& b) { +template Value xxPlusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.xx_plus_yy(0.123, 0.456, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledXxPlusYY(QIRProgramBuilder& b) { +template +Value singleControlledXxPlusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cxx_plus_yy(0.123, 0.456, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledXxPlusYY(QIRProgramBuilder& b) { +template +Value multipleControlledXxPlusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcxx_plus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void xxMinusYY(QIRProgramBuilder& b) { +template Value xxMinusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.xx_minus_yy(0.123, 0.456, q[0], q[1]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void singleControlledXxMinusYY(QIRProgramBuilder& b) { +template +Value singleControlledXxMinusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.cxx_minus_yy(0.123, 0.456, q[0], q[1], q[2]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void multipleControlledXxMinusYY(QIRProgramBuilder& b) { +template +Value multipleControlledXxMinusYY(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcxx_minus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); + return measureAndRecord(b, q.qubits, IntoRegister); } -void simpleIf(QIRProgramBuilder& b) { +template Value simpleIf(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); auto cond = b.measure(q[0], 0); b.scfIf(cond, [&] { b.x(q[0]); }); + return measureAndRecord(b, q.qubits, IntoRegister, 1); } -void ifElse(QIRProgramBuilder& b) { +template Value ifElse(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); auto cond = b.measure(q[0], 0); b.scfIf(cond, [&] { b.x(q[0]); }, [&] { b.z(q[0]); }); + return measureAndRecord(b, q.qubits, IntoRegister, 1); } -void ifTwoQubits(QIRProgramBuilder& b) { +template Value ifTwoQubits(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); auto cond = b.measure(q[0], 0); @@ -637,13 +841,14 @@ void ifTwoQubits(QIRProgramBuilder& b) { b.x(q[0]); b.x(q[1]); }); + return measureAndRecord(b, q.qubits, IntoRegister, 1); } -void nestedIfOpForLoop(QIRProgramBuilder& b) { +template Value nestedIfOpForLoop(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto q0 = b.allocQubit(); b.h(q0); - auto cond = b.measure(q0, 0); + auto cond = b.measure(q0, 0, false); b.scfIf( cond, [&] { b.h(q0); }, [&] { @@ -652,50 +857,55 @@ void nestedIfOpForLoop(QIRProgramBuilder& b) { b.h(q1); }); }); + return measureAndRecord(b, {q0}, IntoRegister, 1); } -void simpleWhileReset(QIRProgramBuilder& b) { +template Value simpleWhileReset(QIRProgramBuilder& b) { auto q = b.allocQubit(); b.h(q); b.scfWhile( [&] { - auto measureResult = b.measure(q, 0); + auto measureResult = b.measure(q, 0, false); return measureResult; }, [&] { b.h(q); }); + return measureAndRecord(b, {q}, IntoRegister, 1); } -void simpleDoWhileReset(QIRProgramBuilder& b) { +template Value simpleDoWhileReset(QIRProgramBuilder& b) { auto q = b.allocQubit(); b.scfWhile([&] { b.h(q); - auto measureResult = b.measure(q, 0); + auto measureResult = b.measure(q, 0, false); return measureResult; }); + return measureAndRecord(b, {q}, IntoRegister, 1); } -void simpleForLoop(QIRProgramBuilder& b) { +template Value simpleForLoop(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.scfFor(0, 2, 1, [&](Value iv) { auto q = b.load(reg.value, iv); b.h(q); }); + return measureAndRecord(b, reg.qubits, IntoRegister); }; -void nestedForLoopIfOp(QIRProgramBuilder& b) { +template Value nestedForLoopIfOp(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); auto qCond = b.allocQubit(); b.scfFor(0, 2, 1, [&](Value iv) { b.h(qCond); - auto cond = b.measure(qCond, 0); + auto cond = b.measure(qCond, 0, false); b.scfIf(cond, [&] { auto q = b.load(reg.value, iv); b.h(q); }); }); + return measureAndRecord(b, {qCond}, IntoRegister, 1); } -void nestedForLoopWhileOp(QIRProgramBuilder& b) { +template Value nestedForLoopWhileOp(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.scfFor(0, 2, 1, [&](Value iv) { auto q = b.load(reg.value, iv); @@ -705,14 +915,16 @@ void nestedForLoopWhileOp(QIRProgramBuilder& b) { auto q = b.load(reg.value, iv); b.scfWhile( [&] { - auto measureResult = b.measure(q, 0); + auto measureResult = b.measure(q, 0, false); return measureResult; }, [&] { b.h(q); }); }); + return measureAndRecord(b, reg.qubits, IntoRegister, 1); } -void nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b) { +template +Value nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto control = b.allocQubit(); b.h(control); @@ -721,9 +933,11 @@ void nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b) { b.h(q0); b.cx(control, q0); }); + return measureAndRecord(b, {control}, IntoRegister); } -void nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b) { +template +Value nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(4); b.h(reg[0]); b.scfFor(1, 4, 1, [&](Value iv) { @@ -731,12 +945,262 @@ void nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b) { b.h(q0); b.cx(reg[0], q0); }); + return measureAndRecord(b, {reg[0]}, IntoRegister); } -void ctrlTwo(QIRProgramBuilder& b) { +template Value ctrlTwo(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.mcx({q[0], q[1]}, q[2]); b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); -} - + return measureAndRecord(b, q.qubits, IntoRegister); +} + +// Instantiate the templates for IntoRegister = false +template Value emptyQIR(QIRProgramBuilder& builder); +template Value allocQubit(QIRProgramBuilder& b); +template Value alloc1QubitRegister(QIRProgramBuilder& b); +template Value allocQubitRegister(QIRProgramBuilder& b); +template Value alloc3QubitRegister(QIRProgramBuilder& b); +template Value allocMultipleQubitRegisters(QIRProgramBuilder& b); +template Value allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b); +template Value allocLargeRegister(QIRProgramBuilder& b); +template Value singleMeasurementToSingleBit(QIRProgramBuilder& b); +template Value repeatedMeasurementToSameBit(QIRProgramBuilder& b); +template Value repeatedMeasurementToDifferentBits(QIRProgramBuilder& b); +template Value +multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b); +template Value measurementWithoutRegisters(QIRProgramBuilder& b); +template Value resetQubitWithoutOp(QIRProgramBuilder& b); +template Value resetMultipleQubitsWithoutOp(QIRProgramBuilder& b); +template Value repeatedResetWithoutOp(QIRProgramBuilder& b); +template Value resetQubitAfterSingleOp(QIRProgramBuilder& b); +template Value resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b); +template Value repeatedResetAfterSingleOp(QIRProgramBuilder& b); +template Value globalPhase(QIRProgramBuilder& b); +template Value identity(QIRProgramBuilder& b); +template Value singleControlledIdentity(QIRProgramBuilder& b); +template Value twoQubitsOneIdentity(QIRProgramBuilder& b); +template Value threeQubitsOneIdentity(QIRProgramBuilder& b); +template Value multipleControlledIdentity(QIRProgramBuilder& b); +template Value x(QIRProgramBuilder& b); +template Value singleControlledX(QIRProgramBuilder& b); +template Value multipleControlledX(QIRProgramBuilder& b); +template Value y(QIRProgramBuilder& b); +template Value singleControlledY(QIRProgramBuilder& b); +template Value multipleControlledY(QIRProgramBuilder& b); +template Value z(QIRProgramBuilder& b); +template Value singleControlledZ(QIRProgramBuilder& b); +template Value multipleControlledZ(QIRProgramBuilder& b); +template Value h(QIRProgramBuilder& b); +template Value singleControlledH(QIRProgramBuilder& b); +template Value multipleControlledH(QIRProgramBuilder& b); +template Value hWithoutRegister(QIRProgramBuilder& b); +template Value s(QIRProgramBuilder& b); +template Value singleControlledS(QIRProgramBuilder& b); +template Value multipleControlledS(QIRProgramBuilder& b); +template Value sdg(QIRProgramBuilder& b); +template Value singleControlledSdg(QIRProgramBuilder& b); +template Value multipleControlledSdg(QIRProgramBuilder& b); +template Value t_(QIRProgramBuilder& b); +template Value singleControlledT(QIRProgramBuilder& b); +template Value multipleControlledT(QIRProgramBuilder& b); +template Value tdg(QIRProgramBuilder& b); +template Value singleControlledTdg(QIRProgramBuilder& b); +template Value multipleControlledTdg(QIRProgramBuilder& b); +template Value sx(QIRProgramBuilder& b); +template Value singleControlledSx(QIRProgramBuilder& b); +template Value multipleControlledSx(QIRProgramBuilder& b); +template Value sxdg(QIRProgramBuilder& b); +template Value singleControlledSxdg(QIRProgramBuilder& b); +template Value multipleControlledSxdg(QIRProgramBuilder& b); +template Value rx(QIRProgramBuilder& b); +template Value singleControlledRx(QIRProgramBuilder& b); +template Value multipleControlledRx(QIRProgramBuilder& b); +template Value ry(QIRProgramBuilder& b); +template Value singleControlledRy(QIRProgramBuilder& b); +template Value multipleControlledRy(QIRProgramBuilder& b); +template Value rz(QIRProgramBuilder& b); +template Value singleControlledRz(QIRProgramBuilder& b); +template Value multipleControlledRz(QIRProgramBuilder& b); +template Value p(QIRProgramBuilder& b); +template Value singleControlledP(QIRProgramBuilder& b); +template Value multipleControlledP(QIRProgramBuilder& b); +template Value r(QIRProgramBuilder& b); +template Value singleControlledR(QIRProgramBuilder& b); +template Value multipleControlledR(QIRProgramBuilder& b); +template Value u2(QIRProgramBuilder& b); +template Value singleControlledU2(QIRProgramBuilder& b); +template Value multipleControlledU2(QIRProgramBuilder& b); +template Value u(QIRProgramBuilder& b); +template Value singleControlledU(QIRProgramBuilder& b); +template Value multipleControlledU(QIRProgramBuilder& b); +template Value swap(QIRProgramBuilder& b); +template Value singleControlledSwap(QIRProgramBuilder& b); +template Value multipleControlledSwap(QIRProgramBuilder& b); +template Value iswap(QIRProgramBuilder& b); +template Value singleControlledIswap(QIRProgramBuilder& b); +template Value multipleControlledIswap(QIRProgramBuilder& b); +template Value dcx(QIRProgramBuilder& b); +template Value singleControlledDcx(QIRProgramBuilder& b); +template Value multipleControlledDcx(QIRProgramBuilder& b); +template Value ecr(QIRProgramBuilder& b); +template Value singleControlledEcr(QIRProgramBuilder& b); +template Value multipleControlledEcr(QIRProgramBuilder& b); +template Value rxx(QIRProgramBuilder& b); +template Value singleControlledRxx(QIRProgramBuilder& b); +template Value multipleControlledRxx(QIRProgramBuilder& b); +template Value tripleControlledRxx(QIRProgramBuilder& b); +template Value ryy(QIRProgramBuilder& b); +template Value singleControlledRyy(QIRProgramBuilder& b); +template Value multipleControlledRyy(QIRProgramBuilder& b); +template Value rzx(QIRProgramBuilder& b); +template Value singleControlledRzx(QIRProgramBuilder& b); +template Value multipleControlledRzx(QIRProgramBuilder& b); +template Value rzz(QIRProgramBuilder& b); +template Value singleControlledRzz(QIRProgramBuilder& b); +template Value multipleControlledRzz(QIRProgramBuilder& b); +template Value xxPlusYY(QIRProgramBuilder& b); +template Value singleControlledXxPlusYY(QIRProgramBuilder& b); +template Value multipleControlledXxPlusYY(QIRProgramBuilder& b); +template Value xxMinusYY(QIRProgramBuilder& b); +template Value singleControlledXxMinusYY(QIRProgramBuilder& b); +template Value multipleControlledXxMinusYY(QIRProgramBuilder& b); +template Value simpleIf(QIRProgramBuilder& b); +template Value ifElse(QIRProgramBuilder& b); +template Value ifTwoQubits(QIRProgramBuilder& b); +template Value nestedIfOpForLoop(QIRProgramBuilder& b); +template Value simpleWhileReset(QIRProgramBuilder& b); +template Value simpleDoWhileReset(QIRProgramBuilder& b); +template Value simpleForLoop(QIRProgramBuilder& b); +template Value nestedForLoopIfOp(QIRProgramBuilder& b); +template Value nestedForLoopWhileOp(QIRProgramBuilder& b); +template Value +nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b); +template Value +nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b); +template Value ctrlTwo(QIRProgramBuilder& b); + +// Instantiate the templates for IntoRegister = true +template Value emptyQIR(QIRProgramBuilder& builder); +template Value allocQubit(QIRProgramBuilder& b); +template Value alloc1QubitRegister(QIRProgramBuilder& b); +template Value allocQubitRegister(QIRProgramBuilder& b); +template Value alloc3QubitRegister(QIRProgramBuilder& b); +template Value allocMultipleQubitRegisters(QIRProgramBuilder& b); +template Value allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b); +template Value allocLargeRegister(QIRProgramBuilder& b); +template Value singleMeasurementToSingleBit(QIRProgramBuilder& b); +template Value repeatedMeasurementToSameBit(QIRProgramBuilder& b); +template Value repeatedMeasurementToDifferentBits(QIRProgramBuilder& b); +template Value +multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b); +template Value measurementWithoutRegisters(QIRProgramBuilder& b); +template Value resetQubitWithoutOp(QIRProgramBuilder& b); +template Value resetMultipleQubitsWithoutOp(QIRProgramBuilder& b); +template Value repeatedResetWithoutOp(QIRProgramBuilder& b); +template Value resetQubitAfterSingleOp(QIRProgramBuilder& b); +template Value resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b); +template Value repeatedResetAfterSingleOp(QIRProgramBuilder& b); +template Value globalPhase(QIRProgramBuilder& b); +template Value identity(QIRProgramBuilder& b); +template Value singleControlledIdentity(QIRProgramBuilder& b); +template Value twoQubitsOneIdentity(QIRProgramBuilder& b); +template Value threeQubitsOneIdentity(QIRProgramBuilder& b); +template Value multipleControlledIdentity(QIRProgramBuilder& b); +template Value x(QIRProgramBuilder& b); +template Value singleControlledX(QIRProgramBuilder& b); +template Value multipleControlledX(QIRProgramBuilder& b); +template Value y(QIRProgramBuilder& b); +template Value singleControlledY(QIRProgramBuilder& b); +template Value multipleControlledY(QIRProgramBuilder& b); +template Value z(QIRProgramBuilder& b); +template Value singleControlledZ(QIRProgramBuilder& b); +template Value multipleControlledZ(QIRProgramBuilder& b); +template Value h(QIRProgramBuilder& b); +template Value singleControlledH(QIRProgramBuilder& b); +template Value multipleControlledH(QIRProgramBuilder& b); +template Value hWithoutRegister(QIRProgramBuilder& b); +template Value s(QIRProgramBuilder& b); +template Value singleControlledS(QIRProgramBuilder& b); +template Value multipleControlledS(QIRProgramBuilder& b); +template Value sdg(QIRProgramBuilder& b); +template Value singleControlledSdg(QIRProgramBuilder& b); +template Value multipleControlledSdg(QIRProgramBuilder& b); +template Value t_(QIRProgramBuilder& b); +template Value singleControlledT(QIRProgramBuilder& b); +template Value multipleControlledT(QIRProgramBuilder& b); +template Value tdg(QIRProgramBuilder& b); +template Value singleControlledTdg(QIRProgramBuilder& b); +template Value multipleControlledTdg(QIRProgramBuilder& b); +template Value sx(QIRProgramBuilder& b); +template Value singleControlledSx(QIRProgramBuilder& b); +template Value multipleControlledSx(QIRProgramBuilder& b); +template Value sxdg(QIRProgramBuilder& b); +template Value singleControlledSxdg(QIRProgramBuilder& b); +template Value multipleControlledSxdg(QIRProgramBuilder& b); +template Value rx(QIRProgramBuilder& b); +template Value singleControlledRx(QIRProgramBuilder& b); +template Value multipleControlledRx(QIRProgramBuilder& b); +template Value ry(QIRProgramBuilder& b); +template Value singleControlledRy(QIRProgramBuilder& b); +template Value multipleControlledRy(QIRProgramBuilder& b); +template Value rz(QIRProgramBuilder& b); +template Value singleControlledRz(QIRProgramBuilder& b); +template Value multipleControlledRz(QIRProgramBuilder& b); +template Value p(QIRProgramBuilder& b); +template Value singleControlledP(QIRProgramBuilder& b); +template Value multipleControlledP(QIRProgramBuilder& b); +template Value r(QIRProgramBuilder& b); +template Value singleControlledR(QIRProgramBuilder& b); +template Value multipleControlledR(QIRProgramBuilder& b); +template Value u2(QIRProgramBuilder& b); +template Value singleControlledU2(QIRProgramBuilder& b); +template Value multipleControlledU2(QIRProgramBuilder& b); +template Value u(QIRProgramBuilder& b); +template Value singleControlledU(QIRProgramBuilder& b); +template Value multipleControlledU(QIRProgramBuilder& b); +template Value swap(QIRProgramBuilder& b); +template Value singleControlledSwap(QIRProgramBuilder& b); +template Value multipleControlledSwap(QIRProgramBuilder& b); +template Value iswap(QIRProgramBuilder& b); +template Value singleControlledIswap(QIRProgramBuilder& b); +template Value multipleControlledIswap(QIRProgramBuilder& b); +template Value dcx(QIRProgramBuilder& b); +template Value singleControlledDcx(QIRProgramBuilder& b); +template Value multipleControlledDcx(QIRProgramBuilder& b); +template Value ecr(QIRProgramBuilder& b); +template Value singleControlledEcr(QIRProgramBuilder& b); +template Value multipleControlledEcr(QIRProgramBuilder& b); +template Value rxx(QIRProgramBuilder& b); +template Value singleControlledRxx(QIRProgramBuilder& b); +template Value multipleControlledRxx(QIRProgramBuilder& b); +template Value tripleControlledRxx(QIRProgramBuilder& b); +template Value ryy(QIRProgramBuilder& b); +template Value singleControlledRyy(QIRProgramBuilder& b); +template Value multipleControlledRyy(QIRProgramBuilder& b); +template Value rzx(QIRProgramBuilder& b); +template Value singleControlledRzx(QIRProgramBuilder& b); +template Value multipleControlledRzx(QIRProgramBuilder& b); +template Value rzz(QIRProgramBuilder& b); +template Value singleControlledRzz(QIRProgramBuilder& b); +template Value multipleControlledRzz(QIRProgramBuilder& b); +template Value xxPlusYY(QIRProgramBuilder& b); +template Value singleControlledXxPlusYY(QIRProgramBuilder& b); +template Value multipleControlledXxPlusYY(QIRProgramBuilder& b); +template Value xxMinusYY(QIRProgramBuilder& b); +template Value singleControlledXxMinusYY(QIRProgramBuilder& b); +template Value multipleControlledXxMinusYY(QIRProgramBuilder& b); +template Value simpleIf(QIRProgramBuilder& b); +template Value ifElse(QIRProgramBuilder& b); +template Value ifTwoQubits(QIRProgramBuilder& b); +template Value nestedIfOpForLoop(QIRProgramBuilder& b); +template Value simpleWhileReset(QIRProgramBuilder& b); +template Value simpleDoWhileReset(QIRProgramBuilder& b); +template Value simpleForLoop(QIRProgramBuilder& b); +template Value nestedForLoopIfOp(QIRProgramBuilder& b); +template Value nestedForLoopWhileOp(QIRProgramBuilder& b); +template Value nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b); +template Value +nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b); +template Value ctrlTwo(QIRProgramBuilder& b); } // namespace mlir::qir diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index afa3daaef6..b58a3ff396 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -10,468 +10,567 @@ #pragma once +#include + namespace mlir::qir { class QIRProgramBuilder; /// Creates an empty QIR program. -void emptyQIR(QIRProgramBuilder& builder); +template Value emptyQIR(QIRProgramBuilder& builder); // --- Qubit Management ----------------------------------------------------- // /// Allocates a single qubit. -void allocQubit(QIRProgramBuilder& b); +template Value allocQubit(QIRProgramBuilder& b); + +/// Allocates a qubit register of size `1`. +template +Value alloc1QubitRegister(QIRProgramBuilder& b); /// Allocates a qubit register of size `2`. -void allocQubitRegister(QIRProgramBuilder& b); +template +Value allocQubitRegister(QIRProgramBuilder& b); + +/// Allocates a qubit register of size `3`. +template +Value alloc3QubitRegister(QIRProgramBuilder& b); /// Allocates two qubit registers of size `2` and `3`. -void allocMultipleQubitRegisters(QIRProgramBuilder& b); +template +Value allocMultipleQubitRegisters(QIRProgramBuilder& b); /// Allocates two qubit registers of size `2` and `3` and applies operations. -void allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b); +template +Value allocMultipleQubitRegistersWithOps(QIRProgramBuilder& b); /// Allocates a large qubit register. -void allocLargeRegister(QIRProgramBuilder& b); +template +Value allocLargeRegister(QIRProgramBuilder& b); /// Allocates two inline qubits. -void staticQubits(QIRProgramBuilder& b); +Value staticQubits(QIRProgramBuilder& b); /// Allocates two static qubits and applies operations. -void staticQubitsWithOps(QIRProgramBuilder& b); +Value staticQubitsWithOps(QIRProgramBuilder& b); /// Allocates two static qubits and applies parametric gates. -void staticQubitsWithParametricOps(QIRProgramBuilder& b); +Value staticQubitsWithParametricOps(QIRProgramBuilder& b); /// Allocates two static qubits and applies a two-target gate. -void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b); +Value staticQubitsWithTwoTargetOps(QIRProgramBuilder& b); /// Allocates two static qubits and applies a controlled gate. -void staticQubitsWithCtrl(QIRProgramBuilder& b); +Value staticQubitsWithCtrl(QIRProgramBuilder& b); /// Allocates a static qubit and applies the inverse of a T gate (Tdg). -void staticQubitsWithInv(QIRProgramBuilder& b); +Value staticQubitsWithInv(QIRProgramBuilder& b); /// Allocates duplicate static qubits and applies operations on both. -void staticQubitsWithDuplicates(QIRProgramBuilder& b); +Value staticQubitsWithDuplicates(QIRProgramBuilder& b); /// Same as `staticQubitsWithDuplicates`, but with canonical static qubit /// retrievals. -void staticQubitsCanonical(QIRProgramBuilder& b); +Value staticQubitsCanonical(QIRProgramBuilder& b); // --- Invalid / mixed addressing (unit tests) -------------------------------- /// @pre `builder.initialize()`. Fatal mixed addressing: static then dynamic /// alloc. -void mixedStaticThenDynamicQubit(QIRProgramBuilder& b); +Value mixedStaticThenDynamicQubit(QIRProgramBuilder& b); /// @pre `builder.initialize()`. Fatal mixed addressing: dynamic register then /// static. -void mixedDynamicRegisterThenStaticQubit(QIRProgramBuilder& b); +Value mixedDynamicRegisterThenStaticQubit(QIRProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. -void singleMeasurementToSingleBit(QIRProgramBuilder& b); +template +Value singleMeasurementToSingleBit(QIRProgramBuilder& b); /// Repeatedly measures a single qubit into the same classical bit. -void repeatedMeasurementToSameBit(QIRProgramBuilder& b); +template +Value repeatedMeasurementToSameBit(QIRProgramBuilder& b); /// Repeatedly measures a single qubit into different classical bits. -void repeatedMeasurementToDifferentBits(QIRProgramBuilder& b); +template +Value repeatedMeasurementToDifferentBits(QIRProgramBuilder& b); /// Measures multiple qubits into multiple classical bits. -void multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b); +template +Value multipleClassicalRegistersAndMeasurements(QIRProgramBuilder& b); /// Measures a single qubit into a single classical bit, without explicitly /// allocating a quantum or classical register. -void measurementWithoutRegisters(QIRProgramBuilder& b); +template +Value measurementWithoutRegisters(QIRProgramBuilder& b); // --- ResetOp -------------------------------------------------------------- // /// Resets a single qubit without any operations being applied. -void resetQubitWithoutOp(QIRProgramBuilder& b); +template +Value resetQubitWithoutOp(QIRProgramBuilder& b); /// Resets multiple qubits without any operations being applied. -void resetMultipleQubitsWithoutOp(QIRProgramBuilder& b); +template +Value resetMultipleQubitsWithoutOp(QIRProgramBuilder& b); /// Repeatedly resets a single qubit without any operations being applied. -void repeatedResetWithoutOp(QIRProgramBuilder& b); +template +Value repeatedResetWithoutOp(QIRProgramBuilder& b); /// Resets a single qubit after a single operation. -void resetQubitAfterSingleOp(QIRProgramBuilder& b); +template +Value resetQubitAfterSingleOp(QIRProgramBuilder& b); /// Resets multiple qubits after a single operation. -void resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b); +template +Value resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b); /// Repeatedly resets a single qubit after a single operation. -void repeatedResetAfterSingleOp(QIRProgramBuilder& b); +template +Value repeatedResetAfterSingleOp(QIRProgramBuilder& b); // --- GPhaseOp ------------------------------------------------------------- // /// Creates a circuit with just a global phase. -void globalPhase(QIRProgramBuilder& b); +template Value globalPhase(QIRProgramBuilder& b); // --- IdOp ----------------------------------------------------------------- // /// Creates a circuit with just an identity gate. -void identity(QIRProgramBuilder& b); +template Value identity(QIRProgramBuilder& b); /// Creates a controlled identity gate with a single control qubit. -void singleControlledIdentity(QIRProgramBuilder& b); +template +Value singleControlledIdentity(QIRProgramBuilder& b); + +/// Creates an identity gate on a single qubit in a two-qubit register. +template +Value twoQubitsOneIdentity(QIRProgramBuilder& b); + +/// Creates an identity gate on a single qubit in a three-qubit register. +template +Value threeQubitsOneIdentity(QIRProgramBuilder& b); /// Creates a multi-controlled identity gate with multiple control qubits. -void multipleControlledIdentity(QIRProgramBuilder& b); +template +Value multipleControlledIdentity(QIRProgramBuilder& b); // --- XOp ------------------------------------------------------------------ // /// Creates a circuit with just an X gate. -void x(QIRProgramBuilder& b); +template Value x(QIRProgramBuilder& b); /// Creates a circuit with a single controlled X gate. -void singleControlledX(QIRProgramBuilder& b); +template +Value singleControlledX(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled X gate. -void multipleControlledX(QIRProgramBuilder& b); +template +Value multipleControlledX(QIRProgramBuilder& b); // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. -void y(QIRProgramBuilder& b); +template Value y(QIRProgramBuilder& b); /// Creates a circuit with a single controlled Y gate. -void singleControlledY(QIRProgramBuilder& b); +template +Value singleControlledY(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled Y gate. -void multipleControlledY(QIRProgramBuilder& b); +template +Value multipleControlledY(QIRProgramBuilder& b); // --- ZOp ------------------------------------------------------------------ // /// Creates a circuit with just a Z gate. -void z(QIRProgramBuilder& b); +template Value z(QIRProgramBuilder& b); /// Creates a circuit with a single controlled Z gate. -void singleControlledZ(QIRProgramBuilder& b); +template +Value singleControlledZ(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled Z gate. -void multipleControlledZ(QIRProgramBuilder& b); +template +Value multipleControlledZ(QIRProgramBuilder& b); // --- HOp ------------------------------------------------------------------ // /// Creates a circuit with just an H gate. -void h(QIRProgramBuilder& b); +template Value h(QIRProgramBuilder& b); /// Creates a circuit with a single controlled H gate. -void singleControlledH(QIRProgramBuilder& b); +template +Value singleControlledH(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled H gate. -void multipleControlledH(QIRProgramBuilder& b); +template +Value multipleControlledH(QIRProgramBuilder& b); /// Creates a circuit with just an H gate and no qubit register. -void hWithoutRegister(QIRProgramBuilder& b); +template +Value hWithoutRegister(QIRProgramBuilder& b); // --- SOp ------------------------------------------------------------------ // /// Creates a circuit with just an S gate. -void s(QIRProgramBuilder& b); +template Value s(QIRProgramBuilder& b); /// Creates a circuit with a single controlled S gate. -void singleControlledS(QIRProgramBuilder& b); +template +Value singleControlledS(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled S gate. -void multipleControlledS(QIRProgramBuilder& b); +template +Value multipleControlledS(QIRProgramBuilder& b); // --- SdgOp ---------------------------------------------------------------- // /// Creates a circuit with just an Sdg gate. -void sdg(QIRProgramBuilder& b); +template Value sdg(QIRProgramBuilder& b); /// Creates a circuit with a single controlled Sdg gate. -void singleControlledSdg(QIRProgramBuilder& b); +template +Value singleControlledSdg(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled Sdg gate. -void multipleControlledSdg(QIRProgramBuilder& b); +template +Value multipleControlledSdg(QIRProgramBuilder& b); // --- TOp ------------------------------------------------------------------ // /// Creates a circuit with just a T gate. -void t_(QIRProgramBuilder& b); // NOLINT(*-identifier-naming) +template +Value t_(QIRProgramBuilder& b); // NOLINT(*-identifier-naming) /// Creates a circuit with a single controlled T gate. -void singleControlledT(QIRProgramBuilder& b); +template +Value singleControlledT(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled T gate. -void multipleControlledT(QIRProgramBuilder& b); +template +Value multipleControlledT(QIRProgramBuilder& b); // --- TdgOp ---------------------------------------------------------------- // /// Creates a circuit with just a Tdg gate. -void tdg(QIRProgramBuilder& b); +template Value tdg(QIRProgramBuilder& b); /// Creates a circuit with a single controlled Tdg gate. -void singleControlledTdg(QIRProgramBuilder& b); +template +Value singleControlledTdg(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled Tdg gate. -void multipleControlledTdg(QIRProgramBuilder& b); +template +Value multipleControlledTdg(QIRProgramBuilder& b); // --- SXOp ----------------------------------------------------------------- // /// Creates a circuit with just an SX gate. -void sx(QIRProgramBuilder& b); +template Value sx(QIRProgramBuilder& b); /// Creates a circuit with a single controlled SX gate. -void singleControlledSx(QIRProgramBuilder& b); +template +Value singleControlledSx(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled SX gate. -void multipleControlledSx(QIRProgramBuilder& b); +template +Value multipleControlledSx(QIRProgramBuilder& b); // --- SXdgOp --------------------------------------------------------------- // /// Creates a circuit with just an SXdg gate. -void sxdg(QIRProgramBuilder& b); +template Value sxdg(QIRProgramBuilder& b); /// Creates a circuit with a single controlled SXdg gate. -void singleControlledSxdg(QIRProgramBuilder& b); +template +Value singleControlledSxdg(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled SXdg gate. -void multipleControlledSxdg(QIRProgramBuilder& b); +template +Value multipleControlledSxdg(QIRProgramBuilder& b); // --- RXOp ----------------------------------------------------------------- // /// Creates a circuit with just an RX gate. -void rx(QIRProgramBuilder& b); +template Value rx(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RX gate. -void singleControlledRx(QIRProgramBuilder& b); +template +Value singleControlledRx(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RX gate. -void multipleControlledRx(QIRProgramBuilder& b); +template +Value multipleControlledRx(QIRProgramBuilder& b); // --- RYOp ----------------------------------------------------------------- // /// Creates a circuit with just an RY gate. -void ry(QIRProgramBuilder& b); +template Value ry(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RY gate. -void singleControlledRy(QIRProgramBuilder& b); +template +Value singleControlledRy(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RY gate. -void multipleControlledRy(QIRProgramBuilder& b); +template +Value multipleControlledRy(QIRProgramBuilder& b); // --- RZOp ----------------------------------------------------------------- // /// Creates a circuit with just an RZ gate. -void rz(QIRProgramBuilder& b); +template Value rz(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RZ gate. -void singleControlledRz(QIRProgramBuilder& b); +template +Value singleControlledRz(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RZ gate. -void multipleControlledRz(QIRProgramBuilder& b); +template +Value multipleControlledRz(QIRProgramBuilder& b); // --- POp ------------------------------------------------------------------ // /// Creates a circuit with just a P gate. -void p(QIRProgramBuilder& b); +template Value p(QIRProgramBuilder& b); /// Creates a circuit with a single controlled P gate. -void singleControlledP(QIRProgramBuilder& b); +template +Value singleControlledP(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled P gate. -void multipleControlledP(QIRProgramBuilder& b); +template +Value multipleControlledP(QIRProgramBuilder& b); // --- ROp ------------------------------------------------------------------ // /// Creates a circuit with just an R gate. -void r(QIRProgramBuilder& b); +template Value r(QIRProgramBuilder& b); /// Creates a circuit with a single controlled R gate. -void singleControlledR(QIRProgramBuilder& b); +template +Value singleControlledR(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled R gate. -void multipleControlledR(QIRProgramBuilder& b); +template +Value multipleControlledR(QIRProgramBuilder& b); // --- U2Op ----------------------------------------------------------------- // /// Creates a circuit with just a U2 gate. -void u2(QIRProgramBuilder& b); +template Value u2(QIRProgramBuilder& b); /// Creates a circuit with a single controlled U2 gate. -void singleControlledU2(QIRProgramBuilder& b); +template +Value singleControlledU2(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled U2 gate. -void multipleControlledU2(QIRProgramBuilder& b); +template +Value multipleControlledU2(QIRProgramBuilder& b); // --- UOp ------------------------------------------------------------------ // /// Creates a circuit with just a U gate. -void u(QIRProgramBuilder& b); +template Value u(QIRProgramBuilder& b); /// Creates a circuit with a single controlled U gate. -void singleControlledU(QIRProgramBuilder& b); +template +Value singleControlledU(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled U gate. -void multipleControlledU(QIRProgramBuilder& b); +template +Value multipleControlledU(QIRProgramBuilder& b); // --- SWAPOp --------------------------------------------------------------- // /// Creates a circuit with just a SWAP gate. -void swap(QIRProgramBuilder& b); +template Value swap(QIRProgramBuilder& b); /// Creates a circuit with a single controlled SWAP gate. -void singleControlledSwap(QIRProgramBuilder& b); +template +Value singleControlledSwap(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled SWAP gate. -void multipleControlledSwap(QIRProgramBuilder& b); +template +Value multipleControlledSwap(QIRProgramBuilder& b); // --- iSWAPOp -------------------------------------------------------------- // /// Creates a circuit with just an iSWAP gate. -void iswap(QIRProgramBuilder& b); +template Value iswap(QIRProgramBuilder& b); /// Creates a circuit with a single controlled iSWAP gate. -void singleControlledIswap(QIRProgramBuilder& b); +template +Value singleControlledIswap(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled iSWAP gate. -void multipleControlledIswap(QIRProgramBuilder& b); +template +Value multipleControlledIswap(QIRProgramBuilder& b); // --- DCXOp ---------------------------------------------------------------- // /// Creates a circuit with just a DCX gate. -void dcx(QIRProgramBuilder& b); +template Value dcx(QIRProgramBuilder& b); /// Creates a circuit with a single controlled DCX gate. -void singleControlledDcx(QIRProgramBuilder& b); +template +Value singleControlledDcx(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled DCX gate. -void multipleControlledDcx(QIRProgramBuilder& b); +template +Value multipleControlledDcx(QIRProgramBuilder& b); // --- ECROp ---------------------------------------------------------------- // /// Creates a circuit with just an ECR gate. -void ecr(QIRProgramBuilder& b); +template Value ecr(QIRProgramBuilder& b); /// Creates a circuit with a single controlled ECR gate. -void singleControlledEcr(QIRProgramBuilder& b); +template +Value singleControlledEcr(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled ECR gate. -void multipleControlledEcr(QIRProgramBuilder& b); +template +Value multipleControlledEcr(QIRProgramBuilder& b); // --- RXXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RXX gate. -void rxx(QIRProgramBuilder& b); +template Value rxx(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RXX gate. -void singleControlledRxx(QIRProgramBuilder& b); +template +Value singleControlledRxx(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RXX gate. -void multipleControlledRxx(QIRProgramBuilder& b); +template +Value multipleControlledRxx(QIRProgramBuilder& b); /// Creates a circuit with a triple-controlled RXX gate. -void tripleControlledRxx(QIRProgramBuilder& b); +template +Value tripleControlledRxx(QIRProgramBuilder& b); // --- RYYOp ---------------------------------------------------------------- // /// Creates a circuit with just an RYY gate. -void ryy(QIRProgramBuilder& b); +template Value ryy(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RYY gate. -void singleControlledRyy(QIRProgramBuilder& b); +template +Value singleControlledRyy(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RYY gate. -void multipleControlledRyy(QIRProgramBuilder& b); +template +Value multipleControlledRyy(QIRProgramBuilder& b); // --- RZXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZX gate. -void rzx(QIRProgramBuilder& b); +template Value rzx(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RZX gate. -void singleControlledRzx(QIRProgramBuilder& b); +template +Value singleControlledRzx(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RZX gate. -void multipleControlledRzx(QIRProgramBuilder& b); +template +Value multipleControlledRzx(QIRProgramBuilder& b); // --- RZZOp ---------------------------------------------------------------- // /// Creates a circuit with just an RZZ gate. -void rzz(QIRProgramBuilder& b); +template Value rzz(QIRProgramBuilder& b); /// Creates a circuit with a single controlled RZZ gate. -void singleControlledRzz(QIRProgramBuilder& b); +template +Value singleControlledRzz(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled RZZ gate. -void multipleControlledRzz(QIRProgramBuilder& b); +template +Value multipleControlledRzz(QIRProgramBuilder& b); // --- XXPlusYYOp ----------------------------------------------------------- // /// Creates a circuit with just an XXPlusYY gate. -void xxPlusYY(QIRProgramBuilder& b); +template Value xxPlusYY(QIRProgramBuilder& b); /// Creates a circuit with a single controlled XXPlusYY gate. -void singleControlledXxPlusYY(QIRProgramBuilder& b); +template +Value singleControlledXxPlusYY(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled XXPlusYY gate. -void multipleControlledXxPlusYY(QIRProgramBuilder& b); +template +Value multipleControlledXxPlusYY(QIRProgramBuilder& b); // --- XXMinusYYOp ---------------------------------------------------------- // /// Creates a circuit with just an XXMinusYY gate. -void xxMinusYY(QIRProgramBuilder& b); +template Value xxMinusYY(QIRProgramBuilder& b); /// Creates a circuit with a single controlled XXMinusYY gate. -void singleControlledXxMinusYY(QIRProgramBuilder& b); +template +Value singleControlledXxMinusYY(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled XXMinusYY gate. -void multipleControlledXxMinusYY(QIRProgramBuilder& b); +template +Value multipleControlledXxMinusYY(QIRProgramBuilder& b); // --- IfOp ----------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. -void simpleIf(QIRProgramBuilder& b); +template Value simpleIf(QIRProgramBuilder& b); /// Creates a circuit with an if operation with an else branch. -void ifElse(QIRProgramBuilder& b); +template Value ifElse(QIRProgramBuilder& b); /// Creates a circuit with an if operation with two qubits. -void ifTwoQubits(QIRProgramBuilder& b); +template Value ifTwoQubits(QIRProgramBuilder& b); /// Creates a circuit with an if operation with a nested for operation with /// a register. -void nestedIfOpForLoop(QIRProgramBuilder& b); +template +Value nestedIfOpForLoop(QIRProgramBuilder& b); // --- WhileOp -------------------------------------------------------------- // /// Creates a circuit with a while operation using a while loop. -void simpleWhileReset(QIRProgramBuilder& b); +template +Value simpleWhileReset(QIRProgramBuilder& b); /// Creates a circuit with a while operation using a do-while loop. -void simpleDoWhileReset(QIRProgramBuilder& b); +template +Value simpleDoWhileReset(QIRProgramBuilder& b); // --- ForOp ---------------------------------------------------------------- // /// Creates a circuit with a simple for operation with a register. -void simpleForLoop(QIRProgramBuilder& b); +template Value simpleForLoop(QIRProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested if operation. -void nestedForLoopIfOp(QIRProgramBuilder& b); +template +Value nestedForLoopIfOp(QIRProgramBuilder& b); /// Creates a circuit with a for operation with a register and a nested while /// operation. -void nestedForLoopWhileOp(QIRProgramBuilder& b); +template +Value nestedForLoopWhileOp(QIRProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is separately allocated from the /// register. -void nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b); +template +Value nestedForLoopCtrlOpWithSeparateQubit(QIRProgramBuilder& b); /// Creates a circuit with a for operation with a register and a qubit and a /// nested ctrl operation where the qubit is extracted from the register. -void nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b); +template +Value nestedForLoopCtrlOpWithExtractedQubit(QIRProgramBuilder& b); // --- CtrlOp --------------------------------------------------------------- // /// Creates a circuit with a control modifier applied to two gates. -void ctrlTwo(QIRProgramBuilder& b); +template Value ctrlTwo(QIRProgramBuilder& b); } // namespace mlir::qir diff --git a/mlir/unittests/programs/quantum_computation_programs.cpp b/mlir/unittests/programs/quantum_computation_programs.cpp index f0b9b305cd..d473a61bb4 100644 --- a/mlir/unittests/programs/quantum_computation_programs.cpp +++ b/mlir/unittests/programs/quantum_computation_programs.cpp @@ -22,19 +22,27 @@ namespace qc { -void allocQubit(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); } +void allocQubit(QuantumComputation& comp) { + auto qr = comp.addQubitRegister(1, "q"); + comp.measureAll(true, false); +} void allocQubitRegister(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); + comp.measureAll(true, false); } void allocMultipleQubitRegisters(QuantumComputation& comp) { comp.addQubitRegister(2, "reg0"); comp.addQubitRegister(3, "reg1"); + comp.measureAll(true, false); } void allocLargeRegister(QuantumComputation& comp) { comp.addQubitRegister(100, "q"); + comp.addClassicalRegister(2, "meas"); + comp.measure(0, 0); + comp.measure(99, 1); } void singleMeasurementToSingleBit(QuantumComputation& comp) { @@ -72,6 +80,7 @@ void resetQubitAfterSingleOp(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.h(0); comp.reset(0); + comp.measureAll(true, false); } void resetMultipleQubitsAfterSingleOp(QuantumComputation& comp) { @@ -80,6 +89,7 @@ void resetMultipleQubitsAfterSingleOp(QuantumComputation& comp) { comp.reset(0); comp.h(1); comp.reset(1); + comp.measureAll(true, false); } void repeatedResetAfterSingleOp(QuantumComputation& comp) { @@ -88,6 +98,7 @@ void repeatedResetAfterSingleOp(QuantumComputation& comp) { comp.reset(0); comp.reset(0); comp.reset(0); + comp.measureAll(true, false); } void globalPhase(QuantumComputation& comp) { comp.gphase(0.123); } @@ -95,451 +106,541 @@ void globalPhase(QuantumComputation& comp) { comp.gphase(0.123); } void identity(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.i(0); + comp.measureAll(true, false); } void singleControlledIdentity(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); - comp.ci(1, 0); + comp.ci(0, 1); + comp.measureAll(true, false); } void multipleControlledIdentity(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); - comp.mci({2, 1}, 0); + comp.mci({0, 1}, 2); + comp.measureAll(true, false); } void x(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.x(0); + comp.measureAll(true, false); } void singleControlledX(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cx(0, 1); + comp.measureAll(true, false); } void multipleControlledX(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcx({0, 1}, 2); + comp.measureAll(true, false); } void y(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.y(0); + comp.measureAll(true, false); } void singleControlledY(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cy(0, 1); + comp.measureAll(true, false); } void multipleControlledY(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcy({0, 1}, 2); + comp.measureAll(true, false); } void z(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.z(0); + comp.measureAll(true, false); } void singleControlledZ(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cz(0, 1); + comp.measureAll(true, false); } void multipleControlledZ(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcz({0, 1}, 2); + comp.measureAll(true, false); } void h(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.h(0); + comp.measureAll(true, false); } void singleControlledH(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.ch(0, 1); + comp.measureAll(true, false); } void multipleControlledH(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mch({0, 1}, 2); + comp.measureAll(true, false); } void s(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.s(0); + comp.measureAll(true, false); } void singleControlledS(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cs(0, 1); + comp.measureAll(true, false); } void multipleControlledS(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcs({0, 1}, 2); + comp.measureAll(true, false); } void sdg(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.sdg(0); + comp.measureAll(true, false); } void singleControlledSdg(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.csdg(0, 1); + comp.measureAll(true, false); } void multipleControlledSdg(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcsdg({0, 1}, 2); + comp.measureAll(true, false); } void t_(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.t(0); + comp.measureAll(true, false); } void singleControlledT(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.ct(0, 1); + comp.measureAll(true, false); } void multipleControlledT(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mct({0, 1}, 2); + comp.measureAll(true, false); } void tdg(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.tdg(0); + comp.measureAll(true, false); } void singleControlledTdg(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.ctdg(0, 1); + comp.measureAll(true, false); } void multipleControlledTdg(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mctdg({0, 1}, 2); + comp.measureAll(true, false); } void sx(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.sx(0); + comp.measureAll(true, false); } void singleControlledSx(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.csx(0, 1); + comp.measureAll(true, false); } void multipleControlledSx(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcsx({0, 1}, 2); + comp.measureAll(true, false); } void sxdg(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.sxdg(0); + comp.measureAll(true, false); } void singleControlledSxdg(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.csxdg(0, 1); + comp.measureAll(true, false); } void multipleControlledSxdg(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcsxdg({0, 1}, 2); + comp.measureAll(true, false); } void rx(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.rx(0.123, 0); + comp.measureAll(true, false); } void singleControlledRx(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.crx(0.123, 0, 1); + comp.measureAll(true, false); } void multipleControlledRx(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcrx(0.123, {0, 1}, 2); + comp.measureAll(true, false); } void ry(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.ry(0.456, 0); + comp.measureAll(true, false); } void singleControlledRy(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cry(0.456, 0, 1); + comp.measureAll(true, false); } void multipleControlledRy(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcry(0.456, {0, 1}, 2); + comp.measureAll(true, false); } void rz(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.rz(0.789, 0); + comp.measureAll(true, false); } void singleControlledRz(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.crz(0.789, 0, 1); + comp.measureAll(true, false); } void multipleControlledRz(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcrz(0.789, {0, 1}, 2); + comp.measureAll(true, false); } void p(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.p(0.123, 0); + comp.measureAll(true, false); } void singleControlledP(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cp(0.123, 0, 1); + comp.measureAll(true, false); } void multipleControlledP(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcp(0.123, {0, 1}, 2); + comp.measureAll(true, false); } void r(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.r(0.123, 0.456, 0); + comp.measureAll(true, false); } void singleControlledR(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cr(0.123, 0.456, 0, 1); + comp.measureAll(true, false); } void multipleControlledR(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcr(0.123, 0.456, {0, 1}, 2); + comp.measureAll(true, false); } void u2(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.u2(0.234, 0.567, 0); + comp.measureAll(true, false); } void singleControlledU2(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cu2(0.234, 0.567, 0, 1); + comp.measureAll(true, false); } void multipleControlledU2(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcu2(0.234, 0.567, {0, 1}, 2); + comp.measureAll(true, false); } void u(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.u(0.1, 0.2, 0.3, 0); + comp.measureAll(true, false); } void singleControlledU(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.cu(0.1, 0.2, 0.3, 0, 1); + comp.measureAll(true, false); } void multipleControlledU(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.mcu(0.1, 0.2, 0.3, {0, 1}, 2); + comp.measureAll(true, false); } void swap(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.swap(0, 1); + comp.measureAll(true, false); } void singleControlledSwap(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cswap(0, 1, 2); + comp.measureAll(true, false); } void multipleControlledSwap(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcswap({0, 1}, 2, 3); + comp.measureAll(true, false); } void iswap(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.iswap(0, 1); + comp.measureAll(true, false); } void singleControlledIswap(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.ciswap(0, 1, 2); + comp.measureAll(true, false); } void multipleControlledIswap(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mciswap({0, 1}, 2, 3); + comp.measureAll(true, false); } void inverseIswap(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.iswapdg(0, 1); + comp.measureAll(true, false); } void inverseMultipleControlledIswap(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mciswapdg({0, 1}, 2, 3); + comp.measureAll(true, false); } void dcx(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.dcx(0, 1); + comp.measureAll(true, false); } void singleControlledDcx(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cdcx(0, 1, 2); + comp.measureAll(true, false); } void multipleControlledDcx(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcdcx({0, 1}, 2, 3); + comp.measureAll(true, false); } void ecr(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.ecr(0, 1); + comp.measureAll(true, false); } void singleControlledEcr(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cecr(0, 1, 2); + comp.measureAll(true, false); } void multipleControlledEcr(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcecr({0, 1}, 2, 3); + comp.measureAll(true, false); } void rxx(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.rxx(0.123, 0, 1); + comp.measureAll(true, false); } void singleControlledRxx(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.crxx(0.123, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledRxx(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcrxx(0.123, {0, 1}, 2, 3); + comp.measureAll(true, false); } void tripleControlledRxx(QuantumComputation& comp) { comp.addQubitRegister(5, "q"); comp.mcrxx(0.123, {0, 1, 2}, 3, 4); + comp.measureAll(true, false); } void ryy(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.ryy(0.123, 0, 1); + comp.measureAll(true, false); } void singleControlledRyy(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cryy(0.123, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledRyy(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcryy(0.123, {0, 1}, 2, 3); + comp.measureAll(true, false); } void rzx(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.rzx(0.123, 0, 1); + comp.measureAll(true, false); } void singleControlledRzx(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.crzx(0.123, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledRzx(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcrzx(0.123, {0, 1}, 2, 3); + comp.measureAll(true, false); } void rzz(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.rzz(0.123, 0, 1); + comp.measureAll(true, false); } void singleControlledRzz(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.crzz(0.123, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledRzz(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcrzz(0.123, {0, 1}, 2, 3); + comp.measureAll(true, false); } void xxPlusYY(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.xx_plus_yy(0.123, 0.456, 0, 1); + comp.measureAll(true, false); } void singleControlledXxPlusYY(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cxx_plus_yy(0.123, 0.456, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledXxPlusYY(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcxx_plus_yy(0.123, 0.456, {0, 1}, 2, 3); + comp.measureAll(true, false); } void xxMinusYY(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.xx_minus_yy(0.123, 0.456, 0, 1); + comp.measureAll(true, false); } void singleControlledXxMinusYY(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.cxx_minus_yy(0.123, 0.456, 0, 1, 2); + comp.measureAll(true, false); } void multipleControlledXxMinusYY(QuantumComputation& comp) { comp.addQubitRegister(4, "q"); comp.mcxx_minus_yy(0.123, 0.456, {0, 1}, 2, 3); + comp.measureAll(true, false); } void barrier(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); comp.barrier(0); + comp.measureAll(true, false); } void barrierTwoQubits(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); comp.barrier({0, 1}); + comp.measureAll(true, false); } void barrierMultipleQubits(QuantumComputation& comp) { comp.addQubitRegister(3, "q"); comp.barrier({0, 1, 2}); + comp.measureAll(true, false); } void ctrlTwo(QuantumComputation& comp) { @@ -551,6 +652,7 @@ void ctrlTwo(QuantumComputation& comp) { compound.addControl(0); compound.addControl(1); comp.emplace_back(std::move(compound)); + comp.measureAll(true, false); } void ctrlTwoMixed(QuantumComputation& comp) { @@ -562,6 +664,7 @@ void ctrlTwoMixed(QuantumComputation& comp) { compound.addControl(0); compound.addControl(1); comp.emplace_back(std::move(compound)); + comp.measureAll(true, false); } void simpleIf(QuantumComputation& comp) { @@ -570,6 +673,8 @@ void simpleIf(QuantumComputation& comp) { comp.h(q[0]); comp.measure(q[0], c[0]); comp.if_(X, q[0], c[0]); + const auto& c2 = comp.addClassicalRegister(1, "meas"); + comp.measure(q[0], c2[0]); } void ifTwoQubits(QuantumComputation& comp) { @@ -583,6 +688,7 @@ void ifTwoQubits(QuantumComputation& comp) { IfElseOperation ifElse( std::make_unique(std::move(compound)), nullptr, c[0]); comp.emplace_back(std::move(ifElse)); + comp.measureAll(true, false); } void ifElse(QuantumComputation& comp) { @@ -592,6 +698,7 @@ void ifElse(QuantumComputation& comp) { comp.measure(q[0], c[0]); comp.ifElse(std::make_unique(q[0], X), std::make_unique(q[0], Z), c[0]); + comp.measureAll(true, false); } } // namespace qc diff --git a/pyproject.toml b/pyproject.toml index 69158dc490..5524b3a2a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ [build-system] requires = [ "nanobind~=2.13.0", - "scikit-build-core>=0.12.2", + "scikit-build-core~=0.12", "setuptools-scm>=9.2.2", ] build-backend = "scikit_build_core.build" @@ -328,7 +328,7 @@ exclude = [ [dependency-groups] build = [ "nanobind~=2.13.0", - "scikit-build-core>=0.12.2", + "scikit-build-core~=0.12", "setuptools-scm>=9.2.2", ] docs = [ diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 6dfef7e4db..9a1cd3a637 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -251,7 +251,18 @@ class Device: def supported_program_formats(self) -> list[ProgramFormat]: """Returns the list of program formats supported by the device.""" - def submit_job(self, program: str, program_format: ProgramFormat, num_shots: int) -> Job: + def submit_job( + self, + program: str, + program_format: ProgramFormat, + num_shots: int, + *, + custom1: str | bool | float | None = None, + custom2: str | bool | float | None = None, + custom3: str | bool | float | None = None, + custom4: str | bool | float | None = None, + custom5: str | bool | float | None = None, + ) -> Job: """Submits a job to the device.""" def __eq__(self, arg: object, /) -> bool: ... diff --git a/python/mqt/core/ir/__init__.pyi b/python/mqt/core/ir/__init__.pyi index 69ea76967a..b95847f98b 100644 --- a/python/mqt/core/ir/__init__.pyi +++ b/python/mqt/core/ir/__init__.pyi @@ -1850,15 +1850,17 @@ class QuantumComputation(MutableSequence[operations.Operation]): cbits: The classical bits to store the results """ - def measure_all(self, *, add_bits: bool = True) -> None: + 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 diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 2f96b8c4ca..a64b98bdea 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -22,112 +22,111 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include #include namespace fomac { -auto Session::Device::Site::getIndex() const -> size_t { +size_t Site::getIndex() const { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } -auto Session::Device::Site::getT1() const -> std::optional { +std::optional Site::getT1() const { return queryProperty>(QDMI_SITE_PROPERTY_T1); } -auto Session::Device::Site::getT2() const -> std::optional { +std::optional Site::getT2() const { return queryProperty>(QDMI_SITE_PROPERTY_T2); } -auto Session::Device::Site::getName() const -> std::optional { +std::optional Site::getName() const { return queryProperty>(QDMI_SITE_PROPERTY_NAME); } -auto Session::Device::Site::getXCoordinate() const -> std::optional { +std::optional Site::getXCoordinate() const { return queryProperty>(QDMI_SITE_PROPERTY_XCOORDINATE); } -auto Session::Device::Site::getYCoordinate() const -> std::optional { +std::optional Site::getYCoordinate() const { return queryProperty>(QDMI_SITE_PROPERTY_YCOORDINATE); } -auto Session::Device::Site::getZCoordinate() const -> std::optional { +std::optional Site::getZCoordinate() const { return queryProperty>(QDMI_SITE_PROPERTY_ZCOORDINATE); } -auto Session::Device::Site::isZone() const -> bool { +bool Site::isZone() const { return queryProperty>(QDMI_SITE_PROPERTY_ISZONE) .value_or(false); } -auto Session::Device::Site::getXExtent() const -> std::optional { +std::optional Site::getXExtent() const { return queryProperty>(QDMI_SITE_PROPERTY_XEXTENT); } -auto Session::Device::Site::getYExtent() const -> std::optional { +std::optional Site::getYExtent() const { return queryProperty>(QDMI_SITE_PROPERTY_YEXTENT); } -auto Session::Device::Site::getZExtent() const -> std::optional { +std::optional Site::getZExtent() const { return queryProperty>(QDMI_SITE_PROPERTY_ZEXTENT); } -auto Session::Device::Site::getModuleIndex() const -> std::optional { +std::optional Site::getModuleIndex() const { return queryProperty>(QDMI_SITE_PROPERTY_MODULEINDEX); } -auto Session::Device::Site::getSubmoduleIndex() const - -> std::optional { +std::optional Site::getSubmoduleIndex() const { return queryProperty>( QDMI_SITE_PROPERTY_SUBMODULEINDEX); } -auto Session::Device::Operation::getName( - const std::vector& sites, const std::vector& params) const - -> std::string { +std::string Operation::getName(const std::vector& sites, + const std::vector& params) const { return queryProperty(QDMI_OPERATION_PROPERTY_NAME, sites, params); } -auto Session::Device::Operation::getQubitsNum( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getQubitsNum(const std::vector& sites, + const std::vector& params) const { return queryProperty>(QDMI_OPERATION_PROPERTY_QUBITSNUM, sites, params); } -auto Session::Device::Operation::getParametersNum( - const std::vector& sites, const std::vector& params) const - -> size_t { +size_t Operation::getParametersNum(const std::vector& sites, + const std::vector& params) const { return queryProperty(QDMI_OPERATION_PROPERTY_PARAMETERSNUM, sites, params); } -auto Session::Device::Operation::getDuration( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getDuration(const std::vector& sites, + const std::vector& params) const { return queryProperty>( QDMI_OPERATION_PROPERTY_DURATION, sites, params); } -auto Session::Device::Operation::getFidelity( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getFidelity(const std::vector& sites, + const std::vector& params) const { return queryProperty>(QDMI_OPERATION_PROPERTY_FIDELITY, sites, params); } -auto Session::Device::Operation::getInteractionRadius( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getInteractionRadius(const std::vector& sites, + const std::vector& params) const { return queryProperty>( QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS, sites, params); } -auto Session::Device::Operation::getBlockingRadius( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getBlockingRadius(const std::vector& sites, + const std::vector& params) const { return queryProperty>( QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS, sites, params); } -auto Session::Device::Operation::getIdlingFidelity( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getIdlingFidelity(const std::vector& sites, + const std::vector& params) const { return queryProperty>( QDMI_OPERATION_PROPERTY_IDLINGFIDELITY, sites, params); } -auto Session::Device::Operation::isZoned() const -> bool { +bool Operation::isZoned() const { return queryProperty>(QDMI_OPERATION_PROPERTY_ISZONED, {}, {}) .value_or(false); } -auto Session::Device::Operation::getSites() const - -> std::optional> { +std::optional> Operation::getSites() const { const auto& qdmiSites = queryProperty>>( QDMI_OPERATION_PROPERTY_SITES, {}, {}); if (!qdmiSites.has_value()) { @@ -137,12 +136,12 @@ auto Session::Device::Operation::getSites() const returnedSites.reserve(qdmiSites->size()); std::ranges::transform(*qdmiSites, std::back_inserter(returnedSites), [device = device_](const QDMI_Site& site) -> Site { - return {Token{}, device, site}; + return {device, site}; }); return returnedSites; } -auto Session::Device::Operation::getSitePairs() const - -> std::optional>> { +std::optional>> +Operation::getSitePairs() const { if (const auto qubitsNum = getQubitsNum({}, {}); !qubitsNum.has_value() || *qubitsNum != 2 || isZoned()) { return std::nullopt; // Not a 2-qubit operation or operation is zoned @@ -167,152 +166,200 @@ auto Session::Device::Operation::getSitePairs() const return pairs; } -auto Session::Device::Operation::getMeanShuttlingSpeed( - const std::vector& sites, const std::vector& params) const - -> std::optional { +std::optional +Operation::getMeanShuttlingSpeed(const std::vector& sites, + const std::vector& params) const { return queryProperty>( QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED, sites, params); } -auto Session::Device::getName() const -> std::string { +std::string Device::getName() const { return queryProperty(QDMI_DEVICE_PROPERTY_NAME); } -auto Session::Device::getVersion() const -> std::string { + +std::string Device::getVersion() const { return queryProperty(QDMI_DEVICE_PROPERTY_VERSION); } -auto Session::Device::getStatus() const -> QDMI_Device_Status { + +QDMI_Device_Status Device::getStatus() const { return queryProperty(QDMI_DEVICE_PROPERTY_STATUS); } -auto Session::Device::getLibraryVersion() const -> std::string { + +std::string Device::getLibraryVersion() const { return queryProperty(QDMI_DEVICE_PROPERTY_LIBRARYVERSION); } -auto Session::Device::getQubitsNum() const -> size_t { + +size_t Device::getQubitsNum() const { return queryProperty(QDMI_DEVICE_PROPERTY_QUBITSNUM); } -auto Session::Device::getSites() const -> std::vector { + +std::vector Device::getSites() const { const auto& qdmiSites = queryProperty>(QDMI_DEVICE_PROPERTY_SITES); std::vector sites; sites.reserve(qdmiSites.size()); - std::ranges::transform(qdmiSites, std::back_inserter(sites), - [device = device_](const QDMI_Site& site) -> Site { - return {Token{}, device, site}; - }); + std::ranges::transform( + qdmiSites, std::back_inserter(sites), + [this](const QDMI_Site& site) -> Site { return {this, site}; }); return sites; } -auto Session::Device::getRegularSites() const -> std::vector { + +std::vector Device::getRegularSites() const { auto allSites = getSites(); const auto newEnd = std::ranges::remove_if( - allSites, [](const Site& s) { return s.isZone(); }); + allSites, [](const auto& s) { return s.isZone(); }); allSites.erase(newEnd.begin(), newEnd.end()); return allSites; } -auto Session::Device::getZones() const -> std::vector { + +std::vector Device::getZones() const { const auto& allSites = getSites(); std::vector zones; zones.reserve(3); // Reserve space for a typical max number of zones std::ranges::copy_if(allSites, std::back_inserter(zones), - [](const Site& s) { return s.isZone(); }); + [](const auto& s) { return s.isZone(); }); return zones; } -auto Session::Device::getOperations() const -> std::vector { + +std::vector Device::getOperations() const { const auto& qdmiOperations = queryProperty>( QDMI_DEVICE_PROPERTY_OPERATIONS); std::vector operations; operations.reserve(qdmiOperations.size()); std::ranges::transform( qdmiOperations, std::back_inserter(operations), - [device = device_](const QDMI_Operation& op) -> Operation { - return {Token{}, device, op}; - }); + [this](const QDMI_Operation& op) -> Operation { return {this, op}; }); return operations; } -auto Session::Device::getCouplingMap() const - -> std::optional>> { + +std::optional>> +Device::getCouplingMap() const { const auto& qdmiCouplingMap = queryProperty< std::optional>>>( QDMI_DEVICE_PROPERTY_COUPLINGMAP); if (!qdmiCouplingMap.has_value()) { return std::nullopt; } + std::vector> couplingMap; couplingMap.reserve(qdmiCouplingMap->size()); std::ranges::transform(*qdmiCouplingMap, std::back_inserter(couplingMap), [this](const std::pair& pair) -> std::pair { - return {Site{Token{}, device_, pair.first}, - Site{Token{}, device_, pair.second}}; + return {Site{this, pair.first}, + Site{this, pair.second}}; }); return couplingMap; } -auto Session::Device::getNeedsCalibration() const -> std::optional { + +std::optional Device::getNeedsCalibration() const { return queryProperty>( QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION); } -auto Session::Device::getLengthUnit() const -> std::optional { + +std::optional Device::getLengthUnit() const { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHUNIT); } -auto Session::Device::getLengthScaleFactor() const -> std::optional { + +std::optional Device::getLengthScaleFactor() const { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR); } -auto Session::Device::getDurationUnit() const -> std::optional { + +std::optional Device::getDurationUnit() const { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONUNIT); } -auto Session::Device::getDurationScaleFactor() const -> std::optional { + +std::optional Device::getDurationScaleFactor() const { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR); } -auto Session::Device::getMinAtomDistance() const -> std::optional { + +std::optional Device::getMinAtomDistance() const { return queryProperty>( QDMI_DEVICE_PROPERTY_MINATOMDISTANCE); } -auto Session::Device::getSupportedProgramFormats() const - -> std::vector { +std::vector Device::getSupportedProgramFormats() const { return queryProperty>( QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS); } -auto Session::Device::submitJob(const std::string& program, - const QDMI_Program_Format format, - const size_t numShots) const -> Job { +Job Device::submitJob(const std::string& program, + const QDMI_Program_Format format, const size_t numShots, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) const { QDMI_Job job = nullptr; qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); - Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions + Job jobWrapper{job}; - // Set program format qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAMFORMAT, sizeof(format), &format), "Setting program format"); - - // Set program qdmi::throwIfError( QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, program.size() + 1, program.c_str()), "Setting program"); - - // Set number of shots qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(numShots), &numShots), "Setting number of shots"); - // Submit the job + if (custom1.has_value()) { + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM1, *custom1); + } + if (custom2.has_value()) { + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM2, *custom2); + } + if (custom3.has_value()) { + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM3, *custom3); + } + if (custom4.has_value()) { + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM4, *custom4); + } + if (custom5.has_value()) { + setCustomJobParam(jobWrapper, QDMI_JOB_PARAMETER_CUSTOM5, *custom5); + } + qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); return jobWrapper; } -auto Session::Job::check() const -> QDMI_Job_Status { +void Device::setCustomJobParam(QDMI_Job job, const QDMI_Job_Parameter param, + const CustomJobParameter& value) { + std::visit( + [&](const CustomValue& customValue) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + qdmi::throwIfError(QDMI_job_set_parameter(job, param, + customValue.size() + 1, + customValue.c_str()), + "Setting custom parameter"); + } else { + static_assert(std::is_trivially_copyable_v, + "Custom job parameters must be trivially copyable"); + qdmi::throwIfError( + QDMI_job_set_parameter(job, param, sizeof(T), &customValue), + "Setting custom parameter"); + } + }, + value); +} + +QDMI_Job_Status Job::check() const { QDMI_Job_Status status{}; - qdmi::throwIfError(QDMI_job_check(job_, &status), "Checking job status"); + qdmi::throwIfError(QDMI_job_check(job_.get(), &status), + "Checking job status"); return status; } -auto Session::Job::wait(const size_t timeout) const -> bool { - const auto ret = QDMI_job_wait(job_, timeout); +bool Job::wait(const size_t timeout) const { + const auto ret = QDMI_job_wait(job_.get(), timeout); if (ret == QDMI_SUCCESS) { return true; } @@ -323,65 +370,67 @@ auto Session::Job::wait(const size_t timeout) const -> bool { qdmi::unreachable(); } -auto Session::Job::cancel() const -> void { - qdmi::throwIfError(QDMI_job_cancel(job_), "Cancelling job"); +void Job::cancel() const { + qdmi::throwIfError(QDMI_job_cancel(job_.get()), "Cancelling job"); } -auto Session::Job::getId() const -> std::string { +std::string Job::getId() const { size_t size = 0; - qdmi::throwIfError( - QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, 0, nullptr, &size), - "Querying job ID size"); + qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_ID, + 0, nullptr, &size), + "Querying job ID size"); std::string id(size - 1, '\0'); - qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, size, - id.data(), nullptr), + qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_ID, + size, id.data(), nullptr), "Querying job ID"); return id; } -auto Session::Job::getProgramFormat() const -> QDMI_Program_Format { +QDMI_Program_Format Job::getProgramFormat() const { QDMI_Program_Format format{}; - qdmi::throwIfError(QDMI_job_query_property(job_, + qdmi::throwIfError(QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_PROGRAMFORMAT, sizeof(format), &format, nullptr), "Querying program format"); return format; } -auto Session::Job::getProgram() const -> std::string { +std::string Job::getProgram() const { size_t size = 0; - qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, + qdmi::throwIfError(QDMI_job_query_property(job_.get(), + QDMI_JOB_PROPERTY_PROGRAM, 0, nullptr, &size), "Querying program size"); std::string program(size - 1, '\0'); - qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, - size, program.data(), nullptr), + qdmi::throwIfError(QDMI_job_query_property(job_.get(), + QDMI_JOB_PROPERTY_PROGRAM, size, + program.data(), nullptr), "Querying program"); return program; } -auto Session::Job::getNumShots() const -> size_t { +size_t Job::getNumShots() const { size_t numShots = 0; - qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, - sizeof(numShots), &numShots, - nullptr), - "Querying number of shots"); + qdmi::throwIfError( + QDMI_job_query_property(job_.get(), QDMI_JOB_PROPERTY_SHOTSNUM, + sizeof(numShots), &numShots, nullptr), + "Querying number of shots"); return numShots; } -auto Session::Job::getShots() const -> std::vector { +std::vector Job::getShots() const { size_t shotsSize = 0; - qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, 0, nullptr, &shotsSize), - "Querying shots size"); + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_SHOTS, 0, + nullptr, &shotsSize), + "Querying shots size"); if (shotsSize == 0) { return {}; } std::string shots(shotsSize - 1, '\0'); - qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_SHOTS, shotsSize, shots.data(), nullptr), "Querying shots"); @@ -401,11 +450,11 @@ auto Session::Job::getShots() const -> std::vector { return shotsVec; } -auto Session::Job::getCounts() const -> std::map { +std::map Job::getCounts() const { // Get the histogram keys size_t keysSize = 0; - qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, - nullptr, &keysSize), + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_HIST_KEYS, + 0, nullptr, &keysSize), "Querying histogram keys size"); if (keysSize == 0) { @@ -413,13 +462,14 @@ auto Session::Job::getCounts() const -> std::map { } std::string keys(keysSize - 1, '\0'); - qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_HIST_KEYS, keysSize, keys.data(), nullptr), "Querying histogram keys"); // Get the histogram values size_t valuesSize = 0; - qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, 0, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), + QDMI_JOB_RESULT_HIST_VALUES, 0, nullptr, &valuesSize), "Querying histogram values size"); @@ -429,7 +479,8 @@ auto Session::Job::getCounts() const -> std::map { } std::vector values(valuesSize / sizeof(size_t)); - qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), + QDMI_JOB_RESULT_HIST_VALUES, valuesSize, values.data(), nullptr), "Querying histogram values"); @@ -452,10 +503,9 @@ auto Session::Job::getCounts() const -> std::map { return counts; } -auto Session::Job::getDenseStateVector() const - -> std::vector> { +std::vector> Job::getDenseStateVector() const { size_t size = 0; - qdmi::throwIfError(QDMI_job_get_results(job_, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, nullptr, &size), "Querying dense state vector size"); @@ -467,16 +517,16 @@ auto Session::Job::getDenseStateVector() const std::vector> stateVector(size / sizeof(std::complex)); - qdmi::throwIfError(QDMI_job_get_results(job_, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_STATEVECTOR_DENSE, size, stateVector.data(), nullptr), "Querying dense state vector"); return stateVector; } -auto Session::Job::getDenseProbabilities() const -> std::vector { +std::vector Job::getDenseProbabilities() const { size_t size = 0; - qdmi::throwIfError(QDMI_job_get_results(job_, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_PROBABILITIES_DENSE, 0, nullptr, &size), "Querying dense probabilities size"); @@ -487,19 +537,18 @@ auto Session::Job::getDenseProbabilities() const -> std::vector { } std::vector probabilities(size / sizeof(double)); - qdmi::throwIfError(QDMI_job_get_results(job_, + qdmi::throwIfError(QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_PROBABILITIES_DENSE, size, probabilities.data(), nullptr), "Querying dense probabilities"); return probabilities; } -auto Session::Job::getSparseStateVector() const - -> std::map> { +std::map> Job::getSparseStateVector() const { size_t keysSize = 0; qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, 0, - nullptr, &keysSize), + QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, + 0, nullptr, &keysSize), "Querying sparse state vector keys size"); if (keysSize == 0) { @@ -508,15 +557,15 @@ auto Session::Job::getSparseStateVector() const std::string keys(keysSize - 1, '\0'); qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, + QDMI_job_get_results(job_.get(), QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, keysSize, keys.data(), nullptr), "Querying sparse state vector keys"); size_t valuesSize = 0; - qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, 0, - nullptr, &valuesSize), - "Querying sparse state vector values size"); + qdmi::throwIfError(QDMI_job_get_results( + job_.get(), QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, + 0, nullptr, &valuesSize), + "Querying sparse state vector values size"); if (valuesSize % sizeof(std::complex) != 0) { throw std::runtime_error( @@ -526,10 +575,10 @@ auto Session::Job::getSparseStateVector() const std::vector> values(valuesSize / sizeof(std::complex)); - qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, - valuesSize, values.data(), nullptr), - "Querying sparse state vector values"); + qdmi::throwIfError(QDMI_job_get_results( + job_.get(), QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, + valuesSize, values.data(), nullptr), + "Querying sparse state vector values"); // Parse the keys (comma-separated) std::map> stateVector; @@ -550,27 +599,27 @@ auto Session::Job::getSparseStateVector() const return stateVector; } -auto Session::Job::getSparseProbabilities() const - -> std::map { +std::map Job::getSparseProbabilities() const { size_t keysSize = 0; - qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, 0, - nullptr, &keysSize), - "Querying sparse probabilities keys size"); + qdmi::throwIfError(QDMI_job_get_results( + job_.get(), QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, + 0, nullptr, &keysSize), + "Querying sparse probabilities keys size"); if (keysSize == 0) { return {}; // Empty probabilities } std::string keys(keysSize - 1, '\0'); - qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, - keysSize, keys.data(), nullptr), - "Querying sparse probabilities keys"); + qdmi::throwIfError(QDMI_job_get_results( + job_.get(), QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, + keysSize, keys.data(), nullptr), + "Querying sparse probabilities keys"); size_t valuesSize = 0; qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, 0, + QDMI_job_get_results(job_.get(), + QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, 0, nullptr, &valuesSize), "Querying sparse probabilities values size"); @@ -581,7 +630,8 @@ auto Session::Job::getSparseProbabilities() const std::vector values(valuesSize / sizeof(double)); qdmi::throwIfError( - QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, + QDMI_job_get_results(job_.get(), + QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, valuesSize, values.data(), nullptr), "Querying sparse probabilities values"); @@ -603,23 +653,25 @@ auto Session::Job::getSparseProbabilities() const return probabilities; } +Device Session::createSessionlessDevice(QDMI_Device device) { + return Device(device); +} + Session::Session(const SessionConfig& config) { - const auto result = QDMI_session_alloc(&session_); - qdmi::throwIfError(result, "Allocating QDMI session"); - - // Helper to ensure session is freed if an exception is thrown during setup - const auto cleanup = [this]() -> void { - if (session_ != nullptr) { - QDMI_session_free(session_); - session_ = nullptr; - } - }; + session_ = [] { + QDMI_Session session = nullptr; + const auto result = QDMI_session_alloc(&session); + qdmi::throwIfError(result, "Allocating QDMI session"); + return std::unique_ptr( + session, QDMI_session_free); + }(); + // Helper to set session parameters const auto setParameter = [this](const std::optional& value, QDMI_Session_Parameter param) -> void { if (value) { const auto status = static_cast(QDMI_session_set_parameter( - session_, param, value->size() + 1, value->c_str())); + session_.get(), param, value->size() + 1, value->c_str())); if (status == QDMI_ERROR_NOTSUPPORTED) { // Optional parameter not supported by session - skip it SPDLOG_INFO("Session parameter {} not supported (skipped)", @@ -636,97 +688,70 @@ Session::Session(const SessionConfig& config) { } }; - try { - // Validate file existence for authFile - if (config.authFile) { - if (!std::filesystem::exists(*config.authFile)) { - throw std::runtime_error("Authentication file does not exist: " + - *config.authFile); - } - } - // Validate URL format for authUrl - if (config.authUrl) { - // Breakdown of the regex pattern: - // 1. ^https?:// -> Start with http:// or https:// - // 2. (?: -> Start Host Group - // \[[a-fA-F0-9:]+\] -> Branch A: IPv6 (Must be in brackets like - // [::1]) - // -> Note: No \b used here because ']' is a - // non-word char - // | -> OR - // (?: -> Branch B: Alphanumeric Hosts (Group for - // \b check) - // (?:\d{1,3}\.){3}\d{1,3} -> IPv4 (e.g., 127.0.0.1) - // | -> OR - // localhost -> Localhost - // | -> OR - // (?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6} -> - // Domain - // )\b -> End Branch B + Word Boundary (Prevents - // "localhostX") - // ) -> End Host Group - // 3. (?::\d+)? -> Optional Port (e.g., :8080) - // 4. (?:...)*$ -> Optional Path/Query params + End of - // string - static const std::regex URL_PATTERN( - R"(^https?://(?:\[[a-fA-F0-9:]+\]|(?:(?:\d{1,3}\.){3}\d{1,3}|localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b)(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)", - std::regex::optimize); - if (!std::regex_match(*config.authUrl, URL_PATTERN)) { - throw std::runtime_error("Invalid URL format: " + *config.authUrl); - } + // Validate file existence for authFile + if (config.authFile) { + if (!std::filesystem::exists(*config.authFile)) { + throw std::runtime_error("Authentication file does not exist: " + + *config.authFile); } - - // Set session parameters - setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); - setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); - setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); - setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); - setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); - setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); - setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); - setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); - setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); - setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); - setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); - - // Initialize the session - qdmi::throwIfError(QDMI_session_init(session_), "Initializing session"); - } catch (...) { - cleanup(); - throw; } -} - -Session::~Session() { - if (session_ != nullptr) { - QDMI_session_free(session_); + // Validate URL format for authUrl + if (config.authUrl) { + // Breakdown of the regex pattern: + // 1. ^https?:// -> Start with http:// or https:// + // 2. (?: -> Start Host Group + // \[[a-fA-F0-9:]+\] -> Branch A: IPv6 (Must be in brackets like + // [::1]) + // -> Note: No \b used here because ']' is a + // non-word char + // | -> OR + // (?: -> Branch B: Alphanumeric Hosts (Group for + // \b check) + // (?:\d{1,3}\.){3}\d{1,3} -> IPv4 (e.g., 127.0.0.1) + // | -> OR + // localhost -> Localhost + // | -> OR + // (?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6} -> + // Domain + // )\b -> End Branch B + Word Boundary (Prevents + // "localhostX") + // ) -> End Host Group + // 3. (?::\d+)? -> Optional Port (e.g., :8080) + // 4. (?:...)*$ -> Optional Path/Query params + End of + // string + static const std::regex URL_PATTERN( + R"(^https?://(?:\[[a-fA-F0-9:]+\]|(?:(?:\d{1,3}\.){3}\d{1,3}|localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b)(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)", + std::regex::optimize); + if (!std::regex_match(*config.authUrl, URL_PATTERN)) { + throw std::runtime_error("Invalid URL format: " + *config.authUrl); + } } -} -Session::Session(Session&& other) noexcept : session_(other.session_) { - other.session_ = nullptr; -} + // Set session parameters + setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); + setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); + setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); -Session& Session::operator=(Session&& other) noexcept { - if (this != &other) { - if (session_ != nullptr) { - QDMI_session_free(session_); - } - session_ = other.session_; - other.session_ = nullptr; - } - return *this; + // Initialize the session + qdmi::throwIfError(QDMI_session_init(session_.get()), "Initializing session"); } -auto Session::getDevices() -> std::vector { - +std::vector Session::getDevices() { const auto& qdmiDevices = queryProperty>(QDMI_SESSION_PROPERTY_DEVICES); std::vector devices; devices.reserve(qdmiDevices.size()); std::ranges::transform( qdmiDevices, std::back_inserter(devices), - [](const QDMI_Device& dev) -> Device { return {Token{}, dev}; }); + [](const QDMI_Device& dev) -> Device { return Device(dev); }); return devices; } } // namespace fomac diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index e0b63f17b0..7b018582fd 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -1541,7 +1541,7 @@ void QuantumComputation::measure(const Targets& qubits, emplace_back(qubits, bits); } -void QuantumComputation::measureAll(const bool addBits) { +void QuantumComputation::measureAll(const bool addBits, const bool addBarrier) { if (addBits) { addClassicalRegister(getNqubits(), "meas"); } @@ -1553,7 +1553,9 @@ void QuantumComputation::measureAll(const bool addBits) { throw std::runtime_error(ss.str()); } - barrier(); + if (addBarrier) { + barrier(); + } Qubit start = 0U; if (addBits) { start = static_cast(classicalRegisters.at("meas").getStartIndex()); diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index be3dfd4f7a..474035413f 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -43,8 +43,8 @@ namespace { * @param sites is a vector of Session sites * @return the extent covering all given sites */ -auto calculateExtentFromSites( - const std::vector& sites) -> Device::Region { +auto calculateExtentFromSites(const std::vector& sites) + -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); auto minY = std::numeric_limits::max(); @@ -68,8 +68,7 @@ auto calculateExtentFromSites( * @return the extent covering all sites in the pairs */ auto calculateExtentFromSites( - const std::vector>& sitePairs) + const std::vector>& sitePairs) -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); @@ -125,7 +124,7 @@ auto Session::Device::initQubitsNumFromDevice() -> void { numQubits = getQubitsNum(); } auto Session::Device::initLengthUnitFromDevice() -> bool { - const auto& u = fomac::Session::Device::getLengthUnit(); + const auto& u = fomac::Device::getLengthUnit(); if (!u.has_value()) { SPDLOG_INFO("Length unit not set"); return false; @@ -135,7 +134,7 @@ auto Session::Device::initLengthUnitFromDevice() -> bool { return true; } auto Session::Device::initDurationUnitFromDevice() -> bool { - const auto& u = fomac::Session::Device::getDurationUnit(); + const auto& u = fomac::Device::getDurationUnit(); if (!u.has_value()) { SPDLOG_INFO("Duration unit not set"); return false; @@ -298,7 +297,7 @@ auto Session::Device::initTrapsfromDevice() -> bool { auto Session::Device::initOperationsFromDevice() -> bool { std::map>> shuttlingUnitsPerId; - for (const fomac::Session::Device::Operation& op : getOperations()) { + for (const fomac::Operation& op : getOperations()) { const auto zoned = op.isZoned(); const auto& nq = op.getQubitsNum(); const auto& opName = op.getName(); @@ -308,10 +307,9 @@ auto Session::Device::initOperationsFromDevice() -> bool { return false; } if (zoned) { - if (std::ranges::any_of( - *sitesOpt, [](const fomac::Session::Device::Site& site) -> bool { - return !site.isZone(); - })) { + if (std::ranges::any_of(*sitesOpt, [](const fomac::Site& site) -> bool { + return !site.isZone(); + })) { SPDLOG_INFO("Operation marked as zoned but has non-zone sites"); return false; } @@ -577,8 +575,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { } return true; } -Session::Device::Device(const fomac::Session::Device& device) - : fomac::Session::Device(device) {} + auto Session::getDevices() -> std::vector { std::vector devices; fomac::Session session; diff --git a/src/qasm3/Parser.cpp b/src/qasm3/Parser.cpp index 04ae35473b..ce45d9f1f2 100644 --- a/src/qasm3/Parser.cpp +++ b/src/qasm3/Parser.cpp @@ -168,7 +168,11 @@ std::shared_ptr Parser::parseStatement() { if (current().kind == Token::Kind::Const) { scan(); - return parseDeclaration(true); + return parseDeclaration(true, false); + } + if (current().kind == Token::Kind::Output) { + scan(); + return parseDeclaration(false, true); } if (current().kind == Token::Kind::Int || @@ -181,7 +185,7 @@ std::shared_ptr Parser::parseStatement() { current().kind == Token::Kind::Duration || current().kind == Token::Kind::CReg || current().kind == Token::Kind::Qreg) { - return parseDeclaration(false); + return parseDeclaration(false, false); } if (current().kind == Token::Kind::InitialLayout) { @@ -558,7 +562,8 @@ std::shared_ptr Parser::parseGateOperand() { return std::make_shared(parseIndexedIdentifier()); } -std::shared_ptr Parser::parseDeclaration(bool isConst) { +std::shared_ptr Parser::parseDeclaration(bool isConst, + bool isOutput) { auto const tBegin = current(); auto [type, isOldStyleDeclaration] = parseType(); Token const identifier = expect(Token::Kind::Identifier); @@ -588,7 +593,7 @@ std::shared_ptr Parser::parseDeclaration(bool isConst) { auto const tEnd = expect(Token::Kind::Semicolon); auto statement = std::make_shared(DeclarationStatement{ - makeDebugInfo(tBegin, tEnd), isConst, type, name, expression}); + makeDebugInfo(tBegin, tEnd), isConst, isOutput, type, name, expression}); return statement; } diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index d30de4973e..785a8c5460 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -483,9 +483,13 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling() }, program_); auto jitSession = qir::JitSession(irBytes, "QDMI job"); + runtime.outputProgramHeader(); for (size_t i = 0; i < numShots_; ++i) { runtime.reset(); - if (const auto rc = jitSession.run(); rc != 0) { + runtime.outputShotStart(); + const auto rc = jitSession.run(); + runtime.outputShotEnd(); + if (rc != 0) { throw std::runtime_error( llvm::formatv("QIR program failed with error: {}", rc)); } diff --git a/src/qdmi/driver/Driver.cpp b/src/qdmi/driver/Driver.cpp index c2c1dba036..18b85434c7 100644 --- a/src/qdmi/driver/Driver.cpp +++ b/src/qdmi/driver/Driver.cpp @@ -275,6 +275,16 @@ namespace { return QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT; case QDMI_JOB_PARAMETER_SHOTSNUM: return QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM; + case QDMI_JOB_PARAMETER_CUSTOM1: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM1; + case QDMI_JOB_PARAMETER_CUSTOM2: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM2; + case QDMI_JOB_PARAMETER_CUSTOM3: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM3; + case QDMI_JOB_PARAMETER_CUSTOM4: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM4; + case QDMI_JOB_PARAMETER_CUSTOM5: + return QDMI_DEVICE_JOB_PARAMETER_CUSTOM5; default: return QDMI_DEVICE_JOB_PARAMETER_MAX; } @@ -286,7 +296,8 @@ QDMI_Job_impl_d::~QDMI_Job_impl_d() { } auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size, const void* value) const -> int { - if ((value != nullptr && size == 0) || param >= QDMI_JOB_PARAMETER_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(param, QDMI_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return device_->getLibrary().device_job_set_parameter( diff --git a/src/qir/jit/Session.cpp b/src/qir/jit/Session.cpp index db04ffd48f..947f3758ea 100644 --- a/src/qir/jit/Session.cpp +++ b/src/qir/jit/Session.cpp @@ -12,6 +12,7 @@ #include "qir/jit/IRRewriter.hpp" #include "qir/runtime/QIR.h" +#include "qir/runtime/Runtime.hpp" #include #include @@ -44,6 +45,7 @@ #include #include +#include #include #include #include @@ -57,6 +59,28 @@ namespace qir { +/// Returns the function marked with the `entry_point` attribute, +/// or @c nullptr if no such function is defined. +static const llvm::Function* getEntryPointFunction(const llvm::Module& m) { + const auto it = std::ranges::find_if(m, [](const llvm::Function& fn) { + return fn.hasFnAttribute("entry_point"); + }); + return it != m.end() ? &*it : nullptr; +} + +/// Read the `output_labeling_schema` attribute from the program's entry point. +/// Returns @c Labeled when the attribute is absent or its value is not +/// exactly `ordered` (spec default). +static Runtime::OutputSchema readOutputSchema(const llvm::Module& m) { + if (const auto* fn = getEntryPointFunction(m); fn != nullptr) { + if (const auto attr = fn->getFnAttribute("output_labeling_schema"); + attr.isValid() && attr.getValueAsString() == "ordered") { + return Runtime::OutputSchema::Ordered; + } + } + return Runtime::OutputSchema::Labeled; +} + static void exitOnLazyCallThroughFailure() { exit(1); } static int mingwNoopMain() { @@ -239,6 +263,11 @@ void JitSession::initialize( } module_ = std::move(*llvmModule); + // Configure the runtime's labeling schema from the program's entry point. + module_.withModuleDo([](const llvm::Module& m) { + Runtime::getInstance().setOutputSchema(readOutputSchema(m)); + }); + // In StateExtraction mode, strip QIR measurement and result-management calls // so the runtime's quantum state remains intact after main returns. if (mode == Execution::StateExtraction) { diff --git a/src/qir/runner/CMakeLists.txt b/src/qir/runner/CMakeLists.txt index 4577bbfa0a..f52b85bbfb 100644 --- a/src/qir/runner/CMakeLists.txt +++ b/src/qir/runner/CMakeLists.txt @@ -12,7 +12,7 @@ if(NOT TARGET ${TARGET_NAME}) add_llvm_tool(${TARGET_NAME} Runner.cpp DEPENDS intrinsics_gen EXPORT_SYMBOLS) # Add link libraries - target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT) + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQIRJIT MQT::CoreQIRRuntime) # Set versioning information set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} EXPORT_NAME diff --git a/src/qir/runner/Runner.cpp b/src/qir/runner/Runner.cpp index 4ef8fc200e..b6626cec41 100644 --- a/src/qir/runner/Runner.cpp +++ b/src/qir/runner/Runner.cpp @@ -9,6 +9,7 @@ */ #include "qir/jit/Session.hpp" +#include "qir/runtime/Runtime.hpp" #include #include @@ -42,7 +43,12 @@ auto main(int argc, char* argv[]) -> int { try { auto jitSession = qir::JitSession(llvm::StringRef(InputFile)); - return jitSession.run(InputArgv, InputFile); + auto& runtime = qir::Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + const auto rc = jitSession.run(InputArgv, InputFile); + runtime.outputShotEnd(); + return rc; } catch (const std::exception& e) { ExitOnError(llvm::createStringError(e.what())); } diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index 6f225d4f5f..1d756a063d 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include extern "C" { @@ -384,37 +382,30 @@ bool __quantum__rt__read_result(Result* result) { void __quantum__rt__result_record_output(Result* result, const char* label) { const bool bit = __quantum__rt__read_result(result); auto& runtime = qir::Runtime::getInstance(); - runtime.outputValue(bit ? "1" : "0", label); + runtime.outputResult(bit, label); // Accumulate new measurement bit. runtime.appendMeasurementBit(bit); } void __quantum__rt__bool_record_output(bool value, const char* label) { - qir::Runtime::getInstance().outputValue(value ? "1" : "0", label); + qir::Runtime::getInstance().outputBool(value, label); } void __quantum__rt__int_record_output(int64_t value, const char* label) { - qir::Runtime::getInstance().outputValue(std::to_string(value), label); + qir::Runtime::getInstance().outputInt(value, label); } void __quantum__rt__float_record_output(double value, const char* label) { - // Use std::ostringstream rather than std::to_string. - // std::to_string formats with six digits after the decimal point and - // can print 0.000000 for very small numbers. - // std::ostringstream uses six significant digits by default and - // outputs very small numbers with scientific notation. - std::ostringstream oss; - oss << value; - qir::Runtime::getInstance().outputValue(oss.str(), label); + qir::Runtime::getInstance().outputFloat(value, label); } void __quantum__rt__tuple_record_output(int64_t elementCount, const char* label) { - qir::Runtime::getInstance().outputContainer(elementCount, label); + qir::Runtime::getInstance().outputTuple(elementCount, label); } void __quantum__rt__array_record_output(int64_t size, const char* label) { - qir::Runtime::getInstance().outputContainer(size, label); + qir::Runtime::getInstance().outputArray(size, label); } } // extern "C" diff --git a/src/qir/runtime/Runtime.cpp b/src/qir/runtime/Runtime.cpp index 6c998b75ac..8b2555b2a9 100644 --- a/src/qir/runtime/Runtime.cpp +++ b/src/qir/runtime/Runtime.cpp @@ -179,20 +179,76 @@ auto Runtime::takeState() -> QState { return ret; } -auto Runtime::outputContainer(int64_t /* elementCount */, - const char* label) const -> void { - *os << (label != nullptr ? label : "") << ":\n"; +auto Runtime::getOstream() const -> std::ostream& { return *os; } + +auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } + +auto Runtime::resetOstream() -> void { os = &std::cout; } + +void Runtime::outputType(const char* type, std::string_view value, + const char* label) const { + *os << "OUTPUT\t" << type << "\t" << value; + if (label != nullptr && outputSchema == OutputSchema::Labeled) { + *os << "\t" << label; + } + *os << "\n"; +} + +auto Runtime::outputResult(bool value, const char* label) const -> void { + outputType("RESULT", value ? "1" : "0", label); +} + +auto Runtime::outputBool(bool value, const char* label) const -> void { + outputType("BOOL", value ? "true" : "false", label); +} + +auto Runtime::outputInt(int64_t value, const char* label) const -> void { + outputType("INT", std::to_string(value), label); +} + +auto Runtime::outputFloat(double value, const char* label) const -> void { + // Use std::ostringstream rather than std::to_string. + // std::to_string formats with six digits after the decimal point and + // can print 0.000000 for very small numbers. + // std::ostringstream uses six significant digits by default and + // outputs very small numbers with scientific notation. + std::ostringstream oss; + oss << value; + outputType("DOUBLE", oss.str(), label); } -auto Runtime::outputValue(std::string_view valueStr, const char* label) const +auto Runtime::outputTuple(int64_t elementCount, const char* label) const -> void { - *os << (label != nullptr ? label : "") << ": " << valueStr << "\n"; + outputType("TUPLE", std::to_string(elementCount), label); } -auto Runtime::getOstream() const -> std::ostream& { return *os; } +auto Runtime::outputArray(int64_t elementCount, const char* label) const + -> void { + outputType("ARRAY", std::to_string(elementCount), label); +} -auto Runtime::setOstream(std::ostream& other) -> void { os = &other; } +auto Runtime::outputProgramHeader() const -> void { + *os << "HEADER\tschema_id\t" << outputSchema << "\n"; + *os << "HEADER\tschema_version\t2.1\n"; +} -auto Runtime::resetOstream() -> void { os = &std::cout; } +auto Runtime::outputShotStart() const -> void { + *os << "START\n"; + *os << "METADATA\toutput_labeling_schema\t" << outputSchema << "\n"; +} + +auto Runtime::outputShotEnd() const -> void { *os << "END\t0\n"; } + +auto Runtime::getOutputSchema() const -> OutputSchema { return outputSchema; } + +auto Runtime::setOutputSchema(OutputSchema schema) -> void { + outputSchema = schema; +} + +auto operator<<(std::ostream& os, const Runtime::OutputSchema schema) + -> std::ostream& { + return os << (schema == Runtime::OutputSchema::Labeled ? "labeled" + : "ordered"); +} } // namespace qir diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index a3166e5f67..57a1229a78 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -32,35 +33,35 @@ namespace fomac { namespace { -class DeviceTest : public testing::TestWithParam { +class DeviceTest : public testing::TestWithParam { protected: - Session::Device device; + Device device; DeviceTest() : device(GetParam()) {} }; class SiteTest : public DeviceTest { protected: - std::vector sites; + std::vector sites; void SetUp() override { sites = device.getSites(); } }; class OperationTest : public DeviceTest { protected: - std::vector operations; + std::vector operations; void SetUp() override { operations = device.getOperations(); } }; class DDSimulatorDeviceTest : public testing::Test { protected: - Session::Device device; + Device device; DDSimulatorDeviceTest() : device(getDDSimulatorDevice()) {} private: - static auto getDDSimulatorDevice() -> Session::Device { + static auto getDDSimulatorDevice() -> Device { Session session; for (const auto& dev : session.getDevices()) { if (dev.getName() == "MQT Core DDSIM QDMI Device") { @@ -73,11 +74,11 @@ class DDSimulatorDeviceTest : public testing::Test { class JobTest : public DDSimulatorDeviceTest { protected: - Session::Job job; + Job job; JobTest() : job(createTestJob()) {} - [[nodiscard]] Session::Job createTestJob() const { + [[nodiscard]] Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[1] q; @@ -91,11 +92,11 @@ c[0] = measure q[0]; class SimulatorJobTest : public DDSimulatorDeviceTest { protected: - Session::Job job; + Job job; SimulatorJobTest() : job(createTestJob()) {} - [[nodiscard]] Session::Job createTestJob() const { + [[nodiscard]] Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[2] q; @@ -556,6 +557,49 @@ c = measure q;)"; EXPECT_EQ(job.check(), QDMI_JOB_STATUS_DONE); } +TEST_F(DDSimulatorDeviceTest, SubmitJobCustomSupportedTypes) { + constexpr auto qasm3Program = "OPENQASM 3.0;"; + + auto submitWithCustoms = [&](auto custom, const size_t which) { + try { + switch (which) { + case 1: + device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, custom); + break; + case 2: + device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, + std::nullopt, custom); + break; + case 3: + device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, + std::nullopt, std::nullopt, custom); + break; + case 4: + device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, + std::nullopt, std::nullopt, std::nullopt, custom); + break; + case 5: + device.submitJob(qasm3Program, QDMI_PROGRAM_FORMAT_QASM3, 10, + std::nullopt, std::nullopt, std::nullopt, std::nullopt, + custom); + break; + default: + throw std::invalid_argument("Invalid 'which' value"); + } + } catch (const std::runtime_error& e) { + const std::string errorMsg(e.what()); + EXPECT_TRUE(errorMsg.find("Setting custom parameter") != + std::string::npos); + } + }; + for (size_t i = 1; i <= 5; ++i) { + submitWithCustoms(std::string("custom"), i); + submitWithCustoms(42, i); + submitWithCustoms(3.14, i); + submitWithCustoms(true, i); + } +} + TEST_F(DDSimulatorDeviceTest, SubmitJobPreservesNumShots) { const std::string qasm3Program = R"( OPENQASM 3.0; @@ -993,7 +1037,7 @@ TEST(AuthenticationTest, SessionMultipleInstances) { namespace { // Helper function to get all devices for parameterized tests -auto getDevices() -> std::vector { +auto getDevices() -> std::vector { Session session; return session.getDevices(); } @@ -1006,7 +1050,7 @@ INSTANTIATE_TEST_SUITE_P( DeviceTest, // Parameters to test with testing::ValuesIn(getDevices()), - [](const testing::TestParamInfo& paramInfo) { + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -1020,7 +1064,7 @@ INSTANTIATE_TEST_SUITE_P( SiteTest, // Parameters to test with testing::ValuesIn(getDevices()), - [](const testing::TestParamInfo& paramInfo) { + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -1034,7 +1078,7 @@ INSTANTIATE_TEST_SUITE_P( OperationTest, // Parameters to test with testing::ValuesIn(getDevices()), - [](const testing::TestParamInfo& paramInfo) { + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index e53a04799b..42c1264ea6 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -421,6 +421,20 @@ def test_device_submit_job_returns_valid_job(ddsim_device: Device) -> None: assert job.num_shots == 100 +def test_device_submit_job_handles_custom_parameters(ddsim_device: Device) -> None: + """Test that submit_job forwards custom job parameters to DDSIM.""" + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom1="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom2="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom3="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom4="value") + with pytest.raises(RuntimeError, match=r"Setting custom parameter: Not supported\."): + ddsim_device.submit_job("OPENQASM 3.0;", ProgramFormat.QASM3, 1, custom5="value") + + def test_device_submit_job_preserves_num_shots(ddsim_device: Device) -> None: """Test that different shot counts are correctly preserved.""" qasm3_program = """ diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index 42b876e5c1..79b2d89a84 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -168,6 +168,14 @@ TEST_P(DriverJobTest, JobSetParameter) { EXPECT_THAT(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(size_t), &numShots), testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + constexpr std::array customParams{ + QDMI_JOB_PARAMETER_CUSTOM1, QDMI_JOB_PARAMETER_CUSTOM2, + QDMI_JOB_PARAMETER_CUSTOM3, QDMI_JOB_PARAMETER_CUSTOM4, + QDMI_JOB_PARAMETER_CUSTOM5}; + for (const auto param : customParams) { + EXPECT_THAT(QDMI_job_set_parameter(job, param, 0, nullptr), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + } EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_MAX, 0, nullptr), QDMI_ERROR_INVALIDARGUMENT); } diff --git a/test/qir/jit/test_jit_session.cpp b/test/qir/jit/test_jit_session.cpp index 3c614dd18f..8a7a362dda 100644 --- a/test/qir/jit/test_jit_session.cpp +++ b/test/qir/jit/test_jit_session.cpp @@ -30,7 +30,11 @@ class JitSessionTest : public testing::Test { runtime.reset(); runtime.setOstream(sink); } - void TearDown() override { qir::Runtime::getInstance().resetOstream(); } + void TearDown() override { + auto& runtime = qir::Runtime::getInstance(); + runtime.resetOstream(); + runtime.setOutputSchema(qir::Runtime::OutputSchema::Labeled); + } }; TEST_F(JitSessionTest, LoadModuleFromMemory) { @@ -55,6 +59,39 @@ TEST_F(JitSessionTest, StateExtractionLeavesNoRecordedOutputs) { EXPECT_TRUE(qir::Runtime::getInstance().getMeasurements().empty()); } +TEST_F(JitSessionTest, OutputSchemaDefaultsToLabeledWhenAttributeAbsent) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" } +)"; + // Preset @c Ordered so we can tell the default kicked in. + qir::Runtime::getInstance().setOutputSchema( + qir::Runtime::OutputSchema::Ordered); + const qir::JitSession session(ir, "NoOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Labeled); +} + +TEST_F(JitSessionTest, OutputSchemaFromLabeledAttribute) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" "output_labeling_schema"="labeled" } +)"; + const qir::JitSession session(ir, "LabeledOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Labeled); +} + +TEST_F(JitSessionTest, OutputSchemaFromOrderedAttribute) { + constexpr std::string_view ir = R"( +define i32 @main() #0 { ret i32 0 } +attributes #0 = { "entry_point" "output_labeling_schema"="ordered" } +)"; + const qir::JitSession session(ir, "OrderedOutputSchema.ll"); + EXPECT_EQ(qir::Runtime::getInstance().getOutputSchema(), + qir::Runtime::OutputSchema::Ordered); +} + TEST(JitSessionErrors, MalformedIRThrows) { constexpr std::string_view ir = R"(define i32 @main() {})"; EXPECT_THROW(qir::JitSession(ir, "MalformedIR.ll"), std::runtime_error); diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index 4a08b0030b..2987e0870e 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -36,11 +37,89 @@ class QIRRuntimeTest : public testing::Test { protected: std::ostringstream sink; void SetUp() override { Runtime::getInstance().setOstream(sink); } - void TearDown() override { Runtime::getInstance().resetOstream(); } + void TearDown() override { + Runtime::getInstance().resetOstream(); + Runtime::getInstance().setOutputSchema(Runtime::OutputSchema::Labeled); + } }; } // namespace +// Any test that emits output relies on the runtime producing the spec-mandated +// HEADER/START/METADATA/END records around the per-shot OUTPUT block. +// The runtime picks @c Labeled as default output schema, which is why the +// the framing emits `labeled` in both HEADER and METADATA here. +TEST_F(QIRRuntimeTest, OutputFraming) { + auto& runtime = Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tlabeled\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tlabeled\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + +// In Labeled mode: +// - the HEADER announces `labeled`, +// - the per-shot METADATA line matches the output schema, and +// - OUTPUT records carry the label column. +TEST_F(QIRRuntimeTest, OutputFramingLabeled) { + auto& runtime = Runtime::getInstance(); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputBool(true, "bool_label"); + runtime.outputInt(42, "int_label"); + runtime.outputFloat(3.14, "double_label"); + runtime.outputTuple(2, "tuple_label"); + runtime.outputArray(3, "array_label"); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tlabeled\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tlabeled\n" + << "OUTPUT\tBOOL\ttrue\tbool_label\n" + << "OUTPUT\tINT\t42\tint_label\n" + << "OUTPUT\tDOUBLE\t3.14\tdouble_label\n" + << "OUTPUT\tTUPLE\t2\ttuple_label\n" + << "OUTPUT\tARRAY\t3\tarray_label\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + +// In Ordered mode: +// - the HEADER announces `ordered`, +// - the per-shot METADATA line matches the output schema, and +// - OUTPUT records drop the label column. +TEST_F(QIRRuntimeTest, OutputFramingOrdered) { + auto& runtime = Runtime::getInstance(); + runtime.setOutputSchema(Runtime::OutputSchema::Ordered); + runtime.outputProgramHeader(); + runtime.outputShotStart(); + runtime.outputBool(true, "bool_label"); + runtime.outputInt(42, "int_label"); + runtime.outputFloat(3.14, "double_label"); + runtime.outputTuple(2, "tuple_label"); + runtime.outputArray(3, "array_label"); + runtime.outputShotEnd(); + std::ostringstream expected; + expected << "HEADER\tschema_id\tordered\n" + << "HEADER\tschema_version\t2.1\n" + << "START\n" + << "METADATA\toutput_labeling_schema\tordered\n" + << "OUTPUT\tBOOL\ttrue\n" + << "OUTPUT\tINT\t42\n" + << "OUTPUT\tDOUBLE\t3.14\n" + << "OUTPUT\tTUPLE\t2\n" + << "OUTPUT\tARRAY\t3\n" + << "END\t0\n"; + EXPECT_EQ(sink.str(), expected.str()); +} + TEST_F(QIRRuntimeTest, XGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); @@ -256,13 +335,19 @@ TEST_F(QIRRuntimeTest, SwapGate) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__x__body(q0); __quantum__qis__swap__body(q0, q1); __quantum__qis__mz__body(q0, r0); __quantum__qis__mz__body(q1, r1); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_EQ(sink.str(), "r0: 0\nr1: 1\n"); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t0\tr0\n" + << "OUTPUT\tRESULT\t1\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, CSwapGate) { @@ -356,6 +441,8 @@ TEST_F(QIRRuntimeTest, BellPairStatic) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__h__body(q0); __quantum__qis__cx__body(q0, q1); __quantum__qis__mz__body(q0, r0); @@ -365,11 +452,17 @@ TEST_F(QIRRuntimeTest, BellPairStatic) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, BellPairDynamic) { __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); auto* q0 = __quantum__rt__qubit_allocate(); auto* q1 = __quantum__rt__qubit_allocate(); __quantum__qis__h__body(q0); @@ -383,7 +476,11 @@ TEST_F(QIRRuntimeTest, BellPairDynamic) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -394,6 +491,8 @@ TEST_F(QIRRuntimeTest, BellPairStaticReverse) { auto* r0 = reinterpret_cast(0UL); auto* r1 = reinterpret_cast(1UL); __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); __quantum__qis__h__body(q1); __quantum__qis__cx__body(q1, q0); __quantum__qis__mz__body(q0, r0); @@ -403,11 +502,17 @@ TEST_F(QIRRuntimeTest, BellPairStaticReverse) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); } TEST_F(QIRRuntimeTest, BellPairDynamicReverse) { __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); auto* q0 = __quantum__rt__qubit_allocate(); auto* q1 = __quantum__rt__qubit_allocate(); __quantum__qis__h__body(q1); @@ -421,7 +526,11 @@ TEST_F(QIRRuntimeTest, BellPairDynamicReverse) { EXPECT_EQ(m1, m2); __quantum__rt__result_record_output(r0, "r0"); __quantum__rt__result_record_output(r1, "r1"); - EXPECT_THAT(sink.str(), testing::AnyOf("r0: 0\nr1: 0\n", "r0: 1\nr1: 1\n")); + Runtime::getInstance().outputShotEnd(); + std::ostringstream expected; + expected << "OUTPUT\tRESULT\t" << m1 << "\tr0\n" + << "OUTPUT\tRESULT\t" << m2 << "\tr1\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); } @@ -530,6 +639,8 @@ TEST_F(QIRRuntimeTest, TakeStateReturnsStateAndResetsRuntime) { TEST_F(QIRRuntimeTest, AdaptiveRecordOutputs) { __quantum__rt__initialize(nullptr); + Runtime::getInstance().outputProgramHeader(); + Runtime::getInstance().outputShotStart(); auto* q0 = __quantum__rt__qubit_allocate(); auto* q1 = __quantum__rt__qubit_allocate(); auto* q2 = __quantum__rt__qubit_allocate(); @@ -553,22 +664,24 @@ TEST_F(QIRRuntimeTest, AdaptiveRecordOutputs) { // Output: tuple of 3 elements (array of 3 bools, int weight, float mean). __quantum__rt__tuple_record_output(3, "outputs"); - __quantum__rt__array_record_output(3, " measurements"); - __quantum__rt__bool_record_output(b0, " m0"); - __quantum__rt__bool_record_output(b1, " m1"); - __quantum__rt__bool_record_output(b2, " m2"); - __quantum__rt__int_record_output(weight, " hamming_weight"); - __quantum__rt__float_record_output(mean, " mean"); + __quantum__rt__array_record_output(3, "measurements"); + __quantum__rt__bool_record_output(b0, "m0"); + __quantum__rt__bool_record_output(b1, "m1"); + __quantum__rt__bool_record_output(b2, "m2"); + __quantum__rt__int_record_output(weight, "hamming_weight"); + __quantum__rt__float_record_output(mean, "mean"); + Runtime::getInstance().outputShotEnd(); std::ostringstream expected; - expected << "outputs:\n" - << " measurements:\n" - << " m0: " << b0 << "\n" - << " m1: " << b1 << "\n" - << " m2: " << b2 << "\n" - << " hamming_weight: " << weight << "\n" - << " mean: " << mean << "\n"; - EXPECT_EQ(sink.str(), expected.str()); + expected.setf(std::ios::boolalpha); + expected << "OUTPUT\tTUPLE\t3\toutputs\n" + << "OUTPUT\tARRAY\t3\tmeasurements\n" + << "OUTPUT\tBOOL\t" << b0 << "\tm0\n" + << "OUTPUT\tBOOL\t" << b1 << "\tm1\n" + << "OUTPUT\tBOOL\t" << b2 << "\tm2\n" + << "OUTPUT\tINT\t" << weight << "\thamming_weight\n" + << "OUTPUT\tDOUBLE\t" << mean << "\tmean\n"; + EXPECT_THAT(sink.str(), ::testing::HasSubstr(expected.str())); __quantum__rt__result_update_reference_count(r0, -1); __quantum__rt__result_update_reference_count(r1, -1); diff --git a/uv.lock b/uv.lock index 1107582cdf..0dcee73873 100644 --- a/uv.lock +++ b/uv.lock @@ -79,11 +79,11 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.1" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, ] [[package]] @@ -142,189 +142,199 @@ wheels = [ [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, - { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, - { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, - { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, - { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, - { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] @@ -538,100 +548,100 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/97/c52dc440c390b6cfa87be9432b141a956e2d56d9b9f5fc8bd71c5f471722/coverage-7.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50913d4bf5ddafa6ca3693da5e4dd833dd1b772e0283c99ca7f7d287db67331a", size = 220539, upload-time = "2026-07-02T13:08:19.252Z" }, - { url = "https://files.pythonhosted.org/packages/3f/26/602de8c2aec7e2e3e99ebfb8e04ba65598f746275396eea5f6794ff4673f/coverage-7.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:359e141ccd33893ce3f1ad5525f8b96083003677c82182e5907d62d4ea5799fc", size = 221058, upload-time = "2026-07-02T13:08:21.013Z" }, - { url = "https://files.pythonhosted.org/packages/fc/13/ebab0743138891c1d646d61e247ec29639afcbb6c4e1905e6a0f0c75291a/coverage-7.15.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3200b6204935f928c64b2ca1f923ab8c1acb7c9de45ec61569711b34d25cccaf", size = 247797, upload-time = "2026-07-02T13:08:22.474Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/b6ffb9e042aa48dc4144a8a65529affaec8dca0685309353614a2a7386ad/coverage-7.15.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be616bf61346883b2cfdc5178669647e03531d81ab761a7e378558b7e8bcb628", size = 249626, upload-time = "2026-07-02T13:08:23.803Z" }, - { url = "https://files.pythonhosted.org/packages/9c/06/243ff05b652333d8e3d060c11223efc2723b19cacf6605e433fa686ab5d4/coverage-7.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7bafc3fe1059463a8fdd97ca79972d6e2bf819d775c7d54991b5b1971201d6", size = 251493, upload-time = "2026-07-02T13:08:25.397Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2b/867faa17030a806114dae388b32a3fa929d8cd4bf39226fbc11f6e6bb705/coverage-7.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b713aa7fcf325a01d4184d848acb46fd84f78fdb0978470c636b23a06a753d91", size = 253406, upload-time = "2026-07-02T13:08:26.842Z" }, - { url = "https://files.pythonhosted.org/packages/94/c0/d789ce18f6605afc4895db75723424be2ef494282f77f61d8e5832923183/coverage-7.15.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e38e6fba2d56652fdfaf0231f8f78aeb805234a912de25dc291ee5cce5b8faa4", size = 248512, upload-time = "2026-07-02T13:08:28.398Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b6/b2673c30739f4a2e06649a0a38ad8b093c4d865462dc7bab0e9524a2c3b1/coverage-7.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:884499f42e382675be80770391983b90e0c0c774d87dbeeebf5f991cf6612b20", size = 249532, upload-time = "2026-07-02T13:08:29.731Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/acd79e9a41beabee92b623afe4f30b549916f48566271475f2907e752828/coverage-7.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:840481b12e083dbcbafab14794a8781a958edf327c8d3d70b4eee42f9b8253aa", size = 247537, upload-time = "2026-07-02T13:08:31.173Z" }, - { url = "https://files.pythonhosted.org/packages/12/d4/2d301c4d1b3238d7c88b70ab9d13fd53ed9505662a7ff1b46ba1e2e4e3c3/coverage-7.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:276646e9481703d09f854f3b2f018f24e19fd7049ae670a92570043eb97203b1", size = 251348, upload-time = "2026-07-02T13:08:32.63Z" }, - { url = "https://files.pythonhosted.org/packages/35/bb/c67708b2bc00f32e12805ec23d5fa677a0a51652f449341a89f9d6b1b715/coverage-7.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4de4b4d3f5545aa6c60dc4efd9c63b5b5dcc3bf00fe83146b2bdfffb8f6613bd", size = 247806, upload-time = "2026-07-02T13:08:33.931Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6c/57c4f653c47a6e917748f8938e389e72fbcae44e3643cd906664f0477a13/coverage-7.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5c504097b2a89b1e85bc6070d920df77daec701337e3aeef2c17775a5dd0ca90", size = 248410, upload-time = "2026-07-02T13:08:35.189Z" }, - { url = "https://files.pythonhosted.org/packages/6c/94/bb083041aef828903668f134273f319f2bd49224962875359c52faa5497f/coverage-7.15.0-cp310-cp310-win32.whl", hash = "sha256:f6e80ed91f98316e86b9c137206b04b2bcfbffccbdff49bd2eb09dddb1cf14e0", size = 222588, upload-time = "2026-07-02T13:08:36.486Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/a09d8ee618956f626741b0734854bac4425a00e10c0565f5abca64e7e751/coverage-7.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3b3e22030f3f6f5e01a5ce69936552a5c0f6992b7698777377b99041961031f", size = 223214, upload-time = "2026-07-02T13:08:37.885Z" }, - { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, - { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, - { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, - { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, - { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, - { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, - { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, - { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, - { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, - { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, - { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, - { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, - { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, - { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, - { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, - { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, - { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, - { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, - { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, - { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, - { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, - { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, - { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, - { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, - { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, - { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, - { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, - { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, - { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, - { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, - { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, - { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, - { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, - { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, - { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, - { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, - { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, - { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, - { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, - { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, - { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, - { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, - { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, - { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, - { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, - { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, - { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, - { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +version = "7.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/83/6051c2a2feab48ae5bd27c84ef047191d2d4a3172f689e38eaa48ed17db1/coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67", size = 927640, upload-time = "2026-07-12T20:58:19.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/76/eef242d6bb95fb737fefd9bbf3a318897e4e82cf543b53a61c2a6e8588eb/coverage-7.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:05d87c2a43373ad6b976d0a99ad58c48633633bcdeb896dc645a006472cc4a71", size = 221195, upload-time = "2026-07-12T20:55:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/89/73/41cd50da59d513a30fd3a34b5516b962ac17514defdb6705948446a5c0b1/coverage-7.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2afce82f2cf8f4c9002746a42755e1dc61baff33d9f7ab5569b4c9101f8f4d1d", size = 221714, upload-time = "2026-07-12T20:55:58.104Z" }, + { url = "https://files.pythonhosted.org/packages/22/9d/6df6ac6677719a087c839208186525b7b1f3653c50196c43246482ada65e/coverage-7.15.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a545ef5384d787d0fcac6c349afc2c5f99dcc39e13ed3c191b2c06305f64c04", size = 248454, upload-time = "2026-07-12T20:55:59.336Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f5/86f92c429fbf942986d01c3bb7b0b6c3ad06b09f7fa745023877413dde83/coverage-7.15.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:afaa144b8f5b3bc69fe0ce50d401c46b01ab264782553bfd05a3f98804524ecb", size = 250282, upload-time = "2026-07-12T20:56:00.589Z" }, + { url = "https://files.pythonhosted.org/packages/47/c6/99d0f1c1dd1583aaf8c1deca447e44426be54a76027a8c55e29970f22b15/coverage-7.15.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b6099490e5f88569c46b18605f556c3b30acc9a0a219cf7ef8fab8f7161ec4", size = 252149, upload-time = "2026-07-12T20:56:01.853Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c3/5d30740ec96f5fd8a659e46b6ee9224197f87aa66ce4a20e93dcf46221ac/coverage-7.15.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb5f6c2cf1ffd0bf2bd925c7cdcae9b4f208e9696d453ed51eb1f5fa0cc5b45b", size = 254061, upload-time = "2026-07-12T20:56:03.277Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/3afb2950673ca6cd22bc9ad6a9d6058c853bef8fe27a6d1df3adc274fd88/coverage-7.15.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81572011fc1fc271317da35da593944daef7bfd507085e35751abbe702b74f69", size = 249152, upload-time = "2026-07-12T20:56:04.618Z" }, + { url = "https://files.pythonhosted.org/packages/ca/53/25ab3c0a7cf517ed4d30cd4dd82331e005d4b57fec8c19c0ffd94fb50baa/coverage-7.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f765d13c08497687d0780cca66115c6aa4ba6703ad43b61e94fab9db689e3a3", size = 250187, upload-time = "2026-07-12T20:56:06Z" }, + { url = "https://files.pythonhosted.org/packages/ad/2a/8afb5f994e26e919e5dca5164f1c7b6b7ce6090aab1aac32b1ae1782f047/coverage-7.15.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:771caba880fee96493d18dfc465c318e08ab74e3bc2a3e4089e52514be6a6e54", size = 248193, upload-time = "2026-07-12T20:56:07.376Z" }, + { url = "https://files.pythonhosted.org/packages/d4/62/b6616e15288c9a7b628f7745e832fd8af2c03ec1f7dc11da25947f5ab16f/coverage-7.15.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:433a73200848e80f27712fc113b6ff5311f29b479a7d3bd4b1106138a77f9674", size = 252004, upload-time = "2026-07-12T20:56:08.787Z" }, + { url = "https://files.pythonhosted.org/packages/9e/21/5d80d65e4df3018dedb23a44f276f20ce26e429ffd76df03de03bcf9a767/coverage-7.15.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5d6fa45079db9fbeba0a69e3d91189f05301d6ac918162a53179d32fc9ed4910", size = 248463, upload-time = "2026-07-12T20:56:10.245Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/04a22708691561c44d77e1d5a60857b351310fdfbf253533780bb31c8b6f/coverage-7.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:00b6703c6640075cdce5124e9335dfdf9167272475301828acfdd09c0e5ee731", size = 249064, upload-time = "2026-07-12T20:56:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/82/c9/a5c1e8954e657559de478acc1ef73e9393d3fc9b4a2ab4427501d765aca2/coverage-7.15.1-cp310-cp310-win32.whl", hash = "sha256:3ad9a0eac4728327fd870d52f74d2e631d176c5f178eaea2d9983ab5b9755a55", size = 223248, upload-time = "2026-07-12T20:56:13.068Z" }, + { url = "https://files.pythonhosted.org/packages/54/49/5f6566e14611a58e93a926f3a8a7b22e4e0702ebb4c467ac38f452c7f4cf/coverage-7.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:eb5fa75dc3d30e3a1b75da97973479b20ffa9b0641ff56d6e94b5f3e210daa54", size = 223874, upload-time = "2026-07-12T20:56:14.396Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/5095e07a0986930174fc153fd0bb115f7f2724f5344cc672e016d4f23a4e/coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0", size = 221320, upload-time = "2026-07-12T20:56:16.036Z" }, + { url = "https://files.pythonhosted.org/packages/43/7a/7e2598ce98433a214020a61c0755d5961f9f26df0c630dc73e06b86d3416/coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9", size = 221823, upload-time = "2026-07-12T20:56:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4f/67dac6a69139b490a239d91b6d5ef127982ffb89159769241656ab879ee5/coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea", size = 252242, upload-time = "2026-07-12T20:56:18.868Z" }, + { url = "https://files.pythonhosted.org/packages/05/37/5e439725a96de592309725550c8df639c359c209dcb4b152ed46032d4c0d/coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2", size = 254153, upload-time = "2026-07-12T20:56:20.118Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1c/671ba9e15f9651a99962fb588a1fe4fb267cae4bb85f9bb4ac4413c6f7ef/coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29", size = 256261, upload-time = "2026-07-12T20:56:21.682Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3c/88305322b4d015b4536b7174b8f9b87f850f01af9d89008cdbf984b65ff2/coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89", size = 258222, upload-time = "2026-07-12T20:56:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/8d/20/d02e42333a57f266ded61ed4fb3821da1f2dc143734aaa3dcc9c117204c5/coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d", size = 252368, upload-time = "2026-07-12T20:56:24.475Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d0/94ebc010926a191d83e83b1f1236f8bd0d14d0eb98b5c324aa4be2589a8e/coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c", size = 253953, upload-time = "2026-07-12T20:56:26.036Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2c8553191da0c2da2c43ff294fb9bab57a5b0fae03560304a82a8b5addc3/coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab", size = 252016, upload-time = "2026-07-12T20:56:27.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/20f47986d8360fa1a31d9858dd2ba0be009d44ecfa8d02120b1eb93c028b/coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358", size = 255783, upload-time = "2026-07-12T20:56:28.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/36/fb61983b5259f78fa5e62ee74e91fe4de4c556fcbc9a3b9c9021c2f8b57b/coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404", size = 251735, upload-time = "2026-07-12T20:56:30.465Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2e/d109d8d2d6c726ba2e78125297073918a2a91464f0ea777e5398fa3cf75c/coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34", size = 252645, upload-time = "2026-07-12T20:56:32.076Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/5b3219cbb46135e14673427f2bf5890c4da4e6f655243692f3448aa3f42c/coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7", size = 223414, upload-time = "2026-07-12T20:56:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/5b/80/24e13ade0b6df8090e2492f9c55044bf1423f7ef28c76fc1af8c827a61cb/coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b", size = 223888, upload-time = "2026-07-12T20:56:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9e/34659930ae380491af48e2f5f5f9a2e99cd9fb7de3d30f27be5ac5425137/coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c", size = 223435, upload-time = "2026-07-12T20:56:36.302Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/32c1826309beaf4604c54accef108fdd611e5e5e93f2f5192f050cd5f6bd/coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80", size = 221497, upload-time = "2026-07-12T20:56:37.628Z" }, + { url = "https://files.pythonhosted.org/packages/db/5c/b88ce0d68fa550c7f3b58617fbf363bce64df5bf8295a01b627e4696e022/coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189", size = 221854, upload-time = "2026-07-12T20:56:39.033Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/8509fd2a66fc4e0a829f76a0f0b1dc3cc163368352435b5f243168658077/coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615", size = 253359, upload-time = "2026-07-12T20:56:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/e5/81/c7009ed7ea9765adb2b9d095054d748266fae5f07ac6c5f925f33715fcde/coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3", size = 256096, upload-time = "2026-07-12T20:56:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/21/52/dc8ee03968a5ba86e2da5aa48ddc9e3747bd65d63825fdce2d96acb9c5ff/coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304", size = 257211, upload-time = "2026-07-12T20:56:43.513Z" }, + { url = "https://files.pythonhosted.org/packages/b8/27/95d7623908da8937deb53d48efcdbf423907a47540e63c62fa21372c652b/coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c", size = 259473, upload-time = "2026-07-12T20:56:44.974Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3b/730d761928de97d585465680b568ae69622fb40716babadeabffe75cb51b/coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b", size = 253759, upload-time = "2026-07-12T20:56:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fc/6b9277acff1f9484b6c12857af5774689d1a6a95e13265f7405329d2f5da/coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61", size = 255131, upload-time = "2026-07-12T20:56:48.073Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f2/c704f86129594ba34e25a64695d2068c71d51c2b98907184d716c94f4aec/coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e", size = 253275, upload-time = "2026-07-12T20:56:49.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/29/80fee8af47de4a6dce71ccf2938491f444687a756af258a56d8469b8f1b0/coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5", size = 257345, upload-time = "2026-07-12T20:56:51.038Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/a1e7d7ed1b48a8adf8fd5154d9e83fcc5ad8e6ff20ae00e44865057dce8d/coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d", size = 252844, upload-time = "2026-07-12T20:56:52.535Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8c/a4bc26e6ee207d412f3678f04d74be1550e83140563ca0e4997510579712/coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2", size = 254716, upload-time = "2026-07-12T20:56:53.968Z" }, + { url = "https://files.pythonhosted.org/packages/11/9d/8ad0266ecfada6353cf6627a1a02294cf55a907521b6ee0bd7b770cfd659/coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76", size = 223554, upload-time = "2026-07-12T20:56:55.583Z" }, + { url = "https://files.pythonhosted.org/packages/81/6d/24224929e06c6e05a93f738bc5f9e8e6ab658f8f1d9b823e7b85430e28b8/coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374", size = 224087, upload-time = "2026-07-12T20:56:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/35/23/f81441dd01de88e53c97842e706907b307d9078918c3f4998b11e9ac7250/coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a", size = 223472, upload-time = "2026-07-12T20:56:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1e/6fa289d7993a2a39f1b283ddb58c4bfec80f7800be654b8ba8a9f6a07c63/coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a", size = 221519, upload-time = "2026-07-12T20:57:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/0db4902a0588234a70ab0218073c0b20fbc5c740aa35f91d360160a2ebc9/coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b", size = 221895, upload-time = "2026-07-12T20:57:01.867Z" }, + { url = "https://files.pythonhosted.org/packages/b4/cb/3719783865092dac5e08df842730305ee9ab1973ae7ddb6fbdf27d401f30/coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e", size = 252882, upload-time = "2026-07-12T20:57:03.459Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5e/caf3abbdbb22629626160ffc9c017eb995b7cb11c0be46b974834cef1792/coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a", size = 255479, upload-time = "2026-07-12T20:57:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f1/d60f375bfe095fef944f0f19427aefdbf9bdd5a9571c41a4bf6e2f5fdb81/coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c", size = 256715, upload-time = "2026-07-12T20:57:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/d7/17/8b0cbc90d02dc5adad4d9034c1824ec3fa567771b4c39d9c1e3f9b1431b8/coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90", size = 258845, upload-time = "2026-07-12T20:57:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/c5e69f5fb75c322e9a3e4ef64d02eebfc3d66efceccc8514ff80a3c13a56/coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f", size = 253098, upload-time = "2026-07-12T20:57:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/57/21144252fdd0c01d707d48fbcea13a80b0b7c42ced3f299f885ab8978c3a/coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8", size = 254844, upload-time = "2026-07-12T20:57:11.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/2a/499a28a322b0ce6768328e6c5bb2e2ad00ac068a7c7adb2ecd8533c8c5d9/coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a", size = 252807, upload-time = "2026-07-12T20:57:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/928a95da5da8b60f2b00e1482c7787b3316188e6d2d227fb8e124ada43a1/coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31", size = 256965, upload-time = "2026-07-12T20:57:14.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/10/889adbc1b8c9f866ed51e18a98bcafc0259fb9d29b81f50a719407c64ea8/coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad", size = 252628, upload-time = "2026-07-12T20:57:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/1a/30/a5e1871e5d93416511f8e359d1ccebfe0cbb050a1bbf7dd20228533ec0cf/coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a", size = 254399, upload-time = "2026-07-12T20:57:17.703Z" }, + { url = "https://files.pythonhosted.org/packages/2d/26/c36fbffd549dadbdd1a75827528fb00a4c46aa3187b007b750b1e2cebbf2/coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e", size = 223564, upload-time = "2026-07-12T20:57:19.253Z" }, + { url = "https://files.pythonhosted.org/packages/16/fc/becbb9d2c4206d242b9b1e1e8e24a42f7926c0200dd3c788b9fab4bb96d5/coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7", size = 224106, upload-time = "2026-07-12T20:57:21.108Z" }, + { url = "https://files.pythonhosted.org/packages/d3/30/1cfc641461369b6858799fca61c0a8b5edc490c519bf7c636ffa6bbf556f/coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49", size = 223497, upload-time = "2026-07-12T20:57:22.734Z" }, + { url = "https://files.pythonhosted.org/packages/f0/46/81961952e7aebfb38ad0ae4264e8954cc607a7af9e7ac111f9fa986595cc/coverage-7.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8", size = 221560, upload-time = "2026-07-12T20:57:24.282Z" }, + { url = "https://files.pythonhosted.org/packages/13/d2/ee14d715889f216baf47301d9f469e08fff6995552aaf67e897b282865f6/coverage-7.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4", size = 221894, upload-time = "2026-07-12T20:57:25.87Z" }, + { url = "https://files.pythonhosted.org/packages/f3/38/f830bc6e6c2c5f23f43847125e6c650d378872f7eeba8d49f1d42193e8a9/coverage-7.15.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81", size = 252938, upload-time = "2026-07-12T20:57:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/e1/53/0d3dd963631259d794c898735d5436e68d6a8d40749c419a07ff7c171469/coverage-7.15.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca", size = 255445, upload-time = "2026-07-12T20:57:29.234Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fd/aabed228557565c958259251b89bab8c5669b31291fa63b3e2154ebb017a/coverage-7.15.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74", size = 256790, upload-time = "2026-07-12T20:57:30.826Z" }, + { url = "https://files.pythonhosted.org/packages/bc/aa/1cc888e5d3623e603c4e5399653cb25728bb2b40d7519188a3e293d24620/coverage-7.15.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047", size = 259104, upload-time = "2026-07-12T20:57:32.63Z" }, + { url = "https://files.pythonhosted.org/packages/5f/61/fc16d5f5e53098dae41efa21e8ccc611a9b4fe922750dd03dc56db552182/coverage-7.15.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9", size = 252956, upload-time = "2026-07-12T20:57:34.316Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f3/52384668c3de4519ca770bf1975a89e4d6eb5aa2faf0da0577a14008cba4/coverage-7.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652", size = 254797, upload-time = "2026-07-12T20:57:35.947Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/54b807e7c1868178e902fd8360b5d4e559394462f97285c50edf1c4608db/coverage-7.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4", size = 252762, upload-time = "2026-07-12T20:57:37.856Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/dde8adf0338e3ace738757dccf1ce817e5fdcadfae77e1b48a77e5a3b265/coverage-7.15.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c", size = 257037, upload-time = "2026-07-12T20:57:39.488Z" }, + { url = "https://files.pythonhosted.org/packages/07/f2/179dd88cf60a0aeeee16a970ffe250dccea8b80ed4beab4c5d3f6c41ad4b/coverage-7.15.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633", size = 252577, upload-time = "2026-07-12T20:57:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/2c/37/8a593d69ab521beb6a105a2017cac4ba94425ee0a8349e29c3c0b522d24f/coverage-7.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41", size = 254235, upload-time = "2026-07-12T20:57:43.025Z" }, + { url = "https://files.pythonhosted.org/packages/6d/34/bc9b3bced66f2cdad4bf5e57ae51c54ea226e8aaaebfc9370a9a11877bf3/coverage-7.15.1-cp314-cp314-win32.whl", hash = "sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b", size = 223771, upload-time = "2026-07-12T20:57:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/4d20337bed61915d14349e62b88d5e4144d5a9872b64adbe90e9906db6db/coverage-7.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a", size = 224257, upload-time = "2026-07-12T20:57:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/7b/df/bbfeae4948f3ded516f92b32f2d57952427fc5ecfc0924487bb6ee6a5f38/coverage-7.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74", size = 223683, upload-time = "2026-07-12T20:57:48.106Z" }, + { url = "https://files.pythonhosted.org/packages/35/65/0b431856064e387d1f5cf474625e4a0465e907024d42f35de6af19ced0be/coverage-7.15.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b", size = 222298, upload-time = "2026-07-12T20:57:49.882Z" }, + { url = "https://files.pythonhosted.org/packages/a6/96/50eac9bd49df8a3df5f3d38746d1bf332299dffb554486c94ebd55c9dc49/coverage-7.15.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49", size = 222561, upload-time = "2026-07-12T20:57:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5b/6ba1c4a27e10b8816fd2622b98162c83d3bdf1185097360373611bf96364/coverage-7.15.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864", size = 263923, upload-time = "2026-07-12T20:57:53.392Z" }, + { url = "https://files.pythonhosted.org/packages/e4/59/fe03ade97a3ca2d890e98c572cf48a99fda9adba85757c34b823f41efe1e/coverage-7.15.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c", size = 266043, upload-time = "2026-07-12T20:57:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/16/e0/55c4b1217a572a43e13b39e1eb78d0da29fb23679003bd0cdf22c50b1978/coverage-7.15.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07", size = 268465, upload-time = "2026-07-12T20:57:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/ee47944f76afc03909119b036fe9e0da8cbd274a5141287de79791a0fb6d/coverage-7.15.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e", size = 269584, upload-time = "2026-07-12T20:57:58.958Z" }, + { url = "https://files.pythonhosted.org/packages/cb/8a/6b4d9779c7b2e21c3d12c3425e3261aa7411399319e27aa402dfec4db5d0/coverage-7.15.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e", size = 263019, upload-time = "2026-07-12T20:58:00.979Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1e/db5c7fa0c8ba5ece390a1e1a3f30db71d440240a80589df28e66a7503c40/coverage-7.15.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf", size = 265916, upload-time = "2026-07-12T20:58:03.005Z" }, + { url = "https://files.pythonhosted.org/packages/83/53/fe5176682b00709b13fab36addd26883139d0dea430816fea412e69255e2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82", size = 263520, upload-time = "2026-07-12T20:58:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e7/16f15127be93fbc70c667df5ec5dce934fc76c9b0888d84969a5d5341e2c/coverage-7.15.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82", size = 267254, upload-time = "2026-07-12T20:58:06.824Z" }, + { url = "https://files.pythonhosted.org/packages/cd/73/e5119111f6f065376395a525f7ce6e9174d83f3db6d217ea0211a61cca4d/coverage-7.15.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7", size = 262366, upload-time = "2026-07-12T20:58:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9c/6d0a81182df18a73b081e7a8630f0e2a52b12dfd7898c6ab839551a454d2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594", size = 264680, upload-time = "2026-07-12T20:58:10.359Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a2/bac0cbd4450638f1be2041a464b1766c8cc94abf705a2df6f1c8d4be870d/coverage-7.15.1-cp314-cp314t-win32.whl", hash = "sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070", size = 224077, upload-time = "2026-07-12T20:58:12.065Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b2/d83c5403155172a43ba47c08641bad3f89822d8405102423a41339d2c857/coverage-7.15.1-cp314-cp314t-win_amd64.whl", hash = "sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f", size = 224908, upload-time = "2026-07-12T20:58:13.956Z" }, + { url = "https://files.pythonhosted.org/packages/cc/41/442b74cad832cc77712080585455482e7cc4f4a9a13192f65731dcd18231/coverage-7.15.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b", size = 224219, upload-time = "2026-07-12T20:58:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/34/98/07a67cf1a26e795d617ed5c540c042b0ac87b72f810c30c07f076cf334f3/coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c", size = 213284, upload-time = "2026-07-12T20:58:18.079Z" }, ] [package.optional-dependencies] @@ -793,11 +803,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.5" +version = "3.29.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, + { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, ] [[package]] @@ -1746,7 +1756,7 @@ provides-extras = ["qiskit"] [package.metadata.requires-dev] build = [ { name = "nanobind", specifier = "~=2.13.0" }, - { name = "scikit-build-core", specifier = ">=0.12.2" }, + { name = "scikit-build-core", specifier = "~=0.12" }, { name = "setuptools-scm", specifier = ">=9.2.2" }, ] dev = [ @@ -1761,7 +1771,7 @@ dev = [ { name = "pytest-sugar", specifier = ">=1.1.1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "qiskit", extras = ["qasm3-import"], specifier = ">=1.1.0" }, - { name = "scikit-build-core", specifier = ">=0.12.2" }, + { name = "scikit-build-core", specifier = "~=0.12" }, { name = "setuptools-scm", specifier = ">=9.2.2" }, ] docs = [ @@ -1920,7 +1930,7 @@ wheels = [ [[package]] name = "nox" -version = "2026.4.10" +version = "2026.7.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -1932,9 +1942,9 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/e672c862a43cfca704d32359221fa3780226daa1e5db5dfc401bcc8be9c9/nox-2026.4.10.tar.gz", hash = "sha256:2d0af5374f3f37a295428c927d1b04a8182aa01762897d172446dda2f1ce9692", size = 4034839, upload-time = "2026-04-10T17:42:42.209Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/bf/aafe066019cb1bcd3e1c22957412d6eb560bd6553c1afd28502168a51418/nox-2026.7.11.tar.gz", hash = "sha256:dec9bd2c854540a2d5c0b841eaaf1d23a7c26cd90af36d9f1f1668b34524bfd9", size = 4042267, upload-time = "2026-07-12T00:32:19.859Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/95/4df134a100b5a9a12378d5301b934366686ef6fbdaffcd21211d5654970e/nox-2026.4.10-py3-none-any.whl", hash = "sha256:082c117627590d9b90aa21f86df89b310b07c5842539524203bcb3c719f116c1", size = 75536, upload-time = "2026-04-10T17:42:40.664Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/4b459b66bbd7c9be3c9b15f2cf1605bbe10f58c6395fd99a59a21f3c0777/nox-2026.7.11-py3-none-any.whl", hash = "sha256:f5e811693ee8374d269396204eb39990d2084da67ed968239f94301805c9a169", size = 77265, upload-time = "2026-07-12T00:32:18.07Z" }, ] [[package]] @@ -2678,15 +2688,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.3" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/26/8b004cc36f430345136f6f00fa1aa9ed596c8ed1e8504625fa79522ff39c/python_discovery-1.4.3.tar.gz", hash = "sha256:ad57d7045a862460d4a235986c33f13ed707d3aeb9153fa47eb7dfd0d4673289", size = 70438, upload-time = "2026-07-03T13:21:51.621Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/78/9b77ecb4644d1bbea94d29abf78f21c47eca6eb79e9745b702ec0bed2e19/python_discovery-1.4.3-py3-none-any.whl", hash = "sha256:b6e1e4a7d9e3f6948c39746ffe8218225162d738ba39d05ab1d2f6c1cac4878c", size = 33885, upload-time = "2026-07-03T13:21:50.174Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" }, ] [[package]] @@ -4018,11 +4028,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] @@ -4050,7 +4060,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.5.1" +version = "21.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -4059,9 +4069,9 @@ dependencies = [ { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/d9/b477fddb68840b570af8b22afe9b035cbc277b5fb7b33dea390617a8b10f/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128", size = 5526620, upload-time = "2026-07-10T19:33:53.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7c/4e7225d46d634a0d8d534dd8a6ce0c319d09b4d0cf0337eb314ca4789d8c/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b", size = 5506392, upload-time = "2026-07-10T19:33:51.629Z" }, ] [[package]] From bc6741399462f381653a294fe28aaa67e43507fc Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 13 Jul 2026 12:03:24 +0200 Subject: [PATCH 16/27] =?UTF-8?q?=F0=9F=93=9D=20Reorder=20CHANGELOG.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75357fe3fc..be7c8c0c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,13 @@ releases may include breaking changes. ### Added +- ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit + unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**], + [**@burgholzer**]) - ✨ Add support for custom job parameters to C++ and Python FoMaC library ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) - ✨ Add QIR Output Schemas support to the QIR runtime ([#1877]) ([**@rturrado**]) -- ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit - unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**], - [**@burgholzer**]) - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function From 2236c0c235db454e48c84d05a5682e9576de9bac Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 13 Jul 2026 12:08:30 +0200 Subject: [PATCH 17/27] =?UTF-8?q?=E2=9C=A8=20Add=20missing=20empty=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Decomposition/test_weyl_decomposition.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index 153edee416..2b822c6cba 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -789,6 +789,7 @@ TEST_F(NativeGatesetMlirTest, AllowsOpMatchesGateset) { spec->allowsOp(GPhaseOp::create(builder, loc, 0.1).getOperation())); EXPECT_TRUE(spec->allowsOp( UOp::create(builder, loc, q0, 0.1, 0.2, 0.3).getOperation())); + auto cx = CtrlOp::create(builder, loc, q0, q1, [&](Value target) { return XOp::create(builder, loc, target).getOutputQubit(0); }); From 170eb491a8560e07fc85e6e2abe823068189d8ce Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 13 Jul 2026 12:45:37 +0200 Subject: [PATCH 18/27] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index e96e6ccfd1..14c76e7e71 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include From cca42fc1ac46ca24ad5b7858cbd7b3697d398256 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 14:29:27 +0200 Subject: [PATCH 19/27] =?UTF-8?q?=E2=9C=A8=20Add=20fuse=20two-qubit=20unit?= =?UTF-8?q?ary=20runs=20to=20compiler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Compiler/Programs.h | 5 +++++ mlir/lib/Compiler/Programs.cpp | 11 +++++++++++ mlir/unittests/Compiler/test_compiler_pipeline.cpp | 3 +++ 3 files changed, 19 insertions(+) diff --git a/mlir/include/mlir/Compiler/Programs.h b/mlir/include/mlir/Compiler/Programs.h index 96e7bc5aa1..2334b2f664 100644 --- a/mlir/include/mlir/Compiler/Programs.h +++ b/mlir/include/mlir/Compiler/Programs.h @@ -182,6 +182,11 @@ class QCOProgram final : public Program { /** @brief Fuse single-qubit unitary runs into the selected Euler basis. */ [[nodiscard]] bool fuseSingleQubitUnitaryRuns(std::string_view basis = "zyz"); + /** + * @brief Lower unitaries to a native gate menu via two-qubit run fusion. + */ + [[nodiscard]] bool fuseTwoQubitUnitaryRuns(std::string_view nativeGates = ""); + /** @brief Unroll loops containing quantum operations. */ [[nodiscard]] bool unrollQuantumLoops(int64_t factor = -1); diff --git a/mlir/lib/Compiler/Programs.cpp b/mlir/lib/Compiler/Programs.cpp index 822289abb8..f06fb7bd0d 100644 --- a/mlir/lib/Compiler/Programs.cpp +++ b/mlir/lib/Compiler/Programs.cpp @@ -343,6 +343,17 @@ bool QCOProgram::fuseSingleQubitUnitaryRuns(const std::string_view basis) { "failed to fuse single-qubit unitary runs")); } +bool QCOProgram::fuseTwoQubitUnitaryRuns(const std::string_view nativeGates) { + qco::FuseTwoQubitUnitaryRunsOptions options; + options.nativeGates = nativeGates; + return succeeded(runPasses( + module(), + [&options](OpPassManager& pm) { + pm.addPass(qco::createFuseTwoQubitUnitaryRuns(options)); + }, + "failed to fuse two-qubit unitary runs")); +} + bool QCOProgram::unrollQuantumLoops(const int64_t factor) { qco::QuantumLoopUnrollOptions options; options.unrollFactor = factor; diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 3c46089067..7f9d87edf5 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -432,6 +432,9 @@ cx q[0], q[2]; EXPECT_TRUE(qco.fuseSingleQubitUnitaryRuns("zyz")); EXPECT_NE(qco.str(), beforeFusion); + const auto beforeTwoQubitFusion = qco.str(); + EXPECT_TRUE(qco.fuseTwoQubitUnitaryRuns("u,cx")); + EXPECT_NE(qco.str(), beforeTwoQubitFusion); const std::vector> coupling = { {0, 1}, {1, 0}, {1, 2}, {2, 1}}; EXPECT_TRUE(qco.placeAndRoute(coupling)); From f387240ee9fe5630a40d1d9ce9a5fb5caa948543 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 14:29:54 +0200 Subject: [PATCH 20/27] =?UTF-8?q?=F0=9F=90=8D=20Expose=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/mlir/register_mlir.cpp | 6 ++++++ python/mqt/core/mlir.pyi | 3 +++ 2 files changed, 9 insertions(+) diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index d30a79c5e2..530081b398 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -387,6 +387,12 @@ operations.)pb"); &mlir::QCOProgram::fuseSingleQubitUnitaryRuns>::call, nb::kw_only(), "basis"_a = "zyz", "Fuse single-qubit unitary runs into the chosen decomposition basis.") + .def("fuse_two_qubit_unitary_runs", + &BooleanMemberAdapter< + &mlir::QCOProgram::fuseTwoQubitUnitaryRuns>::call, + nb::kw_only(), "native_gates"_a = "", + "Lower unitaries to a native gate menu via two-qubit run fusion. " + "An empty gateset is a no-op.") .def("unroll_quantum_loops", &BooleanMemberAdapter<&mlir::QCOProgram::unrollQuantumLoops>::call, nb::kw_only(), "unroll_factor"_a = -1, diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 25a795438d..5ee50c03dd 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -138,6 +138,9 @@ class QCOProgram(Program): def fuse_single_qubit_unitary_runs(self, *, basis: str = "zyz") -> None: """Fuse single-qubit unitary runs into the chosen decomposition basis.""" + def fuse_two_qubit_unitary_runs(self, *, native_gates: str = "") -> None: + """Lower unitaries to a native gate menu via two-qubit run fusion.""" + def unroll_quantum_loops(self, *, unroll_factor: int = -1) -> None: """Unroll quantum loops, optionally using a maximum unroll factor.""" From 5074c6227fa8a6c31fe2a91f782278df5ba23f72 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 14:38:37 +0200 Subject: [PATCH 21/27] =?UTF-8?q?=E2=9C=A8=20Add=20native=20gates=20option?= =?UTF-8?q?=20for=20fuse=20two-qubit=20unitary=20runs=20and=20register=20t?= =?UTF-8?q?he=20corresponding=20pass.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Support/Passes.cpp | 1 + mlir/tools/mqt-cc/mqt-cc.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/mlir/lib/Support/Passes.cpp b/mlir/lib/Support/Passes.cpp index e9f5dd7296..f4a28e241e 100644 --- a/mlir/lib/Support/Passes.cpp +++ b/mlir/lib/Support/Passes.cpp @@ -46,6 +46,7 @@ runWithPassManager(ModuleOp module, void registerMQTCompilerPasses() { static const auto REGISTERED = [] { qco::registerFuseSingleQubitUnitaryRuns(); + qco::registerFuseTwoQubitUnitaryRuns(); qco::registerHadamardLifting(); qco::registerMergeSingleQubitRotationGates(); qco::registerQuantumLoopUnroll(); diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 2170c32d78..c695b52bec 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -17,6 +17,7 @@ #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Support/Passes.h" @@ -83,6 +84,13 @@ static llvm::cl::opt outputFormat( "qir-adaptive, or jeff"), llvm::cl::value_desc("format"), llvm::cl::init("mlir")); +static llvm::cl::opt nativeGates( + "native-gates", + llvm::cl::desc( + "Comma-separated native gate menu for the fuse-two-qubit-unitary-runs " + "pass"), + llvm::cl::value_desc("csv"), llvm::cl::init("")); + namespace { enum class InputFormat : std::uint8_t { MLIR, QCO, QASM, Jeff }; enum class InputDialect : std::uint8_t { QC, QCO }; @@ -389,6 +397,12 @@ int main(int argc, char** argv) { } else { populateDefaultQCOOptimizationPipeline(pm); } + if (!nativeGates.empty()) { + pm.addPass(qco::createFuseTwoQubitUnitaryRuns( + qco::FuseTwoQubitUnitaryRunsOptions{ + .nativeGates = nativeGates.getValue(), + })); + } populateQCOCleanupPipeline(pm); return success(); }))) { From 5b5d8bd88d58b16c5d953f32fd86650bb4120a4d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 14:49:26 +0200 Subject: [PATCH 22/27] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20fusing=20?= =?UTF-8?q?tqo-qubit=20unitary=20runs=20using=20the=20compiler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Compiler/test_compiler_pipeline.cpp | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 7f9d87edf5..3da43bf489 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -998,4 +998,98 @@ INSTANTIATE_TEST_SUITE_P( nullptr, MQT_NAMED_BUILDER(mlir::qc::ctrlTwo), MQT_NAMED_BUILDER(mlir::qir::ctrlTwo)})); +/** + * @brief Test: fuseTwoQubitUnitaryRuns lowers Hadamards into native menus. + */ +TEST_F(CompilerPipelineTest, FuseTwoQubitUnitaryRunsLowersHadamard) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +h q[1]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qcoResult = std::move(*qc).intoQCO(); + ASSERT_TRUE(qcoResult); + auto qco = std::move(*qcoResult); + ASSERT_TRUE(qco.cleanup()); + EXPECT_NE(qco.str().find("qco.h"), std::string::npos); + + EXPECT_TRUE(qco.fuseTwoQubitUnitaryRuns("x,sx,rz,cx")); + EXPECT_EQ(qco.str().find("qco.h"), std::string::npos); + + auto qcU = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qcU); + auto qcoUResult = std::move(*qcU).intoQCO(); + ASSERT_TRUE(qcoUResult); + auto qcoU = std::move(*qcoUResult); + ASSERT_TRUE(qcoU.cleanup()); + EXPECT_TRUE(qcoU.fuseTwoQubitUnitaryRuns("u,cx")); + EXPECT_EQ(qcoU.str().find("qco.h"), std::string::npos); + EXPECT_NE(qcoU.str().find("qco.u"), std::string::npos); + + auto qcExpanded = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qcExpanded); + auto qcoExpandedResult = std::move(*qcExpanded).intoQCO(); + ASSERT_TRUE(qcoExpandedResult); + auto qcoExpanded = std::move(*qcoExpandedResult); + ASSERT_TRUE(qcoExpanded.cleanup()); + EXPECT_TRUE(qcoExpanded.fuseTwoQubitUnitaryRuns("u,rx,rz,cx,cz")); + EXPECT_EQ(qcoExpanded.str().find("qco.h"), std::string::npos); +} + +/** + * @brief Test: fuseTwoQubitUnitaryRuns rejects invalid native gate menus. + */ +TEST_F(CompilerPipelineTest, FuseTwoQubitUnitaryRunsRejectsInvalidMenus) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +h q[1]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qcoResult = std::move(*qc).intoQCO(); + ASSERT_TRUE(qcoResult); + auto qco = std::move(*qcoResult); + ASSERT_TRUE(qco.cleanup()); + EXPECT_FALSE(qco.fuseTwoQubitUnitaryRuns("cx,cz")); + + auto qcInvalid = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qcInvalid); + auto qcoInvalidResult = std::move(*qcInvalid).intoQCO(); + ASSERT_TRUE(qcoInvalidResult); + auto qcoInvalid = std::move(*qcoInvalidResult); + ASSERT_TRUE(qcoInvalid.cleanup()); + EXPECT_FALSE(qcoInvalid.fuseTwoQubitUnitaryRuns("not-a-gate")); +} + +/** + * @brief Test: an empty or whitespace-only native gateset is a no-op. + */ +TEST_F(CompilerPipelineTest, FuseTwoQubitUnitaryRunsEmptyMenuIsNoOp) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +h q[1]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qcoResult = std::move(*qc).intoQCO(); + ASSERT_TRUE(qcoResult); + auto qco = std::move(*qcoResult); + ASSERT_TRUE(qco.cleanup()); + const auto before = qco.str(); + EXPECT_NE(before.find("qco.h"), std::string::npos); + + EXPECT_TRUE(qco.fuseTwoQubitUnitaryRuns("")); + EXPECT_EQ(qco.str(), before); + + EXPECT_TRUE(qco.fuseTwoQubitUnitaryRuns(" \t ")); + EXPECT_EQ(qco.str(), before); +} + } // namespace mqt::test::compiler From 1c6173709c8c01a8d534f9d7729fd21e55cd0ea8 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 15:11:07 +0200 Subject: [PATCH 23/27] =?UTF-8?q?=F0=9F=93=9D=20Update=20documentation=20f?= =?UTF-8?q?or=20native=20gates=20option=20in=20two-qubit=20unitary=20runs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/mlir/register_mlir.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 026f101eec..d6bf6cabc2 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -391,8 +391,7 @@ operations.)pb"); &BooleanMemberAdapter< &mlir::QCOProgram::fuseTwoQubitUnitaryRuns>::call, nb::kw_only(), "native_gates"_a = "", - "Lower unitaries to a native gate menu via two-qubit run fusion. " - "An empty gateset is a no-op.") + "Lower unitaries to a native gate menu via two-qubit run fusion.") .def("unroll_quantum_loops", &BooleanMemberAdapter<&mlir::QCOProgram::unrollQuantumLoops>::call, nb::kw_only(), "unroll_factor"_a = -1, From 7b2cd01365d4dfd498178fb2a02cc18587d6d33f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 21 Jul 2026 16:23:16 +0200 Subject: [PATCH 24/27] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp index f8491d70b8..14caf79d0e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseTwoQubitUnitaryRuns.cpp @@ -348,6 +348,9 @@ struct LowerTwoQubitOpPattern final LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { + if (!op.isTwoQubit()) { + return failure(); + } Operation* raw = op.getOperation(); if (!isWalkableUnitaryShell(raw) || spec.allowsOp(raw)) { return failure(); From 82a189c335e04ec9ee197fe8642a28cfead5f494 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 23 Jul 2026 09:43:44 +0200 Subject: [PATCH 25/27] =?UTF-8?q?=F0=9F=94=80=20Merge=20main=20and=20use?= =?UTF-8?q?=20new=20dd=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/upstream.yml | 4 +- CHANGELOG.md | 9 +- .../Dialect/QCO/Builder/QCOProgramBuilder.h | 59 ++ mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 16 +- mlir/include/mlir/Dialect/QCO/QCOUtils.h | 72 ++ .../mlir/Dialect/QCO/Transforms/Passes.td | 18 + .../mlir/Dialect/QCO/Utils/DDFunctionality.h | 69 ++ .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 116 ++- .../lib/Dialect/QCO/IR/Operations/ResetOp.cpp | 17 +- .../Dialect/QCO/IR/QubitManagement/SinkOp.cpp | 68 +- mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp | 21 + .../Optimizations/MeasurementLifting.cpp | 4 - .../ReplaceClassicalControls.cpp | 201 +++++ mlir/lib/Dialect/QCO/Utils/CMakeLists.txt | 25 +- .../lib/Dialect/QCO/Utils/DDFunctionality.cpp | 415 ++++++++++ mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 14 +- .../Transforms/Decomposition/CMakeLists.txt | 1 + .../Decomposition/test_weyl_decomposition.cpp | 117 +-- .../Transforms/NativeSynthesis/CMakeLists.txt | 1 + .../test_fuse_two_qubit_unitary_runs.cpp | 367 +++------ .../Transforms/Optimizations/CMakeLists.txt | 7 +- .../test_qco_measurement_lifting.cpp | 86 +-- .../test_qco_replace_classical_controls.cpp | 718 ++++++++++++++++++ .../Dialect/QCO/Utils/CMakeLists.txt | 8 +- .../QCO/Utils/test_dd_functionality.cpp | 596 +++++++++++++++ mlir/unittests/programs/qasm_programs.cpp | 14 +- mlir/unittests/programs/qc_programs.cpp | 18 +- mlir/unittests/programs/qc_programs.h | 4 +- mlir/unittests/programs/qco_programs.cpp | 65 +- mlir/unittests/programs/qco_programs.h | 10 +- mlir/unittests/programs/qir_programs.cpp | 55 +- .../programs/quantum_computation_programs.cpp | 14 +- 32 files changed, 2637 insertions(+), 572 deletions(-) create mode 100644 mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp create mode 100644 mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp create mode 100644 mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index f7ef34fa39..765f3d9356 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -18,11 +18,13 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-24.04, macos-14, windows-2022] + runs-on: [ubuntu-24.04, macos-15, windows-2025] uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-qiskit-upstream-tests.yml@e7f84f39ce2d3b6c5d1d04526b8f94f98e455143 # v2.2.0 with: runs-on: ${{ matrix.runs-on }} setup-z3: true + setup-mlir: true + llvm-version: "22.1.7" create-issue-on-failure: name: Create issue on failure diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e599c129..1a54ef90dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ releases may include breaking changes. ### Added +- ✨ Add decision diagram-based construction and simulation of static unitary + QCO functions ([#1915]) ([**@simon1hofmann**]) - ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**], [**@burgholzer**]) @@ -57,7 +59,8 @@ releases may include breaking changes. [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870], [#1904], [#1911]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add a pass for qubit reuse in quantum programs, as well as related - auxiliary passes and patterns ([#1705], [#1755]) ([**@DRovara**]) + auxiliary passes and patterns ([#1705], [#1755], [#1756], [#1924]) + ([**@DRovara**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], @@ -645,6 +648,8 @@ changelogs._ +[#1924]: https://github.com/munich-quantum-toolkit/core/pull/1924 +[#1915]: https://github.com/munich-quantum-toolkit/core/pull/1915 [#1914]: https://github.com/munich-quantum-toolkit/core/pull/1914 [#1911]: https://github.com/munich-quantum-toolkit/core/pull/1911 [#1904]: https://github.com/munich-quantum-toolkit/core/pull/1904 @@ -690,6 +695,7 @@ changelogs._ [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 +[#1756]: https://github.com/munich-quantum-toolkit/core/pull/1756 [#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 @@ -836,6 +842,7 @@ changelogs._ [#1164]: https://github.com/munich-quantum-toolkit/core/pull/1164 [#1157]: https://github.com/munich-quantum-toolkit/core/pull/1157 [#1151]: https://github.com/munich-quantum-toolkit/core/pull/1151 +[#1150]: https://github.com/munich-quantum-toolkit/core/pull/1150 [#1148]: https://github.com/munich-quantum-toolkit/core/pull/1148 [#1147]: https://github.com/munich-quantum-toolkit/core/pull/1147 [#1140]: https://github.com/munich-quantum-toolkit/core/pull/1140 diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index 06c92deb3f..caf1c01b90 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -1467,6 +1467,48 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { function_ref(ValueRange)> thenBody, function_ref(ValueRange)> elseBody = nullptr); + /** + * @brief Construct an if operation for qubits with a single target qubit or + * tensor. + * + * @details + * Constructs an if operation that takes a bool Value and a single qubit + * or qtensor value that is used in the then/else region of this operation. + * The value is passed down as block arguments to each region. Qubits that + * were extracted from a tensor that is used as an argument for this operation + * are automatically inserted before the operation is constructed. + * + * @param condition Bool condition + * @param initArg Initial argument for the if branches + * @param thenBody Function that builds the then body of the if operation + * @param elseBody Function that builds the else body of the if operation + * @return Value as a result + * + * @par Example: + * ```c++ + * result = builder.qcoIf(condition, initArg, [&](Value arg) + * -> Value { + * auto q1 = builder.x(arg); + * return q1; + * }, [&](Value arg) -> Value { + * auto q2 = builder.z(arg); + * return q2; + * }); + * ``` + * ```mlir + * %q3 = qco.if %condition args(%arg0 = %q0) -> (!qco.qubit) { + * %q1 = qco.x %arg0 : !qco.qubit -> !qco.qubit + * qco.yield %q1 : !qco.qubit + * } else args(%arg0 = %q0) { + * %q2 = qco.z %arg0 : !qco.qubit -> !qco.qubit + * qco.yield %q2 : !qco.qubit + * } + * ``` + */ + Value qcoIf(const std::variant& condition, Value initArg, + function_ref thenBody, + function_ref elseBody = nullptr); + /** * @brief Construct an scf.for operation * @@ -1706,6 +1748,23 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { */ SmallVector prepareInitArgs(ValueRange initArgs); + /** + * @brief Prepare one initial argument by re-inserting extracted qubits into + * its tensor, if necessary. + * @param initArg Initial value + * @return Updated initial value + */ + Value prepareInitArg(Value initArg); + + Value prepareInitArg(Value initArg, const DenseSet* initQubits); + + /** + * @brief Update linear-value tracking for one replaced value + * @param oldValue The old value to be replaced + * @param newValue The new value to be tracked + */ + void updateQubitValueTracking(Value oldValue, Value newValue); + /** * @brief Update the qubit tracking of the old values with the new values * @param oldValues The old values to be replaced diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 78167d3684..f9003d0917 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1314,14 +1314,18 @@ def IfOp let regions = (region SizedRegion<1>:$thenRegion, SizedRegion<1>:$elseRegion); let hasCustomAssemblyFormat = 1; - let builders = [OpBuilder<(ins "Value":$condition, "ValueRange":$qubits), [{ + let builders = + [OpBuilder<(ins "Value":$condition, "ValueRange":$qubits), [{ build($_builder, $_state, qubits.getTypes(), condition, qubits); }]>, - OpBuilder<(ins "Value":$condition, "ValueRange":$qubits, - "function_ref(ValueRange)" - ">":$thenBuilder, - CArg<"function_ref(ValueRange)>", - "nullptr">:$elseBuilder)>]; + OpBuilder<(ins "Value":$condition, "ValueRange":$qubits, + "function_ref(ValueRange)" + ">":$thenBuilder, + CArg<"function_ref(ValueRange)>", + "nullptr">:$elseBuilder)>, + OpBuilder<(ins "Value":$condition, "Value":$input, + "function_ref":$thenBuilder, + CArg<"function_ref", "nullptr">:$elseBuilder)>]; let extraClassDeclaration = [{ /// Return the then-block. diff --git a/mlir/include/mlir/Dialect/QCO/QCOUtils.h b/mlir/include/mlir/Dialect/QCO/QCOUtils.h index 8bd7adf67f..36ab3f8098 100644 --- a/mlir/include/mlir/Dialect/QCO/QCOUtils.h +++ b/mlir/include/mlir/Dialect/QCO/QCOUtils.h @@ -13,6 +13,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" +#include #include #include #include @@ -25,6 +26,24 @@ namespace mlir::qco { +/** + * @brief Check if given quantum operation is unused (i.e., only used by sinks + * or resets and has no memory effects). + * + * @param op The operation to check. + * @return bool True if the operation is unused, false otherwise. + */ +inline bool checkDeadGate(Operation* op) { + if (!isMemoryEffectFree(op)) { + // This ignores operations and regions that have children with memory + // effects, which should never be considered dead. + return false; + } + return llvm::all_of(op->getUsers(), [](Operation* user) { + return isa(user); + }); +} + /// Maximum number of modifier targets supported by @ref /// composeBodyMatrix. inline constexpr size_t kMaxModifierTargetQubits = 10; @@ -290,4 +309,57 @@ LogicalResult mergeXXPlusMinusYY(OpType op, PatternRewriter& rewriter) { return mergeTwoTargetOneParameterImpl(op, nextOp, rewriter, true); } +/** + * @brief Search for and remove gates when their outputs are no longer used + * before the next `ResetOp` or `SinkOp`. + * + * @param qubit The value that was an input to a `ResetOp` or `SinkOp` from + * which the search is started. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the elimination. + */ +inline LogicalResult tryEliminateDeadGateValue(Value qubit, + PatternRewriter& rewriter) { + auto* currentOp = qubit.getDefiningOp(); + auto success = false; + while (currentOp != nullptr) { + if (!checkDeadGate(currentOp)) { + break; + } + + qubit = + TypeSwitch(currentOp) + .Case([&](auto measureOp) { + auto newValue = measureOp.getQubitIn(); + rewriter.replaceAllUsesWith(measureOp.getQubitOut(), newValue); + rewriter.eraseOp(measureOp); + return newValue; + }) + .Case([&](auto ifOp) { + auto* tiedQubit = ifOp.getTiedQubit(cast(qubit)); + auto newValue = tiedQubit->get(); + rewriter.replaceOp(ifOp, ifOp.getQubits()); + return newValue; + }) + .Case([&](auto resetOp) { + auto newValue = resetOp.getQubitIn(); + rewriter.replaceOp(resetOp, resetOp->getOperands()); + return newValue; + }) + .Case([&](auto unitaryOp) { + auto newValue = unitaryOp.getInputForOutput(qubit); + rewriter.replaceOp(unitaryOp, unitaryOp.getInputQubits()); + return newValue; + }) + .Default([&](auto) { return nullptr; }); + + if (qubit == nullptr) { + break; + } + currentOp = qubit.getDefiningOp(); + success = true; + } + return mlir::success(success); +} + } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 0260e86139..42c334124e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -285,4 +285,22 @@ def MeasurementLifting : Pass<"measurement-lifting", "mlir::ModuleOp"> { }]; } +def ReplaceClassicalControls + : Pass<"replace-classical-controls", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect", + "::mlir::arith::ArithDialect", + ]; + let summary = + "This pass attempts to replace controlled gates that are controlled by " + "values that are in a computational basis state and available as " + "measurement outcome by a classical " + "`qco.if` instruction. This is done to enable qubit reuse and other " + "optimizations."; + let description = [{ + This pass searches for control operations that immediately follow measurements and replaces them + with classically controlled operations, represented by `qco.if` operations. + This reduces quantum interactions and allows for more optimizations, e.g., by enabling qubit reuse. + }]; +} + #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h new file mode 100644 index 0000000000..15ba481237 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h @@ -0,0 +1,69 @@ +/* + * 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 + */ + +#pragma once + +#include "dd/Package_fwd.hpp" + +#include +#include + +namespace mlir::qco { + +/** + * @brief Sequentially build a matrix DD for a static unitary QCO `func.func`. + * + * @details Walks the entry block of @p func, maps `qco.static` SSA values to + * wire indices (or, if none are present, qubit-typed function arguments as + * wires `0..n-1`), and applies unitary operations via decision-diagram + * multiplication. + * + * Supported programs: + * - Standard single- and two-qubit gates with compile-time constant parameters + * (sparse DD path) + * - `ctrl` with a sole standard-gate body (same sparse path) + * - Other `UnitaryOpInterface` ops with a compile-time known matrix (`inv`, + * compound `ctrl`, ...), including `gphase` and `barrier` + * - Skips: `static`, `sink`, `arith.constant`; `func.return` accepts qubit + * results only in canonical wire order + * + * Known one- and two-qubit matrices are constructed directly as DD gates. The + * dense matrix fallback accepts full-width unitaries on wires `0..n-1` and + * rewrites the QCO/MSB-first basis into the DD package's LSB-first indexing. + * Only this full-width fallback is limited to 12 qubits (dense `2^n × 2^n` + * storage). Measurements, resets, symbolic parameters, and control-flow ops + * are not supported. + * + * @param func The QCO function to construct the functionality for + * @param dd The DD package to use (must hold at least the function's qubits) + * @return The matrix DD on success, or failure for unsupported programs + */ +FailureOr buildFunctionality(func::FuncOp func, dd::Package& dd); + +/** + * @brief Simulate a static unitary QCO `func.func` on a given input state. + * + * @details Same supported op set and limitations as @ref buildFunctionality. + * Mirrors @ref dd::simulate: sequentially applies unitaries to @p in via + * decision-diagram multiplication. Only purely quantum, measurement-free + * programs are supported. Consumes one reference to @p in regardless of + * whether simulation succeeds or fails. + * + * @param func The QCO function to simulate + * @param in The input state, represented as a vector DD; one reference is + * consumed + * @param dd The DD package to use (must hold at least the function's qubits) + * @return The output statevector DD on success, or failure for unsupported + * programs + */ +FailureOr simulate(func::FuncOp func, const dd::VectorDD& in, + dd::Package& dd); + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 07e747d270..43821cbb53 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -237,10 +237,39 @@ void QCOProgramBuilder::updateTensorTracking(Value inputTensor, validTensors.try_emplace(outputTensor, info); } +Value QCOProgramBuilder::prepareInitArg(Value initArg, + const DenseSet* initQubits) { + if (isa(initArg.getType())) { + return initArg; + } + + const auto regId = validTensors[initArg].regId; + auto currentTensor = initArg; + for (auto it = validQubits.begin(); it != validQubits.end();) { + auto& [qubit, qubitInfo] = *it; + if (qubitInfo.regId == regId && + (initQubits == nullptr || !initQubits->contains(qubit))) { + auto newTensor = qtensor::InsertOp::create(*this, qubit, currentTensor, + qubitInfo.regIndex) + .getResult(); + updateTensorTracking(currentTensor, newTensor); + currentTensor = newTensor; + validQubits.erase(it++); + } else { + ++it; + } + } + return currentTensor; +} + +Value QCOProgramBuilder::prepareInitArg(Value initArg) { + checkQubitType(ValueRange{initArg}); + return prepareInitArg(initArg, nullptr); +} + SmallVector QCOProgramBuilder::prepareInitArgs(ValueRange initArgs) { checkQubitType(initArgs); - // Gather all initial qubits first DenseSet initQubits; for (auto initArg : initArgs) { if (isa(initArg.getType())) { @@ -250,54 +279,28 @@ SmallVector QCOProgramBuilder::prepareInitArgs(ValueRange initArgs) { SmallVector updatedArgs; updatedArgs.reserve(initArgs.size()); - - // Iterate through the initial values and add the latest value to the updated - // arguments for (auto initArg : initArgs) { - if (isa(initArg.getType())) { - // Directly insert qubits - updatedArgs.emplace_back(initArg); - } else { - // For tensors check if you have to insert qubits first - const auto regId = validTensors[initArg].regId; - auto currentTensor = initArg; - // Iterate through the validQubits and find the qubits that were extracted - // from this tensor - for (auto it = validQubits.begin(); it != validQubits.end();) { - auto& [qubit, qubitInfo] = *it; - // Ignore qubits that are also used as initArgs - if (qubitInfo.regId == regId && !initQubits.contains(qubit)) { - // Create an InsertOp for the qubit - auto newTensor = qtensor::InsertOp::create( - *this, qubit, currentTensor, qubitInfo.regIndex) - .getResult(); - // Update the tensor tracking - updateTensorTracking(currentTensor, newTensor); - currentTensor = newTensor; - validQubits.erase(it++); - } else { - ++it; - } - } - // Add the tensor after all qubits are inserted and the tensor tracking is - // updated - updatedArgs.emplace_back(currentTensor); - } + updatedArgs.emplace_back(prepareInitArg(initArg, &initQubits)); } return updatedArgs; } +void QCOProgramBuilder::updateQubitValueTracking(Value oldValue, + Value newValue) { + if (oldValue.getType() != newValue.getType()) { + llvm::reportFatalUsageError("Result types must match input types"); + } + if (isa(oldValue.getType())) { + updateQubitTracking(oldValue, newValue); + } else { + updateTensorTracking(oldValue, newValue); + } +} + void QCOProgramBuilder::updateQubitValueTracking(ValueRange oldValues, ValueRange newValues) { for (auto [oldValue, newValue] : llvm::zip_equal(oldValues, newValues)) { - if (oldValue.getType() != newValue.getType()) { - llvm::reportFatalUsageError("Result types must match input types"); - } - if (isa(oldValue.getType())) { - updateQubitTracking(oldValue, newValue); - } else { - updateTensorTracking(oldValue, newValue); - } + updateQubitValueTracking(oldValue, newValue); } } @@ -1155,6 +1158,37 @@ ValueRange QCOProgramBuilder::qcoIf( return ifOp->getResults(); } +Value QCOProgramBuilder::qcoIf(const std::variant& condition, + Value initArg, + function_ref thenBody, + function_ref elseBody) { + checkFinalized(); + + const auto conditionValue = variantToValue(*this, getLoc(), condition); + const auto updatedArg = prepareInitArg(initArg); + Value thenResult; + const auto trackedThenBody = [&](Value arg) { + updateQubitValueTracking(updatedArg, arg); + thenResult = thenBody(arg); + return thenResult; + }; + if (elseBody) { + Value elseResult; + auto ifOp = IfOp::create(*this, conditionValue, updatedArg, trackedThenBody, + [&](Value arg) { + updateQubitValueTracking(thenResult, arg); + elseResult = elseBody(arg); + return elseResult; + }); + updateQubitValueTracking(elseResult, ifOp.getResult(0)); + return ifOp.getResult(0); + } + + auto ifOp = IfOp::create(*this, conditionValue, updatedArg, trackedThenBody); + updateQubitValueTracking(thenResult, ifOp.getResult(0)); + return ifOp.getResult(0); +} + QCOProgramBuilder& QCOProgramBuilder::scfCondition(Value condition, ValueRange yieldedValues) { checkFinalized(); diff --git a/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp index 8832162354..db13c71400 100644 --- a/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/QTensor/IR/QTensorUtils.h" @@ -101,6 +102,20 @@ struct RemoveResetAfterExtract final : OpRewritePattern { } }; +/** + * @brief Remove dead gates. + */ +struct DeadGateElimination final : OpRewritePattern { + + explicit DeadGateElimination(MLIRContext* context) + : OpRewritePattern(context) {} + + LogicalResult matchAndRewrite(ResetOp op, + PatternRewriter& rewriter) const override { + return tryEliminateDeadGateValue(op.getQubitIn(), rewriter); + } +}; + } // namespace OpFoldResult ResetOp::fold(FoldAdaptor /*adaptor*/) { @@ -113,5 +128,5 @@ OpFoldResult ResetOp::fold(FoldAdaptor /*adaptor*/) { void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index c07fa324aa..16b8da3c7d 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -8,40 +8,18 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/QCOUtils.h" -#include -#include -#include #include -#include #include #include -#include #include #include using namespace mlir; using namespace mlir::qco; -/** - * @brief Check if given quantum operation is unused (i.e., only used by - * sinks and no memory effects). - * - * @param op The operation to check. - * @return bool True if the operation is unused, false otherwise. - */ -static bool checkDeadGate(Operation* op) { - if (!isMemoryEffectFree(op)) { - // This ignores operations and regions that have children with memory - // effects, which should never be considered dead. - return false; - } - return llvm::all_of(op->getUsers(), - [](Operation* user) { return isa(user); }); -} - namespace { /** @@ -74,49 +52,7 @@ struct DeadGateElimination final : OpRewritePattern { LogicalResult matchAndRewrite(SinkOp op, PatternRewriter& rewriter) const override { - Value currentValue = op.getQubit(); - auto* currentOp = currentValue.getDefiningOp(); - auto success = false; - while (currentOp != nullptr) { - if (!checkDeadGate(currentOp)) { - break; - } - - currentValue = - TypeSwitch(currentOp) - .Case([&](auto measureOp) { - auto newValue = measureOp.getQubitIn(); - rewriter.replaceAllUsesWith(measureOp.getQubitOut(), newValue); - rewriter.eraseOp(measureOp); - return newValue; - }) - .Case([&](auto ifOp) { - auto* tiedQubit = - ifOp.getTiedQubit(cast(currentValue)); - auto newValue = tiedQubit->get(); - rewriter.replaceOp(ifOp, ifOp.getQubits()); - return newValue; - }) - .Case([&](auto resetOp) { - auto newValue = resetOp.getQubitIn(); - rewriter.replaceOp(resetOp, resetOp->getOperands()); - return newValue; - }) - .Case([&](auto unitaryOp) { - auto newValue = unitaryOp.getInputForOutput(currentValue); - rewriter.replaceOp(unitaryOp, unitaryOp.getInputQubits()); - return newValue; - }) - .Default([&](auto) { return nullptr; }); - - if (currentValue == nullptr) { - break; - } - currentOp = currentValue.getDefiningOp(); - success = true; - } - - return llvm::success(success); + return tryEliminateDeadGateValue(op.getQubit(), rewriter); } }; diff --git a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp index f22e963584..2b515b858b 100644 --- a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp @@ -61,6 +61,27 @@ void IfOp::build(OpBuilder& odsBuilder, OperationState& odsState, } } +void IfOp::build(OpBuilder& odsBuilder, OperationState& odsState, + Value condition, Value input, + function_ref thenBuilder, + function_ref elseBuilder) { + build(odsBuilder, odsState, input.getType(), condition, input); + + auto& thenBlock = odsState.regions.front()->emplaceBlock(); + auto& elseBlock = odsState.regions.back()->emplaceBlock(); + + const OpBuilder::InsertionGuard guard(odsBuilder); + const auto location = odsState.location; + auto thenArgument = thenBlock.addArgument(input.getType(), location); + odsBuilder.setInsertionPointToStart(&thenBlock); + YieldOp::create(odsBuilder, location, thenBuilder(thenArgument)); + + auto elseArgument = elseBlock.addArgument(input.getType(), location); + odsBuilder.setInsertionPointToStart(&elseBlock); + YieldOp::create(odsBuilder, location, + elseBuilder ? elseBuilder(elseArgument) : elseArgument); +} + // Adjusted from // https://github.com/llvm/llvm-project/blob/llvmorg-22.1.1/mlir/lib/Dialect/SCF/IR/SCF.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp index b8d6fbb571..57239e6ae1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp @@ -8,10 +8,6 @@ * Licensed under the MIT License */ -// -// Created by damian on 5/13/26. -// - #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp new file mode 100644 index 0000000000..afe9fc081f --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp @@ -0,0 +1,201 @@ +/* + * 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 "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mlir::qco { + +#define GEN_PASS_DEF_REPLACECLASSICALCONTROLS +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +/** + * @brief Retrieves the measurement outcome that directly precedes the given + * qubit, if it exists. + * @param qubit The qubit for which to find the predecessor measurement outcome + * @return The measurement outcome if a predecessor measurement exists, nullptr + * otherwise + */ +static Value getPredecessorMeasurementOutcome(Value qubit) { + auto* definingOp = qubit.getDefiningOp(); + if (auto measureOp = dyn_cast_or_null(definingOp)) { + return measureOp.getResult(); + } + return nullptr; +} + +/** + * @brief Checks if the given operation is a phase gate, i.e., it only + * applies a phase to the target qubit(s) in the 1 state. + * @param op The operation to check + * @return true if the operation is a phase gate, false otherwise + */ +static bool isPhaseGate(Operation* op) { + return isa(op); +} + +/** + * @brief For a phase gate whose target has a predecessor measurement, swaps the + * target with an eligible control. + * @param op The control operation containing the phase gate + * @param rewriter The pattern rewriter used to perform the transformation + */ +static void trySwapControlAndTargetOfPhaseGate(CtrlOp op, + PatternRewriter& rewriter) { + assert(op.getNumTargets() == 1 && + "Only single-qubit gates can be swapped around controls"); + auto target = op.getTargetsIn()[0]; + auto predecessorOutcome = getPredecessorMeasurementOutcome(target); + if (!predecessorOutcome) { + // No advantage gained from swapping. + return; + } + + size_t controlIndex = 0; + for (auto control : op.getControlsIn()) { + auto controlOutcome = getPredecessorMeasurementOutcome(control); + if (controlOutcome) { + controlIndex++; + continue; + } + + Value controlOut = op.getControlsOut()[controlIndex]; + Value targetOut = op.getTargetsOut()[0]; + + rewriter.modifyOpInPlace(op, [&]() { + op.getTargetsInMutable()[0].set(control); + op.getControlsInMutable()[controlIndex].set(target); + }); + + // This works because each qubit is only ever used once. + auto controlUse = controlOut.getUses().begin(); + auto targetUse = targetOut.getUses().begin(); + controlUse->set(targetOut); + targetUse->set(controlOut); + + break; + } +} + +namespace { +/** + * @brief This pattern is responsible for replacing controls after measurements + * with `if` constructs. + */ +struct ReplaceBasisStateControlsWithIfPattern final + : OpRewritePattern { + + explicit ReplaceBasisStateControlsWithIfPattern(MLIRContext* context) + : OpRewritePattern(context) {} + + LogicalResult matchAndRewrite(MeasureOp measure, + PatternRewriter& rewriter) const override { + auto ctrlOp = dyn_cast(*measure.getQubitOut().getUsers().begin()); + if (!ctrlOp) { + return failure(); + } + rewriter.setInsertionPointAfter(ctrlOp); + + if (auto unitary = + utils::getSoleBodyUnitary(*ctrlOp.getBody()); + unitary && isPhaseGate(unitary.getOperation())) { + trySwapControlAndTargetOfPhaseGate(ctrlOp, rewriter); + } + + ValueRange controlsIn = ctrlOp.getControlsIn(); + ValueRange controlResults = ctrlOp.getControlsOut(); + + SmallVector ifOperands; + ifOperands.reserve(ctrlOp.getNumQubits()); + SmallVector oldOutputs; + oldOutputs.reserve(ctrlOp->getNumResults()); + Value condition; + for (auto [control, oldOutput] : + llvm::zip_equal(controlsIn, controlResults)) { + if (Value outcome = getPredecessorMeasurementOutcome(control)) { + rewriter.replaceAllUsesWith(oldOutput, control); + condition = condition ? arith::AndIOp::create(rewriter, ctrlOp.getLoc(), + condition, outcome) + .getResult() + : outcome; + } else { + ifOperands.push_back(control); + oldOutputs.push_back(oldOutput); + } + } + + if (!condition) { + return failure(); + } + + const auto numRemaining = ifOperands.size(); + llvm::append_range(ifOperands, ctrlOp.getTargetsIn()); + llvm::append_range(oldOutputs, ctrlOp.getTargetsOut()); + + auto ifOp = IfOp::create( + rewriter, ctrlOp.getLoc(), condition, ifOperands, + [&](ValueRange qubits) -> SmallVector { + auto newCtrl = CtrlOp::create(rewriter, ctrlOp.getLoc(), + qubits.take_front(numRemaining), + qubits.drop_front(numRemaining)); + rewriter.inlineRegionBefore(ctrlOp.getRegion(), newCtrl.getRegion(), + newCtrl.getRegion().begin()); + return newCtrl.getOutputQubits(); + }); + + rewriter.replaceAllUsesWith(oldOutputs, ifOp.getResults()); + rewriter.eraseOp(ctrlOp); + + return success(); + } +}; + +/** + * @brief Pass replaces controls with `IfOp` operations if the qubits' + * control values are available classically. + */ +struct ReplaceClassicalControls final + : impl::ReplaceClassicalControlsBase { + using ReplaceClassicalControlsBase::ReplaceClassicalControlsBase; + +protected: + void runOnOperation() override { + const auto op = getOperation(); + auto* ctx = &getContext(); + + // Define the set of patterns to use. + RewritePatternSet patterns(ctx); + patterns.add(patterns.getContext()); + + // Apply patterns in an iterative and greedy manner. + if (failed(applyPatternsGreedily(op, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt b/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt index c671a9f564..0908de519f 100644 --- a/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt @@ -14,7 +14,7 @@ target_sources(MLIRQCOMatrix PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Utils/Matrix.h) file(GLOB_RECURSE UTILS_CPP "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") -list(FILTER UTILS_CPP EXCLUDE REGEX "Matrix\\.cpp$") +list(FILTER UTILS_CPP EXCLUDE REGEX "(DDFunctionality|Matrix)\\.cpp$") add_mlir_dialect_library( MLIRQCOUtils @@ -34,7 +34,7 @@ mqt_mlir_target_use_project_options(MLIRQCOUtils) # collect header files file(GLOB_RECURSE UTILS_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Utils/*.h") -list(FILTER UTILS_HEADERS_SOURCE EXCLUDE REGEX "Matrix\\.h$") +list(FILTER UTILS_HEADERS_SOURCE EXCLUDE REGEX "(DDFunctionality|Matrix)\\.h$") file(GLOB_RECURSE UTILS_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/Utils/*.inc") # add public headers using file sets @@ -52,3 +52,24 @@ target_sources( ${MQT_MLIR_BUILD_INCLUDE_DIR} FILES ${UTILS_HEADERS_BUILD}) + +add_mlir_library( + MLIRQCODDFunctionality + PARTIAL_SOURCES_INTENDED + DDFunctionality.cpp + DEPENDS + MLIRQCOOpsIncGen + MLIRQCOInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRQCODialect + MLIRQCOMatrix + MLIRFuncDialect + MQT::CoreDD) + +mqt_mlir_target_use_project_options(MLIRQCODDFunctionality) + +target_sources( + MLIRQCODDFunctionality + PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Utils/DDFunctionality.h) diff --git a/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp new file mode 100644 index 0000000000..8bbbb97f0b --- /dev/null +++ b/mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp @@ -0,0 +1,415 @@ +/* + * 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 "mlir/Dialect/QCO/Utils/DDFunctionality.h" + +#include "dd/DDDefinitions.hpp" +#include "dd/Operations.hpp" +#include "dd/Package.hpp" +#include "ir/Definitions.hpp" +#include "ir/operations/Control.hpp" +#include "ir/operations/OpType.hpp" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/Matrix.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { +namespace { + +struct QubitMap { + DenseMap qubits; + size_t numQubits = 0; + + void bind(Value value, qc::Qubit q) { qubits[value] = q; } + + [[nodiscard]] std::optional lookup(Value value) const { + const auto it = qubits.find(value); + if (it == qubits.end()) { + return std::nullopt; + } + return it->second; + } + + LogicalResult remapUnitary(UnitaryOpInterface unitary) { + for (auto [in, out] : + llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { + const auto q = lookup(in); + if (!q) { + return unitary.emitError() + << "qubit SSA value is not mapped for QCO DD construction"; + } + bind(out, *q); + } + return success(); + } + + FailureOr> lookupRange(ValueRange values, + Operation* op) const { + SmallVector out; + out.reserve(values.size()); + for (Value value : values) { + const auto q = lookup(value); + if (!q) { + return op->emitError() + << "qubit SSA value is not mapped for QCO DD construction"; + } + out.push_back(*q); + } + return out; + } +}; + +struct DecodedGate { + qc::OpType type = qc::OpType::None; + std::vector params; +}; + +} // namespace + +/// `std::nullopt` if @p unitary is not a standard gate; failure if its unitary +/// matrix is not known at compile time. +static FailureOr> +decodeStandardGate(UnitaryOpInterface unitary) { + Operation* op = unitary.getOperation(); + const qc::OpType 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) { + return std::optional{std::nullopt}; + } + if (!unitary.hasCompileTimeKnownUnitaryMatrix()) { + return unitary.emitError() + << "unitary must have a compile-time constant matrix"; + } + + DecodedGate decoded{.type = type, .params = {}}; + for (Value param : unitary.getParameters()) { + decoded.params.push_back(static_cast(*utils::valueToDouble(param))); + } + return std::optional{std::move(decoded)}; +} + +/// QCO matrices are MSB-first (operand 0 = high bit); DD is LSB-first. +[[nodiscard]] static dd::CMat toCMatInDdBasis(const DynamicMatrix& qcoMatrix, + size_t numQubits) { + const auto dim = static_cast(qcoMatrix.rows()); + const auto shift = static_cast(64 - numQubits); + dd::CMat out(dim, dd::CVec(dim)); + for (size_t row = 0; row < dim; ++row) { + for (size_t col = 0; col < dim; ++col) { + const auto qcoRow = llvm::reverseBits(row) >> shift; + const auto qcoCol = llvm::reverseBits(col) >> shift; + out[row][col] = + qcoMatrix(static_cast(qcoRow), static_cast(qcoCol)); + } + } + return out; +} + +template +static LogicalResult applyUnitaryMatrix(UnitaryOpInterface unitary, + QubitMap& qubits, dd::Package& dd, + StateDD& state) { + Operation* op = unitary.getOperation(); + if (!unitary.hasCompileTimeKnownUnitaryMatrix()) { + return unitary.emitError() + << "unitary must have a compile-time constant matrix"; + } + if (auto gphase = dyn_cast(op)) { + const auto theta = *utils::valueToDouble(gphase.getTheta()); + auto id = dd::Package::makeIdent(); + id.w = dd.cn.lookup(std::cos(theta), std::sin(theta)); + state = dd.applyOperation(id, state); + return success(); + } + if (isa(op)) { + return qubits.remapUnitary(unitary); + } + + DynamicMatrix local; + if (!unitary.getUnitaryMatrixDynamic(local)) { + return unitary.emitError() + << "unitary must have a compile-time constant matrix"; + } + + auto wiresOr = qubits.lookupRange(unitary.getInputQubits(), op); + if (failed(wiresOr)) { + return failure(); + } + const ArrayRef wires = *wiresOr; + + if (wires.size() == 1) { + const dd::GateMatrix mat{local(0, 0), local(0, 1), local(1, 0), + local(1, 1)}; + state = dd.applyOperation(dd.makeGateDD(mat, wires[0]), state); + return qubits.remapUnitary(unitary); + } + + if (wires.size() == 2) { + dd::TwoQubitGateMatrix mat{}; + for (size_t row = 0; row < mat.size(); ++row) { + for (size_t col = 0; col < mat[row].size(); ++col) { + mat[row][col] = + local(static_cast(row), static_cast(col)); + } + } + state = dd.applyOperation(dd.makeTwoQubitGateDD(mat, wires[0], wires[1]), + state); + return qubits.remapUnitary(unitary); + } + + // Map full-width matrices from QCO/MSB order to DD/LSB. Cap at 12 qubits + // (~256 MiB dense `CMat`). + if (qubits.numQubits > 12) { + return unitary.emitError() + << "QCO DD matrix fallback supports at most 12 qubits"; + } + + if (wires.size() != qubits.numQubits || + !llvm::all_of(llvm::enumerate(wires), + [](const auto& it) { return it.value() == it.index(); })) { + return op->emitError() + << "QCO DD matrix fallback supports full-width unitaries on qubits " + "0..n-1"; + } + + state = dd.applyOperation( + dd.makeDDFromMatrix(toCMatInDdBasis(local, qubits.numQubits)), state); + return qubits.remapUnitary(unitary); +} + +template +static LogicalResult +applyDecodedStandard(UnitaryOpInterface unitary, const DecodedGate& gate, + const qc::Controls& controls, QubitMap& qubits, + dd::Package& dd, StateDD& state) { + SmallVector targetVals; + for (size_t i = 0; i < unitary.getNumTargets(); ++i) { + targetVals.push_back(unitary.getInputTarget(i)); + } + auto targets = qubits.lookupRange(targetVals, unitary.getOperation()); + if (failed(targets)) { + return failure(); + } + state = dd.applyOperation( + getStandardOperationDD(dd, gate.type, gate.params, controls, + {targets->begin(), targets->end()}), + state); + return qubits.remapUnitary(unitary); +} + +static LogicalResult validateReturn(func::ReturnOp returnOp, + const QubitMap& qubits) { + qc::Qubit expected = 0; + for (Value value : returnOp.getOperands()) { + if (!isa(value.getType())) { + continue; + } + const auto mapped = qubits.lookup(value); + if (!mapped) { + return returnOp.emitError() + << "returned qubit SSA value is not mapped for QCO DD " + "construction"; + } + if (*mapped != expected) { + return returnOp.emitError() + << "returned qubits must preserve canonical wire order; qubit " + "result " + << static_cast(expected) << " maps to wire " + << static_cast(*mapped); + } + ++expected; + } + return success(); +} + +template +static LogicalResult applyOp(Operation& op, QubitMap& qubits, dd::Package& dd, + StateDD& state) { + return TypeSwitch(&op) + .template Case( + [](auto) { return success(); }) + .template Case([&](func::ReturnOp returnOp) { + return validateReturn(returnOp, qubits); + }) + .template Case([&](CtrlOp ctrlOp) -> LogicalResult { + if (auto inner = utils::getSoleBodyUnitary( + *ctrlOp.getBody())) { + auto decoded = decodeStandardGate(inner); + if (failed(decoded)) { + return failure(); + } + if (*decoded) { + auto controlQubits = + qubits.lookupRange(ctrlOp.getControlsIn(), ctrlOp); + if (failed(controlQubits)) { + return failure(); + } + qc::Controls controls; + for (qc::Qubit q : *controlQubits) { + controls.emplace(q); + } + return applyDecodedStandard(ctrlOp, **decoded, controls, qubits, dd, + state); + } + } + return applyUnitaryMatrix(ctrlOp, qubits, dd, state); + }) + .template Case( + [&](UnitaryOpInterface unitary) -> LogicalResult { + auto decoded = decodeStandardGate(unitary); + if (failed(decoded)) { + return failure(); + } + if (*decoded) { + return applyDecodedStandard(unitary, **decoded, {}, qubits, dd, + state); + } + return applyUnitaryMatrix(unitary, qubits, dd, state); + }) + .Default([](Operation* unsupported) { + return unsupported->emitError() + << "unsupported op for QCO DD construction: " + << unsupported->getName().getStringRef(); + }); +} + +template +static LogicalResult walk(func::FuncOp func, QubitMap& qubits, dd::Package& dd, + StateDD& state) { + for (Operation& op : func.getBody().front()) { + if (failed(applyOp(op, qubits, dd, state))) { + return failure(); + } + } + return success(); +} + +static FailureOr prepare(func::FuncOp func, const dd::Package& dd) { + if (!func.getBody().hasOneBlock()) { + return func.emitError() + << "QCO DD construction expects a single-block function body"; + } + + QubitMap qubits; + for (StaticOp staticOp : func.getBody().front().getOps()) { + const auto q = static_cast(staticOp.getIndex()); + qubits.bind(staticOp.getQubit(), q); + qubits.numQubits = std::max(qubits.numQubits, static_cast(q) + 1); + } + // No `qco.static`: treat qubit-typed block arguments as wires `0..n-1`. + if (qubits.numQubits == 0) { + qc::Qubit next = 0; + for (Value arg : func.getArguments()) { + if (!isa(arg.getType())) { + continue; + } + qubits.bind(arg, next); + qubits.numQubits = + std::max(qubits.numQubits, static_cast(next) + 1); + ++next; + } + } + if (dd.qubits() < qubits.numQubits) { + return func.emitError() << "DD package has " << dd.qubits() + << " qubits but function uses " << qubits.numQubits; + } + return qubits; +} + +FailureOr buildFunctionality(func::FuncOp func, dd::Package& dd) { + auto qubitsOr = prepare(func, dd); + if (failed(qubitsOr)) { + return failure(); + } + QubitMap qubits = std::move(*qubitsOr); + + dd::MatrixDD state = + qubits.numQubits == 0 + ? dd::MatrixDD::one() + : dd.createInitialMatrix(std::vector(qubits.numQubits, false)); + if (failed(walk(func, qubits, dd, state))) { + if (qubits.numQubits != 0) { + dd.decRef(state); + } + return failure(); + } + return state; +} + +FailureOr simulate(func::FuncOp func, const dd::VectorDD& in, + dd::Package& dd) { + auto qubitsOr = prepare(func, dd); + if (failed(qubitsOr)) { + dd.decRef(in); + return failure(); + } + QubitMap qubits = std::move(*qubitsOr); + + dd::VectorDD state = in; + if (failed(walk(func, qubits, dd, state))) { + dd.decRef(state); + return failure(); + } + return state; +} + +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index c4f54c67dc..4d96f48706 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -125,12 +125,9 @@ TEST_F(QCOTest, DirectIfBuilder) { auto extractOp = qtensor::ExtractOp::create(builder, r0, c0); auto q1 = HOp::create(builder, extractOp.getResult()); auto measureOp = MeasureOp::create(builder, q1); - auto ifOp = - IfOp::create(builder, measureOp.getResult(), measureOp.getQubitOut(), - [&](ValueRange qubits) -> SmallVector { - auto innerQubit = XOp::create(builder, qubits[0]); - return SmallVector{innerQubit}; - }); + auto ifOp = IfOp::create( + builder, measureOp.getResult(), measureOp.getQubitOut(), + [&](Value qubit) -> Value { return {XOp::create(builder, qubit)}; }); auto finalMeasureOp = MeasureOp::create(builder, ifOp.getResult(0)); auto r2 = qtensor::InsertOp::create(builder, finalMeasureOp.getQubitOut(), extractOp.getOutTensor(), c0); @@ -210,6 +207,8 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ifTwoQubits)}, QCOTestCase{"IfElse", MQT_NAMED_BUILDER(ifElse), MQT_NAMED_BUILDER(ifElse)}, + QCOTestCase{"OneTensorIf", MQT_NAMED_BUILDER(ifOneTensor), + MQT_NAMED_BUILDER(x)}, QCOTestCase{"ConstantTrueIf", MQT_NAMED_BUILDER(constantTrueIf), MQT_NAMED_BUILDER(x)}, QCOTestCase{"ConstantFalseIf", MQT_NAMED_BUILDER(constantFalseIf), @@ -1190,6 +1189,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(staticQubitsWithInv)}, QCOTestCase{"DeadGateElimination", MQT_NAMED_BUILDER(deadGatesProgram), MQT_NAMED_BUILDER(alloc2Qubits)}, + QCOTestCase{"DeadGateEliminationReset", + MQT_NAMED_BUILDER(deadGatesResetProgram), + MQT_NAMED_BUILDER(allocQubit)}, QCOTestCase{"DeadGateEliminationIfOp", MQT_NAMED_BUILDER(deadGatesWithIfOpProgram), MQT_NAMED_BUILDER(deadGatesWithIfOpSimplified)}, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt index c6160821cd..ed1e823f2c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries( ${target_name} PRIVATE GTest::gtest_main MLIRQCOProgramBuilder + MLIRQCODDFunctionality MLIRQCOTransforms MLIRQCOUtils MLIRPass diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index 2b822c6cba..fafa1be842 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -8,16 +8,17 @@ * Licensed under the MIT License */ +#include "dd/Package.hpp" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/Weyl.h" +#include "mlir/Dialect/QCO/Utils/DDFunctionality.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" #include -#include #include #include #include @@ -47,8 +48,6 @@ using namespace mlir; using namespace mlir::qco; using namespace mlir::qco::decomposition; -using QubitId = std::size_t; - static constexpr Matrix4x4 TWO_QUBIT_CONTROLLED_X01 = Matrix4x4::fromElements(1.0, 0.0, 0.0, 0.0, // 0.0, 1.0, 0.0, 0.0, // @@ -439,99 +438,21 @@ struct Synthesized2QCircuit { } // namespace -[[nodiscard]] static std::optional -lookupWireId(const llvm::DenseMap& wireIds, Value wire) { - if (const auto it = wireIds.find(wire); it != wireIds.end()) { - return it->second; - } - return std::nullopt; -} - -[[nodiscard]] static std::optional -embeddedStepOnWires(UnitaryOpInterface op, QubitId q0, - std::optional q1) { - if (op.isSingleQubit()) { - Matrix2x2 matrix; - if (!op.getUnitaryMatrix2x2(matrix)) { - return std::nullopt; - } - return matrix.embedInTwoQubit(q0); - } - if (!q1.has_value()) { - return std::nullopt; - } - Matrix4x4 matrix; - if (!op.getUnitaryMatrix4x4(matrix)) { - return std::nullopt; - } - return matrix.reorderForQubits(q0, *q1); -} - -static std::optional +[[nodiscard]] static FailureOr computeTwoQubitUnitaryFromFunc(func::FuncOp funcOp) { - Matrix4x4 unitary = Matrix4x4::identity(); - Complex global{1.0, 0.0}; - llvm::DenseMap wireIds; - wireIds[funcOp.getArgument(0)] = 0; - wireIds[funcOp.getArgument(1)] = 1; - - for (Operation& op : funcOp.getBody().front()) { - if (isa(op)) { - continue; - } - - if (auto returnOp = dyn_cast(op)) { - if (returnOp.getNumOperands() != 2) { - return std::nullopt; - } - const auto out0 = lookupWireId(wireIds, returnOp.getOperand(0)); - const auto out1 = lookupWireId(wireIds, returnOp.getOperand(1)); - if (!out0.has_value() || !out1.has_value() || *out0 != 0 || *out1 != 1) { - return std::nullopt; - } - continue; - } - - if (auto gphase = dyn_cast(op)) { - if (const auto matrix = gphase.getUnitaryMatrix()) { - global *= (*matrix)(0, 0); - } - continue; - } - - auto unitaryOp = dyn_cast(op); - if (!unitaryOp) { - return std::nullopt; - } - - const auto q0 = lookupWireId(wireIds, unitaryOp.getInputQubit(0)); - if (!q0.has_value()) { - return std::nullopt; - } - std::optional q1; - if (unitaryOp.isTwoQubit()) { - q1 = lookupWireId(wireIds, unitaryOp.getInputQubit(1)); - if (!q1.has_value()) { - return std::nullopt; - } - } else if (!unitaryOp.isSingleQubit()) { - return std::nullopt; - } - - const auto step = embeddedStepOnWires(unitaryOp, *q0, q1); - if (!step.has_value()) { - return std::nullopt; - } - unitary.premultiplyBy(*step); - - wireIds[unitaryOp.getOutputQubit(0)] = *q0; - if (q1.has_value()) { - wireIds[unitaryOp.getOutputQubit(1)] = *q1; - } + auto dd = std::make_unique(2); + auto u = buildFunctionality(funcOp, *dd); + if (failed(u)) { + return failure(); } - - unitary *= global; - return unitary; + // `getMatrix` is DD/LSB-first; QCO is MSB-first — index `1↔2` swaps the + // middle basis states (`|01>` ↔ `|10>`). + const auto& m = u->getMatrix(2); + const Matrix4x4 matrix = Matrix4x4::fromElements( + m[0][0], m[0][2], m[0][1], m[0][3], m[2][0], m[2][2], m[2][1], m[2][3], + m[1][0], m[1][2], m[1][1], m[1][3], m[3][0], m[3][2], m[3][1], m[3][3]); + dd->decRef(*u); + return matrix; } [[nodiscard]] static Synthesized2QCircuit @@ -565,7 +486,7 @@ static void expectSynthesized2QMatrix(MLIRContext* ctx, const Matrix4x4& target, const auto circuit = synthesize2QMatrix(ctx, target, spec); ASSERT_TRUE(succeeded(verify(*circuit.mlirModule))); const auto actual = computeTwoQubitUnitaryFromFunc(circuit.func); - ASSERT_TRUE(actual.has_value()); + ASSERT_TRUE(succeeded(actual)); EXPECT_TRUE(actual->isApprox(target, WEYL_TOLERANCE)); } @@ -662,9 +583,9 @@ TEST_F(NativeGatesetMlirTest, ReconstructionRejectsUnhandledOps) { builder.setInsertionPointToStart(entry); Value q0 = entry->getArgument(0); Value q1 = entry->getArgument(1); - BarrierOp::create(builder, loc, ValueRange{q0, q1}); - func::ReturnOp::create(builder, loc, ValueRange{q0, q1}); - EXPECT_FALSE(computeTwoQubitUnitaryFromFunc(func).has_value()); + auto meas = MeasureOp::create(builder, loc, q0); + func::ReturnOp::create(builder, loc, ValueRange{meas.getQubitOut(), q1}); + EXPECT_TRUE(failed(computeTwoQubitUnitaryFromFunc(func))); } TEST_F(NativeGatesetMlirTest, SynthesisFailsWithoutEulerBasis) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt index 0fa87a5daf..e70a874187 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries( GTest::gtest_main MLIRQCPrograms MLIRQCOProgramBuilder + MLIRQCODDFunctionality MLIRQCOUtils MLIRQCToQCO MLIRQCOTransforms diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 14c76e7e71..6eec2eaf7e 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -8,19 +8,18 @@ * Licensed under the MIT License */ +#include "dd/Package.hpp" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QCO/Utils/DDFunctionality.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" #include "qc_programs.h" #include -#include -#include -#include #include #include #include @@ -50,13 +49,7 @@ using namespace mlir; using namespace mlir::qco; -using ProgramFn = SmallVector (*)(mlir::qc::QCProgramBuilder&); - -static SmallVector measureAndReturn(mlir::qc::QCProgramBuilder& b, - ValueRange qubits) { - return llvm::to_vector( - llvm::map_range(qubits, [&](Value q) { return b.measure(q); })); -} +using ProgramFn = Value (*)(mlir::qc::QCProgramBuilder&); // --- Native-gateset membership check ------------------------------------- // @@ -85,168 +78,31 @@ static bool allOpsNative(OwningOpRef& moduleOp, return ok; } -// --- Reference unitary reconstruction ------------------------------------ // - -static std::optional unitaryQubit(Value v, size_t index, - size_t numQubits) { - if (index >= numQubits || !isa(v.getType())) { - return std::nullopt; - } - return v; -} +// --- DD-based equivalence ------------------------------------------------ // -static std::optional unitaryQubitOperand(UnitaryOpInterface op, - size_t index) { - return unitaryQubit(op->getOperand(index), index, op.getNumQubits()); +[[nodiscard]] static func::FuncOp mainFunc(ModuleOp module) { + return *module.getBody()->getOps().begin(); } -static std::optional unitaryQubitResult(UnitaryOpInterface op, - size_t index) { - return unitaryQubit(op->getResult(index), index, op.getNumQubits()); -} - -static bool extractSingleQubitMatrix(UnitaryOpInterface op, Matrix2x2& out) { - if (op.getUnitaryMatrix2x2(out)) { - return true; - } - DynamicMatrix dynamic; - if (!op.getUnitaryMatrixDynamic(dynamic) || dynamic.rows() != 2 || - dynamic.cols() != 2) { - return false; - } - out = Matrix2x2::fromElements(dynamic(0, 0), dynamic(0, 1), dynamic(1, 0), - dynamic(1, 1)); - return true; -} - -static bool extractTwoQubitMatrix(UnitaryOpInterface op, Matrix4x4& out) { - if (auto ctrl = dyn_cast(op.getOperation()); - ctrl && (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1)) { - return false; - } - return op.getUnitaryMatrix4x4(out); -} - -static std::optional -computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { - ModuleOp module = moduleOp.get(); - if (!module) { - return std::nullopt; - } - - DenseMap qubitIds; - size_t nextQubitId = 0; +[[nodiscard]] static size_t countStaticQubits(func::FuncOp func) { size_t numQubits = 0; - - for (auto func : module.getOps()) { - for (auto& block : func.getBlocks()) { - for (auto& rawOp : block.getOperations()) { - if (auto staticOp = dyn_cast(&rawOp)) { - const auto index = static_cast(staticOp.getIndex()); - qubitIds.try_emplace(staticOp.getQubit(), index); - numQubits = std::max(numQubits, index + 1); - } else if (auto alloc = dyn_cast(&rawOp)) { - qubitIds.try_emplace(alloc.getResult(), nextQubitId++); - numQubits = std::max(numQubits, nextQubitId); - } - } - } + for (StaticOp staticOp : func.getOps()) { + numQubits = + std::max(numQubits, static_cast(staticOp.getIndex()) + 1); } + return numQubits; +} - if (numQubits == 0) { - return std::nullopt; - } - - DynamicMatrix unitary = - DynamicMatrix::identity(static_cast(1ULL << numQubits)); - Complex globalPhase{1.0, 0.0}; - - auto getQubitId = [&](Value qubit) -> std::optional { - const auto it = qubitIds.find(qubit); - if (it == qubitIds.end()) { - return std::nullopt; - } - return it->second; - }; - - for (auto func : module.getOps()) { - for (auto& block : func.getBlocks()) { - for (auto& rawOp : block.getOperations()) { - auto op = dyn_cast(&rawOp); - if (!op) { - continue; - } - if (isa(op.getOperation())) { - // A barrier is an identity on its wires, but it still threads qubit - // values, so carry each input's id over to the matching output. - for (size_t i = 0; i < op.getNumQubits(); ++i) { - if (const auto id = getQubitId(op->getOperand(i))) { - qubitIds[op->getResult(i)] = *id; - } - } - continue; - } - if (auto gphase = dyn_cast(op.getOperation())) { - if (const auto matrix = gphase.getUnitaryMatrix()) { - globalPhase *= (*matrix)(0, 0); - } - continue; - } - - if (op.isSingleQubit()) { - const auto qIn = unitaryQubitOperand(op, 0); - if (!qIn) { - return std::nullopt; - } - const auto qid = getQubitId(*qIn); - if (!qid) { - return std::nullopt; - } - Matrix2x2 oneQ; - if (!extractSingleQubitMatrix(op, oneQ)) { - return std::nullopt; - } - unitary = oneQ.embedInNqubit(numQubits, *qid) * unitary; - const auto qOut = unitaryQubitResult(op, 0); - if (!qOut) { - return std::nullopt; - } - qubitIds[*qOut] = *qid; - continue; - } - - if (op.isTwoQubit()) { - const auto q0In = unitaryQubitOperand(op, 0); - const auto q1In = unitaryQubitOperand(op, 1); - if (!q0In || !q1In) { - return std::nullopt; - } - const auto q0id = getQubitId(*q0In); - const auto q1id = getQubitId(*q1In); - if (!q0id || !q1id) { - return std::nullopt; - } - Matrix4x4 twoQ; - if (!extractTwoQubitMatrix(op, twoQ)) { - return std::nullopt; - } - unitary = twoQ.embedInNqubit(numQubits, *q0id, *q1id) * unitary; - const auto q0Out = unitaryQubitResult(op, 0); - const auto q1Out = unitaryQubitResult(op, 1); - if (!q0Out || !q1Out) { - return std::nullopt; - } - qubitIds[*q0Out] = *q0id; - qubitIds[*q1Out] = *q1id; - continue; - } - - return std::nullopt; - } +[[nodiscard]] static DynamicMatrix matrixFromDD(const dd::CMat& matrix) { + const auto dim = static_cast(matrix.size()); + DynamicMatrix out(dim); + for (int64_t row = 0; row < dim; ++row) { + for (int64_t col = 0; col < dim; ++col) { + out(row, col) = + matrix[static_cast(row)][static_cast(col)]; } } - - return globalPhase * unitary; + return out; } // --- Expressive circuits -------------------------------------------------- // @@ -254,17 +110,17 @@ computeUnitaryFromQcoModule(const OwningOpRef& moduleOp) { // A handful of circuits, which are crossed with the gateset table below. /// A bare SWAP (three-entangler class), the canonical two-qubit decomposition. -static SmallVector swapTwoQ(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value swapTwoQ(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.swap(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// Rich single-qubit variety on both wires, followed by a two-qubit entangler. -static SmallVector broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.x(q0); b.y(q1); b.h(q0); @@ -273,73 +129,73 @@ static SmallVector broadOneQThenCz(mlir::qc::QCProgramBuilder& b) { b.ry(-0.47, q1); b.rz(0.29, q0); b.cz(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// Long single-qubit run on one wire, then an entangler (Euler-run fusion). -static SmallVector hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value hstycxTwoQ(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.h(q0); b.s(q0); b.t(q0); b.y(q0); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// Zero-angle rotations that must canonicalize away before the entangler. -static SmallVector zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value zeroAngleThenCz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.rx(0.0, q0); b.ry(0.0, q1); b.rz(0.0, q0); b.p(0.0, q1); b.cz(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// Single-qubit gates surrounding an entangler on both sides. -static SmallVector hCxSq1(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value hCxSq1(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.h(q0); b.cx(q0, q1); b.s(q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// Three-qubit program with chained entanglers on overlapping pairs. -static SmallVector threeQGhz(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); +static Value threeQGhz(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); + const auto q2 = b.staticQubit(2); b.h(q0); b.cx(q0, q1); b.cx(q1, q2); - return measureAndReturn(b, {q0, q1, q2}); + return b.intConstant(0); } /// Single-qubit gates wrapped in an inverse modifier (no entangler). -static SmallVector inverseTwoX(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); +static Value inverseTwoX(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); b.inv(q0, [&](Value qubit) { b.x(qubit); b.x(qubit); }); - return measureAndReturn(b, {q0}); + return b.intConstant(0); } /// A controlled two-gate body that must be synthesized as a two-qubit unitary. -static SmallVector controlledXH(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value controlledXH(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.ctrl(q0, q1, [&](Value target) { b.x(target); b.h(target); }); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } // --- Fusion-window circuits ---------------------------------------------- // @@ -347,111 +203,105 @@ static SmallVector controlledXH(mlir::qc::QCProgramBuilder& b) { // These probe window geometry (where fusion starts/stops), so they run on a // single fixed gateset rather than the full table. -static SmallVector fusionCxCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionCxCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } -static SmallVector -fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionHCxInterleavedTCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.h(q0); b.cx(q0, q1); b.t(q1); b.s(q0); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } -static SmallVector fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); +static Value fusionThreeLineCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); + const auto q2 = b.staticQubit(2); b.cx(q0, q1); b.cx(q1, q2); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1, q2}); + return b.intConstant(0); } -static SmallVector -fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); +static Value fusionCxRSharedOtherPair(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); + const auto q2 = b.staticQubit(2); b.cx(q0, q1); b.rz(0.17, q1); b.cx(q1, q2); - return measureAndReturn(b, {q0, q1, q2}); + return b.intConstant(0); } -static SmallVector fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionCxBarrierCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.barrier({q0, q1}); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// A single-wire barrier between two entanglers: a non-walkable single-qubit /// shell terminates the run scan on wire A (and breaks the run-start chain of /// the second entangler), so neither entangler fuses. -static SmallVector -fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionCxSingleWireBarrierCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.barrier({q0}); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } /// An entangler followed by a three-qubit gate sharing both run wires: the run /// scan must stop at the wider gate (it is neither a single- nor a two-qubit /// run member) and leave it untouched, since gates on more than two qubits are /// out of scope for this pass rather than a failure. -static SmallVector -fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); - const auto q2 = b.allocQubit(); +static Value fusionCxThenMultiControlledX(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); + const auto q2 = b.staticQubit(2); b.cx(q0, q1); b.mcx({q0, q1}, q2); - return measureAndReturn(b, {q0, q1, q2}); + return b.intConstant(0); } -static SmallVector fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionSwapCxPattern(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.cx(q1, q0); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } -static SmallVector -fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionOffMenuGateInWindow(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.h(q0); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } -static SmallVector -fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { - const auto q0 = b.allocQubit(); - const auto q1 = b.allocQubit(); +static Value fusionDualWireOneQBetweenCx(mlir::qc::QCProgramBuilder& b) { + const auto q0 = b.staticQubit(0); + const auto q1 = b.staticQubit(1); b.cx(q0, q1); b.rz(0.11, q0); b.ry(0.22, q1); b.cx(q0, q1); - return measureAndReturn(b, {q0, q1}); + return b.intConstant(0); } static Value determinismSwap(mlir::qc::QCProgramBuilder& b) { @@ -542,11 +392,23 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { static void expectQcoModulesEquivalent(const OwningOpRef& lhs, const OwningOpRef& rhs) { - const auto lhsUnitary = computeUnitaryFromQcoModule(lhs); - ASSERT_TRUE(lhsUnitary.has_value()); - const auto rhsUnitary = computeUnitaryFromQcoModule(rhs); - ASSERT_TRUE(rhsUnitary.has_value()); - EXPECT_TRUE(lhsUnitary->isApprox(*rhsUnitary)); + const auto lhsFunc = mainFunc(*lhs); + const auto rhsFunc = mainFunc(*rhs); + const auto numQubits = countStaticQubits(lhsFunc); + ASSERT_EQ(numQubits, countStaticQubits(rhsFunc)); + ASSERT_GT(numQubits, 0U); + + auto dd = std::make_unique(numQubits); + const auto lhsUnitary = buildFunctionality(lhsFunc, *dd); + ASSERT_TRUE(succeeded(lhsUnitary)); + const auto rhsUnitary = buildFunctionality(rhsFunc, *dd); + ASSERT_TRUE(succeeded(rhsUnitary)); + + const auto lhsMatrix = matrixFromDD(lhsUnitary->getMatrix(numQubits)); + const auto rhsMatrix = matrixFromDD(rhsUnitary->getMatrix(numQubits)); + dd->decRef(*lhsUnitary); + dd->decRef(*rhsUnitary); + EXPECT_TRUE(lhsMatrix.isApprox(rhsMatrix)); } void expectEquivalentAndNativeAfterSynthesis(ProgramFn program, @@ -570,8 +432,9 @@ class FuseTwoQubitUnitaryRunsPassTest : public testing::Test { EXPECT_TRUE(failed(pm.run(*moduleOp))); } - void expectSynthesisFailure(Value (*program)(mlir::qc::QCProgramBuilder&), - StringRef nativeGates) { + void expectSynthesisFailure( + SmallVector (*program)(mlir::qc::QCProgramBuilder&), + StringRef nativeGates) { auto moduleOp = mlir::qc::QCProgramBuilder::build(context.get(), program); PassManager pm(moduleOp->getContext()); pm.addPass(createQCToQCO()); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index bdc78401c8..5a4180196a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -7,8 +7,11 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-optimizations) -add_executable(${target_name} test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp test_quantum_loop_unroll.cpp) +add_executable( + ${target_name} + test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp + test_qco_merge_single_qubit_rotation.cpp test_qco_replace_classical_controls.cpp + test_quantum_loop_unroll.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp index 00f19a9530..5ee7a318d9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp @@ -39,7 +39,7 @@ class QCOMeasurementLiftingTest : public testing::Test { MLIRContext context; QCOProgramBuilder programBuilder; QCOProgramBuilder referenceBuilder; - OwningOpRef module; + OwningOpRef program; OwningOpRef reference; QCOMeasurementLiftingTest() @@ -56,20 +56,20 @@ class QCOMeasurementLiftingTest : public testing::Test { /** * @brief Adds the measurementLiftingPass to the current context and runs it. */ - static LogicalResult runMeasurementLiftingPass(ModuleOp module) { - PassManager pm(module.getContext()); + static LogicalResult runMeasurementLiftingPass(ModuleOp program) { + PassManager pm(program.getContext()); pm.addPass(createMeasurementLifting()); pm.addPass(createCanonicalizerPass()); - return pm.run(module); + return pm.run(program); } /** * @brief Adds the canonicalizerPass to the current context and runs it. */ - static LogicalResult runCanonicalizerPass(ModuleOp module) { - PassManager pm(module.getContext()); + static LogicalResult runCanonicalizerPass(ModuleOp program) { + PassManager pm(program.getContext()); pm.addPass(createCanonicalizerPass()); - return pm.run(module); + return pm.run(program); } }; @@ -96,7 +96,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPositiveControl) { programBuilder.sink(q0); programBuilder.sink(q1); - module = programBuilder.finalize({c0, c1}); + program = programBuilder.finalize({c0, c1}); referenceBuilder.initialize( {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); @@ -116,11 +116,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPositiveControl) { referenceBuilder.sink(r1); reference = referenceBuilder.finalize({cr0, cr1}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -165,7 +165,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverOneOfMultipleControls) { programBuilder.sink(q1); programBuilder.sink(q2); - module = programBuilder.finalize({c0, c1, c2}); + program = programBuilder.finalize({c0, c1, c2}); referenceBuilder.initialize({referenceBuilder.getI1Type(), referenceBuilder.getI1Type(), @@ -206,11 +206,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverOneOfMultipleControls) { reference = referenceBuilder.finalize({cr0, cr1, cr2}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -241,7 +241,7 @@ TEST_F(QCOMeasurementLiftingTest, programBuilder.sink(q0Vec[0]); programBuilder.sink(q1); programBuilder.sink(q2); - module = programBuilder.finalize({c1, c2}); + program = programBuilder.finalize({c1, c2}); referenceBuilder.initialize( {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); @@ -266,11 +266,11 @@ TEST_F(QCOMeasurementLiftingTest, referenceBuilder.sink(r12[1]); reference = referenceBuilder.finalize({cr1, cr2}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -293,7 +293,7 @@ TEST_F(QCOMeasurementLiftingTest, programBuilder.sink(q0); programBuilder.sink(q1); - module = programBuilder.finalize({c0, c1}); + program = programBuilder.finalize({c0, c1}); referenceBuilder.initialize( {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); @@ -312,11 +312,11 @@ TEST_F(QCOMeasurementLiftingTest, referenceBuilder.sink(r1); reference = referenceBuilder.finalize({cr0, cr1}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -332,7 +332,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleX) { std::tie(q, c) = programBuilder.measure(q); q = programBuilder.h(q); programBuilder.sink(q); - module = programBuilder.finalize(c); + program = programBuilder.finalize(c); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r = referenceBuilder.allocQubit(); @@ -346,11 +346,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleX) { referenceBuilder.sink(r); reference = referenceBuilder.finalize(xorOp.getResult()); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -364,7 +364,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleY) { Value c; std::tie(q, c) = programBuilder.measure(q); programBuilder.sink(q); - module = programBuilder.finalize({c}); + program = programBuilder.finalize({c}); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r = referenceBuilder.allocQubit(); @@ -376,11 +376,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleY) { referenceBuilder.sink(r); reference = referenceBuilder.finalize({xorOp.getResult()}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -400,7 +400,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPhaseGates) { Value c; std::tie(q, c) = programBuilder.measure(q); programBuilder.sink(q); - module = programBuilder.finalize({c}); + program = programBuilder.finalize({c}); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r = referenceBuilder.allocQubit(); @@ -409,11 +409,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPhaseGates) { referenceBuilder.sink(r); reference = referenceBuilder.finalize({cr}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -427,7 +427,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverMultipleXY) { Value c; std::tie(q, c) = programBuilder.measure(q); programBuilder.sink(q); - module = programBuilder.finalize({c}); + program = programBuilder.finalize({c}); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r = referenceBuilder.allocQubit(); @@ -436,11 +436,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverMultipleXY) { referenceBuilder.sink(r); reference = referenceBuilder.finalize({cr}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -462,7 +462,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverXAndControlledGates) { programBuilder.sink(q0); programBuilder.sink(q1); - module = programBuilder.finalize({c0}); + program = programBuilder.finalize({c0}); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r0 = referenceBuilder.allocQubit(); @@ -479,11 +479,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverXAndControlledGates) { referenceBuilder.sink(r1); reference = referenceBuilder.finalize({cr0}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -504,7 +504,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverDiagonalGateInControl) { programBuilder.sink(q0); programBuilder.sink(q1); - module = programBuilder.finalize({c0, c1}); + program = programBuilder.finalize({c0, c1}); referenceBuilder.initialize( {referenceBuilder.getI1Type(), programBuilder.getI1Type()}); @@ -520,11 +520,11 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverDiagonalGateInControl) { referenceBuilder.sink(r1); reference = referenceBuilder.finalize({cr0, cr1}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -547,7 +547,7 @@ TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) { programBuilder.sink(q0S2); programBuilder.sink(q1S2); - module = programBuilder.finalize({c0, c1}); + program = programBuilder.finalize({c0, c1}); referenceBuilder.initialize( {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); @@ -567,11 +567,11 @@ TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) { referenceBuilder.sink(r1S2); reference = referenceBuilder.finalize({cr0, cr1}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } /** @@ -587,7 +587,7 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverInvertedPhaseGates) { Value c; std::tie(q, c) = programBuilder.measure(q); programBuilder.sink(q); - module = programBuilder.finalize({c}); + program = programBuilder.finalize({c}); referenceBuilder.initialize({referenceBuilder.getI1Type()}); auto r = referenceBuilder.allocQubit(); @@ -596,9 +596,9 @@ TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverInvertedPhaseGates) { referenceBuilder.sink(r); reference = referenceBuilder.finalize({cr}); - ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runMeasurementLiftingPass(program.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); + areModulesEquivalentWithPermutations(program.get(), reference.get())); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp new file mode 100644 index 0000000000..9a5a0c871b --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp @@ -0,0 +1,718 @@ +/* + * 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 "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Support/IRVerification.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +class QCOReplaceClassicalControlsTest : public testing::Test { + +protected: + MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + OwningOpRef program; + OwningOpRef reference; + + QCOReplaceClassicalControlsTest() + : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + // Register all necessary dialects + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + } + + /** + * @brief Adds the replaceClassicalControls pass to the current context and + * runs it. + */ + static LogicalResult + runReplaceClassicalControlsPass(ModuleOp program, + bool liftMeasurements = false) { + PassManager pm(program.getContext()); + pm.addPass(createReplaceClassicalControls()); + if (liftMeasurements) { + pm.addPass(createMeasurementLifting()); + } + pm.addPass(createCanonicalizerPass()); + return pm.run(program); + } + + /** + * @brief Adds the canonicalizerPass to the current context and runs it. + */ + static LogicalResult runCanonicalizerPass(ModuleOp program) { + PassManager pm(program.getContext()); + pm.addPass(createCanonicalizerPass()); + return pm.run(program); + } +}; + +} // namespace + +/** + * @brief Test: Tests replacing a classically controlled gate where there is + * only one control. + */ +TEST_F(QCOReplaceClassicalControlsTest, replaceClassicalControlsOnlyControl) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q0, q1) = programBuilder.cx(q0, q1); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + + programBuilder.sink(q0); + programBuilder.sink(q1); + program = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + r1 = referenceBuilder.qcoIf( + cr0, r1, [&](Value qubit) -> Value { return referenceBuilder.x(qubit); }); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where only one of + * two controls can be replaced. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsOneOfTwoControls) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + q2 = programBuilder.h(q2); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + + SmallVector q01; + SmallVector q2Vec; + std::tie(q01, q2Vec) = programBuilder.ctrl( + {q0, q1}, {q2}, [&](ValueRange targets) -> SmallVector { + return SmallVector{programBuilder.x(targets[0])}; + }); + + Value c1; + std::tie(q1, c1) = programBuilder.measure(q01[1]); + Value c2; + std::tie(q2, c2) = programBuilder.measure(q2Vec[0]); + + programBuilder.sink(q01[0]); + programBuilder.sink(q1); + programBuilder.sink(q2); + program = programBuilder.finalize({c0, c1, c2}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + r2 = referenceBuilder.h(r2); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + SmallVector r12 = referenceBuilder.qcoIf( + cr0, {r1, r2}, [&](ValueRange qubits) -> SmallVector { + Value t1 = qubits[0]; + Value t2 = qubits[1]; + std::tie(t1, t2) = referenceBuilder.cx(t1, t2); + return SmallVector{t1, t2}; + }); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r12[0]); + Value cr2; + std::tie(r2, cr2) = referenceBuilder.measure(r12[1]); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + referenceBuilder.sink(r2); + + reference = referenceBuilder.finalize({cr0, cr1, cr2}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where both of the + * two controls can be replaced. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsTwoOfTwoControls) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + q2 = programBuilder.h(q2); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + + SmallVector q01; + SmallVector q2Vec; + std::tie(q01, q2Vec) = programBuilder.ctrl( + {q0, q1}, {q2}, [&](ValueRange targets) -> SmallVector { + return SmallVector{programBuilder.x(targets[0])}; + }); + + Value c2; + std::tie(q2, c2) = programBuilder.measure(q2Vec[0]); + + programBuilder.sink(q01[0]); + programBuilder.sink(q01[1]); + programBuilder.sink(q2); + program = programBuilder.finalize({c0, c1, c2}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + r2 = referenceBuilder.h(r2); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + auto andOp = arith::AndIOp::create(referenceBuilder, cr0, cr1); + + r2 = referenceBuilder.qcoIf(andOp.getResult(), r2, [&](Value qubit) -> Value { + return referenceBuilder.x(qubit); + }); + Value cr2; + std::tie(r2, cr2) = referenceBuilder.measure(r2); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + referenceBuilder.sink(r2); + + reference = referenceBuilder.finalize({cr0, cr1, cr2}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where two out of + * three controls can be replaced. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsTwoOfThreeControls) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type(), + programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + auto q3 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + q2 = programBuilder.h(q2); + q3 = programBuilder.h(q3); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + + SmallVector q012; + SmallVector q3Vec; + std::tie(q012, q3Vec) = programBuilder.ctrl( + {q0, q1, q2}, {q3}, [&](ValueRange targets) -> SmallVector { + return SmallVector{programBuilder.x(targets[0])}; + }); + + Value c2; + std::tie(q2, c2) = programBuilder.measure(q012[2]); + Value c3; + std::tie(q3, c3) = programBuilder.measure(q3Vec[0]); + + programBuilder.sink(q012[0]); + programBuilder.sink(q012[1]); + programBuilder.sink(q2); + programBuilder.sink(q3); + program = programBuilder.finalize({c0, c1, c2, c3}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + auto r3 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + r2 = referenceBuilder.h(r2); + r3 = referenceBuilder.h(r3); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + auto andOp = arith::AndIOp::create(referenceBuilder, cr0, cr1); + + SmallVector r23 = + referenceBuilder.qcoIf(andOp.getResult(), {r2, r3}, + [&](ValueRange qubits) -> SmallVector { + Value t2 = qubits[0]; + Value t3 = qubits[1]; + std::tie(t2, t3) = referenceBuilder.cx(t2, t3); + return SmallVector{t2, t3}; + }); + Value cr2; + std::tie(r2, cr2) = referenceBuilder.measure(r23[0]); + Value cr3; + std::tie(r3, cr3) = referenceBuilder.measure(r23[1]); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + referenceBuilder.sink(r2); + referenceBuilder.sink(r3); + + reference = referenceBuilder.finalize({cr0, cr1, cr2, cr3}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: A measured target of a non-phase gate must not be mistaken for a + * replaceable classical control. + */ +TEST_F(QCOReplaceClassicalControlsTest, doNotReplaceMeasuredNonPhaseTarget) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto target = programBuilder.h(programBuilder.allocQubit()); + auto control = programBuilder.h(programBuilder.allocQubit()); + + Value initialTargetOutcome; + std::tie(target, initialTargetOutcome) = programBuilder.measure(target); + std::tie(control, target) = programBuilder.cx(control, target); + + Value controlOutcome; + Value targetOutcome; + std::tie(control, controlOutcome) = programBuilder.measure(control); + std::tie(target, targetOutcome) = programBuilder.measure(target); + programBuilder.sink(control); + programBuilder.sink(target); + program = programBuilder.finalize( + {initialTargetOutcome, controlOutcome, targetOutcome}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto referenceTarget = referenceBuilder.h(referenceBuilder.allocQubit()); + auto referenceControl = referenceBuilder.h(referenceBuilder.allocQubit()); + + Value referenceInitialTargetOutcome; + std::tie(referenceTarget, referenceInitialTargetOutcome) = + referenceBuilder.measure(referenceTarget); + std::tie(referenceControl, referenceTarget) = + referenceBuilder.cx(referenceControl, referenceTarget); + + Value referenceControlOutcome; + Value referenceTargetOutcome; + std::tie(referenceControl, referenceControlOutcome) = + referenceBuilder.measure(referenceControl); + std::tie(referenceTarget, referenceTargetOutcome) = + referenceBuilder.measure(referenceTarget); + referenceBuilder.sink(referenceControl); + referenceBuilder.sink(referenceTarget); + reference = referenceBuilder.finalize({referenceInitialTargetOutcome, + referenceControlOutcome, + referenceTargetOutcome}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: A measured control of a multi-target gate can be replaced + * without attempting a single-target phase-gate swap. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceMeasuredControlOfMultiTargetGate) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto control = programBuilder.h(programBuilder.allocQubit()); + auto target0 = programBuilder.h(programBuilder.allocQubit()); + auto target1 = programBuilder.h(programBuilder.allocQubit()); + + Value controlOutcome; + std::tie(control, controlOutcome) = programBuilder.measure(control); + auto [controlOut, targetsOut] = + programBuilder.cswap(control, target0, target1); + + Value target0Outcome; + Value target1Outcome; + std::tie(target0, target0Outcome) = programBuilder.measure(targetsOut.first); + std::tie(target1, target1Outcome) = programBuilder.measure(targetsOut.second); + programBuilder.sink(controlOut); + programBuilder.sink(target0); + programBuilder.sink(target1); + program = + programBuilder.finalize({controlOutcome, target0Outcome, target1Outcome}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto referenceControl = referenceBuilder.h(referenceBuilder.allocQubit()); + auto referenceTarget0 = referenceBuilder.h(referenceBuilder.allocQubit()); + auto referenceTarget1 = referenceBuilder.h(referenceBuilder.allocQubit()); + + Value referenceControlOutcome; + std::tie(referenceControl, referenceControlOutcome) = + referenceBuilder.measure(referenceControl); + auto referenceTargets = referenceBuilder.qcoIf( + referenceControlOutcome, {referenceTarget0, referenceTarget1}, + [&](ValueRange targets) -> SmallVector { + auto [out0, out1] = referenceBuilder.swap(targets[0], targets[1]); + return {out0, out1}; + }); + + Value referenceTarget0Outcome; + Value referenceTarget1Outcome; + std::tie(referenceTarget0, referenceTarget0Outcome) = + referenceBuilder.measure(referenceTargets[0]); + std::tie(referenceTarget1, referenceTarget1Outcome) = + referenceBuilder.measure(referenceTargets[1]); + referenceBuilder.sink(referenceControl); + referenceBuilder.sink(referenceTarget0); + referenceBuilder.sink(referenceTarget1); + reference = referenceBuilder.finalize({referenceControlOutcome, + referenceTarget0Outcome, + referenceTarget1Outcome}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where a phase + * target gate needs to be swapped with to achieve a replaceable control. + */ +TEST_F(QCOReplaceClassicalControlsTest, replaceClassicalControlsSwapPhase) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q1, q0) = programBuilder.cz(q1, q0); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + + programBuilder.sink(q0); + programBuilder.sink(q1); + program = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + r1 = referenceBuilder.qcoIf( + cr0, r1, [&](Value qubit) -> Value { return referenceBuilder.z(qubit); }); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests that a phase target gate is not swapped with a + * classical control if it's not necessary. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsDontSwapPhaseIfNotNecessary) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + std::tie(q1, q0) = programBuilder.cz(q1, q0); + Value c2; + std::tie(q0, c2) = programBuilder.measure(q0); + + programBuilder.sink(q0); + programBuilder.sink(q1); + program = programBuilder.finalize({c0, c1, c2}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + r0 = referenceBuilder.qcoIf(cr1, r0, [&](Value qubits) -> Value { + return referenceBuilder.z(qubits); + }); + Value cr2; + std::tie(r0, cr2) = referenceBuilder.measure(r0); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + + reference = referenceBuilder.finalize({cr0, cr1, cr2}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where one of two + * control qubits of a phase gate is swapped with the target qubit. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsSwapOneOfTwoPhase) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + q2 = programBuilder.h(q2); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + SmallVector q12; + SmallVector q0Vec; + std::tie(q12, q0Vec) = programBuilder.ctrl( + {q1, q2}, {q0}, [&](ValueRange targets) -> SmallVector { + return SmallVector{programBuilder.z(targets[0])}; + }); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q12[0]); + + programBuilder.sink(q0Vec[0]); + programBuilder.sink(q1); + programBuilder.sink(q12[1]); + program = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + r2 = referenceBuilder.h(r2); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + SmallVector r21 = referenceBuilder.qcoIf( + cr0, {r2, r1}, [&](ValueRange qubits) -> SmallVector { + Value t2 = qubits[0]; + Value t1 = qubits[1]; + std::tie(t2, t1) = referenceBuilder.cz(t2, t1); + return SmallVector{t2, t1}; + }); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r21[1]); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + referenceBuilder.sink(r21[0]); + + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +/** + * @brief Test: Tests replacing a classically controlled gate where only one of + * two controls can possibly be swapped with the target qubit of a phase + * operation. + */ +TEST_F(QCOReplaceClassicalControlsTest, + replaceClassicalControlsSwapOnlyPossiblePhase) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + q0 = programBuilder.h(q0); + q1 = programBuilder.h(q1); + q2 = programBuilder.h(q2); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + Value c1; + std::tie(q1, c1) = programBuilder.measure(q1); + SmallVector q12; + SmallVector q0Vec; + std::tie(q12, q0Vec) = programBuilder.ctrl( + {q1, q2}, {q0}, [&](ValueRange targets) -> SmallVector { + return SmallVector{programBuilder.z(targets[0])}; + }); + Value c2; + std::tie(q2, c2) = programBuilder.measure(q12[1]); + + programBuilder.sink(q0Vec[0]); + programBuilder.sink(q12[0]); + programBuilder.sink(q2); + program = programBuilder.finalize({c0, c1, c2}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + r0 = referenceBuilder.h(r0); + r1 = referenceBuilder.h(r1); + r2 = referenceBuilder.h(r2); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + auto andOp = arith::AndIOp::create(referenceBuilder, cr1, cr0); + + r2 = referenceBuilder.qcoIf(andOp.getResult(), r2, [&](Value qubit) -> Value { + return referenceBuilder.z(qubit); + }); + Value cr2; + std::tie(r2, cr2) = referenceBuilder.measure(r2); + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + referenceBuilder.sink(r2); + + reference = referenceBuilder.finalize({cr0, cr1, cr2}); + + ASSERT_TRUE(runReplaceClassicalControlsPass(program.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} diff --git a/mlir/unittests/Dialect/QCO/Utils/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Utils/CMakeLists.txt index ac92f4e9aa..8e7b338a1c 100644 --- a/mlir/unittests/Dialect/QCO/Utils/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Utils/CMakeLists.txt @@ -7,9 +7,11 @@ # Licensed under the MIT License set(qco_utils_target mqt-core-mlir-unittest-qco-utils) -add_executable(${qco_utils_target} test_drivers.cpp test_unitary_matrix.cpp test_wireiterator.cpp) -target_link_libraries(${qco_utils_target} PRIVATE GTest::gtest_main MLIRQCOUtils - MLIRQCOProgramBuilder MLIRQCOMatrix) +add_executable(${qco_utils_target} test_dd_functionality.cpp test_drivers.cpp + test_unitary_matrix.cpp test_wireiterator.cpp) +target_link_libraries( + ${qco_utils_target} PRIVATE GTest::gtest_main MLIRParser MLIRQCODDFunctionality MLIRQCOUtils + MLIRQCOProgramBuilder MLIRQCOMatrix) mqt_mlir_configure_unittest_target(${qco_utils_target}) gtest_discover_tests(${qco_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp new file mode 100644 index 0000000000..c259870e0a --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp @@ -0,0 +1,596 @@ +/* + * 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/Node.hpp" +#include "dd/Package.hpp" +#include "dd/Simulation.hpp" +#include "dd/StateGeneration.hpp" +#include "ir/QuantumComputation.hpp" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Utils/DDFunctionality.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace qco; + +namespace { + +class QCODDFunctionalityTest : public testing::Test { +protected: + std::unique_ptr context; + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + [[nodiscard]] static func::FuncOp mainFunc(ModuleOp mod) { + return *mod.getBody()->getOps().begin(); + } + + template + [[nodiscard]] OwningOpRef buildModule(BuildFn&& buildFn) { + return QCOProgramBuilder::build(context.get(), + std::forward(buildFn)); + } + + /// Compare `mlir::qco::{buildFunctionality,simulate}` to + /// `dd::{buildFunctionality,simulate}` on an equivalent circuit. + static void expectEqualToQc(func::FuncOp func, + const qc::QuantumComputation& qc) { + const auto numQubits = qc.getNqubits(); + auto dd = std::make_unique(numQubits); + + const auto fromQcFn = dd::buildFunctionality(qc, *dd); + const auto fromQcoFn = buildFunctionality(func, *dd); + ASSERT_TRUE(succeeded(fromQcoFn)); + EXPECT_EQ(fromQcoFn->getMatrix(numQubits), fromQcFn.getMatrix(numQubits)); + dd->decRef(*fromQcoFn); + dd->decRef(fromQcFn); + + const auto fromQcSim = + dd::simulate(qc, dd::makeZeroState(numQubits, *dd), *dd); + const auto fromQcoSim = + simulate(func, dd::makeZeroState(numQubits, *dd), *dd); + ASSERT_TRUE(succeeded(fromQcoSim)); + EXPECT_EQ(fromQcoSim->getVector(), fromQcSim.getVector()); + dd->decRef(*fromQcoSim); + dd->decRef(fromQcSim); + } + + void expectMlirFails(size_t numQubits, StringRef mlirCode) const { + auto mod = parseSourceString(mlirCode, context.get()); + ASSERT_TRUE(mod); + auto dd = std::make_unique(numQubits); + EXPECT_TRUE(failed(buildFunctionality(mainFunc(*mod), *dd))); + } + + template + void expectBuiltFails(size_t numQubits, BuildFn&& buildFn) { + auto mod = buildModule(std::forward(buildFn)); + ASSERT_TRUE(mod); + auto dd = std::make_unique(numQubits); + EXPECT_TRUE(failed(buildFunctionality(mainFunc(*mod), *dd))); + } +}; + +TEST_F(QCODDFunctionalityTest, MatchesQuantumComputation) { + // Every `decodeStandardGate` branch once (distinct angles catch param-order + // bugs), plus barrier / sparse ctrl / inv / sink. + constexpr double theta = 0.31; + constexpr double phi = 0.42; + constexpr double lambda = 0.53; + constexpr double beta = 0.64; + + auto mod = buildModule([&](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto q2 = b.staticQubit(2); + q0 = b.id(q0); + q0 = b.x(q0); + q0 = b.y(q0); + q0 = b.z(q0); + q0 = b.h(q0); + q0 = b.s(q0); + q0 = b.sdg(q0); + q0 = b.t(q0); + q0 = b.tdg(q0); + q0 = b.sx(q0); + q0 = b.sxdg(q0); + q0 = b.rx(theta, q0); + q0 = b.ry(theta, q0); + q0 = b.rz(theta, q0); + q0 = b.p(theta, q0); + q0 = b.r(theta, phi, q0); + q0 = b.u2(phi, lambda, q0); + q0 = b.u(theta, phi, lambda, q0); + std::tie(q0, q1) = b.swap(q0, q1); + std::tie(q0, q1) = b.iswap(q0, q1); + std::tie(q0, q1) = b.dcx(q0, q1); + std::tie(q0, q1) = b.ecr(q0, q1); + std::tie(q0, q1) = b.rxx(theta, q0, q1); + std::tie(q0, q1) = b.ryy(theta, q0, q1); + std::tie(q0, q1) = b.rzz(theta, q0, q1); + std::tie(q0, q1) = b.rzx(theta, q0, q1); + std::tie(q0, q1) = b.xx_plus_yy(theta, beta, q0, q1); + std::tie(q0, q1) = b.xx_minus_yy(theta, beta, q0, q1); + q0 = b.barrier({q0})[0]; + std::tie(q0, q1) = b.cx(q0, q1); + std::tie(q1, q2) = b.cp(std::numbers::pi / 5.0, q1, q2); + auto [controls, target] = b.mcx({q0, q1}, q2); + q0 = controls[0]; + q1 = controls[1]; + q2 = target; + q2 = b.inv(q2, [&](Value q) { return b.s(q); }); + b.sink(q0); + b.sink(q1); + b.sink(q2); + return b.intConstant(0); + }); + 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); +} + +TEST_F(QCODDFunctionalityTest, Rccx) { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto q2 = b.staticQubit(2); + auto q3 = b.staticQubit(3); + std::tie(q2, q0, q3) = b.rccx(q2, q0, q3); + auto [control, targets] = b.crccx(q1, q2, q0, q3); + const auto& [q2Out, q0Out, q3Out] = targets; + q1 = control; + q2 = q2Out; + q0 = q0Out; + q3 = q3Out; + b.sink(q0); + b.sink(q1); + b.sink(q2); + b.sink(q3); + return b.intConstant(0); + }); + ASSERT_TRUE(mod); + + qc::QuantumComputation qc(4); + qc.rccx(2, 0, 3); + qc.crccx(1, 2, 0, 3); + expectEqualToQc(mainFunc(*mod), qc); +} + +TEST_F(QCODDFunctionalityTest, DensePaths) { + // Compound `ctrl` (dense) with sparse gates, 2-qubit `inv`, full-width `inv`. + { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto q2 = b.staticQubit(2); + q1 = b.x(q1); + std::tie(q2, q0) = b.ctrl(q2, q0, [&](Value t) { return b.h(b.t(t)); }); + b.sink(q0); + b.sink(q1); + b.sink(q2); + 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); + } + { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto outs = b.inv({q0, q1}, [&](ValueRange qs) -> SmallVector { + auto [a, c] = b.swap(qs[0], qs[1]); + return {a, c}; + }); + b.sink(outs[0]); + b.sink(outs[1]); + return b.intConstant(0); + }); + ASSERT_TRUE(mod); + qc::QuantumComputation qc(2); + qc.swap(0, 1); + expectEqualToQc(mainFunc(*mod), qc); + } + { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto q2 = b.staticQubit(2); + auto outs = b.inv({q0, q1, q2}, [&](ValueRange t) -> SmallVector { + return {b.rx(0.2, t[0]), b.ry(0.3, t[1]), b.rz(0.4, t[2])}; + }); + b.sink(outs[0]); + b.sink(outs[1]); + b.sink(outs[2]); + 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); + } +} + +TEST_F(QCODDFunctionalityTest, TwoQubitDensePathBeyondFallbackLimit) { + auto mod = buildModule([](QCOProgramBuilder& b) { + SmallVector qs; + for (int i = 0; i < 13; ++i) { + qs.push_back(b.staticQubit(static_cast(i))); + } + std::tie(qs[12], qs[0]) = + b.ctrl(qs[12], qs[0], [&](Value t) { return b.h(b.t(t)); }); + for (Value q : qs) { + b.sink(q); + } + return b.intConstant(0); + }); + ASSERT_TRUE(mod); + + auto dd = std::make_unique(13); + const auto functionality = buildFunctionality(mainFunc(*mod), *dd); + ASSERT_TRUE(succeeded(functionality)); + dd->decRef(*functionality); +} + +TEST_F(QCODDFunctionalityTest, Gphase) { + auto without = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + q0 = b.h(q0); + b.sink(q0); + return b.intConstant(0); + }); + auto with = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + q0 = b.h(q0); + b.gphase(0.25); + b.sink(q0); + return b.intConstant(0); + }); + auto zeroQubit = buildModule([](QCOProgramBuilder& b) { + b.gphase(0.5); + return b.intConstant(0); + }); + ASSERT_TRUE(without); + ASSERT_TRUE(with); + ASSERT_TRUE(zeroQubit); + + auto dd = std::make_unique(1); + const auto u0 = buildFunctionality(mainFunc(*without), *dd); + const auto u1 = buildFunctionality(mainFunc(*with), *dd); + ASSERT_TRUE(succeeded(u0)); + ASSERT_TRUE(succeeded(u1)); + const auto phase = std::polar(1.0, 0.25); + const auto m0 = u0->getMatrix(1); + const auto m1 = u1->getMatrix(1); + for (std::size_t r = 0; r < 2; ++r) { + for (std::size_t c = 0; c < 2; ++c) { + EXPECT_TRUE(std::abs(m1[r][c] - (m0[r][c] * phase)) < 1e-10); + } + } + dd->decRef(*u0); + dd->decRef(*u1); + + auto dd0 = std::make_unique(0); + const auto uZ = buildFunctionality(mainFunc(*zeroQubit), *dd0); + ASSERT_TRUE(succeeded(uZ)); + EXPECT_TRUE(uZ->isTerminal()); + dd0->decRef(*uZ); +} + +TEST_F(QCODDFunctionalityTest, FuncArgs) { + // Qubit block args (no `qco.static`); non-qubit args are skipped. + auto mod = parseSourceString(R"mlir( + module { + func.func @main(%c: i32, %q: !qco.qubit) -> !qco.qubit { + %q1 = qco.h %q : !qco.qubit -> !qco.qubit + return %q1 : !qco.qubit + } + } + )mlir", + context.get()); + ASSERT_TRUE(mod); + + qc::QuantumComputation qc(1); + qc.h(0); + expectEqualToQc(mainFunc(*mod), qc); +} + +TEST_F(QCODDFunctionalityTest, ReturnedQubitsMustPreserveWireOrder) { + auto canonical = parseSourceString(R"mlir( + module { + func.func @main(%q0: !qco.qubit, %q1: !qco.qubit) + -> (!qco.qubit, !qco.qubit) { + %q0_out = qco.h %q0 : !qco.qubit -> !qco.qubit + %q1_out = qco.x %q1 : !qco.qubit -> !qco.qubit + return %q0_out, %q1_out : !qco.qubit, !qco.qubit + } + } + )mlir", + context.get()); + auto swapped = parseSourceString(R"mlir( + module { + func.func @main(%q0: !qco.qubit, %q1: !qco.qubit) + -> (!qco.qubit, !qco.qubit) { + %q0_out = qco.h %q0 : !qco.qubit -> !qco.qubit + %q1_out = qco.x %q1 : !qco.qubit -> !qco.qubit + return %q1_out, %q0_out : !qco.qubit, !qco.qubit + } + } + )mlir", + context.get()); + ASSERT_TRUE(canonical); + ASSERT_TRUE(swapped); + + auto dd = std::make_unique(2); + const auto canonicalFunctionality = + buildFunctionality(mainFunc(*canonical), *dd); + ASSERT_TRUE(succeeded(canonicalFunctionality)); + dd->decRef(*canonicalFunctionality); + const auto canonicalSimulation = + simulate(mainFunc(*canonical), dd::makeZeroState(2, *dd), *dd); + ASSERT_TRUE(succeeded(canonicalSimulation)); + dd->decRef(*canonicalSimulation); + + EXPECT_TRUE(failed(buildFunctionality(mainFunc(*swapped), *dd))); + EXPECT_TRUE( + failed(simulate(mainFunc(*swapped), dd::makeZeroState(2, *dd), *dd))); +} + +TEST_F(QCODDFunctionalityTest, SimulationConsumesInputReference) { + auto valid = buildModule([](QCOProgramBuilder& b) { + auto q = b.x(b.staticQubit(0)); + b.sink(q); + return b.intConstant(0); + }); + auto tooWide = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.sink(q0); + b.sink(q1); + return b.intConstant(0); + }); + ASSERT_TRUE(valid); + ASSERT_TRUE(tooWide); + + auto dd = std::make_unique(1); + auto& roots = dd->getRootSet(); + for (size_t i = 0; i < 3; ++i) { + const auto output = + simulate(mainFunc(*valid), dd::makeZeroState(1, *dd), *dd); + ASSERT_TRUE(succeeded(output)); + EXPECT_EQ(roots.size(), 1U); + EXPECT_EQ(roots.at(*output), 1U); + dd->decRef(*output); + EXPECT_TRUE(roots.empty()); + } + for (size_t i = 0; i < 3; ++i) { + EXPECT_TRUE( + failed(simulate(mainFunc(*tooWide), dd::makeZeroState(1, *dd), *dd))); + EXPECT_TRUE(roots.empty()); + } + + auto zeroQubitDd = std::make_unique(0); + EXPECT_TRUE( + failed(simulate(mainFunc(*valid), dd::VectorDD::one(), *zeroQubitDd))); + EXPECT_TRUE(zeroQubitDd->getRootSet().empty()); +} + +TEST_F(QCODDFunctionalityTest, Rejects) { + { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + std::tie(q0, std::ignore) = b.measure(q0); + b.sink(q0); + return b.intConstant(0); + }); + ASSERT_TRUE(mod); + auto dd = std::make_unique(1); + EXPECT_TRUE(failed(buildFunctionality(mainFunc(*mod), *dd))); + EXPECT_TRUE( + failed(simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd))); + } + + { + auto mod = buildModule([](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + q0 = b.h(q0); + b.sink(q0); + b.sink(q1); + return b.intConstant(0); + }); + ASSERT_TRUE(mod); + auto dd = std::make_unique(1); + EXPECT_TRUE(failed(buildFunctionality(mainFunc(*mod), *dd))); + EXPECT_TRUE( + failed(simulate(mainFunc(*mod), dd::makeZeroState(1, *dd), *dd))); + } + + expectBuiltFails(4, [](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + auto q2 = b.staticQubit(2); + auto q3 = b.staticQubit(3); + auto outs = b.inv({q0, q1, q2}, [&](ValueRange t) -> SmallVector { + return {b.rx(0.2, t[0]), b.ry(0.3, t[1]), b.rz(0.4, t[2])}; + }); + b.sink(outs[0]); + b.sink(outs[1]); + b.sink(outs[2]); + b.sink(q3); + return b.intConstant(0); + }); + + expectMlirFails(1, R"mlir( + module { + func.func @main(%qarg: !qco.qubit) { + %q = qco.static 0 : !qco.qubit + %q1 = qco.h %qarg : !qco.qubit -> !qco.qubit + return + } + } + )mlir"); + expectMlirFails(1, R"mlir( + module { + func.func @main(%qarg: !qco.qubit) { + %q = qco.static 0 : !qco.qubit + %q1 = qco.barrier %qarg : !qco.qubit -> !qco.qubit + return + } + } + )mlir"); + expectMlirFails(1, R"mlir( + module { + func.func @main(%qarg: !qco.qubit) { + %q = qco.static 0 : !qco.qubit + %q_out = qco.inv (%q_in = %qarg) { + %q1 = qco.x %q_in : !qco.qubit -> !qco.qubit + qco.yield %q1 : !qco.qubit + } : {!qco.qubit} -> {!qco.qubit} + return + } + } + )mlir"); + expectMlirFails(1, R"mlir( + module { + func.func @main(%qarg: !qco.qubit) { + %q = qco.static 0 : !qco.qubit + %c_out, %t_out = qco.ctrl(%qarg) targets(%t = %q) { + %t1 = qco.x %t : !qco.qubit -> !qco.qubit + qco.yield %t1 : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + return + } + } + )mlir"); + expectMlirFails(1, R"mlir( + module { + func.func @main(%theta: f64) { + %q = qco.static 0 : !qco.qubit + %q1 = qco.rz(%theta) %q : !qco.qubit -> !qco.qubit + return + } + } + )mlir"); + expectMlirFails(0, R"mlir( + module { + func.func @main(%theta: f64) { + qco.gphase(%theta) + return + } + } + )mlir"); + expectMlirFails(1, R"mlir( + module { + func.func @main(%theta: f64) { + %q = qco.static 0 : !qco.qubit + %q_out = qco.inv (%q_in = %q) { + %q1 = qco.rz(%theta) %q_in : !qco.qubit -> !qco.qubit + qco.yield %q1 : !qco.qubit + } : {!qco.qubit} -> {!qco.qubit} + return + } + } + )mlir"); + expectMlirFails(2, R"mlir( + module { + func.func @main(%theta: f64) { + %q0 = qco.static 0 : !qco.qubit + %q1 = qco.static 1 : !qco.qubit + %c_out, %t_out = qco.ctrl(%q0) targets(%t = %q1) { + %t1 = qco.rz(%theta) %t : !qco.qubit -> !qco.qubit + qco.yield %t1 : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + return + } + } + )mlir"); + + OwningOpRef multi = + ModuleOp::create(UnknownLoc::get(context.get())); + OpBuilder builder(context.get()); + builder.setInsertionPointToStart(multi->getBody()); + auto func = func::FuncOp::create(builder, multi->getLoc(), "main", + builder.getFunctionType({}, {})); + auto* entry = func.addEntryBlock(); + func.addBlock(); + builder.setInsertionPointToStart(entry); + func::ReturnOp::create(builder, func.getLoc()); + auto dd = std::make_unique(0); + EXPECT_TRUE(failed(buildFunctionality(func, *dd))); +} + +} // namespace diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index df3fd310ec..ad1862897b 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -81,29 +81,37 @@ measure q[2] -> c1[1]; const std::string resetQubitAfterSingleOp = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; +bit[2] c; h q[0]; +c[0] = measure q; reset q[0]; -bit[1] c = measure q; +c[1] = measure q; )qasm"; const std::string resetMultipleQubitsAfterSingleOp = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; +bit[4] c; h q[0]; +c[0] = measure q[0]; reset q[0]; +c[1] = measure q[0]; h q[1]; +c[2] = measure q[1]; reset q[1]; -bit[2] c = measure q; +c[3] = measure q[1]; )qasm"; const std::string repeatedResetAfterSingleOp = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; +bit[2] c; h q[0]; +c[0] = measure q; reset q[0]; reset q[0]; reset q[0]; -bit[1] c = measure q; +c[1] = measure q; )qasm"; const std::string globalPhase = R"qasm(OPENQASM 3.0; diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index c11fe548a9..e98fff505c 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -237,29 +237,37 @@ Value repeatedResetWithoutOp(QCProgramBuilder& b) { return b.measure(q[0]); } -Value resetQubitAfterSingleOp(QCProgramBuilder& b) { +SmallVector resetQubitAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); + auto c0 = b.measure(q[0]); b.reset(q[0]); - return b.measure(q[0]); + auto c1 = b.measure(q[0]); + return {c0, c1}; } SmallVector resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); + auto c0 = b.measure(q[0]); b.reset(q[0]); + auto c1 = b.measure(q[0]); b.h(q[1]); + auto c2 = b.measure(q[1]); b.reset(q[1]); - return measureAndReturn(b, q.qubits); + auto c3 = b.measure(q[1]); + return {c0, c1, c2, c3}; } -Value repeatedResetAfterSingleOp(QCProgramBuilder& b) { +SmallVector repeatedResetAfterSingleOp(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); + auto c0 = b.measure(q[0]); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); - return b.measure(q[0]); + auto c1 = b.measure(q[0]); + return {c0, c1}; } Value globalPhase(QCProgramBuilder& b) { diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 31d1ffbcd3..697e08636b 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -117,13 +117,13 @@ SmallVector resetMultipleQubitsWithoutOp(QCProgramBuilder& b); Value repeatedResetWithoutOp(QCProgramBuilder& b); /// Resets a single qubit after a single operation. -Value resetQubitAfterSingleOp(QCProgramBuilder& b); +SmallVector resetQubitAfterSingleOp(QCProgramBuilder& b); /// Resets multiple qubits after a single operation. SmallVector resetMultipleQubitsAfterSingleOp(QCProgramBuilder& b); /// Repeatedly resets a single qubit after a single operation. -Value repeatedResetAfterSingleOp(QCProgramBuilder& b); +SmallVector repeatedResetAfterSingleOp(QCProgramBuilder& b); // --- GPhaseOp ------------------------------------------------------------- // diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index aff53aa1f5..5c7acec312 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -169,6 +169,18 @@ SmallVector deadGatesProgram(QCOProgramBuilder& b) { return {m0, m1}; } +SmallVector deadGatesResetProgram(QCOProgramBuilder& b) { + auto q0 = b.allocQubit(); + Value c; + + q0 = b.h(q0); + q0 = b.reset(q0); + std::tie(q0, c) = b.measure(q0); + q0 = b.reset(q0); + + return {c}; +} + Value deadGatesWithIfOpProgram(QCOProgramBuilder& b) { auto q0 = b.allocQubit(); auto q1 = b.allocQubit(); @@ -302,29 +314,41 @@ Value repeatedResetWithoutOp(QCOProgramBuilder& b) { return b.measure(q).second; } -Value resetQubitAfterSingleOp(QCOProgramBuilder& b) { +SmallVector resetQubitAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.h(q[0]); + Value c0; + std::tie(q[0], c0) = b.measure(q[0]); q[0] = b.reset(q[0]); - return b.measure(q[0]).second; + return {c0, b.measure(q[0]).second}; } SmallVector resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); + Value c0; + Value c1; + Value c2; + Value c3; q[0] = b.h(q[0]); + std::tie(q[0], c0) = b.measure(q[0]); q[0] = b.reset(q[0]); + std::tie(q[0], c1) = b.measure(q[0]); q[1] = b.h(q[1]); + std::tie(q[1], c2) = b.measure(q[1]); q[1] = b.reset(q[1]); - return measureAndReturn(b, q.qubits); + std::tie(q[1], c3) = b.measure(q[1]); + return {c0, c1, c2, c3}; } -Value repeatedResetAfterSingleOp(QCOProgramBuilder& b) { +SmallVector repeatedResetAfterSingleOp(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); + Value c0; q[0] = b.h(q[0]); + std::tie(q[0], c0) = b.measure(q[0]); q[0] = b.reset(q[0]); q[0] = b.reset(q[0]); q[0] = b.reset(q[0]); - return b.measure(q[0]).second; + return {c0, b.measure(q[0]).second}; } Value globalPhase(QCOProgramBuilder& b) { @@ -3142,11 +3166,8 @@ SmallVector simpleIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - auto res = b.qcoIf(measureResult, measuredQubit, [&](ValueRange args) { - auto innerQubit = b.x(args[0]); - return SmallVector{innerQubit}; - }); - q[0] = res[0]; + q[0] = b.qcoIf(measureResult, measuredQubit, + [&](Value arg) { return b.x(arg); }); auto [q1, bit] = b.measure(q[0]); return {measureResult, bit}; } @@ -3172,17 +3193,9 @@ SmallVector ifElse(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); auto [measuredQubit, measureResult] = b.measure(q0); - auto res = b.qcoIf( - measureResult, {measuredQubit}, - [&](ValueRange args) { - auto innerQubit = b.x(args[0]); - return SmallVector{innerQubit}; - }, - [&](ValueRange args) { - auto innerQubit = b.z(args[0]); - return SmallVector{innerQubit}; - }); - q[0] = res[0]; + q[0] = b.qcoIf( + measureResult, measuredQubit, [&](Value arg) { return b.x(arg); }, + [&](Value arg) { return b.z(arg); }); auto [q0_, c0] = b.measure(q[0]); return {measureResult, c0}; } @@ -3203,6 +3216,16 @@ Value ifOneQubitOneTensor(QCOProgramBuilder& b) { return b.measure(ifRes[0]).second; } +Value ifOneTensor(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + auto result = b.qcoIf(true, q.value, [&](Value tensor) { + auto [updatedTensor, qubit] = b.qtensorExtract(tensor, 0); + qubit = b.x(qubit); + return b.qtensorInsert(qubit, updatedTensor, 0); + }); + return measureAndReturnQTensor(b, result, 1).front(); +} + Value constantTrueIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto ifRes = b.qcoIf( diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 61d8538f89..9cc185f9fc 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -72,6 +72,9 @@ Value allocSinkPair(QCOProgramBuilder& b); /// Allocates two qubits and performs a set of dead gates on them. SmallVector deadGatesProgram(QCOProgramBuilder& b); +/// Allocates a qubit and uses a bunch of dead gates together with ResetOps. +SmallVector deadGatesResetProgram(QCOProgramBuilder& b); + /// Allocates two qubits and performs a set of dead gates on them, including /// `if` operations. Value deadGatesWithIfOpProgram(QCOProgramBuilder& b); @@ -120,13 +123,13 @@ SmallVector resetMultipleQubitsWithoutOp(QCOProgramBuilder& b); Value repeatedResetWithoutOp(QCOProgramBuilder& b); /// Resets a single qubit after a single operation. -Value resetQubitAfterSingleOp(QCOProgramBuilder& b); +SmallVector resetQubitAfterSingleOp(QCOProgramBuilder& b); /// Resets multiple qubits after a single operation. SmallVector resetMultipleQubitsAfterSingleOp(QCOProgramBuilder& b); /// Repeatedly resets a single qubit after a single operation. -Value repeatedResetAfterSingleOp(QCOProgramBuilder& b); +SmallVector repeatedResetAfterSingleOp(QCOProgramBuilder& b); // --- GPhaseOp ------------------------------------------------------------- // @@ -1108,6 +1111,9 @@ SmallVector ifElse(QCOProgramBuilder& b); /// Creates a circuit with an if operation with one qubit and one register. Value ifOneQubitOneTensor(QCOProgramBuilder& b); +/// Creates a circuit with an if operation with one register. +Value ifOneTensor(QCOProgramBuilder& b); + /// Creates a circuit with an if operation that uses a constant true as /// condition. Value constantTrueIf(QCOProgramBuilder& b); diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index de039682c1..e6391c92ef 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -249,28 +249,63 @@ template Value resetQubitAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); - b.reset(q[0]); - return measureAndRecord(b, q.qubits, IntoRegister); + if constexpr (IntoRegister) { + auto c = b.allocClassicalBitRegister(static_cast(2), "c"); + b.measure(q[0], c[0]); + b.reset(q[0]); + b.measure(q[0], c[1]); + } else { + b.measure(q[0], 0); + b.reset(q[0]); + b.measure(q[0], 1); + } + return b.intConstant(0); } template Value resetMultipleQubitsAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.h(q[0]); - b.reset(q[0]); - b.h(q[1]); - b.reset(q[1]); - return measureAndRecord(b, q.qubits, IntoRegister); + if constexpr (IntoRegister) { + auto c = b.allocClassicalBitRegister(static_cast(4), "c"); + b.measure(q[0], c[0]); + b.reset(q[0]); + b.measure(q[0], c[1]); + b.h(q[1]); + b.measure(q[1], c[2]); + b.reset(q[1]); + b.measure(q[1], c[3]); + } else { + b.measure(q[0], 0); + b.reset(q[0]); + b.measure(q[0], 1); + b.h(q[1]); + b.measure(q[1], 2); + b.reset(q[1]); + b.measure(q[1], 3); + } + return b.intConstant(0); } template Value repeatedResetAfterSingleOp(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); - b.reset(q[0]); - b.reset(q[0]); - b.reset(q[0]); - return measureAndRecord(b, q.qubits, IntoRegister); + if constexpr (IntoRegister) { + auto c = b.allocClassicalBitRegister(static_cast(2), "c"); + b.measure(q[0], c[0]); + b.reset(q[0]); + b.reset(q[0]); + b.reset(q[0]); + b.measure(q[0], c[1]); + } else { + b.measure(q[0], 0); + b.reset(q[0]); + b.reset(q[0]); + b.reset(q[0]); + b.measure(q[0], 1); + } + return b.intConstant(0); } template Value globalPhase(QIRProgramBuilder& b) { diff --git a/mlir/unittests/programs/quantum_computation_programs.cpp b/mlir/unittests/programs/quantum_computation_programs.cpp index 3eb8257e04..6bebc22d2f 100644 --- a/mlir/unittests/programs/quantum_computation_programs.cpp +++ b/mlir/unittests/programs/quantum_computation_programs.cpp @@ -78,27 +78,35 @@ void multipleClassicalRegistersAndMeasurements(QuantumComputation& comp) { void resetQubitAfterSingleOp(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); + comp.addClassicalRegister(2, "c"); comp.h(0); + comp.measure(0, 0); comp.reset(0); - comp.measureAll(true, false); + comp.measure(0, 1); } void resetMultipleQubitsAfterSingleOp(QuantumComputation& comp) { comp.addQubitRegister(2, "q"); + comp.addClassicalRegister(4, "c"); comp.h(0); + comp.measure(0, 0); comp.reset(0); + comp.measure(0, 1); comp.h(1); + comp.measure(1, 2); comp.reset(1); - comp.measureAll(true, false); + comp.measure(1, 3); } void repeatedResetAfterSingleOp(QuantumComputation& comp) { comp.addQubitRegister(1, "q"); + comp.addClassicalRegister(2, "c"); comp.h(0); + comp.measure(0, 0); comp.reset(0); comp.reset(0); comp.reset(0); - comp.measureAll(true, false); + comp.measure(0, 1); } void globalPhase(QuantumComputation& comp) { comp.gphase(0.123); } From 2e401f012b7fd0dd8bd1b745c1343234f50ed31f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 23 Jul 2026 09:59:21 +0200 Subject: [PATCH 26/27] chore: nudge PR sync From 1c37a385f982182127329e8da0dcdf5162cda77f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 24 Jul 2026 15:23:50 +0200 Subject: [PATCH 27/27] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Decomposition/test_weyl_decomposition.cpp | 1 - .../NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index fafa1be842..7df48cd6dd 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -10,7 +10,6 @@ #include "dd/Package.hpp" #include "mlir/Dialect/QCO/IR/QCODialect.h" -#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" diff --git a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp index 6eec2eaf7e..0d2caf1c22 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_fuse_two_qubit_unitary_runs.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "dd/DDDefinitions.hpp" #include "dd/Package.hpp" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Dialect/QCO/IR/QCODialect.h"