Skip to content

Commit

Permalink
Update documentation of TrotterProduct regarding operands and cir…
Browse files Browse the repository at this point in the history
…cuit structure (#6629)

**Context:**
The decomposition logic of `TrotterProduct` relies on the `operands`
attribute of the input `hamiltonian` argument.
The documentation makes it sound like the `terms()` method of the
`hamiltonian` is the used information to determine the circuit structure
of the decomposition.
Also, there is no example illustrating the logic behind these
terms/operands being grouped when creating the circuit from
`TrotterProduct`.

**Description of the Change:**
- Change wording from `terms` towards `operands`
- Add example illustrating how the `operands` impact the created
circuit.

**Benefits:**
Documentation clarity/extent

**Possible Drawbacks:**
N/A

**Related GitHub Issues:**

[sc-79003]

---------

Co-authored-by: Jay Soni <[email protected]>
  • Loading branch information
dwierichs and Jaybsoni authored Nov 30, 2024
1 parent ad37b71 commit 343ecc1
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 15 deletions.
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ same information.

<h3>Documentation 📝</h3>

* Updated the documentation of `TrotterProduct` to include the impact of the operands in the
Hamiltonian on the strucutre of the created circuit. Included an illustrative example on this.
[(#6629)](https://github.com/PennyLaneAI/pennylane/pull/6629)

* Add reporting of test warnings as failures.
[(#6217)](https://github.com/PennyLaneAI/pennylane/pull/6217)

Expand Down
68 changes: 57 additions & 11 deletions pennylane/templates/subroutines/trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ class TrotterProduct(ErrorOperation, ResourcesOperation):
exponential of a given 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
Hamiltonian expressed as a linear combination of operands 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::
Expand Down Expand Up @@ -102,8 +102,9 @@ class TrotterProduct(ErrorOperation, ResourcesOperation):
Raises:
TypeError: The ``hamiltonian`` is not of type :class:`~.Sum`.
ValueError: The ``hamiltonian`` must have atleast two terms.
ValueError: One or more of the terms in ``hamiltonian`` are not Hermitian.
ValueError: The ``hamiltonian`` has only one term or no terms.
ValueError: One or more of the terms in ``hamiltonian`` are not Hermitian
(only for ``check_hermitian=True``)
ValueError: The ``order`` is not one or a positive even integer.
**Example**
Expand Down Expand Up @@ -134,21 +135,23 @@ def my_circ():
The Trotter-Suzuki decomposition depends on the order of the summed observables. Two
mathematically identical :class:`~.LinearCombination` objects may undergo different time
evolutions due to the order in which those observables are stored. The order of observables
can be queried using the :meth:`~.Sum.terms` method.
can be queried using the :attr:`~.Sum.operands` attribute. Also see the advanced example
below.
.. warning::
``TrotterProduct`` does not automatically simplify the input Hamiltonian, allowing
for a more fine-grained control over the decomposition but also risking an increased
runtime and number of gates required. Simplification can be performed manually by
``TrotterProduct`` does not automatically simplify the input Hamiltonian. This allows
for a more fine-grained control over the decomposition but also risks an increased
runtime and/or number of gates. Simplification can be performed manually by
applying :func:`~.simplify` to your Hamiltonian before using it in ``TrotterProduct``.
.. details::
:title: Usage Details
An *upper-bound* for the error in approximating time-evolution using this operator can be
computed by calling :func:`~.TrotterProduct.error()`. It is computed using two different methods; the
"one-norm-bound" scaling method and the "commutator-bound" scaling method. (see `Childs et al. (2021) <https://arxiv.org/abs/1912.08854>`_)
computed by calling :func:`~.TrotterProduct.error()`. It is computed using two different
methods; the "one-norm-bound" scaling method and the "commutator-bound" scaling method.
(see `Childs et al. (2021) <https://arxiv.org/abs/1912.08854>`_)
>>> hamiltonian = qml.dot([1.0, 0.5, -0.25], [qml.X(0), qml.Y(0), qml.Z(0)])
>>> op = qml.TrotterProduct(hamiltonian, time=0.01, order=2)
Expand All @@ -162,6 +165,49 @@ def my_circ():
>>> qml.adjoint(qml.TrotterProduct(hamiltonian, time, order=1, n=n))
The grouping of terms in the ``operands`` attribute of the ``hamiltonian`` impacts
the structure of the gates created by ``TrotterProduct``. To understand this, first
consider this simple two-qubit Hamiltonian with four Pauli word terms:
>>> coeffs = [0.5, 0.2, 0.1, -0.6]
>>> ops = [qml.X(0), qml.Y(1), qml.Y(0) @ qml.Z(1), qml.X(0) @ qml.Y(1)]
>>> H_flat = qml.dot(coeffs, ops)
>>> H_flat
>>> print(*H_flat.operands, sep="\n")
0.5 * X(0)
0.2 * Y(1)
0.1 * (Y(0) @ Z(1))
-0.6 * (X(0) @ Y(1))
As we can see, each Pauli word contributes an individual operand. As a result, the
``TrotterProduct`` (of first order, for simplicity) of this Hamiltonian will contain four
exponentials per Trotter step:
>>> qml.TrotterProduct(H_flat, 1., n=1, order=1).decomposition()
[Exp(1j -0.6 * (X(0) @ Y(1))),
Exp(1j 0.1 * (Y(0) @ Z(1))),
Exp(1j 0.2 * Y(1)),
Exp(1j 0.5 * X(0))]
If we first create two operands with two Pauli words each and then sum those, this is
reflected in the structure of the operator:
>>> H_grouped = qml.sum(qml.dot(coeffs[:2], ops[:2]), qml.dot(coeffs[2:], ops[2:]))
>>> print(*H_grouped.operands, sep="\n")
0.5 * X(0) + 0.2 * Y(1)
0.1 * (Y(0) @ Z(1)) + -0.6 * (X(0) @ Y(1))
The ``TrotterProduct`` accordingly has a different structure as well:
>>> qml.TrotterProduct(H_grouped, 1., n=1, order=1).decomposition()
[Exp(1j 0.1 * (Y(0) @ Z(1)) + -0.6 * (X(0) @ Y(1))),
Exp(1j 0.5 * X(0) + 0.2 * Y(1))]
As we can see, the ``operands`` structure of the Hamiltonian directly impacts the
constructed Trotter circuit, and in general, those circuits will be different
approximations to the true time evolution.
We can also compute the gradient with respect to the coefficients of the Hamiltonian and the
evolution time:
Expand Down
5 changes: 1 addition & 4 deletions tests/templates/test_subroutines/test_trotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,7 @@ def test_error_type(self, hamiltonian, raise_error):
qml.TrotterProduct(hamiltonian, time=1.23)

else:
try:
qml.TrotterProduct(hamiltonian, time=1.23)
except TypeError:
assert False # test should fail if an error was raised when we expect it not to
qml.TrotterProduct(hamiltonian, time=1.23)

@pytest.mark.parametrize(
"hamiltonian",
Expand Down

0 comments on commit 343ecc1

Please sign in to comment.