diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 20f2e46918..bcc8f7ff57 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -2,6 +2,13 @@

New features since last release

+* A new PennyLane operation :func:`~.fabricate` has been added to expose the PBC + ``pbc.fabricate`` instruction from the frontend. The operation produces a new auxiliary + qubit in a logical factory state (``plus_i``, ``minus_i``, ``magic``, or ``magic_conj``) + and is lowered through the ``pbc.ref.fabricate`` reference-semantics op to + ``pbc.fabricate`` during compilation. + [(#3019)](https://github.com/PennyLaneAI/catalyst/pull/3019) + * The `local-random` unitary folding option for :func:`~.mitigate_with_zne` is now implemented, reproducing Mitiq's ``fold_gates_at_random``: every gate is folded ``floor((scale_factor-1)/2)`` times, then a random subset is folded once more (without replacement) to reach ``scale_factor * n`` diff --git a/frontend/catalyst/api_extensions/__init__.py b/frontend/catalyst/api_extensions/__init__.py index 6c020597c8..50e6d41622 100644 --- a/frontend/catalyst/api_extensions/__init__.py +++ b/frontend/catalyst/api_extensions/__init__.py @@ -37,8 +37,10 @@ MidCircuitPauliMeasure, adjoint, ctrl, + deallocate, measure, pauli_measure, + fabricate, ) __all__ = ( @@ -58,6 +60,8 @@ "vmap", "measure", "pauli_measure", + "fabricate", + "deallocate", "adjoint", "ctrl", ) diff --git a/frontend/catalyst/api_extensions/quantum_operators.py b/frontend/catalyst/api_extensions/quantum_operators.py index 959f939bba..bfc79cfb94 100644 --- a/frontend/catalyst/api_extensions/quantum_operators.py +++ b/frontend/catalyst/api_extensions/quantum_operators.py @@ -46,7 +46,14 @@ deduce_avals, new_inner_tracer, ) -from catalyst.jax_primitives import AbstractQreg, adjoint_p, measure_p, pauli_measure_p +from catalyst.jax_primitives import ( + AbstractQreg, + adjoint_p, + fabricate_p, + measure_p, + pauli_measure_p, + qdealloc_qb_p, +) from catalyst.jax_tracer import ( HybridOp, HybridOpRegion, @@ -243,6 +250,56 @@ def pauli_measure( return m +def fabricate(init_state: str) -> DynamicJaxprTracer: + r"""A :func:`qjit` compatible fabricate operation for PennyLane/Catalyst. + + .. important:: + + Under the legacy tracing pathway, use :func:`catalyst.fabricate` or ensure + :func:`qp.fabricate() ` is called from within an active + :func:`~.qjit` compilation. + + Args: + init_state (str): The logical state to fabricate. One of ``"plus_i"``, + ``"minus_i"``, ``"magic"``, or ``"magic_conj"``. + + Returns: + A JAX tracer for the fabricated qubit. + """ + EvaluationContext.check_is_tracing("catalyst.fabricate can only be used from within @qjit.") + EvaluationContext.check_is_quantum_tracing( + "catalyst.fabricate can only be used from within a qp.qnode." + ) + + valid_states = {"plus_i", "minus_i", "magic", "magic_conj"} + if init_state not in valid_states: + raise ValueError( + f'The init_state "{init_state}" is not allowed. ' + f"Allowed values are {sorted(valid_states)}." + ) + + (qubit,) = fabricate_p.bind(init_state=init_state) + return qubit + + +def deallocate(*qubits) -> None: + r"""A :func:`qjit` compatible deallocate operation for standalone fabricated qubits. + + Deallocates one or more standalone value-semantics qubits, such as those returned + by :func:`fabricate`. + + Args: + *qubits: Standalone qubit tracers to deallocate. + """ + EvaluationContext.check_is_tracing("catalyst.deallocate can only be used from within @qjit.") + EvaluationContext.check_is_quantum_tracing( + "catalyst.deallocate can only be used from within a qp.qnode." + ) + + for qubit in qubits: + qdealloc_qb_p.bind(qubit) + + def adjoint(f: Union[Callable, Operator], lazy=True) -> Union[Callable, Operator]: """A :func:`~.qjit` compatible adjoint transformer for PennyLane/Catalyst. diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index cc6ef89e53..144cd62bf5 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -82,6 +82,7 @@ "MultiRZ", "PauliRot", "PauliMeasure", + "Fabricate", "PauliX", "PauliY", "PauliZ", diff --git a/frontend/catalyst/from_plxpr/qfunc_interpreter.py b/frontend/catalyst/from_plxpr/qfunc_interpreter.py index f644fe1337..6a1b0d1cac 100644 --- a/frontend/catalyst/from_plxpr/qfunc_interpreter.py +++ b/frontend/catalyst/from_plxpr/qfunc_interpreter.py @@ -27,6 +27,7 @@ from pennylane.capture import PlxprInterpreter, pause from pennylane.capture.primitives import cond_prim as pl_cond_prim from pennylane.capture.primitives import ctrl_transform_prim as plxpr_ctrl_transform_prim +from pennylane.capture.primitives import fabricate_prim as plxpr_fabricate_prim from pennylane.capture.primitives import measure_prim as plxpr_measure_prim from pennylane.capture.primitives import operator_p from pennylane.capture.primitives import pauli_measure_prim as plxpr_pauli_measure_prim @@ -40,6 +41,8 @@ qref_alloc_p, qref_compbasis_p, qref_dealloc_p, + qref_dealloc_qb_p, + qref_fabricate_p, qref_get_p, qref_gphase_p, qref_hermitian_p, @@ -427,17 +430,25 @@ def handle_allocate(self, *, num_wires, state=None, restored=False): def handle_deallocate(self, *wires): """Handle the conversion from plxpr to Catalyst jaxpr for the qp.deallocate primitive""" qregs = set() + standalone_qubits = [] for w in wires: - get_op = w.parent + parent_eqn = w.parent + if parent_eqn.primitive is qref_fabricate_p: + standalone_qubits.append(w) + elif parent_eqn.primitive is qref_get_p: + qreg = parent_eqn.in_tracers[0] + qregs.add(qreg) + else: + raise AssertionError( + "Manual deallocation is only supported for manually allocated or fabricated wires" + ) + for qubit in standalone_qubits: + qref_dealloc_qb_p.bind(qubit) + if qregs: assert ( - get_op.primitive is qref_get_p - ), "Manual deallocation is only supported for manually allocated wires" - qreg = get_op.in_tracers[0] - qregs.add(qreg) - assert ( - len(qregs) == 1 - ), "Expected all wires to deallocate to come from the same allocation instruction" - qref_dealloc_p.bind(list(qregs)[0]) + len(qregs) == 1 + ), "Expected all wires to deallocate to come from the same allocation instruction" + qref_dealloc_p.bind(list(qregs)[0]) return [] @@ -543,6 +554,13 @@ def wrapper(*args): return () +@PLxPRToQuantumJaxprInterpreter.register_primitive(plxpr_fabricate_prim) +def handle_fabricate(self, *_, init_state=""): + """Handle the conversion from plxpr to Catalyst jaxpr for the fabricate primitive""" + (qubit,) = qref_fabricate_p.bind(init_state=init_state) + return (qubit,) + + @PLxPRToQuantumJaxprInterpreter.register_primitive(plxpr_pauli_measure_prim) def handle_pauli_measure(self, *wires_inval, pauli_word, **params): """Handle the conversion from plxpr to Catalyst jaxpr for the PauliMeasure primitive""" diff --git a/frontend/catalyst/from_plxpr/qref_jax_primitives.py b/frontend/catalyst/from_plxpr/qref_jax_primitives.py index 903942dfe3..103e8583f2 100644 --- a/frontend/catalyst/from_plxpr/qref_jax_primitives.py +++ b/frontend/catalyst/from_plxpr/qref_jax_primitives.py @@ -40,6 +40,7 @@ from catalyst.jax_extras.patches import mock_attributes from catalyst.jax_primitives import ( AbstractObs, + _logical_init_attr, _named_obs_attribute, extract_scalar, safe_cast_to_f64, @@ -61,13 +62,14 @@ ), ): from mlir_quantum.dialects.mbqc import RefMeasureInBasisOp - from mlir_quantum.dialects.pbc import RefPPMeasurementOp + from mlir_quantum.dialects.pbc import RefFabricateOp, RefPPMeasurementOp from mlir_quantum.dialects.qref import ( AdjointOp, AllocOp, ComputationalBasisOp, CustomOp, DeallocOp, + DeallocQubitOp, GetOp, GlobalPhaseOp, HermitianOp, @@ -147,6 +149,8 @@ class MeasurementPlane(Enum): qref_alloc_p = Primitive("qref_alloc") qref_dealloc_p = Primitive("qref_dealloc") qref_dealloc_p.multiple_results = True +qref_dealloc_qb_p = Primitive("qref_dealloc_qb") +qref_dealloc_qb_p.multiple_results = True qref_get_p = Primitive("qref_get") qref_set_state_p = Primitive("qref_state_prep") qref_set_state_p.multiple_results = True @@ -157,6 +161,8 @@ class MeasurementPlane(Enum): qref_gphase_p = Primitive("qref_gphase") qref_gphase_p.multiple_results = True qref_pauli_measure_p = Primitive("pref_pauli_measure") +qref_fabricate_p = Primitive("qref_fabricate") +qref_fabricate_p.multiple_results = True qref_pauli_rot_p = Primitive("qref_pauli_rot") qref_pauli_rot_p.multiple_results = True qref_unitary_p = Primitive("qref_unitary") @@ -215,6 +221,21 @@ def _qref_dealloc_lowering(jax_ctx: mlir.LoweringRuleContext, qreg): return () +# +# qref_dealloc_qb_p +# +@qref_dealloc_qb_p.def_abstract_eval +def _qref_dealloc_qb_abstract_eval(qubit): + return () + + +def _qref_dealloc_qb_lowering(jax_ctx: mlir.LoweringRuleContext, qubit): + ctx = jax_ctx.module_context.context + ctx.allow_unregistered_dialects = True + DeallocQubitOp(qubit=qubit) + return () + + # # qref_get_p # @@ -542,6 +563,24 @@ def _qref_pauli_measure_lowering( return (from_elements_op.results[0],) +# +# fabricate operation +# +@qref_fabricate_p.def_abstract_eval +def _qref_fabricate_abstract_eval(*_, init_state=""): + return (AbstractQubit(),) + + +def _qref_fabricate_lowering(jax_ctx: mlir.LoweringRuleContext, *_, init_state=""): + ctx = jax_ctx.module_context.context + ctx.allow_unregistered_dialects = True + + qubit_type = ir.OpaqueType.get("qref", "bit", ctx) + return RefFabricateOp( + qubits=[qubit_type], init_state=_logical_init_attr(ctx, init_state) + ).results + + # # qubit unitary operation # @@ -826,6 +865,7 @@ def _qref_hermitian_lowering(jax_ctx: mlir.LoweringRuleContext, matrix: ir.Value (qref_operator_p, _qref_operator_p_lowering), (qref_alloc_p, _qref_alloc_lowering), (qref_dealloc_p, _qref_dealloc_lowering), + (qref_dealloc_qb_p, _qref_dealloc_qb_lowering), (qref_get_p, _qref_get_lowering), (qref_set_state_p, _qref_set_state_lowering), (qref_set_basis_state_p, _qref_set_basis_state_lowering), @@ -833,6 +873,7 @@ def _qref_hermitian_lowering(jax_ctx: mlir.LoweringRuleContext, matrix: ir.Value (qref_gphase_p, _qref_gphase_lowering), (qref_pauli_rot_p, _qref_pauli_rot_lowering), (qref_pauli_measure_p, _qref_pauli_measure_lowering), + (qref_fabricate_p, _qref_fabricate_lowering), (qref_unitary_p, _qref_unitary_lowering), (qref_measure_p, _qref_measure_lowering), (qref_measure_in_basis_p, _qref_measure_in_basis_lowering), diff --git a/frontend/catalyst/jax_primitives.py b/frontend/catalyst/jax_primitives.py index 4be7ff097e..cc3086aa91 100644 --- a/frontend/catalyst/jax_primitives.py +++ b/frontend/catalyst/jax_primitives.py @@ -87,7 +87,7 @@ VJPOp, ) from mlir_quantum.dialects.mitigation import ZneOp - from mlir_quantum.dialects.pbc import PPMeasurementOp + from mlir_quantum.dialects.pbc import FabricateOp, PPMeasurementOp from mlir_quantum.dialects.quantum import ( AdjointOp, AllocOp, @@ -298,6 +298,8 @@ class Folding(Enum): pauli_rot_p.multiple_results = True pauli_measure_p = Primitive("pauli_measure") pauli_measure_p.multiple_results = True +fabricate_p = Primitive("fabricate") +fabricate_p.multiple_results = True measure_p = Primitive("measure") measure_p.multiple_results = True compbasis_p = Primitive("compbasis") @@ -1660,6 +1662,29 @@ def _pauli_measure_lowering( return (from_elements_op.results[0],) + tuple(out_qubits) +# +# fabricate operation +# +@fabricate_p.def_abstract_eval +def _fabricate_abstract_eval(*_, init_state=""): + return (AbstractQbit(),) + + +@fabricate_p.def_impl +def _fabricate_def_impl(*args, **kwargs): # pragma: no cover + raise NotImplementedError() + + +def _fabricate_lowering(jax_ctx: mlir.LoweringRuleContext, *_, init_state=""): + ctx = jax_ctx.module_context.context + ctx.allow_unregistered_dialects = True + + qubit_type = ir.OpaqueType.get("quantum", "bit", ctx) + return FabricateOp( + out_qubits=[qubit_type], init_state=_logical_init_attr(ctx, init_state) + ).results + + # # measure # @@ -1760,6 +1785,10 @@ def _namedobs_abstract_eval(qubit, kind): return AbstractObs() +def _logical_init_attr(ctx, init_state: str): + return ir.Attribute.parse(f"#pbc", context=ctx) + + def _named_obs_attribute(ctx, kind: str): return ir.OpaqueAttr.get( "quantum", ("named_observable " + kind).encode("utf-8"), ir.NoneType.get(ctx), ctx @@ -3080,6 +3109,7 @@ def subroutine_lowering(*args, **kwargs): (unitary_p, _unitary_lowering), (pauli_rot_p, _pauli_rot_lowering), (pauli_measure_p, _pauli_measure_lowering), + (fabricate_p, _fabricate_lowering), (measure_p, _measure_lowering), (compbasis_p, _compbasis_lowering), (namedobs_p, _named_obs_lowering), diff --git a/frontend/catalyst/jax_tracer.py b/frontend/catalyst/jax_tracer.py index 3eb26bae34..a619c66dc6 100644 --- a/frontend/catalyst/jax_tracer.py +++ b/frontend/catalyst/jax_tracer.py @@ -86,6 +86,7 @@ ) from catalyst.jax_extras.tracing import uses_transform from catalyst.jax_primitives import ( + AbstractQbit, AbstractQreg, compbasis_p, counts_p, @@ -398,6 +399,11 @@ def unify_convert_result_types(jaxprs, consts, nimplouts): return jaxpr_acc, type_acc[0], tracers_acc, consts_acc +def _is_standalone_qubit(wire) -> bool: + """Return True if ``wire`` is a standalone value-semantics qubit tracer.""" + return isinstance(wire, Tracer) and isinstance(get_aval(wire), AbstractQbit) + + class QRegPromise: """QReg adaptor tracing the qubit extractions and insertions. The adaptor works by postponing the insertions in order to re-use qubits later thus skipping the extractions.""" @@ -413,12 +419,21 @@ def extract(self, wires: List[Any], allow_reuse=False) -> List[DynamicJaxprTrace from cache""" # pylint: disable=consider-iterating-dictionary qrp = self - cached_tracers = {w for w in qrp.cache.keys() if isinstance(w, Tracer)} - requested_tracers = {w for w in wires if isinstance(w, Tracer)} + cached_tracers = {w for w in qrp.cache.keys() if _is_standalone_qubit(w)} + requested_tracers = {w for w in wires if _is_standalone_qubit(w)} if cached_tracers != requested_tracers: qrp.actualize() qubits = [] for w in wires: + if _is_standalone_qubit(w): + if w in qrp.cache and qrp.cache[w] is not None: + qubit = qrp.cache[w] + qubits.append(qubit) + if not allow_reuse: + qrp.cache[w] = None + else: + qubits.append(w) + continue if w in qrp.cache: qubit = qrp.cache[w] assert ( @@ -447,10 +462,15 @@ def actualize(self) -> DynamicJaxprTracer: """Prune the qubit cache by performing the postponed insertions.""" qrp = self qreg = qrp.base + standalone_cache = {} for w, qubit in qrp.cache.items(): - if qubit is not None: + if qubit is None: + continue + if _is_standalone_qubit(w): + standalone_cache[w] = qubit + else: qreg = qinsert_p.bind(qreg, w, qubit) - qrp.cache = {} + qrp.cache = standalone_cache qrp.base = qreg return qreg diff --git a/frontend/test/lit/test_fabricate.py b/frontend/test/lit/test_fabricate.py new file mode 100644 index 0000000000..a834f8675e --- /dev/null +++ b/frontend/test/lit/test_fabricate.py @@ -0,0 +1,54 @@ +# Copyright 2026 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: %PYTHON %s | FileCheck %s + +"""Test fabricate lowering to pbc.fabricate.""" + +import pennylane as qp + +from catalyst import qjit + +_PIPELINE = [ + ( + "pipe", + [ + "canonicalize", + "verify-no-quantum-use-after-free", + "convert-to-value-semantics", + "canonicalize", + ], + ) +] + + +def test_fabricate_lowering(): + """Test that qp.fabricate is lowered to pbc.fabricate.""" + dev = qp.device("null.qubit", wires=2) + + @qjit(pipelines=_PIPELINE, target="mlir", capture=True) + @qp.qnode(device=dev) + def circuit(): + magic = qp.fabricate("magic") + qp.pauli_measure("ZZ", wires=[0, magic]) + qp.deallocate(magic) + return qp.expval(qp.Z(0)) + + # CHECK: pbc.fabricate magic + # CHECK-NOT: pbc.ref.fabricate + # CHECK: pbc.ppm ["Z", "Z"] + print(circuit.mlir_opt) + + +test_fabricate_lowering() diff --git a/frontend/test/pytest/conftest.py b/frontend/test/pytest/conftest.py index 1e2e2901f2..70af9b13d7 100644 --- a/frontend/test/pytest/conftest.py +++ b/frontend/test/pytest/conftest.py @@ -58,14 +58,19 @@ def use_capture(): @pytest.fixture(scope="function") -def use_capture_dgraph(): +def enable_graph_decomposition(): + """Enable graph-decomposition around each test.""" + with qp.decomposition.toggle_graph_ctx(True): + yield + + +@pytest.fixture(scope="function") +def use_capture_dgraph(enable_graph_decomposition): """Enable capture and graph-decomposition before and disable them both after the test.""" qp.capture.enable() - qp.decomposition.enable_graph() try: yield finally: - qp.decomposition.disable_graph() qp.capture.disable() diff --git a/frontend/test/pytest/test_fabricate.py b/frontend/test/pytest/test_fabricate.py new file mode 100644 index 0000000000..6ad3af737f --- /dev/null +++ b/frontend/test/pytest/test_fabricate.py @@ -0,0 +1,109 @@ +# Copyright 2026 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. + +"""Tests for the fabricate operation.""" + +import pytest + +import pennylane as qp +from pennylane.ops.qubit.fabricate import Fabricate, fabricate, _VALID_INIT_STATES + +from catalyst import qjit + +_FABRICATE_PIPELINE = [ + ( + "pipe", + [ + "canonicalize", + "verify-no-quantum-use-after-free", + "convert-to-value-semantics", + "canonicalize", + ], + ) +] + + +class TestFabricateOp: + """Tests for the Fabricate operator and function.""" + + @pytest.mark.parametrize("init_state", sorted(_VALID_INIT_STATES)) + def test_fabricate_valid_init_states(self, init_state): + """Test that valid init states are accepted.""" + op = Fabricate(init_state) + assert op.init_state == init_state + assert len(op.wires) == 0 + + def test_fabricate_invalid_init_state(self): + """Test that invalid init states are rejected.""" + with pytest.raises(ValueError, match="not allowed"): + Fabricate("zero") + + @pytest.mark.parametrize("init_state", sorted(_VALID_INIT_STATES)) + @pytest.mark.usefixtures("use_capture") + def test_fabricate_function_capture(self, init_state): + """Test fabricate primitive binding under capture.""" + import jax + + def circuit(): + fabricate(init_state) + + jaxpr = jax.make_jaxpr(circuit)().jaxpr + assert any(eqn.primitive.name == "fabricate" for eqn in jaxpr.eqns) + + @pytest.mark.usefixtures("enable_graph_decomposition") + def test_fabricate_mlir_lowering(self): + """Test that fabricate appears as pbc.fabricate in optimized MLIR.""" + dev = qp.device("null.qubit", wires=1) + + @qjit(pipelines=_FABRICATE_PIPELINE, target="mlir", capture=True) + @qp.qnode(device=dev) + def circuit(): + magic = fabricate("magic_conj") + qp.pauli_measure("Z", wires=[magic]) + qp.deallocate(magic) + return qp.expval(qp.Z(0)) + + mlir = circuit.mlir_opt + assert "pbc.fabricate" in mlir and "magic_conj" in mlir + assert "pbc.ref.fabricate" not in mlir + + @pytest.mark.usefixtures("enable_graph_decomposition") + def test_fabricate_mlir_lowering_capture_false(self): + """Test fabricate lowering via the legacy tracing pathway.""" + dev = qp.device("null.qubit", wires=2) + pipe = [ + ( + "pipe", + [ + "canonicalize", + "verify-no-quantum-use-after-free", + "convert-to-value-semantics", + "canonicalize", + ], + ) + ] + + @qjit(pipelines=pipe, target="mlir", capture=False) + @qp.qnode(device=dev) + def circuit(): + magic = fabricate("magic") + qp.pauli_measure("ZZ", wires=[0, magic]) + qp.deallocate(magic) + return qp.expval(qp.Z(0)) + + mlir = circuit.mlir_opt + assert "pbc.fabricate" in mlir and "magic" in mlir + assert "pbc.ref.fabricate" not in mlir + assert "pbc.ppm" in mlir + assert "quantum.dealloc_qb" in mlir diff --git a/mlir/include/PBC/IR/PBCRefOps.td b/mlir/include/PBC/IR/PBCRefOps.td index aa9989dd61..f80e1ee07f 100644 --- a/mlir/include/PBC/IR/PBCRefOps.td +++ b/mlir/include/PBC/IR/PBCRefOps.td @@ -37,3 +37,26 @@ def RefPPMeasurementOp : PBC_Op<"ref.ppm"> { let hasVerifier = 1; } + +def RefFabricateOp : PBC_Op<"ref.fabricate"> { + let summary = "Fabricate auxiliary qubits from qubit factories (reference semantics)."; + + let description = [{ + Identical to the pbc.fabricate operation, except in reference semantics. + }]; + + let arguments = (ins + LogicalInit:$init_state + ); + + let results = (outs + Variadic:$qubits + ); + + let assemblyFormat = [{ + $init_state attr-dict `:` type($qubits) + }]; + + let hasCanonicalizeMethod = 1; + let hasVerifier = 1; +} diff --git a/mlir/lib/PBC/IR/PBCOps.cpp b/mlir/lib/PBC/IR/PBCOps.cpp index 49aca12605..f5746d08fb 100644 --- a/mlir/lib/PBC/IR/PBCOps.cpp +++ b/mlir/lib/PBC/IR/PBCOps.cpp @@ -23,6 +23,7 @@ #include "mlir/IR/Region.h" #include "QRef/IR/QRefDialect.h" +#include "QRef/IR/QRefTypes.h" #include "Quantum/IR/QuantumDialect.h" using namespace mlir; @@ -91,6 +92,26 @@ LogicalResult RefPPMeasurementOp::verify() return success(); } +LogicalResult RefFabricateOp::verify() +{ + auto initState = getInitState(); + if (initState == LogicalInitKind::zero || initState == LogicalInitKind::one || + initState == LogicalInitKind::plus || initState == LogicalInitKind::minus) { + return emitOpError("Logical state should not be fabricated, use `PrepareStateOp` instead."); + } + return mlir::success(); +} + +LogicalResult RefFabricateOp::canonicalize(RefFabricateOp op, PatternRewriter &rewriter) +{ + if (op->use_empty()) { + rewriter.eraseOp(op); + return success(); + } + + return failure(); +} + LogicalResult SelectPPMeasurementOp::verify() { if (getInQubits().size() != getPauliProduct_0().size() || diff --git a/mlir/lib/QRef/Transforms/value_semantics_conversion.cpp b/mlir/lib/QRef/Transforms/value_semantics_conversion.cpp index 68cb97ea67..98f9d8d611 100644 --- a/mlir/lib/QRef/Transforms/value_semantics_conversion.cpp +++ b/mlir/lib/QRef/Transforms/value_semantics_conversion.cpp @@ -1208,6 +1208,17 @@ void handlePPM(IRRewriter &builder, pbc::RefPPMeasurementOp rPPMOp, QubitValueTr builder.eraseOp(rPPMOp); } +void handleFabricate(IRRewriter &builder, pbc::RefFabricateOp rFabricateOp, + QubitValueTracker &tracker) +{ + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPoint(rFabricateOp); + Location loc = rFabricateOp.getLoc(); + + auto vFabricateOp = pbc::FabricateOp::create(builder, loc, rFabricateOp.getInitState()); + tracker.setCurrentVQubit(rFabricateOp.getQubits().front(), vFabricateOp.getOutQubits().front()); +} + void handleCall(IRRewriter &builder, func::CallOp callOp, QubitValueTracker &tracker) { OpBuilder::InsertionGuard guard(builder); @@ -1878,6 +1889,7 @@ void handleRegion(IRRewriter &builder, Region &r, QubitValueTracker &tracker) .Case( [&](auto o) { handleMeasureInBasis(builder, o, tracker); }) .Case([&](auto o) { handlePPM(builder, o, tracker); }) + .Case([&](auto o) { handleFabricate(builder, o, tracker); }) .Case([&](auto o) { handleAdjoint(builder, o, tracker); }) .Case([&](auto o) { handleIf(builder, o, tracker); }) .Case([&](auto o) { handleSwitch(builder, o, tracker); })