Sequential Quantum Gate Decomposer  v1.9.3
Powerful decomposition of general unitarias into one- and two-qubit gates gates
16_qubit_trained_circuit_VQE.py
Go to the documentation of this file.
1 from scipy.stats import unitary_group
2 import numpy as np
3 from squander import Variational_Quantum_Eigensolver
4 from squander import utils as utils
5 import time
6 import sys
7 import scipy as sp
8 
9 from networkx.generators.random_graphs import random_regular_graph
10 from qiskit.quantum_info import SparsePauliOp
11 np.random.seed(31415)
12 np.set_printoptions(linewidth=200)
13 
14 import pickle
15 
16 
17 
18 topology = []
19 
21 
22  topology = random_regular_graph(3,n,seed=31415).edges
23 
24  oplist = []
25  for i in topology:
26  oplist.append(("XX",[i[0],i[1]],1))
27  oplist.append(("YY",[i[0],i[1]],1))
28  oplist.append(("ZZ",[i[0],i[1]],1))
29  for i in range(n):
30  oplist.append(("Z",[i],1))
31  return SparsePauliOp.from_sparse_list(oplist,num_qubits=n).to_matrix(True)
32 
33 
34 
36  topology = random_regular_graph(3,n,seed=31415).edges
37  oplist = []
38  for i in topology:
39  oplist.append(("XX",[i[0],i[1]],1))
40  oplist.append(("YY",[i[0],i[1]],1))
41  oplist.append(("ZZ",[i[0],i[1]],1))
42  for i in range(n):
43  oplist.append(("Z",[i],1))
44  return SparsePauliOp.from_sparse_list(oplist,num_qubits=n).to_matrix(True)
45 
46 
47 
48 
49 def generate_circuit_ansatz( layers, inner_blocks, qbit_num):
50 
51  from squander import Circuit
52 
53  ret = Circuit( qbit_num )
54 
55 
56  for layer_idx in range(layers) :
57 
58 
59  for int in range(inner_blocks):
60  # we organize gates into blocks that can be merged and thus faster to execute
61  block1 = Circuit( qbit_num )
62  block2 = Circuit( qbit_num )
63 
64  block1.add_RZ( 1 )
65  block1.add_RY( 1 )
66  block1.add_RZ( 1 )
67 
68  block2.add_RZ( 0 )
69  block2.add_RY( 0 )
70  block2.add_RZ( 0 )
71 
72  # add the partitions to the circuit
73  ret.add_Circuit( block1 )
74  ret.add_Circuit( block2 )
75 
76  ret.add_CNOT(1,0)
77 
78 
79  for control_qbit in range(1, qbit_num-1, 2):
80 
81  if control_qbit+2<qbit_num :
82 
83  for int in range(inner_blocks):
84 
85 
86  # we organize gates into blocks that can be merged and thus faster to execute
87  block1 = Circuit( qbit_num )
88  block2 = Circuit( qbit_num )
89 
90  block1.add_RZ( control_qbit+1 )
91  block1.add_RY( control_qbit+1 )
92  block1.add_RZ( control_qbit+1 )
93 
94  block2.add_RZ( control_qbit+2 )
95  block2.add_RY( control_qbit+2 )
96  block2.add_RZ( control_qbit+2 )
97 
98  # add the partitions to the circuit
99  ret.add_Circuit( block1 )
100  ret.add_Circuit( block2 )
101 
102  ret.add_CNOT(control_qbit+2,control_qbit+1);
103 
104 
105 
106  for int in range(inner_blocks):
107 
108 
109  # we organize gates into blocks that can be merged and thus faster to execute
110  block1 = Circuit( qbit_num )
111  block2 = Circuit( qbit_num )
112 
113  block1.add_RZ( control_qbit+1 )
114  block1.add_RY( control_qbit+1 )
115  block1.add_RZ( control_qbit+1 )
116 
117  block2.add_RZ( control_qbit )
118  block2.add_RY( control_qbit )
119  block2.add_RZ( control_qbit )
120 
121  # add the partitions to the circuit
122  ret.add_Circuit( block1 )
123  ret.add_Circuit( block2 )
124 
125  ret.add_CNOT(control_qbit+1,control_qbit);
126 
127 
128  return ret
129 
130 
131 # The number of circuit layers
132 layers = 1000
133 
134 # the number of subblocks in a single layer
135 inner_blocks = 1
136 
137 # The number of qubits
138 qbit_num = 16
139 
140 
141 
142 # generate the Hamiltonian
143 Hamiltonian = generate_hamiltonian_tmp( qbit_num )
144 
145 
146 # generate custom circuit ansatz
147 squander_circuit = generate_circuit_ansatz( layers, inner_blocks, qbit_num)
148 
149 # obtain the groud state energy of the Hamitonian
150 [eigvals, eigvecs] = sp.sparse.linalg.eigs( Hamiltonian, k=10, which='SR' )
151 eigval = np.real(eigvals[0])
152 eigvec = eigvecs[:,0]
153 
154 print( 'The target eigenvalue is: ', eigval )
155 
156 
157 # generate configuration dictionary for the solver (if permforming further training)
158 config = {"max_inner_iterations":800,
159  "batch_size": 128,
160  "convergence_length": 20}
161 
162 # initiate the VQE object with the Hamiltonian
163 VQE_Heisenberg = Variational_Quantum_Eigensolver(Hamiltonian, qbit_num, config)
164 
165 # set the optimization engine to agents
166 VQE_Heisenberg.set_Optimizer("COSINE")
167 
168 # set the ansatz variant (U3 rotations and CNOT gates)
169 VQE_Heisenberg.set_Gate_Structure( squander_circuit )
170 
171 # load pretrained parameters
172 param_num = VQE_Heisenberg.get_Parameter_Num()
173 print('The number of free parameters is: ', str(param_num) )
174 
175 parameters = np.load( 'COSINE_1000x1_layers_Heisenberg_16_3point_zero_init_3overlap0.8602661822824491.npy' )
176 
177 VQE_Heisenberg.set_Optimized_Parameters(parameters)
178 
179 #VQE_Heisenberg.set_Initial_State( eigvec )
180 
181 
182 # calculate the entropy of the exact ground state
183 page_entropy = 2 * np.log(2.0) - 1.0/( pow(2, qbit_num-2*2+1) )
184 entropy_exact_gs = VQE_Heisenberg.get_Second_Renyi_Entropy( parameters=np.array([]), qubit_list=[0,1], input_state=eigvec )
185 normalized_entropy_exact_gs = entropy_exact_gs/page_entropy
186 print('The normalized entropy of the exact ground state evaluated on qubits 0 and 1 is:', normalized_entropy_exact_gs)
187 print(' ')
188 print(' ')
189 print(' ', flush=True)
190 
191 
192 # retrive the current VQE energy after max_inner_iterations iterations
193 VQE_energy = VQE_Heisenberg.Optimization_Problem( parameters )
194 
195 # calculate the Renyi entropy after max_inner_iterations iterations on the subsystem made of the 0-th and the 1st qubits
196 qubit_list = [0,1]
197 
198 page_entropy = len(qubit_list) * np.log(2.0) - 1.0/( pow(2, qbit_num-2*len(qubit_list)+1) )
199 entropy = VQE_Heisenberg.get_Second_Renyi_Entropy( parameters=parameters, qubit_list=qubit_list )
200 normalized_entropy = entropy/page_entropy
201 
202 
203 print('Current VQE energy: ', VQE_energy, ' normalized entropy: ', normalized_entropy)
204 
205 
206 initial_state = np.zeros( (1 << qbit_num), dtype=np.complex128 )
207 initial_state[0] = 1.0 + 0j
208 
209 
210 state_to_transform = initial_state.copy()
211 VQE_Heisenberg.apply_to( parameters, state_to_transform );
212 # or equivalently: squander_circuit.apply_to( parameters, state_to_transform );
213 
214 
215 overlap = state_to_transform.transpose().conjugate() @ eigvecs
216 overlap_norm = np.real(overlap * overlap.conjugate())
217 
218 for idx in range( overlap_norm.size) :
219  print('The overlap integral with the exact eigenstates of energy ', eigvals[idx], ' is: ', overlap_norm[idx] )
220 
221 print('The sum of the calculated overlaps: ', np.sum(overlap_norm ) )
222 
223 
224 
def generate_circuit_ansatz(layers, inner_blocks, qbit_num)