-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
lie_trotter.py
143 lines (119 loc) · 5.59 KB
/
lie_trotter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""The Lie-Trotter product formula."""
from __future__ import annotations
import inspect
from collections.abc import Callable
from typing import Any
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.quantum_info.operators import SparsePauliOp, Pauli
from qiskit.utils.deprecation import deprecate_arg
from .product_formula import ProductFormula
class LieTrotter(ProductFormula):
r"""The Lie-Trotter product formula.
The Lie-Trotter formula approximates the exponential of two non-commuting operators
with products of their exponentials up to a second order error:
.. math::
e^{A + B} \approx e^{A}e^{B}.
In this implementation, the operators are provided as sum terms of a Pauli operator.
For example, we approximate
.. math::
e^{-it(XX + ZZ)} = e^{-it XX}e^{-it ZZ} + \mathcal{O}(t^2).
References:
[1]: D. Berry, G. Ahokas, R. Cleve and B. Sanders,
"Efficient quantum algorithms for simulating sparse Hamiltonians" (2006).
`arXiv:quant-ph/0508139 <https://arxiv.org/abs/quant-ph/0508139>`_
[2]: N. Hatano and M. Suzuki,
"Finding Exponential Product Formulas of Higher Orders" (2005).
`arXiv:math-ph/0506007 <https://arxiv.org/pdf/math-ph/0506007.pdf>`_
"""
@deprecate_arg(
name="atomic_evolution",
since="1.2",
predicate=lambda callable: callable is not None
and len(inspect.signature(callable).parameters) == 2,
deprecation_description=(
"The 'Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]' signature of the "
"'atomic_evolution' argument"
),
additional_msg=(
"Instead you should update your 'atomic_evolution' function to be of the following "
"type: 'Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]'."
),
pending=True,
)
def __init__(
self,
reps: int = 1,
insert_barriers: bool = False,
cx_structure: str = "chain",
atomic_evolution: (
Callable[[Pauli | SparsePauliOp, float], QuantumCircuit]
| Callable[[QuantumCircuit, Pauli | SparsePauliOp, float], None]
| None
) = None,
wrap: bool = False,
) -> None:
"""
Args:
reps: The number of time steps.
insert_barriers: Whether to insert barriers between the atomic evolutions.
cx_structure: How to arrange the CX gates for the Pauli evolutions, can be
``"chain"``, where next neighbor connections are used, or ``"fountain"``,
where all qubits are connected to one. This only takes effect when
``atomic_evolution is None``.
atomic_evolution: A function to apply the evolution of a single :class:`.Pauli`, or
:class:`.SparsePauliOp` of only commuting terms, to a circuit. The function takes in
three arguments: the circuit to append the evolution to, the Pauli operator to
evolve, and the evolution time. By default, a single Pauli evolution is decomposed
into a chain of ``CX`` gates and a single ``RZ`` gate.
Alternatively, the function can also take Pauli operator and evolution time as
inputs and returns the circuit that will be appended to the overall circuit being
built.
wrap: Whether to wrap the atomic evolutions into custom gate objects. This only takes
effect when ``atomic_evolution is None``.
"""
super().__init__(1, reps, insert_barriers, cx_structure, atomic_evolution, wrap)
def synthesize(self, evolution):
# get operators and time to evolve
operators = evolution.operator
time = evolution.time
# construct the evolution circuit
single_rep = QuantumCircuit(operators[0].num_qubits)
if not isinstance(operators, list):
pauli_list = [(Pauli(op), np.real(coeff)) for op, coeff in operators.to_list()]
else:
pauli_list = [(op, 1) for op in operators]
for i, (op, coeff) in enumerate(pauli_list):
self.atomic_evolution(single_rep, op, coeff * time / self.reps)
if self.insert_barriers and i != len(pauli_list) - 1:
single_rep.barrier()
return single_rep.repeat(self.reps, insert_barriers=self.insert_barriers).decompose()
@property
def settings(self) -> dict[str, Any]:
"""Return the settings in a dictionary, which can be used to reconstruct the object.
Returns:
A dictionary containing the settings of this product formula.
Raises:
NotImplementedError: If a custom atomic evolution is set, which cannot be serialized.
"""
if self._atomic_evolution is not None:
raise NotImplementedError(
"Cannot serialize a product formula with a custom atomic evolution."
)
return {
"reps": self.reps,
"insert_barriers": self.insert_barriers,
"cx_structure": self._cx_structure,
"wrap": self._wrap,
}