Source code for pennylane.estimator.templates.stateprep
# 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.
r"""Resource operators for state preparation templates."""
import math
import pennylane.estimator as qre
import pennylane.numpy as np
from pennylane.estimator.compact_hamiltonian import THCHamiltonian
from pennylane.estimator.resource_operator import (
CompressedResourceOp,
GateCount,
ResourceOperator,
resource_rep,
)
from pennylane.estimator.wires_manager import Allocate, Deallocate
from pennylane.wires import Wires, WiresLike
# pylint: disable= signature-differs, arguments-differ, too-many-arguments
[docs]
class UniformStatePrep(ResourceOperator):
r"""Resource class for preparing a uniform superposition.
This operation prepares a uniform superposition over a given number of
basis states. The uniform superposition is defined as:
.. math::
\frac{1}{\sqrt{l}} \sum_{i=0}^{l} |i\rangle
where :math:`l` is the number of states.
This operation uses ``Hadamard`` gates to create the uniform superposition when
the number of states is a power of two. If the number of states is not a power of two,
the amplitude amplification technique defined in
`arXiv:1805.03662 <https://arxiv.org/abs/1805.03662>`_ is used.
Args:
num_states (int): the number of states in the uniform superposition
wires (WiresLike | None): the wires the operation acts on
Resources:
The resources are obtained from Figure 12 in `arXiv:1805.03662 <https://arxiv.org/abs/1805.03662>`_.
The circuit uses amplitude amplification to prepare a uniform superposition over :math:`l`
basis states.
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> unif_state_prep = qre.UniformStatePrep(10)
>>> print(qre.estimate(unif_state_prep))
--- Resources: ---
Total wires: 5
algorithmic wires: 4
allocated wires: 1
zero state: 1
any state: 0
Total gates : 124
'Toffoli': 4,
'T': 88,
'CNOT': 4,
'X': 12,
'Hadamard': 16
"""
resource_keys = {"num_states"}
def __init__(self, num_states: int, wires: WiresLike = None):
self.num_states = num_states
k = (num_states & -num_states).bit_length() - 1
L = num_states // (2**k)
self.num_wires = k
if L != 1:
self.num_wires += int(math.ceil(math.log2(L)))
if wires is not None and len(Wires(wires)) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* num_states (int): the number of states over which the uniform superposition is being prepared
"""
return {"num_states": self.num_states}
[docs]
@classmethod
def resource_rep(cls, num_states: int) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
k = (num_states & -num_states).bit_length() - 1
L = num_states // (2**k)
num_wires = k
if L != 1:
num_wires += int(math.ceil(math.log2(L)))
return CompressedResourceOp(cls, num_wires, {"num_states": num_states})
[docs]
@classmethod
def resource_decomp(cls, num_states: int) -> 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:
num_states (int): the number of states over which the uniform superposition is being prepared
Resources:
The resources are obtained from Figure 12 in `arXiv:1805.03662 <https://arxiv.org/abs/1805.03662>`_.
The circuit uses amplitude amplification to prepare a uniform superposition over :math:`l` basis states.
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.
"""
gate_lst = []
k = (num_states & -num_states).bit_length() - 1
L = num_states // (2**k)
if L == 1:
gate_lst.append(GateCount(resource_rep(qre.Hadamard), k))
return gate_lst
logl = int(math.ceil(math.log2(L)))
gate_lst.append(GateCount(resource_rep(qre.Hadamard), k + 3 * logl))
gate_lst.append(
GateCount(resource_rep(qre.IntegerComparator, {"value": L, "register_size": logl}), 1)
)
gate_lst.append(GateCount(resource_rep(qre.RZ), 2))
gate_lst.append(
GateCount(
resource_rep(
qre.Adjoint,
{
"base_cmpr_op": resource_rep(
qre.IntegerComparator, {"value": L, "register_size": logl}
)
},
),
1,
)
)
return gate_lst
[docs]
class AliasSampling(ResourceOperator):
r"""Resource class for preparing a state using coherent alias sampling.
Args:
num_coeffs (int): the number of unique coefficients in the state
precision (float): the precision with which the coefficients are loaded
wires (WiresLike | None): the wires the operation acts on
Resources:
The resources are obtained from Section III D in `arXiv:1805.03662 <https://arxiv.org/abs/1805.03662>`_.
The circuit uses coherent alias sampling to prepare a state with the given coefficients.
**Example**
The resources for this operation are computed using:
>>> alias_sampling = qre.AliasSampling(num_coeffs=100)
>>> print(qre.estimate(alias_sampling))
--- Resources: ---
Total wires: 133
algorithmic wires: 7
allocated wires: 126
zero state: 58
any state: 68
Total gates : 6.505E+3
'Toffoli': 272,
'T': 88,
'CNOT': 4.646E+3,
'X': 595,
'Hadamard': 904
"""
resource_keys = {"num_coeffs", "precision"}
def __init__(self, num_coeffs: int, precision: float | None = None, wires: WiresLike = None):
self.num_coeffs = num_coeffs
self.precision = precision
self.num_wires = int(math.ceil(math.log2(num_coeffs)))
if wires is not None and len(Wires(wires)) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* num_coeffs (int): the number of unique coefficients in the state
* precision (float): the precision with which the coefficients are loaded
"""
return {"num_coeffs": self.num_coeffs, "precision": self.precision}
[docs]
@classmethod
def resource_rep(cls, num_coeffs: int, precision: float | None = None) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
num_wires = int(math.ceil(math.log2(num_coeffs)))
return CompressedResourceOp(
cls, num_wires, {"num_coeffs": num_coeffs, "precision": precision}
)
[docs]
@classmethod
def resource_decomp(cls, num_coeffs: int, 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:
num_coeffs (int): the number of unique coefficients in the state
precision (float): the precision with which the coefficients are loaded
Resources:
The resources are obtained from Section III D in `arXiv:1805.03662 <https://arxiv.org/abs/1805.03662>`_.
The circuit uses coherent alias sampling to prepare a state with the given coefficients.
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.
"""
gate_lst = []
logl = int(math.ceil(math.log2(num_coeffs)))
num_prec_wires = abs(math.floor(math.log2(precision)))
gate_lst.append(Allocate(logl + 2 * num_prec_wires + 1))
gate_lst.append(
GateCount(resource_rep(qre.UniformStatePrep, {"num_states": num_coeffs}), 1)
)
gate_lst.append(GateCount(resource_rep(qre.Hadamard), num_prec_wires))
gate_lst.append(
GateCount(
resource_rep(
qre.QROM,
{"num_bitstrings": num_coeffs, "size_bitstring": logl + num_prec_wires},
),
1,
)
)
gate_lst.append(
GateCount(
resource_rep(
qre.RegisterComparator,
{"first_register": num_prec_wires, "second_register": num_prec_wires},
),
1,
)
)
gate_lst.append(GateCount(resource_rep(qre.CSWAP), logl))
return gate_lst
[docs]
class MPSPrep(ResourceOperator):
r"""Resource class for the MPSPrep template.
The resource operation for preparing an initial state from a matrix product state (MPS)
representation.
Args:
num_mps_matrices (int): the number of matrices in the MPS representation
max_bond_dim (int): the bond dimension of the MPS representation
precision (float | None): the precision used when loading the MPS matricies
wires (WiresLike | None): the wires the operation acts on
Resources:
The resources for MPSPrep rely on a decomposition which uses the generic
:class:`~.pennylane.estimator.QubitUnitary`. This decomposition is based on
the routine described in `arXiv:2310.18410 <https://arxiv.org/abs/2310.18410>`_.
.. seealso:: :class:`~.MPSPrep`
**Example**
The resources for this operation are computed using:
>>> mps = qre.MPSPrep(num_mps_matrices=10, max_bond_dim=2**3)
>>> print(qre.estimate(mps, gate_set={"CNOT", "RZ", "RY"}))
--- Resources: ---
Total wires: 13
algorithmic wires: 10
allocated wires: 3
zero state: 3
any state: 0
Total gates : 1.654E+3
'RZ': 728,
'RY': 152,
'CNOT': 774
"""
resource_keys = {"num_mps_matrices", "max_bond_dim", "precision"}
def __init__(
self,
num_mps_matrices: int,
max_bond_dim: int,
precision: float | None = None,
wires: WiresLike = None,
):
self.num_wires = num_mps_matrices
self.precision = precision
self.max_bond_dim = max_bond_dim
self.num_mps_matrices = num_mps_matrices
if wires is not None and len(Wires(wires)) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* num_mps_matrices (int): the number of matrices in the MPS representation
* max_bond_dim (int): the bond dimension of the MPS representation
* precision (float | None): the precision used when loading the
MPS matrices
"""
return {
"num_mps_matrices": self.num_mps_matrices,
"max_bond_dim": self.max_bond_dim,
"precision": self.precision,
}
[docs]
@classmethod
def resource_rep(
cls, num_mps_matrices: int, max_bond_dim: int, 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:
num_mps_matrices (int): the number of matrices in the MPS representation
max_bond_dim (int): the bond dimension of the MPS representation
precision (float | None): the precision used when loading the MPS matrices
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
params = {
"num_mps_matrices": num_mps_matrices,
"max_bond_dim": max_bond_dim,
"precision": precision,
}
num_wires = num_mps_matrices
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
num_mps_matrices: int,
max_bond_dim: int,
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:
num_mps_matrices (int): the number of matrices in the MPS representation
max_bond_dim (int): the bond dimension of the MPS representation
precision (float | None): the precision used when loading
the MPS matrices
Resources:
The resources for MPSPrep are estimated according to the decomposition, which uses the generic
:class:`~.pennylane.estimator.QubitUnitary`. The decomposition is based on
the routine described in `arXiv:2310.18410 <https://arxiv.org/abs/2310.18410>`_.
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_work_wires = min(
math.ceil(math.log2(max_bond_dim)), math.ceil(num_mps_matrices / 2) # truncate bond dim
)
gate_lst = [Allocate(num_work_wires)]
for index in range(1, num_mps_matrices + 1):
qubit_unitary_wires = min(index + 1, num_work_wires + 1, (num_mps_matrices - index) + 2)
qubit_unitary = qre.QubitUnitary.resource_rep(
num_wires=qubit_unitary_wires, precision=precision
)
gate_lst.append(GateCount(qubit_unitary))
gate_lst.append(Deallocate(num_work_wires))
return gate_lst
[docs]
@classmethod
def tracking_name(cls, num_mps_matrices, max_bond_dim, precision) -> str:
return f"MPSPrep({num_mps_matrices}, {max_bond_dim}, {precision})"
[docs]
class QROMStatePreparation(ResourceOperator):
r"""Resource class for the QROMStatePreparation template.
This operation implements the state preparation method described
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_, using
:class:`~.pennylane.estimator.QROM` to dynamically load the rotation angles.
.. note::
This decomposition assumes an appropriately sized phase gradient state is available.
Users should ensure the cost of constructing such a state has been accounted for.
See also :class:`~.pennylane.pennylane.estimator.PhaseGradient`.
Args:
num_state_qubits (int): number of qubits required to represent the statevector
precision (float): the precision threshold for loading in the binary representation
of the rotation angles
positive_and_real (bool): indicates whether or not the coefficients of the statevector are all real
and positive
select_swap_depths (int | Iterable(int) | None): A parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth.
Can be ``None``, ``1`` or a positive integer power of two.
Defaults to ``None``, which internally corresponds to the optimal depth.
wires (WiresLike | None): The wires on which to prepare the target state. This excludes any
additional qubits allocated during the decomposition (via select-swap).
Resources:
The resources for QROMStatePreparation are computed according to the decomposition described
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_, using
:class:`~.pennylane.estimator.QROM` to dynamically load the rotation angles.
These rotations gates are implemented using an in-place controlled-adder operation
(see figure 4. of `arXiv:2409.07332 <https://arxiv.org/abs/2409.07332>`_) to a phase gradient state.
.. seealso:: :class:`~.QROMStatePreparation`
**Example**
The resources for this operation are computed using:
>>> qrom_prep = qre.QROMStatePreparation(num_state_qubits=5, precision=1e-3)
>>> print(qre.estimate(qrom_prep))
--- Resources: ---
Total wires: 28
algorithmic wires: 5
allocated wires: 23
zero state: 23
any state: 0
Total gates : 2.756E+3
'Toffoli': 236,
'CNOT': 1.522E+3,
'X': 230,
'Z': 12,
'S': 24,
'Hadamard': 732
.. details::
:title: Usage Details
This operation uses the :code:`QROM` subroutine to dynamically load the rotation angles.
>>> import pennylane.estimator as qre
>>> gate_set = {"QROM", "Hadamard", "CNOT", "T", "Adjoint(QROM)"}
>>> qrom_prep = qre.QROMStatePreparation(
... num_state_qubits = 4,
... precision = 1e-2,
... select_swap_depths = 1,
... )
>>> res = qre.estimate(qrom_prep, gate_set)
>>> print(res)
--- Resources: ---
Total wires: 21
algorithmic wires: 4
allocated wires: 17
zero state: 17
any state: 0
Total gates : 2.680E+3
'QROM': 5,
'Adjoint(QROM)': 5,
'T': 1.832E+3,
'CNOT': 580,
'Hadamard': 258
The ``precision`` argument is used to allocate the target wires in the underlying QROM
operations. It corresponds to the precision with which the rotation angles of the
template are encoded. This means that the binary representation of the angle is truncated up to
the :math:`m`-th digit, where :math:`m` is the number of precision wires allocated. See Eq. 5
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_ for more details.
The ``select_swap_depths`` parameter allows a user to configure the ``select_swap_depth`` of
each individual :class:`~.pennylane.estimator.QROM` used. The
``select_swap_depths`` argument can be one of :code:`(int, None, Iterable(int, None))`.
If an integer or :code:`None` is passed (the default value for this parameter is 1), then that
is used as the ``select_swap_depth`` for all :code:`QROM` operations in the resource decomposition.
>>> print(res.gate_breakdown())
Adjoint(QROM) total: 5
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=9, params={'num_bit_flips':4, 'num_bitstrings':1, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=10, params={'num_bit_flips':9, 'num_bitstrings':2, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=11, params={'num_bit_flips':18, 'num_bitstrings':4, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=12, params={'num_bit_flips':36, 'num_bitstrings':8, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=13, params={'num_bit_flips':72, 'num_bitstrings':16, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
QROM total: 5
QROM {'num_bit_flips': 4, 'num_bitstrings': 1, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 9, 'num_bitstrings': 2, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 18, 'num_bitstrings': 4, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 36, 'num_bitstrings': 8, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 72, 'num_bitstrings': 16, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
T total: 1.832E+3
CNOT total: 580
Hadamard total: 258
Alternatively, we can configure each value independently by specifying a list. Note the size
of this list should be :code:`num_state_qubits + 1` (or :code:`num_state_qubits` if the state
is positive and real).
>>> qrom_prep = qre.QROMStatePreparation(
... num_state_qubits = 4,
... precision = 1e-2,
... select_swap_depths = [1, None, 1, 1, None],
... )
>>> res = qre.estimate(qrom_prep, gate_set)
>>> print(res.gate_breakdown())
Adjoint(QROM) total: 5
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=9, params={'num_bit_flips':4, 'num_bitstrings':1, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=10, params={'num_bit_flips':9, 'num_bitstrings':2, 'restored':False, 'select_swap_depth':None, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=11, params={'num_bit_flips':18, 'num_bitstrings':4, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=12, params={'num_bit_flips':36, 'num_bitstrings':8, 'restored':False, 'select_swap_depth':1, 'size_bitstring':9})}: 1
Adjoint(QROM) {'base_cmpr_op': CompressedResourceOp(QROM, num_wires=13, params={'num_bit_flips':72, 'num_bitstrings':16, 'restored':False, 'select_swap_depth':None, 'size_bitstring':9})}: 1
QROM total: 5
QROM {'num_bit_flips': 4, 'num_bitstrings': 1, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 9, 'num_bitstrings': 2, 'restored': False, 'select_swap_depth': None, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 18, 'num_bitstrings': 4, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 36, 'num_bitstrings': 8, 'restored': False, 'select_swap_depth': 1, 'size_bitstring': 9}: 1
QROM {'num_bit_flips': 72, 'num_bitstrings': 16, 'restored': False, 'select_swap_depth': None, 'size_bitstring': 9}: 1
T total: 1.832E+3
CNOT total: 580
Hadamard total: 258
"""
resource_keys = {"num_state_qubits", "precision", "positive_and_real", "selswap_depths"}
def __init__(
self,
num_state_qubits: int,
precision: float | None = None,
positive_and_real: bool = False,
select_swap_depths: int = 1,
wires: WiresLike = None,
):
# Overriding the default init method to allow for CompactState as an input.
self.num_wires = num_state_qubits
if wires is not None and len(Wires(wires)) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}")
self.precision = precision
self.positive_and_real = positive_and_real
expected_size = num_state_qubits if positive_and_real else num_state_qubits + 1
if isinstance(select_swap_depths, (list, tuple, np.ndarray)):
if len(select_swap_depths) != expected_size:
raise ValueError(
f"Expected the length of `select_swap_depths` to be {expected_size}, got {len(select_swap_depths)}"
)
elif not (isinstance(select_swap_depths, int) or select_swap_depths is None):
raise TypeError("`select_swap_depths` must be an integer, None or iterable")
self.selswap_depths = select_swap_depths
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* num_state_qubits (int): number of qubits required to represent the state-vector
* precision (float): the precision threshold for loading in the binary representation
of the rotation angles
* positive_and_real (bool): flag that the coefficients of the statevector are all real
and positive
* selswap_depths (int | Iterable(int) | None): a parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth
"""
return {
"num_state_qubits": self.num_wires,
"precision": self.precision,
"positive_and_real": self.positive_and_real,
"selswap_depths": self.selswap_depths,
}
[docs]
@classmethod
def resource_rep(
cls,
num_state_qubits: int,
precision: float | None = None,
positive_and_real: bool = False,
selswap_depths=1,
) -> CompressedResourceOp:
r"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
num_state_qubits (int): number of qubits required to represent the state-vector
precision (float): the precision threshold for loading in the binary representation
of the rotation angles
positive_and_real (bool): flag that the coefficients of the statevector are all real
and positive
selswap_depths (int | Iterable(int) | None): a parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
expected_size = num_state_qubits if positive_and_real else num_state_qubits + 1
if isinstance(selswap_depths, (list, tuple, np.ndarray)):
if len(selswap_depths) != expected_size:
raise ValueError(
f"Expected the length of `selswap_depths` to be {expected_size}, got {len(selswap_depths)}"
)
elif not (isinstance(selswap_depths, int) or selswap_depths is None):
raise TypeError("`selswap_depths` must be an integer, None or iterable")
params = {
"num_state_qubits": num_state_qubits,
"precision": precision,
"positive_and_real": positive_and_real,
"selswap_depths": selswap_depths,
}
num_wires = num_state_qubits
return CompressedResourceOp(cls, num_wires, params)
@classmethod
def _decomp_selection_helper(
cls,
use_phase_grad_trick: bool,
num_state_qubits: int,
positive_and_real: bool,
precision: float | None = None,
selswap_depths=1,
) -> list[GateCount]:
r"""A private function which implements two variants of the decomposition of QROMStatePrep,
based on the value of the :code:`use_phase_grad_trick` argument.
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:
use_phase_grad_trick (bool): a flag which determines if the phase gradient trick is used
instead of controlled-RY gates and phaseshifts.
num_state_qubits (int): number of qubits required to represent the state-vector
positive_and_real (bool): flag that the coefficients of the statevector are all real
and positive
precision (float): the precision threshold for loading in the binary representation
of the rotation angles
select_swap_depths (int | Iterable(int) | None): a parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth
Resources:
The resources for QROMStatePreparation are according to the decomposition as described
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_, using
:class:`~.pennylane.estimator.QROM` to dynamically load the rotation angles.
Controlled-RY (and phase shifts) gates are used to apply all of the rotations coherently. If
:code:`use_phase_grad_trick == True` then these rotations gates are implmented using an
inplace controlled semi-adder operation (see figure 4. of
`arXiv:2409.07332 <https://arxiv.org/abs/2409.07332>`_).
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.
"""
gate_counts = []
expected_size = num_state_qubits if positive_and_real else num_state_qubits + 1
if isinstance(selswap_depths, int) or selswap_depths is None:
selswap_depths = [selswap_depths] * expected_size
num_precision_wires = math.ceil(math.log2(math.pi / precision))
gate_counts.append(Allocate(num_precision_wires))
for j in range(num_state_qubits):
num_bitstrings = 2**j
num_bit_flips = num_bitstrings * num_precision_wires // 2
gate_counts.append(
GateCount(
qre.QROM.resource_rep(
num_bitstrings=num_bitstrings,
size_bitstring=num_precision_wires,
num_bit_flips=num_bit_flips,
restored=False,
select_swap_depth=selswap_depths[j],
)
)
)
gate_counts.append(
GateCount(
qre.Adjoint.resource_rep(
qre.resource_rep(
qre.QROM,
{
"num_bitstrings": num_bitstrings,
"num_bit_flips": num_bit_flips,
"size_bitstring": num_precision_wires,
"restored": False,
"select_swap_depth": selswap_depths[j],
},
),
)
)
)
if use_phase_grad_trick:
semi_adder = qre.SemiAdder.resource_rep(max_register_size=num_precision_wires)
h = qre.Hadamard.resource_rep()
s = qre.S.resource_rep()
s_dagg = qre.Adjoint.resource_rep(base_cmpr_op=s)
gate_counts.append(
GateCount(
qre.Controlled.resource_rep(
base_cmpr_op=semi_adder, num_ctrl_wires=1, num_zero_ctrl=0
),
count=num_state_qubits,
)
)
gate_counts.append(GateCount(h, 2 * num_precision_wires))
gate_counts.append(GateCount(s, num_precision_wires))
gate_counts.append(
GateCount(s_dagg, num_precision_wires)
) # map RY rotations to RZ for phase grad
else:
cry = qre.CRY.resource_rep()
gate_counts.append(GateCount(cry, num_precision_wires * num_state_qubits))
if not positive_and_real:
gate_counts.append(
GateCount(
qre.QROM.resource_rep(
num_bitstrings=2**num_state_qubits,
size_bitstring=num_precision_wires,
num_bit_flips=((2**num_state_qubits) * num_precision_wires // 2),
restored=False,
select_swap_depth=selswap_depths[-1],
)
)
)
gate_counts.append(
GateCount(
qre.Adjoint.resource_rep(
qre.resource_rep(
qre.QROM,
{
"num_bitstrings": 2**num_state_qubits,
"size_bitstring": num_precision_wires,
"num_bit_flips": ((2**num_state_qubits) * num_precision_wires // 2),
"restored": False,
"select_swap_depth": selswap_depths[-1],
},
),
)
)
)
if use_phase_grad_trick:
semi_adder = qre.SemiAdder.resource_rep(max_register_size=num_precision_wires)
gate_counts.append(
GateCount(
qre.Controlled.resource_rep(
base_cmpr_op=semi_adder, num_ctrl_wires=1, num_zero_ctrl=0
),
)
)
else:
phase_shift = qre.PhaseShift.resource_rep()
gate_counts.append(GateCount(phase_shift, num_precision_wires))
gate_counts.append(Deallocate(num_precision_wires))
return gate_counts
[docs]
@classmethod
def controlled_ry_resource_decomp(
cls,
num_state_qubits: int,
positive_and_real: bool,
precision: float | None = None,
selswap_depths=1,
) -> 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:
num_state_qubits (int): number of qubits required to represent the state-vector
positive_and_real (bool): Flag that the coefficients of the statevector are all real
and positive.
precision (float): The precision threshold for loading in the binary representation
of the rotation angles.
selswap_depths (int | Iterable(int) | None): A parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth.
Resources:
The resources for QROMStatePreparation are according to the decomposition as described
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_, using
:class:`~.pennylane.estimator.QROM` to dynamically load the rotation angles.
Controlled-RY (and phase shifts) gates are used to apply all of the rotations coherently.
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.
"""
return cls._decomp_selection_helper(
use_phase_grad_trick=False,
num_state_qubits=num_state_qubits,
positive_and_real=positive_and_real,
precision=precision,
selswap_depths=selswap_depths,
)
[docs]
@classmethod
def resource_decomp(
cls,
num_state_qubits: int,
positive_and_real: bool,
precision: float | None = None,
selswap_depths=1,
) -> 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.
.. note::
This decomposition assumes an appropriately sized phase gradient state is available.
Users should ensure the cost of constructing such a state has been accounted for.
See also :class:`~.pennylane.pennylane.estimator.PhaseGradient`.
Args:
num_state_qubits (int): number of qubits required to represent the state-vector
positive_and_real (bool): Flag that the coefficients of the statevector are all real
and positive.
precision (float): The precision threshold for loading in the binary representation
of the rotation angles.
selswap_depths (int | Iterable(int) | None): A parameter of :code:`QROM`
used to trade-off extra qubits for reduced circuit depth.
Resources:
The resources for QROMStatePreparation are according to the decomposition as described
in `arXiv:0208112 <https://arxiv.org/abs/quant-ph/0208112>`_, using
:class:`~.pennylane.estimator.QROM` to dynamically load the rotation angles.
These rotations gates are implmented using an inplace controlled-adder operation
(see figure 4. of `arXiv:2409.07332 <https://arxiv.org/abs/2409.07332>`_) to phase gradient.
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.
"""
return cls._decomp_selection_helper(
use_phase_grad_trick=True,
num_state_qubits=num_state_qubits,
positive_and_real=positive_and_real,
precision=precision,
selswap_depths=selswap_depths,
)
[docs]
class PrepTHC(ResourceOperator):
r"""Resource class for preparing the state for tensor hypercontracted (THC) Hamiltonian.
This operator customizes the Prepare circuit based on the structure of THC Hamiltonian.
Args:
thc_ham (:class:`~pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian for which the state is being prepared
coeff_precision (int | None): The number of bits used to represent the precision for loading
the coefficients of Hamiltonian. If :code:`None` is provided, the default value from the
:class:`~.pennylane.estimator.resource_config.ResourceConfig` is used.
select_swap_depth (int | None): A parameter of :class:`~.pennylane.estimator.templates.subroutines.QROM`
used to trade-off extra wires for reduced circuit depth. Defaults to :code:`None`, which internally determines the optimal depth.
wires (WiresLike | None): the wires on which the operator acts
Resources:
The resources are calculated based on Figures 3 and 4 in `arXiv:2011.03494 <https://arxiv.org/abs/2011.03494>`_
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> thc_ham = qre.THCHamiltonian(num_orbitals=20, tensor_rank=40)
>>> res = qre.estimate(qre.PrepTHC(thc_ham, coeff_precision=15))
>>> print(res)
--- Resources: ---
Total wires: 185
algorithmic wires: 12
allocated wires: 173
zero state: 28
any state: 145
Total gates : 1.485E+4
'Toffoli': 467,
'CNOT': 1.307E+4,
'X': 512,
'Hadamard': 797
"""
resource_keys = {"thc_ham", "coeff_precision", "select_swap_depth"}
def __init__(
self,
thc_ham: THCHamiltonian,
coeff_precision: int | None = None,
select_swap_depth: int | None = None,
wires: WiresLike | None = None,
):
if not isinstance(thc_ham, THCHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for PrepTHC."
f"This method works with thc Hamiltonian, {type(thc_ham)} provided"
)
if not isinstance(coeff_precision, int) and coeff_precision is not None:
raise TypeError(
f"`coeff_precision` must be an integer, but type {type(coeff_precision)} was provided."
)
self.thc_ham = thc_ham
self.coeff_precision = coeff_precision
self.select_swap_depth = select_swap_depth
tensor_rank = thc_ham.tensor_rank
self.num_wires = 2 * int(math.ceil(math.log2(tensor_rank + 1)))
if wires is not None and len(Wires(wires)) != self.num_wires:
raise ValueError(f"Expected {self.num_wires} wires, got {len(Wires(wires))}")
super().__init__(wires=wires)
@property
def resource_params(self) -> dict:
r"""Returns a dictionary containing the minimal information needed to compute the resources.
Returns:
dict: A dictionary containing the resource parameters:
* thc_ham (:class:`~.pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian for which the state is being prepared
* coeff_precision (int | None): The number of bits used to represent the precision for loading
the coefficients of Hamiltonian. If :code:`None` is provided, the default value from the
:class:`~.pennylane.estimator.resource_config.ResourceConfig` is used.
* select_swap_depth (int | None): A parameter of :class:`~.pennylane.estimator.templates.QROM`
used to trade-off extra wires for reduced circuit depth. Defaults to :code:`None`, which internally determines the optimal depth.
"""
return {
"thc_ham": self.thc_ham,
"coeff_precision": self.coeff_precision,
"select_swap_depth": self.select_swap_depth,
}
[docs]
@classmethod
def resource_rep(
cls,
thc_ham: THCHamiltonian,
coeff_precision: int | None = None,
select_swap_depth: int | None = None,
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute a resource estimation.
Args:
thc_ham (:class:`~pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian for which the state is being prepared
coeff_precision (int | None): The number of bits used to represent the precision for loading
the coefficients of Hamiltonian. If :code:`None` is provided, the default value from the
:class:`~.pennylane.estimator.resource_config.ResourceConfig` is used.
select_swap_depth (int | None): A parameter of :class:`~.pennylane.estimator.templates.QROM`
used to trade-off extra wires for reduced circuit depth. Defaults to :code:`None`, which internally determines the optimal depth.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(thc_ham, THCHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for PrepTHC."
f"This method works with thc Hamiltonian, {type(thc_ham)} provided"
)
if not isinstance(coeff_precision, int) and coeff_precision is not None:
raise TypeError(
f"`coeff_precision` must be an integer, but type {type(coeff_precision)} was provided."
)
tensor_rank = thc_ham.tensor_rank
num_wires = 2 * int(math.ceil(math.log2(tensor_rank + 1)))
params = {
"thc_ham": thc_ham,
"coeff_precision": coeff_precision,
"select_swap_depth": select_swap_depth,
}
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
thc_ham: THCHamiltonian,
coeff_precision: int | None = None,
select_swap_depth: int | None = None,
) -> list[GateCount]:
r"""Returns a list representing the resources of the operator. Each object represents a quantum gate
and the number of times it occurs in the decomposition.
Args:
thc_ham (:class:`~pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian for which the walk operator is being created
coeff_precision (int | None): The number of bits used to represent the precision for loading
the coefficients of Hamiltonian. If :code:`None` is provided, the default value from the
:class:`~.pennylane.estimator.resource_config.ResourceConfig` is used.
select_swap_depth (int | None): A parameter of :class:`~.pennylane.estimator.templates.QROM`
used to trade-off extra qubits for reduced circuit depth. Defaults to :code:`None`, which internally determines the optimal depth.
Resources:
The resources are calculated based on Figures 3 and 4 in `arXiv:2011.03494 <https://arxiv.org/abs/2011.03494>`_
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_orb = thc_ham.num_orbitals
tensor_rank = thc_ham.tensor_rank
num_coeff = num_orb + tensor_rank * (tensor_rank + 1) / 2 # N+M(M+1)/2
coeff_register = int(math.ceil(math.log2(num_coeff)))
m_register = int(math.ceil(math.log2(tensor_rank + 1)))
gate_list = []
# 6 auxiliary account for 2 spin registers, 1 for rotation on auxiliary, 1 flag for success of inequality,
# 1 flag for one-body vs two-body and 1 to control swap of \mu and \nu registers.
gate_list.append(Allocate(coeff_register + 2 * m_register + 2 * coeff_precision + 6))
hadamard = resource_rep(qre.Hadamard)
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Figure - 3
# Inquality tests
toffoli = resource_rep(qre.Toffoli)
gate_list.append(qre.GateCount(toffoli, 4 * m_register - 4))
# Reflection on 5 registers
ccz = resource_rep(qre.CCZ)
gate_list.append(
qre.GateCount(
resource_rep(
qre.Controlled,
{"base_cmpr_op": ccz, "num_ctrl_wires": 1, "num_zero_ctrl": 0},
),
1,
)
)
gate_list.append(qre.GateCount(toffoli, 2))
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Rotate and invert the rotation of ancilla to obtain amplitude of success
gate_list.append(Allocate(coeff_precision))
gate_list.append(qre.GateCount(toffoli, 2 * (coeff_precision - 3)))
gate_list.append(Deallocate(coeff_precision))
# Reflecting about the success amplitude
gate_list.append(qre.GateCount(ccz, 2 * m_register - 1))
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Inequality tests
gate_list.append(qre.GateCount(toffoli, 4 * m_register - 4))
# Checking that inequality is satisfied
mcx = resource_rep(qre.MultiControlledX, {"num_ctrl_wires": 3, "num_zero_ctrl": 0})
gate_list.append(qre.GateCount(mcx, 1))
gate_list.append(qre.GateCount(toffoli, 2))
x = resource_rep(qre.X)
gate_list.append(qre.GateCount(x, 2))
# Figure- 4(Subprepare Circuit)
gate_list.append(qre.GateCount(hadamard, coeff_precision + 1))
# Contiguous register cost Eq.29 in arXiv:2011.03494
gate_list.append(qre.GateCount(toffoli, m_register**2 + m_register - 1))
# QROM for keep values Eq.31 in arXiv:2011.03494
qrom_coeff = resource_rep(
qre.QROM,
{
"num_bitstrings": num_coeff,
"size_bitstring": 2 * m_register + 2 + coeff_precision,
"restored": False,
"select_swap_depth": select_swap_depth,
},
)
gate_list.append(qre.GateCount(qrom_coeff, 1))
# Inequality test between alt and keep registers
comparator = resource_rep(
qre.RegisterComparator,
{
"first_register": coeff_precision,
"second_register": coeff_precision,
"geq": False,
},
)
gate_list.append(qre.GateCount(comparator))
cz = resource_rep(qre.CZ)
gate_list.append(qre.GateCount(cz, 2))
gate_list.append(qre.GateCount(x, 2))
# Swap \mu and \nu registers with alt registers
cswap = resource_rep(qre.CSWAP)
gate_list.append(qre.GateCount(cswap, 2 * m_register))
# Swap \mu and \nu registers controlled on |+> state and success of inequality
gate_list.append(qre.GateCount(cswap, m_register))
gate_list.append(qre.GateCount(toffoli, 1))
return gate_list
[docs]
@classmethod
def adjoint_resource_decomp(
cls,
target_resource_params: dict,
) -> list[GateCount]:
r"""Returns a list representing the resources of the adjoint of the operator. Each object represents a quantum gate
and the number of times it occurs in the decomposition.
Args:
thc_ham (:class:`~pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian for which the walk operator is being created
target_resource_params(dict): A dictionary containing the resource parameters of the target operator.
Resources:
The resources are calculated based on Figures 3 and 4 in `arXiv:2011.03494 <https://arxiv.org/abs/2011.03494>`_
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.
"""
thc_ham = target_resource_params["thc_ham"]
coeff_precision = target_resource_params["coeff_precision"]
select_swap_depth = target_resource_params.get("select_swap_depth", None)
num_orb = thc_ham.num_orbitals
tensor_rank = thc_ham.tensor_rank
num_coeff = num_orb + tensor_rank * (tensor_rank + 1) / 2
coeff_register = int(math.ceil(math.log2(num_coeff)))
m_register = int(math.ceil(math.log2(tensor_rank + 1)))
gate_list = []
hadamard = resource_rep(qre.Hadamard)
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Figure - 3
# Inquality tests from arXiv:2011.03494
toffoli = resource_rep(qre.Toffoli)
gate_list.append(qre.GateCount(toffoli, 4 * m_register - 4))
# Reflection on 5 registers
ccz = resource_rep(qre.CCZ)
gate_list.append(
qre.GateCount(
resource_rep(
qre.Controlled,
{"base_cmpr_op": ccz, "num_ctrl_wires": 1, "num_zero_ctrl": 0},
),
1,
)
)
gate_list.append(qre.GateCount(toffoli, 2))
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Rotate and invert the rotation of ancilla to obtain amplitude of success
gate_list.append(Allocate(coeff_precision))
gate_list.append(qre.GateCount(toffoli, 2 * (coeff_precision - 3)))
gate_list.append(Deallocate(coeff_precision))
# Reflecting about the success amplitude
gate_list.append(qre.GateCount(ccz, 2 * m_register - 1))
gate_list.append(qre.GateCount(hadamard, 2 * m_register))
# Inequality tests
gate_list.append(qre.GateCount(toffoli, 4 * m_register - 4))
# Checking that inequality is satisfied
mcx = resource_rep(qre.MultiControlledX, {"num_ctrl_wires": 3, "num_zero_ctrl": 0})
gate_list.append(qre.GateCount(mcx, 1))
gate_list.append(qre.GateCount(toffoli, 2))
x = resource_rep(qre.X)
gate_list.append(qre.GateCount(x, 2))
# Figure- 4 (Subprepare Circuit)
gate_list.append(qre.GateCount(hadamard, coeff_precision + 1))
# Contiguous register cost
gate_list.append(qre.GateCount(toffoli, m_register**2 + m_register - 1))
# Adjoint of QROM for keep values Eq.32 in arXiv:2011.03494
qrom_adj = resource_rep(
qre.Adjoint,
{
"base_cmpr_op": resource_rep(
qre.QROM,
{
"num_bitstrings": num_coeff,
"size_bitstring": 2 * m_register + 2 + coeff_precision,
"restored": False,
"select_swap_depth": select_swap_depth,
},
)
},
)
gate_list.append(qre.GateCount(qrom_adj, 1))
cz = resource_rep(qre.CZ)
gate_list.append(qre.GateCount(cz, 2))
gate_list.append(qre.GateCount(x, 2))
# Swap \mu and \nu registers with alt registers
cswap = resource_rep(qre.CSWAP)
gate_list.append(qre.GateCount(cswap, 2 * m_register))
# Swap \mu and \nu registers controlled on |+> state and success of inequality
gate_list.append(qre.GateCount(cswap, m_register))
gate_list.append(qre.GateCount(toffoli, 1))
# Free Prepare Wires
# 6 ancillas account for 2 spin registers, 1 for rotation on ancilla, 1 flag for success of inequality,
# 1 flag for one-body vs two-body and 1 to control swap of \mu and \nu registers.
gate_list.append(Deallocate(coeff_register + 2 * m_register + 2 * coeff_precision + 6))
return gate_list
_modules/pennylane/estimator/templates/stateprep
Download Python script
Download Notebook
View on GitHub