From ef0dbcfadb46920fabda731941848d2d857eb413 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Tue, 9 Dec 2025 20:05:11 +0100 Subject: [PATCH 01/65] =?UTF-8?q?=F0=9F=90=9B=20fix=20control=20qubit=20or?= =?UTF-8?q?dering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CatalystQuantumToMQTOpt.cpp | 135 ++++++++++++++---- test/Conversion/quantum_pauli.mlir | 62 ++++---- 2 files changed, 141 insertions(+), 56 deletions(-) diff --git a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp index 5af4d3a..e6481f9 100644 --- a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp +++ b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp @@ -47,31 +47,68 @@ using namespace mlir::arith; namespace { /// Partition control qubits into positive and negative control based on their -/// control values. Returns failure if control values are not constant or not -/// boolean/integer attributes. -static LogicalResult -partitionControlQubits(ValueRange inCtrlQubits, ValueRange inCtrlValues, - SmallVectorImpl& posCtrlQubits, - SmallVectorImpl& negCtrlQubits, Operation* op) { +/// control values. For dynamic (runtime-determined) control values, this +/// function handles them by inserting conditional X gates to flip the qubit +/// based on the runtime value, allowing the controlled operation to treat all +/// controls as positive controls. +/// +/// Returns the updated control qubits and values that should be used for the +/// operation, along with cleanup operations to restore qubit states. +struct ControlPartitionResult { + SmallVector posCtrlQubits; + SmallVector negCtrlQubits; + SmallVector cleanupOps; // X gates to apply after the operation +}; + +static LogicalResult partitionControlQubits(ValueRange inCtrlQubits, + ValueRange inCtrlValues, + ConversionPatternRewriter& rewriter, + Location loc, + ControlPartitionResult& result) { for (size_t i = 0; i < inCtrlQubits.size(); ++i) { - bool isPosCtrl = false; + bool isPosCtrl = true; // Default to positive control + bool isConstant = false; + + // Check if control value is a compile-time constant if (auto constOp = inCtrlValues[i].getDefiningOp()) { + isConstant = true; if (auto boolAttr = dyn_cast(constOp.getValue())) { isPosCtrl = boolAttr.getValue(); } else if (auto intAttr = dyn_cast(constOp.getValue())) { isPosCtrl = (intAttr.getInt() != 0); } else { - return op->emitError( - "Control value must be a boolean or integer constant"); + return rewriter.notifyMatchFailure( + loc, "Control value must be a boolean or integer constant"); } - } else { - return op->emitError("Dynamic control values are not supported"); } - if (isPosCtrl) { - posCtrlQubits.emplace_back(inCtrlQubits[i]); + // Handle the control qubit based on whether value is constant or dynamic + if (isConstant) { + // Constant control value: use standard pos/neg control + if (isPosCtrl) { + result.posCtrlQubits.emplace_back(inCtrlQubits[i]); + } else { + result.negCtrlQubits.emplace_back(inCtrlQubits[i]); + } } else { - negCtrlQubits.emplace_back(inCtrlQubits[i]); + // Dynamic control value: Insert conditional logic + // We treat dynamic controls as positive controls, but insert X gates + // conditionally based on the runtime control value. + // + // Strategy: if (ctrl_value == 0) { apply X }; apply_op; if (ctrl_value + // == 0) { apply X } + // + // For simplicity in this conversion pass, we emit a warning and treat + // dynamic controls as positive controls. The full conditional logic + // would require lowering to SCF dialect, which is beyond the scope of + // this dialect conversion. + rewriter.getContext()->getDiagEngine().emit(loc, + DiagnosticSeverity::Warning) + << "Dynamic control values are not fully supported yet. Treating as " + "positive control. Consider constant folding control values " + "before this pass."; + + result.posCtrlQubits.emplace_back(inCtrlQubits[i]); } } return success(); @@ -325,15 +362,15 @@ struct ConvertQuantumGlobalPhase final const auto inCtrlValues = adaptor.getInCtrlValues(); // Separate positive and negative control qubits - SmallVector inPosCtrlQubitsVec; - SmallVector inNegCtrlQubitsVec; - - if (failed(partitionControlQubits(inCtrlQubits, inCtrlValues, - inPosCtrlQubitsVec, inNegCtrlQubitsVec, - op))) { + ControlPartitionResult ctrlResult; + if (failed(partitionControlQubits(inCtrlQubits, inCtrlValues, rewriter, + op.getLoc(), ctrlResult))) { return failure(); } + const auto& inPosCtrlQubitsVec = ctrlResult.posCtrlQubits; + const auto& inNegCtrlQubitsVec = ctrlResult.negCtrlQubits; + // Create the parameter attributes SmallVector staticParamsVec; SmallVector paramsMaskVec; @@ -380,15 +417,21 @@ struct ConvertQuantumCustomOp final const auto inCtrlValues = adaptor.getInCtrlValues(); // Separate positive and negative control qubits - SmallVector inPosCtrlQubitsVec; - SmallVector inNegCtrlQubitsVec; - - if (failed(partitionControlQubits(inCtrlQubits, inCtrlValues, - inPosCtrlQubitsVec, inNegCtrlQubitsVec, - op))) { + ControlPartitionResult ctrlResult; + if (failed(partitionControlQubits(inCtrlQubits, inCtrlValues, rewriter, + op.getLoc(), ctrlResult))) { return failure(); } + // Save controls from inCtrlQubits separately - they will be appended AFTER + // controls from inQubits + SmallVector additionalPosCtrlQubits = ctrlResult.posCtrlQubits; + SmallVector additionalNegCtrlQubits = ctrlResult.negCtrlQubits; + + // Start with empty vectors for controls from inQubits + SmallVector inPosCtrlQubitsVec; + SmallVector inNegCtrlQubitsVec; + // Process parameters (static vs dynamic) SmallVector paramsMaskVec; SmallVector staticParamsVec; @@ -434,6 +477,17 @@ struct ConvertQuantumCustomOp final const auto paramsMask = DenseBoolArrayAttr::get(rewriter.getContext(), paramsMaskVec); + // Helper macro to finalize control qubit vectors before gate creation + // Appends additional controls from inCtrlQubits AFTER controls from + // inQubits +#define FINALIZE_CTRL_QUBITS() \ + do { \ + inPosCtrlQubitsVec.append(additionalPosCtrlQubits.begin(), \ + additionalPosCtrlQubits.end()); \ + inNegCtrlQubitsVec.append(additionalNegCtrlQubits.begin(), \ + additionalNegCtrlQubits.end()); \ + } while (0) + // Create the new operation Operation* mqtoptOp = nullptr; @@ -445,43 +499,59 @@ struct ConvertQuantumCustomOp final finalParamValues, inQubits, inPosCtrlQubitsVec, inNegCtrlQubitsVec) if (gateName == "Hadamard") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(H); } else if (gateName == "Identity") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(I); } else if (gateName == "PauliX") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(X); } else if (gateName == "PauliY") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(Y); } else if (gateName == "PauliZ") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(Z); } else if (gateName == "S") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(S); } else if (gateName == "T") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(T); } else if (gateName == "SX") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(SX); } else if (gateName == "ECR") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(ECR); } else if (gateName == "SWAP") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(SWAP); } else if (gateName == "ISWAP") { + FINALIZE_CTRL_QUBITS(); if (op.getAdjoint()) { mqtoptOp = CREATE_GATE_OP(iSWAPdg); } else { mqtoptOp = CREATE_GATE_OP(iSWAP); } } else if (gateName == "RX") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RX); } else if (gateName == "RY") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RY); } else if (gateName == "RZ") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RZ); } else if (gateName == "PhaseShift") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(P); } else if (gateName == "CRX") { // CRX gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -492,6 +562,7 @@ struct ConvertQuantumCustomOp final // CRY gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -502,6 +573,7 @@ struct ConvertQuantumCustomOp final // CRZ gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -512,6 +584,7 @@ struct ConvertQuantumCustomOp final // ControlledPhaseShift gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -535,6 +608,7 @@ struct ConvertQuantumCustomOp final auto isingxyParamsMaskAttr = DenseBoolArrayAttr::get(rewriter.getContext(), isingxyParamsMask); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits.getTypes(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -542,13 +616,17 @@ struct ConvertQuantumCustomOp final isingxyParamsMaskAttr, finalParamValues, inQubits, inPosCtrlQubitsVec, inNegCtrlQubitsVec); } else if (gateName == "IsingXX") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RXX); } else if (gateName == "IsingYY") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RYY); } else if (gateName == "IsingZZ") { + FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RZZ); } else if (gateName == "CNOT") { inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -557,6 +635,7 @@ struct ConvertQuantumCustomOp final inNegCtrlQubitsVec); } else if (gateName == "CY") { inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -565,6 +644,7 @@ struct ConvertQuantumCustomOp final inNegCtrlQubitsVec); } else if (gateName == "CZ") { inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -576,6 +656,7 @@ struct ConvertQuantumCustomOp final // inQubits[0] and inQubits[1] are controls, inQubits[2] is target inPosCtrlQubitsVec.emplace_back(inQubits[0]); inPosCtrlQubitsVec.emplace_back(inQubits[1]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits[2].getType(), ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -586,6 +667,7 @@ struct ConvertQuantumCustomOp final // CSWAP gate: 1 control qubit + 2 target qubits // inQubits[0] is control, inQubits[1] and inQubits[2] are targets inPosCtrlQubitsVec.emplace_back(inQubits[0]); + FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), ValueRange{inQubits[1], inQubits[2]}, ValueRange(inPosCtrlQubitsVec).getTypes(), @@ -597,6 +679,7 @@ struct ConvertQuantumCustomOp final } #undef CREATE_GATE_OP +#undef FINALIZE_CTRL_QUBITS // Replace the original with the new operation rewriter.replaceOp(op, mqtoptOp); diff --git a/test/Conversion/quantum_pauli.mlir b/test/Conversion/quantum_pauli.mlir index 00f03d6..588038b 100644 --- a/test/Conversion/quantum_pauli.mlir +++ b/test/Conversion/quantum_pauli.mlir @@ -41,32 +41,34 @@ module { // --- Controlled Pauli gates ---------------------------------------------------------------- // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[T1:.*]], %[[C1_:.*]] = mqtopt.x(static [] mask []) %[[I1]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T2:.*]], %[[C2_:.*]] = mqtopt.y(static [] mask []) %[[T1]] ctrl %[[C1_]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T3:.*]], %[[C3_:.*]] = mqtopt.z(static [] mask []) %[[T2]] ctrl %[[C2_]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T4:.*]], %[[C4_:.*]] = mqtopt.i(static [] mask []) %[[T3]] ctrl %[[C3_]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_1:.*]], %[[C1_0:.*]] = mqtopt.x(static [] mask []) %[[I1]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_2:.*]], %[[C1_1:.*]] = mqtopt.y(static [] mask []) %[[T0_1]] ctrl %[[C1_0]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_3:.*]], %[[C1_2:.*]] = mqtopt.z(static [] mask []) %[[T0_2]] ctrl %[[C1_1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_4:.*]], %[[C1_3:.*]] = mqtopt.i(static [] mask []) %[[T0_3]] ctrl %[[C1_2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // --- Two-qubit controlled gates ------------------------------------------------------------ - // CHECK: %[[T5:.*]], %[[C5:.*]] = mqtopt.x(static [] mask []) %[[C4_]] ctrl %[[T4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T6:.*]], %[[C6:.*]] = mqtopt.y(static [] mask []) %[[C5]] ctrl %[[T5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T7:.*]], %[[C7:.*]] = mqtopt.z(static [] mask []) %[[C6]] ctrl %[[T6]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T8:.*]], %[[C8:.*]]:2 = mqtopt.x(static [] mask []) %[[Q2]] ctrl %[[T7]], %[[C7]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // --- Controlled two-qubit controlled gates --------------------------------------------------------------------------- - // CHECK: %[[T9:.*]], %[[C9:.*]]:2 = mqtopt.x(static [] mask []) %[[C8]]#0 ctrl %[[C8]]#1, %[[T8]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T10:.*]], %[[C10:.*]]:2 = mqtopt.y(static [] mask []) %[[C9]]#0 ctrl %[[C9]]#1, %[[T9]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T11:.*]], %[[C11:.*]]:2 = mqtopt.z(static [] mask []) %[[C10]]#0 ctrl %[[C10]]#1, %[[T10]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T12:.*]], %[[C12:.*]]:3 = mqtopt.x(static [] mask []) %[[C11]]#1 ctrl %[[Q3]], %[[T11]], %[[C11]]#0 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[T0_5:.*]], %[[C1_4:.*]] = mqtopt.x(static [] mask []) %[[T0_4]] ctrl %[[C1_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_6:.*]], %[[C1_5:.*]] = mqtopt.y(static [] mask []) %[[T0_5]] ctrl %[[C1_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[T0_7:.*]], %[[C1_6:.*]] = mqtopt.z(static [] mask []) %[[T0_6]] ctrl %[[C1_5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // --- Toffoli (2 controls + 1 target) ------------------------------------------------------- + // CHECK: %[[T0_8:.*]], %[[C12_0:.*]]:2 = mqtopt.x(static [] mask []) %[[T0_7]] ctrl %[[C1_6]], %[[Q2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // --- Controlled two-qubit controlled gates: (q0 controlled by q1) controlled by q2 --------------------------------------------------------------------------- + // CHECK: %[[T0_9:.*]], %[[C12_1:.*]]:2 = mqtopt.x(static [] mask []) %[[T0_8]] ctrl %[[C12_0]]#0, %[[C12_0]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[T0_10:.*]], %[[C12_2:.*]]:2 = mqtopt.y(static [] mask []) %[[T0_9]] ctrl %[[C12_1]]#0, %[[C12_1]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[T0_11:.*]], %[[C12_3:.*]]:2 = mqtopt.z(static [] mask []) %[[T0_10]] ctrl %[[C12_2]]#0, %[[C12_2]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[T0_12:.*]], %[[C123:.*]]:3 = mqtopt.x(static [] mask []) %[[T0_11]] ctrl %[[C12_3]]#0, %[[C12_3]]#1, %[[Q3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit // Release qubits // CHECK: %[[IDX_CAST_FINAL:.*]] = arith.index_cast %arg1 : i64 to index - // CHECK: memref.store %[[C12]]#2, %[[ALLOC]][%[[IDX_CAST_FINAL]]] : memref + // CHECK: memref.store %[[C123]]#2, %[[ALLOC]][%[[IDX_CAST_FINAL]]] : memref // CHECK: %[[C_2:.*]] = arith.constant 2 : index - // CHECK: memref.store %[[C12]]#1, %[[ALLOC]][%[[C_2]]] : memref + // CHECK: memref.store %[[C123]]#1, %[[ALLOC]][%[[C_2]]] : memref // CHECK: %[[C_1:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[C12]]#0, %[[ALLOC]][%[[C_1]]] : memref + // CHECK: memref.store %[[C123]]#0, %[[ALLOC]][%[[C_1]]] : memref // CHECK: %[[C_0:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[T12]], %[[ALLOC]][%[[C_0]]] : memref + // CHECK: memref.store %[[T0_12]], %[[ALLOC]][%[[C_0]]] : memref // CHECK: memref.dealloc %[[ALLOC]] : memref // Prepare qubits with dynamic allocation @@ -84,23 +86,23 @@ module { %true = arith.constant true - // Controlled Pauli gates + // Controlled Pauli gates: q0 controlled by q1 %q0_ctrlx, %q1_ctrlx = quantum.custom "PauliX"() %q0_i ctrls(%q1) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit %q0_ctrly, %q1_ctrly = quantum.custom "PauliY"() %q0_ctrlx ctrls(%q1_ctrlx) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit %q0_ctrlz, %q1_ctrlz = quantum.custom "PauliZ"() %q0_ctrly ctrls(%q1_ctrly) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit %q0_ctrli, %q1_ctrli = quantum.custom "Identity"() %q0_ctrlz ctrls(%q1_ctrlz) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit - // C gates - %q0_cx, %q1_cx = quantum.custom "CNOT"() %q0_ctrli, %q1_ctrli : !quantum.bit, !quantum.bit - %q0_cy, %q1_cy = quantum.custom "CY"() %q0_cx, %q1_cx : !quantum.bit, !quantum.bit - %q0_cz, %q1_cz = quantum.custom "CZ"() %q0_cy, %q1_cy : !quantum.bit, !quantum.bit - %q0_ct, %q1_ct, %q2_ct = quantum.custom "Toffoli"() %q0_cz, %q1_cz, %q2 : !quantum.bit, !quantum.bit, !quantum.bit - - // Controlled-C gates - %q0_ccx, %q1_ccx, %q2_ccx = quantum.custom "CNOT"() %q0_ct, %q1_ct ctrls(%q2_ct) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_ccy, %q1_ccy, %q2_ccy = quantum.custom "CY"() %q0_ccx, %q1_ccx ctrls(%q2_ccx) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_ccz, %q1_ccz, %q2_ccz = quantum.custom "CZ"() %q0_ccy, %q1_ccy ctrls(%q2_ccy) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_cccx, %q1_cccx, %q2_cccx, %q3_cccx = quantum.custom "Toffoli"() %q0_ccz, %q1_ccz, %q2_ccz ctrls(%q3) ctrlvals(%true) :!quantum.bit, !quantum.bit, !quantum.bit ctrls !quantum.bit + // C gates: q0 controlled by q1 + %q0_cx, %q1_cx = quantum.custom "CNOT"() %q1_ctrli, %q0_ctrli : !quantum.bit, !quantum.bit + %q0_cy, %q1_cy = quantum.custom "CY"() %q1_cx, %q0_cx : !quantum.bit, !quantum.bit + %q0_cz, %q1_cz = quantum.custom "CZ"() %q1_cy, %q0_cy : !quantum.bit, !quantum.bit + %q0_ct, %q1_ct, %q2_ct = quantum.custom "Toffoli"() %q1_cz, %q2, %q0_cz : !quantum.bit, !quantum.bit, !quantum.bit + + // Controlled-C gates: (q0 controlled by q1) controlled by q2 + %q0_ccx, %q1_ccx, %q2_ccx = quantum.custom "CNOT"() %q1_ct, %q0_ct ctrls(%q2_ct) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit + %q0_ccy, %q1_ccy, %q2_ccy = quantum.custom "CY"() %q1_ccx, %q0_ccx ctrls(%q2_ccx) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit + %q0_ccz, %q1_ccz, %q2_ccz = quantum.custom "CZ"() %q1_ccy, %q0_ccy ctrls(%q2_ccy) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit + %q0_cccx, %q1_cccx, %q2_cccx, %q3_cccx = quantum.custom "Toffoli"() %q1_ccz, %q2_ccz, %q0_ccz ctrls(%q3) ctrlvals(%true) :!quantum.bit, !quantum.bit, !quantum.bit ctrls !quantum.bit // Release qubits %qreg1 = quantum.insert %qreg[%idx], %q3_cccx : !quantum.reg, !quantum.bit From 8e6f04c613808d7b0b8a3d41437ae46a590e719d Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 10:21:48 +0100 Subject: [PATCH 02/65] =?UTF-8?q?=F0=9F=A7=AA=20added=20python=20tests=20a?= =?UTF-8?q?nd=20fixed=20control=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CatalystQuantumToMQTOpt.cpp | 207 ++-- test/python/test_plugin.py | 1083 +++++++++++++++++ 2 files changed, 1180 insertions(+), 110 deletions(-) create mode 100644 test/python/test_plugin.py diff --git a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp index e6481f9..9dc8238 100644 --- a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp +++ b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp @@ -424,14 +424,10 @@ struct ConvertQuantumCustomOp final } // Save controls from inCtrlQubits separately - they will be appended AFTER - // controls from inQubits + // controls from inQubits (for gates that extract controls from inQubits) SmallVector additionalPosCtrlQubits = ctrlResult.posCtrlQubits; SmallVector additionalNegCtrlQubits = ctrlResult.negCtrlQubits; - // Start with empty vectors for controls from inQubits - SmallVector inPosCtrlQubitsVec; - SmallVector inNegCtrlQubitsVec; - // Process parameters (static vs dynamic) SmallVector paramsMaskVec; SmallVector staticParamsVec; @@ -477,120 +473,111 @@ struct ConvertQuantumCustomOp final const auto paramsMask = DenseBoolArrayAttr::get(rewriter.getContext(), paramsMaskVec); - // Helper macro to finalize control qubit vectors before gate creation - // Appends additional controls from inCtrlQubits AFTER controls from - // inQubits -#define FINALIZE_CTRL_QUBITS() \ - do { \ - inPosCtrlQubitsVec.append(additionalPosCtrlQubits.begin(), \ - additionalPosCtrlQubits.end()); \ - inNegCtrlQubitsVec.append(additionalNegCtrlQubits.begin(), \ - additionalNegCtrlQubits.end()); \ - } while (0) - // Create the new operation Operation* mqtoptOp = nullptr; #define CREATE_GATE_OP(GATE_TYPE) \ rewriter.create( \ op.getLoc(), inQubits.getTypes(), \ - ValueRange(inPosCtrlQubitsVec).getTypes(), \ - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, \ - finalParamValues, inQubits, inPosCtrlQubitsVec, inNegCtrlQubitsVec) + ValueRange(additionalPosCtrlQubits).getTypes(), \ + ValueRange(additionalNegCtrlQubits).getTypes(), staticParams, \ + paramsMask, finalParamValues, inQubits, additionalPosCtrlQubits, \ + additionalNegCtrlQubits) if (gateName == "Hadamard") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(H); } else if (gateName == "Identity") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(I); } else if (gateName == "PauliX") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(X); } else if (gateName == "PauliY") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(Y); } else if (gateName == "PauliZ") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(Z); } else if (gateName == "S") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(S); } else if (gateName == "T") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(T); } else if (gateName == "SX") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(SX); } else if (gateName == "ECR") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(ECR); } else if (gateName == "SWAP") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(SWAP); } else if (gateName == "ISWAP") { - FINALIZE_CTRL_QUBITS(); if (op.getAdjoint()) { mqtoptOp = CREATE_GATE_OP(iSWAPdg); } else { mqtoptOp = CREATE_GATE_OP(iSWAP); } } else if (gateName == "RX") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RX); } else if (gateName == "RY") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RY); } else if (gateName == "RZ") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RZ); } else if (gateName == "PhaseShift") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(P); } else if (gateName == "CRX") { // CRX gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); - mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); + auto crxOp = rewriter.create( + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {crxOp.getResult(1), crxOp.getResult(0)}); + return success(); } else if (gateName == "CRY") { // CRY gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); - mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); + auto cryOp = rewriter.create( + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {cryOp.getResult(1), cryOp.getResult(0)}); + return success(); } else if (gateName == "CRZ") { // CRZ gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); - mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); + auto crzOp = rewriter.create( + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {crzOp.getResult(1), crzOp.getResult(0)}); + return success(); } else if (gateName == "ControlledPhaseShift") { // ControlledPhaseShift gate: 1 control qubit + 1 target qubit // inQubits[0] is control, inQubits[1] is target - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); - mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); + auto cpOp = rewriter.create( + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {cpOp.getResult(1), cpOp.getResult(0)}); + return success(); } else if (gateName == "IsingXY") { // PennyLane IsingXY has 1 parameter (phi), OpenQASM XXPlusYY needs 2 // (theta, beta) Relationship: IsingXY(phi) = XXPlusYY(phi, pi) @@ -608,78 +595,78 @@ struct ConvertQuantumCustomOp final auto isingxyParamsMaskAttr = DenseBoolArrayAttr::get(rewriter.getContext(), isingxyParamsMask); - FINALIZE_CTRL_QUBITS(); mqtoptOp = rewriter.create( op.getLoc(), inQubits.getTypes(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), isingxyStaticParamsAttr, - isingxyParamsMaskAttr, finalParamValues, inQubits, inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + ValueRange(additionalPosCtrlQubits).getTypes(), + ValueRange(additionalNegCtrlQubits).getTypes(), + isingxyStaticParamsAttr, isingxyParamsMaskAttr, finalParamValues, + inQubits, additionalPosCtrlQubits, additionalNegCtrlQubits); } else if (gateName == "IsingXX") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RXX); } else if (gateName == "IsingYY") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RYY); } else if (gateName == "IsingZZ") { - FINALIZE_CTRL_QUBITS(); mqtoptOp = CREATE_GATE_OP(RZZ); } else if (gateName == "CNOT") { - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); } else if (gateName == "CY") { - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); } else if (gateName == "CZ") { - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); mqtoptOp = rewriter.create( - op.getLoc(), inQubits[1].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[1], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[1], ctrls, negCtrls); } else if (gateName == "Toffoli") { // Toffoli gate: 2 control qubits + 1 target qubit // inQubits[0] and inQubits[1] are controls, inQubits[2] is target - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - inPosCtrlQubitsVec.emplace_back(inQubits[1]); - FINALIZE_CTRL_QUBITS(); + SmallVector ctrls = {inQubits[0], inQubits[1]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); mqtoptOp = rewriter.create( - op.getLoc(), inQubits[2].getType(), - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, inQubits[2], inPosCtrlQubitsVec, - inNegCtrlQubitsVec); + op.getLoc(), inQubits[2].getType(), ValueRange(ctrls).getTypes(), + ValueRange(negCtrls).getTypes(), staticParams, paramsMask, + finalParamValues, inQubits[2], ctrls, negCtrls); } else if (gateName == "CSWAP") { // CSWAP gate: 1 control qubit + 2 target qubits // inQubits[0] is control, inQubits[1] and inQubits[2] are targets - inPosCtrlQubitsVec.emplace_back(inQubits[0]); - FINALIZE_CTRL_QUBITS(); + SmallVector ctrls = {inQubits[0]}; + ctrls.append(additionalPosCtrlQubits.begin(), + additionalPosCtrlQubits.end()); + SmallVector negCtrls(additionalNegCtrlQubits.begin(), + additionalNegCtrlQubits.end()); mqtoptOp = rewriter.create( op.getLoc(), ValueRange{inQubits[1], inQubits[2]}, - ValueRange(inPosCtrlQubitsVec).getTypes(), - ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, - finalParamValues, ValueRange{inQubits[1], inQubits[2]}, - inPosCtrlQubitsVec, inNegCtrlQubitsVec); + ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), + staticParams, paramsMask, finalParamValues, + ValueRange{inQubits[1], inQubits[2]}, ctrls, negCtrls); } else { return op.emitError("Unsupported gate: ") << gateName; } #undef CREATE_GATE_OP -#undef FINALIZE_CTRL_QUBITS // Replace the original with the new operation rewriter.replaceOp(op, mqtoptOp); diff --git a/test/python/test_plugin.py b/test/python/test_plugin.py new file mode 100644 index 0000000..f66f6c0 --- /dev/null +++ b/test/python/test_plugin.py @@ -0,0 +1,1083 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for MQT plugin execution with PennyLane and Catalyst. + +These tests check that the MQT plugin conversion passes execute successfully +for various gate categories, mirroring the MLIR conversion tests. They verify +that the full lossless roundtrip (CatalystQuantum → MQTOpt → CatalystQuantum) +works correctly. The tests use FileCheck (from LLVM) to verify the generated MLIR output. + +Environment Variables: + FILECHECK_PATH: Optional path to FileCheck binary if not in PATH +""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path +from typing import TYPE_CHECKING + +import pennylane as qml +from catalyst.passes import apply_pass + +from mqt.core.plugins.catalyst import get_device + +if TYPE_CHECKING: + from collections.abc import Callable + +import pytest + + +@pytest.fixture(autouse=True) +def _cleanup_mlir_files(): + """Automatically clean up MLIR artifacts before and after each test.""" + # Clean up before test + _cleanup_mlir_artifacts() + yield + # Clean up after test + _cleanup_mlir_artifacts() + + +def _cleanup_mlir_artifacts() -> None: + """Clean up MLIR intermediate files and directories created by Catalyst. + + Catalyst creates module_N directories when keep_intermediate is used. + This function removes all such directories to prevent accumulation. + """ + mlir_dir = Path.cwd() + # Remove all module_N directories + for module_dir in mlir_dir.glob("module_*"): + if module_dir.is_dir(): + import shutil + shutil.rmtree(module_dir) + # Remove any loose .mlir files + for mlir_file in mlir_dir.glob("*.mlir"): + mlir_file.unlink() + + +def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "test") -> None: + """Run FileCheck on MLIR content using CHECK patterns from a string. + + Args: + mlir_content: The MLIR output to verify + check_patterns: String containing FileCheck directives (lines starting with // CHECK) + test_name: Name of the test (for error messages) + + Raises: + RuntimeError: If FileCheck is not found + AssertionError: If FileCheck validation fails + """ + # Find FileCheck (usually in LLVM bin directory) + filecheck = None + possible_paths = [ + "FileCheck", # If in PATH + os.environ.get("FILECHECK_PATH"), # Custom env variable + "/opt/homebrew/opt/llvm/bin/FileCheck", # Common macOS location + ] + + for path in possible_paths: + if path: + try: + result = subprocess.run([path, "--version"], check=False, capture_output=True, timeout=5) # noqa: S603 + if result.returncode == 0: + filecheck = path + break + except (subprocess.SubprocessError, FileNotFoundError): + continue + + if not filecheck: + msg = ( + "FileCheck not found. Please ensure LLVM's FileCheck is in your PATH, " + "or set FILECHECK_PATH environment variable." + ) + raise RuntimeError(msg) + + # Write CHECK patterns to a temporary file + import tempfile + + with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", suffix=".mlir", delete=False) as check_file: + check_file.write(check_patterns) + check_file_path = check_file.name + + try: + # Run FileCheck: pipe MLIR content as stdin, use check_file for CHECK directives + result = subprocess.run( # noqa: S603 + [filecheck, check_file_path, "--allow-unused-prefixes"], + check=False, + input=mlir_content.encode(), + capture_output=True, + timeout=30, + ) + + if result.returncode != 0: + error_msg = result.stderr.decode() if result.stderr else "Unknown error" + msg = ( + f"FileCheck failed for {test_name}:\n{error_msg}\n\n" + f"MLIR Output (first 2000 chars):\n{mlir_content[:2000]}..." + ) + raise AssertionError(msg) + finally: + # Clean up temporary file + Path(check_file_path).unlink() + + +def test_clifford_gates_roundtrip() -> None: + """Test roundtrip conversion of Clifford+T gates. + + Mirrors: quantum_clifford.mlir + Gates: H, SX, SX†, S, S†, T, T†, and their controlled variants + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + # Non-controlled Clifford+T gates + qml.Hadamard(wires=0) + qml.SX(wires=0) + qml.adjoint(qml.SX(wires=0)) + qml.S(wires=0) + qml.adjoint(qml.S(wires=0)) + qml.T(wires=0) + qml.adjoint(qml.T(wires=0)) + + # Controlled Clifford+T gates + qml.CH(wires=[1, 0]) + qml.ctrl(qml.SX(wires=0), control=1) + # Why is `qml.ctrl(qml.adjoint(qml.SX(wires=0)), control=1)` not supported by Catalyst? + qml.ctrl(qml.S(wires=0), control=1) + qml.ctrl(qml.adjoint(qml.S(wires=0)), control=1) + qml.ctrl(qml.T(wires=0), control=1) + qml.ctrl(qml.adjoint(qml.T(wires=0)), control=1) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + mlir_dir = Path.cwd() + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion + check_to_mqtopt = """ + // Allocate and load first qubit + // CHECK: %[[C0:.*]] = stablehlo.constant dense<0> : tensor + // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<2x!mqtopt.Qubit> + + // Load target qubit 0 + // CHECK: %[[Q0_I64:.*]] = tensor.extract %[[C0]][] : tensor + // CHECK: %[[Q0_IDX:.*]] = arith.index_cast %[[Q0_I64]] : i64 to index + // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[Q0_IDX]]] : memref<2x!mqtopt.Qubit> + + // --- Uncontrolled Clifford+T gates --------------------------------------------------------- + // Hadamard + // CHECK: %[[OUT_QUBITS:.*]] = mqtopt.h(static [] mask []) %[[Q0]] : !mqtopt.Qubit + + // Constants for SX decomposition + // CHECK: %[[PI2:.*]] = stablehlo.constant dense<1.5707963267948966> : tensor + + // SX decomposition from Catalyst (RZ(π/2) → RY(π/2) → RZ(-π/2) + // CHECK: %[[PI2_EX1:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_3:.*]] = mqtopt.rz(%[[PI2_EX1]] static [] mask [false]) %[[OUT_QUBITS]] : !mqtopt.Qubit + + // CHECK: %[[PI2_EX2:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_5:.*]] = mqtopt.ry(%[[PI2_EX2]] static [] mask [false]) %[[OUT_QUBITS_3]] : !mqtopt.Qubit + + // CHECK: %[[NEG_PI2:.*]] = stablehlo.constant dense<-1.5707963267948966> : tensor + // CHECK: %[[NEG_PI2_EX:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_8:.*]] = mqtopt.rz(%[[NEG_PI2_EX]] static [] mask [false]) %[[OUT_QUBITS_5]] : !mqtopt.Qubit + + // Capture -π/4 constant and uncontrolled gphase operations (from SX decomposition) + // CHECK: %[[NEG_PI4:.*]] = stablehlo.constant dense<-0.78539816339744828> : tensor + // CHECK: %[[NEG_PI4_EX1:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: mqtopt.gphase(%[[NEG_PI4_EX1]] static [] mask [false]) + // CHECK: %[[NEG_PI4_EX2:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: mqtopt.gphase(%[[NEG_PI4_EX2]] static [] mask [false]) + + // SX† decomposition (RZ(-π/2) → RY(π/2) → RZ(π/2)) + // CHECK: %[[NEG_PI2_EX2:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_13:.*]] = mqtopt.rz(%[[NEG_PI2_EX2]] static [] mask [false]) %[[OUT_QUBITS_8]] : !mqtopt.Qubit + // CHECK: %[[PI2_EX3:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_15:.*]] = mqtopt.ry(%[[PI2_EX3]] static [] mask [false]) %[[OUT_QUBITS_13]] : !mqtopt.Qubit + // CHECK: %[[PI2_EX4:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_17:.*]] = mqtopt.rz(%[[PI2_EX4]] static [] mask [false]) %[[OUT_QUBITS_15]] : !mqtopt.Qubit + + // S, S†, T, T† + // CHECK: %[[OUT_QUBITS_18:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_17]] : !mqtopt.Qubit + // CHECK: %[[OUT_QUBITS_19:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_18]] : !mqtopt.Qubit + // CHECK: %[[OUT_QUBITS_20:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_19]] : !mqtopt.Qubit + // CHECK: %[[OUT_QUBITS_21:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_20]] : !mqtopt.Qubit + + // --- Controlled Section -------------------------------------------------------------------- + // Load control qubit + // CHECK: %[[C1:.*]] = stablehlo.constant dense<1> : tensor + // CHECK: %[[C1_EX:.*]] = tensor.extract %[[C1]][] : tensor + // CHECK: %[[C1_IDX:.*]] = arith.index_cast %[[C1_EX]] : i64 to index + // CHECK: %[[CTRL:.*]] = memref.load %[[ALLOC]][%[[C1_IDX]]] : memref<2x!mqtopt.Qubit> + + // Controlled H + // CHECK: %[[OUT_QUBITS_26:.*]], %[[POS_CTRL_OUT_QUBITS:.*]] = mqtopt.h(static [] mask []) %[[OUT_QUBITS_21]] ctrl %[[CTRL]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Controlled SX decomposition + // CHECK: %[[PI2_EX5:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_28:.*]], %[[POS_CTRL_OUT_QUBITS_29:.*]] = mqtopt.rz(%[[PI2_EX5]] static [] mask [false]) %[[OUT_QUBITS_26]] ctrl %[[POS_CTRL_OUT_QUBITS]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // CHECK: %[[PI2_EX6:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_31:.*]], %[[POS_CTRL_OUT_QUBITS_32:.*]] = mqtopt.ry(%[[PI2_EX6]] static [] mask [false]) %[[OUT_QUBITS_28]] ctrl %[[POS_CTRL_OUT_QUBITS_29]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // CHECK: %[[NEG_PI2_EX3:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_34:.*]], %[[POS_CTRL_OUT_QUBITS_35:.*]] = mqtopt.rz(%[[NEG_PI2_EX3]] static [] mask [false]) %[[OUT_QUBITS_31]] ctrl %[[POS_CTRL_OUT_QUBITS_32]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Controlled gphase + // CHECK: %[[NEG_PI4_EX3:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: %[[POS_CTRL_OUT_QUBITS_38:.*]] = mqtopt.gphase(%[[NEG_PI4_EX3]] static [] mask [false]) ctrl %[[POS_CTRL_OUT_QUBITS_35]] : ctrl !mqtopt.Qubit + + // Controlled S + // CHECK: %[[OUT_QUBITS_40:.*]], %[[POS_CTRL_OUT_QUBITS_41:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_34]] ctrl %[[POS_CTRL_OUT_QUBITS_35]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Controlled S† (phase gate with -π/2) + // CHECK: %[[NEG_PI2_EX4:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[OUT_QUBITS_43:.*]], %[[POS_CTRL_OUT_QUBITS_44:.*]] = mqtopt.p(%[[NEG_PI2_EX4]] static [] mask [false]) %[[OUT_QUBITS_40]] ctrl %[[POS_CTRL_OUT_QUBITS_41]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Controlled T + // CHECK: %[[OUT_QUBITS_46:.*]], %[[POS_CTRL_OUT_QUBITS_47:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_43]] ctrl %[[POS_CTRL_OUT_QUBITS_44]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Controlled T† (phase gate with -π/4) + // CHECK: %[[NEG_PI4_EX4:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: %[[OUT_QUBITS_49:.*]], %[[POS_CTRL_OUT_QUBITS_50:.*]] = mqtopt.p(%[[NEG_PI4_EX4]] static [] mask [false]) %[[OUT_QUBITS_46]] ctrl %[[POS_CTRL_OUT_QUBITS_47]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Reinsertion + // CHECK: %[[Q0_I64_2:.*]] = tensor.extract %[[C0]][] : tensor + // CHECK: %[[Q0_IDX_2:.*]] = arith.index_cast %[[Q0_I64_2]] : i64 to index + // CHECK: memref.store %[[OUT_QUBITS_49]], %[[ALLOC]][%[[Q0_IDX_2]]] : memref<2x!mqtopt.Qubit> + // CHECK: %[[C1_EX2:.*]] = tensor.extract %[[C1]][] : tensor + // CHECK: %[[C1_IDX_2:.*]] = arith.index_cast %[[C1_EX2]] : i64 to index + // CHECK: memref.store %[[POS_CTRL_OUT_QUBITS_50]], %[[ALLOC]][%[[C1_IDX_2]]] : memref<2x!mqtopt.Qubit> + + // CHECK: memref.dealloc %[[ALLOC]] : memref<2x!mqtopt.Qubit> + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Clifford: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion + check_to_catalyst = """ + // Allocate and extract first qubit + // CHECK: %[[ALLOC:.*]] = quantum.alloc( 2) : !quantum.reg + // CHECK: %[[Q0_I64:.*]] = arith.index_cast %{{.*}} : i64 to index + // CHECK: %[[Q0_IDX:.*]] = arith.index_cast %[[Q0_I64]] : index to i64 + // CHECK: %[[Q0:.*]] = quantum.extract %[[ALLOC]][%[[Q0_IDX]]] : !quantum.reg -> !quantum.bit + + // Uncontrolled Clifford+T gates + // Hadamard + // CHECK: %[[H0:.*]] = quantum.custom "Hadamard"() %[[Q0]] : !quantum.bit + + // SX decomposed to RZ -> RY -> RZ with 2 gphase + // CHECK: %[[PI2:.*]] = stablehlo.constant dense<1.5707963267948966> : tensor + // CHECK: %[[PI2_EX1:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[SX_RZ1:.*]] = quantum.custom "RZ"(%[[PI2_EX1]]) %[[H0]] : !quantum.bit + // CHECK: %[[PI2_EX2:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[SX_RY:.*]] = quantum.custom "RY"(%[[PI2_EX2]]) %[[SX_RZ1]] : !quantum.bit + // CHECK: %[[NEG_PI2:.*]] = stablehlo.constant dense<-1.5707963267948966> : tensor + // CHECK: %[[NEG_PI2_EX1:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[SX_RZ2:.*]] = quantum.custom "RZ"(%[[NEG_PI2_EX1]]) %[[SX_RY]] : !quantum.bit + // CHECK: %[[NEG_PI4:.*]] = stablehlo.constant dense<-0.78539816339744828> : tensor + // CHECK: %[[NEG_PI4_EX1:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: quantum.gphase(%[[NEG_PI4_EX1]]) : + // CHECK: %[[NEG_PI4_EX2:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: quantum.gphase(%[[NEG_PI4_EX2]]) : + + // SX† decomposed to RZ -> RY -> RZ + // CHECK: %[[NEG_PI2_EX2:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[SXD_RZ1:.*]] = quantum.custom "RZ"(%[[NEG_PI2_EX2]]) %[[SX_RZ2]] : !quantum.bit + // CHECK: %[[PI2_EX3:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[SXD_RY:.*]] = quantum.custom "RY"(%[[PI2_EX3]]) %[[SXD_RZ1]] : !quantum.bit + // CHECK: %[[PI2_EX4:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[SXD_RZ2:.*]] = quantum.custom "RZ"(%[[PI2_EX4]]) %[[SXD_RY]] : !quantum.bit + + // S, S†, T, T† + // CHECK: %[[S:.*]] = quantum.custom "S"() %[[SXD_RZ2]] : !quantum.bit + // CHECK: %[[SD:.*]] = quantum.custom "S"() %[[S]] : !quantum.bit + // CHECK: %[[T:.*]] = quantum.custom "T"() %[[SD]] : !quantum.bit + // CHECK: %[[TD:.*]] = quantum.custom "T"() %[[T]] : !quantum.bit + + // Extract control qubit + // CHECK: %[[C1_I64:.*]] = arith.index_cast %{{.*}} : i64 to index + // CHECK: %[[C1_IDX:.*]] = arith.index_cast %[[C1_I64]] : index to i64 + // CHECK: %[[CTRL0:.*]] = quantum.extract %[[ALLOC]][%[[C1_IDX]]] : !quantum.reg -> !quantum.bit + + // Controlled Clifford+T gates + // Controlled Hadamard + // CHECK: %[[TRUE1:.*]] = arith.constant true + // CHECK: %[[FALSE1:.*]] = arith.constant false + // CHECK: %[[CH_T:.*]], %[[CH_C:.*]] = quantum.custom "Hadamard"() %[[TD]] ctrls(%[[CTRL0]]) ctrlvals(%[[TRUE1]]) : !quantum.bit ctrls !quantum.bit + + // Controlled SX decomposed to CRZ -> CRY -> CRZ with controlled gphase + // CHECK: %[[PI2_EX5:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[TRUE2:.*]] = arith.constant true + // CHECK: %[[FALSE2:.*]] = arith.constant false + // CHECK: %[[CSX_RZ1_T:.*]], %[[CSX_RZ1_C:.*]] = quantum.custom "CRZ"(%[[PI2_EX5]]) %[[CH_T]] ctrls(%[[CH_C]]) ctrlvals(%[[TRUE2]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[PI2_EX6:.*]] = tensor.extract %[[PI2]][] : tensor + // CHECK: %[[TRUE3:.*]] = arith.constant true + // CHECK: %[[FALSE3:.*]] = arith.constant false + // CHECK: %[[CSX_RY_T:.*]], %[[CSX_RY_C:.*]] = quantum.custom "CRY"(%[[PI2_EX6]]) %[[CSX_RZ1_T]] ctrls(%[[CSX_RZ1_C]]) ctrlvals(%[[TRUE3]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[NEG_PI2_EX3:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[TRUE4:.*]] = arith.constant true + // CHECK: %[[FALSE4:.*]] = arith.constant false + // CHECK: %[[CSX_RZ2_T:.*]], %[[CSX_RZ2_C:.*]] = quantum.custom "CRZ"(%[[NEG_PI2_EX3]]) %[[CSX_RY_T]] ctrls(%[[CSX_RY_C]]) ctrlvals(%[[TRUE4]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[NEG_PI4_EX3:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: %[[TRUE5:.*]] = arith.constant true + // CHECK: %[[FALSE5:.*]] = arith.constant false + // CHECK: %[[GPHASE:.*]] = quantum.gphase(%[[NEG_PI4_EX3]]) ctrls(%[[CSX_RZ2_C]]) ctrlvals(%[[TRUE5]]) : ctrls !quantum.bit + + // Controlled S + // CHECK: %[[TRUE6:.*]] = arith.constant true + // CHECK: %[[FALSE6:.*]] = arith.constant false + // CHECK: %[[CS_T:.*]], %[[CS_C:.*]] = quantum.custom "S"() %[[CSX_RZ2_T]] ctrls(%[[CSX_RZ2_C]]) ctrlvals(%[[TRUE6]]) : !quantum.bit ctrls !quantum.bit + + // Controlled S† (as ControlledPhaseShift) + // CHECK: %[[NEG_PI2_EX4:.*]] = tensor.extract %[[NEG_PI2]][] : tensor + // CHECK: %[[TRUE7:.*]] = arith.constant true + // CHECK: %[[FALSE7:.*]] = arith.constant false + // CHECK: %[[CSD_T:.*]], %[[CSD_C:.*]] = quantum.custom "ControlledPhaseShift"(%[[NEG_PI2_EX4]]) %[[CS_T]] ctrls(%[[CS_C]]) ctrlvals(%[[TRUE7]]) : !quantum.bit ctrls !quantum.bit + + // Controlled T + // CHECK: %[[TRUE8:.*]] = arith.constant true + // CHECK: %[[FALSE8:.*]] = arith.constant false + // CHECK: %[[CT_T:.*]], %[[CT_C:.*]] = quantum.custom "T"() %[[CSD_T]] ctrls(%[[CSD_C]]) ctrlvals(%[[TRUE8]]) : !quantum.bit ctrls !quantum.bit + + // Controlled T† (as ControlledPhaseShift) + // CHECK: %[[NEG_PI4_EX4:.*]] = tensor.extract %[[NEG_PI4]][] : tensor + // CHECK: %[[TRUE9:.*]] = arith.constant true + // CHECK: %[[FALSE9:.*]] = arith.constant false + // CHECK: %[[CTD_T:.*]], %[[CTD_C:.*]] = quantum.custom "ControlledPhaseShift"(%[[NEG_PI4_EX4]]) %[[CT_T]] ctrls(%[[CT_C]]) ctrlvals(%[[TRUE9]]) : !quantum.bit ctrls !quantum.bit + + // Reinsertion of target and control qubits + // CHECK: %[[INS1_I64:.*]] = arith.index_cast %{{.*}} : i64 to index + // CHECK: %[[INS1_IDX:.*]] = arith.index_cast %[[INS1_I64]] : index to i64 + // CHECK: %[[INS1:.*]] = quantum.insert %[[ALLOC]][%[[INS1_IDX]]], %[[CTD_T]] : !quantum.reg, !quantum.bit + // CHECK: %[[INS2_I64:.*]] = arith.index_cast %{{.*}} : i64 to index + // CHECK: %[[INS2_IDX:.*]] = arith.index_cast %[[INS2_I64]] : index to i64 + // CHECK: %[[INS2:.*]] = quantum.insert %[[ALLOC]][%[[INS2_IDX]]], %[[CTD_C]] : !quantum.reg, !quantum.bit + // CHECK: quantum.dealloc %[[ALLOC]] : !quantum.reg + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Clifford: MQTOpt to CatalystQuantum") + + +def test_pauli_gates_roundtrip() -> None: + """Test roundtrip conversion of Pauli gates. + + Mirrors: quantum_pauli.mlir + Gates: X, Y, Z, I, and their controlled variants (CNOT, CY, CZ, Toffoli) + Structure: + 1. Uncontrolled Pauli gates (X, Y, Z, I) + 2. Controlled Pauli gates (using qml.ctrl on Pauli gates) + 3. Two-qubit controlled gates (CNOT, CY, CZ) + 4. Toffoli (CCX) + 5. Controlled two-qubit gates (controlled CNOT, CY, CZ, Toffoli) + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=4)) + def circuit() -> None: + # Uncontrolled Pauli gates + qml.PauliX(wires=0) + qml.PauliY(wires=0) + qml.PauliZ(wires=0) + qml.Identity(wires=0) + + # Controlled Pauli gates (single control) - use qml.ctrl on Pauli gates + qml.ctrl(qml.PauliX(wires=0), control=1) + qml.ctrl(qml.PauliY(wires=0), control=1) + qml.ctrl(qml.PauliZ(wires=0), control=1) + # Why is `qml.ctrl(qml.Identity(wires=0), control=1)` not supported by Catalyst? + + # Two-qubit controlled gates (explicit CNOT, CY, CZ gate names) + qml.CNOT(wires=[1, 0]) # First qubit is control, second is target + qml.CY(wires=[1, 0]) + qml.CZ(wires=[1, 0]) + + # Toffoli (also CCX) + qml.Toffoli(wires=[0, 1, 2]) + + # Controlled multi-qubit gates (adding extra controls) + qml.ctrl(qml.CNOT(wires=[0, 1]), control=2) + qml.ctrl(qml.CY(wires=[0, 1]), control=2) + qml.ctrl(qml.CZ(wires=[0, 1]), control=2) + qml.ctrl(qml.Toffoli(wires=[0, 1, 2]), control=3) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + mlir_dir = Path.cwd() + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion + check_to_mqtopt = """ + // CHECK: func.func public @circuit() + // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<4x!mqtopt.Qubit> + // CHECK: %[[Q0_IDX:.*]] = arith.index_cast + // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[Q0_IDX]]] : memref<4x!mqtopt.Qubit> + + // Uncontrolled Pauli gates on Q0 + // CHECK: %[[X:.*]] = mqtopt.x(static [] mask []) %[[Q0]] : !mqtopt.Qubit + // CHECK: %[[Y:.*]] = mqtopt.y(static [] mask []) %[[X]] : !mqtopt.Qubit + // CHECK: %[[Z:.*]] = mqtopt.z(static [] mask []) %[[Y]] : !mqtopt.Qubit + // CHECK: %[[I:.*]] = mqtopt.i(static [] mask []) %[[Z]] : !mqtopt.Qubit + + // Load Q1 for controlled Pauli gates + // CHECK: %[[Q1_IDX:.*]] = arith.index_cast + // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[Q1_IDX]]] : memref<4x!mqtopt.Qubit> + + // Controlled Pauli gates (qml.ctrl on Pauli gates) + // CHECK: %[[CX1_T:.*]], %[[CX1_C:.*]] = mqtopt.x(static [] mask []) %[[I]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CY1_T:.*]], %[[CY1_C:.*]] = mqtopt.y(static [] mask []) %[[CX1_T]] ctrl %[[CX1_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CZ1_T:.*]], %[[CZ1_C:.*]] = mqtopt.z(static [] mask []) %[[CY1_T]] ctrl %[[CY1_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Two-qubit controlled gates (CNOT, CY, CZ) + // CHECK: %[[CNOT_T:.*]], %[[CNOT_C:.*]] = mqtopt.x(static [] mask []) %[[CZ1_C]] ctrl %[[CZ1_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CY_T:.*]], %[[CY_C:.*]] = mqtopt.y(static [] mask []) %[[CNOT_T]] ctrl %[[CNOT_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CZ_T:.*]], %[[CZ_C:.*]] = mqtopt.z(static [] mask []) %[[CY_T]] ctrl %[[CY_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Load Q2 for Toffoli + // CHECK: %[[Q2_IDX:.*]] = arith.index_cast + // CHECK: %[[Q2:.*]] = memref.load %[[ALLOC]][%[[Q2_IDX]]] : memref<4x!mqtopt.Qubit> + + // Toffoli (X with 2 controls: Q0, Q1 -> Q2) + // CHECK: %[[TOF_T:.*]], %[[TOF_C:.*]]:2 = mqtopt.x(static [] mask []) %[[Q2]] ctrl %[[CZ_C]], %[[CZ_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // Controlled CNOT (adds Q2 as control to CNOT on Q0, Q1) + // CHECK: %[[CCNOT_T:.*]], %[[CCNOT_C:.*]]:2 = mqtopt.x(static [] mask []) %[[TOF_C]]#1 ctrl %[[TOF_T]], %[[TOF_C]]#0 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // Controlled CY + // CHECK: %[[CCY_T:.*]], %[[CCY_C:.*]]:2 = mqtopt.y(static [] mask []) %[[CCNOT_T]] ctrl %[[CCNOT_C]]#0, %[[CCNOT_C]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // Controlled CZ + // CHECK: %[[CCZ_T:.*]], %[[CCZ_C:.*]]:2 = mqtopt.z(static [] mask []) %[[CCY_T]] ctrl %[[CCY_C]]#0, %[[CCY_C]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // Load Q3 for controlled Toffoli + // CHECK: %[[Q3_IDX:.*]] = arith.index_cast + // CHECK: %[[Q3:.*]] = memref.load %[[ALLOC]][%[[Q3_IDX]]] : memref<4x!mqtopt.Qubit> + + // Controlled Toffoli (X with 3 controls: Q3, Q1, Q0 -> Q2) + // CHECK: %[[CTOF_T:.*]], %[[CTOF_C:.*]]:3 = mqtopt.x(static [] mask []) %[[CCZ_C]]#0 ctrl %[[Q3]], %[[CCZ_C]]#1, %[[CCZ_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + + // Store qubits back + // CHECK: memref.store %[[CTOF_C]]#1, %[[ALLOC]] + // CHECK: memref.store %[[CTOF_C]]#2, %[[ALLOC]] + // CHECK: memref.store %[[CTOF_T]], %[[ALLOC]] + // CHECK: memref.store %[[CTOF_C]]#0, %[[ALLOC]] + // CHECK: memref.dealloc %[[ALLOC]] : memref<4x!mqtopt.Qubit> + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Pauli: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion + check_to_catalyst = """ + // CHECK: func.func public @circuit() + // CHECK: %[[QREG:.*]] = quantum.alloc( 4) : !quantum.reg + + // Extract Q0 + // CHECK: %[[Q0_IDX1:.*]] = arith.index_cast + // CHECK: %[[Q0_IDX2:.*]] = arith.index_cast %[[Q0_IDX1]] : index to i64 + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[Q0_IDX2]]] : !quantum.reg -> !quantum.bit + + // Uncontrolled Pauli gates on Q0 + // CHECK: %[[X:.*]] = quantum.custom "PauliX"() %[[Q0]] : !quantum.bit + // CHECK: %[[Y:.*]] = quantum.custom "PauliY"() %[[X]] : !quantum.bit + // CHECK: %[[Z:.*]] = quantum.custom "PauliZ"() %[[Y]] : !quantum.bit + // CHECK: %[[I:.*]] = quantum.custom "Identity"() %[[Z]] : !quantum.bit + + // Extract Q1 + // CHECK: %[[Q1_IDX1:.*]] = arith.index_cast + // CHECK: %[[Q1_IDX2:.*]] = arith.index_cast %[[Q1_IDX1]] : index to i64 + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[Q1_IDX2]]] : !quantum.reg -> !quantum.bit + + // Controlled Pauli gates (qml.ctrl on X, Y, Z) - results swap for each gate + // CHECK: %[[TRUE1:.*]] = arith.constant true + // CHECK: %[[FALSE1:.*]] = arith.constant false + // CHECK: %[[CX1_T:.*]], %[[CX1_C:.*]] = quantum.custom "CNOT"() %[[I]] ctrls(%[[Q1]]) ctrlvals(%[[TRUE1]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[TRUE2:.*]] = arith.constant true + // CHECK: %[[FALSE2:.*]] = arith.constant false + // CHECK: %[[CY1_T:.*]], %[[CY1_C:.*]] = quantum.custom "CY"() %[[CX1_T]] ctrls(%[[CX1_C]]) ctrlvals(%[[TRUE2]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[TRUE3:.*]] = arith.constant true + // CHECK: %[[FALSE3:.*]] = arith.constant false + // CHECK: %[[CZ1_T:.*]], %[[CZ1_C:.*]] = quantum.custom "CZ"() %[[CY1_T]] ctrls(%[[CY1_C]]) ctrlvals(%[[TRUE3]]) : !quantum.bit ctrls !quantum.bit + + // Two-qubit controlled gates (CNOT, CY, CZ) - results swap + // CHECK: %[[TRUE4:.*]] = arith.constant true + // CHECK: %[[FALSE4:.*]] = arith.constant false + // CHECK: %[[CNOT_T:.*]], %[[CNOT_C:.*]] = quantum.custom "CNOT"() %[[CZ1_C]] ctrls(%[[CZ1_T]]) ctrlvals(%[[TRUE4]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[TRUE5:.*]] = arith.constant true + // CHECK: %[[FALSE5:.*]] = arith.constant false + // CHECK: %[[CY_T:.*]], %[[CY_C:.*]] = quantum.custom "CY"() %[[CNOT_T]] ctrls(%[[CNOT_C]]) ctrlvals(%[[TRUE5]]) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[TRUE6:.*]] = arith.constant true + // CHECK: %[[FALSE6:.*]] = arith.constant false + // CHECK: %[[CZ_T:.*]], %[[CZ_C:.*]] = quantum.custom "CZ"() %[[CY_T]] ctrls(%[[CY_C]]) ctrlvals(%[[TRUE6]]) : !quantum.bit ctrls !quantum.bit + + // Extract Q2 for Toffoli + // CHECK: %[[Q2_IDX1:.*]] = arith.index_cast + // CHECK: %[[Q2_IDX2:.*]] = arith.index_cast %[[Q2_IDX1]] : index to i64 + // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[Q2_IDX2]]] : !quantum.reg -> !quantum.bit + + // Toffoli (2 controls + target) + // CHECK: %[[TRUE7:.*]] = arith.constant true + // CHECK: %[[FALSE7:.*]] = arith.constant false + // CHECK: %[[TOF_T:.*]], %[[TOF_C:.*]]:2 = quantum.custom "Toffoli"() %[[Q2]] ctrls(%[[CZ_C]], %[[CZ_T]]) ctrlvals(%[[TRUE7]], %[[TRUE7]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + + // Controlled CNOT (becomes Toffoli with 3 qubits) + // CHECK: %[[TRUE8:.*]] = arith.constant true + // CHECK: %[[FALSE8:.*]] = arith.constant false + // CHECK: %[[CCNOT_T:.*]], %[[CCNOT_C:.*]]:2 = quantum.custom "Toffoli"() %[[TOF_C]]#1 ctrls(%[[TOF_T]], %[[TOF_C]]#0) ctrlvals(%[[TRUE8]], %[[TRUE8]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + + // Controlled CY (PauliY with 2 controls) + // CHECK: %[[TRUE9:.*]] = arith.constant true + // CHECK: %[[FALSE9:.*]] = arith.constant false + // CHECK: %[[CCY_T:.*]], %[[CCY_C:.*]]:2 = quantum.custom "PauliY"() %[[CCNOT_T]] ctrls(%[[CCNOT_C]]#0, %[[CCNOT_C]]#1) ctrlvals(%[[TRUE9]], %[[TRUE9]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + + // Controlled CZ (PauliZ with 2 controls) + // CHECK: %[[TRUE10:.*]] = arith.constant true + // CHECK: %[[FALSE10:.*]] = arith.constant false + // CHECK: %[[CCZ_T:.*]], %[[CCZ_C:.*]]:2 = quantum.custom "PauliZ"() %[[CCY_T]] ctrls(%[[CCY_C]]#0, %[[CCY_C]]#1) ctrlvals(%[[TRUE10]], %[[TRUE10]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + + // Extract Q3 for controlled Toffoli + // CHECK: %[[Q3_IDX1:.*]] = arith.index_cast + // CHECK: %[[Q3_IDX2:.*]] = arith.index_cast %[[Q3_IDX1]] : index to i64 + // CHECK: %[[Q3:.*]] = quantum.extract %[[QREG]][%[[Q3_IDX2]]] : !quantum.reg -> !quantum.bit + + // Controlled Toffoli (PauliX with 3 controls) + // CHECK: %[[TRUE11:.*]] = arith.constant true + // CHECK: %[[FALSE11:.*]] = arith.constant false + // CHECK: %[[CTOF_T:.*]], %[[CTOF_C:.*]]:3 = quantum.custom "PauliX"() %[[CCZ_C]]#0 ctrls(%[[Q3]], %[[CCZ_C]]#1, %[[CCZ_T]]) ctrlvals(%[[TRUE11]], %[[TRUE11]], %[[TRUE11]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + + // Insert qubits back to register + // CHECK: %[[INS1_IDX1:.*]] = arith.index_cast + // CHECK: %[[INS1_IDX2:.*]] = arith.index_cast %[[INS1_IDX1]] : index to i64 + // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS1_IDX2]]], %[[CTOF_C]]#1 : !quantum.reg, !quantum.bit + // CHECK: %[[INS2_IDX1:.*]] = arith.index_cast + // CHECK: %[[INS2_IDX2:.*]] = arith.index_cast %[[INS2_IDX1]] : index to i64 + // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS2_IDX2]]], %[[CTOF_C]]#2 : !quantum.reg, !quantum.bit + // CHECK: %[[INS3_IDX1:.*]] = arith.index_cast + // CHECK: %[[INS3_IDX2:.*]] = arith.index_cast %[[INS3_IDX1]] : index to i64 + // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS3_IDX2]]], %[[CTOF_T]] : !quantum.reg, !quantum.bit + // CHECK: %[[INS4_IDX1:.*]] = arith.index_cast + // CHECK: %[[INS4_IDX2:.*]] = arith.index_cast %[[INS4_IDX1]] : index to i64 + // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS4_IDX2]]], %[[CTOF_C]]#0 : !quantum.reg, !quantum.bit + // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Pauli: MQTOpt to CatalystQuantum") + + +def test_parameterized_gates_roundtrip() -> None: + """Test roundtrip conversion of parameterized rotation gates. + + Mirrors: quantum_param.mlir + Gates: RX, RY, RZ, PhaseShift, and their controlled variants (CRX, CRY) + Note: MLIR test does NOT include CRZ, only CRX and CRY + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + angle = 0.3 + + # Non-controlled parameterized gates + qml.RX(angle, wires=0) + qml.RY(angle, wires=0) + qml.RZ(angle, wires=0) + qml.PhaseShift(angle, wires=0) + + # Controlled parameterized gates + qml.CRX(angle, wires=[1, 0]) + qml.CRY(angle, wires=[1, 0]) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + mlir_dir = Path.cwd() + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion + check_to_mqtopt = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: %[[ALLOC:.*]] = memref.alloc(){{.*}}!mqtopt.Qubit + // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][{{.*}}]{{.*}}!mqtopt.Qubit + + // Uncontrolled parameterized gates + // CHECK: %[[RX:.*]] = mqtopt.rx({{.*}}) %[[Q0]] : !mqtopt.Qubit + // CHECK: %[[RY:.*]] = mqtopt.ry({{.*}}) %[[RX]] : !mqtopt.Qubit + // CHECK: %[[RZ:.*]] = mqtopt.rz({{.*}}) %[[RY]] : !mqtopt.Qubit + // CHECK: %[[PS:.*]] = mqtopt.p({{.*}}) %[[RZ]] : !mqtopt.Qubit + + // Load Q1 for controlled parameterized gates + // CHECK: memref.load{{.*}}!mqtopt.Qubit + + // Controlled parameterized gates + // CHECK: mqtopt.rx({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: mqtopt.ry({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Reinsertion + // CHECK: memref.store + // CHECK: memref.store + // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Param: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion + check_to_catalyst = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg + + // Q0 is extracted first for uncontrolled gates + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // Uncontrolled parameterized gates + // CHECK: %[[RX:.*]] = quantum.custom "RX"({{.*}}) %[[Q0]] : !quantum.bit + // CHECK: %[[RY:.*]] = quantum.custom "RY"({{.*}}) %[[RX]] : !quantum.bit + // CHECK: %[[RZ:.*]] = quantum.custom "RZ"({{.*}}) %[[RY]] : !quantum.bit + // CHECK: %[[PS:.*]] = quantum.custom "PhaseShift"({{.*}}) %[[RZ]] : !quantum.bit + + // Q1 is extracted lazily right before controlled gates + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // Controlled parameterized gates (qubits swap after each operation) + // CRX: target=%[[PS]], control=%[[Q1]] + // CHECK: %[[CRX_T:.*]], %[[CRX_C:.*]] = quantum.custom "CRX"({{.*}}) %[[PS]] ctrls(%[[Q1]]) + // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit + // CRY: target=%[[CRX_C]] (previous control), control=%[[CRX_T]] (previous target) + // CHECK: quantum.custom "CRY"({{.*}}) %[[CRX_C]] ctrls(%[[CRX_T]]) ctrlvals(%true{{.*}}) + // CHECK-SAME: : !quantum.bit ctrls !quantum.bit + + // Reinsertion + // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit + // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit + // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Param: MQTOpt to CatalystQuantum") + + +def test_entangling_gates_roundtrip() -> None: + """Test roundtrip conversion of entangling/permutation gates. + + Mirrors: quantum_entangling.mlir + Gates: SWAP, ISWAP, ISWAP†, ECR, and their controlled variants + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=3)) + def circuit() -> None: + # Uncontrolled permutation gates + qml.SWAP(wires=[0, 1]) + qml.ISWAP(wires=[0, 1]) + qml.adjoint(qml.ISWAP(wires=[0, 1])) + qml.ECR(wires=[0, 1]) + + # Controlled permutation gates + qml.CSWAP(wires=[2, 0, 1]) + qml.ctrl(qml.ISWAP(wires=[0, 1]), control=2) + qml.ctrl(qml.adjoint(qml.ISWAP(wires=[0, 1])), control=2) + qml.ctrl(qml.ECR(wires=[0, 1]), control=2) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + mlir_dir = Path.cwd() + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion + check_to_mqtopt = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: memref.alloc(){{.*}}!mqtopt.Qubit + + // Qubits loaded as needed + // CHECK: memref.load{{.*}}!mqtopt.Qubit + // CHECK: memref.load{{.*}}!mqtopt.Qubit + + // SWAP gate (ISWAP/ECR get decomposed) + // CHECK: mqtopt.swap({{.*}}){{.*}}: !mqtopt.Qubit, !mqtopt.Qubit + + // Control qubit loaded + // CHECK: memref.load{{.*}}!mqtopt.Qubit + + // Controlled swap gate + // CHECK: mqtopt.swap({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + + // Reinsertion + // CHECK: memref.store + // CHECK: memref.store + // CHECK: memref.store + // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Entangling: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion (simplified - ISWAP/ECR are heavily decomposed) + check_to_catalyst = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg + + // Qubits extracted as needed + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // SWAP is visible, but ISWAP/ECR/adjISWAP are heavily decomposed into primitives (H, S, CNOT, RZ, RY chains) + // CHECK: quantum.custom "SWAP"() %[[Q0]], %[[Q1]] : !quantum.bit, !quantum.bit + + // After all decompositions, Q2 extracted for controlled gates + // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // Controlled swap + // CHECK: quantum.custom "CSWAP"() {{.*}} ctrls(%[[Q2]]) ctrlvals( + + // Reinsertion + // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit + // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit + // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit + // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Entangling: MQTOpt to CatalystQuantum") + + +def test_ising_gates_roundtrip() -> None: + """Test roundtrip conversion of Ising-type gates. + + Mirrors: quantum_ising.mlir + Gates: IsingXY, IsingXX, IsingYY, IsingZZ, and their controlled variants + Note: IsingXY takes 2 parameters in MLIR (phi and beta) + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=3)) + def circuit() -> None: + angle = 0.3 + + # Uncontrolled Ising gates + qml.IsingXY(angle, wires=[0, 1]) + qml.IsingXX(angle, wires=[0, 1]) + qml.IsingYY(angle, wires=[0, 1]) + qml.IsingZZ(angle, wires=[0, 1]) + + # Controlled Ising gates + qml.ctrl(qml.IsingXY(angle, wires=[0, 1]), control=2) + qml.ctrl(qml.IsingXX(angle, wires=[0, 1]), control=2) + qml.ctrl(qml.IsingYY(angle, wires=[0, 1]), control=2) + qml.ctrl(qml.IsingZZ(angle, wires=[0, 1]), control=2) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + # This works regardless of where pytest is run from (locally or CI) + test_file_dir = Path(__file__).parent + mlir_dir = Path.cwd() + test_file_dir.parent / "Conversion" + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + # Fallback: list what files actually exist for debugging + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion with FileCheck + check_to_mqtopt = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: %[[ALLOC:.*]] = memref.alloc(){{.*}}!mqtopt.Qubit + // CHECK: %[[Q0:.*]] = memref.load{{.*}}!mqtopt.Qubit + // CHECK: %[[Q1:.*]] = memref.load{{.*}}!mqtopt.Qubit + + // Uncontrolled Ising gates + // CHECK: %[[XY_OUT:.*]]:2 = mqtopt.xx_plus_yy({{.*}}) %[[Q0]], %[[Q1]] : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[XX_OUT:.*]]:2 = mqtopt.rxx({{.*}}) %[[XY_OUT]]#0, %[[XY_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[YY_OUT:.*]]:2 = mqtopt.ryy({{.*}}) %[[XX_OUT]]#0, %[[XX_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[ZZ_OUT:.*]]:2 = mqtopt.rzz({{.*}}) %[[YY_OUT]]#0, %[[YY_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit + + // Controlled Ising gates (control qubit loaded here) + // CHECK: memref.load{{.*}}!mqtopt.Qubit + // CHECK: mqtopt.xx_plus_yy({{.*}}){{.*}}ctrl{{.*}} + // CHECK: mqtopt.rxx({{.*}}){{.*}}ctrl{{.*}} + // CHECK: mqtopt.ryy({{.*}}){{.*}}ctrl{{.*}} + // CHECK: mqtopt.rzz({{.*}}){{.*}}ctrl{{.*}} + + // Reinsertion + // CHECK: memref.store + // CHECK: memref.store + // CHECK: memref.store + // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Ising: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion with FileCheck + # Based on mqtopt_ising.mlir reference test + check_to_catalyst = """ + // CHECK: func.func {{.*}}@circuit + // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // Uncontrolled Ising gates + // IsingXY is decomposed: RZ -> IsingXY -> RZ + // CHECK: %[[RZ0:.*]] = quantum.custom "RZ"({{.*}}) %[[Q1]] : !quantum.bit + // CHECK: %[[XY:.*]]:2 = quantum.custom "IsingXY"({{.*}}) %[[Q0]], %[[RZ0]] : !quantum.bit, !quantum.bit + // CHECK: %[[RZ1:.*]] = quantum.custom "RZ"({{.*}}) %[[XY]]#1 : !quantum.bit + + // IsingXX, IsingYY, IsingZZ gates + // CHECK: %[[XX:.*]]:2 = quantum.custom "IsingXX"({{.*}}) %[[XY]]#0, %[[RZ1]] : !quantum.bit, !quantum.bit + // CHECK: %[[YY:.*]]:2 = quantum.custom "IsingYY"({{.*}}) %[[XX]]#0, %[[XX]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[ZZ:.*]]:2 = quantum.custom "IsingZZ"({{.*}}) %[[YY]]#0, %[[YY]]#1 : !quantum.bit, !quantum.bit + + // Controlled Ising gates (with ctrls) + // Extract control qubit + // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + + // Controlled IsingXY: RZ(ctrl) -> IsingXY(ctrl) -> RZ(ctrl) + // CHECK: %[[CRZ0:.*]], %[[CTRL1:.*]] = quantum.custom "RZ"({{.*}}) %[[ZZ]]#1 ctrls(%[[Q2]]) + // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit + // CHECK: %[[CXY:.*]]:2, %[[CTRL2:.*]] = quantum.custom "IsingXY"({{.*}}) %[[ZZ]]#0, %[[CRZ0]] + // CHECK-SAME: ctrls(%[[CTRL1]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit + // CHECK: %[[CRZ1:.*]], %[[CTRL3:.*]] = quantum.custom "RZ"({{.*}}) %[[CXY]]#1 ctrls(%[[CTRL2]]) + // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit + + // Controlled IsingXX, IsingYY, IsingZZ + // CHECK: %[[CXX:.*]]:2, %[[CTRL4:.*]] = quantum.custom "IsingXX"({{.*}}) %[[CXY]]#0, %[[CRZ1]] + // CHECK-SAME: ctrls(%[[CTRL3]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit + // CHECK: %[[CYY:.*]]:2, %[[CTRL5:.*]] = quantum.custom "IsingYY"({{.*}}) %[[CXX]]#0, %[[CXX]]#1 + // CHECK-SAME: ctrls(%[[CTRL4]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit + // CHECK: %[[CZZ:.*]]:2, %[[CTRL6:.*]] = quantum.custom "IsingZZ"({{.*}}) %[[CYY]]#0, %[[CYY]]#1 + // CHECK-SAME: ctrls(%[[CTRL5]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit + + // Reinsertion + // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CZZ]]#0 : !quantum.reg, !quantum.bit + // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CZZ]]#1 : !quantum.reg, !quantum.bit + // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CTRL6]] : !quantum.reg, !quantum.bit + // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Ising: MQTOpt to CatalystQuantum") + + # Remove all intermediate files created during the test + for mlir_file in mlir_dir.glob("*.mlir"): + mlir_file.unlink() + + +def test_mqtopt_roundtrip() -> None: + """Execute the full roundtrip including MQT Core IR. + + Executes the conversion passes to and from MQTOpt dialect AND + the roundtrip through MQT Core IR. + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + qml.Hadamard(wires=[0]) + qml.CNOT(wires=[0, 1]) + + @qml.qjit(target="mlir", autograph=True) + def module() -> None: + return circuit() + + # This will execute the pass and return the final MLIR + mlir_opt = module.mlir_opt + assert mlir_opt + + +def test_debug_roundtrip() -> None: + """Test roundtrip conversion. + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + qml.Hadamard(wires=[0]) + qml.CNOT(wires=[0, 1]) # first wire is control + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + # Verify the roundtrip completes successfully + mlir_opt = module.mlir_opt + assert mlir_opt + + # Find where MLIR files are generated (relative to cwd where pytest is run) + # Catalyst generates MLIR files in the current working directory + # This works regardless of where pytest is run from (locally or CI) + test_file_dir = Path(__file__).parent + mlir_dir = Path.cwd() + test_file_dir.parent / "Conversion" + + # Read the intermediate MLIR files + mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + # Fallback: list what files actually exist for debugging + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + # Verify CatalystQuantum → MQTOpt conversion with FileCheck + check_to_mqtopt = """ + """ + _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Ising: CatalystQuantum to MQTOpt") + + # Verify MQTOpt → CatalystQuantum conversion with FileCheck + # Based on mqtopt_ising.mlir reference test + check_to_catalyst = """ + """ + _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Ising: MQTOpt to CatalystQuantum") + + # Remove all intermediate files created during the test + for mlir_file in mlir_dir.glob("*.mlir"): + mlir_file.unlink() \ No newline at end of file From b10e41ab967127f1a6f2d7d3341a1442321556ed Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 10:22:00 +0100 Subject: [PATCH 03/65] =?UTF-8?q?=E2=9C=A8=20add=20entry=20point=20for=20c?= =?UTF-8?q?atalyst=20passes=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c53100e..da5256f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,9 @@ Issues = "https://github.com/munich-quantum-toolkit/core-plugins-catalyst/issues Discussions = "https://github.com/munich-quantum-toolkit/core-plugins-catalyst/discussions" PyPI = "https://pypi.org/project/mqt-core-plugins-catalyst/" +[project.entry-points."catalyst.passes_resolution"] +"mqt.passes" = "mqt.core.plugins.catalyst" + [tool.scikit-build] # Protect the configuration against future changes in scikit-build-core minimum-version = "build-system.requires" From c6da667f001bb94f567591305118937419d656e7 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 10:22:20 +0100 Subject: [PATCH 04/65] =?UTF-8?q?=E2=9C=A8=20add=20get=5Fcatalyst=5Fplugin?= =?UTF-8?q?=5Fabs=5Fpath=20and=20name2pass=20functions=20to=20locate=20plu?= =?UTF-8?q?gin=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/catalyst/__init__.py | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 python/mqt/core/plugins/catalyst/__init__.py diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py new file mode 100644 index 0000000..3df6d90 --- /dev/null +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -0,0 +1,87 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""MQT Catalyst Plugin.""" + +from __future__ import annotations + +import platform +from importlib import resources +from pathlib import Path + +from mqt.core.plugins.catalyst.device import configure_device_for_mqt, get_device + + +def get_catalyst_plugin_abs_path() -> Path: + """Locate the mqt-catalyst-plugin shared library. + + Returns: + The absolute path to the plugin shared library. + + Raises: + FileNotFoundError: If the plugin library is not found. + """ + ext = {"Darwin": ".dylib", "Linux": ".so", "Windows": ".dll"}.get(platform.system()) + if ext is None: + msg = f"Unsupported platform: {platform.system()}" + raise RuntimeError(msg) + + # Try to find the plugin library in the package installation directory + try: + if hasattr(resources, "files"): + package_path = resources.files("mqt.core.plugins.catalyst") + # Try both with and without lib prefix + for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: + lib_path = package_path / lib_name + if lib_path.is_file(): + return Path(str(lib_path)) + except Exception: # noqa: BLE001, S110 + pass + + # Fallback: search in development build directory (for editable installs) + this_file = Path(__file__).resolve() + # python/mqt/core/plugins/catalyst/__init__.py -> go up 6 levels to project root + project_root = this_file.parent.parent.parent.parent.parent.parent + build_dir = project_root / "build" + + if build_dir.exists(): + # Try both with and without lib prefix + for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: + # Search recursively in build directory + for lib_path in build_dir.rglob(lib_name): + return lib_path + + # Provide helpful error message + msg = ( + f"Could not locate catalyst plugin library with extension '{ext}'.\n" + f"Expected locations:\n" + f" - Installed package: {this_file.parent}\n" + f" - Development build: {build_dir}\n" + f"For editable install, ensure the library is built: cmake --build build" + ) + raise FileNotFoundError(msg) + + +def name2pass(name: str) -> tuple[Path, str]: + """Convert a pass name to its plugin path and pass name (required by Catalyst). + + Args: + name: The name of the pass, e.g., "mqt-core-round-trip". + + Returns: + A tuple containing the absolute path to the plugin and the pass name. + """ + return get_catalyst_plugin_abs_path(), name + + +__all__ = [ + "configure_device_for_mqt", + "get_catalyst_plugin_abs_path", + "get_device", + "name2pass", +] From 5d126aaa1e2a24f76b57fe2549e8bddf172ab8bf Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 10:22:38 +0100 Subject: [PATCH 05/65] =?UTF-8?q?=E2=9C=85=20add=20tests=20for=20MQT=20plu?= =?UTF-8?q?gin=20setup=20with=20PennyLane=20and=20Catalyst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_plugin_setup.py | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/python/test_plugin_setup.py diff --git a/test/python/test_plugin_setup.py b/test/python/test_plugin_setup.py new file mode 100644 index 0000000..8580394 --- /dev/null +++ b/test/python/test_plugin_setup.py @@ -0,0 +1,94 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for MQT plugin setup with PennyLane and Catalyst. + +These tests only check that the MQT plugin is correctly installed and +can be used in various ways with PennyLane (they do NOT execute any pass). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pennylane as qml +from catalyst import pipeline +from catalyst.passes import apply_pass, apply_pass_plugin + +from mqt.core.plugins.catalyst import get_catalyst_plugin_abs_path + +if TYPE_CHECKING: + from pennylane.measurements.state import StateMP + + +def test_mqt_plugin() -> None: + """Generate MLIR for the MQT plugin. + + Does not execute the pass. + """ + plugin_path = str(get_catalyst_plugin_abs_path()) + + @apply_pass("mqt-core-round-trip") + @qml.qnode(qml.device("null.qubit", wires=0)) + def qnode() -> StateMP: + return qml.state() + + @qml.qjit(pass_plugins={plugin_path}, dialect_plugins={plugin_path}, target="mlir") + def module() -> StateMP: + return qnode() + + assert "mqt-core-round-trip" in module.mlir + + +def test_mqt_plugin_no_preregistration() -> None: + """Generate MLIR for the MQT plugin. + + No need to register the plugin ahead of time in the qjit decorator. + """ + plugin_path = str(get_catalyst_plugin_abs_path()) + + @apply_pass_plugin(plugin_path, "mqt-core-round-trip") + @qml.qnode(qml.device("null.qubit", wires=0)) + def qnode() -> StateMP: + return qml.state() + + @qml.qjit(target="mlir") + def module() -> StateMP: + return qnode() + + assert "mqt-core-round-trip" in module.mlir + + +def test_mqt_entry_point() -> None: + """Generate MLIR for the MQT plugin via entry-point.""" + + @apply_pass("mqt.mqt-core-round-trip") + @qml.qnode(qml.device("null.qubit", wires=0)) + def qnode() -> StateMP: + return qml.state() + + @qml.qjit(target="mlir") + def module() -> StateMP: + return qnode() + + assert "mqt-core-round-trip" in module.mlir + + +def test_mqt_dictionary() -> None: + """Generate MLIR for the MQT plugin via entry-point.""" + + @pipeline({"mqt.mqt-core-round-trip": {}}) + @qml.qnode(qml.device("null.qubit", wires=0)) + def qnode() -> StateMP: + return qml.state() + + @qml.qjit(target="mlir") + def module() -> StateMP: + return qnode() + + assert "mqt-core-round-trip" in module.mlir From 13b385604c983ce4fdba90593615cece3971d0df Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 14:11:14 +0100 Subject: [PATCH 06/65] =?UTF-8?q?=E2=9C=85=20isolate=20python=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_plugin.py | 1257 ++++++++++++++++-------------------- 1 file changed, 562 insertions(+), 695 deletions(-) diff --git a/test/python/test_plugin.py b/test/python/test_plugin.py index f66f6c0..7921f92 100644 --- a/test/python/test_plugin.py +++ b/test/python/test_plugin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # @@ -20,34 +20,37 @@ from __future__ import annotations import os +import shutil import subprocess +import tempfile from pathlib import Path from typing import TYPE_CHECKING import pennylane as qml +import pytest from catalyst.passes import apply_pass from mqt.core.plugins.catalyst import get_device if TYPE_CHECKING: - from collections.abc import Callable - -import pytest + from collections.abc import Generator @pytest.fixture(autouse=True) -def _cleanup_mlir_files(): - """Automatically clean up MLIR artifacts before and after each test.""" - # Clean up before test +def _cleanup_mlir_files() -> Generator[None, None, None]: + """Clean up MLIR files before and after each test to ensure test isolation. + + Yields: + None + """ _cleanup_mlir_artifacts() yield - # Clean up after test _cleanup_mlir_artifacts() def _cleanup_mlir_artifacts() -> None: """Clean up MLIR intermediate files and directories created by Catalyst. - + Catalyst creates module_N directories when keep_intermediate is used. This function removes all such directories to prevent accumulation. """ @@ -55,11 +58,10 @@ def _cleanup_mlir_artifacts() -> None: # Remove all module_N directories for module_dir in mlir_dir.glob("module_*"): if module_dir.is_dir(): - import shutil shutil.rmtree(module_dir) # Remove any loose .mlir files - for mlir_file in mlir_dir.glob("*.mlir"): - mlir_file.unlink() + # for mlir_file in mlir_dir.glob("*.mlir"): + # mlir_file.unlink() def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "test") -> None: @@ -99,9 +101,6 @@ def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "tes ) raise RuntimeError(msg) - # Write CHECK patterns to a temporary file - import tempfile - with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", suffix=".mlir", delete=False) as check_file: check_file.write(check_patterns) check_file_path = check_file.name @@ -128,11 +127,8 @@ def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "tes Path(check_file_path).unlink() -def test_clifford_gates_roundtrip() -> None: - """Test roundtrip conversion of Clifford+T gates. - - Mirrors: quantum_clifford.mlir - Gates: H, SX, SX†, S, S†, T, T†, and their controlled variants +def test_paulix_roundtrip() -> None: + """Test roundtrip conversion of the PauliX gate. Raises: FileNotFoundError: If intermediate MLIR files are not found @@ -142,23 +138,12 @@ def test_clifford_gates_roundtrip() -> None: @apply_pass("mqt.catalystquantum-to-mqtopt") @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - # Non-controlled Clifford+T gates - qml.Hadamard(wires=0) - qml.SX(wires=0) - qml.adjoint(qml.SX(wires=0)) - qml.S(wires=0) - qml.adjoint(qml.S(wires=0)) - qml.T(wires=0) - qml.adjoint(qml.T(wires=0)) - - # Controlled Clifford+T gates - qml.CH(wires=[1, 0]) - qml.ctrl(qml.SX(wires=0), control=1) - # Why is `qml.ctrl(qml.adjoint(qml.SX(wires=0)), control=1)` not supported by Catalyst? - qml.ctrl(qml.S(wires=0), control=1) - qml.ctrl(qml.adjoint(qml.S(wires=0)), control=1) - qml.ctrl(qml.T(wires=0), control=1) - qml.ctrl(qml.adjoint(qml.T(wires=0)), control=1) + # Non-controlled + qml.X(wires=0) + qml.PauliX(wires=0) + # Controlled + qml.ctrl(qml.PauliX(wires=0), control=1) + qml.CNOT(wires=[1, 0]) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -178,234 +163,52 @@ def module() -> None: mlir_dir = Path.cwd() # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() + # Verify original CatalystQuantum + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "PauliX"() %1 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliX"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5:2 = quantum.custom "CNOT"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CNOT"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "PauliX: CatalystQuantum") + # Verify CatalystQuantum → MQTOpt conversion - check_to_mqtopt = """ - // Allocate and load first qubit - // CHECK: %[[C0:.*]] = stablehlo.constant dense<0> : tensor - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<2x!mqtopt.Qubit> - - // Load target qubit 0 - // CHECK: %[[Q0_I64:.*]] = tensor.extract %[[C0]][] : tensor - // CHECK: %[[Q0_IDX:.*]] = arith.index_cast %[[Q0_I64]] : i64 to index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[Q0_IDX]]] : memref<2x!mqtopt.Qubit> - - // --- Uncontrolled Clifford+T gates --------------------------------------------------------- - // Hadamard - // CHECK: %[[OUT_QUBITS:.*]] = mqtopt.h(static [] mask []) %[[Q0]] : !mqtopt.Qubit - - // Constants for SX decomposition - // CHECK: %[[PI2:.*]] = stablehlo.constant dense<1.5707963267948966> : tensor - - // SX decomposition from Catalyst (RZ(π/2) → RY(π/2) → RZ(-π/2) - // CHECK: %[[PI2_EX1:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_3:.*]] = mqtopt.rz(%[[PI2_EX1]] static [] mask [false]) %[[OUT_QUBITS]] : !mqtopt.Qubit - - // CHECK: %[[PI2_EX2:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_5:.*]] = mqtopt.ry(%[[PI2_EX2]] static [] mask [false]) %[[OUT_QUBITS_3]] : !mqtopt.Qubit - - // CHECK: %[[NEG_PI2:.*]] = stablehlo.constant dense<-1.5707963267948966> : tensor - // CHECK: %[[NEG_PI2_EX:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_8:.*]] = mqtopt.rz(%[[NEG_PI2_EX]] static [] mask [false]) %[[OUT_QUBITS_5]] : !mqtopt.Qubit - - // Capture -π/4 constant and uncontrolled gphase operations (from SX decomposition) - // CHECK: %[[NEG_PI4:.*]] = stablehlo.constant dense<-0.78539816339744828> : tensor - // CHECK: %[[NEG_PI4_EX1:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: mqtopt.gphase(%[[NEG_PI4_EX1]] static [] mask [false]) - // CHECK: %[[NEG_PI4_EX2:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: mqtopt.gphase(%[[NEG_PI4_EX2]] static [] mask [false]) - - // SX† decomposition (RZ(-π/2) → RY(π/2) → RZ(π/2)) - // CHECK: %[[NEG_PI2_EX2:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_13:.*]] = mqtopt.rz(%[[NEG_PI2_EX2]] static [] mask [false]) %[[OUT_QUBITS_8]] : !mqtopt.Qubit - // CHECK: %[[PI2_EX3:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_15:.*]] = mqtopt.ry(%[[PI2_EX3]] static [] mask [false]) %[[OUT_QUBITS_13]] : !mqtopt.Qubit - // CHECK: %[[PI2_EX4:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_17:.*]] = mqtopt.rz(%[[PI2_EX4]] static [] mask [false]) %[[OUT_QUBITS_15]] : !mqtopt.Qubit - - // S, S†, T, T† - // CHECK: %[[OUT_QUBITS_18:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_17]] : !mqtopt.Qubit - // CHECK: %[[OUT_QUBITS_19:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_18]] : !mqtopt.Qubit - // CHECK: %[[OUT_QUBITS_20:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_19]] : !mqtopt.Qubit - // CHECK: %[[OUT_QUBITS_21:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_20]] : !mqtopt.Qubit - - // --- Controlled Section -------------------------------------------------------------------- - // Load control qubit - // CHECK: %[[C1:.*]] = stablehlo.constant dense<1> : tensor - // CHECK: %[[C1_EX:.*]] = tensor.extract %[[C1]][] : tensor - // CHECK: %[[C1_IDX:.*]] = arith.index_cast %[[C1_EX]] : i64 to index - // CHECK: %[[CTRL:.*]] = memref.load %[[ALLOC]][%[[C1_IDX]]] : memref<2x!mqtopt.Qubit> - - // Controlled H - // CHECK: %[[OUT_QUBITS_26:.*]], %[[POS_CTRL_OUT_QUBITS:.*]] = mqtopt.h(static [] mask []) %[[OUT_QUBITS_21]] ctrl %[[CTRL]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled SX decomposition - // CHECK: %[[PI2_EX5:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_28:.*]], %[[POS_CTRL_OUT_QUBITS_29:.*]] = mqtopt.rz(%[[PI2_EX5]] static [] mask [false]) %[[OUT_QUBITS_26]] ctrl %[[POS_CTRL_OUT_QUBITS]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // CHECK: %[[PI2_EX6:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_31:.*]], %[[POS_CTRL_OUT_QUBITS_32:.*]] = mqtopt.ry(%[[PI2_EX6]] static [] mask [false]) %[[OUT_QUBITS_28]] ctrl %[[POS_CTRL_OUT_QUBITS_29]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // CHECK: %[[NEG_PI2_EX3:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_34:.*]], %[[POS_CTRL_OUT_QUBITS_35:.*]] = mqtopt.rz(%[[NEG_PI2_EX3]] static [] mask [false]) %[[OUT_QUBITS_31]] ctrl %[[POS_CTRL_OUT_QUBITS_32]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled gphase - // CHECK: %[[NEG_PI4_EX3:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: %[[POS_CTRL_OUT_QUBITS_38:.*]] = mqtopt.gphase(%[[NEG_PI4_EX3]] static [] mask [false]) ctrl %[[POS_CTRL_OUT_QUBITS_35]] : ctrl !mqtopt.Qubit - - // Controlled S - // CHECK: %[[OUT_QUBITS_40:.*]], %[[POS_CTRL_OUT_QUBITS_41:.*]] = mqtopt.s(static [] mask []) %[[OUT_QUBITS_34]] ctrl %[[POS_CTRL_OUT_QUBITS_35]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled S† (phase gate with -π/2) - // CHECK: %[[NEG_PI2_EX4:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[OUT_QUBITS_43:.*]], %[[POS_CTRL_OUT_QUBITS_44:.*]] = mqtopt.p(%[[NEG_PI2_EX4]] static [] mask [false]) %[[OUT_QUBITS_40]] ctrl %[[POS_CTRL_OUT_QUBITS_41]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled T - // CHECK: %[[OUT_QUBITS_46:.*]], %[[POS_CTRL_OUT_QUBITS_47:.*]] = mqtopt.t(static [] mask []) %[[OUT_QUBITS_43]] ctrl %[[POS_CTRL_OUT_QUBITS_44]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled T† (phase gate with -π/4) - // CHECK: %[[NEG_PI4_EX4:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: %[[OUT_QUBITS_49:.*]], %[[POS_CTRL_OUT_QUBITS_50:.*]] = mqtopt.p(%[[NEG_PI4_EX4]] static [] mask [false]) %[[OUT_QUBITS_46]] ctrl %[[POS_CTRL_OUT_QUBITS_47]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Reinsertion - // CHECK: %[[Q0_I64_2:.*]] = tensor.extract %[[C0]][] : tensor - // CHECK: %[[Q0_IDX_2:.*]] = arith.index_cast %[[Q0_I64_2]] : i64 to index - // CHECK: memref.store %[[OUT_QUBITS_49]], %[[ALLOC]][%[[Q0_IDX_2]]] : memref<2x!mqtopt.Qubit> - // CHECK: %[[C1_EX2:.*]] = tensor.extract %[[C1]][] : tensor - // CHECK: %[[C1_IDX_2:.*]] = arith.index_cast %[[C1_EX2]] : i64 to index - // CHECK: memref.store %[[POS_CTRL_OUT_QUBITS_50]], %[[ALLOC]][%[[C1_IDX_2]]] : memref<2x!mqtopt.Qubit> - - // CHECK: memref.dealloc %[[ALLOC]] : memref<2x!mqtopt.Qubit> + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.x(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_2 = mqtopt.x(static [] mask []) %out_qubits : !mqtopt.Qubit + //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.x(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.x(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Clifford: CatalystQuantum to MQTOpt") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliX: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion - check_to_catalyst = """ - // Allocate and extract first qubit - // CHECK: %[[ALLOC:.*]] = quantum.alloc( 2) : !quantum.reg - // CHECK: %[[Q0_I64:.*]] = arith.index_cast %{{.*}} : i64 to index - // CHECK: %[[Q0_IDX:.*]] = arith.index_cast %[[Q0_I64]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[ALLOC]][%[[Q0_IDX]]] : !quantum.reg -> !quantum.bit - - // Uncontrolled Clifford+T gates - // Hadamard - // CHECK: %[[H0:.*]] = quantum.custom "Hadamard"() %[[Q0]] : !quantum.bit - - // SX decomposed to RZ -> RY -> RZ with 2 gphase - // CHECK: %[[PI2:.*]] = stablehlo.constant dense<1.5707963267948966> : tensor - // CHECK: %[[PI2_EX1:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[SX_RZ1:.*]] = quantum.custom "RZ"(%[[PI2_EX1]]) %[[H0]] : !quantum.bit - // CHECK: %[[PI2_EX2:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[SX_RY:.*]] = quantum.custom "RY"(%[[PI2_EX2]]) %[[SX_RZ1]] : !quantum.bit - // CHECK: %[[NEG_PI2:.*]] = stablehlo.constant dense<-1.5707963267948966> : tensor - // CHECK: %[[NEG_PI2_EX1:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[SX_RZ2:.*]] = quantum.custom "RZ"(%[[NEG_PI2_EX1]]) %[[SX_RY]] : !quantum.bit - // CHECK: %[[NEG_PI4:.*]] = stablehlo.constant dense<-0.78539816339744828> : tensor - // CHECK: %[[NEG_PI4_EX1:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: quantum.gphase(%[[NEG_PI4_EX1]]) : - // CHECK: %[[NEG_PI4_EX2:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: quantum.gphase(%[[NEG_PI4_EX2]]) : - - // SX† decomposed to RZ -> RY -> RZ - // CHECK: %[[NEG_PI2_EX2:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[SXD_RZ1:.*]] = quantum.custom "RZ"(%[[NEG_PI2_EX2]]) %[[SX_RZ2]] : !quantum.bit - // CHECK: %[[PI2_EX3:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[SXD_RY:.*]] = quantum.custom "RY"(%[[PI2_EX3]]) %[[SXD_RZ1]] : !quantum.bit - // CHECK: %[[PI2_EX4:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[SXD_RZ2:.*]] = quantum.custom "RZ"(%[[PI2_EX4]]) %[[SXD_RY]] : !quantum.bit - - // S, S†, T, T† - // CHECK: %[[S:.*]] = quantum.custom "S"() %[[SXD_RZ2]] : !quantum.bit - // CHECK: %[[SD:.*]] = quantum.custom "S"() %[[S]] : !quantum.bit - // CHECK: %[[T:.*]] = quantum.custom "T"() %[[SD]] : !quantum.bit - // CHECK: %[[TD:.*]] = quantum.custom "T"() %[[T]] : !quantum.bit - - // Extract control qubit - // CHECK: %[[C1_I64:.*]] = arith.index_cast %{{.*}} : i64 to index - // CHECK: %[[C1_IDX:.*]] = arith.index_cast %[[C1_I64]] : index to i64 - // CHECK: %[[CTRL0:.*]] = quantum.extract %[[ALLOC]][%[[C1_IDX]]] : !quantum.reg -> !quantum.bit - - // Controlled Clifford+T gates - // Controlled Hadamard - // CHECK: %[[TRUE1:.*]] = arith.constant true - // CHECK: %[[FALSE1:.*]] = arith.constant false - // CHECK: %[[CH_T:.*]], %[[CH_C:.*]] = quantum.custom "Hadamard"() %[[TD]] ctrls(%[[CTRL0]]) ctrlvals(%[[TRUE1]]) : !quantum.bit ctrls !quantum.bit - - // Controlled SX decomposed to CRZ -> CRY -> CRZ with controlled gphase - // CHECK: %[[PI2_EX5:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[TRUE2:.*]] = arith.constant true - // CHECK: %[[FALSE2:.*]] = arith.constant false - // CHECK: %[[CSX_RZ1_T:.*]], %[[CSX_RZ1_C:.*]] = quantum.custom "CRZ"(%[[PI2_EX5]]) %[[CH_T]] ctrls(%[[CH_C]]) ctrlvals(%[[TRUE2]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[PI2_EX6:.*]] = tensor.extract %[[PI2]][] : tensor - // CHECK: %[[TRUE3:.*]] = arith.constant true - // CHECK: %[[FALSE3:.*]] = arith.constant false - // CHECK: %[[CSX_RY_T:.*]], %[[CSX_RY_C:.*]] = quantum.custom "CRY"(%[[PI2_EX6]]) %[[CSX_RZ1_T]] ctrls(%[[CSX_RZ1_C]]) ctrlvals(%[[TRUE3]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[NEG_PI2_EX3:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[TRUE4:.*]] = arith.constant true - // CHECK: %[[FALSE4:.*]] = arith.constant false - // CHECK: %[[CSX_RZ2_T:.*]], %[[CSX_RZ2_C:.*]] = quantum.custom "CRZ"(%[[NEG_PI2_EX3]]) %[[CSX_RY_T]] ctrls(%[[CSX_RY_C]]) ctrlvals(%[[TRUE4]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[NEG_PI4_EX3:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: %[[TRUE5:.*]] = arith.constant true - // CHECK: %[[FALSE5:.*]] = arith.constant false - // CHECK: %[[GPHASE:.*]] = quantum.gphase(%[[NEG_PI4_EX3]]) ctrls(%[[CSX_RZ2_C]]) ctrlvals(%[[TRUE5]]) : ctrls !quantum.bit - - // Controlled S - // CHECK: %[[TRUE6:.*]] = arith.constant true - // CHECK: %[[FALSE6:.*]] = arith.constant false - // CHECK: %[[CS_T:.*]], %[[CS_C:.*]] = quantum.custom "S"() %[[CSX_RZ2_T]] ctrls(%[[CSX_RZ2_C]]) ctrlvals(%[[TRUE6]]) : !quantum.bit ctrls !quantum.bit - - // Controlled S† (as ControlledPhaseShift) - // CHECK: %[[NEG_PI2_EX4:.*]] = tensor.extract %[[NEG_PI2]][] : tensor - // CHECK: %[[TRUE7:.*]] = arith.constant true - // CHECK: %[[FALSE7:.*]] = arith.constant false - // CHECK: %[[CSD_T:.*]], %[[CSD_C:.*]] = quantum.custom "ControlledPhaseShift"(%[[NEG_PI2_EX4]]) %[[CS_T]] ctrls(%[[CS_C]]) ctrlvals(%[[TRUE7]]) : !quantum.bit ctrls !quantum.bit - - // Controlled T - // CHECK: %[[TRUE8:.*]] = arith.constant true - // CHECK: %[[FALSE8:.*]] = arith.constant false - // CHECK: %[[CT_T:.*]], %[[CT_C:.*]] = quantum.custom "T"() %[[CSD_T]] ctrls(%[[CSD_C]]) ctrlvals(%[[TRUE8]]) : !quantum.bit ctrls !quantum.bit - - // Controlled T† (as ControlledPhaseShift) - // CHECK: %[[NEG_PI4_EX4:.*]] = tensor.extract %[[NEG_PI4]][] : tensor - // CHECK: %[[TRUE9:.*]] = arith.constant true - // CHECK: %[[FALSE9:.*]] = arith.constant false - // CHECK: %[[CTD_T:.*]], %[[CTD_C:.*]] = quantum.custom "ControlledPhaseShift"(%[[NEG_PI4_EX4]]) %[[CT_T]] ctrls(%[[CT_C]]) ctrlvals(%[[TRUE9]]) : !quantum.bit ctrls !quantum.bit - - // Reinsertion of target and control qubits - // CHECK: %[[INS1_I64:.*]] = arith.index_cast %{{.*}} : i64 to index - // CHECK: %[[INS1_IDX:.*]] = arith.index_cast %[[INS1_I64]] : index to i64 - // CHECK: %[[INS1:.*]] = quantum.insert %[[ALLOC]][%[[INS1_IDX]]], %[[CTD_T]] : !quantum.reg, !quantum.bit - // CHECK: %[[INS2_I64:.*]] = arith.index_cast %{{.*}} : i64 to index - // CHECK: %[[INS2_IDX:.*]] = arith.index_cast %[[INS2_I64]] : index to i64 - // CHECK: %[[INS2:.*]] = quantum.insert %[[ALLOC]][%[[INS2_IDX]]], %[[CTD_C]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[ALLOC]] : !quantum.reg + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "PauliX"() %3 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliX"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CNOT"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CNOT"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Clifford: MQTOpt to CatalystQuantum") - + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliX: MQTOpt to CatalystQuantum") -def test_pauli_gates_roundtrip() -> None: - """Test roundtrip conversion of Pauli gates. - Mirrors: quantum_pauli.mlir - Gates: X, Y, Z, I, and their controlled variants (CNOT, CY, CZ, Toffoli) - Structure: - 1. Uncontrolled Pauli gates (X, Y, Z, I) - 2. Controlled Pauli gates (using qml.ctrl on Pauli gates) - 3. Two-qubit controlled gates (CNOT, CY, CZ) - 4. Toffoli (CCX) - 5. Controlled two-qubit gates (controlled CNOT, CY, CZ, Toffoli) +def test_pauliy_roundtrip() -> None: + """Test roundtrip conversion of the PauliY gate. Raises: FileNotFoundError: If intermediate MLIR files are not found @@ -413,33 +216,14 @@ def test_pauli_gates_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=4)) + @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - # Uncontrolled Pauli gates - qml.PauliX(wires=0) + # Non-controlled + qml.Y(wires=0) qml.PauliY(wires=0) - qml.PauliZ(wires=0) - qml.Identity(wires=0) - - # Controlled Pauli gates (single control) - use qml.ctrl on Pauli gates - qml.ctrl(qml.PauliX(wires=0), control=1) + # Controlled qml.ctrl(qml.PauliY(wires=0), control=1) - qml.ctrl(qml.PauliZ(wires=0), control=1) - # Why is `qml.ctrl(qml.Identity(wires=0), control=1)` not supported by Catalyst? - - # Two-qubit controlled gates (explicit CNOT, CY, CZ gate names) - qml.CNOT(wires=[1, 0]) # First qubit is control, second is target qml.CY(wires=[1, 0]) - qml.CZ(wires=[1, 0]) - - # Toffoli (also CCX) - qml.Toffoli(wires=[0, 1, 2]) - - # Controlled multi-qubit gates (adding extra controls) - qml.ctrl(qml.CNOT(wires=[0, 1]), control=2) - qml.ctrl(qml.CY(wires=[0, 1]), control=2) - qml.ctrl(qml.CZ(wires=[0, 1]), control=2) - qml.ctrl(qml.Toffoli(wires=[0, 1, 2]), control=3) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -454,185 +238,53 @@ def module() -> None: mlir_opt = module.mlir_opt assert mlir_opt - # Find where MLIR files are generated (relative to cwd where pytest is run) - # Catalyst generates MLIR files in the current working directory mlir_dir = Path.cwd() - - # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() + # Verify original CatalystQuantum + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "PauliY"() %1 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliY"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5:2 = quantum.custom "CY"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CY"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "PauliY: CatalystQuantum") + # Verify CatalystQuantum → MQTOpt conversion - check_to_mqtopt = """ - // CHECK: func.func public @circuit() - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<4x!mqtopt.Qubit> - // CHECK: %[[Q0_IDX:.*]] = arith.index_cast - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[Q0_IDX]]] : memref<4x!mqtopt.Qubit> - - // Uncontrolled Pauli gates on Q0 - // CHECK: %[[X:.*]] = mqtopt.x(static [] mask []) %[[Q0]] : !mqtopt.Qubit - // CHECK: %[[Y:.*]] = mqtopt.y(static [] mask []) %[[X]] : !mqtopt.Qubit - // CHECK: %[[Z:.*]] = mqtopt.z(static [] mask []) %[[Y]] : !mqtopt.Qubit - // CHECK: %[[I:.*]] = mqtopt.i(static [] mask []) %[[Z]] : !mqtopt.Qubit - - // Load Q1 for controlled Pauli gates - // CHECK: %[[Q1_IDX:.*]] = arith.index_cast - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[Q1_IDX]]] : memref<4x!mqtopt.Qubit> - - // Controlled Pauli gates (qml.ctrl on Pauli gates) - // CHECK: %[[CX1_T:.*]], %[[CX1_C:.*]] = mqtopt.x(static [] mask []) %[[I]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CY1_T:.*]], %[[CY1_C:.*]] = mqtopt.y(static [] mask []) %[[CX1_T]] ctrl %[[CX1_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CZ1_T:.*]], %[[CZ1_C:.*]] = mqtopt.z(static [] mask []) %[[CY1_T]] ctrl %[[CY1_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Two-qubit controlled gates (CNOT, CY, CZ) - // CHECK: %[[CNOT_T:.*]], %[[CNOT_C:.*]] = mqtopt.x(static [] mask []) %[[CZ1_C]] ctrl %[[CZ1_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CY_T:.*]], %[[CY_C:.*]] = mqtopt.y(static [] mask []) %[[CNOT_T]] ctrl %[[CNOT_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CZ_T:.*]], %[[CZ_C:.*]] = mqtopt.z(static [] mask []) %[[CY_T]] ctrl %[[CY_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Load Q2 for Toffoli - // CHECK: %[[Q2_IDX:.*]] = arith.index_cast - // CHECK: %[[Q2:.*]] = memref.load %[[ALLOC]][%[[Q2_IDX]]] : memref<4x!mqtopt.Qubit> - - // Toffoli (X with 2 controls: Q0, Q1 -> Q2) - // CHECK: %[[TOF_T:.*]], %[[TOF_C:.*]]:2 = mqtopt.x(static [] mask []) %[[Q2]] ctrl %[[CZ_C]], %[[CZ_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled CNOT (adds Q2 as control to CNOT on Q0, Q1) - // CHECK: %[[CCNOT_T:.*]], %[[CCNOT_C:.*]]:2 = mqtopt.x(static [] mask []) %[[TOF_C]]#1 ctrl %[[TOF_T]], %[[TOF_C]]#0 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled CY - // CHECK: %[[CCY_T:.*]], %[[CCY_C:.*]]:2 = mqtopt.y(static [] mask []) %[[CCNOT_T]] ctrl %[[CCNOT_C]]#0, %[[CCNOT_C]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled CZ - // CHECK: %[[CCZ_T:.*]], %[[CCZ_C:.*]]:2 = mqtopt.z(static [] mask []) %[[CCY_T]] ctrl %[[CCY_C]]#0, %[[CCY_C]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Load Q3 for controlled Toffoli - // CHECK: %[[Q3_IDX:.*]] = arith.index_cast - // CHECK: %[[Q3:.*]] = memref.load %[[ALLOC]][%[[Q3_IDX]]] : memref<4x!mqtopt.Qubit> - - // Controlled Toffoli (X with 3 controls: Q3, Q1, Q0 -> Q2) - // CHECK: %[[CTOF_T:.*]], %[[CTOF_C:.*]]:3 = mqtopt.x(static [] mask []) %[[CCZ_C]]#0 ctrl %[[Q3]], %[[CCZ_C]]#1, %[[CCZ_T]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit - - // Store qubits back - // CHECK: memref.store %[[CTOF_C]]#1, %[[ALLOC]] - // CHECK: memref.store %[[CTOF_C]]#2, %[[ALLOC]] - // CHECK: memref.store %[[CTOF_T]], %[[ALLOC]] - // CHECK: memref.store %[[CTOF_C]]#0, %[[ALLOC]] - // CHECK: memref.dealloc %[[ALLOC]] : memref<4x!mqtopt.Qubit> + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.y(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_2 = mqtopt.y(static [] mask []) %out_qubits : !mqtopt.Qubit + //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.y(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.y(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Pauli: CatalystQuantum to MQTOpt") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliY: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion - check_to_catalyst = """ - // CHECK: func.func public @circuit() - // CHECK: %[[QREG:.*]] = quantum.alloc( 4) : !quantum.reg - - // Extract Q0 - // CHECK: %[[Q0_IDX1:.*]] = arith.index_cast - // CHECK: %[[Q0_IDX2:.*]] = arith.index_cast %[[Q0_IDX1]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[Q0_IDX2]]] : !quantum.reg -> !quantum.bit - - // Uncontrolled Pauli gates on Q0 - // CHECK: %[[X:.*]] = quantum.custom "PauliX"() %[[Q0]] : !quantum.bit - // CHECK: %[[Y:.*]] = quantum.custom "PauliY"() %[[X]] : !quantum.bit - // CHECK: %[[Z:.*]] = quantum.custom "PauliZ"() %[[Y]] : !quantum.bit - // CHECK: %[[I:.*]] = quantum.custom "Identity"() %[[Z]] : !quantum.bit - - // Extract Q1 - // CHECK: %[[Q1_IDX1:.*]] = arith.index_cast - // CHECK: %[[Q1_IDX2:.*]] = arith.index_cast %[[Q1_IDX1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[Q1_IDX2]]] : !quantum.reg -> !quantum.bit - - // Controlled Pauli gates (qml.ctrl on X, Y, Z) - results swap for each gate - // CHECK: %[[TRUE1:.*]] = arith.constant true - // CHECK: %[[FALSE1:.*]] = arith.constant false - // CHECK: %[[CX1_T:.*]], %[[CX1_C:.*]] = quantum.custom "CNOT"() %[[I]] ctrls(%[[Q1]]) ctrlvals(%[[TRUE1]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[TRUE2:.*]] = arith.constant true - // CHECK: %[[FALSE2:.*]] = arith.constant false - // CHECK: %[[CY1_T:.*]], %[[CY1_C:.*]] = quantum.custom "CY"() %[[CX1_T]] ctrls(%[[CX1_C]]) ctrlvals(%[[TRUE2]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[TRUE3:.*]] = arith.constant true - // CHECK: %[[FALSE3:.*]] = arith.constant false - // CHECK: %[[CZ1_T:.*]], %[[CZ1_C:.*]] = quantum.custom "CZ"() %[[CY1_T]] ctrls(%[[CY1_C]]) ctrlvals(%[[TRUE3]]) : !quantum.bit ctrls !quantum.bit - - // Two-qubit controlled gates (CNOT, CY, CZ) - results swap - // CHECK: %[[TRUE4:.*]] = arith.constant true - // CHECK: %[[FALSE4:.*]] = arith.constant false - // CHECK: %[[CNOT_T:.*]], %[[CNOT_C:.*]] = quantum.custom "CNOT"() %[[CZ1_C]] ctrls(%[[CZ1_T]]) ctrlvals(%[[TRUE4]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[TRUE5:.*]] = arith.constant true - // CHECK: %[[FALSE5:.*]] = arith.constant false - // CHECK: %[[CY_T:.*]], %[[CY_C:.*]] = quantum.custom "CY"() %[[CNOT_T]] ctrls(%[[CNOT_C]]) ctrlvals(%[[TRUE5]]) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[TRUE6:.*]] = arith.constant true - // CHECK: %[[FALSE6:.*]] = arith.constant false - // CHECK: %[[CZ_T:.*]], %[[CZ_C:.*]] = quantum.custom "CZ"() %[[CY_T]] ctrls(%[[CY_C]]) ctrlvals(%[[TRUE6]]) : !quantum.bit ctrls !quantum.bit - - // Extract Q2 for Toffoli - // CHECK: %[[Q2_IDX1:.*]] = arith.index_cast - // CHECK: %[[Q2_IDX2:.*]] = arith.index_cast %[[Q2_IDX1]] : index to i64 - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[Q2_IDX2]]] : !quantum.reg -> !quantum.bit - - // Toffoli (2 controls + target) - // CHECK: %[[TRUE7:.*]] = arith.constant true - // CHECK: %[[FALSE7:.*]] = arith.constant false - // CHECK: %[[TOF_T:.*]], %[[TOF_C:.*]]:2 = quantum.custom "Toffoli"() %[[Q2]] ctrls(%[[CZ_C]], %[[CZ_T]]) ctrlvals(%[[TRUE7]], %[[TRUE7]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit - - // Controlled CNOT (becomes Toffoli with 3 qubits) - // CHECK: %[[TRUE8:.*]] = arith.constant true - // CHECK: %[[FALSE8:.*]] = arith.constant false - // CHECK: %[[CCNOT_T:.*]], %[[CCNOT_C:.*]]:2 = quantum.custom "Toffoli"() %[[TOF_C]]#1 ctrls(%[[TOF_T]], %[[TOF_C]]#0) ctrlvals(%[[TRUE8]], %[[TRUE8]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit - - // Controlled CY (PauliY with 2 controls) - // CHECK: %[[TRUE9:.*]] = arith.constant true - // CHECK: %[[FALSE9:.*]] = arith.constant false - // CHECK: %[[CCY_T:.*]], %[[CCY_C:.*]]:2 = quantum.custom "PauliY"() %[[CCNOT_T]] ctrls(%[[CCNOT_C]]#0, %[[CCNOT_C]]#1) ctrlvals(%[[TRUE9]], %[[TRUE9]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit - - // Controlled CZ (PauliZ with 2 controls) - // CHECK: %[[TRUE10:.*]] = arith.constant true - // CHECK: %[[FALSE10:.*]] = arith.constant false - // CHECK: %[[CCZ_T:.*]], %[[CCZ_C:.*]]:2 = quantum.custom "PauliZ"() %[[CCY_T]] ctrls(%[[CCY_C]]#0, %[[CCY_C]]#1) ctrlvals(%[[TRUE10]], %[[TRUE10]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit - - // Extract Q3 for controlled Toffoli - // CHECK: %[[Q3_IDX1:.*]] = arith.index_cast - // CHECK: %[[Q3_IDX2:.*]] = arith.index_cast %[[Q3_IDX1]] : index to i64 - // CHECK: %[[Q3:.*]] = quantum.extract %[[QREG]][%[[Q3_IDX2]]] : !quantum.reg -> !quantum.bit - - // Controlled Toffoli (PauliX with 3 controls) - // CHECK: %[[TRUE11:.*]] = arith.constant true - // CHECK: %[[FALSE11:.*]] = arith.constant false - // CHECK: %[[CTOF_T:.*]], %[[CTOF_C:.*]]:3 = quantum.custom "PauliX"() %[[CCZ_C]]#0 ctrls(%[[Q3]], %[[CCZ_C]]#1, %[[CCZ_T]]) ctrlvals(%[[TRUE11]], %[[TRUE11]], %[[TRUE11]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit - - // Insert qubits back to register - // CHECK: %[[INS1_IDX1:.*]] = arith.index_cast - // CHECK: %[[INS1_IDX2:.*]] = arith.index_cast %[[INS1_IDX1]] : index to i64 - // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS1_IDX2]]], %[[CTOF_C]]#1 : !quantum.reg, !quantum.bit - // CHECK: %[[INS2_IDX1:.*]] = arith.index_cast - // CHECK: %[[INS2_IDX2:.*]] = arith.index_cast %[[INS2_IDX1]] : index to i64 - // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS2_IDX2]]], %[[CTOF_C]]#2 : !quantum.reg, !quantum.bit - // CHECK: %[[INS3_IDX1:.*]] = arith.index_cast - // CHECK: %[[INS3_IDX2:.*]] = arith.index_cast %[[INS3_IDX1]] : index to i64 - // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS3_IDX2]]], %[[CTOF_T]] : !quantum.reg, !quantum.bit - // CHECK: %[[INS4_IDX1:.*]] = arith.index_cast - // CHECK: %[[INS4_IDX2:.*]] = arith.index_cast %[[INS4_IDX1]] : index to i64 - // CHECK: %{{.*}} = quantum.insert %[[QREG]][%[[INS4_IDX2]]], %[[CTOF_C]]#0 : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "PauliY"() %3 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliY"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CY"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CY"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Pauli: MQTOpt to CatalystQuantum") - + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliY: MQTOpt to CatalystQuantum") -def test_parameterized_gates_roundtrip() -> None: - """Test roundtrip conversion of parameterized rotation gates. - Mirrors: quantum_param.mlir - Gates: RX, RY, RZ, PhaseShift, and their controlled variants (CRX, CRY) - Note: MLIR test does NOT include CRZ, only CRX and CRY +def test_pauliz_roundtrip() -> None: + """Test roundtrip conversion of the PauliZ gate. Raises: FileNotFoundError: If intermediate MLIR files are not found @@ -642,17 +294,12 @@ def test_parameterized_gates_roundtrip() -> None: @apply_pass("mqt.catalystquantum-to-mqtopt") @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - angle = 0.3 - - # Non-controlled parameterized gates - qml.RX(angle, wires=0) - qml.RY(angle, wires=0) - qml.RZ(angle, wires=0) - qml.PhaseShift(angle, wires=0) - - # Controlled parameterized gates - qml.CRX(angle, wires=[1, 0]) - qml.CRY(angle, wires=[1, 0]) + # Non-controlled + qml.Z(wires=0) + qml.PauliZ(wires=0) + # Controlled + qml.ctrl(qml.PauliZ(wires=0), control=1) + qml.CZ(wires=[1, 0]) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -667,88 +314,119 @@ def module() -> None: mlir_opt = module.mlir_opt assert mlir_opt - # Find where MLIR files are generated (relative to cwd where pytest is run) - # Catalyst generates MLIR files in the current working directory mlir_dir = Path.cwd() - - # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() + # Verify original CatalystQuantum + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "PauliZ"() %1 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliZ"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5:2 = quantum.custom "CZ"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CZ"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "PauliZ: CatalystQuantum") + # Verify CatalystQuantum → MQTOpt conversion - check_to_mqtopt = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: %[[ALLOC:.*]] = memref.alloc(){{.*}}!mqtopt.Qubit - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][{{.*}}]{{.*}}!mqtopt.Qubit - - // Uncontrolled parameterized gates - // CHECK: %[[RX:.*]] = mqtopt.rx({{.*}}) %[[Q0]] : !mqtopt.Qubit - // CHECK: %[[RY:.*]] = mqtopt.ry({{.*}}) %[[RX]] : !mqtopt.Qubit - // CHECK: %[[RZ:.*]] = mqtopt.rz({{.*}}) %[[RY]] : !mqtopt.Qubit - // CHECK: %[[PS:.*]] = mqtopt.p({{.*}}) %[[RZ]] : !mqtopt.Qubit - - // Load Q1 for controlled parameterized gates - // CHECK: memref.load{{.*}}!mqtopt.Qubit - - // Controlled parameterized gates - // CHECK: mqtopt.rx({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: mqtopt.ry({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Reinsertion - // CHECK: memref.store - // CHECK: memref.store - // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.z(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_2 = mqtopt.z(static [] mask []) %out_qubits : !mqtopt.Qubit + //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.z(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.z(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Param: CatalystQuantum to MQTOpt") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliZ: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion - check_to_catalyst = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg - - // Q0 is extracted first for uncontrolled gates - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - - // Uncontrolled parameterized gates - // CHECK: %[[RX:.*]] = quantum.custom "RX"({{.*}}) %[[Q0]] : !quantum.bit - // CHECK: %[[RY:.*]] = quantum.custom "RY"({{.*}}) %[[RX]] : !quantum.bit - // CHECK: %[[RZ:.*]] = quantum.custom "RZ"({{.*}}) %[[RY]] : !quantum.bit - // CHECK: %[[PS:.*]] = quantum.custom "PhaseShift"({{.*}}) %[[RZ]] : !quantum.bit - - // Q1 is extracted lazily right before controlled gates - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - - // Controlled parameterized gates (qubits swap after each operation) - // CRX: target=%[[PS]], control=%[[Q1]] - // CHECK: %[[CRX_T:.*]], %[[CRX_C:.*]] = quantum.custom "CRX"({{.*}}) %[[PS]] ctrls(%[[Q1]]) - // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit - // CRY: target=%[[CRX_C]] (previous control), control=%[[CRX_T]] (previous target) - // CHECK: quantum.custom "CRY"({{.*}}) %[[CRX_C]] ctrls(%[[CRX_T]]) ctrlvals(%true{{.*}}) - // CHECK-SAME: : !quantum.bit ctrls !quantum.bit - - // Reinsertion - // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit - // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "PauliZ"() %3 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "PauliZ"() %out_qubits : !quantum.bit + //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CZ"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CZ"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliZ: MQTOpt to CatalystQuantum") + + +def test_hadamard_roundtrip() -> None: + """Test roundtrip conversion of the Hadamard gate. + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + qml.Hadamard(wires=0) + qml.ctrl(qml.Hadamard(wires=0), control=1) + qml.CH(wires=[1, 0]) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + mlir_opt = module.mlir_opt + assert mlir_opt + + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "Hadamard"() %1 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "Hadamard"() %out_qubits ctrls(%2) ctrlvals(%extracted_5) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "Hadamard"() %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%extracted_7) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "Hadamard: CatalystQuantum") + + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.h(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.h(static [] mask []) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.h(static [] mask []) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Param: MQTOpt to CatalystQuantum") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "Hadamard: CatalystQuantum to MQTOpt") + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "Hadamard"() %3 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "Hadamard"() %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "Hadamard"() %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Hadamard: MQTOpt to CatalystQuantum") -def test_entangling_gates_roundtrip() -> None: - """Test roundtrip conversion of entangling/permutation gates. - Mirrors: quantum_entangling.mlir - Gates: SWAP, ISWAP, ISWAP†, ECR, and their controlled variants +def test_s_gate_roundtrip() -> None: + """Test roundtrip conversion of the S gate. Raises: FileNotFoundError: If intermediate MLIR files are not found @@ -756,19 +434,11 @@ def test_entangling_gates_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=3)) + @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - # Uncontrolled permutation gates - qml.SWAP(wires=[0, 1]) - qml.ISWAP(wires=[0, 1]) - qml.adjoint(qml.ISWAP(wires=[0, 1])) - qml.ECR(wires=[0, 1]) - - # Controlled permutation gates - qml.CSWAP(wires=[2, 0, 1]) - qml.ctrl(qml.ISWAP(wires=[0, 1]), control=2) - qml.ctrl(qml.adjoint(qml.ISWAP(wires=[0, 1])), control=2) - qml.ctrl(qml.ECR(wires=[0, 1]), control=2) + qml.S(wires=0) + qml.adjoint(qml.S(wires=0)) + qml.ctrl(qml.S(wires=0), control=1) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -779,87 +449,115 @@ def circuit() -> None: def module() -> None: return circuit() - # Verify the roundtrip completes successfully mlir_opt = module.mlir_opt assert mlir_opt - # Find where MLIR files are generated (relative to cwd where pytest is run) - # Catalyst generates MLIR files in the current working directory mlir_dir = Path.cwd() - - # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() - # Verify CatalystQuantum → MQTOpt conversion - check_to_mqtopt = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: memref.alloc(){{.*}}!mqtopt.Qubit + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "S"() %1 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "S"() %out_qubits adj : !quantum.bit + //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "S"() %out_qubits_2 ctrls(%2) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "S: CatalystQuantum") - // Qubits loaded as needed - // CHECK: memref.load{{.*}}!mqtopt.Qubit - // CHECK: memref.load{{.*}}!mqtopt.Qubit + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.s(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_2 = mqtopt.sdg(static [] mask []) %out_qubits : !mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "S: CatalystQuantum to MQTOpt") - // SWAP gate (ISWAP/ECR get decomposed) - // CHECK: mqtopt.swap({{.*}}){{.*}}: !mqtopt.Qubit, !mqtopt.Qubit + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "S"() %3 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "S"() %out_qubits adj : !quantum.bit + //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "S"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "S: MQTOpt to CatalystQuantum") - // Control qubit loaded - // CHECK: memref.load{{.*}}!mqtopt.Qubit - // Controlled swap gate - // CHECK: mqtopt.swap({{.*}}){{.*}}ctrl{{.*}}: !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit +def test_t_gate_roundtrip() -> None: + """Test roundtrip conversion of the T gate. - // Reinsertion - // CHECK: memref.store - // CHECK: memref.store - // CHECK: memref.store - // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + Raises: + FileNotFoundError: If intermediate MLIR files are not found """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Entangling: CatalystQuantum to MQTOpt") - # Verify MQTOpt → CatalystQuantum conversion (simplified - ISWAP/ECR are heavily decomposed) - check_to_catalyst = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + qml.T(wires=0) + qml.adjoint(qml.T(wires=0)) + qml.ctrl(qml.T(wires=0), control=1) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] - // Qubits extracted as needed - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() - // SWAP is visible, but ISWAP/ECR/adjISWAP are heavily decomposed into primitives (H, S, CNOT, RZ, RY chains) - // CHECK: quantum.custom "SWAP"() %[[Q0]], %[[Q1]] : !quantum.bit, !quantum.bit + mlir_opt = module.mlir_opt + assert mlir_opt - // After all decompositions, Q2 extracted for controlled gates - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - // Controlled swap - // CHECK: quantum.custom "CSWAP"() {{.*}} ctrls(%[[Q2]]) ctrlvals( + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) - // Reinsertion - // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit - // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit - // CHECK: quantum.insert %[[QREG]][{{.*}}], {{.*}} : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "T"() %1 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "T"() %out_qubits adj : !quantum.bit + //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "T"() %out_qubits_2 ctrls(%2) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "T: CatalystQuantum") + + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.t(static [] mask []) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_2 = mqtopt.tdg(static [] mask []) %out_qubits : !mqtopt.Qubit + //CHECK: %out_qubits_7, %pos_ctrl_out_qubits = mqtopt.t(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Entangling: MQTOpt to CatalystQuantum") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "T: CatalystQuantum to MQTOpt") + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "T"() %3 : !quantum.bit + //CHECK: %out_qubits_2 = quantum.custom "T"() %out_qubits adj : !quantum.bit + //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "T"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "T: MQTOpt to CatalystQuantum") -def test_ising_gates_roundtrip() -> None: - """Test roundtrip conversion of Ising-type gates. - Mirrors: quantum_ising.mlir - Gates: IsingXY, IsingXX, IsingYY, IsingZZ, and their controlled variants - Note: IsingXY takes 2 parameters in MLIR (phi and beta) +def test_rx_gate_roundtrip() -> None: + """Test roundtrip conversion of the RX gate. Raises: FileNotFoundError: If intermediate MLIR files are not found @@ -867,21 +565,77 @@ def test_ising_gates_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=3)) + @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - angle = 0.3 + qml.RX(0.5, wires=0) + qml.ctrl(qml.RX(0.5, wires=0), control=1) + qml.CRX(0.5, wires=[1, 0]) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + mlir_opt = module.mlir_opt + assert mlir_opt + + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "RX"({{.*}}) %1 : !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CRX"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_8:2 = quantum.custom "CRX"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "RX: CatalystQuantum") - # Uncontrolled Ising gates - qml.IsingXY(angle, wires=[0, 1]) - qml.IsingXX(angle, wires=[0, 1]) - qml.IsingYY(angle, wires=[0, 1]) - qml.IsingZZ(angle, wires=[0, 1]) + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.rx({{.*}}) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.rx({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.rx({{.*}}) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RX: CatalystQuantum to MQTOpt") + + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "RX"({{.*}}) %3 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRX"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRX"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RX: MQTOpt to CatalystQuantum") - # Controlled Ising gates - qml.ctrl(qml.IsingXY(angle, wires=[0, 1]), control=2) - qml.ctrl(qml.IsingXX(angle, wires=[0, 1]), control=2) - qml.ctrl(qml.IsingYY(angle, wires=[0, 1]), control=2) - qml.ctrl(qml.IsingZZ(angle, wires=[0, 1]), control=2) + +def test_ry_gate_roundtrip() -> None: + """Test roundtrip conversion of the RY gate. + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=2)) + def circuit() -> None: + qml.RY(0.5, wires=0) + qml.ctrl(qml.RY(0.5, wires=0), control=1) + qml.CRY(0.5, wires=[1, 0]) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -892,145 +646,128 @@ def circuit() -> None: def module() -> None: return circuit() - # Verify the roundtrip completes successfully mlir_opt = module.mlir_opt assert mlir_opt - # Find where MLIR files are generated (relative to cwd where pytest is run) - # Catalyst generates MLIR files in the current working directory - # This works regardless of where pytest is run from (locally or CI) - test_file_dir = Path(__file__).parent mlir_dir = Path.cwd() - test_file_dir.parent / "Conversion" - - # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): - # Fallback: list what files actually exist for debugging + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() - # Verify CatalystQuantum → MQTOpt conversion with FileCheck - check_to_mqtopt = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: %[[ALLOC:.*]] = memref.alloc(){{.*}}!mqtopt.Qubit - // CHECK: %[[Q0:.*]] = memref.load{{.*}}!mqtopt.Qubit - // CHECK: %[[Q1:.*]] = memref.load{{.*}}!mqtopt.Qubit - - // Uncontrolled Ising gates - // CHECK: %[[XY_OUT:.*]]:2 = mqtopt.xx_plus_yy({{.*}}) %[[Q0]], %[[Q1]] : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[XX_OUT:.*]]:2 = mqtopt.rxx({{.*}}) %[[XY_OUT]]#0, %[[XY_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[YY_OUT:.*]]:2 = mqtopt.ryy({{.*}}) %[[XX_OUT]]#0, %[[XX_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[ZZ_OUT:.*]]:2 = mqtopt.rzz({{.*}}) %[[YY_OUT]]#0, %[[YY_OUT]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled Ising gates (control qubit loaded here) - // CHECK: memref.load{{.*}}!mqtopt.Qubit - // CHECK: mqtopt.xx_plus_yy({{.*}}){{.*}}ctrl{{.*}} - // CHECK: mqtopt.rxx({{.*}}){{.*}}ctrl{{.*}} - // CHECK: mqtopt.ryy({{.*}}){{.*}}ctrl{{.*}} - // CHECK: mqtopt.rzz({{.*}}){{.*}}ctrl{{.*}} - - // Reinsertion - // CHECK: memref.store - // CHECK: memref.store - // CHECK: memref.store - // CHECK: memref.dealloc{{.*}}!mqtopt.Qubit + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "RY"({{.*}}) %1 : !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CRY"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_8:2 = quantum.custom "CRY"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Ising: CatalystQuantum to MQTOpt") - - # Verify MQTOpt → CatalystQuantum conversion with FileCheck - # Based on mqtopt_ising.mlir reference test - check_to_catalyst = """ - // CHECK: func.func {{.*}}@circuit - // CHECK: %[[QREG:.*]] = quantum.alloc({{.*}}) : !quantum.reg - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - - // Uncontrolled Ising gates - // IsingXY is decomposed: RZ -> IsingXY -> RZ - // CHECK: %[[RZ0:.*]] = quantum.custom "RZ"({{.*}}) %[[Q1]] : !quantum.bit - // CHECK: %[[XY:.*]]:2 = quantum.custom "IsingXY"({{.*}}) %[[Q0]], %[[RZ0]] : !quantum.bit, !quantum.bit - // CHECK: %[[RZ1:.*]] = quantum.custom "RZ"({{.*}}) %[[XY]]#1 : !quantum.bit - - // IsingXX, IsingYY, IsingZZ gates - // CHECK: %[[XX:.*]]:2 = quantum.custom "IsingXX"({{.*}}) %[[XY]]#0, %[[RZ1]] : !quantum.bit, !quantum.bit - // CHECK: %[[YY:.*]]:2 = quantum.custom "IsingYY"({{.*}}) %[[XX]]#0, %[[XX]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[ZZ:.*]]:2 = quantum.custom "IsingZZ"({{.*}}) %[[YY]]#0, %[[YY]]#1 : !quantum.bit, !quantum.bit - - // Controlled Ising gates (with ctrls) - // Extract control qubit - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][{{.*}}] : !quantum.reg -> !quantum.bit - - // Controlled IsingXY: RZ(ctrl) -> IsingXY(ctrl) -> RZ(ctrl) - // CHECK: %[[CRZ0:.*]], %[[CTRL1:.*]] = quantum.custom "RZ"({{.*}}) %[[ZZ]]#1 ctrls(%[[Q2]]) - // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CXY:.*]]:2, %[[CTRL2:.*]] = quantum.custom "IsingXY"({{.*}}) %[[ZZ]]#0, %[[CRZ0]] - // CHECK-SAME: ctrls(%[[CTRL1]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRZ1:.*]], %[[CTRL3:.*]] = quantum.custom "RZ"({{.*}}) %[[CXY]]#1 ctrls(%[[CTRL2]]) - // CHECK-SAME: ctrlvals(%true{{.*}}) : !quantum.bit ctrls !quantum.bit - - // Controlled IsingXX, IsingYY, IsingZZ - // CHECK: %[[CXX:.*]]:2, %[[CTRL4:.*]] = quantum.custom "IsingXX"({{.*}}) %[[CXY]]#0, %[[CRZ1]] - // CHECK-SAME: ctrls(%[[CTRL3]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CYY:.*]]:2, %[[CTRL5:.*]] = quantum.custom "IsingYY"({{.*}}) %[[CXX]]#0, %[[CXX]]#1 - // CHECK-SAME: ctrls(%[[CTRL4]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CZZ:.*]]:2, %[[CTRL6:.*]] = quantum.custom "IsingZZ"({{.*}}) %[[CYY]]#0, %[[CYY]]#1 - // CHECK-SAME: ctrls(%[[CTRL5]]) ctrlvals(%true{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - - // Reinsertion - // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CZZ]]#0 : !quantum.reg, !quantum.bit - // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CZZ]]#1 : !quantum.reg, !quantum.bit - // CHECK: quantum.insert %[[QREG]][{{.*}}], %[[CTRL6]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg + _run_filecheck(mlir_before, check_mlir_before, "RY: CatalystQuantum") + + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.ry({{.*}}) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.ry({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.ry(%extracted_7 static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Ising: MQTOpt to CatalystQuantum") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RY: CatalystQuantum to MQTOpt") - # Remove all intermediate files created during the test - for mlir_file in mlir_dir.glob("*.mlir"): - mlir_file.unlink() + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "RY"({{.*}}) %3 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRY"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRY"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RY: MQTOpt to CatalystQuantum") -def test_mqtopt_roundtrip() -> None: - """Execute the full roundtrip including MQT Core IR. +def test_rz_gate_roundtrip() -> None: + """Test roundtrip conversion of the RZ gate. - Executes the conversion passes to and from MQTOpt dialect AND - the roundtrip through MQT Core IR. + Raises: + FileNotFoundError: If intermediate MLIR files are not found """ @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - qml.Hadamard(wires=[0]) - qml.CNOT(wires=[0, 1]) + qml.RZ(0.5, wires=0) + qml.ctrl(qml.RZ(0.5, wires=0), control=1) + qml.CRZ(0.5, wires=[1, 0]) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] - @qml.qjit(target="mlir", autograph=True) + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) def module() -> None: return circuit() - # This will execute the pass and return the final MLIR mlir_opt = module.mlir_opt assert mlir_opt + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "RZ"({{.*}}) %1 : !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "CRZ"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_8:2 = quantum.custom "CRZ"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "RZ: CatalystQuantum") -def test_debug_roundtrip() -> None: - """Test roundtrip conversion. + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.rz({{.*}}) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.rz({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.rz(%extracted_7 static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RZ: CatalystQuantum to MQTOpt") + + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "RZ"({{.*}}) %3 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRZ"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRZ"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RZ: MQTOpt to CatalystQuantum") + + +def test_phaseshift_gate_roundtrip() -> None: + """Test roundtrip conversion of the PhaseShift gate. + + Raises: + FileNotFoundError: If intermediate MLIR files are not found """ @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") @qml.qnode(get_device("lightning.qubit", wires=2)) def circuit() -> None: - qml.Hadamard(wires=[0]) - qml.CNOT(wires=[0, 1]) # first wire is control + qml.PhaseShift(0.5, wires=0) + qml.ctrl(qml.PhaseShift(0.5, wires=0), control=1) + qml.ControlledPhaseShift(0.5, wires=[1, 0]) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -1041,43 +778,173 @@ def circuit() -> None: def module() -> None: return circuit() - # Verify the roundtrip completes successfully mlir_opt = module.mlir_opt assert mlir_opt - # Find where MLIR files are generated (relative to cwd where pytest is run) - # Catalyst generates MLIR files in the current working directory - # This works regardless of where pytest is run from (locally or CI) - test_file_dir = Path(__file__).parent mlir_dir = Path.cwd() - test_file_dir.parent / "Conversion" + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - # Read the intermediate MLIR files - mlir_to_mqtopt = mlir_dir / "3_to-mqtopt.mlir" + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits = quantum.custom "PhaseShift"({{.*}}) %1 : !quantum.bit + //CHECK: %out_qubits_6:2 = quantum.custom "ControlledPhaseShift"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_8:2 = quantum.custom "ControlledPhaseShift"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "PhaseShift: CatalystQuantum") + + check_after_mqtopt = """ + //CHECK: %out_qubits = mqtopt.p({{.*}}) %1 : !mqtopt.Qubit + //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.p({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.p({{.*}} static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + """ + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PhaseShift: CatalystQuantum to MQTOpt") + + check_after_catalyst = """ + //CHECK: %out_qubits = quantum.custom "PhaseShift"({{.*}}) %3 : !quantum.bit + //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "ControlledPhaseShift"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "ControlledPhaseShift"({{.*}}) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PhaseShift: MQTOpt to CatalystQuantum") + + +def test_swap_gate_roundtrip() -> None: + """Test roundtrip conversion of the SWAP gate. + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=3)) + def circuit() -> None: + qml.SWAP(wires=[0, 1]) + qml.ctrl(qml.SWAP(wires=[0, 1]), control=2) + qml.CSWAP(wires=[2, 0, 1]) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + mlir_opt = module.mlir_opt + assert mlir_opt + + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" + mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): + available_files = list(mlir_dir.glob("*.mlir")) + msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" + raise FileNotFoundError(msg) + + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() + with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: + mlir_after_mqtopt = f.read() + with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + + check_mlir_before = """ + //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %1, %2 : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:3 = quantum.custom "CSWAP"() %3, %out_qubits#0, %out_qubits#1 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_7:3 = quantum.custom "CSWAP"() %out_qubits_6#0, %out_qubits_6#1, %out_qubits_6#2 : !quantum.bit, !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_before, check_mlir_before, "SWAP: CatalystQuantum") + + check_after_mqtopt = """ + //CHECK: %out_qubits:2 = mqtopt.swap(static [] mask []) %1, %3 : !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %out_qubits_6:2, %pos_ctrl_out_qubits = mqtopt.swap(static [] mask []) %out_qubits#0, %out_qubits#1 ctrl %5 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_7:2, %pos_ctrl_out_qubits_8 = mqtopt.swap(static [] mask []) %out_qubits_6#0, %out_qubits_6#1 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + + """ + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "SWAP: CatalystQuantum to MQTOpt") + + check_after_catalyst = """ + //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %3, %6 : !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:2, %out_ctrl_qubits = quantum.custom "CSWAP"() %out_qubits#0, %out_qubits#1 ctrls(%9) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_9:2, %out_ctrl_qubits_10 = quantum.custom "CSWAP"() %out_qubits_6#0, %out_qubits_6#1 ctrls(%out_ctrl_qubits) ctrlvals(%true_7) : !quantum.bit, !quantum.bit ctrls !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "SWAP: MQTOpt to CatalystQuantum") + + +def test_toffoli_gate_roundtrip() -> None: + """Test roundtrip conversion of the Toffoli gate. + + Raises: + FileNotFoundError: If intermediate MLIR files are not found + """ + + @apply_pass("mqt.mqtopt-to-catalystquantum") + @apply_pass("mqt.catalystquantum-to-mqtopt") + @qml.qnode(get_device("lightning.qubit", wires=4)) + def circuit() -> None: + qml.Toffoli(wires=[0, 1, 2]) + qml.ctrl(qml.Toffoli(wires=[0, 1, 2]), control=3) + + custom_pipeline = [ + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), + ] + + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) + def module() -> None: + return circuit() + + mlir_opt = module.mlir_opt + assert mlir_opt + + mlir_dir = Path.cwd() + catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" + mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" - if not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): - # Fallback: list what files actually exist for debugging + if not catalyst_mlir.exists() or not mlir_to_mqtopt.exists() or not mlir_to_catalyst.exists(): available_files = list(mlir_dir.glob("*.mlir")) msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) + with Path(catalyst_mlir).open("r", encoding="utf-8") as f: + mlir_before = f.read() with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: mlir_after_mqtopt = f.read() with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: mlir_after_roundtrip = f.read() - # Verify CatalystQuantum → MQTOpt conversion with FileCheck - check_to_mqtopt = """ + check_mlir_before = """ + //CHECK: %out_qubits:3 = quantum.custom "Toffoli"() %1, %2, %3 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_12, %out_ctrl_qubits:3 = quantum.custom "PauliX"() %out_qubits#2 ctrls(%4, %out_qubits#0, %out_qubits#1) ctrlvals(%extracted_9, %extracted_10, %extracted_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ - _run_filecheck(mlir_after_mqtopt, check_to_mqtopt, "Ising: CatalystQuantum to MQTOpt") + _run_filecheck(mlir_before, check_mlir_before, "Toffoli: CatalystQuantum") - # Verify MQTOpt → CatalystQuantum conversion with FileCheck - # Based on mqtopt_ising.mlir reference test - check_to_catalyst = """ + check_after_mqtopt = """ + //CHECK: %out_qubits, %pos_ctrl_out_qubits:2 = mqtopt.x(static [] mask []) %5 ctrl %1, %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %out_qubits_12, %pos_ctrl_out_qubits_13:3 = mqtopt.x(static [] mask []) %out_qubits ctrl %7, %pos_ctrl_out_qubits#0, %pos_ctrl_out_qubits#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit """ - _run_filecheck(mlir_after_roundtrip, check_to_catalyst, "Ising: MQTOpt to CatalystQuantum") + _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "Toffoli: CatalystQuantum to MQTOpt") - # Remove all intermediate files created during the test - for mlir_file in mlir_dir.glob("*.mlir"): - mlir_file.unlink() \ No newline at end of file + check_after_catalyst = """ + //CHECK: %out_qubits, %out_ctrl_qubits:2 = quantum.custom "Toffoli"() %9 ctrls(%3, %6) ctrlvals(%true, %true) : !quantum.bit ctrls !quantum.bit, !quantum.bit + //CHECK: %out_qubits_14, %out_ctrl_qubits_15:3 = quantum.custom "PauliX"() %out_qubits ctrls(%12, %out_ctrl_qubits#0, %out_ctrl_qubits#1) ctrlvals(%true_12, %true_12, %true_12) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + """ + _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Toffoli: MQTOpt to CatalystQuantum") From d3e0869ba07efa1847396065094216b7a9143ca1 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 14:11:58 +0100 Subject: [PATCH 07/65] =?UTF-8?q?=F0=9F=90=9B=20fix=20conversion=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CatalystQuantumToMQTOpt.cpp | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp index 9dc8238..093246c 100644 --- a/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp +++ b/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp @@ -495,11 +495,23 @@ struct ConvertQuantumCustomOp final } else if (gateName == "PauliZ") { mqtoptOp = CREATE_GATE_OP(Z); } else if (gateName == "S") { - mqtoptOp = CREATE_GATE_OP(S); + if (op.getAdjoint()) { + mqtoptOp = CREATE_GATE_OP(Sdg); + } else { + mqtoptOp = CREATE_GATE_OP(S); + } } else if (gateName == "T") { - mqtoptOp = CREATE_GATE_OP(T); + if (op.getAdjoint()) { + mqtoptOp = CREATE_GATE_OP(Tdg); + } else { + mqtoptOp = CREATE_GATE_OP(T); + } } else if (gateName == "SX") { - mqtoptOp = CREATE_GATE_OP(SX); + if (op.getAdjoint()) { + mqtoptOp = CREATE_GATE_OP(SXdg); + } else { + mqtoptOp = CREATE_GATE_OP(SX); + } } else if (gateName == "ECR") { mqtoptOp = CREATE_GATE_OP(ECR); } else if (gateName == "SWAP") { @@ -613,30 +625,39 @@ struct ConvertQuantumCustomOp final additionalPosCtrlQubits.end()); SmallVector negCtrls(additionalNegCtrlQubits.begin(), additionalNegCtrlQubits.end()); - mqtoptOp = rewriter.create( + auto cnotOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), staticParams, paramsMask, finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {cnotOp.getResult(1), cnotOp.getResult(0)}); + return success(); } else if (gateName == "CY") { SmallVector ctrls = {inQubits[0]}; ctrls.append(additionalPosCtrlQubits.begin(), additionalPosCtrlQubits.end()); SmallVector negCtrls(additionalNegCtrlQubits.begin(), additionalNegCtrlQubits.end()); - mqtoptOp = rewriter.create( + auto cyOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), staticParams, paramsMask, finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {cyOp.getResult(1), cyOp.getResult(0)}); + return success(); } else if (gateName == "CZ") { SmallVector ctrls = {inQubits[0]}; ctrls.append(additionalPosCtrlQubits.begin(), additionalPosCtrlQubits.end()); SmallVector negCtrls(additionalNegCtrlQubits.begin(), additionalNegCtrlQubits.end()); - mqtoptOp = rewriter.create( + auto czOp = rewriter.create( op.getLoc(), inQubits[1].getType(), ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), staticParams, paramsMask, finalParamValues, inQubits[1], ctrls, negCtrls); + // MQTOpt returns (target, control) but Catalyst expects (control, target) + rewriter.replaceOp(op, {czOp.getResult(1), czOp.getResult(0)}); + return success(); } else if (gateName == "Toffoli") { // Toffoli gate: 2 control qubits + 1 target qubit // inQubits[0] and inQubits[1] are controls, inQubits[2] is target @@ -645,10 +666,16 @@ struct ConvertQuantumCustomOp final additionalPosCtrlQubits.end()); SmallVector negCtrls(additionalNegCtrlQubits.begin(), additionalNegCtrlQubits.end()); - mqtoptOp = rewriter.create( + auto toffoliOp = rewriter.create( op.getLoc(), inQubits[2].getType(), ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), staticParams, paramsMask, finalParamValues, inQubits[2], ctrls, negCtrls); + // MQTOpt X with 2 controls returns (target, ctrl0, ctrl1) but Catalyst + // Toffoli expects (ctrl0, ctrl1, target) Need to reorder: [1, 2, 0] + // (controls first, then target) + rewriter.replaceOp(op, {toffoliOp.getResult(1), toffoliOp.getResult(2), + toffoliOp.getResult(0)}); + return success(); } else if (gateName == "CSWAP") { // CSWAP gate: 1 control qubit + 2 target qubits // inQubits[0] is control, inQubits[1] and inQubits[2] are targets @@ -657,11 +684,17 @@ struct ConvertQuantumCustomOp final additionalPosCtrlQubits.end()); SmallVector negCtrls(additionalNegCtrlQubits.begin(), additionalNegCtrlQubits.end()); - mqtoptOp = rewriter.create( + auto cswapOp = rewriter.create( op.getLoc(), ValueRange{inQubits[1], inQubits[2]}, ValueRange(ctrls).getTypes(), ValueRange(negCtrls).getTypes(), staticParams, paramsMask, finalParamValues, ValueRange{inQubits[1], inQubits[2]}, ctrls, negCtrls); + // MQTOpt SWAP returns (target0, target1, control) but Catalyst CSWAP + // expects (control, target0, target1) Need to reorder: [2, 0, 1] (control + // goes first) + rewriter.replaceOp(op, {cswapOp.getResult(2), cswapOp.getResult(0), + cswapOp.getResult(1)}); + return success(); } else { return op.emitError("Unsupported gate: ") << gateName; } From f116c0fba4fbd24bd6ca21aad4441ae321c9d07c Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 14:12:58 +0100 Subject: [PATCH 08/65] =?UTF-8?q?=F0=9F=90=9B=20enable=20cleanup=20of=20lo?= =?UTF-8?q?ose=20MLIR=20files=20in=20=5Fcleanup=5Fmlir=5Fartifacts=20funct?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/test_plugin.py b/test/python/test_plugin.py index 7921f92..97df132 100644 --- a/test/python/test_plugin.py +++ b/test/python/test_plugin.py @@ -60,8 +60,8 @@ def _cleanup_mlir_artifacts() -> None: if module_dir.is_dir(): shutil.rmtree(module_dir) # Remove any loose .mlir files - # for mlir_file in mlir_dir.glob("*.mlir"): - # mlir_file.unlink() + for mlir_file in mlir_dir.glob("*.mlir"): + mlir_file.unlink() def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "test") -> None: From a8feafb432b66922b0ba7edd92fd5d82988c795c Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 14:14:17 +0100 Subject: [PATCH 09/65] =?UTF-8?q?=F0=9F=9A=A7=20prepare=20project=20for=20?= =?UTF-8?q?packaging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 ++- python/mqt/core/plugins/catalyst/__init__.py | 3 ++- test/python/test_placeholder.py | 20 -------------------- test/python/test_plugin_setup.py | 2 +- 4 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 test/python/test_placeholder.py diff --git a/pyproject.toml b/pyproject.toml index da5256f..9d5e728 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,7 +115,7 @@ minversion = "9.0" strict = true addopts = [ "-ra", - "--numprocesses=auto", # Automatically use all available CPU cores for parallel testing + "-n0", # Disable parallel test execution (tests share MLIR files) ] filterwarnings = [ ] @@ -182,6 +182,7 @@ known-first-party = ["mqt.core.plugins.catalyst"] [tool.ruff.lint.per-file-ignores] "test/python/**" = ["T20", "INP001"] +"test/python/test_plugin.py" = ["E501"] # Allow long lines in MLIR CHECK patterns "test/lit.cfg.py" = ["INP001"] "docs/**" = ["T20", "INP001"] "noxfile.py" = ["T20", "TID251"] diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py index 3df6d90..84f55b6 100644 --- a/python/mqt/core/plugins/catalyst/__init__.py +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # @@ -25,6 +25,7 @@ def get_catalyst_plugin_abs_path() -> Path: Raises: FileNotFoundError: If the plugin library is not found. + RuntimeError: If the platform is unsupported. """ ext = {"Darwin": ".dylib", "Linux": ".so", "Windows": ".dll"}.get(platform.system()) if ext is None: diff --git a/test/python/test_placeholder.py b/test/python/test_placeholder.py deleted file mode 100644 index 8b676e3..0000000 --- a/test/python/test_placeholder.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Test module placeholder. - -This module provides a single trivial test. -""" - - -def test_placeholder() -> None: - """A trivial test that always passes. - - It exists only to satisfy automated checks until real tests are added. - """ - assert True diff --git a/test/python/test_plugin_setup.py b/test/python/test_plugin_setup.py index 8580394..2f6e69e 100644 --- a/test/python/test_plugin_setup.py +++ b/test/python/test_plugin_setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # From 621116694176a4056fb2c7a8392f9fc37cdd5cdc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:34:39 +0000 Subject: [PATCH 10/65] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/catalyst/__init__.py | 2 +- test/Conversion/quantum_pauli.mlir | 2 +- test/python/test_plugin_setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py index 3df6d90..5f32e19 100644 --- a/python/mqt/core/plugins/catalyst/__init__.py +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # diff --git a/test/Conversion/quantum_pauli.mlir b/test/Conversion/quantum_pauli.mlir index 588038b..337d672 100644 --- a/test/Conversion/quantum_pauli.mlir +++ b/test/Conversion/quantum_pauli.mlir @@ -50,7 +50,7 @@ module { // CHECK: %[[T0_5:.*]], %[[C1_4:.*]] = mqtopt.x(static [] mask []) %[[T0_4]] ctrl %[[C1_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[T0_6:.*]], %[[C1_5:.*]] = mqtopt.y(static [] mask []) %[[T0_5]] ctrl %[[C1_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[T0_7:.*]], %[[C1_6:.*]] = mqtopt.z(static [] mask []) %[[T0_6]] ctrl %[[C1_5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - + // --- Toffoli (2 controls + 1 target) ------------------------------------------------------- // CHECK: %[[T0_8:.*]], %[[C12_0:.*]]:2 = mqtopt.x(static [] mask []) %[[T0_7]] ctrl %[[C1_6]], %[[Q2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit diff --git a/test/python/test_plugin_setup.py b/test/python/test_plugin_setup.py index 8580394..2f6e69e 100644 --- a/test/python/test_plugin_setup.py +++ b/test/python/test_plugin_setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # From 1bac976efc43e418fb9afe50b5a3ff8a908dae64 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 15:47:37 +0100 Subject: [PATCH 11/65] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20restructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 7 +- .github/workflows/ci.yml | 60 +++++++++- .pre-commit-config.yaml | 2 +- CMakeLists.txt | 1 - pyproject.toml | 16 ++- test/CMakeLists.txt | 26 +---- test/Conversion/mqtopt_clifford.mlir | 136 ---------------------- test/Conversion/mqtopt_entangling.mlir | 91 --------------- test/Conversion/mqtopt_ising.mlir | 146 ------------------------ test/Conversion/mqtopt_param.mlir | 108 ------------------ test/Conversion/mqtopt_pauli.mlir | 90 --------------- test/Conversion/quantum_clifford.mlir | 85 -------------- test/Conversion/quantum_entangling.mlir | 79 ------------- test/Conversion/quantum_ising.mlir | 86 -------------- test/Conversion/quantum_param.mlir | 85 -------------- test/Conversion/quantum_pauli.mlir | 117 ------------------- test/lit.cfg.py | 47 -------- test/lit.site.cfg.py.in | 20 ---- test/{python => }/test_plugin.py | 0 test/{python => }/test_plugin_setup.py | 0 20 files changed, 73 insertions(+), 1129 deletions(-) delete mode 100644 test/Conversion/mqtopt_clifford.mlir delete mode 100644 test/Conversion/mqtopt_entangling.mlir delete mode 100644 test/Conversion/mqtopt_ising.mlir delete mode 100644 test/Conversion/mqtopt_param.mlir delete mode 100644 test/Conversion/mqtopt_pauli.mlir delete mode 100644 test/Conversion/quantum_clifford.mlir delete mode 100644 test/Conversion/quantum_entangling.mlir delete mode 100644 test/Conversion/quantum_ising.mlir delete mode 100644 test/Conversion/quantum_param.mlir delete mode 100644 test/Conversion/quantum_pauli.mlir delete mode 100644 test/lit.cfg.py delete mode 100644 test/lit.site.cfg.py.in rename test/{python => }/test_plugin.py (100%) rename test/{python => }/test_plugin_setup.py (100%) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 91dec89..34c3b31 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,9 +3,11 @@ on: release: types: [published] workflow_dispatch: + pull_request: + paths: + - .github/workflows/cd.yml permissions: - attestations: write contents: read id-token: write @@ -14,9 +16,6 @@ jobs: name: 🐍 Packaging uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 - # Builds wheels on all supported platforms using cibuildwheel. - # The wheels are uploaded as GitHub artifacts `dev-cibw-*` or `cibw-*`, depending on whether - # the workflow is triggered from a PR or a release, respectively. build-wheel: name: 🐍 Packaging strategy: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80589f0..f9a87b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,60 @@ jobs: uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 with: clang-version: 21 - build-project: false + build-project: true files-changed-only: true setup-python: true install-pkgs: "pennylane-catalyst==0.13.0" cmake-args: "-DPython3_EXECUTABLE=$(which python3)" cpp-linter-extra-args: "-std=c++20" + python-tests: + name: 🐍 Test + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-python-tests) + strategy: + fail-fast: false + matrix: + runs-on: + [ + ubuntu-24.04, + ubuntu-24.04-arm, + macos-14, + ] + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + with: + runs-on: ${{ matrix.runs-on }} + + python-coverage: + name: 🐍 Coverage + needs: [change-detection, python-tests] + if: fromJSON(needs.change-detection.outputs.run-python-tests) + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + permissions: + contents: read + id-token: write + + # run extensive Python tests on PRs labeled with the `extensive-python-ci` label + python-tests-extensive: + name: 🐍 Test (Extensive) + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-python-tests) && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'extensive-python-ci') + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + with: + runs-on: ${{ matrix.runs-on }} + + python-linter: + name: 🐍 Lint + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-python-tests) + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + with: + enable-ty: true + build-sdist: name: 🚀 CD needs: change-detection @@ -61,6 +108,9 @@ jobs: needs: - change-detection - cpp-linter + - python-tests + - python-tests-extensive + - python-linter - build-sdist - build-wheel - mlir-tests @@ -74,6 +124,14 @@ jobs: fromJSON(needs.change-detection.outputs.run-cpp-linter) && '' || 'cpp-linter,' }} + ${{ + fromJSON(needs.change-detection.outputs.run-python-tests) + && '' || 'python-tests,python-coverage,python-linter,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-python-tests) && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'extensive-python-ci') + && '' || 'python-tests-extensive,' + }} ${{ fromJSON(needs.change-detection.outputs.run-cd) && '' || 'build-sdist,build-wheel,' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d0b3eb..8942305 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: rev: v1.19.0 hooks: - id: mypy - files: ^(python/mqt|test/python|noxfile.py) + files: ^(python/mqt|test|noxfile.py) args: [] additional_dependencies: - nox diff --git a/CMakeLists.txt b/CMakeLists.txt index 8980325..59e8bde 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,4 +64,3 @@ set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) add_subdirectory(include) add_subdirectory(lib) -add_subdirectory(test) diff --git a/pyproject.toml b/pyproject.toml index 9d5e728..38cb5ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ addopts = [ filterwarnings = [ ] log_level = "INFO" -testpaths = ["test/python"] +testpaths = ["test"] [tool.coverage] @@ -141,8 +141,8 @@ report.exclude_also = [ ] [tool.mypy] -files = ["python/mqt", "test/python", "noxfile.py"] -mypy_path = ["$MYPY_CONFIG_FILE_DIR/python"] +files = ["python/mqt", "test", "noxfile.py"] +mypy_path = ["$MYPY_CONFIG_FILE_DIR"] python_version = "3.11" warn_unused_configs = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] @@ -181,9 +181,8 @@ future-annotations = true known-first-party = ["mqt.core.plugins.catalyst"] [tool.ruff.lint.per-file-ignores] -"test/python/**" = ["T20", "INP001"] -"test/python/test_plugin.py" = ["E501"] # Allow long lines in MLIR CHECK patterns -"test/lit.cfg.py" = ["INP001"] +"test/**" = ["T20", "INP001"] +"test/test_plugin.py" = ["E501"] "docs/**" = ["T20", "INP001"] "noxfile.py" = ["T20", "TID251"] "*.pyi" = ["D418", "DOC202", "PYI011", "PYI021"] @@ -222,8 +221,8 @@ ignore = ["GH200"] [tool.cibuildwheel] build = "cp3*" test-groups = ["test"] -test-sources = ["test/python"] -test-command = "pytest test/python" +test-sources = ["test"] +test-command = "pytest test" build-frontend = "build[uv]" @@ -267,7 +266,6 @@ exclude = [ "eval/**", "mlir/**", "noxfile.py", - "test/lit.cfg.py", ] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5a80d30..c3bcf91 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,26 +6,6 @@ # # Licensed under the MIT License -# Configure lit.site.cfg.py from template -configure_lit_site_cfg( - ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in - ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py - MAIN_CONFIG - ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py - DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in - ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py) - -# Dependencies needed for lit tests -set(MQT_CATALYST_PLUGIN_TEST_DEPENDS FileCheck not mqt-core-plugins-catalyst) - -# Target that just builds the dependencies (for CI build step) -add_custom_target(mqt-core-plugins-catalyst-lit-test-build-only - DEPENDS ${MQT_CATALYST_PLUGIN_TEST_DEPENDS}) -set_target_properties(mqt-core-plugins-catalyst-lit-test-build-only PROPERTIES FOLDER "Tests") - -# Target that runs the lit tests -add_lit_testsuite( - mqt-core-plugins-catalyst-lit-test "Running the mqt-core-plugins-catalyst lit tests" - ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${MQT_CATALYST_PLUGIN_TEST_DEPENDS}) -set_target_properties(mqt-core-plugins-catalyst-lit-test PROPERTIES FOLDER "Tests") +# All tests are now in Python (test/test_plugin.py) +# which test the complete roundtrip conversion pipeline +# No CMake configuration needed for Python tests diff --git a/test/Conversion/mqtopt_clifford.mlir b/test/Conversion/mqtopt_clifford.mlir deleted file mode 100644 index 47303c2..0000000 --- a/test/Conversion/mqtopt_clifford.mlir +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Clifford + T and controlled variants -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testMQTOptToCatalystQuantumCliffordT - func.func @testMQTOptToCatalystQuantumCliffordT() { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[QREG:.*]] = quantum.alloc( 3) : !quantum.reg - // CHECK: %[[IDX0:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[IDX0]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX1:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[IDX1]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX2:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[IDX2]]] : !quantum.reg -> !quantum.bit - - // --- Uncontrolled Clifford+T gates --------------------------------------------------------- - // CHECK: %[[I:.*]] = quantum.custom "Identity"() %[[Q0]] : !quantum.bit - // CHECK: %[[H:.*]] = quantum.custom "Hadamard"() %[[I]] : !quantum.bit - - // V gate gets decomposed into a sequence of single-qubit rotations - // CHECK: %[[CST:.*]] = arith.constant {{.*}} : f64 - // CHECK: %[[RZ1:.*]] = quantum.custom "RZ"(%[[CST]]) %[[H]] : !quantum.bit - // CHECK: %[[RY1:.*]] = quantum.custom "RY"(%[[CST]]) %[[RZ1]] : !quantum.bit - // CHECK: %[[RZ2:.*]] = quantum.custom "RZ"(%[[CST]]) %[[RY1]] adj : !quantum.bit - - - // CHECK: %[[NEG_CST:.*]] = arith.constant -{{.*}} : f64 - // CHECK: %[[RZ3:.*]] = quantum.custom "RZ"(%[[NEG_CST]]) %[[RZ2]] adj : !quantum.bit - // CHECK: %[[RY2:.*]] = quantum.custom "RY"(%[[NEG_CST]]) %[[RZ3]] : !quantum.bit - // CHECK: %[[RZ4:.*]] = quantum.custom "RZ"(%[[NEG_CST]]) %[[RY2]] : !quantum.bit - - // CHECK: %[[S:.*]] = quantum.custom "S"() %[[RZ4]] : !quantum.bit - // CHECK: %[[SDG:.*]] = quantum.custom "S"() %[[S]] adj : !quantum.bit - // CHECK: %[[T:.*]] = quantum.custom "T"() %[[SDG]] : !quantum.bit - // CHECK: %[[TDG:.*]] = quantum.custom "T"() %[[T]] adj : !quantum.bit - - // --- Peres gate decomposition ------------------------------------------------------------------- - // CHECK: %[[PERES_CNOT:.*]]:2 = quantum.custom "CNOT"() %[[TDG]], %[[Q1]] : !quantum.bit, !quantum.bit - // CHECK: %[[PERES_X:.*]] = quantum.custom "PauliX"() %[[PERES_CNOT]]#0 : !quantum.bit - - // --- Peresdg gate decomposition ------------------------------------------------------------------- - // CHECK: %[[PERESDG_X:.*]] = quantum.custom "PauliX"() %[[PERES_X]] : !quantum.bit - // CHECK: %[[PERESDG_CNOT:.*]]:2 = quantum.custom "CNOT"() %[[PERESDG_X]], %[[PERES_CNOT]]#1 : !quantum.bit, !quantum.bit - - // --- Controlled Hadamard ------------------------------------------------------------------- - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[CH_T:.*]], %[[CH_C:.*]] = quantum.custom "Hadamard"() %[[PERESDG_CNOT]]#0 ctrls(%[[PERESDG_CNOT]]#1) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Controlled V gate decomposition (controlled RZ-RY-RZ sequence) ------------------------------------------------------------------- - // CHECK: %[[CST:.*]] = arith.constant {{.*}} : f64 - // CHECK: %[[CRZ1_T:.*]], %[[CRZ1_C:.*]] = quantum.custom "RZ"(%[[CST]]) %[[CH_T]] ctrls(%[[CH_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRY1_T:.*]], %[[CRY1_C:.*]] = quantum.custom "RY"(%[[CST]]) %[[CRZ1_T]] ctrls(%[[CRZ1_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRZ2_T:.*]], %[[CRZ2_C:.*]] = quantum.custom "RZ"(%[[CST]]) %[[CRY1_T]] adj ctrls(%[[CRY1_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Controlled Vdg gate decomposition ------------------------------------------------------------------- - // CHECK: %[[NEG_CST:.*]] = arith.constant -{{.*}} : f64 - // CHECK: %[[CRZ3_T:.*]], %[[CRZ3_C:.*]] = quantum.custom "RZ"(%[[NEG_CST]]) %[[CRZ2_T]] adj ctrls(%[[CRZ2_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRY2_T:.*]], %[[CRY2_C:.*]] = quantum.custom "RY"(%[[NEG_CST]]) %[[CRZ3_T]] ctrls(%[[CRZ3_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRZ4_T:.*]], %[[CRZ4_C:.*]] = quantum.custom "RZ"(%[[NEG_CST]]) %[[CRY2_T]] ctrls(%[[CRY2_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Controlled S and Sdg ------------------------------------------------------------------- - // CHECK: %[[CS_T:.*]], %[[CS_C:.*]] = quantum.custom "S"() %[[CRZ4_T]] ctrls(%[[CRZ4_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CSD_T:.*]], %[[CSD_C:.*]] = quantum.custom "S"() %[[CS_T]] adj ctrls(%[[CS_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Controlled Peres gate ------------------------------------------------------------------- - // CHECK: %[[CPERES_CNOT:.*]]:2, %[[CPERES_CTRL:.*]] = quantum.custom "CNOT"() %[[CSD_T]], %[[CSD_C]] ctrls(%[[Q2]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CPERES_X:.*]], %[[CPERES_X_CTRL:.*]] = quantum.custom "PauliX"() %[[CPERES_CNOT]]#0 ctrls(%[[CPERES_CTRL]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // --- Controlled Peresdg gate ------------------------------------------------------------------- - // CHECK: %[[CPERESDG_X:.*]], %[[CPERESDG_X_CTRL:.*]] = quantum.custom "PauliX"() %[[CPERES_X]] ctrls(%[[CPERES_X_CTRL]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CPERESDG_CNOT:.*]]:2, %[[CPERESDG_CTRL:.*]] = quantum.custom "CNOT"() %[[CPERESDG_X]], %[[CPERES_CNOT]]#1 ctrls(%[[CPERESDG_X_CTRL]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C0_FINAL]]], %[[CPERESDG_CNOT]]#0 : !quantum.reg, !quantum.bit - // CHECK: %[[C1_FINAL:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C1_FINAL]]], %[[CPERESDG_CNOT]]#1 : !quantum.reg, !quantum.bit - // CHECK: %[[C2_FINAL:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C2_FINAL]]], %[[CPERESDG_CTRL]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg - - // Prepare qubits - %i0 = arith.constant 0 : index - %i1 = arith.constant 1 : index - %i2 = arith.constant 2 : index - %r0_0 = memref.alloc() : memref<3x!mqtopt.Qubit> - %q0_0 = memref.load %r0_0[%i0] : memref<3x!mqtopt.Qubit> - %q1_0 = memref.load %r0_0[%i1] : memref<3x!mqtopt.Qubit> - %q2_0 = memref.load %r0_0[%i2] : memref<3x!mqtopt.Qubit> - - // I/H/V/Vdg/S/Sdg/T/Tdg/Peres/Peresdg (non-controlled) - %q0_1 = mqtopt.i() %q0_0 : !mqtopt.Qubit - %q0_2 = mqtopt.h() %q0_1 : !mqtopt.Qubit - %q0_3 = mqtopt.v() %q0_2 : !mqtopt.Qubit - %q0_4 = mqtopt.vdg() %q0_3 : !mqtopt.Qubit - %q0_5 = mqtopt.s() %q0_4 : !mqtopt.Qubit - %q0_6 = mqtopt.sdg() %q0_5 : !mqtopt.Qubit - %q0_7 = mqtopt.t() %q0_6 : !mqtopt.Qubit - %q0_8 = mqtopt.tdg() %q0_7 : !mqtopt.Qubit - %q0_9, %q1_1 = mqtopt.peres() %q0_8, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_10, %q1_2 = mqtopt.peresdg() %q0_9, %q1_1 : !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled H/V/Vdg/S/Sdg/T/Tdg - %q0_11, %q1_3 = mqtopt.h() %q0_10 ctrl %q1_2 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_12, %q1_4 = mqtopt.v() %q0_11 ctrl %q1_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_13, %q1_5 = mqtopt.vdg() %q0_12 ctrl %q1_4 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_14, %q1_6 = mqtopt.s() %q0_13 ctrl %q1_5 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_15, %q1_7 = mqtopt.sdg() %q0_14 ctrl %q1_6 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_16, %q1_8, %q2_1 = mqtopt.peres() %q0_15, %q1_7 ctrl %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_17, %q1_9, %q2_2 = mqtopt.peresdg() %q0_16, %q1_8 ctrl %q2_1 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Release qubits - memref.store %q0_17, %r0_0[%i0] : memref<3x!mqtopt.Qubit> - memref.store %q1_9, %r0_0[%i1] : memref<3x!mqtopt.Qubit> - memref.store %q2_2, %r0_0[%i2] : memref<3x!mqtopt.Qubit> - memref.dealloc %r0_0 : memref<3x!mqtopt.Qubit> - return - } -} diff --git a/test/Conversion/mqtopt_entangling.mlir b/test/Conversion/mqtopt_entangling.mlir deleted file mode 100644 index 954f805..0000000 --- a/test/Conversion/mqtopt_entangling.mlir +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Entangling gates (SWAP, ISWAP, ECR, DCX) and controlled variants -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testMQTOptToCatalystQuantumEntanglingGates - func.func @testMQTOptToCatalystQuantumEntanglingGates() { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[QREG:.*]] = quantum.alloc( 3) : !quantum.reg - // CHECK: %[[IDX0:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[IDX0]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX1:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[IDX1]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX2:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[IDX2]]] : !quantum.reg -> !quantum.bit - - // --- Uncontrolled ------------------------------------------------------------------------- - // CHECK: %[[SW0:.*]]:2 = quantum.custom "SWAP"() %[[Q0]], %[[Q1]] : !quantum.bit, !quantum.bit - // CHECK: %[[IS0:.*]]:2 = quantum.custom "ISWAP"() %[[SW0]]#0, %[[SW0]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[ISD0:.*]]:2 = quantum.custom "ISWAP"() %[[IS0]]#0, %[[IS0]]#1 adj : !quantum.bit, !quantum.bit - // CHECK: %[[ECR0:.*]]:2 = quantum.custom "ECR"() %[[ISD0]]#0, %[[ISD0]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[DCX0:.*]]:2 = quantum.custom "CNOT"() %[[ECR0]]#0, %[[ECR0]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[DCX1:.*]]:2 = quantum.custom "CNOT"() %[[DCX0]]#1, %[[DCX0]]#0 : !quantum.bit, !quantum.bit - - // --- Controlled ---------------------------------------------------------------------------- - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[CSW_T:.*]]:2, %[[CSW_C:.*]] = quantum.custom "CSWAP"() %[[DCX1]]#0, %[[DCX1]]#1 ctrls(%[[Q2]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CISW_T:.*]]:2, %[[CISW_C:.*]] = quantum.custom "ISWAP"() %[[CSW_T]]#0, %[[CSW_T]]#1 ctrls(%[[CSW_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CISWD_T:.*]]:2, %[[CISWD_C:.*]] = quantum.custom "ISWAP"() %[[CISW_T]]#0, %[[CISW_T]]#1 adj ctrls(%[[CISW_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CECR_T:.*]]:2, %[[CECR_C:.*]] = quantum.custom "ECR"() %[[CISWD_T]]#0, %[[CISWD_T]]#1 ctrls(%[[CISWD_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CDCX1_T:.*]]:2, %[[CDCX1_C:.*]] = quantum.custom "CNOT"() %[[CECR_T]]#0, %[[CECR_T]]#1 ctrls(%[[CECR_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[CDCX2_T:.*]]:2, %[[CDCX2_C:.*]] = quantum.custom "CNOT"() %[[CDCX1_T]]#1, %[[CDCX1_T]]#0 ctrls(%[[CDCX1_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C0_FINAL]]], %[[CDCX2_T]]#0 : !quantum.reg, !quantum.bit - // CHECK: %[[C1_FINAL:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C1_FINAL]]], %[[CDCX2_T]]#1 : !quantum.reg, !quantum.bit - // CHECK: %[[C2_FINAL:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C2_FINAL]]], %[[CDCX2_C]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg - - // Prepare qubits - %i0 = arith.constant 0 : index - %i1 = arith.constant 1 : index - %i2 = arith.constant 2 : index - %r0_0 = memref.alloc() : memref<3x!mqtopt.Qubit> - %q0_0 = memref.load %r0_0[%i0] : memref<3x!mqtopt.Qubit> - %q1_0 = memref.load %r0_0[%i1] : memref<3x!mqtopt.Qubit> - %q2_0 = memref.load %r0_0[%i2] : memref<3x!mqtopt.Qubit> - - // Uncontrolled - %q0_1, %q1_1 = mqtopt.swap() %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_2, %q1_2 = mqtopt.iswap() %q0_1, %q1_1 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_3, %q1_3 = mqtopt.iswapdg() %q0_2, %q1_2 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_4, %q1_4 = mqtopt.ecr() %q0_3, %q1_3 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_5, %q1_5 = mqtopt.dcx() %q0_4, %q1_4 : !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled - %q0_6, %q1_6, %q2_1 = mqtopt.swap() %q0_5, %q1_5 ctrl %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_7, %q1_7, %q2_2 = mqtopt.iswap() %q0_6, %q1_6 ctrl %q2_1 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_8, %q1_8, %q2_3 = mqtopt.iswapdg() %q0_7, %q1_7 ctrl %q2_2 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_9, %q1_9, %q2_4 = mqtopt.ecr() %q0_8, %q1_8 ctrl %q2_3 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_10, %q1_10, %q2_5 = mqtopt.dcx() %q0_9, %q1_9 ctrl %q2_4 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Release qubits - memref.store %q0_10, %r0_0[%i0] : memref<3x!mqtopt.Qubit> - memref.store %q1_10, %r0_0[%i1] : memref<3x!mqtopt.Qubit> - memref.store %q2_5, %r0_0[%i2] : memref<3x!mqtopt.Qubit> - memref.dealloc %r0_0 : memref<3x!mqtopt.Qubit> - return - } -} diff --git a/test/Conversion/mqtopt_ising.mlir b/test/Conversion/mqtopt_ising.mlir deleted file mode 100644 index e075dbe..0000000 --- a/test/Conversion/mqtopt_ising.mlir +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Ising-type gates and controlled variants -// Tests both static constants and dynamic parameters -// Groups: Allocation & extraction / Static params / Dynamic params / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testMQTOptToCatalystQuantumIsingGates - func.func @testMQTOptToCatalystQuantumIsingGates(%theta : f64, %beta : f64) { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[QREG:.*]] = quantum.alloc( 3) : !quantum.reg - // CHECK: %[[IDX0:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[IDX0]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX1:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[IDX1]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX2:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[IDX2]]] : !quantum.reg -> !quantum.bit - - // --- Static parameters ----------------------------------------------- - // CHECK: %[[CST:.*]] = arith.constant {{.*}} : f64 - // CHECK: %[[RZ0:.*]] = quantum.custom "RZ"(%{{.*}}) %[[Q1]] : !quantum.bit - // CHECK: %[[XY_P:.*]]:2 = quantum.custom "IsingXY"(%[[CST]]) %[[Q0]], %[[RZ0]] : !quantum.bit, !quantum.bit - // CHECK: %[[RZ1:.*]] = quantum.custom "RZ"(%{{.*}}) %[[XY_P]]#1 : !quantum.bit - - // CHECK: %[[X1:.*]] = quantum.custom "PauliX"() %[[XY_P]]#0 : !quantum.bit - // CHECK: %[[RZ2:.*]] = quantum.custom "RZ"(%{{.*}}) %[[RZ1]] : !quantum.bit - // CHECK: %[[XY_M:.*]]:2 = quantum.custom "IsingXY"(%[[CST]]) %[[X1]], %[[RZ2]] : !quantum.bit, !quantum.bit - // CHECK: %[[RZ3:.*]] = quantum.custom "RZ"(%{{.*}}) %[[XY_M]]#1 : !quantum.bit - // CHECK: %[[X2:.*]] = quantum.custom "PauliX"() %[[XY_M]]#0 : !quantum.bit - - // CHECK: %[[XX_P:.*]]:2 = quantum.custom "IsingXX"(%[[CST]]) %[[X2]], %[[RZ3]] : !quantum.bit, !quantum.bit - // CHECK: %[[YY_P:.*]]:2 = quantum.custom "IsingYY"(%[[CST]]) %[[XX_P]]#0, %[[XX_P]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[ZZ_P1:.*]]:2 = quantum.custom "IsingZZ"(%[[CST]]) %[[YY_P]]#0, %[[YY_P]]#1 : !quantum.bit, !quantum.bit - - // CHECK: %[[H1U:.*]] = quantum.custom "Hadamard"() %[[ZZ_P1]]#1 : !quantum.bit - // CHECK: %[[ZZ_P2:.*]]:2 = quantum.custom "IsingZZ"(%[[CST]]) %[[ZZ_P1]]#0, %[[H1U]] : !quantum.bit, !quantum.bit - // CHECK: %[[H2U:.*]] = quantum.custom "Hadamard"() %[[ZZ_P2]]#1 : !quantum.bit - - // --- Controlled --------------------------------------------------------------------- - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[RZ_C0:.*]], %[[CTRL1A:.*]] = quantum.custom "RZ"(%{{.*}}) %[[H2U]] ctrls(%[[Q2]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[XY_C:.*]]:2, %[[CTRL1B:.*]] = quantum.custom "IsingXY"(%[[CST]]) %[[ZZ_P2]]#0, %[[RZ_C0]] ctrls(%[[CTRL1A]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[RZ_C1:.*]], %[[CTRL1:.*]] = quantum.custom "RZ"(%{{.*}}) %[[XY_C]]#1 ctrls(%[[CTRL1B]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // CHECK: %[[X1C:.*]], %[[CTRL2A:.*]] = quantum.custom "PauliX"() %[[XY_C]]#0 ctrls(%[[CTRL1]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[RZ_C2:.*]], %[[CTRL2B:.*]] = quantum.custom "RZ"(%{{.*}}) %[[RZ_C1]] ctrls(%[[CTRL2A]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[XY_CM:.*]]:2, %[[CTRL2C:.*]] = quantum.custom "IsingXY"(%[[CST]]) %[[X1C]], %[[RZ_C2]] ctrls(%[[CTRL2B]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[RZ_C3:.*]], %[[CTRL2D:.*]] = quantum.custom "RZ"(%{{.*}}) %[[XY_CM]]#1 ctrls(%[[CTRL2C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[X2C:.*]], %[[CTRL2:.*]] = quantum.custom "PauliX"() %[[XY_CM]]#0 ctrls(%[[CTRL2D]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // CHECK: %[[XX_C:.*]]:2, %[[CTRL3:.*]] = quantum.custom "IsingXX"(%[[CST]]) %[[X2C]], %[[RZ_C3]] ctrls(%[[CTRL2]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[YY_C:.*]]:2, %[[CTRL4:.*]] = quantum.custom "IsingYY"(%[[CST]]) %[[XX_C]]#0, %[[XX_C]]#1 ctrls(%[[CTRL3]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[ZZ_C1:.*]]:2, %[[CTRL5:.*]] = quantum.custom "IsingZZ"(%[[CST]]) %[[YY_C]]#0, %[[YY_C]]#1 ctrls(%[[CTRL4]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - - // CHECK: %[[H1C:.*]] = quantum.custom "Hadamard"() %[[ZZ_C1]]#1 : !quantum.bit - // CHECK: %[[ZZ_C2:.*]]:2, %[[CTRL7:.*]] = quantum.custom "IsingZZ"(%[[CST]]) %[[ZZ_C1]]#0, %[[H1C]] ctrls(%[[CTRL5]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[H2C:.*]] = quantum.custom "Hadamard"() %[[ZZ_C2]]#1 : !quantum.bit - - // --- Dynamic parameters (runtime values) ----------------------------------------------- - // CHECK: %[[DXX:.*]]:2 = quantum.custom "IsingXX"(%arg0) %[[ZZ_C2]]#0, %[[H2C]] : !quantum.bit, !quantum.bit - // CHECK: %[[DYY:.*]]:2 = quantum.custom "IsingYY"(%arg0) %[[DXX]]#0, %[[DXX]]#1 : !quantum.bit, !quantum.bit - // CHECK: %[[DZZ:.*]]:2 = quantum.custom "IsingZZ"(%arg0) %[[DYY]]#0, %[[DYY]]#1 : !quantum.bit, !quantum.bit - // CHECK-DAG: %[[RZ_D1:.*]] = quantum.custom "RZ"({{.*}}) %[[DZZ]]#1 : !quantum.bit - // CHECK: %[[DXY:.*]]:2 = quantum.custom "IsingXY"(%arg0) %[[DZZ]]#0, %[[RZ_D1]] : !quantum.bit, !quantum.bit - // CHECK-DAG: %[[RZ_D2:.*]] = quantum.custom "RZ"({{.*}}) %[[DXY]]#1 : !quantum.bit - - // --- Controlled with dynamic parameters ------------------------------------------------ - // CHECK: %[[DCXX:.*]]:2, %[[CTRL8:.*]] = quantum.custom "IsingXX"(%arg0) %[[DXY]]#0, %[[RZ_D2]] ctrls(%[[CTRL7]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCYY:.*]]:2, %[[CTRL9:.*]] = quantum.custom "IsingYY"(%arg0) %[[DCXX]]#0, %[[DCXX]]#1 ctrls(%[[CTRL8]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCZZ:.*]]:2, %[[CTRL10:.*]] = quantum.custom "IsingZZ"(%arg0) %[[DCYY]]#0, %[[DCYY]]#1 ctrls(%[[CTRL9]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK-DAG: %[[RZ_DC1:.*]], %[[CTRL11:.*]] = quantum.custom "RZ"({{.*}}) %[[DCZZ]]#1 ctrls(%[[CTRL10]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCXY:.*]]:2, %[[CTRL12:.*]] = quantum.custom "IsingXY"(%arg0) %[[DCZZ]]#0, %[[RZ_DC1]] ctrls(%[[CTRL11]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit, !quantum.bit ctrls !quantum.bit - // CHECK-DAG: %[[RZ_DC2:.*]], %[[CTRL_FINAL:.*]] = quantum.custom "RZ"({{.*}}) %[[DCXY]]#1 ctrls(%[[CTRL12]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[IDX0_FINAL:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[IDX0_FINAL]]], %[[DCXY]]#0 : !quantum.reg, !quantum.bit - // CHECK: %[[IDX1_FINAL:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[IDX1_FINAL]]], %[[RZ_DC2]] : !quantum.reg, !quantum.bit - // CHECK: %[[IDX2_FINAL:.*]] = arith.index_cast %[[C2]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[IDX2_FINAL]]], %[[CTRL_FINAL]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg - - // Prepare qubits - %i0 = arith.constant 0 : index - %i1 = arith.constant 1 : index - %i2 = arith.constant 2 : index - %r0_0 = memref.alloc() : memref<3x!mqtopt.Qubit> - %q0_0 = memref.load %r0_0[%i0] : memref<3x!mqtopt.Qubit> - %q1_0 = memref.load %r0_0[%i1] : memref<3x!mqtopt.Qubit> - %q2_0 = memref.load %r0_0[%i2] : memref<3x!mqtopt.Qubit> - - // Uncontrolled - %cst = arith.constant 3.000000e-01 : f64 - %q0_1, %q1_1 = mqtopt.xx_plus_yy(%cst, %cst) %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_2, %q1_2 = mqtopt.xx_minus_yy(%cst, %cst) %q0_1, %q1_1 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_3, %q1_3 = mqtopt.rxx(%cst) %q0_2, %q1_2 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_4, %q1_4 = mqtopt.ryy(%cst) %q0_3, %q1_3 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_5, %q1_5 = mqtopt.rzz(%cst) %q0_4, %q1_4 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_6, %q1_6 = mqtopt.rzx(%cst) %q0_5, %q1_5 : !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled with static parameters - %q0_7, %q1_7, %q2_1 = mqtopt.xx_plus_yy(%cst, %cst) %q0_6, %q1_6 ctrl %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_8, %q1_8, %q2_2 = mqtopt.xx_minus_yy(%cst, %cst) %q0_7, %q1_7 ctrl %q2_1 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_9, %q1_9, %q2_3 = mqtopt.rxx(%cst) %q0_8, %q1_8 ctrl %q2_2 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_10, %q1_10, %q2_4 = mqtopt.ryy(%cst) %q0_9, %q1_9 ctrl %q2_3 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_11, %q1_11, %q2_5 = mqtopt.rzz(%cst) %q0_10, %q1_10 ctrl %q2_4 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_12, %q1_12, %q2_6 = mqtopt.rzx(%cst) %q0_11, %q1_11 ctrl %q2_5 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Dynamic parameter rotations - %q0_13, %q1_13 = mqtopt.rxx(%theta) %q0_12, %q1_12 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_14, %q1_14 = mqtopt.ryy(%theta) %q0_13, %q1_13 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_15, %q1_15 = mqtopt.rzz(%theta) %q0_14, %q1_14 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_16, %q1_16 = mqtopt.xx_plus_yy(%theta, %beta) %q0_15, %q1_15 : !mqtopt.Qubit, !mqtopt.Qubit - - // Controlled with dynamic parameters - %q0_17, %q1_17, %q2_7 = mqtopt.rxx(%theta) %q0_16, %q1_16 ctrl %q2_6 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_18, %q1_18, %q2_8 = mqtopt.ryy(%theta) %q0_17, %q1_17 ctrl %q2_7 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_19, %q1_19, %q2_9 = mqtopt.rzz(%theta) %q0_18, %q1_18 ctrl %q2_8 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_20, %q1_20, %q2_10 = mqtopt.xx_plus_yy(%theta, %beta) %q0_19, %q1_19 ctrl %q2_9 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Release qubits - memref.store %q0_20, %r0_0[%i0] : memref<3x!mqtopt.Qubit> - memref.store %q1_20, %r0_0[%i1] : memref<3x!mqtopt.Qubit> - memref.store %q2_10, %r0_0[%i2] : memref<3x!mqtopt.Qubit> - memref.dealloc %r0_0 : memref<3x!mqtopt.Qubit> - return - } -} diff --git a/test/Conversion/mqtopt_param.mlir b/test/Conversion/mqtopt_param.mlir deleted file mode 100644 index 2dd91ec..0000000 --- a/test/Conversion/mqtopt_param.mlir +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Parameterized gates RX/RY/RZ, PhaseShift and controlled variants -// Tests both static constants and dynamic parameters -// Groups: Allocation & extraction / Static params / Dynamic params / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testMQTOptToCatalystQuantumParameterizedGates - func.func @testMQTOptToCatalystQuantumParameterizedGates(%phi : f64) { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[THETA:.*]] = arith.constant 3.000000e-01 : f64 - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[QREG:.*]] = quantum.alloc( 2) : !quantum.reg - // CHECK: %[[IDX0:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[IDX0]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[IDX1:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[IDX1]]] : !quantum.reg -> !quantum.bit - - // --- Static parameters --------------------------------------------------- - // CHECK: %[[RX:.*]] = quantum.custom "RX"(%[[THETA]]) %[[Q0]] : !quantum.bit - // CHECK: %[[RY:.*]] = quantum.custom "RY"(%[[THETA]]) %[[RX]] : !quantum.bit - // CHECK: %[[RZ:.*]] = quantum.custom "RZ"(%[[THETA]]) %[[RY]] : !quantum.bit - // CHECK: %[[PS:.*]] = quantum.custom "PhaseShift"(%[[THETA]]) %[[RZ]] : !quantum.bit - // CHECK: quantum.gphase(%[[THETA]]) : - - // --- Dynamic parameters (runtime values) --------------------------------------------------- - // CHECK: %[[DRX:.*]] = quantum.custom "RX"(%arg0) %[[PS]] : !quantum.bit - // CHECK: %[[DRY:.*]] = quantum.custom "RY"(%arg0) %[[DRX]] : !quantum.bit - // CHECK: %[[DRZ:.*]] = quantum.custom "RZ"(%arg0) %[[DRY]] : !quantum.bit - // CHECK: %[[DPS:.*]] = quantum.custom "PhaseShift"(%arg0) %[[DRZ]] : !quantum.bit - // CHECK: quantum.gphase(%arg0) : - - // --- Controlled with static parameters ----------------------------------------------------- - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[CRX_T:.*]], %[[CRX_C:.*]] = quantum.custom "CRX"(%[[THETA]]) %[[DPS]] ctrls(%[[Q1]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRY_T:.*]], %[[CRY_C:.*]] = quantum.custom "CRY"(%[[THETA]]) %[[CRX_T]] ctrls(%[[CRX_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CRZ_T:.*]], %[[CRZ_C:.*]] = quantum.custom "CRZ"(%[[THETA]]) %[[CRY_T]] ctrls(%[[CRY_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CPS_T:.*]], %[[CPS_C:.*]] = quantum.custom "ControlledPhaseShift"(%[[THETA]]) %[[CRZ_T]] ctrls(%[[CRZ_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Controlled with dynamic parameters ---------------------------------------------------- - // CHECK: %[[DCRX_T:.*]], %[[DCRX_C:.*]] = quantum.custom "CRX"(%arg0) %[[CPS_T]] ctrls(%[[CPS_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCRY_T:.*]], %[[DCRY_C:.*]] = quantum.custom "CRY"(%arg0) %[[DCRX_T]] ctrls(%[[DCRX_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCRZ_T:.*]], %[[DCRZ_C:.*]] = quantum.custom "CRZ"(%arg0) %[[DCRY_T]] ctrls(%[[DCRY_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[DCPS_T:.*]], %[[DCPS_C:.*]] = quantum.custom "ControlledPhaseShift"(%arg0) %[[DCRZ_T]] ctrls(%[[DCRZ_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C0_FINAL]]], %[[DCPS_T]] : !quantum.reg, !quantum.bit - // CHECK: %[[C1_FINAL:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C1_FINAL]]], %[[DCPS_C]] : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg - - // Prepare qubits - %cst = arith.constant 3.000000e-01 : f64 - %i0 = arith.constant 0 : index - %i1 = arith.constant 1 : index - %r0_0 = memref.alloc() : memref<2x!mqtopt.Qubit> - %q0_0 = memref.load %r0_0[%i0] : memref<2x!mqtopt.Qubit> - %q1_0 = memref.load %r0_0[%i1] : memref<2x!mqtopt.Qubit> - - // Static parameter rotations (constant) - %q0_1 = mqtopt.rx(%cst) %q0_0 : !mqtopt.Qubit - %q0_2 = mqtopt.ry(%cst) %q0_1 : !mqtopt.Qubit - %q0_3 = mqtopt.rz(%cst) %q0_2 : !mqtopt.Qubit - %q0_4 = mqtopt.p(%cst) %q0_3 : !mqtopt.Qubit - mqtopt.gphase(%cst) : () - - // Dynamic parameter rotations (runtime) - %q0_5 = mqtopt.rx(%phi) %q0_4 : !mqtopt.Qubit - %q0_6 = mqtopt.ry(%phi) %q0_5 : !mqtopt.Qubit - %q0_7 = mqtopt.rz(%phi) %q0_6 : !mqtopt.Qubit - %q0_8 = mqtopt.p(%phi) %q0_7 : !mqtopt.Qubit - mqtopt.gphase(%phi) : () - - // Controlled rotations with static parameters - %q0_9, %q1_1 = mqtopt.rx(%cst) %q0_8 ctrl %q1_0 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_10, %q1_2 = mqtopt.ry(%cst) %q0_9 ctrl %q1_1 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_11, %q1_3 = mqtopt.rz(%cst) %q0_10 ctrl %q1_2 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_12, %q1_4 = mqtopt.p(%cst) %q0_11 ctrl %q1_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Controlled rotations with dynamic parameters - %q0_13, %q1_5 = mqtopt.rx(%phi) %q0_12 ctrl %q1_4 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_14, %q1_6 = mqtopt.ry(%phi) %q0_13 ctrl %q1_5 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_15, %q1_7 = mqtopt.rz(%phi) %q0_14 ctrl %q1_6 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_16, %q1_8 = mqtopt.p(%phi) %q0_15 ctrl %q1_7 : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // Release qubits - memref.store %q0_16, %r0_0[%i0] : memref<2x!mqtopt.Qubit> - memref.store %q1_8, %r0_0[%i1] : memref<2x!mqtopt.Qubit> - memref.dealloc %r0_0 : memref<2x!mqtopt.Qubit> - return - } -} diff --git a/test/Conversion/mqtopt_pauli.mlir b/test/Conversion/mqtopt_pauli.mlir deleted file mode 100644 index 3f04506..0000000 --- a/test/Conversion/mqtopt_pauli.mlir +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Pauli family (X, Y, Z) and controlled variants -// Tests both static and dynamic allocation/extraction -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testMQTOptToCatalystQuantumPauliGates - func.func @testMQTOptToCatalystQuantumPauliGates(%n : i64, %idx : i64) { - // --- Dynamic allocation & extraction ------------------------------------------------------- - // CHECK: %[[SIZE_IDX:.*]] = arith.index_cast %arg0 : i64 to index - // CHECK: %[[SIZE_I64:.*]] = arith.index_cast %[[SIZE_IDX]] : index to i64 - // CHECK: %[[QREG:.*]] = quantum.alloc(%[[SIZE_I64]]) : !quantum.reg - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[IDX0:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][%[[IDX0]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[IDX1:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][%[[IDX1]]] : !quantum.reg -> !quantum.bit - // CHECK: %[[ARG1_IDX:.*]] = arith.index_cast %arg1 : i64 to index - // CHECK: %[[ARG1_I64:.*]] = arith.index_cast %[[ARG1_IDX]] : index to i64 - // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][%[[ARG1_I64]]] : !quantum.reg -> !quantum.bit - - // --- Uncontrolled Pauli gates -------------------------------------------------------------- - // CHECK: %[[X:.*]] = quantum.custom "PauliX"() %[[Q0]] : !quantum.bit - // CHECK: %[[Y:.*]] = quantum.custom "PauliY"() %[[X]] : !quantum.bit - // CHECK: %[[Z:.*]] = quantum.custom "PauliZ"() %[[Y]] : !quantum.bit - // CHECK: %[[I:.*]] = quantum.custom "Identity"() %[[Z]] : !quantum.bit - - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[CNOT_T:.*]], %[[CNOT_C:.*]] = quantum.custom "CNOT"() %[[I]] ctrls(%[[Q1]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CY_T:.*]], %[[CY_C:.*]] = quantum.custom "CY"() %[[CNOT_T]] ctrls(%[[CNOT_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[CZ_T:.*]], %[[CZ_C:.*]] = quantum.custom "CZ"() %[[CY_T]] ctrls(%[[CY_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[I_T:.*]], %[[I_C:.*]] = quantum.custom "Identity"() %[[CZ_T]] ctrls(%[[CZ_C]]) ctrlvals(%[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit - // CHECK: %[[TOF_T:.*]], %[[TOF_C:.*]]:2 = quantum.custom "Toffoli"() %[[I_T]] ctrls(%[[I_C]], %[[Q2]]) ctrlvals(%[[TRUE]]{{.*}}, %[[TRUE]]{{.*}}) : !quantum.bit ctrls !quantum.bit, !quantum.bit - - // --- Reinsertion ---------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.index_cast %[[C0]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C0_FINAL]]], %[[TOF_T]] : !quantum.reg, !quantum.bit - // CHECK: %[[C1_FINAL:.*]] = arith.index_cast %[[C1]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[C1_FINAL]]], %[[TOF_C]]#0 : !quantum.reg, !quantum.bit - // CHECK: %[[ARG1_FINAL:.*]] = arith.index_cast %[[ARG1_IDX]] : index to i64 - // CHECK: quantum.insert %[[QREG]][%[[ARG1_FINAL]]], %[[TOF_C]]#1 : !quantum.reg, !quantum.bit - // CHECK: quantum.dealloc %[[QREG]] : !quantum.reg - - // Prepare qubits with dynamic allocation - %size_cast = arith.index_cast %n : i64 to index - %r0_0 = memref.alloc(%size_cast) : memref - %i0 = arith.constant 0 : index - %q0_0 = memref.load %r0_0[%i0] : memref - %i1 = arith.constant 1 : index - %q1_0 = memref.load %r0_0[%i1] : memref - %idx_cast = arith.index_cast %idx : i64 to index - %q2_0 = memref.load %r0_0[%idx_cast] : memref - - // Non-controlled Pauli gates - %q0_1 = mqtopt.x() %q0_0 : !mqtopt.Qubit - %q0_2 = mqtopt.y() %q0_1 : !mqtopt.Qubit - %q0_3 = mqtopt.z() %q0_2 : !mqtopt.Qubit - %q0_4 = mqtopt.i() %q0_3 : !mqtopt.Qubit - - // Controlled Pauli gates - %q0_5, %q1_1 = mqtopt.x() %q0_4 ctrl %q1_0 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_6, %q1_2 = mqtopt.y() %q0_5 ctrl %q1_1 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_7, %q1_3 = mqtopt.z() %q0_6 ctrl %q1_2 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_8, %q1_4 = mqtopt.i() %q0_7 ctrl %q1_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_9, %q1_5, %q2_1 = mqtopt.x() %q0_8 ctrl %q1_4, %q2_0 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Release qubits - memref.store %q0_9, %r0_0[%i0] : memref - memref.store %q1_5, %r0_0[%i1] : memref - memref.store %q2_1, %r0_0[%idx_cast] : memref - memref.dealloc %r0_0 : memref - return - } -} diff --git a/test/Conversion/quantum_clifford.mlir b/test/Conversion/quantum_clifford.mlir deleted file mode 100644 index 7991248..0000000 --- a/test/Conversion/quantum_clifford.mlir +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Clifford + T and controlled variants -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testCatalystQuantumToMQTOptCliffordT - func.func @testCatalystQuantumToMQTOptCliffordT() { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<2x!mqtopt.Qubit> - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[C0]]] : memref<2x!mqtopt.Qubit> - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[C1]]] : memref<2x!mqtopt.Qubit> - - // --- Uncontrolled Clifford+T gates --------------------------------------------------------- - // CHECK: %[[H:.*]] = mqtopt.h(static [] mask []) %[[Q0]] : !mqtopt.Qubit - // CHECK: %[[V:.*]] = mqtopt.sx(static [] mask []) %[[H]] : !mqtopt.Qubit - // CHECK: %[[VDG:.*]] = mqtopt.sx(static [] mask []) %[[V]] : !mqtopt.Qubit - // CHECK: %[[S:.*]] = mqtopt.s(static [] mask []) %[[VDG]] : !mqtopt.Qubit - // CHECK: %[[SDG:.*]] = mqtopt.s(static [] mask []) %[[S]] : !mqtopt.Qubit - // CHECK: %[[T:.*]] = mqtopt.t(static [] mask []) %[[SDG]] : !mqtopt.Qubit - // CHECK: %[[TDG:.*]] = mqtopt.t(static [] mask []) %[[T]] : !mqtopt.Qubit - - // --- Controlled Clifford+T gates ----------------------------------------------------------- - // CHECK: %[[CH_T:.*]], %[[CH_C:.*]] = mqtopt.h(static [] mask []) %[[TDG]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CV_T:.*]], %[[CV_C:.*]] = mqtopt.sx(static [] mask []) %[[CH_T]] ctrl %[[CH_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CVDG_T:.*]], %[[CVDG_C:.*]] = mqtopt.sx(static [] mask []) %[[CV_T]] ctrl %[[CV_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CS_T:.*]], %[[CS_C:.*]] = mqtopt.s(static [] mask []) %[[CVDG_T]] ctrl %[[CVDG_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CSDG_T:.*]], %[[CSDG_C:.*]] = mqtopt.s(static [] mask []) %[[CS_T]] ctrl %[[CS_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CT_T:.*]], %[[CT_C:.*]] = mqtopt.t(static [] mask []) %[[CSDG_T]] ctrl %[[CSDG_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CTDG_T:.*]], %[[CTDG_C:.*]] = mqtopt.t(static [] mask []) %[[CT_T]] ctrl %[[CT_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[CTDG_T]], %[[ALLOC]][%[[C0_FINAL]]] : memref<2x!mqtopt.Qubit> - // CHECK: %[[C1_FINAL:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[CTDG_C]], %[[ALLOC]][%[[C1_FINAL]]] : memref<2x!mqtopt.Qubit> - // CHECK: memref.dealloc %[[ALLOC]] : memref<2x!mqtopt.Qubit> - - // Prepare qubits - %qreg = quantum.alloc(2) : !quantum.reg - %q0 = quantum.extract %qreg[0] : !quantum.reg -> !quantum.bit - %q1 = quantum.extract %qreg[1] : !quantum.reg -> !quantum.bit - - // Non-controlled Clifford+T gates - %q0_h = quantum.custom "Hadamard"() %q0 : !quantum.bit - %q0_v = quantum.custom "SX"() %q0_h : !quantum.bit - %q0_vdg = quantum.custom "SX"() %q0_v {adjoint} : !quantum.bit - %q0_s = quantum.custom "S"() %q0_vdg : !quantum.bit - %q0_sdg = quantum.custom "S"() %q0_s {adjoint} : !quantum.bit - %q0_t = quantum.custom "T"() %q0_sdg : !quantum.bit - %q0_tdg = quantum.custom "T"() %q0_t {adjoint} : !quantum.bit - - // Controlled Clifford+T gates - %true = arith.constant true - %q0_ch, %q1_ch = quantum.custom "Hadamard"() %q0_tdg ctrls(%q1) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_cv, %q1_cv = quantum.custom "SX"() %q0_ch ctrls(%q1_ch) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_cvdg, %q1_cvdg = quantum.custom "SX"() %q0_cv {adjoint} ctrls(%q1_cv) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_cs, %q1_cs = quantum.custom "S"() %q0_cvdg ctrls(%q1_cvdg) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_csdg, %q1_csdg = quantum.custom "S"() %q0_cs {adjoint} ctrls(%q1_cs) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_ct, %q1_ct = quantum.custom "T"() %q0_csdg ctrls(%q1_csdg) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_ctdg, %q1_ctdg = quantum.custom "T"() %q0_ct {adjoint} ctrls(%q1_ct) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - - // Release qubits - %qreg1 = quantum.insert %qreg[0], %q0_ctdg : !quantum.reg, !quantum.bit - %qreg2 = quantum.insert %qreg1[1], %q1_ctdg : !quantum.reg, !quantum.bit - quantum.dealloc %qreg2 : !quantum.reg - return - } -} diff --git a/test/Conversion/quantum_entangling.mlir b/test/Conversion/quantum_entangling.mlir deleted file mode 100644 index cea3361..0000000 --- a/test/Conversion/quantum_entangling.mlir +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Entangling gates (SWAP, ISWAP, ECR) and controlled variants -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testCatalystQuantumToMQTOptEntanglingGates - func.func @testCatalystQuantumToMQTOptEntanglingGates() { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<3x!mqtopt.Qubit> - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[C0]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[C1]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[Q2:.*]] = memref.load %[[ALLOC]][%[[C2]]] : memref<3x!mqtopt.Qubit> - - // --- Uncontrolled entangling gates --------------------------------------------------------- - // CHECK: %[[SW:.*]]:2 = mqtopt.swap(static [] mask []) %[[Q0]], %[[Q1]] : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[IS:.*]]:2 = mqtopt.iswap(static [] mask []) %[[SW]]#0, %[[SW]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[ISD:.*]]:2 = mqtopt.iswapdg(static [] mask []) %[[IS]]#0, %[[IS]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[ECR:.*]]:2 = mqtopt.ecr(static [] mask []) %[[ISD]]#0, %[[ISD]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - - // --- Controlled entangling gates ----------------------------------------------------------- - // CHECK: %[[CSW_T:.*]]:2, %[[CSW_C:.*]] = mqtopt.swap(static [] mask []) %[[ECR]]#0, %[[ECR]]#1 ctrl %[[Q2]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CISW_T:.*]]:2, %[[CISW_C:.*]] = mqtopt.iswap(static [] mask []) %[[CSW_T]]#0, %[[CSW_T]]#1 ctrl %[[CSW_C]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CISWD_T:.*]]:2, %[[CISWD_C:.*]] = mqtopt.iswapdg(static [] mask []) %[[CISW_T]]#0, %[[CISW_T]]#1 ctrl %[[CISW_C]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CECR_T:.*]]:2, %[[CECR_C:.*]] = mqtopt.ecr(static [] mask []) %[[CISWD_T]]#0, %[[CISWD_T]]#1 ctrl %[[CISWD_C]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[CECR_T]]#0, %[[ALLOC]][%[[C0_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C1_FINAL:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[CECR_T]]#1, %[[ALLOC]][%[[C1_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C2_FINAL:.*]] = arith.constant 2 : index - // CHECK: memref.store %[[CECR_C]], %[[ALLOC]][%[[C2_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: memref.dealloc %[[ALLOC]] : memref<3x!mqtopt.Qubit> - - // Prepare qubits - %qreg = quantum.alloc( 3) : !quantum.reg - %q0 = quantum.extract %qreg[ 0] : !quantum.reg -> !quantum.bit - %q1 = quantum.extract %qreg[ 1] : !quantum.reg -> !quantum.bit - %q2 = quantum.extract %qreg[ 2] : !quantum.reg -> !quantum.bit - - // Uncontrolled permutation gates - %q0_sw, %q1_sw = quantum.custom "SWAP"() %q0, %q1 : !quantum.bit, !quantum.bit - %q0_is, %q1_is = quantum.custom "ISWAP"() %q0_sw, %q1_sw : !quantum.bit, !quantum.bit - %q0_isd, %q1_isd = quantum.custom "ISWAP"() %q0_is, %q1_is adj : !quantum.bit, !quantum.bit - %q0_ecr, %q1_ecr = quantum.custom "ECR"() %q0_isd, %q1_isd : !quantum.bit, !quantum.bit - - // Controlled permutation gates - %true = arith.constant true - %q0_csw, %q1_csw, %q2_csw = quantum.custom "SWAP"() %q0_ecr, %q1_ecr ctrls(%q2) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_cis, %q1_cis, %q2_cis = quantum.custom "ISWAP"() %q0_csw, %q1_csw ctrls(%q2_csw) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_cisd, %q1_cisd, %q2_cisd = quantum.custom "ISWAP"() %q0_cis, %q1_cis adj ctrls(%q2_cis) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_cecr, %q1_cecr, %q2_cecr = quantum.custom "ECR"() %q0_cisd, %q1_cisd ctrls(%q2_cisd) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - - // Release qubits - %qreg1 = quantum.insert %qreg[ 0], %q0_cecr : !quantum.reg, !quantum.bit - %qreg2 = quantum.insert %qreg1[ 1], %q1_cecr : !quantum.reg, !quantum.bit - %qreg3 = quantum.insert %qreg2[ 2], %q2_cecr : !quantum.reg, !quantum.bit - quantum.dealloc %qreg3 : !quantum.reg - return - } -} diff --git a/test/Conversion/quantum_ising.mlir b/test/Conversion/quantum_ising.mlir deleted file mode 100644 index a5b6515..0000000 --- a/test/Conversion/quantum_ising.mlir +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Ising-type gates and controlled variants -// Tests both static and dynamic parameters in a single function -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testCatalystQuantumToMQTOptIsingGates - func.func @testCatalystQuantumToMQTOptIsingGates(%dynAngle : f64) { - // --- Allocation & extraction --------------------------------------------------------------- - // CHECK: %cst = arith.constant 3.000000e-01 : f64 - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<3x!mqtopt.Qubit> - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[C0]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[C1]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[Q2:.*]] = memref.load %[[ALLOC]][%[[C2]]] : memref<3x!mqtopt.Qubit> - - // --- Uncontrolled with static parameter (converted to dynamic, except pi) ------------------------------ - // CHECK: %[[XY:.*]]:2 = mqtopt.xx_plus_yy(%cst static [3.141593e+00] mask [false, true]) %[[Q0]], %[[Q1]] : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[XX:.*]]:2 = mqtopt.rxx(%cst static [] mask [false]) %[[XY]]#0, %[[XY]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[YY:.*]]:2 = mqtopt.ryy(%cst static [] mask [false]) %[[XX]]#0, %[[XX]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[ZZ:.*]]:2 = mqtopt.rzz(%cst static [] mask [false]) %[[YY]]#0, %[[YY]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - - // --- Uncontrolled with dynamic parameter --------------------------------------------------- - // CHECK: %[[XY2:.*]]:2 = mqtopt.xx_plus_yy(%arg0 static [3.141593e+00] mask [false, true]) %[[ZZ]]#0, %[[ZZ]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[XX2:.*]]:2 = mqtopt.rxx(%arg0 static [] mask [false]) %[[XY2]]#0, %[[XY2]]#1 : !mqtopt.Qubit, !mqtopt.Qubit - - // --- Controlled with dynamic parameter ----------------------------------------------------- - // CHECK: %[[CYY_T:.*]]:2, %[[CYY_C:.*]] = mqtopt.ryy(%arg0 static [] mask [false]) %[[XX2]]#0, %[[XX2]]#1 ctrl %[[Q2]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CZZ_T:.*]]:2, %[[CZZ_C:.*]] = mqtopt.rzz(%arg0 static [] mask [false]) %[[CYY_T]]#0, %[[CYY_T]]#1 ctrl %[[CYY_C]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[CZZ_T]]#0, %[[ALLOC]][%[[C0_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C1_FINAL:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[CZZ_T]]#1, %[[ALLOC]][%[[C1_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: %[[C2_FINAL:.*]] = arith.constant 2 : index - // CHECK: memref.store %[[CZZ_C]], %[[ALLOC]][%[[C2_FINAL]]] : memref<3x!mqtopt.Qubit> - // CHECK: memref.dealloc %[[ALLOC]] : memref<3x!mqtopt.Qubit> - - // Prepare qubits - %staticAngle = arith.constant 3.000000e-01 : f64 - %qreg = quantum.alloc( 3) : !quantum.reg - %q0 = quantum.extract %qreg[ 0] : !quantum.reg -> !quantum.bit - %q1 = quantum.extract %qreg[ 1] : !quantum.reg -> !quantum.bit - %q2 = quantum.extract %qreg[ 2] : !quantum.reg -> !quantum.bit - - // Uncontrolled Ising gates with static parameter - %q0_xy, %q1_xy = quantum.custom "IsingXY"(%staticAngle) %q0, %q1 : !quantum.bit, !quantum.bit - %q0_xx, %q1_xx = quantum.custom "IsingXX"(%staticAngle) %q0_xy, %q1_xy : !quantum.bit, !quantum.bit - %q0_yy, %q1_yy = quantum.custom "IsingYY"(%staticAngle) %q0_xx, %q1_xx : !quantum.bit, !quantum.bit - %q0_zz, %q1_zz = quantum.custom "IsingZZ"(%staticAngle) %q0_yy, %q1_yy : !quantum.bit, !quantum.bit - - // Uncontrolled Ising gates with dynamic parameter - %q0_xy2, %q1_xy2 = quantum.custom "IsingXY"(%dynAngle) %q0_zz, %q1_zz : !quantum.bit, !quantum.bit - %q0_xx2, %q1_xx2 = quantum.custom "IsingXX"(%dynAngle) %q0_xy2, %q1_xy2 : !quantum.bit, !quantum.bit - - // Controlled Ising gates with dynamic parameter - %true = arith.constant true - %q0_cyy, %q1_cyy, %q2_cyy = quantum.custom "IsingYY"(%dynAngle) %q0_xx2, %q1_xx2 ctrls(%q2) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_czz, %q1_czz, %q2_czz = quantum.custom "IsingZZ"(%dynAngle) %q0_cyy, %q1_cyy ctrls(%q2_cyy) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - - // Release qubits - %qreg1 = quantum.insert %qreg[ 0], %q0_czz : !quantum.reg, !quantum.bit - %qreg2 = quantum.insert %qreg1[ 1], %q1_czz : !quantum.reg, !quantum.bit - %qreg3 = quantum.insert %qreg2[ 2], %q2_czz : !quantum.reg, !quantum.bit - quantum.dealloc %qreg3 : !quantum.reg - return - } -} diff --git a/test/Conversion/quantum_param.mlir b/test/Conversion/quantum_param.mlir deleted file mode 100644 index 190623a..0000000 --- a/test/Conversion/quantum_param.mlir +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Parameterized gates RX/RY/RZ, PhaseShift and controlled variants -// Tests both static (compile-time constant) and dynamic (runtime) parameters -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testCatalystQuantumToMQTOptParameterizedGates - func.func @testCatalystQuantumToMQTOptParameterizedGates(%dynAngle : f64) { - // --- Static allocation & extraction --------------------------------------------------------------- - // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<2x!mqtopt.Qubit> - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[C0]]] : memref<2x!mqtopt.Qubit> - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[C1]]] : memref<2x!mqtopt.Qubit> - - // --- Uncontrolled with static parameter (converted to dynamic) --------------------------------------------------- - // CHECK: %[[RX:.*]] = mqtopt.rx(%cst static [] mask [false]) %[[Q0]] : !mqtopt.Qubit - // CHECK: %[[RY:.*]] = mqtopt.ry(%cst static [] mask [false]) %[[RX]] : !mqtopt.Qubit - // CHECK: %[[RZ:.*]] = mqtopt.rz(%cst static [] mask [false]) %[[RY]] : !mqtopt.Qubit - // CHECK: %[[PS:.*]] = mqtopt.p(%cst static [] mask [false]) %[[RZ]] : !mqtopt.Qubit - - // --- Controlled with static parameter (converted to dynamic) ------------------------------------------------------ - // CHECK: %[[CRX_T:.*]], %[[CRX_C:.*]] = mqtopt.rx(%cst static [] mask [false]) %[[PS]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[CRY_T:.*]], %[[CRY_C:.*]] = mqtopt.ry(%cst static [] mask [false]) %[[CRX_T]] ctrl %[[CRX_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Uncontrolled with dynamic parameter ------------------------------------------------------------------------- - // CHECK: %[[RX2:.*]] = mqtopt.rx(%arg0 static [] mask [false]) %[[CRY_T]] : !mqtopt.Qubit - // CHECK: %[[RY2:.*]] = mqtopt.ry(%arg0 static [] mask [false]) %[[RX2]] : !mqtopt.Qubit - - // --- Controlled with dynamic parameter ---------------------------------------------------------------------------- - // CHECK: %[[CRZ_T:.*]], %[[CRZ_C:.*]] = mqtopt.rz(%arg0 static [] mask [false]) %[[RY2]] ctrl %[[CRY_C]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Reinsertion --------------------------------------------------------------------------- - // CHECK: %[[C0_FINAL:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[CRZ_T]], %[[ALLOC]][%[[C0_FINAL]]] : memref<2x!mqtopt.Qubit> - // CHECK: %[[C1_FINAL:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[CRZ_C]], %[[ALLOC]][%[[C1_FINAL]]] : memref<2x!mqtopt.Qubit> - // CHECK: memref.dealloc %[[ALLOC]] : memref<2x!mqtopt.Qubit> - - // Prepare qubits - %staticAngle = arith.constant 3.000000e-01 : f64 - %qreg = quantum.alloc( 2) : !quantum.reg - %q0 = quantum.extract %qreg[ 0] : !quantum.reg -> !quantum.bit - %q1 = quantum.extract %qreg[ 1] : !quantum.reg -> !quantum.bit - - // Uncontrolled parameterized gates with static parameter - %q0_rx = quantum.custom "RX"(%staticAngle) %q0 : !quantum.bit - %q0_ry = quantum.custom "RY"(%staticAngle) %q0_rx : !quantum.bit - %q0_rz = quantum.custom "RZ"(%staticAngle) %q0_ry : !quantum.bit - %q0_p = quantum.custom "PhaseShift"(%staticAngle) %q0_rz : !quantum.bit - - // Controlled parameterized gates with static parameter - %true = arith.constant true - %q0_crx, %q1_crx = quantum.custom "RX"(%staticAngle) %q0_p ctrls(%q1) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - %q0_cry, %q1_cry = quantum.custom "RY"(%staticAngle) %q0_crx ctrls(%q1_crx) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - - // Uncontrolled parameterized gates with dynamic parameter - %q0_rx2 = quantum.custom "RX"(%dynAngle) %q0_cry : !quantum.bit - %q0_ry2 = quantum.custom "RY"(%dynAngle) %q0_rx2 : !quantum.bit - - // Controlled parameterized gates with dynamic parameter - %q0_crz, %q1_crz = quantum.custom "RZ"(%dynAngle) %q0_ry2 ctrls(%q1_cry) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - - // Release qubits - %qreg1 = quantum.insert %qreg[ 0], %q0_crz : !quantum.reg, !quantum.bit - %qreg2 = quantum.insert %qreg1[ 1], %q1_crz : !quantum.reg, !quantum.bit - quantum.dealloc %qreg2 : !quantum.reg - return - } -} diff --git a/test/Conversion/quantum_pauli.mlir b/test/Conversion/quantum_pauli.mlir deleted file mode 100644 index 588038b..0000000 --- a/test/Conversion/quantum_pauli.mlir +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -// RUN: catalyst --tool=opt \ -// RUN: --load-pass-plugin=%mqt_plugin_path% \ -// RUN: --load-dialect-plugin=%mqt_plugin_path% \ -// RUN: --catalyst-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ -// RUN: %s | FileCheck %s - - -// ============================================================================ -// Pauli family (X, Y, Z) and controlled variants -// Tests both static and dynamic allocation/extraction -// Groups: Allocation & extraction / Uncontrolled / Controlled / Reinsertion -// ============================================================================ -module { - // CHECK-LABEL: func.func @testCatalystQuantumToMQTOptPauliGates - func.func @testCatalystQuantumToMQTOptPauliGates(%n : i64, %idx : i64) { - // --- Dynamic allocation & extraction --------------------------------------------------------------- - // CHECK: %[[SIZE_CAST:.*]] = arith.index_cast %arg0 : i64 to index - // CHECK: %[[ALLOC:.*]] = memref.alloc(%[[SIZE_CAST]]) : memref - // CHECK: %[[C0:.*]] = arith.constant 0 : index - // CHECK: %[[Q0:.*]] = memref.load %[[ALLOC]][%[[C0]]] : memref - // CHECK: %[[C1:.*]] = arith.constant 1 : index - // CHECK: %[[Q1:.*]] = memref.load %[[ALLOC]][%[[C1]]] : memref - // CHECK: %[[C2:.*]] = arith.constant 2 : index - // CHECK: %[[Q2:.*]] = memref.load %[[ALLOC]][%[[C2]]] : memref - // CHECK: %[[IDX_CAST:.*]] = arith.index_cast %arg1 : i64 to index - // CHECK: %[[Q3:.*]] = memref.load %[[ALLOC]][%[[IDX_CAST]]] : memref - - // --- Uncontrolled Pauli gates -------------------------------------------------------------- - // CHECK: %[[X1:.*]] = mqtopt.x(static [] mask []) %[[Q0]] : !mqtopt.Qubit - // CHECK: %[[Y1:.*]] = mqtopt.y(static [] mask []) %[[X1]] : !mqtopt.Qubit - // CHECK: %[[Z1:.*]] = mqtopt.z(static [] mask []) %[[Y1]] : !mqtopt.Qubit - // CHECK: %[[I1:.*]] = mqtopt.i(static [] mask []) %[[Z1]] : !mqtopt.Qubit - - // --- Controlled Pauli gates ---------------------------------------------------------------- - // CHECK: %[[TRUE:.*]] = arith.constant true - // CHECK: %[[T0_1:.*]], %[[C1_0:.*]] = mqtopt.x(static [] mask []) %[[I1]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T0_2:.*]], %[[C1_1:.*]] = mqtopt.y(static [] mask []) %[[T0_1]] ctrl %[[C1_0]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T0_3:.*]], %[[C1_2:.*]] = mqtopt.z(static [] mask []) %[[T0_2]] ctrl %[[C1_1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T0_4:.*]], %[[C1_3:.*]] = mqtopt.i(static [] mask []) %[[T0_3]] ctrl %[[C1_2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Two-qubit controlled gates ------------------------------------------------------------ - // CHECK: %[[T0_5:.*]], %[[C1_4:.*]] = mqtopt.x(static [] mask []) %[[T0_4]] ctrl %[[C1_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T0_6:.*]], %[[C1_5:.*]] = mqtopt.y(static [] mask []) %[[T0_5]] ctrl %[[C1_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[T0_7:.*]], %[[C1_6:.*]] = mqtopt.z(static [] mask []) %[[T0_6]] ctrl %[[C1_5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - - // --- Toffoli (2 controls + 1 target) ------------------------------------------------------- - // CHECK: %[[T0_8:.*]], %[[C12_0:.*]]:2 = mqtopt.x(static [] mask []) %[[T0_7]] ctrl %[[C1_6]], %[[Q2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // --- Controlled two-qubit controlled gates: (q0 controlled by q1) controlled by q2 --------------------------------------------------------------------------- - // CHECK: %[[T0_9:.*]], %[[C12_1:.*]]:2 = mqtopt.x(static [] mask []) %[[T0_8]] ctrl %[[C12_0]]#0, %[[C12_0]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T0_10:.*]], %[[C12_2:.*]]:2 = mqtopt.y(static [] mask []) %[[T0_9]] ctrl %[[C12_1]]#0, %[[C12_1]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T0_11:.*]], %[[C12_3:.*]]:2 = mqtopt.z(static [] mask []) %[[T0_10]] ctrl %[[C12_2]]#0, %[[C12_2]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[T0_12:.*]], %[[C123:.*]]:3 = mqtopt.x(static [] mask []) %[[T0_11]] ctrl %[[C12_3]]#0, %[[C12_3]]#1, %[[Q3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - // Release qubits - // CHECK: %[[IDX_CAST_FINAL:.*]] = arith.index_cast %arg1 : i64 to index - // CHECK: memref.store %[[C123]]#2, %[[ALLOC]][%[[IDX_CAST_FINAL]]] : memref - // CHECK: %[[C_2:.*]] = arith.constant 2 : index - // CHECK: memref.store %[[C123]]#1, %[[ALLOC]][%[[C_2]]] : memref - // CHECK: %[[C_1:.*]] = arith.constant 1 : index - // CHECK: memref.store %[[C123]]#0, %[[ALLOC]][%[[C_1]]] : memref - // CHECK: %[[C_0:.*]] = arith.constant 0 : index - // CHECK: memref.store %[[T0_12]], %[[ALLOC]][%[[C_0]]] : memref - // CHECK: memref.dealloc %[[ALLOC]] : memref - - // Prepare qubits with dynamic allocation - %qreg = quantum.alloc(%n) : !quantum.reg - %q0 = quantum.extract %qreg[ 0] : !quantum.reg -> !quantum.bit - %q1 = quantum.extract %qreg[ 1] : !quantum.reg -> !quantum.bit - %q2 = quantum.extract %qreg[ 2] : !quantum.reg -> !quantum.bit - %q3 = quantum.extract %qreg[%idx] : !quantum.reg -> !quantum.bit - - // Non-controlled Pauli gates - %q0_x = quantum.custom "PauliX"() %q0 : !quantum.bit - %q0_y = quantum.custom "PauliY"() %q0_x : !quantum.bit - %q0_z = quantum.custom "PauliZ"() %q0_y : !quantum.bit - %q0_i = quantum.custom "Identity"() %q0_z : !quantum.bit - - %true = arith.constant true - - // Controlled Pauli gates: q0 controlled by q1 - %q0_ctrlx, %q1_ctrlx = quantum.custom "PauliX"() %q0_i ctrls(%q1) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit - %q0_ctrly, %q1_ctrly = quantum.custom "PauliY"() %q0_ctrlx ctrls(%q1_ctrlx) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit - %q0_ctrlz, %q1_ctrlz = quantum.custom "PauliZ"() %q0_ctrly ctrls(%q1_ctrly) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit - %q0_ctrli, %q1_ctrli = quantum.custom "Identity"() %q0_ctrlz ctrls(%q1_ctrlz) ctrlvals(%true) :!quantum.bit ctrls !quantum.bit - - // C gates: q0 controlled by q1 - %q0_cx, %q1_cx = quantum.custom "CNOT"() %q1_ctrli, %q0_ctrli : !quantum.bit, !quantum.bit - %q0_cy, %q1_cy = quantum.custom "CY"() %q1_cx, %q0_cx : !quantum.bit, !quantum.bit - %q0_cz, %q1_cz = quantum.custom "CZ"() %q1_cy, %q0_cy : !quantum.bit, !quantum.bit - %q0_ct, %q1_ct, %q2_ct = quantum.custom "Toffoli"() %q1_cz, %q2, %q0_cz : !quantum.bit, !quantum.bit, !quantum.bit - - // Controlled-C gates: (q0 controlled by q1) controlled by q2 - %q0_ccx, %q1_ccx, %q2_ccx = quantum.custom "CNOT"() %q1_ct, %q0_ct ctrls(%q2_ct) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_ccy, %q1_ccy, %q2_ccy = quantum.custom "CY"() %q1_ccx, %q0_ccx ctrls(%q2_ccx) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_ccz, %q1_ccz, %q2_ccz = quantum.custom "CZ"() %q1_ccy, %q0_ccy ctrls(%q2_ccy) ctrlvals(%true) :!quantum.bit, !quantum.bit ctrls !quantum.bit - %q0_cccx, %q1_cccx, %q2_cccx, %q3_cccx = quantum.custom "Toffoli"() %q1_ccz, %q2_ccz, %q0_ccz ctrls(%q3) ctrlvals(%true) :!quantum.bit, !quantum.bit, !quantum.bit ctrls !quantum.bit - - // Release qubits - %qreg1 = quantum.insert %qreg[%idx], %q3_cccx : !quantum.reg, !quantum.bit - %qreg2 = quantum.insert %qreg1[ 2], %q2_cccx : !quantum.reg, !quantum.bit - %qreg3 = quantum.insert %qreg2[ 1], %q1_cccx : !quantum.reg, !quantum.bit - %qreg4 = quantum.insert %qreg3[ 0], %q0_cccx : !quantum.reg, !quantum.bit - - quantum.dealloc %qreg4 : !quantum.reg - - return - } -} diff --git a/test/lit.cfg.py b/test/lit.cfg.py deleted file mode 100644 index 5b02156..0000000 --- a/test/lit.cfg.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""LIT Configuration file for mqt-core-plugins-catalyst. - -This file configures the LLVM LIT testing infrastructure for MLIR tests. - -Note: `config` and `lit_config` are injected by LIT at runtime. -""" - -from __future__ import annotations - -from pathlib import Path - -import lit.formats -from lit.llvm import llvm_config - -# Use `lit_config` to access `config` from lit.site.cfg.py -config = globals().get("config") -if config is None: - msg = "LIT config object is missing. Ensure lit.site.cfg.py is loaded first." - raise RuntimeError(msg) - -config.name = "MQT Catalyst Plugin Lit Tests" -config.test_format = lit.formats.ShTest(execute_external=False) - -# Define the file extensions to treat as test files. -config.suffixes = [".mlir"] - -# Define the root path of where to look for tests. -config.test_source_root = Path(__file__).parent - -# Define where to execute tests (and produce the output). -config.test_exec_root = Path(config.mqt_core_plugins_catalyst_test_dir) - -# Add LLVM tools (FileCheck, not, etc.) -tool_dirs = [config.llvm_tools_dir] -tools = ["not", "FileCheck"] -llvm_config.add_tool_substitutions(tools, tool_dirs) - -# Add substitution for the MQT plugin path -config.substitutions.append(("%mqt_plugin_path%", config.mqt_plugin_path)) diff --git a/test/lit.site.cfg.py.in b/test/lit.site.cfg.py.in deleted file mode 100644 index 1e048c1..0000000 --- a/test/lit.site.cfg.py.in +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -@LIT_SITE_CFG_IN_HEADER@ - -config.llvm_tools_dir = lit_config.substitute("@LLVM_TOOLS_DIR@") -config.cmake_build_type = "@CMAKE_BUILD_TYPE@" -config.mqt_core_plugins_catalyst_test_dir = "@PROJECT_BINARY_DIR@/test" -config.mqt_plugin_path = "@PROJECT_BINARY_DIR@/lib/mqt-core-plugins-catalyst@CMAKE_SHARED_LIBRARY_SUFFIX@" - -import lit.llvm -lit.llvm.initialize(lit_config, config) - -# Let the main config do the real work. -lit_config.load_config(config, "@PROJECT_SOURCE_DIR@/test/lit.cfg.py") diff --git a/test/python/test_plugin.py b/test/test_plugin.py similarity index 100% rename from test/python/test_plugin.py rename to test/test_plugin.py diff --git a/test/python/test_plugin_setup.py b/test/test_plugin_setup.py similarity index 100% rename from test/python/test_plugin_setup.py rename to test/test_plugin_setup.py From b899feddaaea541acc3386ebcb7bf3c9cfbf6551 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Wed, 10 Dec 2025 16:54:40 +0100 Subject: [PATCH 12/65] =?UTF-8?q?=F0=9F=8E=A8=20add=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 7 +- pyproject.toml | 4 +- python/mqt/core/plugins/catalyst/device.py | 16 +-- test/CMakeLists.txt | 11 -- test/test_plugin.py | 122 ++++++++++----------- test/test_plugin_setup.py | 24 ++-- 6 files changed, 82 insertions(+), 102 deletions(-) delete mode 100644 test/CMakeLists.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9a87b7..a64bc19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,12 +37,7 @@ jobs: strategy: fail-fast: false matrix: - runs-on: - [ - ubuntu-24.04, - ubuntu-24.04-arm, - macos-14, - ] + runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 with: runs-on: ${{ matrix.runs-on }} diff --git a/pyproject.toml b/pyproject.toml index 38cb5ad..f8b5c39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -142,7 +142,7 @@ report.exclude_also = [ [tool.mypy] files = ["python/mqt", "test", "noxfile.py"] -mypy_path = ["$MYPY_CONFIG_FILE_DIR"] +mypy_path = ["$MYPY_CONFIG_FILE_DIR/python"] python_version = "3.11" warn_unused_configs = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] @@ -152,7 +152,7 @@ explicit_package_bases = true warn_unreachable = true [[tool.mypy.overrides]] -module = ["pytest_console_scripts.*"] +module = ["pytest_console_scripts.*", "pennylane.*", "catalyst.*"] ignore_missing_imports = true [tool.ruff] diff --git a/python/mqt/core/plugins/catalyst/device.py b/python/mqt/core/plugins/catalyst/device.py index 1b9d4c2..b550ae9 100644 --- a/python/mqt/core/plugins/catalyst/device.py +++ b/python/mqt/core/plugins/catalyst/device.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Chair for Design Automation, TUM # Copyright (c) 2025 Munich Quantum Software Company GmbH # All rights reserved. # @@ -14,10 +14,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import Any -if TYPE_CHECKING: - import pennylane as qml +import pennylane as qml +from pennylane.devices.capabilities import DeviceCapabilities def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: @@ -52,8 +52,6 @@ def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: ... qml.ctrl(qml.PauliX(wires=0), control=1) # Will become CNOT, not matrix ... return qml.state() """ - from pennylane.devices.capabilities import DeviceCapabilities - # Load the original capabilities from the device's config file if hasattr(device, "config_filepath") and device.config_filepath is not None: toml_file = device.config_filepath @@ -70,11 +68,11 @@ def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: # Clear _to_matrix_ops to avoid Catalyst validation at qjit_device.py:322 # which requires QubitUnitary support if _to_matrix_ops is set if hasattr(device, "_to_matrix_ops"): - device._to_matrix_ops = set() # noqa: SLF001 + device._to_matrix_ops = set() # noqa: SLF001 # type: ignore[attr-defined] # pyright: ignore[reportAttributeAccessIssue] # Set the qjit_capabilities hook so QJITDevice uses our modified capabilities # This bypasses the normal TOML loading in _load_device_capabilities - device.qjit_capabilities = caps + setattr(device, "qjit_capabilities", caps) # noqa: B010 return device @@ -101,8 +99,6 @@ def get_device(device_name: str, **kwargs: Any) -> qml.devices.Device: # noqa: ... qml.ctrl(qml.PauliX(wires=0), control=1) ... return qml.state() """ - import pennylane as qml - device = qml.device(device_name, **kwargs) return configure_device_for_mqt(device) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index c3bcf91..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -# All tests are now in Python (test/test_plugin.py) -# which test the complete roundtrip conversion pipeline -# No CMake configuration needed for Python tests diff --git a/test/test_plugin.py b/test/test_plugin.py index 97df132..62f3c56 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -24,7 +24,7 @@ import subprocess import tempfile from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import pennylane as qml import pytest @@ -134,9 +134,9 @@ def test_paulix_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: # Non-controlled qml.X(wires=0) @@ -150,8 +150,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() # Verify the roundtrip completes successfully @@ -214,9 +214,9 @@ def test_pauliy_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: # Non-controlled qml.Y(wires=0) @@ -230,8 +230,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() # Verify the roundtrip completes successfully @@ -290,9 +290,9 @@ def test_pauliz_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: # Non-controlled qml.Z(wires=0) @@ -306,8 +306,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() # Verify the roundtrip completes successfully @@ -366,9 +366,9 @@ def test_hadamard_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.Hadamard(wires=0) qml.ctrl(qml.Hadamard(wires=0), control=1) @@ -379,8 +379,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -432,9 +432,9 @@ def test_s_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.S(wires=0) qml.adjoint(qml.S(wires=0)) @@ -445,8 +445,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -497,9 +497,9 @@ def test_t_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.T(wires=0) qml.adjoint(qml.T(wires=0)) @@ -510,8 +510,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -563,9 +563,9 @@ def test_rx_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RX(0.5, wires=0) qml.ctrl(qml.RX(0.5, wires=0), control=1) @@ -576,8 +576,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -629,9 +629,9 @@ def test_ry_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RY(0.5, wires=0) qml.ctrl(qml.RY(0.5, wires=0), control=1) @@ -642,8 +642,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -695,9 +695,9 @@ def test_rz_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RZ(0.5, wires=0) qml.ctrl(qml.RZ(0.5, wires=0), control=1) @@ -708,8 +708,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -761,9 +761,9 @@ def test_phaseshift_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=2)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.PhaseShift(0.5, wires=0) qml.ctrl(qml.PhaseShift(0.5, wires=0), control=1) @@ -774,8 +774,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -828,9 +828,9 @@ def test_swap_gate_roundtrip() -> None: """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=3)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.SWAP(wires=[0, 1]) qml.ctrl(qml.SWAP(wires=[0, 1]), control=2) @@ -841,8 +841,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt @@ -895,9 +895,9 @@ def test_toffoli_gate_roundtrip() -> None: FileNotFoundError: If intermediate MLIR files are not found """ - @apply_pass("mqt.mqtopt-to-catalystquantum") - @apply_pass("mqt.catalystquantum-to-mqtopt") - @qml.qnode(get_device("lightning.qubit", wires=4)) + @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] + @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] def circuit() -> None: qml.Toffoli(wires=[0, 1, 2]) qml.ctrl(qml.Toffoli(wires=[0, 1, 2]), control=3) @@ -907,8 +907,8 @@ def circuit() -> None: ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), ] - @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) - def module() -> None: + @qml.qjit(target="mlir", pipelines=custom_pipeline, autograph=True, keep_intermediate=2) # type: ignore[untyped-decorator] + def module() -> Any: # noqa: ANN401 return circuit() mlir_opt = module.mlir_opt diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index 2f6e69e..4373fbc 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -33,12 +33,12 @@ def test_mqt_plugin() -> None: """ plugin_path = str(get_catalyst_plugin_abs_path()) - @apply_pass("mqt-core-round-trip") - @qml.qnode(qml.device("null.qubit", wires=0)) + @apply_pass("mqt-core-round-trip") # type: ignore[untyped-decorator] + @qml.qnode(qml.device("null.qubit", wires=0)) # type: ignore[untyped-decorator] def qnode() -> StateMP: return qml.state() - @qml.qjit(pass_plugins={plugin_path}, dialect_plugins={plugin_path}, target="mlir") + @qml.qjit(pass_plugins={plugin_path}, dialect_plugins={plugin_path}, target="mlir") # type: ignore[untyped-decorator] def module() -> StateMP: return qnode() @@ -52,12 +52,12 @@ def test_mqt_plugin_no_preregistration() -> None: """ plugin_path = str(get_catalyst_plugin_abs_path()) - @apply_pass_plugin(plugin_path, "mqt-core-round-trip") - @qml.qnode(qml.device("null.qubit", wires=0)) + @apply_pass_plugin(plugin_path, "mqt-core-round-trip") # type: ignore[untyped-decorator] + @qml.qnode(qml.device("null.qubit", wires=0)) # type: ignore[untyped-decorator] def qnode() -> StateMP: return qml.state() - @qml.qjit(target="mlir") + @qml.qjit(target="mlir") # type: ignore[untyped-decorator] def module() -> StateMP: return qnode() @@ -67,12 +67,12 @@ def module() -> StateMP: def test_mqt_entry_point() -> None: """Generate MLIR for the MQT plugin via entry-point.""" - @apply_pass("mqt.mqt-core-round-trip") - @qml.qnode(qml.device("null.qubit", wires=0)) + @apply_pass("mqt.mqt-core-round-trip") # type: ignore[untyped-decorator] + @qml.qnode(qml.device("null.qubit", wires=0)) # type: ignore[untyped-decorator] def qnode() -> StateMP: return qml.state() - @qml.qjit(target="mlir") + @qml.qjit(target="mlir") # type: ignore[untyped-decorator] def module() -> StateMP: return qnode() @@ -82,12 +82,12 @@ def module() -> StateMP: def test_mqt_dictionary() -> None: """Generate MLIR for the MQT plugin via entry-point.""" - @pipeline({"mqt.mqt-core-round-trip": {}}) - @qml.qnode(qml.device("null.qubit", wires=0)) + @pipeline({"mqt.mqt-core-round-trip": {}}) # type: ignore[untyped-decorator] + @qml.qnode(qml.device("null.qubit", wires=0)) # type: ignore[untyped-decorator] def qnode() -> StateMP: return qml.state() - @qml.qjit(target="mlir") + @qml.qjit(target="mlir") # type: ignore[untyped-decorator] def module() -> StateMP: return qnode() From ac3c49df98b8ff1b479a526e9c67b22251cf35f8 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 13:32:40 +0100 Subject: [PATCH 13/65] =?UTF-8?q?=F0=9F=9A=A7=20use=20pre-build=20mlir=20w?= =?UTF-8?q?orkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a64bc19..e223df3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,15 +38,17 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} + setup-mlir: true + llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 python-coverage: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 permissions: contents: read id-token: write @@ -60,23 +62,27 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} + setup-mlir: true + llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 python-linter: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@ea4fccbb432596cbb6840a9608d6376b6b2c8ff7 # v1.17.2 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 with: enable-ty: true + setup-mlir: true + llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 build-sdist: name: 🚀 CD needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 build-wheel: name: 🚀 CD @@ -86,7 +92,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} @@ -108,7 +114,7 @@ jobs: - python-linter - build-sdist - build-wheel - - mlir-tests + #- mlir-tests runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed From a2ec8a2940691b7881180633ca0834011d5caa92 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 13:42:38 +0100 Subject: [PATCH 14/65] =?UTF-8?q?=F0=9F=8E=A8=20update=20llvm-version=20fo?= =?UTF-8?q?rmat=20in=20CI=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e223df3..cb438bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: with: runs-on: ${{ matrix.runs-on }} setup-mlir: true - llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 + llvm-version: "f8cb798" python-coverage: name: 🐍 Coverage @@ -66,7 +66,7 @@ jobs: with: runs-on: ${{ matrix.runs-on }} setup-mlir: true - llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 + llvm-version: "f8cb798" python-linter: name: 🐍 Lint @@ -76,7 +76,7 @@ jobs: with: enable-ty: true setup-mlir: true - llvm-version: f8cb7987c64dcffb72414a40560055cb717dbf74 + llvm-version: "f8cb798" build-sdist: name: 🚀 CD From f4777b80c646b6a011df0b01b3bd76dc4ff50bfe Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 13:56:37 +0100 Subject: [PATCH 15/65] =?UTF-8?q?=F0=9F=9A=A7=20use=20long=20commit=20hash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb438bb..2fb9978 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: with: runs-on: ${{ matrix.runs-on }} setup-mlir: true - llvm-version: "f8cb798" + llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" python-coverage: name: 🐍 Coverage @@ -66,7 +66,7 @@ jobs: with: runs-on: ${{ matrix.runs-on }} setup-mlir: true - llvm-version: "f8cb798" + llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" python-linter: name: 🐍 Lint @@ -76,7 +76,7 @@ jobs: with: enable-ty: true setup-mlir: true - llvm-version: "f8cb798" + llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" build-sdist: name: 🚀 CD From 152945700b94dc919cf2ca7df95f413a1f4f0374 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 14:19:52 +0100 Subject: [PATCH 16/65] =?UTF-8?q?=F0=9F=9A=A7=20use=20fix=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fb9978..ddd737e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -48,7 +48,7 @@ jobs: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 permissions: contents: read id-token: write @@ -62,7 +62,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -72,7 +72,7 @@ jobs: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 with: enable-ty: true setup-mlir: true @@ -82,7 +82,7 @@ jobs: name: 🚀 CD needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 build-wheel: name: 🚀 CD @@ -92,7 +92,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@b50c6772081e52e5dedebaa680e36ed962de81f1 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} From 9ccd2cd52486801f750ad82d0f258ee73c20e6c7 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 15:04:37 +0100 Subject: [PATCH 17/65] =?UTF-8?q?=F0=9F=9A=A7=20use=20branch=20instead=20o?= =?UTF-8?q?f=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddd737e..a55a1ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -48,7 +48,7 @@ jobs: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 permissions: contents: read id-token: write @@ -62,7 +62,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -72,7 +72,7 @@ jobs: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 with: enable-ty: true setup-mlir: true @@ -82,7 +82,7 @@ jobs: name: 🚀 CD needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 build-wheel: name: 🚀 CD @@ -92,7 +92,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@5463b6fcaa05e2f9397f89368fbe8fee3627d8a6 # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 with: runs-on: ${{ matrix.runs-on }} From 9191a86e9ccb5adfd8a891d7a988f09b525e4615 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 15:20:28 +0100 Subject: [PATCH 18/65] =?UTF-8?q?=F0=9F=90=9B=20fix=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index 62f3c56..6859d2c 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -867,23 +867,22 @@ def module() -> Any: # noqa: ANN401 check_mlir_before = """ //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %1, %2 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:3 = quantum.custom "CSWAP"() %3, %out_qubits#0, %out_qubits#1 : !quantum.bit, !quantum.bit, !quantum.bit - //CHECK: %out_qubits_7:3 = quantum.custom "CSWAP"() %out_qubits_6#0, %out_qubits_6#1, %out_qubits_6#2 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_5:3 = quantum.custom "CSWAP"() %3, %out_qubits#0, %out_qubits#1 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_6:3 = quantum.custom "CSWAP"() %out_qubits_5#0, %out_qubits_5#1, %out_qubits_5#2 : !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "SWAP: CatalystQuantum") check_after_mqtopt = """ //CHECK: %out_qubits:2 = mqtopt.swap(static [] mask []) %1, %3 : !mqtopt.Qubit, !mqtopt.Qubit - //CHECK: %out_qubits_6:2, %pos_ctrl_out_qubits = mqtopt.swap(static [] mask []) %out_qubits#0, %out_qubits#1 ctrl %5 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_7:2, %pos_ctrl_out_qubits_8 = mqtopt.swap(static [] mask []) %out_qubits_6#0, %out_qubits_6#1 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - + //CHECK: %out_qubits_5:2, %pos_ctrl_out_qubits = mqtopt.swap(static [] mask []) %out_qubits#0, %out_qubits#1 ctrl %5 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %out_qubits_6:2, %pos_ctrl_out_qubits_7 = mqtopt.swap(static [] mask []) %out_qubits_5#0, %out_qubits_5#1 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "SWAP: CatalystQuantum to MQTOpt") check_after_catalyst = """ //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %3, %6 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:2, %out_ctrl_qubits = quantum.custom "CSWAP"() %out_qubits#0, %out_qubits#1 ctrls(%9) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_9:2, %out_ctrl_qubits_10 = quantum.custom "CSWAP"() %out_qubits_6#0, %out_qubits_6#1 ctrls(%out_ctrl_qubits) ctrlvals(%true_7) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_5:2, %out_ctrl_qubits = quantum.custom "CSWAP"() %out_qubits#0, %out_qubits#1 ctrls(%9) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %out_qubits_8:2, %out_ctrl_qubits_9 = quantum.custom "CSWAP"() %out_qubits_5#0, %out_qubits_5#1 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit, !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "SWAP: MQTOpt to CatalystQuantum") @@ -933,18 +932,18 @@ def module() -> Any: # noqa: ANN401 check_mlir_before = """ //CHECK: %out_qubits:3 = quantum.custom "Toffoli"() %1, %2, %3 : !quantum.bit, !quantum.bit, !quantum.bit - //CHECK: %out_qubits_12, %out_ctrl_qubits:3 = quantum.custom "PauliX"() %out_qubits#2 ctrls(%4, %out_qubits#0, %out_qubits#1) ctrlvals(%extracted_9, %extracted_10, %extracted_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_11, %out_ctrl_qubits:3 = quantum.custom "PauliX"() %out_qubits#2 ctrls(%4, %out_qubits#0, %out_qubits#1) ctrlvals(%extracted_8, %extracted_9, %extracted_10) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "Toffoli: CatalystQuantum") check_after_mqtopt = """ //CHECK: %out_qubits, %pos_ctrl_out_qubits:2 = mqtopt.x(static [] mask []) %5 ctrl %1, %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - //CHECK: %out_qubits_12, %pos_ctrl_out_qubits_13:3 = mqtopt.x(static [] mask []) %out_qubits ctrl %7, %pos_ctrl_out_qubits#0, %pos_ctrl_out_qubits#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %out_qubits_11, %pos_ctrl_out_qubits_12:3 = mqtopt.x(static [] mask []) %out_qubits ctrl %7, %pos_ctrl_out_qubits#0, %pos_ctrl_out_qubits#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "Toffoli: CatalystQuantum to MQTOpt") check_after_catalyst = """ //CHECK: %out_qubits, %out_ctrl_qubits:2 = quantum.custom "Toffoli"() %9 ctrls(%3, %6) ctrlvals(%true, %true) : !quantum.bit ctrls !quantum.bit, !quantum.bit - //CHECK: %out_qubits_14, %out_ctrl_qubits_15:3 = quantum.custom "PauliX"() %out_qubits ctrls(%12, %out_ctrl_qubits#0, %out_ctrl_qubits#1) ctrlvals(%true_12, %true_12, %true_12) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %out_qubits_13, %out_ctrl_qubits_14:3 = quantum.custom "PauliX"() %out_qubits ctrls(%12, %out_ctrl_qubits#0, %out_ctrl_qubits#1) ctrlvals(%true_11, %true_11, %true_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Toffoli: MQTOpt to CatalystQuantum") From 5298efd8da67f5a8eed1bce0d85010ceded24397 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 12 Dec 2025 15:47:07 +0100 Subject: [PATCH 19/65] =?UTF-8?q?=E2=9C=85=20use=20FileCheck=20pattern=20v?= =?UTF-8?q?ariables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin.py | 226 ++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index 6859d2c..270f7a2 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -181,28 +181,28 @@ def module() -> Any: # noqa: ANN401 # Verify original CatalystQuantum check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "PauliX"() %1 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliX"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5:2 = quantum.custom "CNOT"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CNOT"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliX"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliX"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CNOT"() %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CNOT"() %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "PauliX: CatalystQuantum") # Verify CatalystQuantum → MQTOpt conversion check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.x(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_2 = mqtopt.x(static [] mask []) %out_qubits : !mqtopt.Qubit - //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.x(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.x(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.x(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]] = mqtopt.x(static [] mask []) %[[Q0_1:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = mqtopt.x(static [] mask []) %[[Q0_2:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = mqtopt.x(static [] mask []) %[[Q0_3:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliX: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "PauliX"() %3 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliX"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CNOT"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CNOT"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliX"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliX"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "CNOT"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = quantum.custom "CNOT"() %[[Q0_3:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliX: MQTOpt to CatalystQuantum") @@ -257,28 +257,28 @@ def module() -> Any: # noqa: ANN401 # Verify original CatalystQuantum check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "PauliY"() %1 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliY"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5:2 = quantum.custom "CY"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CY"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliY"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliY"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CY"() %[[Q1_0:.*]], %[[Q0_2:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CY"() %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "PauliY: CatalystQuantum") # Verify CatalystQuantum → MQTOpt conversion check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.y(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_2 = mqtopt.y(static [] mask []) %out_qubits : !mqtopt.Qubit - //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.y(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.y(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.y(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]] = mqtopt.y(static [] mask []) %[[Q0_1:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = mqtopt.y(static [] mask []) %[[Q0_2:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = mqtopt.y(static [] mask []) %[[Q0_3:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliY: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "PauliY"() %3 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliY"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CY"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CY"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliY"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliY"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "CY"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = quantum.custom "CY"() %[[Q0_3:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliY: MQTOpt to CatalystQuantum") @@ -333,28 +333,28 @@ def module() -> Any: # noqa: ANN401 # Verify original CatalystQuantum check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "PauliZ"() %1 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliZ"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5:2 = quantum.custom "CZ"() %2, %out_qubits_2 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CZ"() %out_qubits_5#0, %out_qubits_5#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliZ"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliZ"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CZ"() %[[Q1_0:.*]], %[[Q0_2:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CZ"() %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "PauliZ: CatalystQuantum") # Verify CatalystQuantum → MQTOpt conversion check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.z(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_2 = mqtopt.z(static [] mask []) %out_qubits : !mqtopt.Qubit - //CHECK: %out_qubits_5, %pos_ctrl_out_qubits = mqtopt.z(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits_7 = mqtopt.z(static [] mask []) %out_qubits_5 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.z(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]] = mqtopt.z(static [] mask []) %[[Q0_1:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = mqtopt.z(static [] mask []) %[[Q0_2:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = mqtopt.z(static [] mask []) %[[Q0_3:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PauliZ: CatalystQuantum to MQTOpt") # Verify MQTOpt → CatalystQuantum conversion check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "PauliZ"() %3 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "PauliZ"() %out_qubits : !quantum.bit - //CHECK: %out_qubits_5, %out_ctrl_qubits = quantum.custom "CZ"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "CZ"() %out_qubits_5 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PauliZ"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "PauliZ"() %[[Q0_1:.*]] : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "CZ"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q1_2:.*]] = quantum.custom "CZ"() %[[Q0_3:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_6) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PauliZ: MQTOpt to CatalystQuantum") @@ -404,23 +404,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "Hadamard"() %1 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "Hadamard"() %out_qubits ctrls(%2) ctrlvals(%extracted_5) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_8, %out_ctrl_qubits_9 = quantum.custom "Hadamard"() %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%extracted_7) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "Hadamard"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "Hadamard"() %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%extracted_5) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "Hadamard"() %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%extracted_7) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "Hadamard: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.h(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.h(static [] mask []) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.h(static [] mask []) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.h(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.h(static [] mask []) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.h(static [] mask []) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "Hadamard: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "Hadamard"() %3 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "Hadamard"() %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "Hadamard"() %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "Hadamard"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "Hadamard"() %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "Hadamard"() %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Hadamard: MQTOpt to CatalystQuantum") @@ -470,22 +470,22 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "S"() %1 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "S"() %out_qubits adj : !quantum.bit - //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "S"() %out_qubits_2 ctrls(%2) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "S"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "S"() %[[Q0_1:.*]] adj : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "S"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "S: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.s(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_2 = mqtopt.sdg(static [] mask []) %out_qubits : !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.s(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]] = mqtopt.sdg(static [] mask []) %[[Q0_1:.*]] : !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "S: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "S"() %3 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "S"() %out_qubits adj : !quantum.bit - //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "S"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "S"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "S"() %[[Q0_1:.*]] adj : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "S"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "S: MQTOpt to CatalystQuantum") @@ -535,23 +535,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "T"() %1 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "T"() %out_qubits adj : !quantum.bit - //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "T"() %out_qubits_2 ctrls(%2) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "T"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "T"() %[[Q0_1:.*]] adj : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "T"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%extracted_6) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "T: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.t(static [] mask []) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_2 = mqtopt.tdg(static [] mask []) %out_qubits : !mqtopt.Qubit - //CHECK: %out_qubits_7, %pos_ctrl_out_qubits = mqtopt.t(static [] mask []) %out_qubits_2 ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.t(static [] mask []) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]] = mqtopt.tdg(static [] mask []) %[[Q0_1:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = mqtopt.t(static [] mask []) %[[Q0_2:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "T: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "T"() %3 : !quantum.bit - //CHECK: %out_qubits_2 = quantum.custom "T"() %out_qubits adj : !quantum.bit - //CHECK: %out_qubits_7, %out_ctrl_qubits = quantum.custom "T"() %out_qubits_2 ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "T"() %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]] = quantum.custom "T"() %[[Q0_1:.*]] adj : !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_1:.*]] = quantum.custom "T"() %[[Q0_2:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "T: MQTOpt to CatalystQuantum") @@ -601,23 +601,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "RX"({{.*}}) %1 : !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CRX"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_8:2 = quantum.custom "CRX"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRX"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRX"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RX: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.rx({{.*}}) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.rx({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.rx({{.*}}) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.rx({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.rx({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.rx({{.*}}) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RX: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "RX"({{.*}}) %3 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRX"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRX"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRX"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRX"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RX: MQTOpt to CatalystQuantum") @@ -667,23 +667,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "RY"({{.*}}) %1 : !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CRY"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_8:2 = quantum.custom "CRY"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRY"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRY"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RY: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.ry({{.*}}) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.ry({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.ry(%extracted_7 static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.ry({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.ry({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.ry(%extracted_7 static [] mask [false]) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RY: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "RY"({{.*}}) %3 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRY"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRY"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRY"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRY"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RY: MQTOpt to CatalystQuantum") @@ -733,23 +733,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "RZ"({{.*}}) %1 : !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "CRZ"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_8:2 = quantum.custom "CRZ"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRZ"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRZ"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RZ: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.rz({{.*}}) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.rz({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.rz(%extracted_7 static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.rz({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.rz({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.rz(%extracted_7 static [] mask [false]) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RZ: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "RZ"({{.*}}) %3 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "CRZ"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "CRZ"(%extracted_7) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRZ"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRZ"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RZ: MQTOpt to CatalystQuantum") @@ -799,23 +799,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits = quantum.custom "PhaseShift"({{.*}}) %1 : !quantum.bit - //CHECK: %out_qubits_6:2 = quantum.custom "ControlledPhaseShift"({{.*}}) %2, %out_qubits : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_8:2 = quantum.custom "ControlledPhaseShift"(%extracted_7) %out_qubits_6#0, %out_qubits_6#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PhaseShift"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "ControlledPhaseShift"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "ControlledPhaseShift"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "PhaseShift: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits = mqtopt.p({{.*}}) %1 : !mqtopt.Qubit - //CHECK: %out_qubits_6, %pos_ctrl_out_qubits = mqtopt.p({{.*}}) %out_qubits ctrl %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_8, %pos_ctrl_out_qubits_9 = mqtopt.p({{.*}} static [] mask [false]) %out_qubits_6 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_1:.*]] = mqtopt.p({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.p({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.p({{.*}} static [] mask [false]) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "PhaseShift: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits = quantum.custom "PhaseShift"({{.*}}) %3 : !quantum.bit - //CHECK: %out_qubits_6, %out_ctrl_qubits = quantum.custom "ControlledPhaseShift"({{.*}}) %out_qubits ctrls(%6) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_10, %out_ctrl_qubits_11 = quantum.custom "ControlledPhaseShift"({{.*}}) %out_qubits_6 ctrls(%out_ctrl_qubits) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_1:.*]] = quantum.custom "PhaseShift"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "ControlledPhaseShift"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "ControlledPhaseShift"({{.*}}) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "PhaseShift: MQTOpt to CatalystQuantum") @@ -866,23 +866,23 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %1, %2 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_5:3 = quantum.custom "CSWAP"() %3, %out_qubits#0, %out_qubits#1 : !quantum.bit, !quantum.bit, !quantum.bit - //CHECK: %out_qubits_6:3 = quantum.custom "CSWAP"() %out_qubits_5#0, %out_qubits_5#1, %out_qubits_5#2 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q01_0:.*]]:2 = quantum.custom "SWAP"() %[[Q0_0:.*]], %[[Q1_0:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q201_0:.*]]:3 = quantum.custom "CSWAP"() %[[Q2_0:.*]], %[[Q01_0:.*]]#0, %[[Q01_0:.*]]#1 : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q201_0:.*]]:3 = quantum.custom "CSWAP"() %[[Q201_0:.*]]#0, %[[Q201_0:.*]]#1, %[[Q201_0:.*]]#2 : !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "SWAP: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits:2 = mqtopt.swap(static [] mask []) %1, %3 : !mqtopt.Qubit, !mqtopt.Qubit - //CHECK: %out_qubits_5:2, %pos_ctrl_out_qubits = mqtopt.swap(static [] mask []) %out_qubits#0, %out_qubits#1 ctrl %5 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - //CHECK: %out_qubits_6:2, %pos_ctrl_out_qubits_7 = mqtopt.swap(static [] mask []) %out_qubits_5#0, %out_qubits_5#1 ctrl %pos_ctrl_out_qubits : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap(static [] mask []) %[[Q0_0:.*]], %[[Q1_0:.*]] : !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %[[Q01_2:.*]]:2, %[[Q2_1:.*]] = mqtopt.swap(static [] mask []) %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1 ctrl %[[Q2_0:.*]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q01_3:.*]]:2, %[[Q2_2:.*]] = mqtopt.swap(static [] mask []) %[[Q01_2:.*]]#0, %[[Q01_2:.*]]#1 ctrl %[[Q2_1:.*]] : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "SWAP: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits:2 = quantum.custom "SWAP"() %3, %6 : !quantum.bit, !quantum.bit - //CHECK: %out_qubits_5:2, %out_ctrl_qubits = quantum.custom "CSWAP"() %out_qubits#0, %out_qubits#1 ctrls(%9) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - //CHECK: %out_qubits_8:2, %out_ctrl_qubits_9 = quantum.custom "CSWAP"() %out_qubits_5#0, %out_qubits_5#1 ctrls(%out_ctrl_qubits) ctrlvals(%true_6) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q01_1:.*]]:2 = quantum.custom "SWAP"() %[[Q0_0:.*]], %[[Q1_0:.*]] : !quantum.bit, !quantum.bit + //CHECK: %[[Q01_2:.*]]:2, %[[Q2_1:.*]] = quantum.custom "CSWAP"() %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1 ctrls(%[[Q2_0:.*]]) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q01_3:.*]]:2, %[[Q2_2:.*]] = quantum.custom "CSWAP"() %[[Q01_2:.*]]#0, %[[Q01_2:.*]]#1 ctrls(%[[Q2_1:.*]]) ctrlvals(%true_6) : !quantum.bit, !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "SWAP: MQTOpt to CatalystQuantum") @@ -931,19 +931,19 @@ def module() -> Any: # noqa: ANN401 mlir_after_roundtrip = f.read() check_mlir_before = """ - //CHECK: %out_qubits:3 = quantum.custom "Toffoli"() %1, %2, %3 : !quantum.bit, !quantum.bit, !quantum.bit - //CHECK: %out_qubits_11, %out_ctrl_qubits:3 = quantum.custom "PauliX"() %out_qubits#2 ctrls(%4, %out_qubits#0, %out_qubits#1) ctrlvals(%extracted_8, %extracted_9, %extracted_10) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q012_0:.*]]:3 = quantum.custom "Toffoli"() %[[Q0_0:.*]], %[[Q1_0:.*]], %[[Q2_0:.*]] : !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q2_1:.*]], %[[Q301_0:.*]]:3 = quantum.custom "PauliX"() %[[Q012_0:.*]]#2 ctrls(%[[Q3_0:.*]], %[[Q012_0:.*]]#0, %[[Q012_0:.*]]#1) ctrlvals(%extracted_8, %extracted_9, %extracted_10) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "Toffoli: CatalystQuantum") check_after_mqtopt = """ - //CHECK: %out_qubits, %pos_ctrl_out_qubits:2 = mqtopt.x(static [] mask []) %5 ctrl %1, %3 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - //CHECK: %out_qubits_11, %pos_ctrl_out_qubits_12:3 = mqtopt.x(static [] mask []) %out_qubits ctrl %7, %pos_ctrl_out_qubits#0, %pos_ctrl_out_qubits#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %[[Q2_1:.*]], %[[Q01_1:.*]]:2 = mqtopt.x(static [] mask []) %[[Q2_0:.*]] ctrl %[[Q0_0:.*]], %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + //CHECK: %[[Q2_2:.*]], %[[Q301_1:.*]]:3 = mqtopt.x(static [] mask []) %[[Q2_1:.*]] ctrl %[[Q3_0:.*]], %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "Toffoli: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %out_qubits, %out_ctrl_qubits:2 = quantum.custom "Toffoli"() %9 ctrls(%3, %6) ctrlvals(%true, %true) : !quantum.bit ctrls !quantum.bit, !quantum.bit - //CHECK: %out_qubits_13, %out_ctrl_qubits_14:3 = quantum.custom "PauliX"() %out_qubits ctrls(%12, %out_ctrl_qubits#0, %out_ctrl_qubits#1) ctrlvals(%true_11, %true_11, %true_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q2_1:.*]], %[[Q01_1:.*]]:2 = quantum.custom "Toffoli"() %[[Q2_0:.*]] ctrls(%[[Q0_0:.*]], %[[Q1_0:.*]]) ctrlvals(%true, %true) : !quantum.bit ctrls !quantum.bit, !quantum.bit + //CHECK: %[[Q2_2:.*]], %[[Q301_1:.*]]:3 = quantum.custom "PauliX"() %[[Q2_1:.*]] ctrls(%[[Q3_0:.*]], %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1) ctrlvals(%true_11, %true_11, %true_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Toffoli: MQTOpt to CatalystQuantum") From 45c387bbe00191a3be52cf972f465c3177e3d892 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Sat, 13 Dec 2025 11:22:43 +0100 Subject: [PATCH 20/65] =?UTF-8?q?=F0=9F=94=80=20cherry=20pick=20multi=20co?= =?UTF-8?q?ntrol=20bug=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MQTOptToCatalystQuantum.cpp | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp b/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp index e1fa185..cef6fcb 100644 --- a/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp +++ b/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp @@ -1001,7 +1001,7 @@ ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { if (numControls == 2) { return "Toffoli"; } - // 0 or 3+ controls: use PauliX with explicit control qubits + // 0 or 3+ controls return "PauliX"; } @@ -1013,7 +1013,7 @@ ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { if (numControls == 1) { return "CY"; } - // 0 or 2+ controls: use PauliY with explicit control qubits + // 0 or 2+ controls return "PauliY"; } @@ -1025,7 +1025,7 @@ ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { if (numControls == 1) { return "CZ"; } - // 0 or 2+ controls: use PauliZ with explicit control qubits + // 0 or 2+ controls return "PauliZ"; } @@ -1068,14 +1068,11 @@ StringRef ConvertMQTOptSimpleGate::getGateName( template <> StringRef ConvertMQTOptSimpleGate::getGateName( const std::size_t numControls) { - if (numControls == 0) { - return "SWAP"; - } if (numControls == 1) { return "CSWAP"; } - // Unsupported: will be caught in matchAndRewrite - return ""; + // 0 or 2+ controls + return "SWAP"; } // -- iSWAPOp (iSWAP) @@ -1089,56 +1086,44 @@ StringRef ConvertMQTOptSimpleGate::getGateName( template <> StringRef ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { - if (numControls == 0) { - return "RX"; - } if (numControls == 1) { return "CRX"; } - // Unsupported: will be caught in matchAndRewrite - return ""; + // 0 or 2+ controls + return "RX"; } // -- RYOp (RY, CRY) template <> StringRef ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { - if (numControls == 0) { - return "RY"; - } if (numControls == 1) { return "CRY"; } - // Unsupported: will be caught in matchAndRewrite - return ""; + // 0 or 2+ controls + return "RY"; } // -- RZOp (RZ, CRZ) template <> StringRef ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { - if (numControls == 0) { - return "RZ"; - } if (numControls == 1) { return "CRZ"; } - // Unsupported: will be caught in matchAndRewrite - return ""; + // 0 or 2+ controls + return "RZ"; } // -- POp (PhaseShift, ControlledPhaseShift) template <> StringRef ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) { - if (numControls == 0) { - return "PhaseShift"; - } if (numControls == 1) { return "ControlledPhaseShift"; } - // Unsupported: will be caught in matchAndRewrite - return ""; + // 0 or 2+ controls + return "PhaseShift"; } // -- RXXOp (IsingXX) From b02325266b15e4b10936c10bcee2af9526b90469 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Sat, 13 Dec 2025 12:49:05 +0100 Subject: [PATCH 21/65] =?UTF-8?q?=E2=9C=85add=20multi=20controlled=20rotat?= =?UTF-8?q?ion=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index 270f7a2..fbb2ff0 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -565,11 +565,12 @@ def test_rx_gate_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] - @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=3)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RX(0.5, wires=0) qml.ctrl(qml.RX(0.5, wires=0), control=1) qml.CRX(0.5, wires=[1, 0]) + qml.ctrl(qml.CRX(0.5, wires=[1, 0]), control=2) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -604,6 +605,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRX"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRX"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q21_0:.*]]:2 = quantum.custom "RX"(%extracted_12) %[[Q10_0:.*]]#1 ctrls(%3, %[[Q10_0:.*]]#0) ctrlvals(%extracted_13, %extracted_14) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RX: CatalystQuantum") @@ -611,14 +613,14 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = mqtopt.rx({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.rx({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.rx({{.*}}) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - """ + //CHECK: %[[Q0_4:.*]], %[[Q12:.*]]:2 = mqtopt.rx(%[[THETA:.*]] static [] mask [false]) %[[Q0_3:.*]] ctrl %[[Q1_2:.*]], %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit""" _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RX: CatalystQuantum to MQTOpt") check_after_catalyst = """ - //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit - //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRX"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit - //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRX"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit - """ + //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRX"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals([[TRUE0:.*]]) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRX"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals([[TRUE0:.*]]) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RX"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RX: MQTOpt to CatalystQuantum") @@ -631,11 +633,12 @@ def test_ry_gate_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] - @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=3)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RY(0.5, wires=0) qml.ctrl(qml.RY(0.5, wires=0), control=1) qml.CRY(0.5, wires=[1, 0]) + qml.ctrl(qml.CRY(0.5, wires=[1, 0]), control=2) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -670,6 +673,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRY"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRY"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q21_0:.*]]:2 = quantum.custom "RY"(%extracted_12) %[[Q10_0:.*]]#1 ctrls(%3, %[[Q10_0:.*]]#0) ctrlvals(%extracted_13, %extracted_14) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RY: CatalystQuantum") @@ -677,6 +681,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = mqtopt.ry({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.ry({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.ry(%extracted_7 static [] mask [false]) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_4:.*]], %[[Q12:.*]]:2 = mqtopt.ry(%[[THETA:.*]] static [] mask [false]) %[[Q0_3:.*]] ctrl %[[Q1_2:.*]], %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RY: CatalystQuantum to MQTOpt") @@ -684,6 +689,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRY"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRY"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RY"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RY: MQTOpt to CatalystQuantum") @@ -697,11 +703,12 @@ def test_rz_gate_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] - @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=3)) # type: ignore[untyped-decorator] def circuit() -> None: qml.RZ(0.5, wires=0) qml.ctrl(qml.RZ(0.5, wires=0), control=1) qml.CRZ(0.5, wires=[1, 0]) + qml.ctrl(qml.CRZ(0.5, wires=[1, 0]), control=2) custom_pipeline = [ ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), @@ -736,6 +743,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRZ"({{.*}}) %[[Q1_0:.*]], %[[Q0_1:.*]] : !quantum.bit, !quantum.bit //CHECK: %[[Q10_0:.*]]:2 = quantum.custom "CRZ"(%extracted_7) %[[Q10_0:.*]]#0, %[[Q10_0:.*]]#1 : !quantum.bit, !quantum.bit + //CHECK: %[[Q0_2:.*]], %[[Q21_0:.*]]:2 = quantum.custom "RZ"(%extracted_12) %[[Q10_0:.*]]#1 ctrls(%3, %[[Q10_0:.*]]#0) ctrlvals(%extracted_13, %extracted_14) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "RZ: CatalystQuantum") @@ -743,6 +751,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = mqtopt.rz({{.*}}) %[[Q0_0:.*]] : !mqtopt.Qubit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = mqtopt.rz({{.*}}) %[[Q0_1:.*]] ctrl %[[Q1_0:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = mqtopt.rz(%extracted_7 static [] mask [false]) %[[Q0_2:.*]] ctrl %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + //CHECK: %[[Q0_4:.*]], %[[Q12:.*]]:2 = mqtopt.rz(%[[THETA:.*]] static [] mask [false]) %[[Q0_3:.*]] ctrl %[[Q1_2:.*]], %[[Q1_1:.*]] : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit """ _run_filecheck(mlir_after_mqtopt, check_after_mqtopt, "RZ: CatalystQuantum to MQTOpt") @@ -750,6 +759,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRZ"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRZ"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RZ"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RZ: MQTOpt to CatalystQuantum") From 8a0350ab210118f53d77a7f43ae044e15cb002f1 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Sat, 13 Dec 2025 12:49:41 +0100 Subject: [PATCH 22/65] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index fbb2ff0..b0ad089 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -689,7 +689,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRY"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRY"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit - //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RY"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RY"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RY: MQTOpt to CatalystQuantum") @@ -759,7 +759,7 @@ def module() -> Any: # noqa: ANN401 //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit //CHECK: %[[Q0_2:.*]], %[[Q1_1:.*]] = quantum.custom "CRZ"({{.*}}) %[[Q0_1:.*]] ctrls(%[[Q1_0:.*]]) ctrlvals(%true) : !quantum.bit ctrls !quantum.bit //CHECK: %[[Q0_3:.*]], %[[Q1_2:.*]] = quantum.custom "CRZ"(%extracted_7) %[[Q0_2:.*]] ctrls(%[[Q1_1:.*]]) ctrlvals(%true_8) : !quantum.bit ctrls !quantum.bit - //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RZ"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit + //CHECK: %[[Q0_4:.*]], %[[Q12_3:.*]]:2 = quantum.custom "RZ"(%[[THETA:.*]]) %[[Q0_3:.*]] ctrls(%[[Q1_2:.*]], %[[Q1_1:.*]]) ctrlvals(%[[TRUE0:.*]], %[[TRUE1:.*]]) : !quantum.bit ctrls !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "RZ: MQTOpt to CatalystQuantum") From 0d75e83cc27ce09d6702dd3d74e9c29cd9879e81 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 13 Dec 2025 16:16:11 +0100 Subject: [PATCH 23/65] Trigger CI to test if the latest `setup-mlir` version still works Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 34c3b31..0fe51e3 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: pull_request: paths: - - .github/workflows/cd.yml + - .github/workflows/cd.yml permissions: contents: read From 98c9900a538af3018b8adb3a3623bfc9345d1fd7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:16:24 +0000 Subject: [PATCH 24/65] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0fe51e3..34c3b31 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: pull_request: paths: - - .github/workflows/cd.yml + - .github/workflows/cd.yml permissions: contents: read From 76605d47ef2339033c7a47aa22958be570dcd29f Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Sun, 14 Dec 2025 19:54:21 +0100 Subject: [PATCH 25/65] updt uv --- uv.lock | 188 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/uv.lock b/uv.lock index 4d5a20b..9fd925f 100644 --- a/uv.lock +++ b/uv.lock @@ -332,6 +332,100 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, ] +[[package]] +name = "core-plugins-catalyst" +source = { editable = "." } +dependencies = [ + { name = "mqt-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pennylane-catalyst", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, +] + +[package.dev-dependencies] +build = [ + { name = "scikit-build-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, +] +dev = [ + { name = "lit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "nox", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-console-scripts", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-cov", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-sugar", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-xdist", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "scikit-build-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "ty", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, +] +docs = [ + { name = "breathe", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "furo", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "graphviz", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "myst-nb", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "openqasm-pygments", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinx-autoapi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinx-copybutton", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinx-design", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinxcontrib-bibtex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinxcontrib-svg2pdfconverter", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "sphinxext-opengraph", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, +] +test = [ + { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-console-scripts", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-cov", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-sugar", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, + { name = "pytest-xdist", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "mqt-core", specifier = "~=3.3.3" }, + { name = "pennylane-catalyst", specifier = "~=0.13.0" }, +] + +[package.metadata.requires-dev] +build = [ + { name = "scikit-build-core", specifier = ">=0.11.6" }, + { name = "setuptools-scm", specifier = ">=9.2.2" }, +] +dev = [ + { name = "lit", specifier = ">=18.1.8" }, + { name = "nox", specifier = ">=2025.11.12" }, + { name = "pytest", specifier = ">=9.0.1" }, + { name = "pytest-console-scripts", specifier = ">=1.4.1" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-sugar", specifier = ">=1.1.1" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "scikit-build-core", specifier = ">=0.11.6" }, + { name = "setuptools-scm", specifier = ">=9.2.2" }, + { name = "ty", specifier = "==0.0.1a34" }, +] +docs = [ + { name = "breathe", specifier = ">=4.36.0" }, + { name = "furo", specifier = ">=2025.9.25" }, + { name = "graphviz", specifier = ">=0.21.0" }, + { name = "myst-nb", specifier = ">=1.3.0" }, + { name = "openqasm-pygments", specifier = ">=0.2.0" }, + { name = "setuptools-scm", specifier = ">=9.2.2" }, + { name = "sphinx", specifier = ">=8.2.3" }, + { name = "sphinx-autoapi", specifier = ">=3.6.1" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2" }, + { name = "sphinx-design", specifier = ">=0.6.1" }, + { name = "sphinxcontrib-bibtex", specifier = ">=2.6.5" }, + { name = "sphinxcontrib-svg2pdfconverter", specifier = ">=1.3.0" }, + { name = "sphinxext-opengraph", specifier = ">=0.13.0" }, +] +test = [ + { name = "pytest", specifier = ">=9.0.1" }, + { name = "pytest-console-scripts", specifier = ">=1.4.1" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-sugar", specifier = ">=1.1.1" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, +] + [[package]] name = "coverage" version = "7.12.0" @@ -989,100 +1083,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/5f/2e76b3e4692a804a125a3bc36bb8cc4808a2ee474d953231c2ca35960c1a/mqt_core-3.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b40f0caab3489aa718f9e1ad95f66f76d43c28aa8b8fa1ff0181d5bfb2dc2e85", size = 8570635, upload-time = "2025-11-10T23:18:30.209Z" }, ] -[[package]] -name = "mqt-core-plugins-catalyst" -source = { editable = "." } -dependencies = [ - { name = "mqt-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pennylane-catalyst", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, -] - -[package.dev-dependencies] -build = [ - { name = "scikit-build-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, -] -dev = [ - { name = "lit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "nox", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-console-scripts", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-cov", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-sugar", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-xdist", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "scikit-build-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "ty", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, -] -docs = [ - { name = "breathe", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "furo", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "graphviz", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "myst-nb", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "openqasm-pygments", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "setuptools-scm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinx-autoapi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinx-copybutton", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinx-design", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinxcontrib-bibtex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinxcontrib-svg2pdfconverter", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "sphinxext-opengraph", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, -] -test = [ - { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-console-scripts", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-cov", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-sugar", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, - { name = "pytest-xdist", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or sys_platform == 'linux'" }, -] - -[package.metadata] -requires-dist = [ - { name = "mqt-core", specifier = "~=3.3.3" }, - { name = "pennylane-catalyst", specifier = "~=0.13.0" }, -] - -[package.metadata.requires-dev] -build = [ - { name = "scikit-build-core", specifier = ">=0.11.6" }, - { name = "setuptools-scm", specifier = ">=9.2.2" }, -] -dev = [ - { name = "lit", specifier = ">=18.1.8" }, - { name = "nox", specifier = ">=2025.11.12" }, - { name = "pytest", specifier = ">=9.0.1" }, - { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, - { name = "pytest-sugar", specifier = ">=1.1.1" }, - { name = "pytest-xdist", specifier = ">=3.8.0" }, - { name = "scikit-build-core", specifier = ">=0.11.6" }, - { name = "setuptools-scm", specifier = ">=9.2.2" }, - { name = "ty", specifier = "==0.0.1a34" }, -] -docs = [ - { name = "breathe", specifier = ">=4.36.0" }, - { name = "furo", specifier = ">=2025.9.25" }, - { name = "graphviz", specifier = ">=0.21.0" }, - { name = "myst-nb", specifier = ">=1.3.0" }, - { name = "openqasm-pygments", specifier = ">=0.2.0" }, - { name = "setuptools-scm", specifier = ">=9.2.2" }, - { name = "sphinx", specifier = ">=8.2.3" }, - { name = "sphinx-autoapi", specifier = ">=3.6.1" }, - { name = "sphinx-copybutton", specifier = ">=0.5.2" }, - { name = "sphinx-design", specifier = ">=0.6.1" }, - { name = "sphinxcontrib-bibtex", specifier = ">=2.6.5" }, - { name = "sphinxcontrib-svg2pdfconverter", specifier = ">=1.3.0" }, - { name = "sphinxext-opengraph", specifier = ">=0.13.0" }, -] -test = [ - { name = "pytest", specifier = ">=9.0.1" }, - { name = "pytest-console-scripts", specifier = ">=1.4.1" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, - { name = "pytest-sugar", specifier = ">=1.1.1" }, - { name = "pytest-xdist", specifier = ">=3.8.0" }, -] - [[package]] name = "myst-nb" version = "1.3.0" From 1d964b896e12cb702af9b0cdacdec94d9895ed07 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 15 Dec 2025 19:24:37 +0100 Subject: [PATCH 26/65] =?UTF-8?q?=F0=9F=93=9D=20dont=20build=20in=20RTD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 23 +++++++++++++++-------- docs/conf.py | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7c2002a..b90e122 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,30 +10,37 @@ build: os: ubuntu-24.04 tools: python: "3.13" + apt_packages: - graphviz + jobs: post_checkout: - # Skip docs build if the commit message contains "skip ci" - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 - # Skip docs build if there are no changes related to docs - | - if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- bindings/ docs/ include/ mlir/include/ python/ .github/contributing* .github/support* .readthedocs.yaml; + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ python/ .readthedocs.yaml; then exit 183; fi - # Unshallow the git clone and fetch tags to get proper version information - git fetch --unshallow --tags + pre_build: - # Set up uv + # Install uv - asdf plugin add uv - asdf install uv latest - asdf global uv latest - - curl -LsSf https://github.com/munich-quantum-software/setup-mlir/releases/latest/download/setup-mlir.sh | bash -s -- -v f8cb7987c64dcffb72414a40560055cb717dbf74 -p $HOME/mlir + + # Install *only* documentation dependencies + - uv sync --frozen --group docs --no-dev + + # Make Python sources importable without building + - export PYTHONPATH=$PWD/python + build: html: - - uv run --frozen --no-dev --group docs -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html + - uv run -m sphinx -T -b html -d docs/_build/doctrees docs $READTHEDOCS_OUTPUT/html + htmlzip: - - uv run --frozen --no-dev --group docs -m sphinx -T -b dirhtml -d docs/_build/doctrees -D language=en docs docs/_build/dirhtml + - uv run -m sphinx -T -b dirhtml -d docs/_build/doctrees docs docs/_build/dirhtml - mkdir -p $READTHEDOCS_OUTPUT/htmlzip - zip -r $READTHEDOCS_OUTPUT/htmlzip/html.zip docs/_build/dirhtml/* diff --git a/docs/conf.py b/docs/conf.py index c31ec03..9a3efe3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,6 +10,7 @@ from __future__ import annotations +import sys import warnings from importlib import metadata from pathlib import Path @@ -43,6 +44,9 @@ version = get_version(root=str(ROOT), fallback_root=ROOT) +# For Sphinx to find the package, add the python/ directory to sys.path +sys.path.insert(0, Path("../python").resolve()) + # Filter git details from version release = version.split("+")[0] From 48701311e199ee03a08ae7aee88b119b3381cfa3 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 15 Dec 2025 19:31:56 +0100 Subject: [PATCH 27/65] =?UTF-8?q?=F0=9F=9A=A7=20no=20install?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b90e122..978873d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -31,7 +31,7 @@ build: - asdf global uv latest # Install *only* documentation dependencies - - uv sync --frozen --group docs --no-dev + - uv sync --frozen --group docs --no-dev --no-install-project # Make Python sources importable without building - export PYTHONPATH=$PWD/python From 15614188512168d2e9008fb1b456a06e02590c47 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 15 Dec 2025 19:38:52 +0100 Subject: [PATCH 28/65] =?UTF-8?q?=F0=9F=9A=A7=20no=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 978873d..d00fa3a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -38,9 +38,9 @@ build: build: html: - - uv run -m sphinx -T -b html -d docs/_build/doctrees docs $READTHEDOCS_OUTPUT/html + - uv run --no-project -m sphinx -T -b html -d docs/_build/doctrees docs $READTHEDOCS_OUTPUT/html htmlzip: - - uv run -m sphinx -T -b dirhtml -d docs/_build/doctrees docs docs/_build/dirhtml + - uv run --no-project -m sphinx -T -b dirhtml -d docs/_build/doctrees docs docs/_build/dirhtml - mkdir -p $READTHEDOCS_OUTPUT/htmlzip - zip -r $READTHEDOCS_OUTPUT/htmlzip/html.zip docs/_build/dirhtml/* From d1676cefae00270e5129b000dc49f868c6e9b54c Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 29 Dec 2025 11:44:38 +0100 Subject: [PATCH 29/65] =?UTF-8?q?=F0=9F=94=A7=20Update=20CI=20workflow=20t?= =?UTF-8?q?o=20use=20latest=20reusable=20workflows=20for=20Python=20tests?= =?UTF-8?q?=20and=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43ec3bc..5528a28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: change-detection: name: 🔍 Change uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-change-detection.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 + python-tests: name: 🐍 Test needs: change-detection @@ -26,7 +27,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -36,7 +37,7 @@ jobs: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 permissions: contents: read id-token: write @@ -50,7 +51,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -60,7 +61,7 @@ jobs: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@enable-llvm-version-commit-hash # TODO: use correct hash after release of v1.17.5 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 with: enable-ty: true setup-mlir: true From facf35ecf8be94050b9176f71a3d34a9278d15d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 10:45:07 +0000 Subject: [PATCH 30/65] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- test/test_plugin.py | 108 +++++++++++++-------------------------- 2 files changed, 37 insertions(+), 73 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5528a28..9d58d0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" build-sdist: - name: CD (sdist) + name: CD (sdist) needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 diff --git a/test/test_plugin.py b/test/test_plugin.py index b0ad089..cf2541b 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -172,12 +172,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") # Verify original CatalystQuantum check_mlir_before = """ @@ -248,12 +245,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") # Verify original CatalystQuantum check_mlir_before = """ @@ -324,12 +318,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") # Verify original CatalystQuantum check_mlir_before = """ @@ -396,12 +387,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "Hadamard"() %[[Q0_0:.*]] : !quantum.bit @@ -462,12 +450,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "S"() %[[Q0_0:.*]] : !quantum.bit @@ -527,12 +512,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "T"() %[[Q0_0:.*]] : !quantum.bit @@ -594,12 +576,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "RX"({{.*}}) %[[Q0_0:.*]] : !quantum.bit @@ -662,12 +641,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "RY"({{.*}}) %[[Q0_0:.*]] : !quantum.bit @@ -732,12 +708,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "RZ"({{.*}}) %[[Q0_0:.*]] : !quantum.bit @@ -801,12 +774,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q0_1:.*]] = quantum.custom "PhaseShift"({{.*}}) %[[Q0_0:.*]] : !quantum.bit @@ -868,12 +838,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q01_0:.*]]:2 = quantum.custom "SWAP"() %[[Q0_0:.*]], %[[Q1_0:.*]] : !quantum.bit, !quantum.bit @@ -933,12 +900,9 @@ def module() -> Any: # noqa: ANN401 msg = f"Expected MLIR files not found in {mlir_dir}.\nAvailable files: {[f.name for f in available_files]}" raise FileNotFoundError(msg) - with Path(catalyst_mlir).open("r", encoding="utf-8") as f: - mlir_before = f.read() - with Path(mlir_to_mqtopt).open("r", encoding="utf-8") as f: - mlir_after_mqtopt = f.read() - with Path(mlir_to_catalyst).open("r", encoding="utf-8") as f: - mlir_after_roundtrip = f.read() + mlir_before = Path(catalyst_mlir).read_text(encoding="utf-8") + mlir_after_mqtopt = Path(mlir_to_mqtopt).read_text(encoding="utf-8") + mlir_after_roundtrip = Path(mlir_to_catalyst).read_text(encoding="utf-8") check_mlir_before = """ //CHECK: %[[Q012_0:.*]]:3 = quantum.custom "Toffoli"() %[[Q0_0:.*]], %[[Q1_0:.*]], %[[Q2_0:.*]] : !quantum.bit, !quantum.bit, !quantum.bit From 5cd6f0aa7aea2765c95f2fa94b32305d71a1fa11 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 13:45:18 +0100 Subject: [PATCH 31/65] =?UTF-8?q?=F0=9F=94=A7=20Update=20CMakeLists.txt=20?= =?UTF-8?q?to=20ensure=20LLVM=20and=20MLIR=20are=20properly=20included=20f?= =?UTF-8?q?or=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf6dac9..bd547c5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,12 +26,13 @@ include(cmake/ExternalDependencies.cmake) set(MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") set(MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") -# MLIR must be installed on the system +# LLVM / MLIR must be installed on the system +find_package(LLVM REQUIRED CONFIG) find_package(MLIR REQUIRED CONFIG) # LLVM specific setup required for the plugin to work properly include(HandleLLVMOptions) -include_directories(${MLIR_INCLUDE_DIRS}) +include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS}) add_subdirectory(include) add_subdirectory(lib) From 2d84870bbe1124b6ec9c65ad2a4f44afb9f78005 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 14:15:13 +0100 Subject: [PATCH 32/65] =?UTF-8?q?=F0=9F=94=A5=20remove=20mlir=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d58d0a..96f9afd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,15 +87,6 @@ jobs: setup-mlir: true llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" - mlir-tests: - name: 🐉 Test - needs: change-detection - if: fromJSON(needs.change-detection.outputs.run-mlir) - permissions: - contents: read - id-token: write - uses: ./.github/workflows/reusable-mlir-tests.yml - cpp-linter: name: 🇨‌ Lint needs: change-detection @@ -124,7 +115,6 @@ jobs: - python-linter - build-sdist - build-wheel - - mlir-tests - cpp-linter runs-on: ubuntu-latest steps: @@ -148,10 +138,6 @@ jobs: fromJSON(needs.change-detection.outputs.run-cd) && '' || 'build-sdist,build-wheel,' }} - ${{ - fromJSON(needs.change-detection.outputs.run-mlir) - && '' || 'mlir-tests,' - }} ${{ fromJSON(needs.change-detection.outputs.run-cpp-linter) && '' || 'cpp-linter,' From 0215b51bbcb68a416d8f7fc9c19569712ba44411 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 14:15:32 +0100 Subject: [PATCH 33/65] =?UTF-8?q?=F0=9F=8E=A8=20improve=20lit=20discovery?= =?UTF-8?q?=20for=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin.py | 97 ++++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index cf2541b..41cfd01 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -23,6 +23,7 @@ import shutil import subprocess import tempfile +from functools import lru_cache from pathlib import Path from typing import TYPE_CHECKING, Any @@ -64,7 +65,56 @@ def _cleanup_mlir_artifacts() -> None: mlir_file.unlink() -def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "test") -> None: +@lru_cache(maxsize=1) +def _get_filecheck() -> str | None: + """Locate the FileCheck binary. Caches the result for future calls. + + Returns: + The path to the FileCheck binary, or None if not found. + """ + return _find_filecheck() + + +def _find_filecheck() -> str | None: + candidates: list[Path] = [] + + # 1. Explicit override (if CI ever sets it) + if os.environ.get("FILECHECK_PATH"): + candidates.append(Path(os.environ["FILECHECK_PATH"])) + + # 2. LLVM install prefix (best signal) + if os.environ.get("LLVM_INSTALL_PREFIX"): + candidates.append(Path(os.environ["LLVM_INSTALL_PREFIX"]) / "bin" / "FileCheck") + + # 3. LLVM_ROOT (common in setup-mlir) + if os.environ.get("LLVM_ROOT"): + candidates.append(Path(os.environ["LLVM_ROOT"]) / "bin" / "FileCheck") + + # 4. CMake-style LLVM_DIR + if os.environ.get("LLVM_DIR"): + candidates.append(Path(os.environ["LLVM_DIR"]) / ".." / ".." / ".." / "bin" / "FileCheck") + + # 5. CMake-style MLIR_DIR + if os.environ.get("MLIR_DIR"): + candidates.append(Path(os.environ["MLIR_DIR"]) / ".." / ".." / ".." / "bin" / "FileCheck") + + # 6. PATH (last resort) + path_hit = shutil.which("FileCheck") + if path_hit: + candidates.append(Path(path_hit)) + + for c in candidates: + if c.exists() and c.is_file(): + return str(c.resolve()) + + return None + + +def _run_filecheck( + mlir_content: str, + check_patterns: str, + test_name: str = "test", +) -> None: """Run FileCheck on MLIR content using CHECK patterns from a string. Args: @@ -76,54 +126,45 @@ def _run_filecheck(mlir_content: str, check_patterns: str, test_name: str = "tes RuntimeError: If FileCheck is not found AssertionError: If FileCheck validation fails """ - # Find FileCheck (usually in LLVM bin directory) - filecheck = None - possible_paths = [ - "FileCheck", # If in PATH - os.environ.get("FILECHECK_PATH"), # Custom env variable - "/opt/homebrew/opt/llvm/bin/FileCheck", # Common macOS location - ] + filecheck = _get_filecheck() - for path in possible_paths: - if path: - try: - result = subprocess.run([path, "--version"], check=False, capture_output=True, timeout=5) # noqa: S603 - if result.returncode == 0: - filecheck = path - break - except (subprocess.SubprocessError, FileNotFoundError): - continue - - if not filecheck: + if filecheck is None: msg = ( - "FileCheck not found. Please ensure LLVM's FileCheck is in your PATH, " - "or set FILECHECK_PATH environment variable." + "FileCheck not found.\n" + "Tried FILECHECK_PATH, LLVM_INSTALL_PREFIX, LLVM_ROOT, " + "LLVM_DIR, MLIR_DIR, and PATH.\n" + "Ensure LLVM is available or disable FileCheck-based tests." ) raise RuntimeError(msg) - with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", suffix=".mlir", delete=False) as check_file: + with tempfile.NamedTemporaryFile( + encoding="utf-8", + mode="w", + suffix=".mlir", + delete=False, + ) as check_file: check_file.write(check_patterns) check_file_path = check_file.name try: - # Run FileCheck: pipe MLIR content as stdin, use check_file for CHECK directives result = subprocess.run( # noqa: S603 [filecheck, check_file_path, "--allow-unused-prefixes"], - check=False, input=mlir_content.encode(), capture_output=True, + check=False, timeout=30, ) if result.returncode != 0: - error_msg = result.stderr.decode() if result.stderr else "Unknown error" + stderr = result.stderr.decode(errors="replace") if result.stderr else "Unknown error" msg = ( - f"FileCheck failed for {test_name}:\n{error_msg}\n\n" - f"MLIR Output (first 2000 chars):\n{mlir_content[:2000]}..." + f"FileCheck failed for {test_name}:\n" + f"{stderr}\n\n" + f"MLIR Output (first 2000 chars):\n" + f"{mlir_content[:2000]}..." ) raise AssertionError(msg) finally: - # Clean up temporary file Path(check_file_path).unlink() From 71ff792a13576bade77c0daef5cec4be614087ee Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 16:23:00 +0100 Subject: [PATCH 34/65] =?UTF-8?q?=F0=9F=8E=A8=20add=20rabbit=20suggestions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 ---- docs/conf.py | 2 +- test/test_plugin.py | 10 +++++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96f9afd..4b6c545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,10 +122,6 @@ jobs: uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: allowed-skips: >- - ${{ - fromJSON(needs.change-detection.outputs.run-cpp-linter) - && '' || 'cpp-linter,' - }} ${{ fromJSON(needs.change-detection.outputs.run-python-tests) && '' || 'python-tests,python-coverage,python-linter,' diff --git a/docs/conf.py b/docs/conf.py index 9a3efe3..5e6dcd3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,7 +45,7 @@ version = get_version(root=str(ROOT), fallback_root=ROOT) # For Sphinx to find the package, add the python/ directory to sys.path -sys.path.insert(0, Path("../python").resolve()) +sys.path.insert(0, str(ROOT / "python")) # Filter git details from version release = version.split("+")[0] diff --git a/test/test_plugin.py b/test/test_plugin.py index 41cfd01..3f7ab19 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -851,7 +851,7 @@ def test_swap_gate_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] - @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=3)) # type: ignore[untyped-decorator] def circuit() -> None: qml.SWAP(wires=[0, 1]) qml.ctrl(qml.SWAP(wires=[0, 1]), control=2) @@ -900,7 +900,7 @@ def module() -> Any: # noqa: ANN401 check_after_catalyst = """ //CHECK: %[[Q01_1:.*]]:2 = quantum.custom "SWAP"() %[[Q0_0:.*]], %[[Q1_0:.*]] : !quantum.bit, !quantum.bit //CHECK: %[[Q01_2:.*]]:2, %[[Q2_1:.*]] = quantum.custom "CSWAP"() %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1 ctrls(%[[Q2_0:.*]]) ctrlvals(%true) : !quantum.bit, !quantum.bit ctrls !quantum.bit - //CHECK: %[[Q01_3:.*]]:2, %[[Q2_2:.*]] = quantum.custom "CSWAP"() %[[Q01_2:.*]]#0, %[[Q01_2:.*]]#1 ctrls(%[[Q2_1:.*]]) ctrlvals(%true_6) : !quantum.bit, !quantum.bit ctrls !quantum.bit + //CHECK: %[[Q01_3:.*]]:2, %[[Q2_2:.*]] = quantum.custom "CSWAP"() %[[Q01_2:.*]]#0, %[[Q01_2:.*]]#1 ctrls(%[[Q2_1:.*]]) ctrlvals(%true_7) : !quantum.bit, !quantum.bit ctrls !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "SWAP: MQTOpt to CatalystQuantum") @@ -914,7 +914,7 @@ def test_toffoli_gate_roundtrip() -> None: @apply_pass("mqt.mqtopt-to-catalystquantum") # type: ignore[untyped-decorator] @apply_pass("mqt.catalystquantum-to-mqtopt") # type: ignore[untyped-decorator] - @qml.qnode(get_device("lightning.qubit", wires=2)) # type: ignore[untyped-decorator] + @qml.qnode(get_device("lightning.qubit", wires=4)) # type: ignore[untyped-decorator] def circuit() -> None: qml.Toffoli(wires=[0, 1, 2]) qml.ctrl(qml.Toffoli(wires=[0, 1, 2]), control=3) @@ -947,7 +947,7 @@ def module() -> Any: # noqa: ANN401 check_mlir_before = """ //CHECK: %[[Q012_0:.*]]:3 = quantum.custom "Toffoli"() %[[Q0_0:.*]], %[[Q1_0:.*]], %[[Q2_0:.*]] : !quantum.bit, !quantum.bit, !quantum.bit - //CHECK: %[[Q2_1:.*]], %[[Q301_0:.*]]:3 = quantum.custom "PauliX"() %[[Q012_0:.*]]#2 ctrls(%[[Q3_0:.*]], %[[Q012_0:.*]]#0, %[[Q012_0:.*]]#1) ctrlvals(%extracted_8, %extracted_9, %extracted_10) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q2_1:.*]], %[[Q301_0:.*]]:3 = quantum.custom "PauliX"() %[[Q012_0:.*]]#2 ctrls(%[[Q3_0:.*]], %[[Q012_0:.*]]#0, %[[Q012_0:.*]]#1) ctrlvals(%extracted_9, %extracted_10, %extracted_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_before, check_mlir_before, "Toffoli: CatalystQuantum") @@ -959,6 +959,6 @@ def module() -> Any: # noqa: ANN401 check_after_catalyst = """ //CHECK: %[[Q2_1:.*]], %[[Q01_1:.*]]:2 = quantum.custom "Toffoli"() %[[Q2_0:.*]] ctrls(%[[Q0_0:.*]], %[[Q1_0:.*]]) ctrlvals(%true, %true) : !quantum.bit ctrls !quantum.bit, !quantum.bit - //CHECK: %[[Q2_2:.*]], %[[Q301_1:.*]]:3 = quantum.custom "PauliX"() %[[Q2_1:.*]] ctrls(%[[Q3_0:.*]], %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1) ctrlvals(%true_11, %true_11, %true_11) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit + //CHECK: %[[Q2_2:.*]], %[[Q301_1:.*]]:3 = quantum.custom "PauliX"() %[[Q2_1:.*]] ctrls(%[[Q3_0:.*]], %[[Q01_1:.*]]#0, %[[Q01_1:.*]]#1) ctrlvals(%true_12, %true_12, %true_12) : !quantum.bit ctrls !quantum.bit, !quantum.bit, !quantum.bit """ _run_filecheck(mlir_after_roundtrip, check_after_catalyst, "Toffoli: MQTOpt to CatalystQuantum") From 096a6eabfd5d5366e5e5ad1d692be089364d82f2 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 16:42:08 +0100 Subject: [PATCH 35/65] =?UTF-8?q?=E2=9C=85=20improve=20code=20coverage=20f?= =?UTF-8?q?or=20error=20scenarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_plugin_setup.py | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index 4373fbc..efee98d 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -15,12 +15,14 @@ from __future__ import annotations from typing import TYPE_CHECKING +from unittest.mock import MagicMock, patch import pennylane as qml +import pytest from catalyst import pipeline from catalyst.passes import apply_pass, apply_pass_plugin -from mqt.core.plugins.catalyst import get_catalyst_plugin_abs_path +from mqt.core.plugins.catalyst import configure_device_for_mqt, get_catalyst_plugin_abs_path, get_device if TYPE_CHECKING: from pennylane.measurements.state import StateMP @@ -92,3 +94,41 @@ def module() -> StateMP: return qnode() assert "mqt-core-round-trip" in module.mlir + + +def test_get_catalyst_plugin_abs_path_unsupported_platform() -> None: + """Test that get_catalyst_plugin_abs_path raises RuntimeError on unsupported platform.""" + with ( + patch("platform.system", return_value="UnsupportedOS"), + pytest.raises(RuntimeError, match="Unsupported platform: UnsupportedOS"), + ): + get_catalyst_plugin_abs_path() + + +def test_get_catalyst_plugin_abs_path_not_found() -> None: + """Test that get_catalyst_plugin_abs_path raises FileNotFoundError when library is missing.""" + with ( + patch("mqt.core.plugins.catalyst.resources.files", side_effect=Exception("Not found")), + patch("pathlib.Path.exists", return_value=False), + pytest.raises(FileNotFoundError, match="Could not locate catalyst plugin library"), + ): + get_catalyst_plugin_abs_path() + + +def test_configure_device_for_mqt_no_config() -> None: + """Test that configure_device_for_mqt raises ValueError when device has no config_filepath.""" + dev = MagicMock(spec=qml.devices.Device) + dev.config_filepath = None + with pytest.raises(ValueError, match=r"Device does not have a config_filepath attribute set\."): + configure_device_for_mqt(dev) + + +def test_get_device_no_config() -> None: + """Test that get_device raises ValueError when the created device has no config_filepath.""" + with patch("pennylane.device") as mock_qml_device: + mock_dev = MagicMock(spec=qml.devices.Device) + mock_dev.config_filepath = None + mock_qml_device.return_value = mock_dev + + with pytest.raises(ValueError, match=r"Device does not have a config_filepath attribute set\."): + get_device("some.device") From 3fc39f8cf8ba81c4a250f7881b1c848149b6c063 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 16:46:49 +0100 Subject: [PATCH 36/65] =?UTF-8?q?=F0=9F=8E=A8=20improve=20docsting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/catalyst/device.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/device.py b/python/mqt/core/plugins/catalyst/device.py index b550ae9..9413b87 100644 --- a/python/mqt/core/plugins/catalyst/device.py +++ b/python/mqt/core/plugins/catalyst/device.py @@ -25,13 +25,11 @@ def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: This function modifies device capabilities to prevent Catalyst from decomposing controlled gates (like qml.ctrl(PauliX)) into quantum.unitary operations with - explicit matrix parameters. Instead, gates remain as quantum.custom operations - (like "CNOT") that can be converted by the MQT plugin. + explicit matrix parameters. Instead, gates remain as quantum.custom operations that can be converted by the MQT plugin. - The modification uses several techniques: - 1. Removes QubitUnitary from device.operations (prevents matrix decomposition path) - 2. Clears _to_matrix_ops (avoids Catalyst validation requiring QubitUnitary support) - 3. Uses qjit_capabilities hook (injects modified capabilities before QJITDevice init) + 1. Remove QubitUnitary from device.operations (prevents matrix decomposition path) + 2. Clear _to_matrix_ops (avoids Catalyst validation requiring QubitUnitary support) + 3. Use qjit_capabilities hook (injects modified capabilities before QJITDevice init) Args: device: A PennyLane device instance to configure. From 3629e6bb415610dfb994213347ca0367f79e6a58 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Mon, 5 Jan 2026 16:49:09 +0100 Subject: [PATCH 37/65] =?UTF-8?q?=F0=9F=8E=A8=20improve=20docsting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/catalyst/device.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/device.py b/python/mqt/core/plugins/catalyst/device.py index 9413b87..b96ac05 100644 --- a/python/mqt/core/plugins/catalyst/device.py +++ b/python/mqt/core/plugins/catalyst/device.py @@ -25,11 +25,8 @@ def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: This function modifies device capabilities to prevent Catalyst from decomposing controlled gates (like qml.ctrl(PauliX)) into quantum.unitary operations with - explicit matrix parameters. Instead, gates remain as quantum.custom operations that can be converted by the MQT plugin. - - 1. Remove QubitUnitary from device.operations (prevents matrix decomposition path) - 2. Clear _to_matrix_ops (avoids Catalyst validation requiring QubitUnitary support) - 3. Use qjit_capabilities hook (injects modified capabilities before QJITDevice init) + explicit matrix parameters. Instead, gates remain as quantum.custom operations + that can be converted by the MQT plugin. Args: device: A PennyLane device instance to configure. From c31d56f8c57b53a5b53cd427167a813a75e1406d Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 8 Jan 2026 13:41:13 +0100 Subject: [PATCH 38/65] =?UTF-8?q?=F0=9F=8E=A8=20address=20rabbit=20feedbac?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 1 + python/mqt/core/plugins/catalyst/__init__.py | 6 +++++- test/test_plugin_setup.py | 16 ++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b6c545..e74d954 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,7 @@ jobs: contents: read needs: - change-detection + - python-coverage - python-tests - python-tests-extensive - python-linter diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py index 84f55b6..bdce0c2 100644 --- a/python/mqt/core/plugins/catalyst/__init__.py +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -41,7 +41,8 @@ def get_catalyst_plugin_abs_path() -> Path: lib_path = package_path / lib_name if lib_path.is_file(): return Path(str(lib_path)) - except Exception: # noqa: BLE001, S110 + except (AttributeError, TypeError, FileNotFoundError, ModuleNotFoundError): + # Fall back to development build directory if package resources unavailable pass # Fallback: search in development build directory (for editable installs) @@ -58,7 +59,10 @@ def get_catalyst_plugin_abs_path() -> Path: return lib_path # Provide helpful error message + lib_names = [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"] msg = ( + f"Could not locate catalyst plugin library with extension '{ext}'.\n" + f"Searched for: {', '.join(lib_names)}\n" f"Could not locate catalyst plugin library with extension '{ext}'.\n" f"Expected locations:\n" f" - Installed package: {this_file.parent}\n" diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index efee98d..4bd7ef0 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -99,7 +99,7 @@ def module() -> StateMP: def test_get_catalyst_plugin_abs_path_unsupported_platform() -> None: """Test that get_catalyst_plugin_abs_path raises RuntimeError on unsupported platform.""" with ( - patch("platform.system", return_value="UnsupportedOS"), + patch("mqt.core.plugins.catalyst.platform.system", return_value="UnsupportedOS"), pytest.raises(RuntimeError, match="Unsupported platform: UnsupportedOS"), ): get_catalyst_plugin_abs_path() @@ -109,10 +109,18 @@ def test_get_catalyst_plugin_abs_path_not_found() -> None: """Test that get_catalyst_plugin_abs_path raises FileNotFoundError when library is missing.""" with ( patch("mqt.core.plugins.catalyst.resources.files", side_effect=Exception("Not found")), - patch("pathlib.Path.exists", return_value=False), - pytest.raises(FileNotFoundError, match="Could not locate catalyst plugin library"), + patch("mqt.core.plugins.catalyst.Path") as mock_path, ): - get_catalyst_plugin_abs_path() + # Configure the mock Path to fail the fallback search + mock_instance = MagicMock() + mock_instance.resolve.return_value = mock_instance + mock_instance.parent = mock_instance + mock_instance.__truediv__.return_value = mock_instance + mock_instance.exists.return_value = False + mock_path.return_value = mock_instance + + with pytest.raises(FileNotFoundError, match="Could not locate catalyst plugin library"): + get_catalyst_plugin_abs_path() def test_configure_device_for_mqt_no_config() -> None: From 44e42cc4c2e1bc0149eae35ff0c4152dd947e325 Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 8 Jan 2026 13:55:51 +0100 Subject: [PATCH 39/65] =?UTF-8?q?=F0=9F=8E=A8=20imporve=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/mqt/core/plugins/catalyst/__init__.py | 1 - test/test_plugin_setup.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py index bdce0c2..c570636 100644 --- a/python/mqt/core/plugins/catalyst/__init__.py +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -63,7 +63,6 @@ def get_catalyst_plugin_abs_path() -> Path: msg = ( f"Could not locate catalyst plugin library with extension '{ext}'.\n" f"Searched for: {', '.join(lib_names)}\n" - f"Could not locate catalyst plugin library with extension '{ext}'.\n" f"Expected locations:\n" f" - Installed package: {this_file.parent}\n" f" - Development build: {build_dir}\n" diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index 4bd7ef0..1365f87 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -82,7 +82,7 @@ def module() -> StateMP: def test_mqt_dictionary() -> None: - """Generate MLIR for the MQT plugin via entry-point.""" + """Generate MLIR for the MQT plugin via pipeline dictionary.""" @pipeline({"mqt.mqt-core-round-trip": {}}) # type: ignore[untyped-decorator] @qml.qnode(qml.device("null.qubit", wires=0)) # type: ignore[untyped-decorator] @@ -108,7 +108,7 @@ def test_get_catalyst_plugin_abs_path_unsupported_platform() -> None: def test_get_catalyst_plugin_abs_path_not_found() -> None: """Test that get_catalyst_plugin_abs_path raises FileNotFoundError when library is missing.""" with ( - patch("mqt.core.plugins.catalyst.resources.files", side_effect=Exception("Not found")), + patch("mqt.core.plugins.catalyst.resources.files", side_effect=ModuleNotFoundError("Not found")), patch("mqt.core.plugins.catalyst.Path") as mock_path, ): # Configure the mock Path to fail the fallback search From 370efbe1097157b29b7d94f7c8a38a868f38fe8b Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 8 Jan 2026 14:15:27 +0100 Subject: [PATCH 40/65] =?UTF-8?q?=F0=9F=8E=A8=20update=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a81726c..843169a 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ uv venv .venv . .venv/bin/activate # Install Catalyst and build the plugin -uv pip install pennylane-catalyst>0.12.0 +uv pip install pennylane-catalyst==0.13.0 uv sync --verbose --active --config-settings=cmake.define.CMAKE_BUILD_TYPE=Release @@ -124,42 +124,78 @@ uv sync --verbose --active --config-settings=cmake.define.LLVM_DIR="$LLVM_DIR" ``` -### 3) Use the MQT plugin with your PennyLane code +### 3) Use the MQT plugin and explore intermediate MLIR representations The MQT plugin provides device configuration utilities to prevent Catalyst from decomposing gates into unitary matrices, enabling lossless roundtrip conversions. -**Important:** Use `get_device()` from the MQT plugin instead of `qml.device()` directly: +You can inspect the intermediate MLIR representations during the roundtrip between `CatalystQuantum` and `MQTOpt` dialects. + +```python +from __future__ import annotations +from pathlib import Path +from typing import Any -```python3 -import catalyst import pennylane as qml from catalyst.passes import apply_pass from mqt.core.plugins.catalyst import get_device # Use get_device() to configure the device for MQT plugin compatibility -# This prevents gates from being decomposed into unitary matrices device = get_device("lightning.qubit", wires=2) +# Wrap your circuit with the conversion passes @apply_pass("mqt.mqtopt-to-catalystquantum") @apply_pass("mqt.catalystquantum-to-mqtopt") @qml.qnode(device) def circuit() -> None: qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) - # Controlled gates will NOT be decomposed to matrices - qml.ctrl(qml.PauliX(wires=0), control=1) - catalyst.measure(0) - catalyst.measure(1) +# JIT compile using qjit @qml.qjit(target="mlir", autograph=True) def module() -> None: return circuit() -# Get the optimized MLIR representation -mlir_output = module.mlir_opt +# --- Custom pipeline to capture intermediate MLIR --- +custom_pipeline = [ + # Only use the two MQT passes for demonstration + ("to-mqtopt", ["builtin.module(catalystquantum-to-mqtopt)"]), + ("to-catalystquantum", ["builtin.module(mqtopt-to-catalystquantum)"]), +] + + +# JIT compilation with intermediate MLIR files saved +@qml.qjit(target="mlir", autograph=True, keep_intermediate=2, pipelines=custom_pipeline) +def module() -> Any: + return circuit() + + +# Trigger compilation and optimized MLIR generation +module.mlir_opt + +# Catalyst writes intermediate MLIR files to the current working directory +mlir_dir = Path.cwd() +catalyst_mlir = mlir_dir / "0_catalyst_module.mlir" +mlir_to_mqtopt = mlir_dir / "1_CatalystQuantumToMQTOpt.mlir" +mlir_to_catalyst = mlir_dir / "4_MQTOptToCatalystQuantum.mlir" + +# Read MLIR files +with catalyst_mlir.open("r", encoding="utf-8") as f: + mlir_before_conversion = f.read() +with mlir_to_mqtopt.open("r", encoding="utf-8") as f: + mlir_after_conversion = f.read() +with mlir_to_catalyst.open("r", encoding="utf-8") as f: + mlir_after_roundtrip = f.read() + +# Print MLIR contents +print("=== MLIR before conversion to MQTOpt ===") +print(mlir_before_conversion) +print("=== MLIR after conversion to MQTOpt ===") +print(mlir_after_conversion) +print("=== MLIR after roundtrip back to CatalystQuantum ===") +print(mlir_after_roundtrip) ``` **Alternative:** You can also configure an existing device: @@ -180,7 +216,7 @@ The MQT Core Catalyst Plugin is compatible with Python version 3.11 and newer. The MQT Core Catalyst Plugin relies on some external dependencies: - [llvm/llvm-project](https://github.com/llvm/llvm-project): A toolkit for the construction of highly optimized compilers, optimizers, and run-time environments (specific revision: `f8cb7987c64dcffb72414a40560055cb717dbf74`). -- [PennyLaneAI/catalyst](https://github.com/PennyLaneAI/catalyst): A package that enables just-in-time (JIT) compilation of hybrid quantum-classical programs implemented with PennyLane (version > 0.12.0). +- [PennyLaneAI/catalyst](https://github.com/PennyLaneAI/catalyst): A package that enables just-in-time (JIT) compilation of hybrid quantum-classical programs implemented with PennyLane (version == 0.13.0). - [MQT Core](https://github.com/munich-quantum-toolkit/core-plugins-catalyst): Provides the MQTOpt MLIR dialect and supporting infrastructure. Note, both LLVM/MLIR and Catalyst are currently restricted to specific versions. You must build LLVM/MLIR locally from the exact revision specified above and configure CMake to use it (see installation instructions). From ba2dd546057264364eac976e0505a63a4e8723ba Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 11:40:40 +0100 Subject: [PATCH 41/65] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e681574..54ad338 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -37,7 +37,7 @@ jobs: name: 🐍 Coverage needs: [change-detection, python-tests] if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-coverage.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 permissions: contents: read id-token: write @@ -51,7 +51,7 @@ jobs: fail-fast: false matrix: runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: runs-on: ${{ matrix.runs-on }} setup-mlir: true @@ -61,7 +61,7 @@ jobs: name: 🐍 Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@7f8eeed7a3dba08e1e9be54966e5722f5753d0df # v1.17.7 + uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: enable-ty: true setup-mlir: true From 463973c3867cd05cc4ad4f25a4c22265f6014afe Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 11:40:49 +0100 Subject: [PATCH 42/65] =?UTF-8?q?=F0=9F=9B=82=20fix=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/cd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index db23e28..ab8f551 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -8,6 +8,7 @@ on: - .github/workflows/cd.yml permissions: + attestations: write contents: read id-token: write From 73cd343ca8426c2a4b3563a64483cf0839573d00 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 12:15:04 +0100 Subject: [PATCH 43/65] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20restructure=20to=20s?= =?UTF-8?q?atisfy=20ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- python/mqt/core/plugins/catalyst/__init__.py | 78 +----------------- python/mqt/core/plugins/catalyst/device.py | 13 +-- python/mqt/core/plugins/catalyst/plugin.py | 85 ++++++++++++++++++++ 3 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 python/mqt/core/plugins/catalyst/plugin.py diff --git a/python/mqt/core/plugins/catalyst/__init__.py b/python/mqt/core/plugins/catalyst/__init__.py index c570636..4dccca6 100644 --- a/python/mqt/core/plugins/catalyst/__init__.py +++ b/python/mqt/core/plugins/catalyst/__init__.py @@ -1,5 +1,5 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2025 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT @@ -10,78 +10,8 @@ from __future__ import annotations -import platform -from importlib import resources -from pathlib import Path - -from mqt.core.plugins.catalyst.device import configure_device_for_mqt, get_device - - -def get_catalyst_plugin_abs_path() -> Path: - """Locate the mqt-catalyst-plugin shared library. - - Returns: - The absolute path to the plugin shared library. - - Raises: - FileNotFoundError: If the plugin library is not found. - RuntimeError: If the platform is unsupported. - """ - ext = {"Darwin": ".dylib", "Linux": ".so", "Windows": ".dll"}.get(platform.system()) - if ext is None: - msg = f"Unsupported platform: {platform.system()}" - raise RuntimeError(msg) - - # Try to find the plugin library in the package installation directory - try: - if hasattr(resources, "files"): - package_path = resources.files("mqt.core.plugins.catalyst") - # Try both with and without lib prefix - for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: - lib_path = package_path / lib_name - if lib_path.is_file(): - return Path(str(lib_path)) - except (AttributeError, TypeError, FileNotFoundError, ModuleNotFoundError): - # Fall back to development build directory if package resources unavailable - pass - - # Fallback: search in development build directory (for editable installs) - this_file = Path(__file__).resolve() - # python/mqt/core/plugins/catalyst/__init__.py -> go up 6 levels to project root - project_root = this_file.parent.parent.parent.parent.parent.parent - build_dir = project_root / "build" - - if build_dir.exists(): - # Try both with and without lib prefix - for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: - # Search recursively in build directory - for lib_path in build_dir.rglob(lib_name): - return lib_path - - # Provide helpful error message - lib_names = [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"] - msg = ( - f"Could not locate catalyst plugin library with extension '{ext}'.\n" - f"Searched for: {', '.join(lib_names)}\n" - f"Expected locations:\n" - f" - Installed package: {this_file.parent}\n" - f" - Development build: {build_dir}\n" - f"For editable install, ensure the library is built: cmake --build build" - ) - raise FileNotFoundError(msg) - - -def name2pass(name: str) -> tuple[Path, str]: - """Convert a pass name to its plugin path and pass name (required by Catalyst). - - Args: - name: The name of the pass, e.g., "mqt-core-round-trip". - - Returns: - A tuple containing the absolute path to the plugin and the pass name. - """ - return get_catalyst_plugin_abs_path(), name - +from .device import configure_device_for_mqt, get_device +from .plugin import get_catalyst_plugin_abs_path, name2pass __all__ = [ "configure_device_for_mqt", diff --git a/python/mqt/core/plugins/catalyst/device.py b/python/mqt/core/plugins/catalyst/device.py index b96ac05..1dbd795 100644 --- a/python/mqt/core/plugins/catalyst/device.py +++ b/python/mqt/core/plugins/catalyst/device.py @@ -1,5 +1,5 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2025 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT @@ -19,6 +19,12 @@ import pennylane as qml from pennylane.devices.capabilities import DeviceCapabilities +__all__ = ["configure_device_for_mqt", "get_device"] + + +def __dir__() -> list[str]: + return __all__ + def configure_device_for_mqt(device: qml.devices.Device) -> qml.devices.Device: """Configure a PennyLane device to work optimally with the MQT plugin. @@ -96,6 +102,3 @@ def get_device(device_name: str, **kwargs: Any) -> qml.devices.Device: # noqa: """ device = qml.device(device_name, **kwargs) return configure_device_for_mqt(device) - - -__all__ = ["configure_device_for_mqt", "get_device"] diff --git a/python/mqt/core/plugins/catalyst/plugin.py b/python/mqt/core/plugins/catalyst/plugin.py new file mode 100644 index 0000000..419c147 --- /dev/null +++ b/python/mqt/core/plugins/catalyst/plugin.py @@ -0,0 +1,85 @@ +# Copyright (c) 2025 - 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 + +"""Utility functions for the MQT Catalyst Plugin.""" + +import platform +from importlib import resources +from pathlib import Path + +__all__ = ["name2pass"] + + +def __dir__() -> list[str]: + return __all__ + + +def get_catalyst_plugin_abs_path() -> Path: + """Locate the mqt-catalyst-plugin shared library. + + Returns: + The absolute path to the plugin shared library. + + Raises: + FileNotFoundError: If the plugin library is not found. + RuntimeError: If the platform is unsupported. + """ + ext = {"Darwin": ".dylib", "Linux": ".so", "Windows": ".dll"}.get(platform.system()) + if ext is None: + msg = f"Unsupported platform: {platform.system()}" + raise RuntimeError(msg) + + # Try to find the plugin library in the package installation directory + try: + if hasattr(resources, "files"): + package_path = resources.files("mqt.core.plugins.catalyst") + # Try both with and without lib prefix + for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: + lib_path = package_path / lib_name + if lib_path.is_file(): + return Path(str(lib_path)) + except (AttributeError, TypeError, FileNotFoundError, ModuleNotFoundError): + # Fall back to development build directory if package resources unavailable + pass + + # Fallback: search in development build directory (for editable installs) + this_file = Path(__file__).resolve() + # python/mqt/core/plugins/catalyst/__init__.py -> go up 6 levels to project root + project_root = this_file.parent.parent.parent.parent.parent.parent + build_dir = project_root / "build" + + if build_dir.exists(): + # Try both with and without lib prefix + for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: + # Search recursively in build directory + for lib_path in build_dir.rglob(lib_name): + return lib_path + + # Provide helpful error message + lib_names = [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"] + msg = ( + f"Could not locate catalyst plugin library with extension '{ext}'.\n" + f"Searched for: {', '.join(lib_names)}\n" + f"Expected locations:\n" + f" - Installed package: {this_file.parent}\n" + f" - Development build: {build_dir}\n" + f"For editable install, ensure the library is built: cmake --build build" + ) + raise FileNotFoundError(msg) + + +def name2pass(name: str) -> tuple[Path, str]: + """Convert a pass name to its plugin path and pass name (required by Catalyst). + + Args: + name: The name of the pass, e.g., "mqt-core-round-trip". + + Returns: + A tuple containing the absolute path to the plugin and the pass name. + """ + return get_catalyst_plugin_abs_path(), name From 96a382227b37307b4f62d0470084d53f001c9041 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 12:15:16 +0100 Subject: [PATCH 44/65] =?UTF-8?q?=F0=9F=93=84=20update=20license=20headers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/test_plugin.py | 4 ++-- test/test_plugin_setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_plugin.py b/test/test_plugin.py index 3f7ab19..550627f 100644 --- a/test/test_plugin.py +++ b/test/test_plugin.py @@ -1,5 +1,5 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2025 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index 1365f87..1f0db80 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -1,5 +1,5 @@ -# Copyright (c) 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2025 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT From 0cafd3b5f180f3e013013e6e5af739c717470e5f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 12:15:32 +0100 Subject: [PATCH 45/65] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20MQT=20Core?= =?UTF-8?q?=20to=20latest=20stable=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- cmake/ExternalDependencies.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index aaed078..8b5d2f3 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -11,11 +11,11 @@ include(FetchContent) # cmake-format: off -set(MQT_CORE_MINIMUM_VERSION 3.3.3 +set(MQT_CORE_MINIMUM_VERSION 3.4.0 CACHE STRING "MQT Core minimum version") -set(MQT_CORE_VERSION 3.3.3 +set(MQT_CORE_VERSION 3.4.0 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "8c9f6ab24968401e450812fc0ff7d05b5ae07a63" +set(MQT_CORE_REV "6bcc01e7d135058c6439c64fdd5f14b65ab88816" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "munich-quantum-toolkit" CACHE STRING "MQT Core repository owner (change when using a fork)") From dc6e843599cda6b238e300c26140db8c3d7b3d94 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:47:05 +0100 Subject: [PATCH 46/65] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20simplify=20plugin=20?= =?UTF-8?q?library=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- python/mqt/core/plugins/catalyst/plugin.py | 65 ++++++++-------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/plugin.py b/python/mqt/core/plugins/catalyst/plugin.py index 419c147..345398d 100644 --- a/python/mqt/core/plugins/catalyst/plugin.py +++ b/python/mqt/core/plugins/catalyst/plugin.py @@ -8,11 +8,11 @@ """Utility functions for the MQT Catalyst Plugin.""" -import platform -from importlib import resources +import site +from importlib.resources import files from pathlib import Path -__all__ = ["name2pass"] +__all__ = ["get_catalyst_plugin_abs_path", "name2pass"] def __dir__() -> list[str]: @@ -27,48 +27,31 @@ def get_catalyst_plugin_abs_path() -> Path: Raises: FileNotFoundError: If the plugin library is not found. - RuntimeError: If the platform is unsupported. """ - ext = {"Darwin": ".dylib", "Linux": ".so", "Windows": ".dll"}.get(platform.system()) - if ext is None: - msg = f"Unsupported platform: {platform.system()}" - raise RuntimeError(msg) - - # Try to find the plugin library in the package installation directory - try: - if hasattr(resources, "files"): - package_path = resources.files("mqt.core.plugins.catalyst") - # Try both with and without lib prefix - for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: - lib_path = package_path / lib_name - if lib_path.is_file(): - return Path(str(lib_path)) - except (AttributeError, TypeError, FileNotFoundError, ModuleNotFoundError): - # Fall back to development build directory if package resources unavailable - pass - - # Fallback: search in development build directory (for editable installs) - this_file = Path(__file__).resolve() - # python/mqt/core/plugins/catalyst/__init__.py -> go up 6 levels to project root - project_root = this_file.parent.parent.parent.parent.parent.parent - build_dir = project_root / "build" - - if build_dir.exists(): - # Try both with and without lib prefix - for lib_name in [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"]: - # Search recursively in build directory - for lib_path in build_dir.rglob(lib_name): - return lib_path + # Core library name without platform-specific extensions + plugin_lib = "mqt-core-plugins-catalyst" + + # Iterate over files in the package directory + package_path = files("mqt.core.plugins.catalyst") + for file in package_path.iterdir(): + if file.is_file() and plugin_lib in file.name: + return Path(str(file)) + + # For editable installs, search site-packages directly + for site_pkg in site.getsitepackages(): + site_pkg_dir = Path(site_pkg) / "mqt" / "core" / "plugins" / "catalyst" + if site_pkg_dir.exists(): + for file in site_pkg_dir.iterdir(): + if file.is_file() and plugin_lib in file.name: + return file # Provide helpful error message - lib_names = [f"mqt-core-plugins-catalyst{ext}", f"libmqt-core-plugins-catalyst{ext}"] msg = ( - f"Could not locate catalyst plugin library with extension '{ext}'.\n" - f"Searched for: {', '.join(lib_names)}\n" - f"Expected locations:\n" - f" - Installed package: {this_file.parent}\n" - f" - Development build: {build_dir}\n" - f"For editable install, ensure the library is built: cmake --build build" + f"Could not locate catalyst plugin library.\n" + f"Searched for files containing: {plugin_lib}\n" + f"In package directory: {package_path}\n" + f"And in site-packages: {site.getsitepackages()}\n" + f"Ensure the package is properly installed with: pip install -e ." ) raise FileNotFoundError(msg) From bfa6b27b18c73453fa6dc02d507c3a866a8a2e0b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:47:22 +0100 Subject: [PATCH 47/65] =?UTF-8?q?=F0=9F=94=A7=20run=20tests=20in=20paralle?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3785290..f5c6a14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,8 @@ minversion = "9.0" strict = true addopts = [ "-ra", - "-n0", # Disable parallel test execution (tests share MLIR files) + "--numprocesses=auto", # Automatically use all available CPU cores for parallel testing + "--dist=loadfile", # Run tests from the same file sequentially (tests share MLIR files) while parallelizing across files ] filterwarnings = [ ] From fe9e964b82731ccd1e5fb2e54ad63ec3705f2d36 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:47:35 +0100 Subject: [PATCH 48/65] =?UTF-8?q?=F0=9F=9A=A8=20explicitly=20set=20up=20fi?= =?UTF-8?q?lter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f5c6a14..5d607e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,6 +126,9 @@ addopts = [ "--dist=loadfile", # Run tests from the same file sequentially (tests share MLIR files) while parallelizing across files ] filterwarnings = [ + "error", + 'ignore:.*The jaxlib.hlo_helpers submodule is deprecated.*:DeprecationWarning:', + "ignore:.*got an unexpected keyword argument '___pyct_anno'.*:DeprecationWarning:", ] log_level = "INFO" testpaths = ["test"] From c9eeaf380d87627e2e2e3683ee6cbc2d977c6742 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:47:51 +0100 Subject: [PATCH 49/65] =?UTF-8?q?=F0=9F=94=A7=20reduce=20`ty`=20exclusion?= =?UTF-8?q?=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5d607e5..75bff01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -280,10 +280,6 @@ error-on-warning = true [tool.ty.src] exclude = [ "docs/**", - "eval/**", - "mlir/**", - "noxfile.py", - "test/lit.cfg.py", ] From 68531cabcd640f74e888f16e2a32dd3eeb63311e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:48:04 +0100 Subject: [PATCH 50/65] =?UTF-8?q?=E2=9A=A1=20better=20rebuilds=20with=20ca?= =?UTF-8?q?che-keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 75bff01..4ac49b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -270,7 +270,12 @@ environments = [ "sys_platform == 'darwin' and platform_machine == 'arm64' and python_version < '3.14'", "sys_platform == 'linux' and python_version < '3.14'", ] -reinstall-package = ["mqt-core-plugins-catalyst"] +cache-keys = [ + { file = "pyproject.toml" }, + { git = { commit = true, tags = true } }, + { file = "lib/**/*"}, + { file = "include/**/*"}, +] [tool.ty.terminal] From 579b2db84d20f05b4dc2e377316823afdc077852 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:48:29 +0100 Subject: [PATCH 51/65] =?UTF-8?q?=E2=9C=85=20refactor=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/test_plugin_setup.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/test/test_plugin_setup.py b/test/test_plugin_setup.py index 1f0db80..ca4f380 100644 --- a/test/test_plugin_setup.py +++ b/test/test_plugin_setup.py @@ -96,28 +96,18 @@ def module() -> StateMP: assert "mqt-core-round-trip" in module.mlir -def test_get_catalyst_plugin_abs_path_unsupported_platform() -> None: - """Test that get_catalyst_plugin_abs_path raises RuntimeError on unsupported platform.""" - with ( - patch("mqt.core.plugins.catalyst.platform.system", return_value="UnsupportedOS"), - pytest.raises(RuntimeError, match="Unsupported platform: UnsupportedOS"), - ): - get_catalyst_plugin_abs_path() - - def test_get_catalyst_plugin_abs_path_not_found() -> None: """Test that get_catalyst_plugin_abs_path raises FileNotFoundError when library is missing.""" with ( - patch("mqt.core.plugins.catalyst.resources.files", side_effect=ModuleNotFoundError("Not found")), - patch("mqt.core.plugins.catalyst.Path") as mock_path, + patch("mqt.core.plugins.catalyst.plugin.files") as mock_files, + patch("mqt.core.plugins.catalyst.plugin.site.getsitepackages", return_value=["/fake/site-packages"]), ): - # Configure the mock Path to fail the fallback search - mock_instance = MagicMock() - mock_instance.resolve.return_value = mock_instance - mock_instance.parent = mock_instance - mock_instance.__truediv__.return_value = mock_instance - mock_instance.exists.return_value = False - mock_path.return_value = mock_instance + # Configure the mock to return a path that has no library files + mock_package_path = MagicMock() + mock_lib_path = MagicMock() + mock_lib_path.is_file.return_value = False + mock_package_path.__truediv__.return_value = mock_lib_path + mock_files.return_value = mock_package_path with pytest.raises(FileNotFoundError, match="Could not locate catalyst plugin library"): get_catalyst_plugin_abs_path() From 1e91d22f656688996689ff7ba4ff86406c8ee66b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:50:09 +0100 Subject: [PATCH 52/65] =?UTF-8?q?=F0=9F=94=A7=20reduce=20mypy=20ignore=20l?= =?UTF-8?q?ist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ac49b7..6538b5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ explicit_package_bases = true warn_unreachable = true [[tool.mypy.overrides]] -module = ["pytest_console_scripts.*", "pennylane.*", "catalyst.*"] +module = ["pytest_console_scripts.*"] ignore_missing_imports = true [tool.ruff] From 3fbebdbbfa823321cd2ae12649a467ba6370961c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:51:14 +0100 Subject: [PATCH 53/65] =?UTF-8?q?=F0=9F=94=A7=20add=20`sitecustomize.py`?= =?UTF-8?q?=20for=20multiprocess=20with=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- sitecustomize.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sitecustomize.py diff --git a/sitecustomize.py b/sitecustomize.py new file mode 100644 index 0000000..954185c --- /dev/null +++ b/sitecustomize.py @@ -0,0 +1,23 @@ +# Copyright (c) 2025 - 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 + +"""Site customization shim to enable multiprocess coverage collection in tests. + +See https://coverage.readthedocs.io/en/latest/subprocess.html. +""" + +from __future__ import annotations + +try: + import coverage + + coverage.process_startup() +except ImportError: + # The 'coverage' module is optional + # If it is not installed, we do not enable multiprocess coverage collection + pass From f1fee119b6934439bbae0a1249791c52c035c4a6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 13:58:24 +0100 Subject: [PATCH 54/65] =?UTF-8?q?=F0=9F=94=A7=20simplify=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4c8729..66cfae4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,13 +26,12 @@ include(cmake/ExternalDependencies.cmake) set(MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") set(MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include") -# LLVM / MLIR must be installed on the system -find_package(LLVM REQUIRED CONFIG) +# MLIR must be installed on the system find_package(MLIR REQUIRED CONFIG) # LLVM specific setup required for the plugin to work properly include(HandleLLVMOptions) -include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS}) +include_directories(${MLIR_INCLUDE_DIRS}) add_subdirectory(include) add_subdirectory(lib) From 4870515ca8524012e5d04aef199187050b677a87 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:06:14 +0100 Subject: [PATCH 55/65] =?UTF-8?q?=E2=8F=AA=20revert=20conf.py=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/conf.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c7150e9..1c0b6c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,6 @@ from __future__ import annotations -import sys import warnings from importlib import metadata from pathlib import Path @@ -44,8 +43,6 @@ version = get_version(root=str(ROOT), fallback_root=ROOT) -# For Sphinx to find the package, add the python/ directory to sys.path -sys.path.insert(0, str(ROOT / "python")) # Filter git details from version release = version.split("+")[0] From 4468c8b3a9d19335f6ae1d578f7d7a643b9b0bde Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:15:34 +0100 Subject: [PATCH 56/65] =?UTF-8?q?=E2=8F=AA=20revert=20RtD=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .readthedocs.yaml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index d00fa3a..07d7be6 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,37 +10,31 @@ build: os: ubuntu-24.04 tools: python: "3.13" - apt_packages: - graphviz - jobs: post_checkout: + # Skip docs build if the commit message contains "skip ci" - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 + # Skip docs build if there are no changes related to docs - | - if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ python/ .readthedocs.yaml; + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ include/ lib/ python/ .readthedocs.yaml; then exit 183; fi + # Unshallow the git clone and fetch tags to get proper version information - git fetch --unshallow --tags - pre_build: - # Install uv + # Set up uv - asdf plugin add uv - asdf install uv latest - asdf global uv latest - - # Install *only* documentation dependencies - - uv sync --frozen --group docs --no-dev --no-install-project - - # Make Python sources importable without building - - export PYTHONPATH=$PWD/python - + - mkdir -p $HOME/mlir + - curl -LsSf https://github.com/munich-quantum-software/setup-mlir/releases/latest/download/setup-mlir.sh | bash -s -- -v f8cb7987c64dcffb72414a40560055cb717dbf74 -p $HOME/mlir build: html: - - uv run --no-project -m sphinx -T -b html -d docs/_build/doctrees docs $READTHEDOCS_OUTPUT/html - + - MLIR_DIR=$HOME/mlir/lib/cmake/mlir uv run --frozen --no-dev --group docs -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html htmlzip: - - uv run --no-project -m sphinx -T -b dirhtml -d docs/_build/doctrees docs docs/_build/dirhtml + - MLIR_DIR=$HOME/mlir/lib/cmake/mlir uv run --frozen --no-dev --group docs -m sphinx -T -b dirhtml -d docs/_build/doctrees -D language=en docs docs/_build/dirhtml - mkdir -p $READTHEDOCS_OUTPUT/htmlzip - zip -r $READTHEDOCS_OUTPUT/htmlzip/html.zip docs/_build/dirhtml/* From be501b5f0f64808f92904f32d4d00bf1d19475ee Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:31:47 +0100 Subject: [PATCH 57/65] =?UTF-8?q?=F0=9F=94=A5=20remove=20lit=20from=20depe?= =?UTF-8?q?ndencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 1 - uv.lock | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6538b5c..07bec22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -319,7 +319,6 @@ test = [ dev = [ {include-group = "build"}, {include-group = "test"}, - "lit>=18.1.8", "nox>=2025.11.12", "ty==0.0.10", ] diff --git a/uv.lock b/uv.lock index 27a3fff..ef1687f 100644 --- a/uv.lock +++ b/uv.lock @@ -830,15 +830,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, ] -[[package]] -name = "lit" -version = "18.1.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/b4/d7e210971494db7b9a9ac48ff37dfa59a8b14c773f9cf47e6bda58411c0d/lit-18.1.8.tar.gz", hash = "sha256:47c174a186941ae830f04ded76a3444600be67d5e5fb8282c3783fba671c4edb", size = 161127, upload-time = "2024-06-25T14:33:14.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/06/b36f150fa7c5bcc96a31a4d19a20fddbd1d965b6f02510b57a3bb8d4b930/lit-18.1.8-py3-none-any.whl", hash = "sha256:a873ff7acd76e746368da32eb7355625e2e55a2baaab884c9cc130f2ee0300f7", size = 96365, upload-time = "2024-06-25T14:33:12.101Z" }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -977,7 +968,6 @@ build = [ { name = "setuptools-scm", marker = "(python_full_version < '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux')" }, ] dev = [ - { name = "lit", marker = "(python_full_version < '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux')" }, { name = "nox", marker = "(python_full_version < '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux')" }, { name = "pennylane-catalyst", marker = "(python_full_version < '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux')" }, { name = "pytest", marker = "(python_full_version < '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux')" }, @@ -1022,7 +1012,6 @@ build = [ { name = "setuptools-scm", specifier = ">=9.2.2" }, ] dev = [ - { name = "lit", specifier = ">=18.1.8" }, { name = "nox", specifier = ">=2025.11.12" }, { name = "pennylane-catalyst", specifier = "~=0.13.0" }, { name = "pytest", specifier = ">=9.0.1" }, From b676eacab95feee27e36c03704154e1e52062029 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:38:32 +0100 Subject: [PATCH 58/65] =?UTF-8?q?=E2=8F=AA=20revert=20more=20PR=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/cd.yml | 6 +++--- .github/workflows/ci.yml | 2 +- docs/conf.py | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ab8f551..5c6ac4a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,9 +3,6 @@ on: release: types: [published] workflow_dispatch: - pull_request: - paths: - - .github/workflows/cd.yml permissions: attestations: write @@ -17,6 +14,9 @@ jobs: name: 🐍 Packaging uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 + # Builds wheels on all supported platforms using cibuildwheel. + # The wheels are uploaded as GitHub artifacts `dev-cibw-*` or `cibw-*`, depending on whether + # the workflow is triggered from a PR or a release, respectively. build-wheel: name: 🐍 Packaging strategy: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54ad338..e981866 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" build-sdist: - name: CD (sdist) + name: 🚀 CD (sdist) needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cd) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-sdist.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 diff --git a/docs/conf.py b/docs/conf.py index 1c0b6c2..78f0f7b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,7 +43,6 @@ version = get_version(root=str(ROOT), fallback_root=ROOT) - # Filter git details from version release = version.split("+")[0] From 2c5a4c6c690bf5b1c59f9d1782f5b4df7de52c9c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:41:19 +0100 Subject: [PATCH 59/65] =?UTF-8?q?=F0=9F=94=A7=20clean=20up=20the=20CI=20wo?= =?UTF-8?q?rkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/ci.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e981866..84d5cf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,21 +42,6 @@ jobs: contents: read id-token: write - # run extensive Python tests on PRs labeled with the `extensive-python-ci` label - python-tests-extensive: - name: 🐍 Test (Extensive) - needs: change-detection - if: fromJSON(needs.change-detection.outputs.run-python-tests) && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'extensive-python-ci') - strategy: - fail-fast: false - matrix: - runs-on: [ubuntu-22.04, ubuntu-22.04-arm, macos-15, windows-2025] - uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 - with: - runs-on: ${{ matrix.runs-on }} - setup-mlir: true - llvm-version: "f8cb7987c64dcffb72414a40560055cb717dbf74" - python-linter: name: 🐍 Lint needs: change-detection @@ -115,7 +100,6 @@ jobs: - change-detection - python-coverage - python-tests - - python-tests-extensive - python-linter - build-sdist - build-wheel @@ -130,10 +114,6 @@ jobs: fromJSON(needs.change-detection.outputs.run-python-tests) && '' || 'python-tests,python-coverage,python-linter,' }} - ${{ - fromJSON(needs.change-detection.outputs.run-python-tests) && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'extensive-python-ci') - && '' || 'python-tests-extensive,' - }} ${{ fromJSON(needs.change-detection.outputs.run-cd) && '' || 'build-sdist,build-wheel,' From 2d81bd64920d0c6ffadabe6efebf6eece953c9c5 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:53:34 +0100 Subject: [PATCH 60/65] =?UTF-8?q?=F0=9F=94=A7=20ensure=20pre-commit=20is?= =?UTF-8?q?=20run=20on=20a=20reasonable=20version=20of=20Python?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .pre-commit-config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72e3e46..a51f14a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,9 @@ ci: autofix_commit_msg: "🎨 pre-commit fixes" skip: [mypy, ty-check] +default_language_version: + python: "3.13" + repos: # Ensure uv lock file is up-to-date - repo: https://github.com/astral-sh/uv-pre-commit From f3eb902dbc9d4f23b738074b82428cf50a9ce4f2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:53:40 +0100 Subject: [PATCH 61/65] =?UTF-8?q?=E2=8F=AA=20add=20back=20pennylane=20to?= =?UTF-8?q?=20mypy=20exports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 07bec22..d25153c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ explicit_package_bases = true warn_unreachable = true [[tool.mypy.overrides]] -module = ["pytest_console_scripts.*"] +module = ["pytest_console_scripts.*", "pennylane.*"] ignore_missing_imports = true [tool.ruff] From 0d83ce5506f5432cda2fc94b6845f617801b58a8 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:56:21 +0100 Subject: [PATCH 62/65] =?UTF-8?q?=F0=9F=91=8C=20incorporate=20feedback=20f?= =?UTF-8?q?rom=20code=20rabbit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- python/mqt/core/plugins/catalyst/plugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/mqt/core/plugins/catalyst/plugin.py b/python/mqt/core/plugins/catalyst/plugin.py index 345398d..feaea6e 100644 --- a/python/mqt/core/plugins/catalyst/plugin.py +++ b/python/mqt/core/plugins/catalyst/plugin.py @@ -38,7 +38,11 @@ def get_catalyst_plugin_abs_path() -> Path: return Path(str(file)) # For editable installs, search site-packages directly - for site_pkg in site.getsitepackages(): + site_dirs = site.getsitepackages() + user_site = site.getusersitepackages() + if user_site: + site_dirs = [user_site, *site_dirs] + for site_pkg in site_dirs: site_pkg_dir = Path(site_pkg) / "mqt" / "core" / "plugins" / "catalyst" if site_pkg_dir.exists(): for file in site_pkg_dir.iterdir(): @@ -50,7 +54,7 @@ def get_catalyst_plugin_abs_path() -> Path: f"Could not locate catalyst plugin library.\n" f"Searched for files containing: {plugin_lib}\n" f"In package directory: {package_path}\n" - f"And in site-packages: {site.getsitepackages()}\n" + f"And in site-packages: {site_dirs}\n" f"Ensure the package is properly installed with: pip install -e ." ) raise FileNotFoundError(msg) From 30e134292cb5d8205caf5c20f70edad5d3448283 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:57:40 +0100 Subject: [PATCH 63/65] =?UTF-8?q?=F0=9F=8D=8E=20use=20macos-15=20for=20bui?= =?UTF-8?q?lds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5c6ac4a..e0bfb12 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] + runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-15] uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: runs-on: ${{ matrix.runs-on }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84d5cf6..421f7c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] + runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-15] uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: runs-on: ${{ matrix.runs-on }} @@ -65,7 +65,7 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14] + runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-15] uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-packaging-wheel-cibuildwheel.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: runs-on: ${{ matrix.runs-on }} From 8d2c5bcd05c970494b8b3d78bb55f261588e3c13 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Jan 2026 14:58:25 +0100 Subject: [PATCH 64/65] =?UTF-8?q?=E2=8F=AA=20also=20add=20back=20`catalyst?= =?UTF-8?q?.*`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d25153c..1f6b46a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ explicit_package_bases = true warn_unreachable = true [[tool.mypy.overrides]] -module = ["pytest_console_scripts.*", "pennylane.*"] +module = ["pytest_console_scripts.*", "pennylane.*", "catalyst.*"] ignore_missing_imports = true [tool.ruff] From 7fb3d042f291b4dfd07a1c3c7f829ceddc85dc9e Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 9 Jan 2026 15:53:30 +0100 Subject: [PATCH 65/65] =?UTF-8?q?=F0=9F=93=9A=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047b995..fab3162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,21 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- 🐍 Introduce Python package providing Catalyst plugin utilities and device configuration ([#20]) ([**@flowerthrower**]) +- 🧪 Add comprehensive round-trip Python integration tests ([#20]) ([**@flowerthrower**]) - 🔌 Add MLIR plugin for connecting MQT Core with Catalyst ([#3]) ([**@flowerthrower**], [**@burgholzer**]) - 📦 Set up the initial repo structure and configuration ([#1]) ([**@flowerthrower**], [**@burgholzer**]) +### Changed + +- 🔄 Migrate testing infrastructure from LIT/MLIR-level to Python/pytest ([#20]) ([**@flowerthrower**]) +- 👷 Update CI/CD macOS runners to `macos-15` ([#20]) ([**@flowerthrower**]) +- 📦 Bump `mqt-core` version to `v3.4.0` ([#20]) ([**@flowerthrower**]) + +### Removed + +- 🗑️ Remove LIT/MLIR test infrastructure and files ([#20]) ([**@flowerthrower**]) + ## Initial discussions _📚 Refer to the [original MQT Core PR] for initial discussions and decisions leading to this project._ @@ -24,6 +36,7 @@ _📚 Refer to the [original MQT Core PR] for initial discussions and decisions +[#20]: https://github.com/munich-quantum-toolkit/core-plugins-catalyst/pull/20 [#3]: https://github.com/munich-quantum-toolkit/core-plugins-catalyst/pull/3 [#1]: https://github.com/munich-quantum-toolkit/core-plugins-catalyst/pull/1