29
29
from qiskit .compiler import transpile
30
30
from qiskit .exceptions import QiskitError
31
31
from qiskit .quantum_info import Clifford , random_clifford
32
+ from qiskit .transpiler import CouplingMap , PassManager
33
+ from qiskit .transpiler .passes .synthesis .high_level_synthesis import HLSConfig , HighLevelSynthesis
32
34
from qiskit .utils .deprecation import deprecate_func
33
35
36
+ DEFAULT_SYNTHESIS_METHOD = "rb_default"
37
+
34
38
_DATA_FOLDER = os .path .join (os .path .dirname (__file__ ), "data" )
35
39
36
40
_CLIFFORD_COMPOSE_1Q = np .load (f"{ _DATA_FOLDER } /clifford_compose_1q.npz" )["table" ]
@@ -110,37 +114,141 @@ def _circuit_compose(
110
114
return self
111
115
112
116
113
- def _truncate_inactive_qubits (
114
- circ : QuantumCircuit , active_qubits : Sequence [Qubit ]
117
+ def _synthesize_clifford (
118
+ clifford : Clifford ,
119
+ basis_gates : Optional [Tuple [str ]],
120
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]] = None ,
121
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
115
122
) -> QuantumCircuit :
116
- res = QuantumCircuit (active_qubits , name = circ .name , metadata = circ .metadata )
117
- for inst in circ :
118
- if all (q in active_qubits for q in inst .qubits ):
119
- res .append (inst )
120
- res .calibrations = circ .calibrations
121
- return res
123
+ """Synthesize a circuit of a Clifford element. The resulting circuit contains only
124
+ ``basis_gates`` and it complies with ``coupling_tuple``.
125
+
126
+ Args:
127
+ clifford: Clifford element to be converted
128
+ basis_gates: basis gates to use in the conversion
129
+ coupling_tuple: coupling map to use in the conversion in the form of tuple of edges
130
+ synthesis_method: conversion algorithm name
131
+
132
+ Returns:
133
+ Synthesized circuit
134
+ """
135
+ qc = QuantumCircuit (clifford .num_qubits , name = str (clifford ))
136
+ qc .append (clifford , qc .qubits )
137
+ return _synthesize_clifford_circuit (
138
+ qc ,
139
+ basis_gates = basis_gates ,
140
+ coupling_tuple = coupling_tuple ,
141
+ synthesis_method = synthesis_method ,
142
+ )
122
143
123
144
124
145
def _synthesize_clifford_circuit (
125
- circuit : QuantumCircuit , basis_gates : Tuple [str ]
146
+ circuit : QuantumCircuit ,
147
+ basis_gates : Optional [Tuple [str ]],
148
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]] = None ,
149
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
126
150
) -> QuantumCircuit :
127
- # synthesizes clifford circuits using given basis gates, for use during
128
- # custom transpilation during RB circuit generation.
129
- return transpile (circuit , basis_gates = list (basis_gates ), optimization_level = 1 )
151
+ """Convert a Clifford circuit into one composed of ``basis_gates`` with
152
+ satisfying ``coupling_tuple`` using the specified synthesis method.
153
+
154
+ Args:
155
+ circuit: Clifford circuit to be converted
156
+ basis_gates: basis gates to use in the conversion
157
+ coupling_tuple: coupling map to use in the conversion in the form of tuple of edges
158
+ synthesis_method: name of Clifford synthesis algorithm to use
159
+
160
+ Returns:
161
+ Synthesized circuit
162
+ """
163
+ if basis_gates :
164
+ basis_gates = list (basis_gates )
165
+ coupling_map = CouplingMap (coupling_tuple ) if coupling_tuple else None
166
+
167
+ # special handling for 1q or 2q case for speed
168
+ if circuit .num_qubits <= 2 :
169
+ if synthesis_method == DEFAULT_SYNTHESIS_METHOD :
170
+ return transpile (
171
+ circuit ,
172
+ basis_gates = basis_gates ,
173
+ coupling_map = coupling_map ,
174
+ optimization_level = 1 ,
175
+ )
176
+ else :
177
+ # Provided custom synthesis method, re-synthesize Clifford circuit
178
+ # convert the circuit back to a Clifford object and then call the synthesis plugin
179
+ new_circuit = QuantumCircuit (circuit .num_qubits , name = circuit .name )
180
+ new_circuit .append (Clifford (circuit ), new_circuit .qubits )
181
+ circuit = new_circuit
182
+
183
+ # for 3q+ or custom synthesis method, synthesizes clifford circuit
184
+ hls_config = HLSConfig (clifford = [(synthesis_method , {"basis_gates" : basis_gates })])
185
+ pm = PassManager ([HighLevelSynthesis (hls_config = hls_config , coupling_map = coupling_map )])
186
+ circuit = pm .run (circuit )
187
+ return circuit
130
188
131
189
132
- @lru_cache (maxsize = None )
190
+ @lru_cache (maxsize = 256 )
133
191
def _clifford_1q_int_to_instruction (
134
- num : Integral , basis_gates : Optional [Tuple [str ]]
192
+ num : Integral ,
193
+ basis_gates : Optional [Tuple [str ]],
194
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
135
195
) -> Instruction :
136
- return CliffordUtils .clifford_1_qubit_circuit (num , basis_gates ).to_instruction ()
196
+ return CliffordUtils .clifford_1_qubit_circuit (
197
+ num , basis_gates = basis_gates , synthesis_method = synthesis_method
198
+ ).to_instruction ()
137
199
138
200
139
201
@lru_cache (maxsize = 11520 )
140
202
def _clifford_2q_int_to_instruction (
141
- num : Integral , basis_gates : Optional [Tuple [str ]]
203
+ num : Integral ,
204
+ basis_gates : Optional [Tuple [str ]],
205
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]],
206
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
142
207
) -> Instruction :
143
- return CliffordUtils .clifford_2_qubit_circuit (num , basis_gates ).to_instruction ()
208
+ return CliffordUtils .clifford_2_qubit_circuit (
209
+ num ,
210
+ basis_gates = basis_gates ,
211
+ coupling_tuple = coupling_tuple ,
212
+ synthesis_method = synthesis_method ,
213
+ ).to_instruction ()
214
+
215
+
216
+ def _hash_cliff (cliff ):
217
+ return cliff .tableau .tobytes (), cliff .tableau .shape
218
+
219
+
220
+ def _dehash_cliff (cliff_hash ):
221
+ tableau = np .frombuffer (cliff_hash [0 ], dtype = bool ).reshape (cliff_hash [1 ])
222
+ return Clifford (tableau )
223
+
224
+
225
+ def _clifford_to_instruction (
226
+ clifford : Clifford ,
227
+ basis_gates : Optional [Tuple [str ]],
228
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]],
229
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
230
+ ) -> Instruction :
231
+ return _cached_clifford_to_instruction (
232
+ _hash_cliff (clifford ),
233
+ basis_gates = basis_gates ,
234
+ coupling_tuple = coupling_tuple ,
235
+ synthesis_method = synthesis_method ,
236
+ )
237
+
238
+
239
+ @lru_cache (maxsize = 256 )
240
+ def _cached_clifford_to_instruction (
241
+ cliff_hash : Tuple [str , Tuple [int , int ]],
242
+ basis_gates : Optional [Tuple [str ]],
243
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]],
244
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
245
+ ) -> Instruction :
246
+ return _synthesize_clifford (
247
+ _dehash_cliff (cliff_hash ),
248
+ basis_gates = basis_gates ,
249
+ coupling_tuple = coupling_tuple ,
250
+ synthesis_method = synthesis_method ,
251
+ ).to_instruction ()
144
252
145
253
146
254
# The classes VGate and WGate are not actually used in the code - we leave them here to give
@@ -254,7 +362,12 @@ def random_clifford_circuits(
254
362
255
363
@classmethod
256
364
@lru_cache (maxsize = 24 )
257
- def clifford_1_qubit_circuit (cls , num , basis_gates : Optional [Tuple [str , ...]] = None ):
365
+ def clifford_1_qubit_circuit (
366
+ cls ,
367
+ num ,
368
+ basis_gates : Optional [Tuple [str , ...]] = None ,
369
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
370
+ ):
258
371
"""Return the 1-qubit clifford circuit corresponding to ``num``,
259
372
where ``num`` is between 0 and 23.
260
373
"""
@@ -275,20 +388,28 @@ def clifford_1_qubit_circuit(cls, num, basis_gates: Optional[Tuple[str, ...]] =
275
388
qc .z (0 )
276
389
277
390
if basis_gates :
278
- qc = _synthesize_clifford_circuit (qc , basis_gates )
391
+ qc = _synthesize_clifford_circuit (qc , basis_gates , synthesis_method = synthesis_method )
279
392
280
393
return qc
281
394
282
395
@classmethod
283
396
@lru_cache (maxsize = 11520 )
284
- def clifford_2_qubit_circuit (cls , num , basis_gates : Optional [Tuple [str , ...]] = None ):
397
+ def clifford_2_qubit_circuit (
398
+ cls ,
399
+ num ,
400
+ basis_gates : Optional [Tuple [str , ...]] = None ,
401
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]] = None ,
402
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
403
+ ):
285
404
"""Return the 2-qubit clifford circuit corresponding to `num`
286
405
where `num` is between 0 and 11519.
287
406
"""
288
407
qc = QuantumCircuit (2 , name = f"Clifford-2Q({ num } )" )
289
408
for layer , idx in enumerate (_layer_indices_from_num (num )):
290
409
if basis_gates :
291
- layer_circ = _transformed_clifford_layer (layer , idx , basis_gates )
410
+ layer_circ = _transformed_clifford_layer (
411
+ layer , idx , basis_gates , coupling_tuple , synthesis_method = synthesis_method
412
+ )
292
413
else :
293
414
layer_circ = _CLIFFORD_LAYER [layer ][idx ]
294
415
_circuit_compose (qc , layer_circ , qubits = (0 , 1 ))
@@ -578,13 +699,22 @@ def _clifford_2q_nums_from_2q_circuit(qc: QuantumCircuit) -> Iterable[Integral]:
578
699
]
579
700
580
701
581
- @lru_cache (maxsize = None )
702
+ @lru_cache (maxsize = 256 )
582
703
def _transformed_clifford_layer (
583
- layer : int , index : Integral , basis_gates : Tuple [str , ...]
704
+ layer : int ,
705
+ index : Integral ,
706
+ basis_gates : Tuple [str , ...],
707
+ coupling_tuple : Optional [Tuple [Tuple [int , int ]]],
708
+ synthesis_method : str = DEFAULT_SYNTHESIS_METHOD ,
584
709
) -> QuantumCircuit :
585
710
# Return the index-th quantum circuit of the layer translated with the basis_gates.
586
711
# The result is cached for speed.
587
- return _synthesize_clifford_circuit (_CLIFFORD_LAYER [layer ][index ], basis_gates )
712
+ return _synthesize_clifford_circuit (
713
+ _CLIFFORD_LAYER [layer ][index ],
714
+ basis_gates = basis_gates ,
715
+ coupling_tuple = coupling_tuple ,
716
+ synthesis_method = synthesis_method ,
717
+ )
588
718
589
719
590
720
def _num_from_layer_indices (triplet : Tuple [Integral , Integral , Integral ]) -> Integral :
0 commit comments