Source code for pennylane.labs.transforms.select_pauli_rot_phase_gradient

# 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.
r"""
Contains the ``select_pauli_rot_phase_gradient`` transform.
"""
import pennylane as qml
from pennylane.tape import QuantumScript, QuantumScriptBatch
from pennylane.transforms import transform
from pennylane.typing import PostprocessingFn
from pennylane.wires import Wires

from .rot_to_phase_gradient import _select_pauli_rot_phase_gradient


[docs] @transform def select_pauli_rot_phase_gradient( tape: QuantumScript, angle_wires: Wires, phase_grad_wires: Wires, work_wires: Wires ) -> tuple[QuantumScriptBatch, PostprocessingFn]: r"""Quantum function transform to decompose all instances of :class:`~.SelectPauliRot` gates into additions using a phase gradient resource state. For this routine to work, the provided ``phase_grad_wires`` need to hold the phase gradient state :math:`|\nabla_Z\rangle = \frac{1}{\sqrt{2^n}} \sum_{m=0}^{2^n-1} e^{-2 \pi i \frac{m}{2^n}} |m\rangle`. Because this state is not modified and can be re-used at a later stage, the transform does not prepare it but rather assumes it has been prepared on those wires at an earlier stage. Look at the example below to see how this state can be prepared. .. figure:: ../../../_static/multiplexer_QROM.png :align: center :width: 70% :target: javascript:void(0); Note that this operator contains :class:`~.SemiAdder` that typically uses additional ``work_wires`` for the semi-in-place addition :math:`\text{SemiAdder}|x\rangle_\text{ang} |y\rangle_\text{phg} = |x\rangle_\text{ang} |x + y\rangle_\text{phg}`. Args: tape (QNode or QuantumTape or Callable): A quantum circuit containing :class:`~.SelectPauliRot` operators. angle_wires (Wires): The qubits that conditionally load the angle :math:`\phi` of the :class:`~.SelectPauliRot` gate in binary as a multiple of :math:`2\pi`. The length of the ``angle_wires`` , i.e. :math:`b`, implicitly determines the precision with which the angle is represented. E.g., :math:`(1 \cdot 2^{-1} + 0 \cdot 2^{-2} + 1 \cdot 2^{-3}) 2\pi` is represented by three bits as ``101``. phase_grad_wires (Wires): Qubits with the catalytic phase gradient state prepared on them. Needs to be at least :math:`b` wires and will only use the first :math:`b`. work_wires (Wires): Additional work wires to realize the :class:`~.SemiAdder` and :class:`~.QROM`. Needs to be at least :math:`b-1` wires. Returns: qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. **Example** .. code-block:: python from pennylane.labs.transforms import select_pauli_rot_phase_gradient from functools import partial import numpy as np precision = 4 wire = "targ" angle_wires = [f"ang_{i}" for i in range(precision)] phase_grad_wires = [f"phg_{i}" for i in range(precision)] work_wires = [f"work_{i}" for i in range(precision - 1)] def phase_gradient(wires): # prepare phase gradient state for i, w in enumerate(wires): qml.H(w) qml.PhaseShift(-np.pi / 2**i, w) @partial( select_pauli_rot_phase_gradient, angle_wires=angle_wires, phase_grad_wires=phase_grad_wires, work_wires=work_wires, ) @qml.qnode(qml.device("default.qubit")) def select_pauli_rot_circ(phis, control_wires, target_wire): phase_gradient(phase_grad_wires) # prepare phase gradient state for wire in control_wires: qml.Hadamard(wire) qml.SelectPauliRot(phis, control_wires, target_wire, rot_axis="X") return qml.probs(target_wire) phis = [ (1 / 2 + 1 / 4 + 1 / 8) * 2 * np.pi, (1 / 2 + 1 / 4 + 0 / 8) * 2 * np.pi, (1 / 2 + 0 / 4 + 1 / 8) * 2 * np.pi, (0 / 2 + 1 / 4 + 1 / 8) * 2 * np.pi, ] >>> print(select_pauli_rot_circ(phis, control_wires=[0, 1], target_wire=wire)) [0.41161165 0.58838835] """ if len(phase_grad_wires) < len(angle_wires): raise ValueError( f"phase_grad_wires needs to be at least as large as angle_wires. Got {len(phase_grad_wires)} phase_grad_wires, which is fewer than the {len(angle_wires)} angle_wires." ) if len(work_wires) < len(angle_wires) - 1: raise ValueError( f"work_wires needs to be at least as large as angle_wires - 1. Got {len(work_wires)} work_wires, which is fewer than the {len(angle_wires) - 1}." ) operations = [] for op in tape.operations: if isinstance(op, qml.SelectPauliRot): control_wires = op.wires[:-1] target_wire = op.wires[-1] rot_axis = op.hyperparameters["rot_axis"] angles = op.parameters[0] pg_op = _select_pauli_rot_phase_gradient( angles, rot_axis, control_wires=control_wires, target_wire=target_wire, angle_wires=angle_wires, phase_grad_wires=phase_grad_wires, work_wires=work_wires, ) operations.append(pg_op) else: operations.append(op) new_tape = tape.copy(operations=operations) def null_postprocessing(results): """A postprocessing function returned by a transform that only converts the batch of results into a result for a single ``QuantumTape``. """ return results[0] return [new_tape], null_postprocessing