-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
pauli_two_design.py
243 lines (193 loc) · 10.8 KB
/
pauli_two_design.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
# 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 Random Pauli circuit class."""
from __future__ import annotations
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library.standard_gates import RXGate, RYGate, RZGate, CZGate
from qiskit.utils.deprecation import deprecate_func
from qiskit._accelerate.circuit_library import Block, py_n_local
from .two_local import TwoLocal
def pauli_two_design(
num_qubits: int,
reps: int = 3,
seed: int | None = None,
insert_barriers: bool = False,
parameter_prefix: str = "θ",
name: str = "PauliTwoDesign",
) -> QuantumCircuit:
r"""Construct a Pauli 2-design ansatz.
This class implements a particular form of a 2-design circuit [1], which is frequently studied
in quantum machine learning literature, such as, e.g., the investigation of Barren plateaus in
variational algorithms [2].
The circuit consists of alternating rotation and entanglement layers with
an initial layer of :math:`\sqrt{H} = RY(\pi/4)` gates.
The rotation layers contain single qubit Pauli rotations, where the axis is chosen uniformly
at random to be X, Y or Z. The entanglement layers is compromised of pairwise CZ gates
with a total depth of 2.
For instance, the circuit could look like this:
.. parsed-literal::
┌─────────┐┌──────────┐ ░ ┌──────────┐ ░ ┌──────────┐
q_0: ┤ RY(π/4) ├┤ RZ(θ[0]) ├─■─────░─┤ RY(θ[4]) ├─■─────░──┤ RZ(θ[8]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ├──────────┤
q_1: ┤ RY(π/4) ├┤ RZ(θ[1]) ├─■──■──░─┤ RY(θ[5]) ├─■──■──░──┤ RX(θ[9]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ┌┴──────────┤
q_2: ┤ RY(π/4) ├┤ RX(θ[2]) ├─■──■──░─┤ RY(θ[6]) ├─■──■──░─┤ RX(θ[10]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ├───────────┤
q_3: ┤ RY(π/4) ├┤ RZ(θ[3]) ├─■─────░─┤ RX(θ[7]) ├─■─────░─┤ RY(θ[11]) ├
└─────────┘└──────────┘ ░ └──────────┘ ░ └───────────┘
Examples:
.. plot::
:include-source:
from qiskit.circuit.library import pauli_two_design
circuit = pauli_two_design(4, reps=2, seed=5, insert_barriers=True)
circuit.draw("mpl")
Args:
num_qubits: The number of qubits of the Pauli Two-Design circuit.
reps: Specifies how often a block consisting of a rotation layer and entanglement
layer is repeated.
seed: The seed for randomly choosing the axes of the Pauli rotations.
parameter_prefix: The prefix used for the rotation parameters.
insert_barriers: If ``True``, barriers are inserted in between each layer. If ``False``,
no barriers are inserted. Defaults to ``False``.
name: The circuit name.
Returns:
A Pauli 2-design circuit.
References:
[1]: Nakata et al., Unitary 2-designs from random X- and Z-diagonal unitaries.
`arXiv:1502.07514 <https://arxiv.org/pdf/1502.07514.pdf>`_
[2]: McClean et al., Barren plateaus in quantum neural network training landscapes.
`arXiv:1803.11173 <https://arxiv.org/pdf/1803.11173.pdf>`_
"""
rng = np.random.default_rng(seed)
random_block = Block.from_callable(1, 1, lambda params: _random_pauli_builder(params, rng))
cz_block = Block.from_standard_gate(CZGate._standard_gate)
data = py_n_local(
num_qubits=num_qubits,
reps=reps,
rotation_blocks=[random_block],
entanglement_blocks=[cz_block],
entanglement=["pairwise"],
insert_barriers=insert_barriers,
skip_final_rotation_layer=False,
skip_unentangled_qubits=False,
parameter_prefix=parameter_prefix,
)
two_design = QuantumCircuit._from_circuit_data(data)
circuit = QuantumCircuit(num_qubits, name=name)
circuit.ry(np.pi / 4, circuit.qubits)
circuit.compose(two_design, inplace=True, copy=False)
return circuit
def _random_pauli_builder(params, rng):
gate_cls = rng.choice([RXGate, RYGate, RZGate])
gate = gate_cls(params[0])
return gate, gate.params
class PauliTwoDesign(TwoLocal):
r"""The Pauli Two-Design ansatz.
This class implements a particular form of a 2-design circuit [1], which is frequently studied
in quantum machine learning literature, such as e.g. the investigating of Barren plateaus in
variational algorithms [2].
The circuit consists of alternating rotation and entanglement layers with
an initial layer of :math:`\sqrt{H} = RY(\pi/4)` gates.
The rotation layers contain single qubit Pauli rotations, where the axis is chosen uniformly
at random to be X, Y or Z. The entanglement layers is compromised of pairwise CZ gates
with a total depth of 2.
For instance, the circuit could look like this (but note that choosing a different seed
yields different Pauli rotations).
.. code-block:: text
┌─────────┐┌──────────┐ ░ ┌──────────┐ ░ ┌──────────┐
q_0: ┤ RY(π/4) ├┤ RZ(θ[0]) ├─■─────░─┤ RY(θ[4]) ├─■─────░──┤ RZ(θ[8]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ├──────────┤
q_1: ┤ RY(π/4) ├┤ RZ(θ[1]) ├─■──■──░─┤ RY(θ[5]) ├─■──■──░──┤ RX(θ[9]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ┌┴──────────┤
q_2: ┤ RY(π/4) ├┤ RX(θ[2]) ├─■──■──░─┤ RY(θ[6]) ├─■──■──░─┤ RX(θ[10]) ├
├─────────┤├──────────┤ │ ░ ├──────────┤ │ ░ ├───────────┤
q_3: ┤ RY(π/4) ├┤ RZ(θ[3]) ├─■─────░─┤ RX(θ[7]) ├─■─────░─┤ RY(θ[11]) ├
└─────────┘└──────────┘ ░ └──────────┘ ░ └───────────┘
Examples:
.. plot::
:include-source:
from qiskit.circuit.library import PauliTwoDesign
circuit = PauliTwoDesign(4, reps=2, seed=5, insert_barriers=True)
circuit.draw('mpl')
.. seealso::
The :func:`.pauli_two_design` function constructs the functionally same circuit, but faster.
References:
[1]: Nakata et al., Unitary 2-designs from random X- and Z-diagonal unitaries.
`arXiv:1502.07514 <https://arxiv.org/pdf/1502.07514.pdf>`_
[2]: McClean et al., Barren plateaus in quantum neural network training landscapes.
`arXiv:1803.11173 <https://arxiv.org/pdf/1803.11173.pdf>`_
"""
@deprecate_func(
since="1.3",
additional_msg="Use the function qiskit.circuit.library.pauli_two_design instead.",
pending=True,
)
def __init__(
self,
num_qubits: int | None = None,
reps: int = 3,
seed: int | None = None,
insert_barriers: bool = False,
name: str = "PauliTwoDesign",
):
"""
Args:
num_qubits: The number of qubits of the Pauli Two-Design circuit.
reps: Specifies how often a block consisting of a rotation layer and entanglement
layer is repeated.
seed: The seed for randomly choosing the axes of the Pauli rotations.
insert_barriers: If ``True``, barriers are inserted in between each layer. If ``False``,
no barriers are inserted. Defaults to ``False``.
"""
# store a random number generator
self._seed = seed
self._rng = np.random.default_rng(seed)
# store a dict to keep track of the random gates
self._gates: dict[int, list[str]] = {}
super().__init__(
num_qubits,
reps=reps,
entanglement_blocks="cz",
entanglement="pairwise",
insert_barriers=insert_barriers,
name=name,
)
# set the initial layer
self._prepended_blocks = [RYGate(np.pi / 4)]
self._prepended_entanglement = ["linear"]
def _invalidate(self):
"""Invalidate the circuit and reset the random number."""
self._rng = np.random.default_rng(self._seed) # reset number generator
super()._invalidate()
def _build_rotation_layer(self, circuit, param_iter, i):
"""Build a rotation layer."""
layer = QuantumCircuit(*self.qregs)
qubits = range(self.num_qubits)
# if no gates for this layer were generated, generate them
if i not in self._gates:
self._gates[i] = list(self._rng.choice(["rx", "ry", "rz"], self.num_qubits))
# if not enough gates exist, add more
elif len(self._gates[i]) < self.num_qubits:
num_missing = self.num_qubits - len(self._gates[i])
self._gates[i] += list(self._rng.choice(["rx", "ry", "rz"], num_missing))
for j in qubits:
getattr(layer, self._gates[i][j])(next(param_iter), j)
# add the layer to the circuit
circuit.compose(layer, inplace=True)
@property
def num_parameters_settable(self) -> int:
"""Return the number of settable parameters.
Returns:
The number of possibly distinct parameters.
"""
return (self.reps + 1) * self.num_qubits