-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
pauli_two_design.py
141 lines (110 loc) · 5.99 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
# 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 .two_local import TwoLocal
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).
.. 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 PauliTwoDesign
circuit = PauliTwoDesign(4, reps=2, seed=5, insert_barriers=True)
circuit.draw('mpl')
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>`_
"""
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``.
"""
from qiskit.circuit.library import RYGate # pylint: disable=cyclic-import
# 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