forked from Qiskit/qiskit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgrover_operator.py
311 lines (261 loc) · 16.3 KB
/
grover_operator.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# 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 Grover operator."""
from __future__ import annotations
from typing import List, Optional, Union
import numpy
from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister
from qiskit.exceptions import QiskitError
from qiskit.quantum_info import Statevector, Operator, DensityMatrix
from .standard_gates import MCXGate
class GroverOperator(QuantumCircuit):
r"""The Grover operator.
Grover's search algorithm [1, 2] consists of repeated applications of the so-called
Grover operator used to amplify the amplitudes of the desired output states.
This operator, :math:`\mathcal{Q}`, consists of the phase oracle, :math:`\mathcal{S}_f`,
zero phase-shift or zero reflection, :math:`\mathcal{S}_0`, and an
input state preparation :math:`\mathcal{A}`:
.. math::
\mathcal{Q} = \mathcal{A} \mathcal{S}_0 \mathcal{A}^\dagger \mathcal{S}_f
In the standard Grover search we have :math:`\mathcal{A} = H^{\otimes n}`:
.. math::
\mathcal{Q} = H^{\otimes n} \mathcal{S}_0 H^{\otimes n} \mathcal{S}_f
= D \mathcal{S_f}
The operation :math:`D = H^{\otimes n} \mathcal{S}_0 H^{\otimes n}` is also referred to as
diffusion operator. In this formulation we can see that Grover's operator consists of two
steps: first, the phase oracle multiplies the good states by -1 (with :math:`\mathcal{S}_f`)
and then the whole state is reflected around the mean (with :math:`D`).
This class allows setting a different state preparation, as in quantum amplitude
amplification (a generalization of Grover's algorithm), :math:`\mathcal{A}` might not be
a layer of Hardamard gates [3].
The action of the phase oracle :math:`\mathcal{S}_f` is defined as
.. math::
\mathcal{S}_f: |x\rangle \mapsto (-1)^{f(x)}|x\rangle
where :math:`f(x) = 1` if :math:`x` is a good state and 0 otherwise. To highlight the fact
that this oracle flips the phase of the good states and does not flip the state of a result
qubit, we call :math:`\mathcal{S}_f` a phase oracle.
Note that you can easily construct a phase oracle from a bitflip oracle by sandwiching the
controlled X gate on the result qubit by a X and H gate. For instance
.. code-block:: text
Bitflip oracle Phaseflip oracle
q_0: ──■── q_0: ────────────■────────────
┌─┴─┐ ┌───┐┌───┐┌─┴─┐┌───┐┌───┐
out: ┤ X ├ out: ┤ X ├┤ H ├┤ X ├┤ H ├┤ X ├
└───┘ └───┘└───┘└───┘└───┘└───┘
There is some flexibility in defining the oracle and :math:`\mathcal{A}` operator. Before the
Grover operator is applied in Grover's algorithm, the qubits are first prepared with one
application of the :math:`\mathcal{A}` operator (or Hadamard gates in the standard formulation).
Thus, we always have operation of the form
:math:`\mathcal{A} \mathcal{S}_f \mathcal{A}^\dagger`. Therefore it is possible to move
bitflip logic into :math:`\mathcal{A}` and leaving the oracle only to do phaseflips via Z gates
based on the bitflips. One possible use-case for this are oracles that do not uncompute the
state qubits.
The zero reflection :math:`\mathcal{S}_0` is usually defined as
.. math::
\mathcal{S}_0 = 2 |0\rangle^{\otimes n} \langle 0|^{\otimes n} - \mathbb{I}_n
where :math:`\mathbb{I}_n` is the identity on :math:`n` qubits.
By default, this class implements the negative version
:math:`2 |0\rangle^{\otimes n} \langle 0|^{\otimes n} - \mathbb{I}_n`, since this can simply
be implemented with a multi-controlled Z sandwiched by X gates on the target qubit and the
introduced global phase does not matter for Grover's algorithm.
Examples:
>>> from qiskit.circuit import QuantumCircuit
>>> from qiskit.circuit.library import GroverOperator
>>> oracle = QuantumCircuit(2)
>>> oracle.z(0) # good state = first qubit is |1>
>>> grover_op = GroverOperator(oracle, insert_barriers=True)
>>> grover_op.decompose().draw()
┌───┐ ░ ┌───┐ ░ ┌───┐ ┌───┐ ░ ┌───┐
state_0: ┤ Z ├─░─┤ H ├─░─┤ X ├───────■──┤ X ├──────░─┤ H ├
└───┘ ░ ├───┤ ░ ├───┤┌───┐┌─┴─┐├───┤┌───┐ ░ ├───┤
state_1: ──────░─┤ H ├─░─┤ X ├┤ H ├┤ X ├┤ H ├┤ X ├─░─┤ H ├
░ └───┘ ░ └───┘└───┘└───┘└───┘└───┘ ░ └───┘
>>> oracle = QuantumCircuit(1)
>>> oracle.z(0) # the qubit state |1> is the good state
>>> state_preparation = QuantumCircuit(1)
>>> state_preparation.ry(0.2, 0) # non-uniform state preparation
>>> grover_op = GroverOperator(oracle, state_preparation)
>>> grover_op.decompose().draw()
┌───┐┌──────────┐┌───┐┌───┐┌───┐┌─────────┐
state_0: ┤ Z ├┤ RY(-0.2) ├┤ X ├┤ Z ├┤ X ├┤ RY(0.2) ├
└───┘└──────────┘└───┘└───┘└───┘└─────────┘
>>> oracle = QuantumCircuit(4)
>>> oracle.z(3)
>>> reflection_qubits = [0, 3]
>>> state_preparation = QuantumCircuit(4)
>>> state_preparation.cry(0.1, 0, 3)
>>> state_preparation.ry(0.5, 3)
>>> grover_op = GroverOperator(oracle, state_preparation,
... reflection_qubits=reflection_qubits)
>>> grover_op.decompose().draw()
┌───┐ ┌───┐
state_0: ──────────────────────■──────┤ X ├───────■──┤ X ├──────────■────────────────
│ └───┘ │ └───┘ │
state_1: ──────────────────────┼──────────────────┼─────────────────┼────────────────
│ │ │
state_2: ──────────────────────┼──────────────────┼─────────────────┼────────────────
┌───┐┌──────────┐┌────┴─────┐┌───┐┌───┐┌─┴─┐┌───┐┌───┐┌────┴────┐┌─────────┐
state_3: ┤ Z ├┤ RY(-0.5) ├┤ RY(-0.1) ├┤ X ├┤ H ├┤ X ├┤ H ├┤ X ├┤ RY(0.1) ├┤ RY(0.5) ├
└───┘└──────────┘└──────────┘└───┘└───┘└───┘└───┘└───┘└─────────┘└─────────┘
>>> mark_state = Statevector.from_label('011')
>>> diffuse_operator = 2 * DensityMatrix.from_label('000') - Operator.from_label('III')
>>> grover_op = GroverOperator(oracle=mark_state, zero_reflection=diffuse_operator)
>>> grover_op.decompose().draw(fold=70)
┌─────────────────┐ ┌───┐ »
state_0: ┤0 ├──────┤ H ├──────────────────────────»
│ │┌─────┴───┴─────┐ ┌───┐ »
state_1: ┤1 UCRZ(0,pi,0,0) ├┤0 ├─────┤ H ├──────────»
│ ││ UCRZ(pi/2,0) │┌────┴───┴────┐┌───┐»
state_2: ┤2 ├┤1 ├┤ UCRZ(-pi/4) ├┤ H ├»
└─────────────────┘└───────────────┘└─────────────┘└───┘»
« ┌─────────────────┐ ┌───┐
«state_0: ┤0 ├──────┤ H ├─────────────────────────
« │ │┌─────┴───┴─────┐ ┌───┐
«state_1: ┤1 UCRZ(pi,0,0,0) ├┤0 ├────┤ H ├──────────
« │ ││ UCRZ(pi/2,0) │┌───┴───┴────┐┌───┐
«state_2: ┤2 ├┤1 ├┤ UCRZ(pi/4) ├┤ H ├
« └─────────────────┘└───────────────┘└────────────┘└───┘
References:
[1]: L. K. Grover (1996), A fast quantum mechanical algorithm for database search,
`arXiv:quant-ph/9605043 <https://arxiv.org/abs/quant-ph/9605043>`_.
[2]: I. Chuang & M. Nielsen, Quantum Computation and Quantum Information,
Cambridge: Cambridge University Press, 2000. Chapter 6.1.2.
[3]: Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000).
Quantum Amplitude Amplification and Estimation.
`arXiv:quant-ph/0005055 <http://arxiv.org/abs/quant-ph/0005055>`_.
"""
def __init__(
self,
oracle: Union[QuantumCircuit, Statevector],
state_preparation: Optional[QuantumCircuit] = None,
zero_reflection: Optional[Union[QuantumCircuit, DensityMatrix, Operator]] = None,
reflection_qubits: Optional[List[int]] = None,
insert_barriers: bool = False,
mcx_mode: str = "noancilla",
name: str = "Q",
) -> None:
r"""
Args:
oracle: The phase oracle implementing a reflection about the bad state. Note that this
is not a bitflip oracle, see the docstring for more information.
state_preparation: The operator preparing the good and bad state.
For Grover's algorithm, this is a n-qubit Hadamard gate and for amplitude
amplification or estimation the operator :math:`\mathcal{A}`.
zero_reflection: The reflection about the zero state, :math:`\mathcal{S}_0`.
reflection_qubits: Qubits on which the zero reflection acts on.
insert_barriers: Whether barriers should be inserted between the reflections and A.
mcx_mode: The mode to use for building the default zero reflection.
name: The name of the circuit.
"""
super().__init__(name=name)
# store inputs
if isinstance(oracle, Statevector):
from qiskit.circuit.library import Diagonal # pylint: disable=cyclic-import
oracle = Diagonal((-1) ** oracle.data)
self._oracle = oracle
if isinstance(zero_reflection, (Operator, DensityMatrix)):
from qiskit.circuit.library import Diagonal # pylint: disable=cyclic-import
zero_reflection = Diagonal(zero_reflection.data.diagonal())
self._zero_reflection = zero_reflection
self._reflection_qubits = reflection_qubits
self._state_preparation = state_preparation
self._insert_barriers = insert_barriers
self._mcx_mode = mcx_mode
# build circuit
self._build()
@property
def reflection_qubits(self):
"""Reflection qubits, on which S0 is applied (if S0 is not user-specified)."""
if self._reflection_qubits is not None:
return self._reflection_qubits
num_state_qubits = self.oracle.num_qubits - self.oracle.num_ancillas
return list(range(num_state_qubits))
@property
def zero_reflection(self) -> QuantumCircuit:
"""The subcircuit implementing the reflection about 0."""
if self._zero_reflection is not None:
return self._zero_reflection
num_state_qubits = self.oracle.num_qubits - self.oracle.num_ancillas
return _zero_reflection(num_state_qubits, self.reflection_qubits, self._mcx_mode)
@property
def state_preparation(self) -> QuantumCircuit:
"""The subcircuit implementing the A operator or Hadamards."""
if self._state_preparation is not None:
return self._state_preparation
num_state_qubits = self.oracle.num_qubits - self.oracle.num_ancillas
hadamards = QuantumCircuit(num_state_qubits, name="H")
# apply Hadamards only on reflection qubits, rest will cancel out
hadamards.h(self.reflection_qubits)
return hadamards
@property
def oracle(self):
"""The oracle implementing a reflection about the bad state."""
return self._oracle
def _build(self):
num_state_qubits = self.oracle.num_qubits - self.oracle.num_ancillas
circuit = QuantumCircuit(QuantumRegister(num_state_qubits, name="state"), name="Q")
num_ancillas = numpy.max(
[
self.oracle.num_ancillas,
self.zero_reflection.num_ancillas,
self.state_preparation.num_ancillas,
]
)
if num_ancillas > 0:
circuit.add_register(AncillaRegister(num_ancillas, name="ancilla"))
circuit.compose(self.oracle, list(range(self.oracle.num_qubits)), inplace=True)
if self._insert_barriers:
circuit.barrier()
circuit.compose(
self.state_preparation.inverse(),
list(range(self.state_preparation.num_qubits)),
inplace=True,
)
if self._insert_barriers:
circuit.barrier()
circuit.compose(
self.zero_reflection, list(range(self.zero_reflection.num_qubits)), inplace=True
)
if self._insert_barriers:
circuit.barrier()
circuit.compose(
self.state_preparation, list(range(self.state_preparation.num_qubits)), inplace=True
)
# minus sign
circuit.global_phase = numpy.pi
self.add_register(*circuit.qregs)
try:
circuit_wrapped = circuit.to_gate()
except QiskitError:
circuit_wrapped = circuit.to_instruction()
self.compose(circuit_wrapped, qubits=self.qubits, inplace=True)
# TODO use the oracle compiler or the bit string oracle
def _zero_reflection(
num_state_qubits: int, qubits: List[int], mcx_mode: Optional[str] = None
) -> QuantumCircuit:
qr_state = QuantumRegister(num_state_qubits, "state")
reflection = QuantumCircuit(qr_state, name="S_0")
num_ancillas = MCXGate.get_num_ancilla_qubits(len(qubits) - 1, mcx_mode)
if num_ancillas > 0:
qr_ancilla = AncillaRegister(num_ancillas, "ancilla")
reflection.add_register(qr_ancilla)
else:
qr_ancilla = AncillaRegister(0)
reflection.x(qubits)
if len(qubits) == 1:
reflection.z(0) # MCX does not allow 0 control qubits, therefore this is separate
else:
reflection.h(qubits[-1])
reflection.mcx(qubits[:-1], qubits[-1], qr_ancilla[:], mode=mcx_mode)
reflection.h(qubits[-1])
reflection.x(qubits)
return reflection