Sequential Quantum Gate Decomposer  v1.9.3
Powerful decomposition of general unitarias into one- and two-qubit gates gates
example_evolutionary.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """
3 Created on Fri Jun 26 14:42:56 2020
4 Copyright 2020 Peter Rakyta, Ph.D.
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 @author: Peter Rakyta, Ph.D.
19 """
20 
22 
23 
24 from squander import N_Qubit_Decomposition_adaptive
25 
26 
27 import numpy as np
28 from qiskit import QuantumCircuit
29 
30 
31 #Here we provide an example to use the SQUANDER package. The following python interface is accessible from version 1.8.0. In this example we use two optimization engines for the decomposition:
32 
33 # An evolutionary engine called AGENTS
34 # Second order gradient descend algorithm (BFGS)
35 
36 
37 # load the quantum circuit from a file andretrieve the unitary of the circuit
38 qc = QuantumCircuit.from_qasm_file( '../../benchmarks/IBM/alu-v4_37.qasm')
39 
40 
41 from qiskit import execute
42 from qiskit import Aer
43 import numpy.linalg as LA
44 
45 # test the decomposition of the matrix
46 
47 backend = Aer.get_backend('unitary_simulator')
48 
49 
50 job = execute(qc, backend)
51 
52 result = job.result()
53 
54 
55 Umtx = result.get_unitary(qc)
56 Umtx = np.asarray(Umtx)
57 Umtx = Umtx*np.exp(-1j*(qc.global_phase))
58 
59 
60 
61 
62 
63 # Firstly we construct a Python map to set hyper-parameters during the gate synthesis.
64 
65 # Python map containing hyper-parameters
66 config = { 'agent_lifetime':200,
67  'max_inner_iterations_agent': 100000,
68  'max_inner_iterations_compression': 100000,
69  'max_inner_iterations' : 10000,
70  'max_inner_iterations_final': 10000,
71  'Randomized_Radius': 0.3,
72  'randomized_adaptive_layers': 1,
73  'optimization_tolerance_agent': 1e-3,
74  'optimization_tolerance_': 1e-8}
75 
76 
77 # Next we initialize the decomposition class with the unitary Umtx to be decomposed.
78 
79 # creating a class to decompose the unitary
80 
81 cDecompose = N_Qubit_Decomposition_adaptive( Umtx.conj().T, config=config )
82 
83 
84 # The verbosity of the execution output can be controlled by the function call
85 
86 # setting the verbosity of the decomposition
87 
88 cDecompose.set_Verbose( 3 )
89 
90 
91 # We construct the initial trial gate structure for the optimization consisting of 2 levels of adaptive layer. (1 level is made of qubit_num*(qubit_num-1) two-qubit building blocks if all-to-all connectivity is assumed)
92 
93 # adding decomposing layers to the gate structure
94 
95 levels = 5
96 for idx in range(levels):
97  cDecompose.add_Adaptive_Layers()
98 
99 cDecompose.add_Finalyzing_Layer_To_Gate_Structure()
100 
101 
102 # We can construct an initial parameters set for the optimization by retrieving the number of free parameters. If the initial parameter set is not set, random parameters are used by default.
103 
104 # setting intial parameter set
105 
106 parameter_num = cDecompose.get_Parameter_Num()
107 parameters = np.zeros( (parameter_num,1), dtype=np.float64 )
108 cDecompose.set_Optimized_Parameters( parameters )
109 
110 
111 
112 # We can choose between several engines to solve the optimization problem. Here we use an evolutionary based algorithm named 'AGENTS'
113 
114 # setting optimizer
115 
116 cDecompose.set_Optimizer("AGENTS")
117 
118 
119 # The optimization process is started by the function call
120 
121 
122 
124 cDecompose.get_Initial_Circuit()
125 
126 
127 #The optimization process terminates by either reaching the tolerance 'optimization_tolerance_agent' or by reaching the maximal iteration number 'max_inner_iterations_agent', or if the engines identifies a convergence to a local minimum. The SQUANDER framework enables one to continue the optimization using a different engine. In particular we set a second order gradient descend method 'BFGS' In order to achieve the best performance one can play around with the hyper-parameters in the map 'config'. (Optimization strategy AGENTS is good in avoiding local minima or get through flat areas of the optimization landscape. Then a gradient descend method can be used for faster convergence toward a solution.)
128 
129 
130 # setting optimizer
131 
132 cDecompose.set_Optimizer("BFGS")
133 
134 
135 # continue the decomposition with a second optimizer method
136 
137 cDecompose.get_Initial_Circuit()
138 
139 
140 #After solving the optimization problem for the initial gate structure, we can initiate gate compression iterations. (This step can be omited.)
141 
142 # starting compression iterations
143 
144 cDecompose.Compress_Circuit()
145 
146 
147 #By finalizing the gate structure we replace the CRY gates with CNOT gates. (CRY gates with small rotation angle are approximately expressed with a single CNOT gate, so further optimization process needs to be initiated.)
148 
149 # finalize the gate structure (replace CRY gates with CNOT gates)
150 
151 cDecompose.Finalize_Circuit()
152 
153 
154 # Finally, we can retrieve the decomposed quantum circuit in QISKIT format.
155 
156 
157 print(' ')
158 print('Constructing quantum circuit:')
159 print(' ')
160 
161 quantum_circuit = cDecompose.get_Qiskit_Circuit()
162 
163 print(quantum_circuit)
164 
165 import numpy.linalg as LA
166 
167 
168 from squander import utils
169 decomposed_matrix = utils.get_unitary_from_qiskit_circuit( quantum_circuit )
170 product_matrix = np.dot(Umtx,decomposed_matrix.conj().T)
171 phase = np.angle(product_matrix[0,0])
172 product_matrix = product_matrix*np.exp(-1j*phase)
173 
174 product_matrix = np.eye(product_matrix.shape[0])*2 - product_matrix - product_matrix.conj().T
175 # the error of the decomposition
176 decomposition_error = (np.real(np.trace(product_matrix)))/2
177 
178 print('The error of the decomposition is ' + str(decomposition_error))
179 
180