/
utils.py
209 lines (185 loc) · 8.26 KB
/
utils.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
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017.
#
# 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.
"""Utility functions for generating random circuits."""
import numpy as np
from qiskit.circuit import ClassicalRegister, QuantumCircuit, CircuitInstruction
from qiskit.circuit import Reset
from qiskit.circuit.library import standard_gates
from qiskit.circuit.exceptions import CircuitError
def random_circuit(
num_qubits, depth, max_operands=4, measure=False, conditional=False, reset=False, seed=None
):
"""Generate random circuit of arbitrary size and form.
This function will generate a random circuit by randomly selecting gates
from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`. For example:
.. plot::
:include-source:
from qiskit.circuit.random import random_circuit
circ = random_circuit(2, 2, measure=True)
circ.draw(output='mpl')
Args:
num_qubits (int): number of quantum wires
depth (int): layers of operations (i.e. critical path length)
max_operands (int): maximum qubit operands of each gate (between 1 and 4)
measure (bool): if True, measure all qubits at the end
conditional (bool): if True, insert middle measurements and conditionals
reset (bool): if True, insert middle resets
seed (int): sets random seed (optional)
Returns:
QuantumCircuit: constructed circuit
Raises:
CircuitError: when invalid options given
"""
if num_qubits == 0:
return QuantumCircuit()
if max_operands < 1 or max_operands > 4:
raise CircuitError("max_operands must be between 1 and 4")
max_operands = max_operands if num_qubits > max_operands else num_qubits
gates_1q = [
# (Gate class, number of qubits, number of parameters)
(standard_gates.IGate, 1, 0),
(standard_gates.SXGate, 1, 0),
(standard_gates.XGate, 1, 0),
(standard_gates.RZGate, 1, 1),
(standard_gates.RGate, 1, 2),
(standard_gates.HGate, 1, 0),
(standard_gates.PhaseGate, 1, 1),
(standard_gates.RXGate, 1, 1),
(standard_gates.RYGate, 1, 1),
(standard_gates.SGate, 1, 0),
(standard_gates.SdgGate, 1, 0),
(standard_gates.SXdgGate, 1, 0),
(standard_gates.TGate, 1, 0),
(standard_gates.TdgGate, 1, 0),
(standard_gates.UGate, 1, 3),
(standard_gates.U1Gate, 1, 1),
(standard_gates.U2Gate, 1, 2),
(standard_gates.U3Gate, 1, 3),
(standard_gates.YGate, 1, 0),
(standard_gates.ZGate, 1, 0),
]
if reset:
gates_1q.append((Reset, 1, 0))
gates_2q = [
(standard_gates.CXGate, 2, 0),
(standard_gates.DCXGate, 2, 0),
(standard_gates.CHGate, 2, 0),
(standard_gates.CPhaseGate, 2, 1),
(standard_gates.CRXGate, 2, 1),
(standard_gates.CRYGate, 2, 1),
(standard_gates.CRZGate, 2, 1),
(standard_gates.CSXGate, 2, 0),
(standard_gates.CUGate, 2, 4),
(standard_gates.CU1Gate, 2, 1),
(standard_gates.CU3Gate, 2, 3),
(standard_gates.CYGate, 2, 0),
(standard_gates.CZGate, 2, 0),
(standard_gates.RXXGate, 2, 1),
(standard_gates.RYYGate, 2, 1),
(standard_gates.RZZGate, 2, 1),
(standard_gates.RZXGate, 2, 1),
(standard_gates.XXMinusYYGate, 2, 2),
(standard_gates.XXPlusYYGate, 2, 2),
(standard_gates.ECRGate, 2, 0),
(standard_gates.CSGate, 2, 0),
(standard_gates.CSdgGate, 2, 0),
(standard_gates.SwapGate, 2, 0),
(standard_gates.iSwapGate, 2, 0),
]
gates_3q = [
(standard_gates.CCXGate, 3, 0),
(standard_gates.CSwapGate, 3, 0),
(standard_gates.CCZGate, 3, 0),
(standard_gates.RCCXGate, 3, 0),
]
gates_4q = [
(standard_gates.C3SXGate, 4, 0),
(standard_gates.RC3XGate, 4, 0),
]
gates = gates_1q.copy()
if max_operands >= 2:
gates.extend(gates_2q)
if max_operands >= 3:
gates.extend(gates_3q)
if max_operands >= 4:
gates.extend(gates_4q)
gates = np.array(
gates, dtype=[("class", object), ("num_qubits", np.int64), ("num_params", np.int64)]
)
gates_1q = np.array(gates_1q, dtype=gates.dtype)
qc = QuantumCircuit(num_qubits)
if measure or conditional:
cr = ClassicalRegister(num_qubits, "c")
qc.add_register(cr)
if seed is None:
seed = np.random.randint(0, np.iinfo(np.int32).max)
rng = np.random.default_rng(seed)
qubits = np.array(qc.qubits, dtype=object, copy=True)
# Apply arbitrary random operations in layers across all qubits.
for layer_number in range(depth):
# We generate all the randomness for the layer in one go, to avoid many separate calls to
# the randomisation routines, which can be fairly slow.
# This reliably draws too much randomness, but it's less expensive than looping over more
# calls to the rng. After, trim it down by finding the point when we've used all the qubits.
gate_specs = rng.choice(gates, size=len(qubits))
cumulative_qubits = np.cumsum(gate_specs["num_qubits"], dtype=np.int64)
# Efficiently find the point in the list where the total gates would use as many as
# possible of, but not more than, the number of qubits in the layer. If there's slack, fill
# it with 1q gates.
max_index = np.searchsorted(cumulative_qubits, num_qubits, side="right")
gate_specs = gate_specs[:max_index]
slack = num_qubits - cumulative_qubits[max_index - 1]
if slack:
gate_specs = np.hstack((gate_specs, rng.choice(gates_1q, size=slack)))
# For efficiency in the Python loop, this uses Numpy vectorisation to pre-calculate the
# indices into the lists of qubits and parameters for every gate, and then suitably
# randomises those lists.
q_indices = np.empty(len(gate_specs) + 1, dtype=np.int64)
p_indices = np.empty(len(gate_specs) + 1, dtype=np.int64)
q_indices[0] = p_indices[0] = 0
np.cumsum(gate_specs["num_qubits"], out=q_indices[1:])
np.cumsum(gate_specs["num_params"], out=p_indices[1:])
parameters = rng.uniform(0, 2 * np.pi, size=p_indices[-1])
rng.shuffle(qubits)
# We've now generated everything we're going to need. Now just to add everything. The
# conditional check is outside the two loops to make the more common case of no conditionals
# faster, since in Python we don't have a compiler to do this for us.
if conditional and layer_number != 0:
is_conditional = rng.random(size=len(gate_specs)) < 0.1
condition_values = rng.integers(
0, 1 << min(num_qubits, 63), size=np.count_nonzero(is_conditional)
)
c_ptr = 0
for gate, q_start, q_end, p_start, p_end, is_cond in zip(
gate_specs["class"],
q_indices[:-1],
q_indices[1:],
p_indices[:-1],
p_indices[1:],
is_conditional,
):
operation = gate(*parameters[p_start:p_end])
if is_cond:
qc.measure(qc.qubits, cr)
# The condition values are required to be bigints, not Numpy's fixed-width type.
operation = operation.c_if(cr, int(condition_values[c_ptr]))
c_ptr += 1
qc._append(CircuitInstruction(operation=operation, qubits=qubits[q_start:q_end]))
else:
for gate, q_start, q_end, p_start, p_end in zip(
gate_specs["class"], q_indices[:-1], q_indices[1:], p_indices[:-1], p_indices[1:]
):
operation = gate(*parameters[p_start:p_end])
qc._append(CircuitInstruction(operation=operation, qubits=qubits[q_start:q_end]))
if measure:
qc.measure(qc.qubits, cr)
return qc