diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h b/mlir/include/mlir/Dialect/QCO/Utils/Matrix.h index cdea703ed4..108e2a95ce 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 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, 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 657c90b8a1..97abb441a8 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,14 +31,66 @@ #include #include -#include #include -#include #include using namespace mlir; using namespace mlir::qco; +/** + * @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]] static std::optional programQubitIndex(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]] static std::optional> +resolveQubitIndices(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; +} + namespace { /** @@ -308,36 +361,37 @@ 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; - }; + const auto controlQubits = resolveQubitIndices(getInputControls()); + const auto targetQubits = resolveQubitIndices(getInputTargets()); + if (!controlQubits || !targetQubits) { + return std::nullopt; + } - // Single inner unitary (e.g. `ctrl { h }`, `ctrl { cx }`). + // Inner unitary on targets: one body op or a composed single-qubit sequence. + std::optional targetMatrix; if (auto bodyUnitary = utils::getSoleBodyUnitary(*getBody())) { - if (const auto targetMatrix = - bodyUnitary.getUnitaryMatrix()) { - assert(targetMatrix->cols() == targetMatrix->rows()); - return controlledMatrix(targetMatrix->cols(), *targetMatrix); + targetMatrix = bodyUnitary.getUnitaryMatrix(); + } else if (getNumTargets() == 1) { + if (const auto composed = composeSingleQubitBodyMatrix(*getBody())) { + targetMatrix = DynamicMatrix(*composed); } + } + if (!targetMatrix) { 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); - } - } + SmallVector participating; + participating.append(*controlQubits); + participating.append(*targetQubits); + llvm::sort(participating); + participating.erase(llvm::unique(participating), participating.end()); - return std::nullopt; + 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/lib/Dialect/QCO/Utils/Matrix.cpp b/mlir/lib/Dialect/QCO/Utils/Matrix.cpp index 50d524daa1..f9d0a945bf 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,102 @@ 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(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"); + } + 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); + 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; + + 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 +397,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 +408,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 +592,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 +942,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..7bd3463c13 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; @@ -85,6 +87,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 { @@ -117,17 +144,40 @@ 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) { comp.addQubitRegister(2, "q"); - comp.cx(1, 0); + comp.cx(0, 1); }); + ASSERT_TRUE(matrix.has_value()); 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 +190,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 +208,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,14 +228,65 @@ 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); }); 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, 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(); + auto tgt = b.allocQubit(); + b.ctrl(ctrl, tgt, + [&](ValueRange targets) { return SmallVector{b.x(targets[0])}; }); + })); +} /// @} /// \name QCO/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp index c23426918b..52c5688132 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_unitary_matrix.cpp @@ -232,33 +232,36 @@ 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); +} + +TEST(DynamicMatrix, EmbedControlledUnitaryRejectsOutOfRangeWire) { + EXPECT_DEATH(embedControlledUnitary(2, {2}, {1}, DynamicMatrix(pauliX())), + "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"); +} - 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, EmbedControlledUnitaryRejectsOverlappingControlAndTarget) { + EXPECT_DEATH(embedControlledUnitary(2, {0}, {0}, DynamicMatrix(pauliX())), + "Control and target wire indices must not overlap"); } TEST(DynamicMatrix, Adjoint) { @@ -409,28 +412,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 +443,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);