Sequential Quantum Gate Decomposer  v1.9.3
Powerful decomposition of general unitarias into one- and two-qubit gates gates
test_gates.py
Go to the documentation of this file.
1 '''
2 Copyright 2020 Peter Rakyta, Ph.D.
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see http://www.gnu.org/licenses/.
18 '''
19 
20 import inspect
21 
22 
23 from squander.utils import get_unitary_from_qiskit_circuit
24 from scipy.stats import unitary_group
25 import numpy as np
26 
27 import qiskit
28 qiskit_version = qiskit.version.get_version_info()
29 
30 from qiskit import QuantumCircuit
31 def make_u2(self, phi, lam, target_qbit):
32  """
33  This function is used to add a U2 gate to the circuit.
34  It is a workaround for the fact that Qiskit does not have a U2 gate.
35  """
36  self.u( np.pi/2.0, phi, lam, target_qbit )
37 QuantumCircuit.u2 = make_u2
38 import qiskit_aer as Aer
39 
40 if qiskit_version[0] == '1' or qiskit_version[0] == '2':
41  from qiskit import transpile
42 else :
43  from qiskit import execute
44 
45 
46 
47 
49  """This is a test class of the python iterface to the gates of the QGD package"""
50 
51 
52 
53  def test_SYC_creation(self):
54  r"""
55  This method is called by pytest.
56  Test to create an instance of CH gate.
57 
58  """
59 
60  from squander import SYC
61 
62 
63  # number of qubits
64  qbit_num = 3
65 
66  # target qbit
67  target_qbit = 2
68 
69  # control_qbit
70  control_qbit = 1
71 
72  # creating an instance of the C++ class
73  SYC_gate = SYC( qbit_num, target_qbit, control_qbit )
74 
75 
77  r"""
78  This method is called by pytest.
79  Test to create an instance of Operation_Block class.
80 
81  """
82 
83  from squander.gates.qgd_Circuit_Wrapper import qgd_Circuit_Wrapper
84 
85  # number of qubits
86  qbit_num = 3
87 
88 
89  # creating an instance of the C++ class
90  cCircuit = qgd_Circuit_Wrapper( qbit_num )
91 
92 
93 
95  r"""
96  This method is called by pytest.
97  Test to add operations to a block of gates
98 
99  """
100 
101  from squander.gates.qgd_Circuit_Wrapper import qgd_Circuit_Wrapper
102 
103  # number of qubits
104  qbit_num = 3
105 
106 
107  # creating an instance of the C++ class
108  cCircuit = qgd_Circuit_Wrapper( qbit_num )
109 
110 
111 
112  # target qbit
113  target_qbit = 0
114 
115  # add U3 gate to the block
116  cCircuit.add_U3( target_qbit )
117 
118 
119  # target qbit
120  target_qbit = 0
121 
122  # control_qbit
123  control_qbit = 1
124 
125  # add CNOT gate to the block
126  cCircuit.add_CNOT( target_qbit, control_qbit )
127 
128 
129 
130 
131 
133  r"""
134  This method is called by pytest.
135  Test to add operations to a block of gates
136 
137  """
138 
139  from squander.gates.qgd_Circuit_Wrapper import qgd_Circuit_Wrapper
140 
141  # number of qubits
142  qbit_num = 3
143 
144 
145  # creating an instance of the C++ class
146  layer = qgd_Circuit_Wrapper( qbit_num )
147 
148  # target qbit
149  target_qbit = 0
150 
151  # add U3 gate to the block
152  layer.add_U3( target_qbit )
153 
154 
155 
156  # target qbit
157  target_qbit = 0
158 
159  # control_qbit
160  control_qbit = 1
161 
162  # add CNOT gate to the block
163  layer.add_CNOT( control_qbit, target_qbit )
164 
165 
166  # add RX gate to the block
167  layer.add_RX( target_qbit )
168 
169  # creating an instance of the C++ class
170  cCircuit = qgd_Circuit_Wrapper( qbit_num )
171 
172  # add inner operation block to the outher operation block
173  cCircuit.add_Circuit( layer )
174 
175 
176 
177  def perform_gate_matrix_testing( self, gate_obj ):
178  """
179  This method is called to test the individual gates
180  Test by comparing the gate matrix of squander gate to Qiskit implementation
181 
182  """
183 
184  is_controlled_gate = (len(gate_obj.__name__) > 1) and (gate_obj.__name__[0] == 'C')
185 
186  for qbit_num in range(2,7):
187 
188  # target qbit
189  target_qbit = qbit_num-2
190 
191  # creating an instance of the C++ class
192  if not is_controlled_gate:
193  # single qbit gate
194  squander_gate = gate_obj( qbit_num, target_qbit )
195  elif is_controlled_gate:
196  # gate with control qbit
197  control_qbit = qbit_num-1
198  squander_gate = gate_obj( qbit_num, target_qbit, control_qbit )
199 
200 
201 
202 
203  #SQUANDER
204 
205  # get the gate matrix
206 
207  # determine the number of input arguments of the get_Matrix function
208  parameter_num = squander_gate.get_Parameter_Num()
209  if parameter_num == 0:
210  gate_matrix_squander = squander_gate.get_Matrix( )
211  elif parameter_num > 0: # Inconsistent use of tabs and spaces in indentation
212  parameters = ( np.random.rand( parameter_num )*2-1.0 ) * np.pi
213  gate_matrix_squander = squander_gate.get_Matrix( parameters )
214 
215 
216  #QISKIT
217 
218  # Create a Quantum Circuit acting on the q register
219  circuit = QuantumCircuit(qbit_num)
220 
221  # Add the gate
222  gate_name = gate_obj.__name__
223 
224  #special cases:
225  if gate_name == "U3":
226  gate_name = "u"
227 
228  elif gate_name == "U1":
229  gate_name = "p"
230 
231  elif gate_name == "CNOT":
232  gate_name = "cx"
233 
234 
235  gate_name = gate_name.lower()
236 
237 
238  gate_adding_fnc = getattr(circuit, gate_name )
239  if not is_controlled_gate and parameter_num == 0:
240  # add parameter-free single qbit gate to Qiskit circuit
241  gate_adding_fnc(target_qbit)
242 
243  elif not is_controlled_gate and parameter_num > 0:
244  # add single qbit gate to Qiskit circuit
245  parameters_QISKIT = list(parameters)
246 
247  #Squander uses half of the theta function for numerical performance
248  if gate_name != "p" and gate_name != "u2":
249  parameters_QISKIT[0] = parameters_QISKIT[0]*2
250  gate_adding_fnc( *parameters_QISKIT, target_qbit)
251 
252  elif is_controlled_gate and parameter_num == 0:
253  # add parameter-free two-qbit controlled gate to Qiskit circuit
254  control_qbit = qbit_num-1
255  gate_adding_fnc(control_qbit, target_qbit)
256 
257  elif is_controlled_gate and parameter_num > 0:
258  # add parameter-free two-qbit controlled gate to Qiskit circuit
259  parameters_QISKIT = list(parameters)
260 
261  #Squander uses half of the theta function for numerical performance
262  parameters_QISKIT[0] = parameters_QISKIT[0]*2
263  gate_adding_fnc( *parameters_QISKIT, control_qbit, target_qbit)
264 
265 
266 
267  # the unitary matrix from the result object
268  gate_matrix_qiskit = get_unitary_from_qiskit_circuit( circuit )
269  gate_matrix_qiskit = np.asarray(gate_matrix_qiskit)
270 
271  #the difference between the SQUANDER and the qiskit result
272  delta_matrix=gate_matrix_squander-gate_matrix_qiskit
273 
274  # compute norm of matrix
275  error=np.linalg.norm(delta_matrix)
276 
277  #print("Get_matrix: The difference between the SQUANDER and the qiskit result is: " , np.around(error,2))
278  assert( error < 1e-3 )
279 
280 
281  def perform_gate_apply_to_testing( self, gate_obj ):
282  """
283  This method is called to test the individual gates
284  Test by comparing the effect of a gate on an input state
285  by squander gate to Qiskit implementation
286 
287  """
288 
289  is_controlled_gate = (len(gate_obj.__name__) > 1) and (gate_obj.__name__[0] == 'C')
290 
291  for qbit_num in range(2,7):
292 
293  # target qbit
294  target_qbit = qbit_num-2
295 
296  # creating an instance of the C++ class
297  if not is_controlled_gate:
298  # single qbit gate
299  squander_gate = gate_obj( qbit_num, target_qbit )
300  elif is_controlled_gate:
301  # gate with control qbit
302  control_qbit = qbit_num-1
303  squander_gate = gate_obj( qbit_num, target_qbit, control_qbit )
304 
305 
306  # matrix size of the unitary
307  matrix_size = 1 << qbit_num #pow(2, qbit_num )
308 
309  initial_state_real = np.random.uniform(-1.0,1.0, (matrix_size,) )
310  initial_state_imag = np.random.uniform(-1.0,1.0, (matrix_size,) )
311  initial_state = initial_state_real + initial_state_imag*1j
312  initial_state = initial_state/np.linalg.norm(initial_state)
313 
314 
315 
316 
317 
318  #SQUANDER
319 
320  state_squander = initial_state.copy()
321 
322 
323  # determine the number of input arguments of the get_Matrix function
324  parameter_num = squander_gate.get_Parameter_Num()
325  if parameter_num == 0:
326  squander_gate.apply_to( state_squander )
327  elif parameter_num > 0: # Inconsistent use of tabs and spaces in indentation
328  parameters = ( np.random.rand( parameter_num )*2-1.0 ) * np.pi
329  squander_gate.apply_to( state_squander, parameters )
330 
331 
332  #QISKIT
333 
334  # Create a Quantum Circuit acting on the q register
335  circuit_qiskit = QuantumCircuit(qbit_num)
336  circuit_qiskit.initialize( initial_state )
337 
338  # Add the gate
339  gate_name = gate_obj.__name__
340 
341  #special cases:
342  if gate_name == "U3":
343  gate_name = "u"
344 
345  elif gate_name == "U1":
346  gate_name = "p"
347 
348  elif gate_name == "CNOT":
349  gate_name = "cx"
350 
351  gate_name = gate_name.lower()
352 
353 
354  gate_adding_fnc = getattr(circuit_qiskit, gate_name )
355  if not is_controlled_gate and parameter_num == 0:
356  # add parameter-free single qbit gate to Qiskit circuit
357  gate_adding_fnc(target_qbit)
358 
359  elif not is_controlled_gate and parameter_num > 0:
360  # add single qbit gate to Qiskit circuit
361  parameters_QISKIT = list(parameters)
362 
363  #Squander uses half of the theta function for numerical performance
364  if gate_name != "p" and gate_name != "u2":
365  parameters_QISKIT[0] = parameters_QISKIT[0]*2
366  gate_adding_fnc( *parameters_QISKIT, target_qbit)
367 
368  elif is_controlled_gate and parameter_num == 0:
369  # add parameter-free two-qbit controlled gate to Qiskit circuit
370  control_qbit = qbit_num-1
371  gate_adding_fnc(control_qbit, target_qbit)
372 
373  elif is_controlled_gate and parameter_num > 0:
374  # add parameter-free two-qbit controlled gate to Qiskit circuit
375  parameters_QISKIT = list(parameters)
376 
377  #Squander uses half of the theta function for numerical performance
378  parameters_QISKIT[0] = parameters_QISKIT[0]*2
379  control_qbit = qbit_num-1
380  gate_adding_fnc( *parameters_QISKIT, control_qbit, target_qbit)
381 
382 
383  # Execute and get the state vector
384  if qiskit_version[0] == '1' or qiskit_version[0] == '2':
385 
386  circuit_qiskit.save_statevector()
387 
388  backend = Aer.AerSimulator(method='statevector')
389  compiled_circuit = transpile(circuit_qiskit, backend)
390  result = backend.run(compiled_circuit).result()
391 
392  state_QISKIT = result.get_statevector(compiled_circuit)
393 
394 
395  elif qiskit_version[0] == '0':
396 
397  # Select the StatevectorSimulator from the Aer provider
398  simulator = Aer.get_backend('statevector_simulator')
399 
400  backend = Aer.get_backend('aer_simulator')
401  result = execute(circuit_qiskit, simulator).result()
402 
403  state_QISKIT = result.get_statevector(circuit_qiskit)
404 
405  state_QISKIT = np.array(state_QISKIT)
406 
407  # compute norm of matrix
408  error=np.linalg.norm( state_squander-state_QISKIT )
409 
410  assert( error < 1e-3 )
411 
412 
413  def test_gates(self):
414  """
415  This method is called by pytest.
416  Test by comparing all of the squander gates to Qiskit implementation
417 
418  """
419 
420  import squander.gates.gates_Wrapper as gates
421 
422  for name in dir(gates):
423  obj = getattr(gates, name)
424 
425  if inspect.isclass(obj):
426  print(f"testing gate: {name}")
427 
428  if name == "SYC" or name == "Gate" or name=="CR" or name=="CROT":
429  continue
430 
431  # NEEDS FIXING: CRY, RX, RY, U1, U2, U3
432  self.perform_gate_matrix_testing( obj )
434 
435 
436 
437 
438 
439 
440 
441 
442 
443 
444 
445 
446 
447 
def make_u2(self, phi, lam, target_qbit)
Definition: test_gates.py:31
def get_unitary_from_qiskit_circuit
Call to retrieve the unitary from QISKIT circuit.
Definition: utils.py:52
def perform_gate_apply_to_testing(self, gate_obj)
Definition: test_gates.py:281
A class representing a SYC operation.
Definition: SYC.h:36
Type definition of the qgd_Circuit_Wrapper Python class of the qgd_Circuit_Wrapper module...
result
the result of the Qiskit job
def perform_gate_matrix_testing(self, gate_obj)
Definition: test_gates.py:177