2 Implementation to optimize wide circuits (i.e. circuits with many qubits) by partitioning the circuit into smaller partitions and redecompose the smaller partitions 11 from qiskit
import QuantumCircuit
13 from typing
import List, Callable
15 import multiprocessing
as mp
16 from multiprocessing
import Process, Pool
20 from squander.partitioning.partition
import (
32 Call to get the number of CNOT gates in the circuit 37 circ (Circuit) A squander circuit representation 42 Returns with the CNOT gate count 47 if not isinstance(circ, Circuit ):
48 raise Exception(
"The input parameters should be an instance of Squander Circuit")
50 gate_counts = circ.get_Gate_Nums()
52 return gate_counts.get(
'CNOT', 0)
63 Class implementing the optimization of wide circuits (i.e. circuits with many qubits) by 64 partitioning the circuit into smaller partitions and redecompose the smaller partitions 70 config.setdefault(
'strategy',
'TreeSearch')
71 config.setdefault(
'parallel', 0 )
72 config.setdefault(
'verbosity', 0 )
73 config.setdefault(
'tolerance', 1e-8 )
74 config.setdefault(
'test_subcircuits',
False )
75 config.setdefault(
'test_final_circuit',
True )
76 config.setdefault(
'max_partition_size', 3 )
79 strategy = config[
'strategy' ]
80 allowed_startegies = [
'TreeSearch',
'TabuSearch' ]
81 if not strategy
in allowed_startegies :
82 raise Exception(f
"The decomposition startegy should be either of {allowed_startegies}, got {startegy}.")
85 parallel = config[
'parallel' ]
86 allowed_parallel = [0, 1, 2 ]
87 if not parallel
in allowed_parallel :
88 raise Exception(f
"The parallel configuration should be either of {allowed_parallel}, got {parallel}.")
91 verbosity = config[
'verbosity' ]
92 if not isinstance( verbosity, int) :
93 raise Exception(f
"The verbosity parameter should be an integer.")
96 tolerance = config[
'tolerance' ]
97 if not isinstance( tolerance, float) :
98 raise Exception(f
"The tolerance parameter should be a float.")
101 test_subcircuits = config[
'test_subcircuits' ]
102 if not isinstance( test_subcircuits, bool) :
103 raise Exception(f
"The test_subcircuits parameter should be a bool.")
106 test_final_circuit = config[
'test_final_circuit' ]
107 if not isinstance( test_final_circuit, bool) :
108 raise Exception(f
"The test_final_circuit parameter should be a bool.")
112 max_partition_size = config[
'max_partition_size' ]
113 if not isinstance( max_partition_size, int) :
114 raise Exception(f
"The max_partition_size parameter should be an integer.")
125 Call to construct the wide quantum circuit from the partitions. 130 circs ( List[Circuit] ) A list of Squander circuits to be compared 132 parameter_arrs ( List[np.ndarray] ) A list of parameter arrays associated with the sqaunder circuits 136 Returns with the constructed circuit and the corresponding parameter array 141 if not isinstance( circs, list ):
142 raise Exception(
"First argument should be a list of squander circuits")
144 if not isinstance( parameter_arrs, list ):
145 raise Exception(
"Second argument should be a list of numpy arrays")
147 if len(circs) != len(parameter_arrs) :
148 raise Exception(
"The first two arguments should be of the same length")
155 wide_parameters = np.concatenate( parameter_arrs, axis=0 )
158 wide_circuit = Circuit( qbit_num )
161 wide_circuit.add_Circuit( circ )
164 assert wide_circuit.get_Parameter_Num() == wide_parameters.size, \
165 f
"Mismatch in the number of parameters: {wide_circuit.get_Parameter_Num()} vs {wide_parameters.size}" 169 return wide_circuit, wide_parameters
175 Call to run the decomposition of a given unitary Umtx, typically associated with the circuit 176 partition to be optimized 181 Umtx (np.ndarray) A complex typed unitary to be decomposed 186 Returns with the the decoposed circuit structure and with the corresponding gate parameters 190 strategy = config[
"strategy"]
191 if strategy ==
"TreeSearch":
193 elif strategy ==
"TabuSearch":
196 raise Exception(f
"Unsupported decomposition type: {strategy}")
199 tolerance = config[
"tolerance"]
202 cDecompose.set_Verbose( config[
"verbosity"] )
203 cDecompose.set_Cost_Function_Variant( 3 )
204 cDecompose.set_Optimization_Tolerance( tolerance )
208 cDecompose.set_Optimizer(
"BFGS" )
211 cDecompose.Start_Decomposition()
214 squander_circuit = cDecompose.get_Circuit()
215 parameters = cDecompose.get_Optimized_Parameters()
218 print(
"Decomposition error: ", cDecompose.get_Decomposition_Error() )
220 if tolerance < cDecompose.get_Decomposition_Error():
224 return squander_circuit, parameters
229 def CompareAndPickCircuits( circs: List[Circuit], parameter_arrs: [List[np.ndarray]], metric : Callable[ [Circuit], int ] = CNOTGateCount ) -> Circuit:
231 Call to pick the most optimal circuit corresponding a specific metric. Looks for the circuit 232 with the minimal metric value. 237 circs ( List[Circuit] ) A list of Squander circuits to be compared 239 parameter_arrs ( List[np.ndarray] ) A list of parameter arrays associated with the sqaunder circuits 241 metric (optional) The metric function to decide which input circuit is better. 246 Returns with the chosen circuit and the corresponding parameter array 251 if not isinstance( circs, list ):
252 raise Exception(
"First argument should be a list of squander circuits")
254 if not isinstance( parameter_arrs, list ):
255 raise Exception(
"Second argument should be a list of numpy arrays")
257 if len(circs) != len(parameter_arrs) :
258 raise Exception(
"The first two arguments should be of the same length")
261 metrics = [metric( circ )
for circ
in circs]
263 metrics = np.array( metrics )
265 min_idx = np.argmin( metrics )
267 return circs[ min_idx ], parameter_arrs[ min_idx ]
274 Implements an asynchronous process to decompose a unitary associated with a partition in a large 280 circ ( Circuit ) A subcircuit representing a partition 282 parameters ( np.ndarray ) A parameter array associated with the input circuit 288 qbit_num_orig_circuit = subcircuit.get_Qbit_Num()
289 involved_qbits = subcircuit.get_Qbits()
290 qbit_num = len( involved_qbits )
294 for idx
in range( len(involved_qbits) ):
295 qbit_map[ involved_qbits[idx] ] = idx
298 remapped_subcircuit = subcircuit.Remap_Qbits( qbit_map, qbit_num )
301 unitary = remapped_subcircuit.get_Matrix( subcircuit_parameters )
304 decomposed_circuit, decomposed_parameters = qgd_Wide_Circuit_Optimization.DecomposePartition( unitary, config )
306 if decomposed_circuit
is None:
307 decomposed_circuit = subcircuit
308 decomposed_parameters = subcircuit_parameters
312 inverse_qbit_map = {}
313 for key, value
in qbit_map.items():
314 inverse_qbit_map[ value ] = key
317 new_subcircuit = decomposed_circuit.Remap_Qbits( inverse_qbit_map, qbit_num_orig_circuit )
320 if config[
"test_subcircuits"]:
321 CompareCircuits( subcircuit, subcircuit_parameters, new_subcircuit, decomposed_parameters, parallel=config[
"parallel"] )
325 new_subcircuit = new_subcircuit.get_Flat_Circuit()
327 return new_subcircuit, decomposed_parameters
334 Call to optimize a wide circuit (i.e. circuits with many qubits) by 335 partitioning the circuit into smaller partitions and redecompose the smaller partitions 340 circ ( Circuit ) A circuit to be partitioned 342 parameters ( np.ndarray ) A parameter array associated with the input circuit 346 Returns with the optimized circuit and the corresponding parameter array 353 qbit_num_orig_circuit = partitined_circuit.get_Qbit_Num()
356 subcircuits = partitined_circuit.get_Gates()
362 optimized_subcircuits = [
None] * len(subcircuits)
365 optimized_parameter_list = [
None] * len(subcircuits)
368 async_results = [
None] * len(subcircuits)
370 with Pool(processes=mp.cpu_count())
as pool:
373 for partition_idx, subcircuit
in enumerate( subcircuits ):
377 start_idx = subcircuit.get_Parameter_Start_Index()
378 end_idx = subcircuit.get_Parameter_Start_Index() + subcircuit.get_Parameter_Num()
379 subcircuit_parameters = parameters[ start_idx:end_idx ]
384 callback_fnc =
lambda x : self.
CompareAndPickCircuits( [subcircuit, x[0]], [subcircuit_parameters, x[1]] )
392 for partition_idx, subcircuit
in enumerate( subcircuits ):
394 new_subcircuit, new_parameters = async_results[partition_idx].get( timeout = 1800 )
397 if subcircuit != new_subcircuit: 399 print( "original subcircuit: ", subcircuit.get_Gate_Nums()) 400 print( "reoptimized subcircuit: ", new_subcircuit.get_Gate_Nums()) 403 optimized_subcircuits[ partition_idx ] = new_subcircuit
404 optimized_parameter_list[ partition_idx ] = new_parameters
411 print(
"original circuit: ", partitined_circuit.get_Gate_Nums())
412 print(
"reoptimized circuit: ", wide_circuit.get_Gate_Nums())
416 if self.
config[
"test_final_circuit"]:
417 CompareCircuits( partitined_circuit, parameters, wide_circuit, wide_parameters )
420 return wide_circuit, wide_parameters
def __init__(self, config)
def ConstructCircuitFromPartitions
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
def PartitionDecompositionProcess
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
def get_Qbit_Num(self)
Call to get the number of qubits in the circuit.
def CompareAndPickCircuits