From 83bf630449087a70b9b63c037fc100d4e8c04233 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 29 Jun 2026 18:57:42 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Implement=20controlled=20unitar?= =?UTF-8?q?y=20embedding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Utils/Matrix.h | 72 ++++----- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 99 ++++++++---- mlir/lib/Dialect/QCO/Utils/Matrix.cpp | 141 +++++++++++------- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 60 +++++++- .../Dialect/QCO/Utils/test_unitary_matrix.cpp | 68 ++++----- 5 files changed, 273 insertions(+), 167 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index cdea703ed4..04bf6a1083 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h @@ -238,10 +238,10 @@ struct Matrix2x2 { * @brief Embed this single-qubit matrix into an @p numQubits-qubit Hilbert * space. * - * Wire @p qubitIndex uses the same MSB-first convention as @ref - * Matrix4x4::kron (high bit first operand, low bit second). For each basis - * pair whose untouched wires match, copies this matrix at the target qubit's - * row/column bits. + * Wire @p qubitIndex uses the same convention as @ref QuantumComputation: + * qubit @p i is bit @p i of the basis index. For each basis pair whose + * untouched wires match, copies this matrix at the target qubit's row/column + * bits. * * @param numQubits Number of qubits in the target Hilbert space. * @param qubitIndex Wire index to act on. @@ -253,7 +253,7 @@ struct Matrix2x2 { /** * @brief Embed this single-qubit matrix into a two-qubit Hilbert space. * - * @param qubitIndex Wire index (`0` = high bit / MSB, `1` = low bit). + * @param qubitIndex Wire index to act on (qubit @p i = bit @p i). * @return The `4x4` embedded unitary. */ [[nodiscard]] Matrix4x4 embedInTwoQubit(std::size_t qubitIndex) const; @@ -407,11 +407,12 @@ struct Matrix4x4 { /** * @brief Kronecker product `lhs (x) rhs` of two single-qubit matrices. * - * Uses the computational-basis bit order where the first operand labels the - * high bit, matching `UnitaryOpInterface::getUnitaryMatrix4x4`. + * Uses the computational-basis bit order where qubit @p i is bit @p i, + * matching @ref QuantumComputation. For @f$A \otimes B@f$, @p lhs acts on + * wire @f$1@f$ and @p rhs on wire @f$0@f$. * - * @param lhs Left factor (acts on the high bit / qubit 0). - * @param rhs Right factor (acts on the low bit / qubit 1). + * @param lhs Left factor (acts on wire 1). + * @param rhs Right factor (acts on wire 0). * @return The `4x4` Kronecker product. */ [[nodiscard]] static Matrix4x4 kron(const Matrix2x2& lhs, @@ -482,9 +483,9 @@ struct Matrix4x4 { * @brief Embed this two-qubit matrix into an @p numQubits-qubit Hilbert * space. * - * Operand 0 labels the high bit of the pair and acts on @p q0Index; operand 1 - * labels the low bit and acts on @p q1Index. For each basis pair whose other - * wires match, copies this matrix at the packed two-qubit row/column indices. + * Operand 0 labels wire @p q0Index and operand 1 labels wire @p q1Index. + * For each basis pair whose other wires match, copies this matrix at the + * packed two-qubit row/column indices. * * @param numQubits Number of qubits in the target Hilbert space. * @param q0Index Wire index of operand 0. @@ -496,9 +497,10 @@ struct Matrix4x4 { std::size_t q1Index) const; /** - * @brief Reorder this matrix to act on qubits `{0, 1}`. + * @brief Reorder this matrix to act on wires @p q0Index and @p q1Index. * - * @param q0Index Wire index of operand 0; @p q1Index wire index of operand 1. + * @param q0Index Wire index of operand 0. + * @param q1Index Wire index of operand 1. * @return Reordered copy of this matrix. */ [[nodiscard]] Matrix4x4 reorderForQubits(std::size_t q0Index, @@ -614,28 +616,6 @@ class DynamicMatrix { */ [[nodiscard]] Complex operator()(std::int64_t row, std::int64_t col) const; - /** - * @brief Copies a 2x2 block into the bottom-right corner. - * @param block Source block placed at indices `(dim-2, dim-2)` through - * `(dim-1, dim-1)`. - */ - void setBottomRightCorner(const Matrix2x2& block); - - /** - * @brief Copies a 4x4 block into the bottom-right corner. - * @param block Source block placed at indices `(dim-4, dim-4)` through - * `(dim-1, dim-1)`. - */ - void setBottomRightCorner(const Matrix4x4& block); - - /** - * @brief Copies a dynamic block into the bottom-right corner. - * @param block Source block placed at indices `(dim - block.rows(), ...)` - * through - * `(dim-1, dim-1)`. - */ - void setBottomRightCorner(const DynamicMatrix& block); - /** * @brief Returns the conjugate transpose (adjoint) of this matrix. * @return Adjoint matrix `A^\dagger`. @@ -754,6 +734,26 @@ class DynamicMatrix { std::unique_ptr impl_; }; +/** + * @brief Embeds @p targetUnitary as multi-controlled on @p controlQubits. + * + * Qubit @p i is bit @p i of the basis index, matching @ref QuantumComputation. + * When every control qubit is \f$|1\rangle\f$, applies @p targetUnitary on + * @p targetQubits; otherwise the identity. + * + * @param numQubits Size of the qubit register the matrix acts on. + * @param controlQubits Program register indices of the control wires. + * @param targetQubits Program register indices of the target wires, in the + * order used to index @p targetUnitary. + * @param targetUnitary Local unitary on the target subspace. + * @return The controlled unitary on the @p numQubits-qubit Hilbert space. + */ +[[nodiscard]] DynamicMatrix +embedControlledUnitary(std::size_t numQubits, + llvm::ArrayRef controlQubits, + llvm::ArrayRef targetQubits, + const DynamicMatrix& targetUnitary); + /** * @brief Type trait for the four supported matrix types. * diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 657c90b8a1..936af50588 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -30,9 +31,7 @@ #include #include -#include #include -#include #include using namespace mlir; @@ -40,6 +39,60 @@ using namespace mlir::qco; namespace { +/** + * @brief Returns the program register index of @p qubit when known at compile + * time. + * + * Supports @c qco.static and @c qtensor.extract with an @c arith.constant + * index. Dynamic or negative indices yield @c std::nullopt. + */ +[[nodiscard]] std::optional programQubitIndex(const Value qubit) { + auto* definingOp = qubit.getDefiningOp(); + if (definingOp == nullptr) { + return std::nullopt; + } + if (auto staticOp = dyn_cast(definingOp)) { + return static_cast(staticOp.getIndex()); + } + auto extractOp = dyn_cast(definingOp); + if (!extractOp) { + return std::nullopt; + } + auto indexOp = extractOp.getIndex().getDefiningOp(); + if (!indexOp) { + return std::nullopt; + } + const auto indexAttr = dyn_cast(indexOp.getValue()); + if (!indexAttr) { + return std::nullopt; + } + const auto index = indexAttr.getInt(); + if (index < 0) { + return std::nullopt; + } + return static_cast(index); +} + +/** + * @brief Maps each SSA qubit in @p qubits to its program register index. + * + * @return Indices in operand order, or @c std::nullopt if any wire is not + * resolved by @ref programQubitIndex. + */ +[[nodiscard]] std::optional> +resolveQubitIndices(const ValueRange qubits) { + SmallVector indices; + indices.reserve(qubits.size()); + for (const auto qubit : qubits) { + if (const auto index = programQubitIndex(qubit)) { + indices.push_back(*index); + } else { + return std::nullopt; + } + } + return indices; +} + /** * @brief Merge nested control modifiers into a single one. */ @@ -308,36 +361,28 @@ std::optional CtrlOp::getUnitaryMatrix() { "is not supported due to memory constraints."); } - const auto numControls = getNumControls(); - - // 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, - const auto& targetBlock) -> DynamicMatrix { - auto matrix = DynamicMatrix::identity(static_cast( - (1ULL << numControls) * static_cast(targetDim))); - matrix.setBottomRightCorner(targetBlock); - return matrix; - }; - - // Single inner unitary (e.g. `ctrl { h }`, `ctrl { cx }`). - if (auto bodyUnitary = - utils::getSoleBodyUnitary(*getBody())) { - if (const auto targetMatrix = - bodyUnitary.getUnitaryMatrix()) { - assert(targetMatrix->cols() == targetMatrix->rows()); - return controlledMatrix(targetMatrix->cols(), *targetMatrix); - } + const auto controlQubits = resolveQubitIndices(getInputControls()); + const auto targetQubits = resolveQubitIndices(getInputTargets()); + if (!controlQubits || !targetQubits) { return std::nullopt; } - // Composed single-qubit body (e.g. `ctrl { h; x }`); embed the 2x2 directly. - if (getNumTargets() == 1) { + // Inner unitary on targets: one body op or a composed single-qubit sequence. + std::optional targetMatrix; + if (auto bodyUnitary = + utils::getSoleBodyUnitary(*getBody())) { + targetMatrix = bodyUnitary.getUnitaryMatrix(); + } else if (getNumTargets() == 1) { if (const auto composed = composeSingleQubitBodyMatrix(*getBody())) { - return controlledMatrix(2, *composed); + targetMatrix = DynamicMatrix(*composed); } } + if (!targetMatrix) { + return std::nullopt; + } - return std::nullopt; + const std::size_t numQubits = 1 + std::max(*llvm::max_element(*controlQubits), + *llvm::max_element(*targetQubits)); + return embedControlledUnitary(numQubits, *controlQubits, *targetQubits, + *targetMatrix); } diff --git a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp index 50d524daa1..270a7e2796 100644 --- a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp @@ -163,50 +163,21 @@ checkedHilbertDim(const std::size_t numQubits) { return std::int64_t{static_cast(std::uint64_t{1} << numQubits)}; } -static void validateCornerDims(const std::int64_t matrixDim, - const std::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, - MutableArrayRef matrixData, - const std::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)]; - } - } -} - /** * @brief Returns the @p qubitIndex bit of a computational-basis label. * - * Qubit 0 is the MSB of @p stateIndex, matching @ref Matrix4x4::kron and - * @ref Matrix2x2::embedInNqubit. + * Qubit @p i is bit @p i of @p stateIndex, matching @ref QuantumComputation. */ [[nodiscard]] static std::size_t qubitBitAt(const std::size_t stateIndex, - const std::size_t numQubits, const std::size_t qubitIndex) { - return (stateIndex >> (numQubits - 1 - qubitIndex)) & 1U; + return (stateIndex >> qubitIndex) & 1U; } /** * @brief True when row and col agree on every wire except @p skipA and @p * skipB. * - * Used when embedding a gate: untouched qubits must match or the matrix entry - * is zero. For a single-qubit embed, pass @p skipB = @p numQubits so only @p - * skipA is skipped. + * Untouched wires must match or the matrix entry is zero. */ [[nodiscard]] static bool otherQubitBitsMatch(const std::size_t row, const std::size_t col, @@ -217,13 +188,84 @@ static void copyBottomRightCorner(const std::int64_t matrixDim, if (q == skipA || q == skipB) { continue; } - if (qubitBitAt(row, numQubits, q) != qubitBitAt(col, numQubits, q)) { + if (qubitBitAt(row, q) != qubitBitAt(col, q)) { return false; } } return true; } +/** + * @brief Packs the target-wire bits of @p stateIndex into a subspace index. + * + * Target wires are read in @p targetQubits order to index @p targetUnitary in + * @ref embedControlledUnitary. + */ +[[nodiscard]] static std::size_t +extractTargetSubIndex(const std::size_t stateIndex, + const ArrayRef targetQubits) { + std::size_t sub = 0; + for (const auto target : targetQubits) { + sub = (sub << 1) | qubitBitAt(stateIndex, target); + } + return sub; +} + +DynamicMatrix embedControlledUnitary(const std::size_t numQubits, + const ArrayRef controlQubits, + const ArrayRef targetQubits, + const DynamicMatrix& targetUnitary) { + assert(targetUnitary.rows() == targetUnitary.cols()); + assert(static_cast(targetUnitary.rows()) == + (std::uint64_t{1} << targetQubits.size())); + assert(numQubits < std::numeric_limits::digits); + for (const auto control : controlQubits) { + assert(control < numQubits && "Control wire index out of range"); + } + for (const auto target : targetQubits) { + assert(target < numQubits && "Target wire index out of range"); + } + + const auto dim = checkedHilbertDim(numQubits); + DynamicMatrix out = DynamicMatrix::identity(dim); + const auto udim = static_cast(dim); + + std::size_t activeMask = 0; + for (const auto control : controlQubits) { + activeMask |= std::size_t{1} << control; + } + const std::size_t controlMask = activeMask; + for (const auto target : targetQubits) { + activeMask |= std::size_t{1} << target; + } + // Wires outside the gate must match between row and col. + const std::size_t passiveMask = + ((std::size_t{1} << numQubits) - 1) & ~activeMask; + + llvm::SmallVector targetIndexByState(udim); + for (std::size_t state = 0; state < udim; ++state) { + targetIndexByState[state] = + static_cast(extractTargetSubIndex(state, targetQubits)); + } + + for (std::size_t row = 0; row < udim; ++row) { + // Identity off the all-ones control subspace. + if ((row & controlMask) != controlMask) { + continue; + } + const std::int64_t targetRow = targetIndexByState[row]; + for (std::size_t col = 0; col < udim; ++col) { + if ((col & controlMask) != controlMask || + ((row ^ col) & passiveMask) != 0) { + continue; + } + out(static_cast(row), static_cast(col)) = + targetUnitary(targetRow, targetIndexByState[col]); + } + } + return out; +} + Matrix1x1 Matrix1x1::fromElements(const Complex m00) { return {m00}; } Complex& Matrix1x1::operator()(const std::size_t row, const std::size_t col) { @@ -337,8 +379,8 @@ DynamicMatrix Matrix2x2::embedInNqubit(const std::size_t numQubits, 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); + const std::size_t rowBit = qubitBitAt(row, qubitIndex); + const std::size_t colBit = qubitBitAt(col, qubitIndex); out(static_cast(row), static_cast(col)) = (*this)(rowBit, colBit); } @@ -348,10 +390,10 @@ DynamicMatrix Matrix2x2::embedInNqubit(const std::size_t numQubits, Matrix4x4 Matrix2x2::embedInTwoQubit(const std::size_t qubitIndex) const { if (qubitIndex == 0) { - return Matrix4x4::kron(*this, Matrix2x2::identity()); + return Matrix4x4::kron(Matrix2x2::identity(), *this); } if (qubitIndex == 1) { - return Matrix4x4::kron(Matrix2x2::identity(), *this); + return Matrix4x4::kron(*this, Matrix2x2::identity()); } llvm::reportFatalInternalError("Invalid qubit index for single-qubit embed"); } @@ -532,10 +574,10 @@ DynamicMatrix Matrix4x4::embedInNqubit(const std::size_t numQubits, 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); + const std::size_t rowPair = + (qubitBitAt(row, q0Index) << 1) | qubitBitAt(row, q1Index); + const std::size_t colPair = + (qubitBitAt(col, q0Index) << 1) | qubitBitAt(col, q1Index); out(static_cast(row), static_cast(col)) = (*this)(rowPair, colPair); } @@ -882,23 +924,6 @@ Complex DynamicMatrix::operator()(const std::int64_t row, return impl_->data[static_cast((row * impl_->dim) + col)]; } -void DynamicMatrix::setBottomRightCorner(const Matrix2x2& block) { - copyBottomRightCorner(impl_->dim, impl_->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); -} - -void DynamicMatrix::setBottomRightCorner(const DynamicMatrix& block) { - copyBottomRightCorner(impl_->dim, impl_->data, block.impl_->dim, - block.impl_->data); -} - DynamicMatrix DynamicMatrix::adjoint() const { DynamicMatrix out(impl_->dim); adjointInto(impl_->data, out.impl_->data, checkedDim(impl_->dim)); 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..dd23ad5d99 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -85,6 +85,31 @@ static void controlledInverseHT(QCOProgramBuilder& b) { }); } +/// Builds @c cx(q[0], q[1]) for control/target ordering tests. +static void cxQubits01(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.cx(q[0], q[1]); +} + +/// Builds @c cx(q[1], q[0]) for control/target ordering tests. +static void cxQubits10(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.cx(q[1], q[0]); +} + +/// Returns the unitary of the sole @c CtrlOp in a one-op program. +[[nodiscard]] static std::optional +ctrlOpMatrixFromBuilder(MLIRContext* ctx, + llvm::function_ref build) { + auto moduleOp = QCOProgramBuilder::build(ctx, build); + if (!moduleOp) { + return std::nullopt; + } + auto funcOp = *moduleOp->getBody()->getOps().begin(); + auto ctrlOp = *funcOp.getBody().getOps().begin(); + return ctrlOp.getUnitaryMatrix(); +} + namespace { struct QCOMatrixTestCase { @@ -122,12 +147,34 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { comp.addQubitRegister(2, "q"); - comp.cx(1, 0); + comp.cx(0, 1); }); ASSERT_TRUE(matrix->isApprox(expected)); } +TEST_F(QCOMatrixTest, CXMatrixDependsOnControlTargetOrder) { + const Matrix4x4 cx01Ref = + expectedMatrixFromComputation([](qc::QuantumComputation& comp) { + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + }); + const Matrix4x4 cx10Ref = + expectedMatrixFromComputation([](qc::QuantumComputation& comp) { + comp.addQubitRegister(2, "q"); + comp.cx(1, 0); + }); + EXPECT_FALSE(cx01Ref.isApprox(cx10Ref)); + + const auto cx01Qco = ctrlOpMatrixFromBuilder(context.get(), cxQubits01); + const auto cx10Qco = ctrlOpMatrixFromBuilder(context.get(), cxQubits10); + ASSERT_TRUE(cx01Qco); + ASSERT_TRUE(cx10Qco); + EXPECT_FALSE(cx01Qco->isApprox(*cx10Qco)); + EXPECT_TRUE(cx01Qco->isApprox(cx01Ref)); + EXPECT_TRUE(cx10Qco->isApprox(cx10Ref)); +} + TEST_F(QCOMatrixTest, ControlledHOpMatrix) { auto moduleOp = QCOProgramBuilder::build(context.get(), singleControlledH); ASSERT_TRUE(moduleOp); @@ -140,7 +187,7 @@ TEST_F(QCOMatrixTest, ControlledHOpMatrix) { const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { comp.addQubitRegister(2, "q"); - comp.ch(1, 0); + comp.ch(0, 1); }); ASSERT_TRUE(matrix->isApprox(expected)); @@ -158,8 +205,8 @@ TEST_F(QCOMatrixTest, ControlledXHOpMatrix) { const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { comp.addQubitRegister(2, "q"); - comp.cx(1, 0); - comp.ch(1, 0); + comp.cx(0, 1); + comp.ch(0, 1); }); ASSERT_TRUE(matrix->isApprox(expected)); @@ -178,9 +225,10 @@ TEST_F(QCOMatrixTest, ControlledInverseHTOpMatrix) { expectedMatrixFromComputation([](qc::QuantumComputation& comp) { comp.addQubitRegister(2, "q"); qc::CompoundOperation body; - body.emplace_back(1, 0, qc::OpType::H); - body.emplace_back(1, 0, qc::OpType::T); + body.emplace_back(1, qc::OpType::H); + body.emplace_back(1, qc::OpType::T); body.invert(); + body.addControl(qc::Control{0}); comp.push_back(body); }); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index c23426918b..797871751c 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -232,33 +232,21 @@ TEST(DynamicMatrix, AssignFrom) { EXPECT_TRUE(dynamic.isApprox(source)); } -TEST(DynamicMatrix, SetBottomRightCorner) { - const Matrix2x2 x = pauliX(); - const Matrix4x4 swap = swapMatrix(); +TEST(DynamicMatrix, EmbedControlledUnitary) { + // Controlled-X with control on wire 0 and target on wire 1. + const auto controlledX = + embedControlledUnitary(2, {0}, {1}, DynamicMatrix(pauliX())); + EXPECT_EQ(controlledX(0, 0), 1.0); + EXPECT_EQ(controlledX(1, 1), 0.0); + EXPECT_EQ(controlledX(1, 3), 1.0); + EXPECT_EQ(controlledX(2, 2), 1.0); + EXPECT_EQ(controlledX(3, 1), 1.0); + EXPECT_EQ(controlledX(3, 3), 0.0); +} - DynamicMatrix with2x2 = DynamicMatrix::identity(4); - with2x2.setBottomRightCorner(x); - EXPECT_EQ(with2x2(0, 0), 1.0); - EXPECT_EQ(with2x2(2, 2), 0.0); - EXPECT_EQ(with2x2(2, 3), 1.0); - EXPECT_EQ(with2x2(3, 2), 1.0); - - DynamicMatrix with4x4 = DynamicMatrix::identity(6); - with4x4.setBottomRightCorner(swap); - EXPECT_EQ(with4x4(0, 0), 1.0); - EXPECT_EQ(with4x4(1, 1), 1.0); - EXPECT_EQ(with4x4(2, 2), 1.0); - EXPECT_EQ(with4x4(3, 4), 1.0); - EXPECT_EQ(with4x4(4, 3), 1.0); - EXPECT_EQ(with4x4(5, 5), 1.0); - - DynamicMatrix block = DynamicMatrix::identity(2); - block(0, 1) = 1i; - DynamicMatrix withDynamic = DynamicMatrix::identity(3); - withDynamic.setBottomRightCorner(block); - EXPECT_EQ(withDynamic(1, 1), 1.0); - EXPECT_EQ(withDynamic(1, 2), 1i); - EXPECT_EQ(withDynamic(2, 1), 0.0); +TEST(DynamicMatrix, EmbedControlledUnitaryRejectsOutOfRangeWire) { + EXPECT_DEATH(embedControlledUnitary(2, {2}, {1}, DynamicMatrix(pauliX())), + "Control wire index out of range"); } TEST(DynamicMatrix, Adjoint) { @@ -409,28 +397,28 @@ TEST(UnitaryMatrix4x4, DiagonalRowsColumnsAndParts) { TEST(UnitaryMatrix4x4, KroneckerProduct) { const Matrix2x2 x = pauliX(); - // X (x) I should swap the high bit. - const Matrix4x4 xi = Matrix4x4::kron(x, Matrix2x2::identity()); - EXPECT_TRUE(xi.isApprox(Matrix4x4::fromElements(0, 0, 1, 0, // row 0 - 0, 0, 0, 1, // row 1 - 1, 0, 0, 0, // row 2 - 0, 1, 0, 0))); - // I (x) X swaps the low bit. + // I (x) X acts on wire 0. const Matrix4x4 ix = Matrix4x4::kron(Matrix2x2::identity(), x); EXPECT_TRUE(ix.isApprox(Matrix4x4::fromElements(0, 1, 0, 0, // row 0 1, 0, 0, 0, // row 1 0, 0, 0, 1, // row 2 0, 0, 1, 0))); + // X (x) I acts on wire 1. + const Matrix4x4 xi = Matrix4x4::kron(x, Matrix2x2::identity()); + EXPECT_TRUE(xi.isApprox(Matrix4x4::fromElements(0, 0, 1, 0, // row 0 + 0, 0, 0, 1, // row 1 + 1, 0, 0, 0, // row 2 + 0, 1, 0, 0))); } TEST(UnitaryMatrix4x4, ReorderTwoQubitMatrix) { const Matrix2x2 x = pauliX(); - const Matrix4x4 onHigh = Matrix4x4::kron(x, Matrix2x2::identity()); - const Matrix4x4 onLow = Matrix4x4::kron(Matrix2x2::identity(), x); + const Matrix4x4 onWire0 = Matrix4x4::kron(Matrix2x2::identity(), x); + const Matrix4x4 onWire1 = Matrix4x4::kron(x, Matrix2x2::identity()); - EXPECT_TRUE(onHigh.reorderForQubits(0, 1).isApprox(onHigh)); - EXPECT_TRUE(onHigh.reorderForQubits(1, 0).isApprox(onLow)); - EXPECT_TRUE(onLow.reorderForQubits(1, 0).isApprox(onHigh)); + EXPECT_TRUE(onWire1.reorderForQubits(0, 1).isApprox(onWire1)); + EXPECT_TRUE(onWire1.reorderForQubits(1, 0).isApprox(onWire0)); + EXPECT_TRUE(onWire0.reorderForQubits(1, 0).isApprox(onWire1)); } TEST(UnitaryDynamicMatrix, NQubitEmbedMatchesTwoQubitSpecialization) { @@ -440,9 +428,9 @@ TEST(UnitaryDynamicMatrix, NQubitEmbedMatchesTwoQubitSpecialization) { 0, 0, 0, 1, // 0, 0, 1, 0); EXPECT_TRUE(x.embedInNqubit(2, 0).isApprox( - Matrix4x4::kron(x, Matrix2x2::identity()))); - EXPECT_TRUE(x.embedInNqubit(2, 1).isApprox( Matrix4x4::kron(Matrix2x2::identity(), x))); + EXPECT_TRUE(x.embedInNqubit(2, 1).isApprox( + Matrix4x4::kron(x, Matrix2x2::identity()))); EXPECT_TRUE(cx.embedInNqubit(2, 0, 1).isApprox(cx.reorderForQubits(0, 1))); const DynamicMatrix cxOn01 = cx.embedInNqubit(3, 0, 1); const DynamicMatrix cxOn12 = cx.embedInNqubit(3, 1, 2); From 66abc830833c5a5aba3826d3b603d5052701bdae Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 14:28:18 +0200 Subject: [PATCH 2/8] =?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 --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 10 ++++++---- mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 936af50588..a95896a47c 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -31,14 +31,13 @@ #include #include +#include #include #include using namespace mlir; using namespace mlir::qco; -namespace { - /** * @brief Returns the program register index of @p qubit when known at compile * time. @@ -46,7 +45,8 @@ namespace { * Supports @c qco.static and @c qtensor.extract with an @c arith.constant * index. Dynamic or negative indices yield @c std::nullopt. */ -[[nodiscard]] std::optional programQubitIndex(const Value qubit) { +[[nodiscard]] static std::optional +programQubitIndex(const Value qubit) { auto* definingOp = qubit.getDefiningOp(); if (definingOp == nullptr) { return std::nullopt; @@ -79,7 +79,7 @@ namespace { * @return Indices in operand order, or @c std::nullopt if any wire is not * resolved by @ref programQubitIndex. */ -[[nodiscard]] std::optional> +[[nodiscard]] static std::optional> resolveQubitIndices(const ValueRange qubits) { SmallVector indices; indices.reserve(qubits.size()); @@ -93,6 +93,8 @@ resolveQubitIndices(const ValueRange qubits) { return indices; } +namespace { + /** * @brief Merge nested control modifiers into a single one. */ 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 dd23ad5d99..d06e1288c6 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -24,6 +24,7 @@ #include "qco_programs.h" #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include using namespace mlir; From f942a2adaf56719326cf13392f151ea3c6c88fcf Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 14:51:08 +0200 Subject: [PATCH 3/8] =?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 --- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 d06e1288c6..e52709695b 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -236,6 +236,34 @@ TEST_F(QCOMatrixTest, ControlledInverseHTOpMatrix) { ASSERT_TRUE(matrix->isApprox(expected)); } + +TEST_F(QCOMatrixTest, StaticQubitControlledXOpMatrix) { + const auto matrix = + ctrlOpMatrixFromBuilder(context.get(), [](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.ctrl(q0, q1, [&](ValueRange targets) { + return SmallVector{b.x(targets[0])}; + }); + }); + ASSERT_TRUE(matrix); + + const Matrix4x4 expected = + expectedMatrixFromComputation([](qc::QuantumComputation& comp) { + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + }); + EXPECT_TRUE(matrix->isApprox(expected)); +} + +TEST_F(QCOMatrixTest, CtrlOpAllocOperandsReturnNullopt) { + EXPECT_FALSE(ctrlOpMatrixFromBuilder(context.get(), [](QCOProgramBuilder& b) { + auto ctrl = b.allocQubit(); + auto tgt = b.allocQubit(); + b.ctrl(ctrl, tgt, + [&](ValueRange targets) { return SmallVector{b.x(targets[0])}; }); + })); +} /// @} /// \name QCO/Modifiers/InvOp.cpp From ba0235522413ddcedf7ca7ca56ac75e9e3db962a Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 16:13:31 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20Refactor=20CtrlOp::getUnitaryMa?= =?UTF-8?q?trix=20to=20use=20minimal=20participating=20qubit=20indices=20a?= =?UTF-8?q?nd=20improve=20matrix=20embedding=20logic.=20Add=20unit=20test?= =?UTF-8?q?=20for=20sparse=20wire=20indices=20to=20ensure=20correctness.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 18 +++++++++++---- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index a95896a47c..24d202bf47 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -383,8 +383,18 @@ std::optional CtrlOp::getUnitaryMatrix() { return std::nullopt; } - const std::size_t numQubits = 1 + std::max(*llvm::max_element(*controlQubits), - *llvm::max_element(*targetQubits)); - return embedControlledUnitary(numQubits, *controlQubits, *targetQubits, - *targetMatrix); + SmallVector participating; + participating.append(*controlQubits); + participating.append(*targetQubits); + llvm::sort(participating); + participating.erase(std::unique(participating.begin(), participating.end()), + participating.end()); + + const auto toLocal = [&](const std::size_t wire) { + return static_cast(llvm::find(participating, wire) - + participating.begin()); + }; + return embedControlledUnitary( + participating.size(), llvm::map_to_vector(*controlQubits, toLocal), + llvm::map_to_vector(*targetQubits, toLocal), *targetMatrix); } 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 e52709695b..d4aefc35b0 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -256,6 +256,28 @@ TEST_F(QCOMatrixTest, StaticQubitControlledXOpMatrix) { EXPECT_TRUE(matrix->isApprox(expected)); } +TEST_F(QCOMatrixTest, SparseWireIndicesUseMinimalParticipatingMatrix) { + const auto cx01 = ctrlOpMatrixFromBuilder(context.get(), cxQubits01); + const auto cx07 = + ctrlOpMatrixFromBuilder(context.get(), [](QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q7 = b.staticQubit(7); + b.ctrl(q0, q7, [&](ValueRange targets) { + return SmallVector{b.x(targets[0])}; + }); + }); + ASSERT_TRUE(cx01); + ASSERT_TRUE(cx07); + EXPECT_TRUE(cx07->isApprox(*cx01)); + + const Matrix4x4 expected = + expectedMatrixFromComputation([](qc::QuantumComputation& comp) { + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + }); + EXPECT_TRUE(cx07->isApprox(expected)); +} + TEST_F(QCOMatrixTest, CtrlOpAllocOperandsReturnNullopt) { EXPECT_FALSE(ctrlOpMatrixFromBuilder(context.get(), [](QCOProgramBuilder& b) { auto ctrl = b.allocQubit(); From 44e18e10d26fb854e754abad4b8dbfb8aac73712 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 16:24:12 +0200 Subject: [PATCH 5/8] =?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 --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 24d202bf47..1ab5a08dde 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -387,7 +387,7 @@ std::optional CtrlOp::getUnitaryMatrix() { participating.append(*controlQubits); participating.append(*targetQubits); llvm::sort(participating); - participating.erase(std::unique(participating.begin(), participating.end()), + participating.erase(std::ranges::unique(participating).end(), participating.end()); const auto toLocal = [&](const std::size_t wire) { From dcbf67547437dd6b23bd21b935bd245d529e2291 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 17:52:57 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comment?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Utils/Matrix.h | 18 +++++++------- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 7 +++--- mlir/lib/Dialect/QCO/Utils/Matrix.cpp | 24 ++++++++++++++++--- .../Dialect/QCO/IR/test_qco_ir_matrix.cpp | 3 ++- .../Dialect/QCO/Utils/test_unitary_matrix.cpp | 15 ++++++++++++ 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index 04bf6a1083..108e2a95ce 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h @@ -741,18 +741,18 @@ class DynamicMatrix { * When every control qubit is \f$|1\rangle\f$, applies @p targetUnitary on * @p targetQubits; otherwise the identity. * - * @param numQubits Size of the qubit register the matrix acts on. - * @param controlQubits Program register indices of the control wires. - * @param targetQubits Program register indices of the target wires, in the - * order used to index @p targetUnitary. + * @param numQubits Size of the embedding Hilbert space (number of local wires). + * @param controlQubits Local wire indices in \f$[0, \texttt{numQubits})\f$ for + * the control wires (compact positions after remapping, not sparse + * program-register indices). + * @param targetQubits Local wire indices in \f$[0, \texttt{numQubits})\f$ for + * the target wires, in the order used to index @p targetUnitary. * @param targetUnitary Local unitary on the target subspace. * @return The controlled unitary on the @p numQubits-qubit Hilbert space. */ -[[nodiscard]] DynamicMatrix -embedControlledUnitary(std::size_t numQubits, - llvm::ArrayRef controlQubits, - llvm::ArrayRef targetQubits, - const DynamicMatrix& targetUnitary); +[[nodiscard]] DynamicMatrix embedControlledUnitary( + std::size_t numQubits, ArrayRef controlQubits, + ArrayRef targetQubits, const DynamicMatrix& targetUnitary); /** * @brief Type trait for the four supported matrix types. diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 1ab5a08dde..69b609ad44 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -45,8 +45,7 @@ using namespace mlir::qco; * Supports @c qco.static and @c qtensor.extract with an @c arith.constant * index. Dynamic or negative indices yield @c std::nullopt. */ -[[nodiscard]] static std::optional -programQubitIndex(const Value qubit) { +[[nodiscard]] static std::optional programQubitIndex(Value qubit) { auto* definingOp = qubit.getDefiningOp(); if (definingOp == nullptr) { return std::nullopt; @@ -80,7 +79,7 @@ programQubitIndex(const Value qubit) { * resolved by @ref programQubitIndex. */ [[nodiscard]] static std::optional> -resolveQubitIndices(const ValueRange qubits) { +resolveQubitIndices(ValueRange qubits) { SmallVector indices; indices.reserve(qubits.size()); for (const auto qubit : qubits) { @@ -387,7 +386,7 @@ std::optional CtrlOp::getUnitaryMatrix() { participating.append(*controlQubits); participating.append(*targetQubits); llvm::sort(participating); - participating.erase(std::ranges::unique(participating).end(), + participating.erase(std::unique(participating.begin(), participating.end()), participating.end()); const auto toLocal = [&](const std::size_t wire) { diff --git a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp index 270a7e2796..f9d0a945bf 100644 --- a/mlir/lib/Dialect/QCO/Utils/Matrix.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp @@ -216,8 +216,6 @@ DynamicMatrix embedControlledUnitary(const std::size_t numQubits, const ArrayRef targetQubits, const DynamicMatrix& targetUnitary) { assert(targetUnitary.rows() == targetUnitary.cols()); - assert(static_cast(targetUnitary.rows()) == - (std::uint64_t{1} << targetQubits.size())); assert(numQubits < std::numeric_limits::digits); for (const auto control : controlQubits) { assert(control < numQubits && "Control wire index out of range"); @@ -225,6 +223,26 @@ DynamicMatrix embedControlledUnitary(const std::size_t numQubits, for (const auto target : targetQubits) { assert(target < numQubits && "Target wire index out of range"); } + for (std::size_t i = 0; i < controlQubits.size(); ++i) { + for (std::size_t j = i + 1; j < controlQubits.size(); ++j) { + assert(controlQubits[i] != controlQubits[j] && + "Duplicate control wire index"); + } + } + for (std::size_t i = 0; i < targetQubits.size(); ++i) { + for (std::size_t j = i + 1; j < targetQubits.size(); ++j) { + assert(targetQubits[i] != targetQubits[j] && + "Duplicate target wire index"); + } + } + for (const auto control : controlQubits) { + for (const auto target : targetQubits) { + assert(control != target && + "Control and target wire indices must not overlap"); + } + } + assert(static_cast(targetUnitary.rows()) == + (std::uint64_t{1} << targetQubits.size())); const auto dim = checkedHilbertDim(numQubits); DynamicMatrix out = DynamicMatrix::identity(dim); @@ -242,7 +260,7 @@ DynamicMatrix embedControlledUnitary(const std::size_t numQubits, const std::size_t passiveMask = ((std::size_t{1} << numQubits) - 1) & ~activeMask; - llvm::SmallVector targetIndexByState(udim); + SmallVector targetIndexByState(udim); for (std::size_t state = 0; state < udim; ++state) { targetIndexByState[state] = static_cast(extractTargetSubIndex(state, targetQubits)); 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 d4aefc35b0..7bd3463c13 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -144,7 +144,7 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { // 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 = ctrlOp.getUnitaryMatrix(); const Matrix4x4 expected = expectedMatrixFromComputation([](qc::QuantumComputation& comp) { @@ -152,6 +152,7 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { comp.cx(0, 1); }); + ASSERT_TRUE(matrix.has_value()); ASSERT_TRUE(matrix->isApprox(expected)); } diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index 797871751c..52c5688132 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -249,6 +249,21 @@ TEST(DynamicMatrix, EmbedControlledUnitaryRejectsOutOfRangeWire) { "Control wire index out of range"); } +TEST(DynamicMatrix, EmbedControlledUnitaryRejectsDuplicateControlWire) { + EXPECT_DEATH(embedControlledUnitary(2, {0, 0}, {1}, DynamicMatrix(pauliX())), + "Duplicate control wire index"); +} + +TEST(DynamicMatrix, EmbedControlledUnitaryRejectsDuplicateTargetWire) { + EXPECT_DEATH(embedControlledUnitary(2, {0}, {1, 1}, DynamicMatrix(pauliX())), + "Duplicate target wire index"); +} + +TEST(DynamicMatrix, EmbedControlledUnitaryRejectsOverlappingControlAndTarget) { + EXPECT_DEATH(embedControlledUnitary(2, {0}, {0}, DynamicMatrix(pauliX())), + "Control and target wire indices must not overlap"); +} + TEST(DynamicMatrix, Adjoint) { DynamicMatrix matrix(2); matrix(0, 0) = 1.0; From 5c8ecde89a77359f49bfd37ad6a2c3bc5b092c90 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 18:03:44 +0200 Subject: [PATCH 7/8] =?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 --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 69b609ad44..09d15ce6e2 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -386,8 +386,7 @@ std::optional CtrlOp::getUnitaryMatrix() { participating.append(*controlQubits); participating.append(*targetQubits); llvm::sort(participating); - participating.erase(std::unique(participating.begin(), participating.end()), - participating.end()); + participating.erase(llvm::unique(participating), participating.end()); const auto toLocal = [&](const std::size_t wire) { return static_cast(llvm::find(participating, wire) - From f8b2d8e27562c68d62fedb1847ce671573eaa4d5 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 30 Jun 2026 18:11:49 +0200 Subject: [PATCH 8/8] =?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 --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 09d15ce6e2..97abb441a8 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -31,7 +31,6 @@ #include #include -#include #include #include