# Copyright 2018-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 submodule contains the adapter class for Qualtran-PennyLane interoperability."""fromcollectionsimportdefaultdictfromfunctoolsimportlru_cache,singledispatchimportnumpyasnpimportpennylaneasqmlfrompennylane.operationimportDecompositionUndefinedError,MatrixUndefinedError,Operationfrompennylane.wiresimportWiresLiketry:importqualtranasqtexcept(ModuleNotFoundError,ImportError)asimport_error:pass# pylint: disable=unused-argument@lru_cachedef_get_to_pl_op():@singledispatchdef_to_pl_op(bloq,wires):returnFromBloq(bloq=bloq,wires=wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.CNOT,wires):returnqml.CNOT(wires=wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.GlobalPhase,wires):returnqml.GlobalPhase(bloq.exponent*np.pi,wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Hadamard,wires):returnqml.Hadamard(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Identity,wires):returnqml.Identity(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Rx,wires):returnqml.RX(bloq.angle,wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Ry,wires):returnqml.RY(bloq.angle,wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Rz,wires):returnqml.RZ(bloq.angle,wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.SGate,wires):returnqml.adjoint(qml.S(wires))ifbloq.is_adjointelseqml.S(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.TwoBitSwap,wires):returnqml.SWAP(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.TwoBitCSwap,wires):returnqml.CSWAP(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.TGate,wires):returnqml.adjoint(qml.T(wires))ifbloq.is_adjointelseqml.T(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.Toffoli,wires):returnqml.Toffoli(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.XGate,wires):returnqml.X(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.YGate,wires):returnqml.Y(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.CYGate,wires):returnqml.CY(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.ZGate,wires):returnqml.Z(wires)@_to_pl_op.registerdef_(bloq:qt.bloqs.basic_gates.CZ,wires):returnqml.CZ(wires)@_to_pl_op.register(qt.bloqs.bookkeeping.Allocate)@_to_pl_op.register(qt.bloqs.bookkeeping.Cast)@_to_pl_op.register(qt.bloqs.bookkeeping.Free)@_to_pl_op.register(qt.bloqs.bookkeeping.Join)@_to_pl_op.register(qt.bloqs.bookkeeping.Partition)@_to_pl_op.register(qt.bloqs.bookkeeping.Split)def_(bloq,wires):returnNonereturn_to_pl_op
[docs]defbloq_registers(bloq):"""Reads a `Qualtran Bloq <https://qualtran.readthedocs.io/en/latest/bloqs/index.html#bloqs-library>`_ signature and returns a dictionary mapping the Bloq's register names to :class:`~.Wires`. .. note:: This function requires the latest version of Qualtran. We recommend installing the main branch via ``pip``: .. code-block:: console pip install qualtran The keys of the ``qml.registers`` dictionary are the register names in the Qualtran Bloq. The values are :class:`~.Wires` objects with a length equal to the bitsize of its respective register. The wires are indexed in ascending order, starting from 0. This function makes it easy to access the wires that a Bloq acts on and use them to precisely control how gates connect. Args: bloq (Bloq): an initialized Qualtran ``Bloq`` to be wrapped as a PennyLane operator Returns: dict: A dictionary built with information from the Bloq's signature. The dictionary keys are strings that come from the names of the Bloq's registers. The values are :class:`~.Wires` objects that are determined by the bitsizes of those same registers. Raises: TypeError: bloq must be an instance of ``Bloq``. **Example** This example shows how to find the estimation wires of a textbook Quantum Phase Estimation Bloq. >>> from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE >>> from qualtran.bloqs.basic_gates import ZPowGate >>> textbook_qpe_small = TextbookQPE(ZPowGate(exponent=2 * 0.234), RectangularWindowState(3)) >>> qml.bloq_registers(textbook_qpe_small) {'q': Wires([0]), 'qpe_reg': Wires([1, 2, 3])} """ifnotisinstance(bloq,qt.Bloq):raiseTypeError(f"bloq must be an instance of {qt.Bloq}.")wire_register_dict=defaultdict()forreginbloq.signature.lefts():wire_register_dict[reg.name]=reg.bitsizeforreginbloq.signature.rights():wire_register_dict[reg.name]=reg.bitsizereturnqml.registers(wire_register_dict)
def_get_named_registers(registers):"""Returns a ``qml.registers`` object associated with the named registers in the bloq"""temp_register_dict={reg.name:reg.total_bits()forreginregisters}returnqml.registers(temp_register_dict)def_preprocess_bloq(bloq):"""Processes a bloq's information to prepare for decomposition"""# Bloqs need to be decomposed in order to access the connectionscbloq=bloq.decompose_bloq()ifnotisinstance(bloq,qt.CompositeBloq)elsebloqtemp_registers=_get_named_registers(cbloq.signature.lefts())soq_to_wires={qt.Soquet(qt.LeftDangle,idx=idx,reg=reg):(list(temp_registers[reg.name])[idx[0]]iflen(idx)==1elselist(temp_registers[reg.name]))forregincbloq.signature.lefts()foridxinreg.all_idxs()}# This is to track the number of wires defined at the LeftDangle stage# so if we need to add more wires, we know what index to start atsoq_to_wires_len=0iflen(soq_to_wires.values())>0:soq_to_wires_len=list(soq_to_wires.values())[-1]ifnotisinstance(soq_to_wires_len,int):soq_to_wires_len=list(soq_to_wires.values())[-1][-1]soq_to_wires_len+=1returncbloq,soq_to_wires,soq_to_wires_len
[docs]classFromBloq(Operation):r""" An adapter for using a `Qualtran Bloq <https://qualtran.readthedocs.io/en/latest/bloqs/index.html#bloqs-library>`_ as a PennyLane :class:`~.Operation`. .. note:: This class requires the latest version of Qualtran. We recommend installing the main branch via ``pip``: .. code-block:: console pip install qualtran Args: bloq (qualtran.Bloq): an initialized Qualtran ``Bloq`` to be wrapped as a PennyLane operator wires (WiresLike): The wires the operator acts on. The number of wires can be determined by using the signature of the ``Bloq`` using ``bloq.signature.n_qubits()``. Raises: TypeError: bloq must be an instance of ``Bloq``. **Example** This example shows how to use ``qml.FromBloq``: >>> from qualtran.bloqs.basic_gates import CNOT >>> qualtran_cnot = qml.FromBloq(CNOT(), wires=[0, 1]) >>> qualtran_cnot.matrix() array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j], [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j], [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]]) This example shows how to use ``qml.FromBloq`` inside a device: >>> from qualtran.bloqs.basic_gates import CNOT >>> dev = qml.device("default.qubit") # Execute on device >>> @qml.qnode(dev) ... def circuit(): ... qml.FromBloq(CNOT(), wires=[0, 1]) ... return qml.state() >>> circuit() array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]) .. details:: :title: Advanced Example This example shows how to use ``qml.FromBloq`` to implement a textbook Quantum Phase Estimation Bloq inside a device: .. code-block:: from qualtran.bloqs.phase_estimation import RectangularWindowState, TextbookQPE from qualtran.bloqs.chemistry.trotter.ising import IsingXUnitary, IsingZZUnitary from qualtran.bloqs.chemistry.trotter.trotterized_unitary import TrotterizedUnitary # Parameters for the TrotterizedUnitary nsites = 5 j_zz, gamma_x = 2, 0.1 zz_bloq = IsingZZUnitary(nsites=nsites, angle=0.02 * j_zz) x_bloq = IsingXUnitary(nsites=nsites, angle=0.01 * gamma_x) trott_unitary = TrotterizedUnitary( bloqs=(x_bloq, zz_bloq), timestep=0.01, indices=(0, 1, 0), coeffs=(0.5 * gamma_x, j_zz, 0.5 * gamma_x) ) # Instantiate the TextbookQPE and pass in the unitary textbook_qpe = TextbookQPE(trott_unitary, RectangularWindowState(3)) # Execute on device dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.FromBloq(textbook_qpe, wires=range(textbook_qpe.signature.n_qubits())) return qml.probs(wires=[5, 6, 7]) circuit() .. details:: :title: Usage Details The decomposition of a ``Bloq`` wrapped in ``qml.FromBloq`` may use more wires than expected. For example, when we wrap Qualtran's ``CZPowGate``, we get >>> from qualtran.bloqs.basic_gates import CZPowGate >>> qml.FromBloq(CZPowGate(0.468, eps=1e-11), wires=[0, 1]).decomposition() [FromBloq(And, wires=Wires([0, 1, 'alloc_free_2'])), FromBloq(Z**0.468, wires=Wires(['alloc_free_2'])), FromBloq(And†, wires=Wires([0, 1, 'alloc_free_2']))] This behaviour results from the decomposition of ``CZPowGate`` as defined in Qualtran, which allocates and frees a wire in the same ``bloq``. In this situation, PennyLane automatically allocates this wire under the hood, and that additional wire is named ``alloc_free_{idx}``. The indexing starts at the length of the wires defined in the signature, which in the case of ``CZPowGate`` is :math:`2`. Due to the current limitations of PennyLane, these wires cannot be accessed manually or mapped. """def__init__(self,bloq,wires:WiresLike):ifnotisinstance(bloq,qt.Bloq):raiseTypeError(f"bloq must be an instance of {qt.Bloq}.")self._hyperparameters={"bloq":bloq}super().__init__(wires=wires,id=None)def__repr__(self):returnf'FromBloq({self.hyperparameters["bloq"]}, wires={self.wires})'
[docs]@staticmethoddefcompute_decomposition(wires,bloq):# pylint: disable=arguments-differ, too-many-branchesops=[]iflen(wires)!=bloq.signature.n_qubits():raiseValueError(f"The length of wires must match the signature of {qt.Bloq}. Please provide a list of wires of length {bloq.signature.n_qubits()}")try:cbloq,soq_to_wires,soq_to_wires_len=_preprocess_bloq(bloq)forbinst,pred_cxns,succ_cxnsincbloq.iter_bloqnections():ifisinstance(binst.bloq,qt.bloqs.bookkeeping.Partition):in_quregs={}forsuccinsucc_cxns:soq=succ.leftifsoq.reg.side==qt.Side.RIGHTandnotsoq.reg.nameinin_quregs:soq_to_wires_len-=np.prod(soq.reg.shape)*soq.reg.bitsizeforsuccinsucc_cxns:soq=succ.leftifsoq.reg.side==qt.Side.RIGHTandnotsoq.reg.nameinin_quregs:total_elements=np.prod(soq.reg.shape)*soq.reg.bitsizeascending_vals=np.arange(soq_to_wires_len,soq_to_wires_len+total_elements,dtype=object,)soq_to_wires_len+=total_elementsin_quregs[soq.reg.name]=ascending_vals.reshape((*soq.reg.shape,soq.reg.bitsize))soq_to_wires[soq]=in_quregs[soq.reg.name][soq.idx]continuein_quregs={reg.name:np.empty((*reg.shape,reg.bitsize),dtype=object)forreginbinst.bloq.signature.lefts()}# The out_quregs inform us of the total # of wires in the circuit to account for# wires that are split or allocated in the cbloqout_quregs={reg.name:np.empty((*reg.shape,reg.bitsize),dtype=object)forreginbinst.bloq.signature.rights()}forpredinpred_cxns:soq=pred.rightsoq_to_wires[soq]=soq_to_wires[pred.left]in_quregs[soq.reg.name][soq.idx]=np.squeeze(soq_to_wires[soq])forsuccinsucc_cxns:soq=succ.leftifsoq.reg.side==qt.Side.RIGHT:# When in_quregs != out_quregs, it means that there are wires unaccounted# for. We account for these wires and update soq_to_wires and in_quregs# accordingly.iflen(in_quregs)!=len(out_quregs):total_elements=np.prod(soq.reg.shape)*soq.reg.bitsizeascending_vals=np.arange(soq_to_wires_len,total_elements+soq_to_wires_len,dtype=object,)soq_to_wires_len+=total_elementsin_quregs[soq.reg.name]=ascending_vals.reshape((*soq.reg.shape,soq.reg.bitsize))soq_to_wires[soq]=in_quregs[soq.reg.name][soq.idx]total_wires=[int(w)forwsinin_quregs.values()forwinlist(ws.ravel())]mapped_wires=[wires[idx]foridxintotal_wiresifidx<len(wires)]ghost_wires=[f"alloc_free_{val}"forvalintotal_wiresifval>=len(wires)]op=_get_to_pl_op()(binst.bloq,mapped_wires+ghost_wires)ifop:ops.append(op)except(qt.DecomposeNotImplementedError,qt.DecomposeTypeError):passiflen(ops)==0:raiseDecompositionUndefinedErrorreturnops
# pylint: disable=invalid-overridden-method, arguments-renamed@propertydefhas_matrix(self)->bool:r"""Return if the ``Bloq`` has a valid matrix representation."""bloq=self.hyperparameters["bloq"]matrix=bloq.tensor_contract()returnmatrix.shape==(2**len(self.wires),2**len(self.wires))