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  gate_name = gate_name.lower()
235 
236 
237  gate_adding_fnc = getattr(circuit, gate_name )
238  if not is_controlled_gate and parameter_num == 0:
239  # add parameter-free single qbit gate to Qiskit circuit
240  gate_adding_fnc(target_qbit)
241 
242  elif not is_controlled_gate and parameter_num > 0:
243  # add single qbit gate to Qiskit circuit
244  parameters_QISKIT = list(parameters)
245 
246  #Squander uses half of the theta function for numerical performance
247  if gate_name != "p" and gate_name != "u2":
248  parameters_QISKIT[0] = parameters_QISKIT[0]*2
249  gate_adding_fnc( *parameters_QISKIT, target_qbit)
250 
251  elif is_controlled_gate and parameter_num == 0:
252  # add parameter-free two-qbit controlled gate to Qiskit circuit
253  control_qbit = qbit_num-1
254  gate_adding_fnc(control_qbit, target_qbit)
255 
256  elif is_controlled_gate and parameter_num > 0:
257  # add parameter-free two-qbit controlled gate to Qiskit circuit
258  parameters_QISKIT = list(parameters)
259 
260  #Squander uses half of the theta function for numerical performance
261  parameters_QISKIT[0] = parameters_QISKIT[0]*2
262  gate_adding_fnc( *parameters_QISKIT, control_qbit, target_qbit)
263 
264 
265 
266  # the unitary matrix from the result object
267  gate_matrix_qiskit = get_unitary_from_qiskit_circuit( circuit )
268  gate_matrix_qiskit = np.asarray(gate_matrix_qiskit)
269 
270  #the difference between the SQUANDER and the qiskit result
271  delta_matrix=gate_matrix_squander-gate_matrix_qiskit
272 
273  # compute norm of matrix
274  error=np.linalg.norm(delta_matrix)
275 
276  #print("Get_matrix: The difference between the SQUANDER and the qiskit result is: " , np.around(error,2))
277  assert( error < 1e-3 )
278 
279 
280  def perform_gate_apply_to_testing( self, gate_obj ):
281  """
282  This method is called to test the individual gates
283  Test by comparing the effect of a gate on an input state
284  by squander gate to Qiskit implementation
285 
286  """
287 
288  is_controlled_gate = (len(gate_obj.__name__) > 1) and (gate_obj.__name__[0] == 'C')
289 
290  for qbit_num in range(2,7):
291 
292  # target qbit
293  target_qbit = qbit_num-2
294 
295  # creating an instance of the C++ class
296  if not is_controlled_gate:
297  # single qbit gate
298  squander_gate = gate_obj( qbit_num, target_qbit )
299  elif is_controlled_gate:
300  # gate with control qbit
301  control_qbit = qbit_num-1
302  squander_gate = gate_obj( qbit_num, target_qbit, control_qbit )
303 
304 
305  # matrix size of the unitary
306  matrix_size = 1 << qbit_num #pow(2, qbit_num )
307 
308  initial_state_real = np.random.uniform(-1.0,1.0, (matrix_size,) )
309  initial_state_imag = np.random.uniform(-1.0,1.0, (matrix_size,) )
310  initial_state = initial_state_real + initial_state_imag*1j
311  initial_state = initial_state/np.linalg.norm(initial_state)
312 
313 
314 
315 
316 
317  #SQUANDER
318 
319  state_squander = initial_state.copy()
320 
321 
322  # determine the number of input arguments of the get_Matrix function
323  parameter_num = squander_gate.get_Parameter_Num()
324  if parameter_num == 0:
325  squander_gate.apply_to( state_squander )
326  elif parameter_num > 0: # Inconsistent use of tabs and spaces in indentation
327  parameters = ( np.random.rand( parameter_num )*2-1.0 ) * np.pi
328  squander_gate.apply_to( state_squander, parameters )
329 
330 
331  #QISKIT
332 
333  # Create a Quantum Circuit acting on the q register
334  circuit_qiskit = QuantumCircuit(qbit_num)
335  circuit_qiskit.initialize( initial_state )
336 
337  # Add the gate
338  gate_name = gate_obj.__name__
339 
340  #special cases:
341  if gate_name == "U3":
342  gate_name = "u"
343 
344  elif gate_name == "U1":
345  gate_name = "p"
346 
347  elif gate_name == "CNOT":
348  gate_name = "cx"
349 
350  gate_name = gate_name.lower()
351 
352 
353  gate_adding_fnc = getattr(circuit_qiskit, gate_name )
354  if not is_controlled_gate and parameter_num == 0:
355  # add parameter-free single qbit gate to Qiskit circuit
356  gate_adding_fnc(target_qbit)
357 
358  elif not is_controlled_gate and parameter_num > 0:
359  # add single qbit gate to Qiskit circuit
360  parameters_QISKIT = list(parameters)
361 
362  #Squander uses half of the theta function for numerical performance
363  if gate_name != "p" and gate_name != "u2":
364  parameters_QISKIT[0] = parameters_QISKIT[0]*2
365  gate_adding_fnc( *parameters_QISKIT, target_qbit)
366 
367  elif is_controlled_gate and parameter_num == 0:
368  # add parameter-free two-qbit controlled gate to Qiskit circuit
369  control_qbit = qbit_num-1
370  gate_adding_fnc(control_qbit, target_qbit)
371 
372  elif is_controlled_gate and parameter_num > 0:
373  # add parameter-free two-qbit controlled gate to Qiskit circuit
374  parameters_QISKIT = list(parameters)
375 
376  #Squander uses half of the theta function for numerical performance
377  parameters_QISKIT[0] = parameters_QISKIT[0]*2
378  control_qbit = qbit_num-1
379  gate_adding_fnc( *parameters_QISKIT, control_qbit, target_qbit)
380 
381 
382  # Execute and get the state vector
383  if qiskit_version[0] == '1' or qiskit_version[0] == '2':
384 
385  circuit_qiskit.save_statevector()
386 
387  backend = Aer.AerSimulator(method='statevector')
388  compiled_circuit = transpile(circuit_qiskit, backend)
389  result = backend.run(compiled_circuit).result()
390 
391  state_QISKIT = result.get_statevector(compiled_circuit)
392 
393 
394  elif qiskit_version[0] == '0':
395 
396  # Select the StatevectorSimulator from the Aer provider
397  simulator = Aer.get_backend('statevector_simulator')
398 
399  backend = Aer.get_backend('aer_simulator')
400  result = execute(circuit_qiskit, simulator).result()
401 
402  state_QISKIT = result.get_statevector(circuit_qiskit)
403 
404  state_QISKIT = np.array(state_QISKIT)
405 
406  # compute norm of matrix
407  error=np.linalg.norm( state_squander-state_QISKIT )
408 
409  assert( error < 1e-3 )
410 
411 
412  def test_gates(self):
413  """
414  This method is called by pytest.
415  Test by comparing all of the squander gates to Qiskit implementation
416 
417  """
418 
419  import squander.gates.gates_Wrapper as gates
420 
421  for name in dir(gates):
422  obj = getattr(gates, name)
423 
424  if inspect.isclass(obj):
425  print(f"testing gate: {name}")
426 
427  if name == "SYC" or name == "Gate":
428  continue
429 
430  # NEEDS FIXING: CRY, RX, RY, U1, U2, U3
431  self.perform_gate_matrix_testing( obj )
433 
434 
435 
436 
437 
438 
439 
440 
441 
442 
443 
444 
445 
446 
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:280
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