4 Created on Tue Jun 30 15:44:26 2020 5 Copyright 2020 Peter Rakyta, Ph.D. 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 11 http://www.apache.org/licenses/LICENSE-2.0 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 19 You should have received a copy of the GNU General Public License 20 along with this program. If not, see http://www.gnu.org/licenses/. 22 @author: Peter Rakyta, Ph.D. 31 from squander.decomposition.qgd_N_Qubit_Decomposition_adaptive_Wrapper
import qgd_N_Qubit_Decomposition_adaptive_Wrapper
48 def __init__( self, Umtx, level_limit_max=8, level_limit_min=0, topology=None, config={}, accelerator_num=0 ):
51 self.qbit_num =
int(round( np.log2( len(Umtx) ) ))
55 topology_validated = list()
56 if isinstance(topology, list)
or isinstance(topology, tuple):
58 if isinstance(item, tuple)
and len(item) == 2:
59 item_validated = (np.intc(item[0]), np.intc(item[1]))
60 topology_validated.append(item_validated)
62 print(
"Elements of topology should be two-component tuples (int, int)")
64 elif topology ==
None:
67 print(
"Input parameter topology should be a list of (int, int) describing the connected qubits in the topology")
72 if not(
type(config)
is dict):
73 print(
"Input parameter config should be a dictionary describing the following hyperparameters:")
77 super().
__init__(Umtx, self.qbit_num, level_limit_max, level_limit_min, topology=topology_validated, config=config, accelerator_num=accelerator_num)
138 from squander
import Qiskit_IO
140 squander_circuit = self.get_Circuit()
141 parameters = self.get_Optimized_Parameters()
143 return Qiskit_IO.get_Qiskit_Circuit( squander_circuit, parameters )
157 circuit = cirq.Circuit()
160 q = cirq.LineQubit.range(self.qbit_num)
163 gates = self.get_Gates()
166 for idx
in range(len(gates)-1, -1, -1):
170 if gate.get(
"type") ==
"CNOT":
172 circuit.append(cirq.CNOT(q[self.qbit_num-1-gate.get(
"control_qbit")], q[self.qbit_num-1-gate.get(
"target_qbit")]))
174 if gate.get(
"type") ==
"CRY":
176 print(
"CRY gate needs to be implemented")
178 elif gate.get(
"type") ==
"CZ":
180 circuit.append(cirq.CZ(q[self.qbit_num-1-gate.get(
"control_qbit")], q[self.qbit_num-1-gate.get(
"target_qbit")]))
182 elif gate.get(
"type") ==
"CH":
184 circuit.append(cirq.CH(q[self.qbit_num-1-gate.get(
"control_qbit")], q[self.qbit_num-1-gate.get(
"target_qbit")]))
186 elif gate.get(
"type") ==
"SYC":
188 circuit.append(cirq.google.SYC(q[self.qbit_num-1-gate.get(
"control_qbit")], q[self.qbit_num-1-gate.get(
"target_qbit")]))
190 elif gate.get(
"type") ==
"U3":
191 print(
"Unsupported gate in the Cirq export: U3 gate")
194 elif gate.get(
"type") ==
"RX":
196 circuit.append(cirq.rx(gate.get(
"Theta")).on(q[self.qbit_num-1-gate.get(
"target_qbit")]))
198 elif gate.get(
"type") ==
"RY":
200 circuit.append(cirq.ry(gate.get(
"Theta")).on(q[self.qbit_num-1-gate.get(
"target_qbit")]))
202 elif gate.get(
"type") ==
"RZ":
204 circuit.append(cirq.rz(gate.get(
"Phi")).on(q[self.qbit_num-1-gate.get(
"target_qbit")]))
206 elif gate.get(
"type") ==
"X":
208 circuit.append(cirq.x(q[self.qbit_num-1-gate.get(
"target_qbit")]))
210 elif gate.get(
"type") ==
"Y":
212 circuit.append(cirq.y(q[self.qbit_num-1-gate.get(
"target_qbit")]))
214 elif gate.get(
"type") ==
"Z":
216 circuit.append(cirq.z(q[self.qbit_num-1-gate.get(
"target_qbit")]))
219 elif gate.get(
"type") ==
"SX":
221 circuit.append(cirq.sx(q[self.qbit_num-1-gate.get(
"target_qbit")]))
232 from qiskit
import QuantumCircuit, transpile
233 from qiskit.circuit
import ParameterExpression
234 from qgd_python.gates.qgd_Circuit_Wrapper
import qgd_Circuit_Wrapper
236 qc = transpile(qc_in, optimization_level=0, basis_gates=[
'cz',
'u3'], layout_method=
'sabre')
238 print(
'Gate counts in teh imported Qiskit transpiled quantum circuit:', qc.count_ops())
243 q_register = qc.qubits
246 register_size = qc.num_qubits
249 single_qubit_gates = dict()
250 for idx
in range(register_size):
251 single_qubit_gates[idx] = []
254 two_qubit_gates = list()
258 optimized_parameters = list()
262 name = gate.operation.name
267 qubit = q_register.index( qubits[0] )
268 single_qubit_gates[qubit].append( {
'params': gate.operation.params,
'type':
'u3'} )
274 qubit0 = q_register.index( qubits[0] )
275 qubit1 = q_register.index( qubits[1] )
278 two_qubit_gate = {
'type':
'cz',
'qubits': [qubit0, qubit1]}
286 if len(single_qubit_gates[qubit0])>0:
287 gate0 = single_qubit_gates[qubit0][0]
288 single_qubit_gates[qubit0].pop(0)
290 if gate0[
'type'] ==
'u3':
291 Layer.add_U3( qubit0,
True,
True,
True )
292 params = gate0[
'params']
295 optimized_parameters = optimized_parameters + [float(param)]
296 optimized_parameters[-1] = optimized_parameters[-1]/2
299 if len(single_qubit_gates[qubit1])>0:
300 gate1 = single_qubit_gates[qubit1][0]
301 single_qubit_gates[qubit1].pop(0)
303 if gate1[
'type'] ==
'u3':
304 Layer.add_U3( qubit1,
True,
True,
True )
305 params = gate1[
'params']
308 optimized_parameters = optimized_parameters + [float(param)]
309 optimized_parameters[-1] = optimized_parameters[-1]/2
315 Layer.add_RX( qubit0 )
316 Layer.add_adaptive( qubit0, qubit1 )
317 Layer.add_RZ( qubit1 )
318 Layer.add_RX( qubit0 )
319 optimized_parameters = optimized_parameters + [np.pi/4, np.pi/2, -np.pi/2, -np.pi/4]
322 Circuit_ret.add_Circuit( Layer )
330 for qubit
in range(register_size):
332 gates = single_qubit_gates[qubit]
335 if gate[
'type'] ==
'u3':
336 Layer.add_U3( qubit,
True,
True,
True )
337 params = gate[
'params']
340 optimized_parameters = optimized_parameters + [float(param)]
341 optimized_parameters[-1] = optimized_parameters[-1]/2
343 Circuit_ret.add_Circuit( Layer )
345 optimized_parameters = np.asarray(optimized_parameters, dtype=np.float64)
348 self.set_Gate_Structure(Circuit_ret)
350 self.set_Optimized_Parameters( np.flip(optimized_parameters,0) )
359 if not isinstance(Gate_structure, qgd_Circuit) :
360 raise Exception(
"Input parameter Gate_structure should be a an instance of Circuit")
499 if parameters
is None:
500 print(
"get_Matrix: arary of input parameters is None")
563 if parameters
is None:
564 print(
"Optimization_Problem: array of input parameters is None")
579 if parameters
is None:
580 print(
"Optimization_Problem: array of input parameters is None")
586 grad = grad.reshape( (-1,))
596 if parameters
is None:
597 print(
"Optimization_Problem_Combined: array of input parameters is None")
603 grad = grad.reshape( (-1,))
605 return cost_function, grad
645 qbit_num = self.get_Qbit_Num()
647 qubit_list_validated = list()
648 if isinstance(qubit_list, list)
or isinstance(qubit_list, tuple):
649 for item
in qubit_list:
650 if isinstance(item, int):
651 qubit_list_validated.append(item)
652 qubit_list_validated = list(set(qubit_list_validated))
654 print(
"Elements of qbit_list should be integers")
656 elif qubit_list ==
None:
657 qubit_list_validated = [ x
for x
in range(qbit_num) ]
660 print(
"Elements of qbit_list should be integers")
664 if parameters
is None:
665 print(
"get_Second_Renyi_entropy: array of input parameters is None")
669 if input_state
is None:
670 matrix_size = 1 << qbit_num
671 input_state = np.zeros( (matrix_size,1) )
def set_Optimized_Parameters(self, new_params)
Call to set the parameters which are used as a starting point in the optimization.
def set_Trace_Offset(self, trace_offset=0)
Call to set the trace offset used in the cost function.
def get_Initial_Circuit(self)
Wrapper function to call the get_initial_circuit method of C++ class N_Qubit_Decomposition.
def export_Unitary(self, filename)
Call to export unitary matrix to binary file.
A QGD Python interface class for the Gates_Block.
def get_Unitary(self)
Call to get unitary matrix.
def Optimization_Problem_Grad(self, parameters=None)
Call to evaluate the gradient components.
def set_Gate_Structure(self, Gate_structure)
Call to set custom gate structure to used in the decomposition.
def set_Global_Phase(self, new_global_phase)
Call to set global phase.
def get_Global_Phase(self)
Call to get global phase.
def set_Max_Iterations(self, max_iters)
Call to set the maximum number of iterations for each optimization loop.
def apply_Imported_Gate_Structure(self)
Call to apply the imported gate structure on the unitary.
def set_Randomized_Radius(self, radius)
Call to set the radius in which randomized parameters are generated around the current minimum duting...
def add_Layer_To_Imported_Gate_Structure(self)
Call to add an adaptive layer to the gate structure previously imported gate structure.
def get_Matrix(self, parameters=None)
Call to retrieve the unitary of the circuit.
def List_Gates(self)
Call to print the gates decomposing the initial unitary.
def set_Project_Name(self, project_name_new)
Call to set the name of the SQUANDER project.
def get_Project_Name(self)
Call to get the name of the SQUANDER project.
def set_Unitary(self, Umtx_arr)
Call to set unitary matrix from a numpy array.
def Start_Decomposition(self)
Wrapper function to call the start_decomposition method of C++ class N_Qubit_Decomposition.
def Reorder_Qubits(self, qbit_list)
Call to reorder the qubits in the matrix of the gate.
def set_Iteration_Threshold_of_Randomization(self, threshold=2500)
Call to set the threshold value for the count of interations, above which the parameters are randomiz...
def add_Adaptive_Layers(self)
Call to add adaptive layers to the gate structure stored by the class.
def add_Finalyzing_Layer_To_Gate_Structure(self)
Call to add finalyzing layer (single qubit rotations on all of the qubits) to the gate structure...
def get_Second_Renyi_Entropy(self, parameters=None, input_state=None, qubit_list=None)
Call to get the second Rényi entropy.
def get_Qiskit_Circuit(self)
Export the unitary decomposition into Qiskit format.
def apply_Global_Phase_Factor(self)
Call to apply global phase on Unitary matrix.
def set_Optimization_Tolerance(self, tolerance)
Call to set the error tolerance of the decomposition.
def Compress_Circuit(self)
Wrapper function to call the compress_circuit method of C++ class N_Qubit_Decomposition.
def get_Parameter_Num(self)
Call to get the number of free parameters in the gate structure used for the decomposition.
def get_Trace_Offset(self)
Call to get the trace offset used in the cost function.
def Optimization_Problem(self, parameters=None)
Call to evaluate the cost function.
def get_Qbit_Num(self)
Call to get the number of qubits in the circuit.
A QGD Python interface class for the decomposition of N-qubit unitaries into U3 and CNOT gates...
def set_Unitary_From_Binary(self, filename)
Call to set unitary matrix from a binary file created from SQUANDER.
def get_Cirq_Circuit(self)
Export the unitary decomposition into Qiskit format.
def set_Cost_Function_Variant(self, costfnc=0)
Call to set the optimizer used in the gate synthesis process.
def __init__(self, Umtx, level_limit_max=8, level_limit_min=0, topology=None, config={}, accelerator_num=0)
Constructor of the class.
def get_Optimized_Parameters(self)
Call to get the optimized parameters set in numpy array.
def Optimization_Problem_Combined(self, parameters=None)
Call to evaluate the cost function and the gradient components.
def add_Gate_Structure_From_Binary(self, filename)
Call to append custom layers to the gate structure that are intended to be used in the decomposition ...
def get_Num_of_Iters(self)
Call to get the number of iterations.
def set_Optimizer(self, optimizer="BFGS")
Call to set the optimizer used in the gate synthesis process.
def set_Gate_Structure_From_Binary(self, filename)
Call to set custom layers to the gate structure that are intended to be used in the decomposition fro...
Type definition of the qgd_Circuit_Wrapper Python class of the qgd_Circuit_Wrapper module...
def Finalize_Circuit(self)
Wrapper function to call the finalize_circuit method of C++ class N_Qubit_Decomposition.
def get_Decomposition_Error(self)
Call to get the error of the decomposition.
def get_Circuit(self)
Call to retrieve the incorporated quantum circuit (Squander format)
def import_Qiskit_Circuit(self, qc_in)
Call to import initial quantum circuit in QISKIT format to be further comporessed.