Source code for pennylane.estimator.templates.qsp
# 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.
"""
Contains templates for Quantum Signal Processing (QSP) based subroutines.
"""
import scipy.special as sps
import pennylane.numpy as qnp
from pennylane.estimator.ops.op_math.symbolic import Adjoint, Controlled
from pennylane.estimator.ops.qubit.parametric_ops_multi_qubit import PCPhase
from pennylane.estimator.ops.qubit.parametric_ops_single_qubit import RX, RZ, Rot
from pennylane.estimator.resource_operator import (
CompressedResourceOp,
GateCount,
ResourceOperator,
_dequeue,
)
from pennylane.wires import Wires, WiresLike
# pylint: disable=arguments-differ,super-init-not-called, signature-differs, too-many-arguments
[docs]
class GQSP(ResourceOperator):
r"""Resource class for the Generalized Quantum Signal Processing (GQSP) algorithm.
The ``GQSP`` operator is defined based on Theorem 6 of `Generalized Quantum Signal Processing (2024)
<https://arxiv.org/pdf/2308.01501>`_:
.. math::
GQSP = \left( \prod_{j=1}^{d^{-}} R(\theta_{j}, \phi_{j}, 0) \hat{A}^{\prime} \right)
\left( \prod_{j=1}^{d^{+}} R(\theta_{j + d^{-}}, \phi_{j + d^{-}}, 0) \hat{A} \right) R(\theta_0, \phi_0, \lambda),
where :math:`R` is the single-qubit rotation operator and :math:`\vec{\phi}`, :math:`\vec{\theta}` and :math:`\lambda`
are the rotation angles that generate the polynomial transformation. The maximum positive and
negative polynomial degrees are denoted by :math:`d^{+}` and :math:`d^{-}`, respectively.
Additionally, :math:`\hat{A}` and :math:`\hat{A}^{\prime}` are given by:
.. math::
\begin{align}
\hat{A} &= \ket{0}\bra{0}\otimes\hat{U} + \ket{1}\bra{1}\otimes\mathbf{I}, \\
\hat{A}^{\prime} &= \ket{0}\bra{0}\otimes\mathbf{I} + \ket{1}\bra{1}\otimes\hat{U}^{\dagger}, \\ \\
\end{align}
where :math:`U` is a signal operator which encodes a target Hamiltonian.
Args:
signal_operator (:class:`~.pennylane.estimator.resource_operator.ResourceOperator`): the
signal operator which encodes a target Hamiltonian
d_plus (int): The largest positive degree :math:`d^{+}` of the polynomial transformation.
d_minus (int): The largest (in absolute value) negative degree :math:`d^{-}` of the polynomial
transformation, representing powers of the adjoint of the signal operator.
rotation_precision (float | None): The precision with which the general rotation gates are applied.
wires (WiresLike | None): The wires the operation acts on. This includes both the wires of the
signal operator and the control wire required for block-encoding.
Resources:
The resources are obtained as described in Theorem 6 of `Generalized Quantum Signal
Processing (2024) <https://arxiv.org/pdf/2308.01501>`_.
Raises:
ValueError: if ``d_plus`` is not a positive integer greater than zero
ValueError: if ``d_minus`` is not an integer greater than or equal to zero
ValueError: if ``rotation_precision`` is not a positive real number greater than zero
ValueError: if the wires provided don't match the number of wires expected by the operator
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> signal_op = qre.RX(0.1, wires=0)
>>> d_plus = 5
>>> d_minus = 3
>>> gqsp = qre.GQSP(signal_op, d_plus, d_minus)
>>> print(qre.estimate(gqsp))
--- Resources: ---
Total wires: 2
algorithmic wires: 2
allocated wires: 0
zero state: 0
any state: 0
Total gates : 1.438E+3
'T': 1.396E+3,
'CNOT': 16,
'X': 10,
'Hadamard': 16
"""
resource_keys = {"cmpr_signal_op", "d_plus", "d_minus", "rotation_precision"}
def __init__(
self,
signal_operator: ResourceOperator,
d_plus: int,
d_minus: int = 0,
rotation_precision: float | None = None,
wires: WiresLike = None,
):
_dequeue(signal_operator) # remove operator
self.queue()
if (not isinstance(d_plus, int)) or d_plus <= 0:
raise ValueError(f"'d_plus' must be a positive integer greater than zero, got {d_plus}")
if (not isinstance(d_minus, int)) or d_minus < 0:
raise ValueError(f"'d_minus' must be a non-negative integer, got {d_minus}")
if rotation_precision is not None and rotation_precision <= 0:
raise ValueError(
f"Expected 'rotation_precision' to be a positive real number greater than zero, got {rotation_precision}"
)
self.d_plus = d_plus
self.d_minus = d_minus
self.rotation_precision = rotation_precision
self.cmpr_signal_op = signal_operator.resource_rep_from_op()
self.num_wires = signal_operator.num_wires + 1 # add control wire
if wires:
self.wires = Wires(wires)
if base_wires := signal_operator.wires:
self.wires = Wires.all_wires([self.wires, base_wires])
if len(self.wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}.")
else:
self.wires = None
@property
def resource_params(self):
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* cmpr_signal_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the compressed representation of the signal operator which encodes the target Hamiltonian
* d_plus (int): The largest positive degree :math:`d^{+}` of the polynomial transformation.
* d_minus (int): The largest (in absolute value) negative degree :math:`d^{-}` of the
polynomial transformation, representing powers of the adjoint of the signal operator.
* rotation_precision (float | None): The precision with which the general
rotation gates are applied.
"""
return {
"cmpr_signal_op": self.cmpr_signal_op,
"d_plus": self.d_plus,
"d_minus": self.d_minus,
"rotation_precision": self.rotation_precision,
}
[docs]
@classmethod
def resource_rep(
cls,
cmpr_signal_op: CompressedResourceOp,
d_plus: int,
d_minus: int = 0,
rotation_precision: float | None = None,
) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
cmpr_signal_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the compressed representation of the signal operator which encodes the target Hamiltonian
d_plus (int): The largest positive degree :math:`d^{+}` of the polynomial transformation.
d_minus (int): The largest (in absolute value) negative degree :math:`d^{-}` of the polynomial
transformation, representing powers of the adjoint of the signal operator.
rotation_precision (float | None): The precision with which the general rotation gates are applied.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if (not isinstance(d_plus, int)) or d_plus <= 0:
raise ValueError(f"'d_plus' must be a positive integer greater than zero, got {d_plus}")
if (not isinstance(d_minus, int)) or d_minus < 0:
raise ValueError(f"'d_minus' must be a non-negative integer, got {d_minus}")
if rotation_precision is not None and rotation_precision <= 0:
raise ValueError(
f"Expected 'rotation_precision' to be a positive real number greater than zero, got {rotation_precision}"
)
num_wires = cmpr_signal_op.num_wires + 1 # add control wire
params = {
"cmpr_signal_op": cmpr_signal_op,
"d_plus": d_plus,
"d_minus": d_minus,
"rotation_precision": rotation_precision,
}
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
cmpr_signal_op: CompressedResourceOp,
d_plus: int,
d_minus: int = 0,
rotation_precision: float | None = None,
) -> list[GateCount]:
r"""Returns a list representing the resources of the operator. Each object in the list
represents a gate and the number of times it occurs in the circuit.
Args:
cmpr_signal_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the compressed representation of the signal operator which encodes the target Hamiltonian
d_plus (int): The largest positive degree :math:`d^{+}` of the polynomial transformation.
d_minus (int): The largest (in absolute value) negative degree :math:`d^{-}` of the polynomial
transformation, representing powers of the adjoint of the signal operator.
rotation_precision (float | None): The precision with which the general rotation gates are applied.
Resources:
The resources are obtained as described in Theorem 6 of
`Generalized Quantum Signal Processing (2024) <https://arxiv.org/pdf/2308.01501>`_.
Returns:
list[:class:`~.pennylane.estimator.resource_operator.GateCount`]: A list of ``GateCount`` objects, where each object
represents a specific quantum gate and the number of times it appears
in the decomposition.
"""
rot = Rot.resource_rep(precision=rotation_precision)
adj_cmpr_signal_op = Adjoint.resource_rep(cmpr_signal_op)
ctrl_cmpr_signal_op = Controlled.resource_rep(
base_cmpr_op=cmpr_signal_op,
num_ctrl_wires=1,
num_zero_ctrl=1,
)
ctrl_adj_cmpr_signal_op = Controlled.resource_rep(
base_cmpr_op=adj_cmpr_signal_op,
num_ctrl_wires=1,
num_zero_ctrl=0,
)
if d_minus == 0:
return [GateCount(rot, d_plus + 1), GateCount(ctrl_cmpr_signal_op, d_plus)]
return [
GateCount(rot, d_plus + d_minus + 1),
GateCount(ctrl_cmpr_signal_op, d_plus),
GateCount(ctrl_adj_cmpr_signal_op, d_minus),
]
[docs]
class GQSPTimeEvolution(ResourceOperator):
r"""Resource class for performing Hamiltonian simulation using GQSP.
Args:
walk_op (:class:`~.pennylane.estimator.resource_operator.ResourceOperator`): the quantum walk operator
time (float): the simulation time
one_norm (float): one norm of the Hamiltonian
poly_approx_precision (float): the tolerance for error in the polynomial approximation
wires (WiresLike | None): The wires the operation acts on. This includes both the wires of the
signal operator and the control wire required for block-encoding.
Resources:
The resources are obtained as described in Theorem 7 and Corollary 8 of
`Generalized Quantum Signal Processing (2024) <https://arxiv.org/pdf/2308.01501>`_.
Raises:
ValueError: if the ``wires`` provided don't match the number of wires expected by the operator
ValueError: if the ``time`` provided is not a positive real number greater than zero
ValueError: if the ``one_norm`` provided is not a positive real number greater than zero
ValueError: if the ``poly_approx_precision`` provided is not a positive real number greater than zero
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> walk_op = qre.RX(0.1, wires=0)
>>> time = 1.0
>>> one_norm = 1.0
>>> approx_error = 0.01
>>> hamsim = qre.GQSPTimeEvolution(walk_op, time, one_norm, approx_error)
>>> print(qre.estimate(hamsim))
--- Resources: ---
Total wires: 2
algorithmic wires: 2
allocated wires: 0
zero state: 0
any state: 0
Total gates : 1.110E+3
'T': 1.080E+3,
'CNOT': 12,
'X': 6,
'Hadamard': 12
"""
resource_keys = {"walk_op", "time", "one_norm", "poly_approx_precision"}
def __init__(
self,
walk_op: ResourceOperator,
time: float,
one_norm: float,
poly_approx_precision: float | None = None,
wires: WiresLike = None,
):
_dequeue(walk_op) # remove operator
self.queue()
if (not isinstance(time, (int, float))) or time <= 0:
raise (
ValueError(
f"Expected 'time' to be a positive real number greater than zero, got {time}"
)
)
if (not isinstance(one_norm, (int, float))) or one_norm <= 0:
raise (
ValueError(
f"Expected 'one_norm' to be a positive real number greater than zero, got {one_norm}"
)
)
if poly_approx_precision is not None:
if (not isinstance(poly_approx_precision, (int, float))) or poly_approx_precision <= 0:
raise (
ValueError(
f"Expected 'poly_approx_precision' to be a positive real number greater than zero, got {poly_approx_precision}"
)
)
self.walk_op = walk_op.resource_rep_from_op()
self.time = time
self.one_norm = one_norm
self.poly_approx_precision = poly_approx_precision
self.num_wires = walk_op.num_wires + 1 # control wire
if wires:
self.wires = Wires(wires)
if walk_op_wires := walk_op.wires:
self.wires = Wires.all_wires([self.wires, walk_op_wires])
if len(self.wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}.")
else:
self.wires = None
@property
def resource_params(self):
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* walk_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the quantum walk operator
* time (float): the simulation time
* one_norm (float): one norm of the Hamiltonian
* poly_approx_precision (float): the tolerance for error in the polynomial
approximation
"""
return {
"walk_op": self.walk_op,
"time": self.time,
"one_norm": self.one_norm,
"poly_approx_precision": self.poly_approx_precision,
}
[docs]
@classmethod
def resource_rep(
cls,
walk_op: CompressedResourceOp,
time: float,
one_norm: float,
poly_approx_precision: float | None = None,
) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
walk_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`): the
quantum walk operator
time (float): the simulation time
one_norm (float): one norm of the Hamiltonian
poly_approx_precision (float | None): the tolerance for error in the polynomial approximation
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if (not isinstance(time, (int, float))) or time <= 0:
raise (
ValueError(
f"Expected 'time' to be a positive real number greater than zero, got {time}"
)
)
if (not isinstance(one_norm, (int, float))) or one_norm <= 0:
raise (
ValueError(
f"Expected 'one_norm' to be a positive real number greater than zero, got {one_norm}"
)
)
if poly_approx_precision is not None:
if (not isinstance(poly_approx_precision, (int, float))) or poly_approx_precision <= 0:
raise (
ValueError(
f"Expected 'poly_approx_precision' to be a positive real number greater than zero, got {poly_approx_precision}"
)
)
num_wires = walk_op.num_wires + 1
params = {
"walk_op": walk_op,
"time": time,
"one_norm": one_norm,
"poly_approx_precision": poly_approx_precision,
}
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
walk_op: CompressedResourceOp,
time: float,
one_norm: float,
poly_approx_precision: float | None = None,
) -> list[GateCount]:
r"""Returns a list representing the resources of the operator. Each object in the list
represents a gate and the number of times it occurs in the circuit.
Args:
walk_op (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`): the
quantum walk operator
time (float): the simulation time
one_norm (float): one norm of the Hamiltonian
poly_approx_precision (float | None): the tolerance for error in the polynomial approximation
Resources:
The resources are obtained as described in Theorem 7 and Corollary 8 of
`Generalized Quantum Signal Processing (2024) <https://arxiv.org/pdf/2308.01501>`_.
Returns:
list[:class:`~.pennylane.estimator.resource_operator.GateCount`]: A list of ``GateCount`` objects, where each object
represents a specific quantum gate and the number of times it appears
in the decomposition.
"""
poly_deg = cls.poly_approx(time, one_norm, poly_approx_precision)
gqsp = GQSP.resource_rep(
walk_op,
d_plus=poly_deg,
d_minus=poly_deg,
rotation_precision=None,
)
return [GateCount(gqsp)]
[docs]
@staticmethod
def poly_approx(time: float, one_norm: float, epsilon: float) -> int:
r"""Obtain the maximum degree of the polynomial approximation required
to approximate :math:`e^{(iHt \cos{\theta})}` within some error epsilon.
Args:
time (float): the simulation time
one_norm (float): one norm of the Hamiltonian
epsilon (float): the tolerance for error in the polynomial approximation
Returns:
int: the minimum degree of the polynomial approximation
"""
N_0 = int(qnp.ceil(qnp.abs(time * one_norm))) # initial guess for the degree
error = qnp.abs(sps.jv(N_0 + 1, time * one_norm)) # initial error
N = N_0
while error > (epsilon / 2):
N += 1
error = qnp.abs(sps.jv(N + 1, time * one_norm))
return N
[docs]
class QSVT(ResourceOperator):
r"""Resource class for Quantum Singular Value Transformation (QSVT).
This operation uses a :class:`~.estimator.resource_operator.ResourceOperator` :math:`U` that
block encodes a matrix :math:`A` in its top-left block. This circuit applies a
polynomial transformation (:math:`Poly^{SV}`) of degree :math:`d` to the singular values of the
block encoded matrix:
.. math::
\begin{align}
U_{QSVT}(A, \vec{\phi}) &=
\begin{bmatrix}
Poly^{SV}(A) & \cdot \\
\cdot & \cdot
\end{bmatrix}.
\end{align}
When the degree of the polynomial is odd, the QSVT circuit is defined as:
.. math::
U_{QSVT} = \tilde{\Pi}_{\phi_1}U\left[\prod^{(d-1)/2}_{k=1}\Pi_{\phi_{2k}}U^\dagger
\tilde{\Pi}_{\phi_{2k+1}}U\right],
and when the degree is even,
.. math::
U_{QSVT} = \left[\prod^{d/2}_{k=1}\Pi_{\phi_{2k-1}}U^\dagger\tilde{\Pi}_{\phi_{2k}}U\right],
where :math:`\Pi_{\phi}` and :math:`\tilde{\Pi}_{\phi}` are projector-controlled phase shifts
(:class:`~.estimator.ops.qubit.parametric_ops_multi_qubit.PCPhase`).
.. seealso::
:func:`~.qsvt` and :class:`~.QSVT`.
Args:
block_encoding (:class:`~.estimator.resource_operator.ResourceOperator`): the block encoding operator
encoding_dims (int | tuple(int)): The dimensions of the encoded matrix.
If an integer is provided, a square matrix is assumed.
poly_deg (int): the degree of the polynomial transformation being applied
wires (WiresLike | None): the wires the operation acts on
Raises:
ValueError: if ``encoding_dims`` is not a positive integer or a tuple of two positive integers
ValueError: if ``poly_deg`` is not a positive integer greater than zero
ValueError: if the ``wires`` provided don't match the number of wires expected by the operator
Resources:
The resources are obtained as described in Theorem 4 of `A Grand Unification of Quantum Algorithms
(2021) <https://arxiv.org/pdf/2105.02859>`_.
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> block_encoding = qre.RX(0.1, wires=0)
>>> encoding_dims = (2, 2)
>>> poly_deg = 3
>>> qsvt = qre.QSVT(block_encoding, encoding_dims, poly_deg)
>>> print(qre.estimate(qsvt))
--- Resources: ---
Total wires: 1
algorithmic wires: 1
allocated wires: 0
zero state: 0
any state: 0
Total gates : 39
'T': 39
"""
resource_keys = {"block_encoding", "encoding_dims", "poly_deg"}
def __init__(
self,
block_encoding: ResourceOperator,
encoding_dims: int | tuple[int],
poly_deg: int,
wires: WiresLike = None,
):
_dequeue(block_encoding) # remove operator
if not isinstance(encoding_dims, (int, tuple)):
raise TypeError(
f"Expected `encoding_dims` to be an integer or tuple of integers. Got {encoding_dims}"
)
if isinstance(encoding_dims, int):
encoding_dims = (encoding_dims, encoding_dims)
if len(encoding_dims) == 1:
dim = encoding_dims[0]
encoding_dims = (dim, dim)
elif len(encoding_dims) > 2:
raise ValueError(
"Expected `encoding_dims` to be a tuple of two integers, representing the dimensions"
f" (row, col) of the subspace where the matrix is encoded. Got {encoding_dims}"
)
if not all(isinstance(d, int) and d > 0 for d in encoding_dims):
raise ValueError("Expected elements of `encoding_dims` to be positive integers.")
self.block_encoding = block_encoding.resource_rep_from_op()
self.encoding_dims = encoding_dims
if (not isinstance(poly_deg, int)) or poly_deg <= 0:
raise ValueError(
f"'poly_deg' must be a positive integer greater than zero, got {poly_deg}"
)
self.poly_deg = poly_deg
self.num_wires = block_encoding.num_wires
if wires is None and block_encoding.wires is not None:
wires = block_encoding.wires
if len(wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(wires)}.")
super().__init__(wires=wires)
@property
def resource_params(self):
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
* encoding_dims (int | tuple(int)): The dimensions of the encoded matrix.
If an integer is provided, a square matrix is assumed.
* poly_deg (int): the degree of the polynomial transformation being applied
"""
return {
"block_encoding": self.block_encoding,
"encoding_dims": self.encoding_dims,
"poly_deg": self.poly_deg,
}
[docs]
@classmethod
def resource_rep(
cls,
block_encoding: CompressedResourceOp,
encoding_dims: int,
poly_deg: int,
):
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
encoding_dims (int | tuple(int)): The dimensions of the encoded matrix.
If an integer is provided, a square matrix is assumed.
poly_deg (int): the degree of the polynomial transformation being applied
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(encoding_dims, (int, tuple)):
raise TypeError(
f"Expected `encoding_dims` to be an integer or tuple of integers. Got {encoding_dims}"
)
if isinstance(encoding_dims, int):
encoding_dims = (encoding_dims, encoding_dims)
if len(encoding_dims) == 1:
dim = encoding_dims[0]
encoding_dims = (dim, dim)
elif len(encoding_dims) > 2:
raise ValueError(
"Expected `encoding_dims` to be a tuple of two integers, representing the dimensions"
f" (row, col) of the subspace where the matrix is encoded. Got {encoding_dims}"
)
if not all(isinstance(d, int) and d > 0 for d in encoding_dims):
raise ValueError("Expected elements of `encoding_dims` to be positive integers.")
if (not isinstance(poly_deg, int)) or poly_deg <= 0:
raise ValueError(
f"'poly_deg' must be a positive integer greater than zero, got {poly_deg}"
)
num_wires = block_encoding.num_wires
params = {
"block_encoding": block_encoding,
"encoding_dims": encoding_dims,
"poly_deg": poly_deg,
}
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
block_encoding: CompressedResourceOp,
encoding_dims: int,
poly_deg: int,
):
r"""Returns a list representing the resources of the operator. Each object in the list
represents a gate and the number of times it occurs in the circuit.
Args:
block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
encoding_dims (int | tuple(int)): The dimensions of the encoded matrix.
If an integer is provided, a square matrix is assumed.
poly_deg (int): the degree of the polynomial transformation being applied
Resources:
The resources are obtained as described in Theorem 4 of `A Grand Unification of Quantum Algorithms
(2021) <https://arxiv.org/pdf/2105.02859>`_.
Returns:
list[:class:`~.pennylane.estimator.resource_operator.GateCount`]: A list of ``GateCount`` objects, where each object
represents a specific quantum gate and the number of times it appears
in the decomposition.
"""
num_rows, num_cols = encoding_dims
num_wires = block_encoding.num_wires
pi = PCPhase.resource_rep(num_wires=num_wires, dim=num_cols)
pi_tilde = PCPhase.resource_rep(num_wires=num_wires, dim=num_rows)
block_encoding_adj = Adjoint.resource_rep(block_encoding)
if poly_deg % 2 == 0: # even degree
be_counts = poly_deg // 2
be_adj_counts = poly_deg // 2
pi_counts = poly_deg // 2
pi_tilde_counts = poly_deg // 2
else: # odd degree
be_counts = ((poly_deg - 1) // 2) + 1
be_adj_counts = (poly_deg - 1) // 2
pi_counts = (poly_deg - 1) // 2
pi_tilde_counts = ((poly_deg - 1) // 2) + 1
return [
GateCount(block_encoding, be_counts),
GateCount(block_encoding_adj, be_adj_counts),
GateCount(pi, pi_counts),
GateCount(pi_tilde, pi_tilde_counts),
]
[docs]
class QSP(ResourceOperator):
r"""Implements the `Quantum Signal Processing <https://arxiv.org/pdf/2105.02859>`_
(QSP) circuit.
This template estimates the resources for a QSP circuit of degree :math:`d` (``poly_deg``).
The circuit uses a single-qubit :class:`~.estimator.resource_operator.ResourceOperator`
:math:`W(a)` that block encodes a scalar value :math:`a` in its top-left entry.
The circuit is given as follows in the Z-convention (``convention="Z"``):
.. math::
\hat{U}_{QSP} = e^{i\phi_{0}\hat{Z}}\prod^{d}_{k=1}\hat{W}(a)e^{i\phi_{k}\hat{Z}} .
The circuit can also be expressed in the X-convention (``convention="X"``):
.. math::
\hat{U}_{QSP} = e^{i\phi_{0}\hat{X}}\prod^{d}_{k=1}\hat{W}(a)e^{i\phi_{k}\hat{X}} .
.. seealso::
:func:`~.qsvt` and :class:`~.QSVT`.
Args:
block_encoding (:class:`~.estimator.resource_operator.ResourceOperator`): the block encoding operator
poly_deg (int): the degree of the polynomial transformation being applied
convention (str): the basis used for the rotation operators, valid conventions are ``"X"`` or ``"Z"``
rotation_precision (float | None): The error threshold for the approximate Clifford + T
decomposition of the single qubit rotation gates used to implement this operation.
wires (WiresLike | None): the wires the operation acts on
Raises:
ValueError: if the block encoding operator acts on more than one qubit
ValueError: if the convention is not ``"X"`` or ``"Z"``
Resources:
The resources are obtained as described in Theorem 1 of `A Grand Unification of Quantum Algorithms
(2021) <https://arxiv.org/pdf/2105.02859>`_.
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> block_encoding = qre.RX(0.1, wires=0)
>>> poly_deg = 3
>>> qsp = qre.QSP(block_encoding, poly_deg, convention="Z", rotation_precision=1e-5)
>>> print(qre.estimate(qsp))
--- Resources: ---
Total wires: 1
algorithmic wires: 1
allocated wires: 0
zero state: 0
any state: 0
Total gates : 151
'T': 151
"""
resource_keys = {"block_encoding", "poly_deg", "convention", "rotation_precision"}
def __init__(
self,
block_encoding: ResourceOperator,
poly_deg: int,
convention: str = "Z",
rotation_precision: float | None = None,
wires: WiresLike = None,
):
_dequeue(block_encoding) # remove operator
if block_encoding.num_wires > 1:
raise ValueError("The block encoding operator should act on a single qubit!")
if not (convention in {"Z", "X"}):
raise ValueError(f"The valid conventions are 'Z' or 'X'. Got {convention}")
self.block_encoding = block_encoding.resource_rep_from_op()
self.convention = convention
self.poly_deg = poly_deg
self.rotation_precision = rotation_precision
self.num_wires = 1
if wires is not None and len(wires) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(wires)}.")
super().__init__(wires=wires)
@property
def resource_params(self):
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
* poly_deg (int): the degree of the polynomial transformation being applied
* convention (str): the basis used for the rotation operators, valid conventions are ``"X"`` or ``"Z"``
* rotation_precision (float | None): The error threshold for the approximate Clifford + T
decomposition of the single qubit rotation gates used to implement this operation.
"""
return {
"block_encoding": self.block_encoding,
"poly_deg": self.poly_deg,
"convention": self.convention,
"rotation_precision": self.rotation_precision,
}
[docs]
@classmethod
def resource_rep(
cls,
block_encoding: CompressedResourceOp,
poly_deg: int,
convention: str = "Z",
rotation_precision: float | None = None,
):
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
poly_deg (int): the degree of the polynomial transformation being applied
convention (str):the basis used for the rotation operators, valid conventions are ``"X"`` or ``"Z"``
rotation_precision (float | None): The error threshold for the approximate Clifford + T
decomposition of the single qubit rotation gates used to implement this operation.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if block_encoding.num_wires > 1:
raise ValueError("The block encoding operator should act on a single qubit!")
if not (convention in {"Z", "X"}):
raise ValueError(f"The valid conventions are 'Z' or 'X'. Got {convention}")
params = {
"block_encoding": block_encoding,
"poly_deg": poly_deg,
"convention": convention,
"rotation_precision": rotation_precision,
}
return CompressedResourceOp(cls, num_wires=1, params=params)
[docs]
@classmethod
def resource_decomp(
cls,
block_encoding: CompressedResourceOp,
poly_deg: int,
convention: str = "Z",
rotation_precision: float | None = None,
):
r"""Returns a list representing the resources of the operator. Each object in the list
represents a gate and the number of times it occurs in the circuit.
Args:
block_encoding (:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`):
the block encoding operator
poly_deg (int): the degree of the polynomial transformation being applied
convention (str): the basis used for the rotation operators, valid conventions are ``"X"`` or ``"Z"``
rotation_precision (float): The error threshold for the approximate Clifford + T
decomposition of the single qubit rotation gates used to implement this operation.
Resources:
The resources are obtained as described in Theorem 1 of `A Grand Unification of Quantum Algorithms
(2021) <https://arxiv.org/pdf/2105.02859>`_.
Raises:
ValueError: if the convention is not ``"X"`` or ``"Z"``
Returns:
list[:class:`~.pennylane.estimator.resource_operator.GateCount`]: A list of ``GateCount`` objects, where each object
represents a specific quantum gate and the number of times it appears
in the decomposition.
"""
if convention == "Z":
rot_op = RZ.resource_rep(rotation_precision)
elif convention == "X":
rot_op = RX.resource_rep(rotation_precision)
else:
raise ValueError(f"The valid conventions are 'Z' or 'X'. Got {convention}")
return [
GateCount(block_encoding, poly_deg),
GateCount(rot_op, poly_deg + 1),
]
_modules/pennylane/estimator/templates/qsp
Download Python script
Download Notebook
View on GitHub