Source code for pennylane.estimator.templates.trotter
# 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 Suzuki-Trotter approximation based subroutines.
"""
import numpy as np
from pennylane.estimator.compact_hamiltonian import (
CDFHamiltonian,
PauliHamiltonian,
THCHamiltonian,
VibrationalHamiltonian,
VibronicHamiltonian,
)
from pennylane.estimator.ops.op_math.symbolic import Controlled, Prod
from pennylane.estimator.ops.qubit.non_parametric_ops import Hadamard, T, X
from pennylane.estimator.ops.qubit.parametric_ops_multi_qubit import MultiRZ, PauliRot
from pennylane.estimator.ops.qubit.parametric_ops_single_qubit import RZ
from pennylane.estimator.resource_operator import (
CompressedResourceOp,
GateCount,
ResourceOperator,
_dequeue,
resource_rep,
)
from pennylane.estimator.wires_manager import Allocate, Deallocate
from pennylane.wires import Wires, WiresLike
from .subroutines import (
BasisRotation,
OutMultiplier,
OutOfPlaceSquare,
PhaseGradient,
Select,
SemiAdder,
)
# pylint: disable=arguments-differ, too-many-arguments, super-init-not-called
[docs]
class TrotterProduct(ResourceOperator):
r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a Hamiltonian operator.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`: the product formula is constructed using
symmetrized products of the terms in the Hamiltonian. The symmetrized products of order
:math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^{\text{th}}` order,
:math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details, see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
first_order_expansion (list[~pennylane.estimator.ResourceOperator]): A list of operators
constituting the first order expansion of the Hamiltonian to be approximately exponentiated.
num_steps (int): number of Trotter steps to perform
order (int): order of the Suzuki-Trotter approximation; must be ``1`` or an even number
wires (list[int] | None): The wires on which the operator acts. If provided, these wire
labels will be used instead of the wires provided by the ResourceOperators in the
:code:`first_order_expansion`.
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math:: C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}
Furthermore, because of the symmetric form of the recursive formula, the first and last terms are grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
.. seealso:: The corresponding PennyLane operation :class:`~.TrotterProduct`
.. seealso::
:class:`~.estimator.templates.TrotterCDF`,
:class:`~.estimator.templates.TrotterTHC`,
:class:`~.estimator.templates.TrotterVibrational`,
:class:`~.estimator.templates.TrotterVibronic`
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> num_steps, order = (1, 2)
>>> first_order_expansion = [qre.RX(), qre.RY()] # H = X + Y
>>> gate_set = {"RX", "RY"}
>>> res = qre.estimate(qre.TrotterProduct(first_order_expansion, num_steps, order), gate_set=gate_set)
>>> print(res)
--- Resources: ---
Total wires: 1
algorithmic wires: 1
allocated wires: 0
zero state: 0
any state: 0
Total gates : 3
'RX': 2,
'RY': 1
"""
resource_keys = {"first_order_expansion", "num_steps", "order", "num_wires"}
def __init__(
self,
first_order_expansion: list,
num_steps: int,
order: int,
wires: WiresLike | None = None,
):
_dequeue(op_to_remove=first_order_expansion)
self.queue()
try:
cmpr_ops = tuple(op.resource_rep_from_op() for op in first_order_expansion)
except AttributeError as error:
raise ValueError(
"All components of first_order_expansion must be instances of `ResourceOperator` in order to obtain resources."
) from error
self.first_order_expansion = cmpr_ops
self.num_steps = num_steps
self.order = order
if wires is not None: # User defined wires take precedent
self.wires = Wires(wires)
self.num_wires = len(self.wires)
else: # Otherwise determine the wires from the ops in the first order expansion
ops_wires = Wires.all_wires(
[op.wires for op in first_order_expansion if op.wires is not None]
)
fewest_unique_wires = max(op.num_wires for op in cmpr_ops)
if len(ops_wires) < fewest_unique_wires: # If the expansion didn't provide enough wire
self.wires = None # labels we assume they all act on the same set
self.num_wires = fewest_unique_wires
else: # If there are more wire labels, use that as the operator wires
self.wires = ops_wires
self.num_wires = len(self.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:
* first_order_expansion (list[:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`]): A list of operators,
in the compressed representation, constituting the first order expansion of the Hamiltonian to be approximately exponentiated.
* num_steps (int): number of Trotter steps to perform
* order (int): order of the Suzuki-Trotter approximation, must be 1 or even
* num_wires (int): number of wires the operator acts on
"""
return {
"first_order_expansion": self.first_order_expansion,
"num_steps": self.num_steps,
"order": self.order,
"num_wires": self.num_wires,
}
[docs]
@classmethod
def resource_rep(
cls,
first_order_expansion: list,
num_steps: int,
order: int,
num_wires: int,
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute a resource estimation.
Args:
first_order_expansion (list[:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`]): A list of operators,
in the compressed representation, constituting
the first order expansion of the Hamiltonian to be approximately exponentiated.
num_steps (int): number of Trotter steps to perform
order (int): order of the Suzuki-Trotter approximation, must be 1 or even
num_wires (int): number of wires the operator acts on
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
params = {
"first_order_expansion": first_order_expansion,
"num_steps": num_steps,
"order": order,
"num_wires": num_wires,
}
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
first_order_expansion: list,
num_steps: int,
order: int,
num_wires: int, # pylint: disable=unused-argument
) -> 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:
first_order_expansion (list[:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`]): A list of operators,
in the compressed representation, constituting
the first order expansion of the Hamiltonian to be approximately exponentiated.
num_steps (int): number of Trotter steps to perform
order (int): order of the Suzuki-Trotter approximation, must be 1 or even
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.
"""
k = order // 2
gate_list = []
if order == 1:
for op in first_order_expansion:
gate_list.append(GateCount(op, num_steps))
return gate_list
# For first and last fragment
first_frag = first_order_expansion[0]
last_frag = first_order_expansion[-1]
gate_list.append(GateCount(first_frag, num_steps * (5 ** (k - 1)) + 1))
gate_list.append(GateCount(last_frag, num_steps * (5 ** (k - 1))))
# For rest of the fragments
for op in first_order_expansion[1:-1]:
gate_list.append(GateCount(op, 2 * num_steps * (5 ** (k - 1))))
return gate_list
[docs]
class TrotterCDF(ResourceOperator):
r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a compressed double-factorized (CDF) Hamiltonian.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`: the product formula is constructed using
symmetrized products of the terms in the Hamiltonian. The symmetrized products of order
:math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^{\text{th}}`
order, :math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math::
e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
cdf_ham (:class:`~.pennylane.estimator.compact_hamiltonian.CDFHamiltonian`):
a compressed double factorized Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be ``1`` or an even number
wires (list[int] | None): the wires on which the operator acts
Raises:
TypeError: if ``cdf_ham`` is not an instance of :class:`~.CDFHamiltonian`
ValueError: if ``num_steps`` is not a positive integer
ValueError: if ``order`` is not 1 or a positive even integer
ValueError: if the number of wires provided does not match the number of wires required by the operator
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of compressed double factorized Hamiltonian are
calculated based on `arXiv:2506.15784 <https://arxiv.org/abs/2506.15784>`_.
.. seealso::
:class:`~.estimator.compact_hamiltonian.CDFHamiltonian`
.. seealso:: :class:`~.TrotterProduct`
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> num_steps, order = (1, 2)
>>> cdf_ham = qre.CDFHamiltonian(num_orbitals = 4, num_fragments = 4)
>>> res = qre.estimate(qre.TrotterCDF(cdf_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 8
algorithmic wires: 8
allocated wires: 0
zero state: 0
any state: 0
Total gates : 2.238E+4
'T': 2.075E+4,
'CNOT': 448,
'Z': 336,
'S': 504,
'Hadamard': 336
"""
resource_keys = {"cdf_ham", "num_steps", "order"}
def __init__(
self,
cdf_ham: CDFHamiltonian,
num_steps: int,
order: int,
wires: WiresLike | None = None,
):
if not isinstance(cdf_ham, CDFHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterCDF."
f"This method works with CDFHamiltonian, {type(cdf_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
self.num_steps = num_steps
self.order = order
self.cdf_ham = cdf_ham
self.num_wires = 2 * cdf_ham.num_orbitals
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:
* cdf_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.CDFHamiltonian`): a compressed double factorized
Hamiltonian to be approximately exponentiated
* num_steps (int): number of Trotter steps to perform
* order (int): order of the approximation, must be 1 or even.
"""
return {
"cdf_ham": self.cdf_ham,
"num_steps": self.num_steps,
"order": self.order,
}
[docs]
@classmethod
def resource_rep(
cls, cdf_ham: CDFHamiltonian, num_steps: int, order: int
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute a resource estimation.
Args:
cdf_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.CDFHamiltonian`):
a compressed double factorized Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(cdf_ham, CDFHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterCDF."
f"This method works with CDFHamiltonian, {type(cdf_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
params = {
"cdf_ham": cdf_ham,
"num_steps": num_steps,
"order": order,
}
num_wires = 2 * cdf_ham.num_orbitals
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls, cdf_ham: CDFHamiltonian, num_steps: int, order: int
) -> 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:
cdf_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.CDFHamiltonian`): a compressed double factorized
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even.
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator, :math:`e^{itO_{j}}`, is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of compressed double factorized Hamiltonian are
calculated based on `arXiv:2506.15784 <https://arxiv.org/abs/2506.15784>`_.
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.
"""
k = order // 2
gate_list = []
num_orb = cdf_ham.num_orbitals
num_frags = cdf_ham.num_fragments
op_onebody = resource_rep(
Prod,
{"cmpr_factors_and_counts": ((RZ.resource_rep(), 2 * num_orb),)},
)
op_twobody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": (
(MultiRZ.resource_rep(num_wires=2), (2 * num_orb - 1) * num_orb),
)
},
)
basis_rot = resource_rep(BasisRotation, {"dim": num_orb})
if order == 1:
gate_list.append(GateCount(basis_rot, 2 * num_frags * num_steps))
gate_list.append(GateCount(op_onebody, num_steps))
gate_list.append(GateCount(op_twobody, (num_frags - 1) * num_steps))
return gate_list
# For first and last fragment
gate_list.append(GateCount(basis_rot, 4 * num_steps * (5 ** (k - 1)) + 2))
gate_list.append(GateCount(op_onebody, num_steps * (5 ** (k - 1)) + 1))
gate_list.append(GateCount(op_twobody, num_steps * (5 ** (k - 1))))
# For rest of the fragments
gate_list.append(GateCount(basis_rot, 4 * num_steps * (num_frags - 2) * (5 ** (k - 1))))
gate_list.append(GateCount(op_twobody, 2 * num_steps * (num_frags - 2) * (5 ** (k - 1))))
return gate_list
[docs]
@classmethod
def controlled_resource_decomp(
cls, num_ctrl_wires: int, num_zero_ctrl: int, target_resource_params: dict | None = None
):
r"""Returns the controlled resource decomposition.
Args:
num_ctrl_wires (int): the number of qubits the operation is controlled on
num_zero_ctrl (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
target_resource_params (dict): dictionary containing the size of the larger of the two registers being added together
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.
Resources:
The original resources are controlled only on the Z rotation gates.
"""
cdf_ham = target_resource_params["cdf_ham"]
num_steps = target_resource_params["num_steps"]
order = target_resource_params["order"]
k = order // 2
gate_list = []
num_orb = cdf_ham.num_orbitals
num_frags = cdf_ham.num_fragments
op_onebody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": [
(
resource_rep(
Controlled,
{
"base_cmpr_op": RZ.resource_rep(),
"num_ctrl_wires": num_ctrl_wires,
"num_zero_ctrl": num_zero_ctrl,
},
),
(2 * num_orb),
)
]
},
)
op_twobody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": [
(
resource_rep(
Controlled,
{
"base_cmpr_op": MultiRZ.resource_rep(num_wires=2),
"num_ctrl_wires": num_ctrl_wires,
"num_zero_ctrl": num_zero_ctrl,
},
),
(2 * num_orb - 1) * num_orb,
)
]
},
)
basis_rot = resource_rep(BasisRotation, {"dim": num_orb})
if order == 1:
gate_list.append(GateCount(basis_rot, 2 * num_frags * num_steps))
gate_list.append(GateCount(op_onebody, num_steps))
gate_list.append(GateCount(op_twobody, (num_frags - 1) * num_steps))
return gate_list
# For first and last fragment
gate_list.append(GateCount(basis_rot, 4 * num_steps * (5 ** (k - 1)) + 2))
gate_list.append(GateCount(op_onebody, num_steps * (5 ** (k - 1)) + 1))
gate_list.append(GateCount(op_twobody, num_steps * (5 ** (k - 1))))
# For rest of the fragments
gate_list.append(GateCount(basis_rot, 4 * num_steps * (num_frags - 2) * (5 ** (k - 1))))
gate_list.append(GateCount(op_twobody, 2 * num_steps * (num_frags - 2) * (5 ** (k - 1))))
return gate_list
[docs]
class TrotterTHC(ResourceOperator):
r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a tensor hypercontracted (THC) Hamiltonian.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`: the product formula is constructed using
symmetrized products of the terms in the Hamiltonian. The symmetrized products of order
:math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^{\text{th}}`
order, :math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math::
e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
thc_ham (:class:`~.pennylane.estimator.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be ``1`` or an even number
wires (list[int] | None): the wires on which the operator acts
Raises:
TypeError: if ``thc_ham`` is not an instance of :class:`~.THCHamiltonian`
ValueError: if ``num_steps`` is not a positive integer
ValueError: if ``order`` is not 1 or a positive even integer
ValueError: if the number of wires provided does not match the number of expected wires for the operation
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last
terms get grouped. This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of tensor hypercontracted Hamiltonian are
calculated based on `arXiv:2407.04432 <https://arxiv.org/abs/2407.04432>`_.
.. seealso::
:class:`~.estimator.compact_hamiltonian.THCHamiltonian`
.. seealso:: :class:`~.TrotterProduct`
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> num_steps, order = (1, 2)
>>> thc_ham = qre.THCHamiltonian(num_orbitals=4, tensor_rank=4)
>>> res = qre.estimate(qre.TrotterTHC(thc_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 8
algorithmic wires: 8
allocated wires: 0
zero state: 0
any state: 0
Total gates : 8.520E+3
'T': 7.888E+3,
'CNOT': 128,
'Z': 144,
'S': 216,
'Hadamard': 144
"""
resource_keys = {"thc_ham", "num_steps", "order"}
def __init__(
self,
thc_ham: THCHamiltonian,
num_steps: int,
order: int,
wires: WiresLike | None = None,
):
if not isinstance(thc_ham, THCHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterTHC."
f"This method works with THCHamiltonian, {type(thc_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
self.num_steps = num_steps
self.order = order
self.thc_ham = thc_ham
self.num_wires = thc_ham.tensor_rank * 2
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.templates.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian to be approximately exponentiated
* num_steps (int): number of Trotter steps to perform
* order (int): order of the approximation, must be 1 or even
"""
return {
"thc_ham": self.thc_ham,
"num_steps": self.num_steps,
"order": self.order,
}
[docs]
@classmethod
def resource_rep(
cls, thc_ham: THCHamiltonian, num_steps: int, order: int
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
thc_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
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 TrotterTHC."
f"This method works with THCHamiltonian, {type(thc_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
params = {
"thc_ham": thc_ham,
"num_steps": num_steps,
"order": order,
}
num_wires = thc_ham.tensor_rank * 2
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls, thc_ham: THCHamiltonian, num_steps: int, order: int
) -> 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.templates.compact_hamiltonian.THCHamiltonian`): a tensor hypercontracted
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator, :math:`e^{itO_{j}}`, is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last
terms get grouped. This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of tensor hypercontracted Hamiltonian are
calculated based on `arXiv:2407.04432 <https://arxiv.org/abs/2407.04432>`_.
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.
"""
k = order // 2
gate_list = []
num_orb = thc_ham.num_orbitals
tensor_rank = thc_ham.tensor_rank
op_onebody = resource_rep(
Prod,
{"cmpr_factors_and_counts": ((RZ.resource_rep(), 2 * num_orb),)},
)
op_twobody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": (
(
MultiRZ.resource_rep(num_wires=2),
(2 * tensor_rank - 1) * tensor_rank,
),
)
},
)
basis_rot_onebody = resource_rep(BasisRotation, {"dim": num_orb})
basis_rot_twobody = resource_rep(BasisRotation, {"dim": tensor_rank})
if order == 1:
gate_list.append(GateCount(basis_rot_onebody, 2 * num_steps))
gate_list.append(GateCount(basis_rot_twobody, 2 * num_steps))
gate_list.append(GateCount(op_onebody, num_steps))
gate_list.append(GateCount(op_twobody, num_steps))
return gate_list
# For one-body tensor
gate_list.append(GateCount(basis_rot_onebody, 2 * num_steps * (5 ** (k - 1)) + 2))
gate_list.append(GateCount(op_onebody, num_steps * (5 ** (k - 1)) + 1))
# For two-body tensor
gate_list.append(GateCount(basis_rot_twobody, 2 * num_steps * (5 ** (k - 1))))
gate_list.append(GateCount(op_twobody, num_steps * (5 ** (k - 1))))
return gate_list
[docs]
@classmethod
def controlled_resource_decomp(
cls, num_ctrl_wires: int, num_zero_ctrl: int, target_resource_params: dict | None = None
):
r"""Returns the controlled resource decomposition.
Args:
num_ctrl_wires (int): the number of qubits the operation is controlled on
num_zero_ctrl (int): the number of control qubits, that are controlled when in the :math:`|0\rangle` state
target_resource_params (dict): dictionary containing the size of the larger of the two registers being added together
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.
Resources:
The original resources are controlled only on the Z rotation gates
"""
thc_ham = target_resource_params["thc_ham"]
num_steps = target_resource_params["num_steps"]
order = target_resource_params["order"]
k = order // 2
gate_list = []
num_orb = thc_ham.num_orbitals
tensor_rank = thc_ham.tensor_rank
op_onebody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": [
(
resource_rep(
Controlled,
{
"base_cmpr_op": RZ.resource_rep(),
"num_ctrl_wires": num_ctrl_wires,
"num_zero_ctrl": num_zero_ctrl,
},
),
(2 * num_orb),
)
]
},
)
op_twobody = resource_rep(
Prod,
{
"cmpr_factors_and_counts": [
(
resource_rep(
Controlled,
{
"base_cmpr_op": MultiRZ.resource_rep(num_wires=2),
"num_ctrl_wires": num_ctrl_wires,
"num_zero_ctrl": num_zero_ctrl,
},
),
(2 * tensor_rank - 1) * tensor_rank,
)
]
},
)
basis_rot_onebody = resource_rep(BasisRotation, {"dim": num_orb})
basis_rot_twobody = resource_rep(BasisRotation, {"dim": tensor_rank})
if order == 1:
gate_list.append(GateCount(basis_rot_onebody, 2 * num_steps))
gate_list.append(GateCount(basis_rot_twobody, 2 * num_steps))
gate_list.append(GateCount(op_onebody, num_steps))
gate_list.append(GateCount(op_twobody, num_steps))
return gate_list
# For one-body tensor
gate_list.append(GateCount(basis_rot_onebody, 2 * num_steps * (5 ** (k - 1)) + 2))
gate_list.append(GateCount(op_onebody, num_steps * (5 ** (k - 1)) + 1))
# For two-body tensor
gate_list.append(GateCount(basis_rot_twobody, 2 * num_steps * (5 ** (k - 1))))
gate_list.append(GateCount(op_twobody, num_steps * (5 ** (k - 1))))
return gate_list
[docs]
class TrotterVibrational(ResourceOperator):
r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a vibrational Hamiltonian.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`: the product formula is constructed using
symmetrized products of the terms in the Hamiltonian. The symmetrized products of order
:math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^\text{th}`
order, :math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math::
e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
vibration_ham (:class:`~.pennylane.estimator.compact_hamiltonian.VibrationalHamiltonian`): a real space vibrational
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be ``1`` or an even number
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
wires (list[int] | None): the wires on which the operator acts
Raises:
TypeError: if ``vibration_ham`` is not an instance of :class:`~.VibrationalHamiltonian`
ValueError: if ``num_steps`` is not a positive integer
ValueError: if ``order`` is not 1 or a positive even integer
ValueError: if the number of wires provided does not match the number of wires expected for the operation
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of vibrational Hamiltonian are calculated based on
`arXiv:2504.10602 <https://arxiv.org/pdf/2504.10602>`_.
.. seealso::
:class:`~.estimator.compact_hamiltonian.VibrationalHamiltonian`
.. seealso:: :class:`~.TrotterProduct`
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> num_steps, order = (10, 2)
>>> vibration_ham = qre.VibrationalHamiltonian(num_modes=2, grid_size=4, taylor_degree=2)
>>> res = qre.estimate(qre.TrotterVibrational(vibration_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 83
algorithmic wires: 8
allocated wires: 75
zero state: 75
any state: 0
Total gates : 1.239E+5
'Toffoli': 2.248E+4,
'T': 749,
'CNOT': 3.520E+4,
'X': 1.216E+3,
'Z': 1,
'S': 1,
'Hadamard': 6.422E+4
"""
resource_keys = {
"vibration_ham",
"num_steps",
"order",
"phase_grad_precision",
"coeff_precision",
}
def __init__(
self,
vibration_ham: VibrationalHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None = None,
coeff_precision: float | None = None,
wires: WiresLike | None = None,
):
if not isinstance(vibration_ham, VibrationalHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterVibrational."
f"This method works with VibrationalHamiltonian, {type(vibration_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
self.num_steps = num_steps
self.order = order
self.vibration_ham = vibration_ham
self.phase_grad_precision = phase_grad_precision
self.coeff_precision = coeff_precision
self.num_wires = vibration_ham.num_modes * vibration_ham.grid_size
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:
* vibration_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibrationalHamiltonian`): a real space vibrational
Hamiltonian to be approximately exponentiated.
* num_steps (int): number of Trotter steps to perform
* order (int): order of the approximation, must be 1 or even
* phase_grad_precision (float): precision for the phase gradient calculation,
* coeff_precision (float): precision for the loading of coefficients,
"""
return {
"vibration_ham": self.vibration_ham,
"num_steps": self.num_steps,
"order": self.order,
"phase_grad_precision": self.phase_grad_precision,
"coeff_precision": self.coeff_precision,
}
[docs]
@classmethod
def resource_rep(
cls,
vibration_ham: VibrationalHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None = None,
coeff_precision: float | None = None,
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
vibration_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibrationalHamiltonian`): a real space vibrational
Hamiltonian to be approximately exponentiated.
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(vibration_ham, VibrationalHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterVibrational."
f"This method works with VibrationalHamiltonian, {type(vibration_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
params = {
"vibration_ham": vibration_ham,
"num_steps": num_steps,
"order": order,
"phase_grad_precision": phase_grad_precision,
"coeff_precision": coeff_precision,
}
num_wires = vibration_ham.num_modes * vibration_ham.grid_size
return CompressedResourceOp(cls, num_wires, params)
@staticmethod
def _cached_terms(grid_size, taylor_degree, coeff_precision, cached_tree, path, index):
r"""Recursive function to compute the resources for the trotterization of vibrational Hamiltonian
while caching the coefficients."""
cur_path, len_path = tuple(path), len(path)
coeff_wires = int(abs(np.floor(np.log2(coeff_precision))))
gate_cache = []
x = X.resource_rep()
if 1 < len_path <= taylor_degree and cur_path not in cached_tree[len_path]:
if len(cached_tree[len_path]):
prev_state = cached_tree[len_path][-1]
if len_path == 2 and prev_state[0] == prev_state[1]:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size)
gate_cache.append(GateCount(out_square, 1))
elif len_path == 4 and len(set(prev_state)) == 1:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size * 2)
gate_cache.append(GateCount(out_square, 1))
else:
multiplier = OutMultiplier.resource_rep(grid_size, grid_size * (len_path - 1))
gate_cache.append(GateCount(multiplier, 1))
# Add the Square / Multiplier for current state
if len_path == 2 and cur_path[-1] == cur_path[-2]:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size)
gate_cache.append(GateCount(out_square, 1))
elif len_path == 4 and len(set(cur_path)) == 1:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size * 2)
gate_cache.append(GateCount(out_square, 1))
else:
multiplier = OutMultiplier.resource_rep(grid_size, grid_size * (len_path - 1))
gate_cache.append(GateCount(multiplier, 1))
# Add the coefficient Initializer for current state
# assuming that half the bits in the coefficient are 1
gate_cache.append(GateCount(x, int(coeff_wires / 2)))
# Add the Multiplier for current coefficient
multiplier = OutMultiplier.resource_rep(grid_size * len_path, coeff_wires)
gate_cache.append(GateCount(multiplier, 1))
# Add the Adder for Resource state
adder = SemiAdder.resource_rep(max_register_size=2 * max(coeff_wires, 2 * grid_size))
gate_cache.append(GateCount(adder, 1))
# Adjoint the Multiplier for current coefficient
multiplier = OutMultiplier.resource_rep(grid_size * len_path, coeff_wires)
gate_cache.append(GateCount(multiplier, 1))
# Adjoint the coefficient Initializer for current state
# assuming that half the bits in the coefficient are 1
gate_cache.append(GateCount(x, coeff_wires / 2))
cached_tree[len_path].append(cur_path)
if len_path < taylor_degree and index + 1:
gate_cache_curr, cached_tree = TrotterVibrational._cached_terms(
grid_size, taylor_degree, coeff_precision, cached_tree, path + [index], index
) # Depth first search traversal with current element
gate_cache += gate_cache_curr
gate_cache_next, cached_tree = TrotterVibrational._cached_terms(
grid_size, taylor_degree, coeff_precision, cached_tree, path, index - 1
) # Depth first search traversal with next element
gate_cache += gate_cache_next
return gate_cache, cached_tree
@staticmethod
def _rep_circuit(vibration_ham: VibrationalHamiltonian, coeff_precision, num_rep):
r"""Returns the expansion of the circuit with given number of repetitions."""
num_modes = vibration_ham.num_modes
grid_size = vibration_ham.grid_size
taylor_degree = vibration_ham.taylor_degree
gate_lst = []
# Shifted QFT for kinetic part
t = T.resource_rep()
gate_lst.append(GateCount(t, num_rep * (num_modes * int(np.ceil(np.log2(num_modes) - 1)))))
kinetic_deg = 2
cached_tree = {index: [] for index in range(1, kinetic_deg + 1)}
gate_cache, cached_tree = TrotterVibrational._cached_terms(
grid_size, kinetic_deg, coeff_precision, cached_tree, path=[], index=num_modes - 1
)
gate_lst += gate_cache * num_rep
cached_tree = {index: [] for index in range(1, taylor_degree + 1)}
gate_cache, cached_tree = TrotterVibrational._cached_terms(
grid_size, taylor_degree, coeff_precision, cached_tree, path=[], index=num_modes - 1
)
gate_lst += gate_cache * num_rep
# Adjoints for the last Squares / Multipliers
for idx in range(2, taylor_degree):
last_state = cached_tree[idx][-1]
if idx == 2 and last_state[-1] == last_state[-2]:
gate_lst.append(
GateCount(OutOfPlaceSquare.resource_rep(register_size=grid_size), num_rep)
)
elif idx == 4 and len(set(last_state)) == 1:
gate_lst.append(
GateCount(
OutOfPlaceSquare.resource_rep(register_size=grid_size * 2),
num_rep,
)
)
else:
gate_lst.append(
GateCount(
OutMultiplier.resource_rep(grid_size, grid_size * (idx - 1)),
num_rep,
)
)
# Shifted QFT Adjoint
gate_lst.append(GateCount(t, num_rep * (num_modes * int(np.ceil(np.log2(num_modes) - 1)))))
return gate_lst
[docs]
@classmethod
def resource_decomp(
cls,
vibration_ham: VibrationalHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None = None,
coeff_precision: float | 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:
vibration_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibrationalHamiltonian`): a real space vibrational
Hamiltonian to be approximately exponentiated.
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator, :math:`e^{itO_{j}}`, is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of vibrational Hamiltonian are calculated based on
`arXiv:2504.10602 <https://arxiv.org/pdf/2504.10602>`_.
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.
"""
k = order // 2
gate_list = []
num_modes = vibration_ham.num_modes
grid_size = vibration_ham.grid_size
taylor_degree = vibration_ham.taylor_degree
phase_grad_wires = int(abs(np.floor(np.log2(phase_grad_precision))))
coeff_wires = int(abs(np.floor(np.log2(coeff_precision))))
x = X.resource_rep()
phase_grad = PhaseGradient.resource_rep(phase_grad_wires)
# Allocate the phase gradient registers
gate_list.append(Allocate(phase_grad_wires * (taylor_degree - 1)))
# Resource Registers
gate_list.append(GateCount(phase_grad, taylor_degree - 1))
# Allocate auxiliary registers for the coefficients
gate_list.append(Allocate(4 * grid_size + 2 * coeff_wires))
# Basis state prep per mode, implemented only for the first step
gate_list.append(GateCount(x, num_modes * grid_size))
if order == 1:
gate_list += TrotterVibrational._rep_circuit(vibration_ham, coeff_precision, num_steps)
else:
gate_list += TrotterVibrational._rep_circuit(
vibration_ham, coeff_precision, 2 * num_steps * (5 ** (k - 1))
)
# Adjoint of Basis state prep, implemented only for the last step
gate_list.append(GateCount(x, num_modes * grid_size))
# Free auxiliary registers for the coefficients
gate_list.append(Deallocate(4 * grid_size + 2 * coeff_wires))
# Deallocate the phase gradient registers
gate_list.append(Deallocate(phase_grad_wires * (taylor_degree - 1)))
return gate_list
[docs]
class TrotterVibronic(ResourceOperator):
r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a real-space vibronic Hamiltonian.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`: the product formula is constructed using
symmetrized products of the terms in the Hamiltonian. The symmetrized products of order
:math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^{\text{th}}`
order, :math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math::
e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
vibronic_ham (:class:`~.pennylane.estimator.compact_hamiltonian.VibronicHamiltonian`): a real-space vibronic
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be ``1`` or an even number
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
wires (list[int] | None): the wires on which the operator acts.
Raises:
TypeError: if ``vibronic_ham`` is not an instance of :class:`~.VibronicHamiltonian`
ValueError: if ``num_steps`` is not a positive integer
ValueError: if ``order`` is not 1 or a positive even integer
ValueError: if the number of wires provided does not match the number of wires expected by the operator
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of real-space vibronic Hamiltonian are calculated
based on `arXiv:2411.13669 <https://arxiv.org/abs/2411.13669>`_.
.. seealso::
:class:`~.estimator.compact_hamiltonian.VibronicHamiltonian`
.. seealso:: :class:`~.TrotterProduct`
**Example**
The resources for this operation are computed using:
>>> import pennylane.estimator as qre
>>> num_steps, order = (10, 2)
>>> vibronic_ham = qre.VibronicHamiltonian(num_modes=2, num_states=4, grid_size=4, taylor_degree=2)
>>> res = qre.estimate(qre.TrotterVibronic(vibronic_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 85
algorithmic wires: 10
allocated wires: 75
zero state: 75
any state: 0
Total gates : 1.332E+5
'Toffoli': 2.320E+4,
'T': 749,
'CNOT': 4.144E+4,
'X': 1.456E+3,
'Z': 1,
'S': 1,
'Hadamard': 6.638E+4
"""
resource_keys = {
"vibronic_ham",
"num_steps",
"order",
"phase_grad_precision",
"coeff_precision",
}
def __init__(
self,
vibronic_ham: VibronicHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None = None,
coeff_precision: float | None = None,
wires: WiresLike | None = None,
):
if not isinstance(vibronic_ham, VibronicHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterVibronic."
f"This method works with VibronicHamiltonian, {type(vibronic_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
self.num_steps = num_steps
self.order = order
self.vibronic_ham = vibronic_ham
self.phase_grad_precision = phase_grad_precision
self.coeff_precision = coeff_precision
self.num_wires = (
int(np.ceil(np.log2(vibronic_ham.num_states)))
+ vibronic_ham.num_modes * vibronic_ham.grid_size
)
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:
* vibronic_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibronicHamiltonian`): a real-space vibronic
Hamiltonian to be approximately exponentiated
* num_steps (int): number of Trotter steps to perform
* order (int): order of the approximation, must be 1 or even
* phase_grad_precision (float): precision for the phase gradient calculation
* coeff_precision (float): precision for the loading of coefficients
"""
return {
"vibronic_ham": self.vibronic_ham,
"num_steps": self.num_steps,
"order": self.order,
"phase_grad_precision": self.phase_grad_precision,
"coeff_precision": self.coeff_precision,
}
[docs]
@classmethod
def resource_rep(
cls,
vibronic_ham: VibronicHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None = None,
coeff_precision: float | None = None,
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute a resource estimation.
Args:
vibronic_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibronicHamiltonian`): a real space vibronic
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(vibronic_ham, VibronicHamiltonian):
raise TypeError(
f"Unsupported Hamiltonian representation for TrotterVibronic."
f"This method works with VibronicHamiltonian, {type(vibronic_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
params = {
"vibronic_ham": vibronic_ham,
"num_steps": num_steps,
"order": order,
"phase_grad_precision": phase_grad_precision,
"coeff_precision": coeff_precision,
}
num_wires = (
int(np.ceil(np.log2(vibronic_ham.num_states)))
+ vibronic_ham.num_modes * vibronic_ham.grid_size
)
return CompressedResourceOp(cls, num_wires, params)
@staticmethod
def _cached_terms(
num_states, grid_size, taylor_degree, coeff_precision, cached_tree, path, index
):
r"""Recursive function to compute the resources for the trotterization of vibronic Hamiltonian
while caching the coefficients."""
cur_path, len_path = tuple(path), len(path)
coeff_wires = int(abs(int(np.floor(np.log2(coeff_precision)))))
gate_cache = []
if 1 < len_path <= taylor_degree and cur_path not in cached_tree[len_path]:
if len(cached_tree[len_path]):
prev_state = cached_tree[len_path][-1]
if len_path == 2 and prev_state[0] == prev_state[1]:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size)
gate_cache.append(GateCount(out_square, 1))
elif len_path == 4 and len(set(prev_state)) == 1:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size * 2)
gate_cache.append(GateCount(out_square, 1))
else:
multiplier = OutMultiplier.resource_rep(grid_size, grid_size * (len_path - 1))
gate_cache.append(GateCount(multiplier, 1))
# Add the Square / Multiplier for current state
if len_path == 2 and cur_path[-1] == cur_path[-2]:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size)
gate_cache.append(GateCount(out_square, 1))
elif len_path == 4 and len(set(cur_path)) == 1:
out_square = OutOfPlaceSquare.resource_rep(register_size=grid_size * 2)
gate_cache.append(GateCount(out_square, 1))
else:
multiplier = OutMultiplier.resource_rep(grid_size, grid_size * (len_path - 1))
gate_cache.append(GateCount(multiplier, 1))
# Add the coefficient Initializer for current state
# assuming that half the bits in the coefficient are 1
coeff_unitaries = (
resource_rep(
Prod,
{"cmpr_factors_and_counts": ((X.resource_rep(), int(coeff_wires / 2)),)},
),
) * num_states
select_op = resource_rep(Select, {"cmpr_ops": coeff_unitaries})
gate_cache.append(GateCount(select_op, 1))
# Add the Multiplier for current coefficient
multiplier = OutMultiplier.resource_rep(grid_size * len_path, coeff_wires)
gate_cache.append(GateCount(multiplier, 1))
# Add the Adder for Resource state
adder = SemiAdder.resource_rep(max_register_size=2 * max(coeff_wires, 2 * grid_size))
gate_cache.append(GateCount(adder, 1))
# Adjoint the Multiplier for current coefficient
multiplier = OutMultiplier.resource_rep(grid_size * len_path, coeff_wires)
gate_cache.append(GateCount(multiplier, 1))
# Adjoint the coefficient Initializer for current state
# assuming that half the bits in the coefficient are 1
gate_cache.append(GateCount(select_op, 1))
cached_tree[len_path].append(cur_path)
if len_path < taylor_degree and index + 1:
gate_cache_curr, cached_tree = TrotterVibronic._cached_terms(
num_states,
grid_size,
taylor_degree,
coeff_precision,
cached_tree,
path + [index],
index,
) # DFS with current element
gate_cache += gate_cache_curr
gate_cache_next, cached_tree = TrotterVibronic._cached_terms(
num_states, grid_size, taylor_degree, coeff_precision, cached_tree, path, index - 1
) # DFS with next element
gate_cache += gate_cache_next
return gate_cache, cached_tree
@staticmethod
def _rep_circuit(vibronic_ham: VibronicHamiltonian, coeff_precision, num_rep):
r"""Returns the expansion of the circuit with given number of repetitions."""
num_modes = vibronic_ham.num_modes
num_states = vibronic_ham.num_states
grid_size = vibronic_ham.grid_size
taylor_degree = vibronic_ham.taylor_degree
gate_lst = []
# Shifted QFT for kinetic part
t = T.resource_rep()
gate_lst.append(GateCount(t, num_rep * (num_modes * int(np.ceil(np.log2(num_modes) - 1)))))
kinetic_deg = 2
cached_tree = {index: [] for index in range(1, kinetic_deg + 1)}
gate_cache, cached_tree = TrotterVibronic._cached_terms(
num_states,
grid_size,
kinetic_deg,
coeff_precision,
cached_tree,
path=[],
index=num_modes - 1,
)
gate_lst += gate_cache * num_rep
cached_tree = {index: [] for index in range(1, taylor_degree + 1)}
gate_cache, cached_tree = TrotterVibronic._cached_terms(
num_states,
grid_size,
taylor_degree,
coeff_precision,
cached_tree,
path=[],
index=num_modes - 1,
)
gate_lst += gate_cache * num_rep
# Adjoints for the last Squares / Multipliers
for idx in range(2, taylor_degree):
last_state = cached_tree[idx][-1]
if idx == 2 and last_state[-1] == last_state[-2]:
gate_lst.append(
GateCount(OutOfPlaceSquare.resource_rep(register_size=grid_size), num_rep)
)
elif idx == 4 and len(set(last_state)) == 1:
gate_lst.append(
GateCount(
OutOfPlaceSquare.resource_rep(register_size=grid_size * 2),
num_rep,
)
)
else:
gate_lst.append(
GateCount(
OutMultiplier.resource_rep(grid_size, grid_size * (idx - 1)),
num_rep,
)
)
# Shifted QFT Adjoint
gate_lst.append(GateCount(t, num_rep * (num_modes * int(np.ceil(np.log2(num_modes) - 1)))))
return gate_lst
[docs]
@classmethod
def resource_decomp(
cls,
vibronic_ham: VibronicHamiltonian,
num_steps: int,
order: int,
phase_grad_precision: float | None,
coeff_precision: float | 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:
vibronic_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.VibronicHamiltonian`): a real space vibronic
Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even
phase_grad_precision (float | None): precision for the phase gradient calculation
coeff_precision (float | None): precision for the loading of coefficients
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator, :math:`e^{itO_{j}}`, is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) and is given by:
.. math::
C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}.
Furthermore, because of the symmetric form of the recursive formula, the first and last terms get grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
The resources for a single step expansion of real-space vibronic Hamiltonian are calculated
based on `arXiv:2411.13669 <https://arxiv.org/abs/2411.13669>`_.
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.
"""
k = order // 2
gate_list = []
num_modes = vibronic_ham.num_modes
num_states = vibronic_ham.num_states
grid_size = vibronic_ham.grid_size
taylor_degree = vibronic_ham.taylor_degree
phase_grad_wires = int(abs(np.floor(np.log2(phase_grad_precision))))
coeff_wires = int(abs(np.floor(np.log2(coeff_precision))))
x = X.resource_rep()
phase_grad = PhaseGradient.resource_rep(phase_grad_wires)
# Allocate the phase gradient registers
gate_list.append(Allocate(phase_grad_wires * (taylor_degree - 1)))
# Resource Registers
gate_list.append(GateCount(phase_grad, taylor_degree - 1))
# Allocate auxiliary registers for the coefficients
gate_list.append(Allocate(4 * grid_size + 2 * coeff_wires))
# Basis state prep per mode, implemented only for the first step
gate_list.append(GateCount(x, num_modes * grid_size))
# electronic state
gate_list.append(GateCount(resource_rep(Hadamard), int(np.ceil(np.log2(num_states)))))
if order == 1:
gate_list += TrotterVibronic._rep_circuit(vibronic_ham, coeff_precision, num_steps)
else:
gate_list += TrotterVibronic._rep_circuit(
vibronic_ham, coeff_precision, 2 * num_steps * (5 ** (k - 1))
)
# Adjoint for electronic state
gate_list.append(GateCount(resource_rep(Hadamard), int(np.ceil(np.log2(num_states)))))
# Adjoint of Basis state prep, implemented only for the first step
gate_list.append(GateCount(x, num_modes * grid_size))
# Free auxiliary registers for the coefficients
gate_list.append(Deallocate(4 * grid_size + 2 * coeff_wires))
# Deallocate the phase gradient registers
gate_list.append(Deallocate(phase_grad_wires * (taylor_degree - 1)))
return gate_list
[docs]
class TrotterPauli(ResourceOperator):
r"""A resource operation representing the Suzuki-Trotter product approximation for the complex matrix
exponential of a Hamiltonian represented as a linear combination of tensor products of Pauli operators.
The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of
Hamiltonian expressed as a linear combination of terms which in general do not commute.
For instance, in the Hamiltonian :math:`H = \Sigma^{N}_{j=0} \alpha_{j} \cdot O_{j}`, the product formula is
constructed using symmetrized products of the terms in the Hamiltonian. The symmetrized products
of order :math:`m \in [1, 2, 4, ..., 2k]` with :math:`k \in \mathbb{N}` are given by:
.. math::
\begin{align}
S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t \alpha_{j} O_{j}} \\
S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} \alpha_{j} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} \alpha_{j} O_{j}} \\
&\vdots \\
S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2},
\end{align}
where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m^{\text{th}}`
order, :math:`n`-step Suzuki-Trotter approximation is then defined as:
.. math::
e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}.
For more details see `J. Math. Phys. 32, 400 (1991) <https://pubs.aip.org/aip/jmp/article-abstract/32/2/400/229229>`_.
Args:
pauli_ham (:class:`~.pennylane.estimator.compact_hamiltonian.PauliHamiltonian`):
the Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be ``1`` or an even number
wires (WiresLike | None): the wires on which the operator acts
Raises:
TypeError: if ``pauli_ham`` is not an instance of :class:`~.PauliHamiltonian`
ValueError: if ``num_steps`` is not a positive integer
ValueError: if ``order`` is not 1 or a positive even integer
ValueError: if the number of wires provided does not match the wires expected by the operator
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) as:
.. math:: C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}
Furthermore, because of the symmetric form of the recursive formula, the first and last terms are grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
.. seealso:: :class:`~.estimator.compact_hamiltonian.PauliHamiltonian`, :class:`~.TrotterProduct`
**Example**
The resources for this operation are computed using the code below.
>>> pauli_terms = {"X":10, "XX":5, "XXXX":3, "YY": 5, "ZZ":5, "Z": 2}
>>> pauli_ham = qre.PauliHamiltonian(num_qubits=10, pauli_terms=pauli_terms)
>>> num_steps, order = (1, 2)
>>> res = qre.estimate(qre.TrotterPauli(pauli_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 10
algorithmic wires: 10
allocated wires: 0
zero state: 0
any state: 0
Total gates : 2.844E+3
'T': 2.640E+3,
'CNOT': 96,
'Z': 20,
'S': 40,
'Hadamard': 48
.. details::
:title: Usage Details
This example computes the resources for a Hamiltonian partitioned into commuting groups of
Pauli terms. See :class:`~.estimator.compact_hamiltonian.PauliHamiltonian` for more
information. Note that placing the largest commuting groups at the
boundaries, either the beginning or the end of the list, optimizes resource reduction. This
efficiency is achieved by merging the final operation of the Trotter step ``i`` with the initial
operation of step ``i+1`` which effectively minimizes gate overhead.
>>> commuting_groups = (
... {"X":10, "XX":5, "XXXX":3},
... {"YY": 5, "ZZ":5},
... {"Z": 2},
... )
>>> pauli_ham = qre.PauliHamiltonian(num_qubits=10, pauli_terms=commuting_groups)
>>> num_steps, order = (1, 2)
>>> res = qre.estimate(qre.TrotterPauli(pauli_ham, num_steps, order))
>>> print(res)
--- Resources: ---
Total wires: 10
algorithmic wires: 10
allocated wires: 0
zero state: 0
any state: 0
Total gates : 2.756E+3
'T': 2.552E+3,
'CNOT': 96,
'Z': 20,
'S': 40,
'Hadamard': 48
"""
resource_keys = {"pauli_ham", "num_steps", "order"}
def __init__(
self,
pauli_ham: PauliHamiltonian,
num_steps: int,
order: int,
wires: WiresLike | None = None,
):
if not isinstance(pauli_ham, PauliHamiltonian):
raise TypeError(
"Unsupported Hamiltonian representation for TrotterPauli."
f"This method works with PauliHamiltonian, {type(pauli_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
self.num_steps = num_steps
self.order = order
self.pauli_ham = pauli_ham
self.num_wires = pauli_ham.num_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))}")
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:
* pauli_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.PauliHamiltonian`):
The Hamiltonian to be approximately exponentiated
* num_steps (int): number of Trotter steps to perform
* order (int): order of the approximation, must be 1 or even.
"""
return {
"pauli_ham": self.pauli_ham,
"num_steps": self.num_steps,
"order": self.order,
}
[docs]
@classmethod
def resource_rep(
cls,
pauli_ham: PauliHamiltonian,
num_steps: int,
order: int,
) -> CompressedResourceOp:
"""Returns a compressed representation containing only the parameters of
the Operator that are needed to compute the resources.
Args:
pauli_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.PauliHamiltonian`):
The Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even.
Returns:
:class:`~.pennylane.estimator.resource_operator.CompressedResourceOp`: the operator in a compressed representation
"""
if not isinstance(pauli_ham, PauliHamiltonian):
raise TypeError(
"Unsupported Hamiltonian representation for TrotterPauli."
f"This method works with PauliHamiltonian, {type(pauli_ham)} provided"
)
if (not isinstance(num_steps, int)) or num_steps < 1:
raise ValueError(
f"`num_steps` is expected to be a positive integer greater than one, got {num_steps}"
)
if not (isinstance(order, int) and order > 0 and (order == 1 or order % 2 == 0)):
raise ValueError(
f"`order` is expected to be a positive integer and either one or a multiple of two; got {order}"
)
params = {
"pauli_ham": pauli_ham,
"num_steps": num_steps,
"order": order,
}
num_wires = pauli_ham.num_qubits
return CompressedResourceOp(cls, num_wires, params)
[docs]
@classmethod
def resource_decomp(
cls,
pauli_ham: PauliHamiltonian,
num_steps: int,
order: int,
) -> 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:
pauli_ham (:class:`~.pennylane.estimator.templates.compact_hamiltonian.PauliHamiltonian`):
The Hamiltonian to be approximately exponentiated
num_steps (int): number of Trotter steps to perform
order (int): order of the approximation, must be 1 or even.
Resources:
The resources are defined according to the recursive formula presented above.
The number of times an operator :math:`e^{itO_{j}}` is applied depends on the
number of Trotter steps (`n`) and the order of the approximation (`m`) as:
.. math:: C_{O_j} = 2 \cdot n \cdot 5^{\frac{m}{2} - 1}
Furthermore, because of the symmetric form of the recursive formula, the first and last terms are grouped.
This reduces the counts for those terms to:
.. math::
\begin{align}
C_{O_{0}} &= n \cdot 5^{\frac{m}{2} - 1} + 1, \\
C_{O_{N}} &= n \cdot 5^{\frac{m}{2} - 1}.
\end{align}
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.
"""
k = order // 2
pauli_terms = pauli_ham.pauli_terms
if isinstance(pauli_terms, dict):
cost_fragments = cls._cost_pauli_group(pauli_terms)
fragment_repetition = num_steps if order == 1 else 2 * num_steps * (5 ** (k - 1))
return [fragment_repetition * gate_count for gate_count in cost_fragments]
num_groups = len(pauli_terms) # commuting groups
cost_groups = [cls._cost_pauli_group(group) for group in pauli_terms]
gate_count_lst = []
if order == 1:
for group_cost_lst in cost_groups:
gate_count_lst.extend([num_steps * gate_count for gate_count in group_cost_lst])
return gate_count_lst
for index, group_cost_lst in enumerate(cost_groups):
if index == 0:
fragment_repetition = num_steps * (5 ** (k - 1)) + 1
elif index == num_groups - 1:
fragment_repetition = num_steps * (5 ** (k - 1))
else:
fragment_repetition = 2 * num_steps * (5 ** (k - 1))
gate_count_lst.extend(
[fragment_repetition * gate_count for gate_count in group_cost_lst]
)
return gate_count_lst
@staticmethod
def _cost_pauli_group(pauli_terms: dict):
"""Given a dictionary of Pauli words and frequencies, return the cost of exponentiating
the group of terms.
Args:
pauli_terms (dict): A dictionary which represents the types of Pauli words in the
Hamiltonian and their relative frequencies.
Returns:
Iterable[~.pennylane.estimator.resource_operator.GateCount]: The cost of exponentiating
a commuting group of Pauli words.
"""
return [
GateCount(PauliRot.resource_rep(pauli_word), count)
for pauli_word, count in pauli_terms.items()
]
_modules/pennylane/estimator/templates/trotter
Download Python script
Download Notebook
View on GitHub