diff --git a/frontend/catalyst/passes/__init__.py b/frontend/catalyst/passes/__init__.py index 120e1cea8..7fc70b51f 100644 --- a/frontend/catalyst/passes/__init__.py +++ b/frontend/catalyst/passes/__init__.py @@ -36,6 +36,7 @@ from catalyst.passes.builtin_passes import ( cancel_inverses, commute_ppr, + get_ppm_spec, ions_decomposition, merge_ppr_ppm, merge_rotations, @@ -49,6 +50,7 @@ "to_ppr", "commute_ppr", "cancel_inverses", + "get_ppm_spec", "ions_decomposition", "merge_rotations", "Pass", diff --git a/frontend/catalyst/passes/builtin_passes.py b/frontend/catalyst/passes/builtin_passes.py index bef361ec8..1a01dbd29 100644 --- a/frontend/catalyst/passes/builtin_passes.py +++ b/frontend/catalyst/passes/builtin_passes.py @@ -15,10 +15,28 @@ """This module exposes built-in Catalyst MLIR passes to the frontend.""" import functools +import json +from catalyst.compiler import _quantum_opt from catalyst.passes.pass_api import PassPipelineWrapper +def get_ppm_spec(QJIT): + """TODO: docstring""" + + if QJIT.mlir is not None: + # aot mode + raw_result = _quantum_opt( + ("--pass-pipeline", "builtin.module(ppm-specs)"), [], stdin=QJIT.mlir_opt + ) + return json.loads( + raw_result[: raw_result.index("module")] + ) # remove MLIR starting with substring "module..." + + else: + raise NotImplementedError("PPM passes only support AOT (Ahead-Of-Time) compilation mode.") + + ## API ## def cancel_inverses(qnode): """ diff --git a/frontend/test/lit/test_ppm_specs.py b/frontend/test/lit/test_ppm_specs.py new file mode 100644 index 000000000..ffe1f1dd3 --- /dev/null +++ b/frontend/test/lit/test_ppm_specs.py @@ -0,0 +1,243 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file performs the frontend tests for printing PPR/PPM specs after the PPR and PPM passes +are correctly lowered.""" + +# RUN: %PYTHON %s | FileCheck %s + +# pylint: disable=line-too-long + +import pennylane as qml + +from catalyst import measure, qjit +from catalyst.passes import ( + commute_ppr, + get_ppm_spec, + merge_ppr_ppm, + ppm_compilation, + ppr_to_ppm, + to_ppr, +) + + +def test_convert_clifford_to_ppr(): + """ + Test the `to_ppr` pass. + Check that the original qnode is correctly kept and untransformed. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + @to_ppr + @qml.qnode(qml.device("null.qubit", wires=2)) + def circuit(): + qml.H(0) + qml.S(1) + qml.T(0) + qml.CNOT([0, 1]) + return measure(0) + + print(get_ppm_spec(circuit)) + + +# CHECK: {'circuit_0': {'max_weight_pi4': 2, 'max_weight_pi8': 1, 'num_logical_qubits': 2, 'num_of_ppm': 1, 'num_pi4_gates': 7, 'num_pi8_gates': 1}} +test_convert_clifford_to_ppr() + + +def test_commute_ppr(): + """ + Test the `commute_ppr` pass. + Ensure that the `qec.ppr` with pi/8 rotations are moved to the beginning of the circuit. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + @commute_ppr + @to_ppr + @qml.qnode(qml.device("null.qubit", wires=2)) + def cir_commute_ppr(): + qml.H(0) + qml.S(1) + qml.T(0) + qml.T(1) + qml.CNOT([0, 1]) + return measure(0), measure(1) + + print(get_ppm_spec(cir_commute_ppr)) + + +# CHECK: {'cir_commute_ppr_0': {'max_weight_pi4': 2, 'max_weight_pi8': 1, 'num_logical_qubits': 2, 'num_of_ppm': 2, 'num_pi4_gates': 7, 'num_pi8_gates': 2}} +test_commute_ppr() + + +def test_commute_ppr_max_pauli_size(): + """ + Test the `commute_ppr` pass with max_pauli_size. + The Pauli string should not be larger than max_pauli_size. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + @commute_ppr(max_pauli_size=2) + @to_ppr + @qml.qnode(qml.device("null.qubit", wires=2)) + def cir_commute_ppr_max_pauli_size(): + qml.CNOT([0, 2]) + qml.T(0) + qml.T(1) + qml.CNOT([0, 1]) + qml.S(0) + qml.H(0) + qml.T(0) + return measure(0), measure(1) + + print(get_ppm_spec(cir_commute_ppr_max_pauli_size)) + + +# CHECK: {'cir_commute_ppr_max_pauli_size_0': {'max_weight_pi4': 2, 'max_weight_pi8': 2, 'num_logical_qubits': 2, 'num_of_ppm': 2, 'num_pi4_gates': 10, 'num_pi8_gates': 3}} +test_commute_ppr_max_pauli_size() + + +def test_merge_ppr_ppm(): + """ + Test the `merge_ppr_ppm` pass. + `qec.ppr` should be merged into `qec.ppm`, thus no `qec.ppr` should be left. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + @merge_ppr_ppm + @to_ppr + @qml.qnode(qml.device("null.qubit", wires=2)) + def cir_merge_ppr_ppm(): + qml.H(0) + qml.S(1) + qml.CNOT([0, 1]) + return measure(0), measure(1) + + print(get_ppm_spec(cir_merge_ppr_ppm)) + + +# CHECK: {'cir_merge_ppr_ppm_0': {'num_logical_qubits': 2, 'num_of_ppm': 2}} +test_merge_ppr_ppm() + + +def test_merge_ppr_ppm_max_pauli_size(): + """ + Test the `merge_ppr_ppm` pass with max_pauli_size. + The Pauli string should not be larger than max_pauli_size. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + @merge_ppr_ppm(max_pauli_size=1) + @to_ppr + @qml.qnode(qml.device("null.qubit", wires=2)) + def cir_merge_ppr_ppm_max_pauli_size(): + qml.CNOT([0, 2]) + qml.T(0) + qml.T(1) + qml.CNOT([0, 1]) + return measure(0), measure(1) + + print(get_ppm_spec(cir_merge_ppr_ppm_max_pauli_size)) + + +# CHECK: {'cir_merge_ppr_ppm_max_pauli_size_0': {'max_weight_pi4': 2, 'max_weight_pi8': 1, 'num_logical_qubits': 2, 'num_of_ppm': 2, 'num_pi4_gates': 3, 'num_pi8_gates': 2}} +test_merge_ppr_ppm_max_pauli_size() + + +def test_ppr_to_ppm(): + """ + Test the pipeline `ppr_to_ppm` pass. + Check that the `qec.ppr` is correctly decomposed into `qec.ppm`. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + device = qml.device("null.qubit", wires=2) + + @qjit(pipelines=pipe, target="mlir") + def circuit_ppr_to_ppm(): + + @ppr_to_ppm + @to_ppr + @qml.qnode(device) + def cir_default(): + qml.S(0) + + @ppr_to_ppm(decompose_method="clifford-corrected", avoid_y_measure=True) + @to_ppr + @qml.qnode(device) + def cir_inject_magic_state(): + qml.T(0) + qml.CNOT([0, 1]) + + return cir_default(), cir_inject_magic_state() + + print(get_ppm_spec(circuit_ppr_to_ppm)) + + +# CHECK: {'cir_default_0': {'max_weight_pi2': 1, 'num_logical_qubits': 2, 'num_of_ppm': 2, 'num_pi2_gates': 1}, 'cir_inject_magic_state_0': {'max_weight_pi2': 2, 'num_logical_qubits': 2, 'num_of_ppm': 10, 'num_pi2_gates': 5}} +test_ppr_to_ppm() + + +def test_clifford_to_ppm(): + """ + Test the pipeline `to_ppm` pass. + Check whole pipeline of PPM's sub-passes. + The Pauli string should not be larger than max_pauli_size, but in ppr_to_ppm, + the Pauli string can increase by one because of an additional auxiliary qubit. + """ + + pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] + + @qjit(pipelines=pipe, target="mlir") + def test_clifford_to_ppm_workflow(): + + @ppm_compilation + @qml.qnode(qml.device("null.qubit", wires=2)) + def cir_clifford_to_ppm(): + qml.H(0) + qml.CNOT(wires=[0, 1]) + qml.T(0) + qml.T(1) + return [measure(0), measure(1)] + + @ppm_compilation( + decompose_method="clifford-corrected", avoid_y_measure=True, max_pauli_size=2 + ) + @qml.qnode(qml.device("null.qubit", wires=5)) + def cir_clifford_to_ppm_with_params(): + for idx in range(5): + qml.H(idx) + qml.CNOT(wires=[idx, idx + 1]) + qml.CNOT(wires=[idx + 1, (idx + 2) % 5]) + qml.T(idx) + qml.T(idx + 1) + return [measure(idx) for idx in range(5)] + + return cir_clifford_to_ppm(), cir_clifford_to_ppm_with_params() + + print(get_ppm_spec(test_clifford_to_ppm_workflow)) + + +# CHECK: {'cir_clifford_to_ppm_0': {'max_weight_pi2': 2, 'num_logical_qubits': 2, 'num_of_ppm': 8, 'num_pi2_gates': 2}, 'cir_clifford_to_ppm_with_params_0': {'max_weight_pi2': 2, 'num_logical_qubits': 5, 'num_of_ppm': 119, 'num_pi2_gates': 57}} +test_clifford_to_ppm() diff --git a/frontend/test/pytest/test_peephole_optimizations.py b/frontend/test/pytest/test_peephole_optimizations.py index 596f756fc..ab2660937 100644 --- a/frontend/test/pytest/test_peephole_optimizations.py +++ b/frontend/test/pytest/test_peephole_optimizations.py @@ -22,6 +22,7 @@ from catalyst.passes import ( cancel_inverses, commute_ppr, + get_ppm_spec, merge_ppr_ppm, merge_rotations, ppm_compilation, @@ -209,6 +210,13 @@ def f(): assert 'transform.apply_registered_pass "to-ppr"' not in optimized_ir assert "qec.ppr" in optimized_ir + ppm_specs = get_ppm_spec(test_convert_clifford_to_ppr_workflow) + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_pi4_gates"] == 7 + assert ppm_specs["f_0"]["max_weight_pi4"] == 2 + assert ppm_specs["f_0"]["num_pi8_gates"] == 1 + assert ppm_specs["f_0"]["max_weight_pi8"] == 1 + def test_commute_ppr(): @@ -235,6 +243,14 @@ def f(): assert "qec.ppr" in optimized_ir assert "qec.ppm" in optimized_ir + ppm_specs = get_ppm_spec(test_commute_ppr_workflow) + assert ppm_specs["f_0"]["num_of_ppm"] == 2 + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_pi4_gates"] == 7 + assert ppm_specs["f_0"]["max_weight_pi4"] == 2 + assert ppm_specs["f_0"]["num_pi8_gates"] == 1 + assert ppm_specs["f_0"]["max_weight_pi8"] == 1 + def test_merge_ppr_ppm(): @@ -260,6 +276,10 @@ def f(): assert 'qec.ppm ["Z", "X"]' in optimized_ir assert 'qec.ppm ["X"]' in optimized_ir + ppm_specs = get_ppm_spec(test_merge_ppr_ppm_workflow) + assert ppm_specs["f_0"]["num_of_ppm"] == 2 + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + def test_ppr_to_ppm(): @@ -295,6 +315,12 @@ def f(): assert "qec.select.ppm" in optimized_ir assert 'qec.ppr ["X"]' in optimized_ir + ppm_specs = get_ppm_spec(test_ppr_to_ppm_workflow) + assert ppm_specs["f_0"]["num_of_ppm"] == 19 + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_pi2_gates"] == 8 + assert ppm_specs["f_0"]["max_weight_pi2"] == 2 + def test_ppr_to_ppm_inject_magic_state(): @@ -326,6 +352,12 @@ def f(): assert 'transform.apply_registered_pass "decompose-non-clifford-ppr"' not in optimized_ir assert 'transform.apply_registered_pass "decompose-clifford-ppr"' not in optimized_ir + ppm_specs = get_ppm_spec(test_ppr_to_ppm_workflow) + assert ppm_specs["f_0"]["num_of_ppm"] == 20 + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_pi2_gates"] == 9 + assert ppm_specs["f_0"]["max_weight_pi2"] == 2 + def test_commute_ppr_and_merge_ppr_ppm_with_max_pauli_size(): @@ -371,6 +403,20 @@ def g(): assert 'transform.apply_registered_pass "commute-ppr"' not in optimized_ir assert 'transform.apply_registered_pass "merge-ppr-ppm"' not in optimized_ir + ppm_specs = get_ppm_spec(test_convert_clifford_to_ppr_workflow) + + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_of_ppm"] == 2 + assert ppm_specs["f_0"]["num_pi8_gates"] == 1 + assert ppm_specs["f_0"]["max_weight_pi8"] == 1 + + assert ppm_specs["g_0"]["num_logical_qubits"] == 2 + assert ppm_specs["g_0"]["num_of_ppm"] == 2 + assert ppm_specs["g_0"]["num_pi4_gates"] == 3 + assert ppm_specs["g_0"]["max_weight_pi4"] == 2 + assert ppm_specs["g_0"]["num_pi8_gates"] == 2 + assert ppm_specs["g_0"]["max_weight_pi8"] == 1 + def test_clifford_to_ppm(): @@ -409,6 +455,15 @@ def g(): assert 'qec.ppm ["Z", "Y"]' in optimized_ir assert 'qec.ppr ["X", "Z"](2)' in optimized_ir + ppm_specs = get_ppm_spec(test_clifford_to_ppm_workflow) + + assert ppm_specs["f_0"]["num_logical_qubits"] == 2 + assert ppm_specs["f_0"]["num_of_ppm"] == 7 + assert ppm_specs["f_0"]["num_pi2_gates"] == 2 + assert ppm_specs["f_0"]["max_weight_pi2"] == 2 + + assert ppm_specs["g_0"]["num_logical_qubits"] == 2 + if __name__ == "__main__": pytest.main(["-x", __file__]) diff --git a/mlir/include/QEC/Transforms/Passes.h b/mlir/include/QEC/Transforms/Passes.h index 32c532608..d7ac5f535 100644 --- a/mlir/include/QEC/Transforms/Passes.h +++ b/mlir/include/QEC/Transforms/Passes.h @@ -29,5 +29,5 @@ std::unique_ptr createMergePPRIntoPPMPass(); std::unique_ptr createDecomposeNonCliffordPPRPass(); std::unique_ptr createDecomposeCliffordPPRPass(); std::unique_ptr createPPMCompilationPass(); - +std::unique_ptr createCountPPMSpecsPass(); } // namespace catalyst diff --git a/mlir/include/QEC/Transforms/Passes.td b/mlir/include/QEC/Transforms/Passes.td index 4155536dd..02673144d 100644 --- a/mlir/include/QEC/Transforms/Passes.td +++ b/mlir/include/QEC/Transforms/Passes.td @@ -125,4 +125,10 @@ def PPMCompilationPass : Pass<"ppm-compilation"> { let options = [MaxPauliSizeOption, DecomposeMethodOption, AvoidYMeasureOption]; } +def CountPPMSpecsPass : Pass<"ppm-specs"> { + let summary = "Count specs in Pauli Product Measurement operations."; + + let constructor = "catalyst::createCountPPMSpecsPass()"; +} + #endif // QEC_PASSES diff --git a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp index 59dc1f42c..ce04d1670 100644 --- a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp +++ b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp @@ -38,6 +38,7 @@ void catalyst::registerAllCatalystPasses() mlir::registerPass(catalyst::createPPMCompilationPass); mlir::registerPass(catalyst::createDecomposeNonCliffordPPRPass); mlir::registerPass(catalyst::createDecomposeCliffordPPRPass); + mlir::registerPass(catalyst::createCountPPMSpecsPass); mlir::registerPass(catalyst::createDetensorizeSCFPass); mlir::registerPass(catalyst::createDisableAssertionPass); mlir::registerPass(catalyst::createDisentangleCNOTPass); diff --git a/mlir/lib/QEC/Transforms/CMakeLists.txt b/mlir/lib/QEC/Transforms/CMakeLists.txt index b517f5477..d8a912810 100644 --- a/mlir/lib/QEC/Transforms/CMakeLists.txt +++ b/mlir/lib/QEC/Transforms/CMakeLists.txt @@ -8,6 +8,7 @@ file(GLOB SRC commute_ppr.cpp MergePPRIntoPPM.cpp merge_ppr_into_ppm.cpp + CountPPMSpecs.cpp decompose_non_clifford_ppr.cpp DecomposeNonCliffordPPR.cpp decompose_clifford_ppr.cpp @@ -31,7 +32,27 @@ set(DEPENDS add_mlir_library(${LIBRARY_NAME} STATIC ${SRC} LINK_LIBS PRIVATE ${LIBS} DEPENDS ${DEPENDS}) target_compile_features(${LIBRARY_NAME} PUBLIC cxx_std_20) + +include(FetchContent) +FetchContent_Declare(json + URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz + URL_HASH SHA256=d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d + DOWNLOAD_EXTRACT_TIMESTAMP true + SYSTEM +) +FetchContent_MakeAvailable(json) +target_link_libraries(${LIBRARY_NAME} + PRIVATE nlohmann_json::nlohmann_json +) + +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") +set_source_files_properties( + CountPPMSpecs.cpp + COMPILE_FLAGS "-Wno-covered-switch-default" +) +endif() + target_include_directories(${LIBRARY_NAME} PUBLIC . ${PROJECT_SOURCE_DIR}/include - ${CMAKE_BINARY_DIR}/include) + ${CMAKE_BINARY_DIR}/include) \ No newline at end of file diff --git a/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp b/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp new file mode 100644 index 000000000..f9cdf04e3 --- /dev/null +++ b/mlir/lib/QEC/Transforms/CountPPMSpecs.cpp @@ -0,0 +1,124 @@ +// Copyright 2025 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define DEBUG_TYPE "ppm-specs" + +#include +#include + +#include + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/Pass/Pass.h" + +#include "QEC/IR/QECDialect.h" +#include "Quantum/IR/QuantumOps.h" + +using namespace llvm; +using namespace mlir; +using namespace catalyst; +using namespace catalyst::qec; +using json = nlohmann::json; + +namespace catalyst { +namespace qec { + +#define GEN_PASS_DEF_COUNTPPMSPECSPASS +#define GEN_PASS_DECL_COUNTPPMSPECSPASS +#include "QEC/Transforms/Passes.h.inc" + +struct CountPPMSpecsPass : public impl::CountPPMSpecsPassBase { + using CountPPMSpecsPassBase::CountPPMSpecsPassBase; + + llvm::DenseMap> + countLogicalQubit(Operation *op, + llvm::DenseMap> PPMSpecs) + { + uint64_t numQubits = cast(op).getNqubitsAttr().value_or(0); + assert(numQubits != 0 && "PPM specs with dynamic number of qubits is not implemented"); + auto parentFuncOp = op->getParentOfType(); + PPMSpecs[parentFuncOp.getName()]["num_logical_qubits"] = numQubits; + return PPMSpecs; + } + + llvm::DenseMap> + countPPM(Operation *op, llvm::DenseMap> PPMSpecs) + { + auto parentFuncOp = op->getParentOfType(); + PPMSpecs[parentFuncOp.getName()]["num_of_ppm"]++; + return PPMSpecs; + } + + llvm::DenseMap> + countPPR(Operation *op, llvm::DenseMap> PPMSpecs, + llvm::BumpPtrAllocator *stringAllocator) + { + int16_t rotationKind = + cast(op).getRotationKindAttr().getValue().getZExtValue(); + auto PauliProductAttr = cast(op).getPauliProductAttr(); + auto parentFuncOp = op->getParentOfType(); + StringRef funcName = parentFuncOp.getName(); + if (rotationKind) { + llvm::StringSaver saver(*stringAllocator); + StringRef numRotationKindKey = + saver.save("num_pi" + std::to_string(abs(rotationKind)) + "_gates"); + StringRef maxWeightRotationKindKey = + saver.save("max_weight_pi" + std::to_string(abs(rotationKind))); + PPMSpecs[funcName][numRotationKindKey]++; + PPMSpecs[funcName][maxWeightRotationKindKey] = + std::max(PPMSpecs[funcName][maxWeightRotationKindKey], + static_cast(PauliProductAttr.size())); + } + return PPMSpecs; + } + void printSpecs() + { + llvm::BumpPtrAllocator stringAllocator; + llvm::DenseMap> PPMSpecs; + // StringRef funcName; + // Walk over all operations in the IR (could be ModuleOp or FuncOp) + getOperation()->walk([&](Operation *op) { + // Skip top-level container ops if desired + if (isa(op)) + return; + + else if (isa(op)) { + PPMSpecs = countLogicalQubit(op, PPMSpecs); + } + + else if (isa(op)) { + PPMSpecs = countPPM(op, PPMSpecs); + } + + else if (isa(op)) { + PPMSpecs = countPPR(op, PPMSpecs, &stringAllocator); + } + }); + + json PPMSpecsJson = PPMSpecs; + llvm::outs() << PPMSpecsJson.dump(4) + << "\n"; // dump(4) makes an indent with 4 spaces when printing JSON + return; + } + + void runOnOperation() final { printSpecs(); } +}; + +} // namespace qec + +/// Create a pass for lowering operations in the `QECDialect`. +std::unique_ptr createCountPPMSpecsPass() { return std::make_unique(); } + +} // namespace catalyst diff --git a/mlir/test/QEC/PPMSpecsTest.mlir b/mlir/test/QEC/PPMSpecsTest.mlir new file mode 100644 index 000000000..3a4fed299 --- /dev/null +++ b/mlir/test/QEC/PPMSpecsTest.mlir @@ -0,0 +1,154 @@ +// Copyright 2025 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// RUN: quantum-opt --to-ppr --ppm-specs --commute-ppr --ppm-specs --merge-ppr-ppm --ppm-specs --decompose-non-clifford-ppr --ppm-specs --decompose-clifford-ppr --ppm-specs --split-input-file -verify-diagnostics %s > %t.ppm + +func.func @test_clifford_t_to_ppm_1() -> (tensor, tensor) { + %0 = quantum.alloc( 2) : !quantum.reg + %1 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit + %out_qubits = quantum.custom "S"() %1 : !quantum.bit + %2 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %out_qubits_0 = quantum.custom "Hadamard"() %2 : !quantum.bit + %out_qubits_1 = quantum.custom "T"() %out_qubits_0 : !quantum.bit + %out_qubits_2:2 = quantum.custom "CNOT"() %out_qubits_1, %out_qubits : !quantum.bit, !quantum.bit + %mres_0, %out_qubit_0 = quantum.measure %out_qubits_2#0 : i1, !quantum.bit + %from_elements_0 = tensor.from_elements %mres_0 : tensor + %mres_1, %out_qubit_1 = quantum.measure %out_qubits_2#1 : i1, !quantum.bit + %from_elements_1 = tensor.from_elements %mres_1 : tensor + %3 = quantum.insert %0[ 0], %out_qubit_0 : !quantum.reg, !quantum.bit + %4 = quantum.insert %3[ 1], %out_qubit_1 : !quantum.reg, !quantum.bit + quantum.dealloc %4 : !quantum.reg + return %from_elements_0, %from_elements_1 : tensor, tensor + // CHECK: { + // CHECK: "test_clifford_t_to_ppm_1": { + // CHECK: "max_weight_pi4": 2, + // CHECK: "max_weight_pi8": 1, + // CHECK: "num_logical_qubits": 2, + // CHECK: "num_of_ppm": 2, + // CHECK: "num_pi4_gates": 7, + // CHECK: "num_pi8_gates": 1 + // CHECK: } + // CHECK: } + // CHECK: { + // CHECK: "test_clifford_t_to_ppm_1": { + // CHECK: "max_weight_pi4": 2, + // CHECK: "max_weight_pi8": 1, + // CHECK: "num_logical_qubits": 2, + // CHECK: "num_of_ppm": 2, + // CHECK: "num_pi4_gates": 7, + // CHECK: "num_pi8_gates": 1 + // CHECK: } + // CHECK: } + // CHECK: { + // CHECK: "test_clifford_t_to_ppm_1": { + // CHECK: "max_weight_pi8": 1, + // CHECK: "num_logical_qubits": 2, + // CHECK: "num_of_ppm": 2, + // CHECK: "num_pi8_gates": 1 + // CHECK: } + // CHECK: } + // CHECK: { + // CHECK: "test_clifford_t_to_ppm_1": { + // CHECK: "max_weight_pi2": 1, + // CHECK: "num_logical_qubits": 2, + // CHECK: "num_of_ppm": 5, + // CHECK: "num_pi2_gates": 1 + // CHECK: } + // CHECK: } + // CHECK: { + // CHECK: "test_clifford_t_to_ppm_1": { + // CHECK: "max_weight_pi2": 1, + // CHECK: "num_logical_qubits": 2, + // CHECK: "num_of_ppm": 5, + // CHECK: "num_pi2_gates": 1 + // CHECK: } + // CHECK: } +} + +// RUN: quantum-opt --ppm-compilation --ppm-specs --split-input-file -verify-diagnostics %s > %t.ppm + +func.func @game_of_surface_code() -> (tensor, tensor, tensor, tensor) { + %c = stablehlo.constant dense<0> : tensor + %extracted = tensor.extract %c[] : tensor + %c_0 = stablehlo.constant dense<4> : tensor + %0 = quantum.alloc( 4) : !quantum.reg + %c_1 = stablehlo.constant dense<3> : tensor + %extracted_2 = tensor.extract %c_1[] : tensor + %1 = quantum.extract %0[%extracted_2] : !quantum.reg -> !quantum.bit + // PPR X(-pi/4) = H Tdag H + %out_qubits = quantum.custom "Hadamard"() %1 : !quantum.bit + %out_qubits_3 = quantum.custom "T"() %out_qubits adj : !quantum.bit + %out_qubits_4 = quantum.custom "Hadamard"() %out_qubits_3 : !quantum.bit + %extracted_5 = tensor.extract %c[] : tensor + %2 = quantum.extract %0[%extracted_5] : !quantum.reg -> !quantum.bit + %out_qubits_6 = quantum.custom "T"() %2 : !quantum.bit + %c_7 = stablehlo.constant dense<2> : tensor + %extracted_8 = tensor.extract %c_7[] : tensor + %3 = quantum.extract %0[%extracted_8] : !quantum.reg -> !quantum.bit + %c_9 = stablehlo.constant dense<1> : tensor + %extracted_10 = tensor.extract %c_9[] : tensor + %4 = quantum.extract %0[%extracted_10] : !quantum.reg -> !quantum.bit + %out_qubits_11:2 = quantum.custom "CNOT"() %3, %4 : !quantum.bit, !quantum.bit + %out_qubits_12:2 = quantum.custom "CNOT"() %out_qubits_11#1, %out_qubits_6 : !quantum.bit, !quantum.bit + %out_qubits_13:2 = quantum.custom "CNOT"() %out_qubits_4, %out_qubits_12#1 : !quantum.bit, !quantum.bit + // PPR X(pi/4) = H T H + %out_qubits_14 = quantum.custom "T"() %out_qubits_13#1 : !quantum.bit + %out_qubits_15 = quantum.custom "Hadamard"() %out_qubits_14 : !quantum.bit + %out_qubits_16 = quantum.custom "T"() %out_qubits_15 adj : !quantum.bit + %out_qubits_17 = quantum.custom "Hadamard"() %out_qubits_16 : !quantum.bit + %mres, %out_qubit = quantum.measure %out_qubits_17 : i1, !quantum.bit + %from_elements = tensor.from_elements %mres : tensor + %out_qubits_18 = quantum.custom "S"() %out_qubits_12#0 : !quantum.bit + %out_qubits_19 = quantum.custom "Hadamard"() %out_qubits_18 : !quantum.bit + %out_qubits_20 = quantum.custom "T"() %out_qubits_19 : !quantum.bit + %out_qubits_21 = quantum.custom "Hadamard"() %out_qubits_20 : !quantum.bit + %mres_22, %out_qubit_23 = quantum.measure %out_qubits_21 : i1, !quantum.bit + %from_elements_24 = tensor.from_elements %mres_22 : tensor + %out_qubits_25 = quantum.custom "Hadamard"() %out_qubits_11#0 : !quantum.bit + %out_qubits_26 = quantum.custom "T"() %out_qubits_25 : !quantum.bit + %out_qubits_27 = quantum.custom "Hadamard"() %out_qubits_26 : !quantum.bit + %out_qubits_28 = quantum.custom "T"() %out_qubits_27 : !quantum.bit + %out_qubits_29 = quantum.custom "Hadamard"() %out_qubits_28 : !quantum.bit + %out_qubits_30 = quantum.custom "T"() %out_qubits_29 : !quantum.bit + %out_qubits_31 = quantum.custom "Hadamard"() %out_qubits_30 : !quantum.bit + %mres_32, %out_qubit_33 = quantum.measure %out_qubits_31 : i1, !quantum.bit + %from_elements_34 = tensor.from_elements %mres_32 : tensor + %out_qubits_35 = quantum.custom "S"() %out_qubits_13#0 : !quantum.bit + %out_qubits_36 = quantum.custom "Hadamard"() %out_qubits_35 : !quantum.bit + %out_qubits_37 = quantum.custom "T"() %out_qubits_36 : !quantum.bit + %out_qubits_38 = quantum.custom "Hadamard"() %out_qubits_37 : !quantum.bit + %mres_39, %out_qubit_40 = quantum.measure %out_qubits_38 : i1, !quantum.bit + %from_elements_41 = tensor.from_elements %mres_39 : tensor + %extracted_42 = tensor.extract %c[] : tensor + %5 = quantum.insert %0[%extracted_42], %out_qubit : !quantum.reg, !quantum.bit + %extracted_43 = tensor.extract %c_7[] : tensor + %6 = quantum.insert %5[%extracted_43], %out_qubit_33 : !quantum.reg, !quantum.bit + %extracted_44 = tensor.extract %c_9[] : tensor + %7 = quantum.insert %6[%extracted_44], %out_qubit_23 : !quantum.reg, !quantum.bit + %extracted_45 = tensor.extract %c_1[] : tensor + %8 = quantum.insert %7[%extracted_45], %out_qubit_40 : !quantum.reg, !quantum.bit + quantum.dealloc %8 : !quantum.reg + quantum.device_release + return %from_elements, %from_elements_24, %from_elements_34, %from_elements_41 : tensor, tensor, tensor, tensor + // CHECK: { + // CHECK: "game_of_surface_code": { + // CHECK: "max_weight_pi2": 4, + // CHECK: "num_logical_qubits": 4, + // CHECK: "num_of_ppm": 31, + // CHECK: "num_pi2_gates": 9 + // CHECK: } + // CHECK: } +} + +// ----- \ No newline at end of file