From 4cfb97cb5f8516cc029db45ecb918d97345490f7 Mon Sep 17 00:00:00 2001 From: Purva Thakre Date: Tue, 9 Nov 2021 10:35:28 -0600 Subject: [PATCH 1/8] old working code for two level arrays --- src/qutip_qip/decompose/_utility.py | 61 ++++++++++++ .../decompose/decompose_general_qubit_gate.py | 94 +++++++++++++++++++ .../test_general_decomposition.py | 34 +++++++ 3 files changed, 189 insertions(+) create mode 100644 src/qutip_qip/decompose/decompose_general_qubit_gate.py create mode 100644 tests/decomposition_functions/test_general_decomposition.py diff --git a/src/qutip_qip/decompose/_utility.py b/src/qutip_qip/decompose/_utility.py index 99ca8f90..cef4791f 100644 --- a/src/qutip_qip/decompose/_utility.py +++ b/src/qutip_qip/decompose/_utility.py @@ -29,3 +29,64 @@ def check_gate(gate, num_qubits): raise ValueError("Input is not unitary.") if gate.dims != [[2] * num_qubits] * 2: raise ValueError(f"Input is not a unitary on {num_qubits} qubits.") + + +def _binary_sequence(num_qubits): + """ Defines the binary sequence list for basis vectors of a n-qubit gate. + The string at index `i` is also the row/column index for a basis vector in + a numpy array. + """ + old_sequence = ['0', '1'] + full_binary_sequence = [] + + if num_qubits == 1: + full_binary_sequence = old_sequence + else: + for x in range(num_qubits-1): + full_binary_sequence = [] + zero_append_sequence = ['0' + x for x in old_sequence] + full_binary_sequence.extend(zero_append_sequence) + one_append_sequence = ['1' + x for x in old_sequence] + full_binary_sequence.extend(one_append_sequence) + old_sequence = full_binary_sequence + + return(full_binary_sequence) + + +def _gray_code_sequence(num_qubits, output_form=None): + """ Finds the sequence of gray codes for basis vectors by using logical + XOR operator of Python. + For print( _gray_code_sequence(2)), the output is [0, 1, 3, 2] i.e. in + terms of the binary sequence, the output is ['00', '01', '11', '10']. + https://docs.python.org/3/library/operator.html#operator.xor' + + Parameters + ---------- + num_qubits + Number of qubits in the circuit + output_form : :"index_values" or None + The format of output list. If a string "index_values" is provided then + the function's output is in terms of array indices of the binary sequence. + The default is a list of binary strings. + + Returns + -------- + list + List of the gray code sequence in terms of array indices or binary + sequence positions. + """ + gray_code_sequence_as_array_indices = [] + + for x in range(2**num_qubits): + gray_code_at_x = x ^ x // 2 # floor operator to shift bits by 1 + # when the shift is done, the new spot is filled with a new value. + gray_code_sequence_as_array_indices.append(gray_code_at_x) + if output_form == "index_values": + output = gray_code_sequence_as_array_indices + else: + gray_code_as_binary = [] + binary_sequence_list = _binary_sequence(num_qubits) + for i in gray_code_sequence_as_array_indices: + gray_code_as_binary.append(binary_sequence_list[i]) + output = gray_code_as_binary + return(output) diff --git a/src/qutip_qip/decompose/decompose_general_qubit_gate.py b/src/qutip_qip/decompose/decompose_general_qubit_gate.py new file mode 100644 index 00000000..a48952c5 --- /dev/null +++ b/src/qutip_qip/decompose/decompose_general_qubit_gate.py @@ -0,0 +1,94 @@ +import numpy as np +import cmath +from qutip import Qobj +from qutip_qip.decompose._utility import ( + check_gate, +) + + +def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): + """Decompose a general qubit gate to two-level arrays. + + Parameters + ----------- + input_gate : :class:`qutip.Qobj` + The gate matrix to be decomposed. + num_qubits : int + Number of qubits being acted upon by the input_gate + expand : True + Default parameter to return the output as full two-level Qobj. If + `expand = False` then the function returns a tuple of index information + and a 2 x 2 Qobj for each gate. The Qobj are returned in reversed order. + """ + check_gate(input_gate, num_qubits) + input_array = input_gate.full() + + # Calculate the two level numpy arrays + array_list = [] + index_list = [] + for i in range(2**num_qubits): + for j in range(i+1, 2**num_qubits): + new_index = [i, j] + index_list.append(new_index) + + for i in range(len(index_list)-1): + index_1, index_2 = index_list[i] + + # Values of single qubit U forming the two level unitary + a = input_array[index_1][index_1] + a_star = np.conj(a) + b = input_array[index_2][index_1] + b_star = np.conj(b) + norm_constant = cmath.sqrt( + np.absolute(a*a_star)+np.absolute(b*b_star)) + + # Create identity array and then replace with above values for + # index_1 and index_2 + U_two_level = np.identity(2**num_qubits, dtype=complex) + U_two_level[index_1][index_1] = a_star/norm_constant + U_two_level[index_2][index_1] = b/norm_constant + U_two_level[index_1][index_2] = b_star/norm_constant + U_two_level[index_2][index_2] = -a/norm_constant + + # Change input by multiplying by above two-level + input_array = np.dot(U_two_level, input_array) + + # U dagger to calculate the gates + U__two_level_dagger = np.transpose(np.conjugate(U_two_level)) + array_list.append(U__two_level_dagger) + + # for U6 - multiply input array by U5 and take dagger + U_last_dagger = input_array + array_list.append(U_last_dagger) + + if expand is True: + array_list_with_qobj = [] + for i in reversed(range(len(index_list))): + U_two_level_array = array_list[i] + array_list_with_qobj.append(Qobj( + U_two_level_array, dims=[[2] * num_qubits] * 2)) + return(array_list_with_qobj) + else: + compact_U_information = [] + for i in reversed(range(len(index_list))): + U_non_trivial = np.full([2, 2], None, dtype=complex) + index_info = [] + U_index_together = [] + + # create index list + index_1, index_2 = index_list[i] + index_info = [index_1, index_2] + U_index_together.append(index_info) + + # create 2 x 2 arrays + U_two_level = array_list[i] + U_non_trivial[0][0] = U_two_level[index_1][index_1] + U_non_trivial[1][0] = U_two_level[index_2][index_1] + U_non_trivial[0][1] = U_two_level[index_1][index_2] + U_non_trivial[1][1] = U_two_level[index_2][index_2] + U_index_together.append( + Qobj(U_non_trivial, dims=[[2] * 1] * 2)) + + compact_U_information.append(U_index_together) + + return(compact_U_information) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py new file mode 100644 index 00000000..1f069166 --- /dev/null +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -0,0 +1,34 @@ + + +import numpy as np +import pytest + +from qutip import ( + Qobj, average_gate_fidelity, rand_unitary +) + + +from qutip_qip.decompose.decompose_general_qubit_gate import ( + _decompose_to_two_level_arrays) + + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) +def test_two_level_full_output(num_qubits): + """ Check if product of full two level array output is equal to the input. + """ + input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=True) + + product_of_U = array_decompose[-1] + + for i in reversed(range(len(array_decompose)-1)): + product_of_U = product_of_U + product_of_U_calculated = np.dot(product_of_U, array_decompose[i]) + product_of_U = product_of_U_calculated + + product_of_U = Qobj(product_of_U, dims=[[2] * num_qubits] * 2) + fidelity_of_input_output = average_gate_fidelity( + product_of_U, input_gate + ) + assert np.isclose(fidelity_of_input_output, 1.0) From 02afabd8e8f35245cba87f7918f6fe5722717f00 Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Thu, 18 Nov 2021 13:27:47 -0600 Subject: [PATCH 2/8] empty dict function --- .../decompose/decompose_general_qubit_gate.py | 14 ++++++++++++++ .../test_general_decomposition.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/qutip_qip/decompose/decompose_general_qubit_gate.py b/src/qutip_qip/decompose/decompose_general_qubit_gate.py index a48952c5..cc3a524d 100644 --- a/src/qutip_qip/decompose/decompose_general_qubit_gate.py +++ b/src/qutip_qip/decompose/decompose_general_qubit_gate.py @@ -92,3 +92,17 @@ def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): compact_U_information.append(U_index_together) return(compact_U_information) + + +def _create_dict_for_two_level_arrays(two_level_output): + """Creates a dictionary with keys for the total number of two-level array + output. This will be used by other functions to store information about + SWAP, PauliX gates etc. + """ + num_two_level_gates = len(two_level_output) + + # create a reversed list of keys based on total number of two-level gates + # ranging from 1 to n where n is the total number of two-level arrays + gate_keys = list(range(1, num_two_level_gates+1))[::-1] + gate_info_dict = dict.fromkeys(gate_keys) + return(gate_info_dict) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index 1f069166..86ab2520 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -9,7 +9,7 @@ from qutip_qip.decompose.decompose_general_qubit_gate import ( - _decompose_to_two_level_arrays) + _decompose_to_two_level_arrays, _create_dict_for_two_level_arrays) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) @@ -32,3 +32,15 @@ def test_two_level_full_output(num_qubits): product_of_U, input_gate ) assert np.isclose(fidelity_of_input_output, 1.0) + + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) +def test_empty_dict_of_two_level_arrays(num_qubits): + """ Check if empty dictionary is of the same length as the two-level array + output. + """ + input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False) + empty_dict_output = _create_dict_for_two_level_arrays(array_decompose) + assert np.equal(len(empty_dict_output), len(array_decompose)) From bf2657f1340aa13d6be3a40cc22f1a4cd0091d88 Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Fri, 19 Nov 2021 21:31:33 -0600 Subject: [PATCH 3/8] partial gray code functions --- .../decompose/decompose_general_qubit_gate.py | 62 ++++++++++++++++++- .../test_general_decomposition.py | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/qutip_qip/decompose/decompose_general_qubit_gate.py b/src/qutip_qip/decompose/decompose_general_qubit_gate.py index cc3a524d..c6fca9ee 100644 --- a/src/qutip_qip/decompose/decompose_general_qubit_gate.py +++ b/src/qutip_qip/decompose/decompose_general_qubit_gate.py @@ -2,7 +2,7 @@ import cmath from qutip import Qobj from qutip_qip.decompose._utility import ( - check_gate, + check_gate, _binary_sequence, _gray_code_sequence ) @@ -106,3 +106,63 @@ def _create_dict_for_two_level_arrays(two_level_output): gate_keys = list(range(1, num_two_level_gates+1))[::-1] gate_info_dict = dict.fromkeys(gate_keys) return(gate_info_dict) + + +def _partial_gray_code(num_qubits, two_level_output): + """Returns a dictionary of partial gray code sequence for each two-level + array.""" + + # create empty dict + gate_key_dict = _create_dict_for_two_level_arrays(two_level_output) + + # create a list of non-trivial indices in two level array output + two_level_indices = [] + for i in range(len(two_level_output)): + two_level_indices.append(two_level_output[i][0]) + + # gray code sequence output as indices of binary sequence and strings + # respectively + gray_code_index = _gray_code_sequence(num_qubits, 'index_values') + gray_code_string = _gray_code_sequence(num_qubits) + + # get the partial gray code sequence + for i in range(len(two_level_indices)): + partial_gray_code = [] + ind1 = two_level_indices[i][0] + ind2 = two_level_indices[i][1] + + ind1_pos_in_gray_code = gray_code_index.index(ind1) + ind2_pos_in_gray_code = gray_code_index.index(ind2) + + if ind1_pos_in_gray_code > ind2_pos_in_gray_code: + partial_gray_code = [ind2_pos_in_gray_code, ind1_pos_in_gray_code] + else: + partial_gray_code = [ind1_pos_in_gray_code, ind2_pos_in_gray_code] + + gate_key_dict[len(two_level_indices)-i] = gray_code_string[ + partial_gray_code[0]:partial_gray_code[1]+1] + + return(gate_key_dict) + + +def _split_partial_gray_code(gate_key_dict): + """Splits the output of gray code sequence into n-bit Toffoli and + two-level array gate of interest. + The output is a list of dictionary of n-bit toffoli and another dictionary + for the gate needing to be decomposed. + + When the decomposed gates are added to the circuit, n-bit toffoli will be + used twice - once in the correct order it is and then in a reversed order. + """ + n_bit_toffoli_dict = {} + two_level_of_int = {} + for key in gate_key_dict.keys(): + if len(gate_key_dict[key]) > 2: + key_value = gate_key_dict[key] + two_level_of_int[key] = [key_value[-1], key_value[-2]] + n_bit_toffoli_dict[key] = key_value[0:-2] + else: + two_level_of_int[key] = gate_key_dict[key] + n_bit_toffoli_dict[key] = None + output_of_separated_gray_code = [n_bit_toffoli_dict, two_level_of_int] + return(output_of_separated_gray_code) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index 86ab2520..a77c8533 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -34,7 +34,7 @@ def test_two_level_full_output(num_qubits): assert np.isclose(fidelity_of_input_output, 1.0) -@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_empty_dict_of_two_level_arrays(num_qubits): """ Check if empty dictionary is of the same length as the two-level array output. From 65e0b0da47575dffb85a122cb7ceebdc44e71a67 Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Thu, 27 Jan 2022 09:44:18 -0600 Subject: [PATCH 4/8] test new laptop commit --- .../decompose/decompose_general_qubit_gate.py | 10 ++++++++-- .../test_general_decomposition.py | 14 +++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/qutip_qip/decompose/decompose_general_qubit_gate.py b/src/qutip_qip/decompose/decompose_general_qubit_gate.py index c6fca9ee..45be6736 100644 --- a/src/qutip_qip/decompose/decompose_general_qubit_gate.py +++ b/src/qutip_qip/decompose/decompose_general_qubit_gate.py @@ -110,7 +110,9 @@ def _create_dict_for_two_level_arrays(two_level_output): def _partial_gray_code(num_qubits, two_level_output): """Returns a dictionary of partial gray code sequence for each two-level - array.""" + array. + + The input is when output from decomposition array output is non-expanded.""" # create empty dict gate_key_dict = _create_dict_for_two_level_arrays(two_level_output) @@ -148,11 +150,15 @@ def _partial_gray_code(num_qubits, two_level_output): def _split_partial_gray_code(gate_key_dict): """Splits the output of gray code sequence into n-bit Toffoli and two-level array gate of interest. + The output is a list of dictionary of n-bit toffoli and another dictionary - for the gate needing to be decomposed. + for the gate needing to be decomposed. When the decomposed gates are added to the circuit, n-bit toffoli will be used twice - once in the correct order it is and then in a reversed order. + + For cases where there is only 1 step in the gray code sequence, first dictionary + will be empty and second will need a decomposition scheme. """ n_bit_toffoli_dict = {} two_level_of_int = {} diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index a77c8533..f5a77ebc 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -9,7 +9,7 @@ from qutip_qip.decompose.decompose_general_qubit_gate import ( - _decompose_to_two_level_arrays, _create_dict_for_two_level_arrays) + _decompose_to_two_level_arrays, _create_dict_for_two_level_arrays, _partial_gray_code) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) @@ -44,3 +44,15 @@ def test_empty_dict_of_two_level_arrays(num_qubits): input_gate, num_qubits, expand=False) empty_dict_output = _create_dict_for_two_level_arrays(array_decompose) assert np.equal(len(empty_dict_output), len(array_decompose)) + + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) +def test_empty_dict_of_two_level_arrays(num_qubits): + """ Check if split gray code output is of the same length as the two-level array + output. + """ + input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False) + empty_dict_output = _partial_gray_code(num_qubits, array_decompose) + assert np.equal(len(empty_dict_output), len(array_decompose)) \ No newline at end of file From 58ca47acec4bc71127ac7966aa3f38f5941e6553 Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Fri, 4 Feb 2022 08:39:08 -0600 Subject: [PATCH 5/8] tests for partial_grey_code --- .../test_general_decomposition.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index f5a77ebc..83a49f68 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -47,7 +47,7 @@ def test_empty_dict_of_two_level_arrays(num_qubits): @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) -def test_empty_dict_of_two_level_arrays(num_qubits): +def test_len_partial_grey_code(num_qubits): """ Check if split gray code output is of the same length as the two-level array output. """ @@ -55,4 +55,20 @@ def test_empty_dict_of_two_level_arrays(num_qubits): array_decompose = _decompose_to_two_level_arrays( input_gate, num_qubits, expand=False) empty_dict_output = _partial_gray_code(num_qubits, array_decompose) - assert np.equal(len(empty_dict_output), len(array_decompose)) \ No newline at end of file + assert np.equal(len(empty_dict_output), len(array_decompose)) + + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) +def test_keys_partial_grey_code(num_qubits): + """ Check if dictionary keys are consistent in partial grey code. + + The keys are for all two-level gates describing the decomposition and + are in a reversed order. + """ + input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False) + dict_output = _partial_gray_code(num_qubits, array_decompose) + gate_key_list = list(_partial_gray_code(num_qubits, array_decompose).keys()) + correct_gate_key_list = list(range(1,len(array_decompose)+1)[::-1]) + assert gate_key_list == correct_gate_key_list \ No newline at end of file From a917a635cb52a9fff869e56ef099ad7869e04770 Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Fri, 4 Feb 2022 09:04:47 -0600 Subject: [PATCH 6/8] partial output test --- .../test_general_decomposition.py | 116 ++++++++++++++---- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index 83a49f68..3de9f63b 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -1,74 +1,136 @@ - - import numpy as np import pytest -from qutip import ( - Qobj, average_gate_fidelity, rand_unitary -) +from qutip import Qobj, average_gate_fidelity, rand_unitary from qutip_qip.decompose.decompose_general_qubit_gate import ( - _decompose_to_two_level_arrays, _create_dict_for_two_level_arrays, _partial_gray_code) + _decompose_to_two_level_arrays, + _create_dict_for_two_level_arrays, + _partial_gray_code, +) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5, 6]) def test_two_level_full_output(num_qubits): - """ Check if product of full two level array output is equal to the input. - """ - input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + """Check if product of full two level array output is equal to the input.""" + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( - input_gate, num_qubits, expand=True) + input_gate, num_qubits, expand=True + ) product_of_U = array_decompose[-1] - for i in reversed(range(len(array_decompose)-1)): + for i in reversed(range(len(array_decompose) - 1)): product_of_U = product_of_U product_of_U_calculated = np.dot(product_of_U, array_decompose[i]) product_of_U = product_of_U_calculated product_of_U = Qobj(product_of_U, dims=[[2] * num_qubits] * 2) - fidelity_of_input_output = average_gate_fidelity( - product_of_U, input_gate - ) + fidelity_of_input_output = average_gate_fidelity(product_of_U, input_gate) assert np.isclose(fidelity_of_input_output, 1.0) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_empty_dict_of_two_level_arrays(num_qubits): - """ Check if empty dictionary is of the same length as the two-level array + """Check if empty dictionary is of the same length as the two-level array output. """ - input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( - input_gate, num_qubits, expand=False) + input_gate, num_qubits, expand=False + ) empty_dict_output = _create_dict_for_two_level_arrays(array_decompose) assert np.equal(len(empty_dict_output), len(array_decompose)) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_len_partial_grey_code(num_qubits): - """ Check if split gray code output is of the same length as the two-level array + """Check if split gray code output is of the same length as the two-level array output. """ - input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( - input_gate, num_qubits, expand=False) + input_gate, num_qubits, expand=False + ) empty_dict_output = _partial_gray_code(num_qubits, array_decompose) assert np.equal(len(empty_dict_output), len(array_decompose)) @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_keys_partial_grey_code(num_qubits): - """ Check if dictionary keys are consistent in partial grey code. + """Check if dictionary keys are consistent in partial grey code. The keys are for all two-level gates describing the decomposition and - are in a reversed order. + are in a reversed order. """ - input_gate = rand_unitary(2**num_qubits, dims=[[2] * num_qubits] * 2) + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( - input_gate, num_qubits, expand=False) + input_gate, num_qubits, expand=False + ) dict_output = _partial_gray_code(num_qubits, array_decompose) - gate_key_list = list(_partial_gray_code(num_qubits, array_decompose).keys()) - correct_gate_key_list = list(range(1,len(array_decompose)+1)[::-1]) - assert gate_key_list == correct_gate_key_list \ No newline at end of file + gate_key_list = list( + _partial_gray_code(num_qubits, array_decompose).keys() + ) + correct_gate_key_list = list(range(1, len(array_decompose) + 1)[::-1]) + assert gate_key_list == correct_gate_key_list + + +def test_two_qubit_partial_grey_code_output(): + """Checks if the gray code output is as expected.""" + num_qubits = 2 + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False + ) + func_output = _partial_gray_code(num_qubits, array_decompose) + expected_output = { + 6: ["11", "10"], + 5: ["01", "11"], + 4: ["01", "11", "10"], + 3: ["00", "01", "11"], + 2: ["00", "01", "11", "10"], + 1: ["00", "01"], + } + assert func_output == expected_output + + +def test_three_qubit_partial_grey_code_output(): + """Checks if the gray code output is as expected.""" + num_qubits = 3 + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False + ) + func_output = _partial_gray_code(num_qubits, array_decompose) + expected_output = { + 28: ["110", "111"], + 27: ["111", "101"], + 26: ["110", "111", "101"], + 25: ["111", "101", "100"], + 24: ["110", "111", "101", "100"], + 23: ["101", "100"], + 22: ["011", "010", "110", "111"], + 21: ["011", "010", "110"], + 20: ["011", "010", "110", "111", "101"], + 19: ["011", "010", "110", "111", "101", "100"], + 18: ["010", "110", "111"], + 17: ["010", "110"], + 16: ["010", "110", "111", "101"], + 15: ["010", "110", "111", "101", "100"], + 14: ["011", "010"], + 13: ["001", "011", "010", "110", "111"], + 12: ["001", "011", "010", "110"], + 11: ["001", "011", "010", "110", "111", "101"], + 10: ["001", "011", "010", "110", "111", "101", "100"], + 9: ["001", "011"], + 8: ["001", "011", "010"], + 7: ["000", "001", "011", "010", "110", "111"], + 6: ["000", "001", "011", "010", "110"], + 5: ["000", "001", "011", "010", "110", "111", "101"], + 4: ["000", "001", "011", "010", "110", "111", "101", "100"], + 3: ["000", "001", "011"], + 2: ["000", "001", "011", "010"], + 1: ["000", "001"], + } + assert func_output == expected_output From c6b88e4af179821d8162b210beddd2175390043c Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Fri, 4 Feb 2022 09:24:49 -0600 Subject: [PATCH 7/8] lint --- .../decompose/decompose_general_qubit_gate.py | 54 ++++++++++--------- .../test_general_decomposition.py | 9 ++-- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/qutip_qip/decompose/decompose_general_qubit_gate.py b/src/qutip_qip/decompose/decompose_general_qubit_gate.py index 45be6736..21edb681 100644 --- a/src/qutip_qip/decompose/decompose_general_qubit_gate.py +++ b/src/qutip_qip/decompose/decompose_general_qubit_gate.py @@ -2,7 +2,9 @@ import cmath from qutip import Qobj from qutip_qip.decompose._utility import ( - check_gate, _binary_sequence, _gray_code_sequence + check_gate, + _binary_sequence, + _gray_code_sequence, ) @@ -26,12 +28,12 @@ def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): # Calculate the two level numpy arrays array_list = [] index_list = [] - for i in range(2**num_qubits): - for j in range(i+1, 2**num_qubits): + for i in range(2 ** num_qubits): + for j in range(i + 1, 2 ** num_qubits): new_index = [i, j] index_list.append(new_index) - for i in range(len(index_list)-1): + for i in range(len(index_list) - 1): index_1, index_2 = index_list[i] # Values of single qubit U forming the two level unitary @@ -40,15 +42,16 @@ def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): b = input_array[index_2][index_1] b_star = np.conj(b) norm_constant = cmath.sqrt( - np.absolute(a*a_star)+np.absolute(b*b_star)) + np.absolute(a * a_star) + np.absolute(b * b_star) + ) # Create identity array and then replace with above values for # index_1 and index_2 - U_two_level = np.identity(2**num_qubits, dtype=complex) - U_two_level[index_1][index_1] = a_star/norm_constant - U_two_level[index_2][index_1] = b/norm_constant - U_two_level[index_1][index_2] = b_star/norm_constant - U_two_level[index_2][index_2] = -a/norm_constant + U_two_level = np.identity(2 ** num_qubits, dtype=complex) + U_two_level[index_1][index_1] = a_star / norm_constant + U_two_level[index_2][index_1] = b / norm_constant + U_two_level[index_1][index_2] = b_star / norm_constant + U_two_level[index_2][index_2] = -a / norm_constant # Change input by multiplying by above two-level input_array = np.dot(U_two_level, input_array) @@ -65,9 +68,10 @@ def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): array_list_with_qobj = [] for i in reversed(range(len(index_list))): U_two_level_array = array_list[i] - array_list_with_qobj.append(Qobj( - U_two_level_array, dims=[[2] * num_qubits] * 2)) - return(array_list_with_qobj) + array_list_with_qobj.append( + Qobj(U_two_level_array, dims=[[2] * num_qubits] * 2) + ) + return array_list_with_qobj else: compact_U_information = [] for i in reversed(range(len(index_list))): @@ -86,12 +90,11 @@ def _decompose_to_two_level_arrays(input_gate, num_qubits, expand=True): U_non_trivial[1][0] = U_two_level[index_2][index_1] U_non_trivial[0][1] = U_two_level[index_1][index_2] U_non_trivial[1][1] = U_two_level[index_2][index_2] - U_index_together.append( - Qobj(U_non_trivial, dims=[[2] * 1] * 2)) + U_index_together.append(Qobj(U_non_trivial, dims=[[2] * 1] * 2)) compact_U_information.append(U_index_together) - return(compact_U_information) + return compact_U_information def _create_dict_for_two_level_arrays(two_level_output): @@ -103,15 +106,15 @@ def _create_dict_for_two_level_arrays(two_level_output): # create a reversed list of keys based on total number of two-level gates # ranging from 1 to n where n is the total number of two-level arrays - gate_keys = list(range(1, num_two_level_gates+1))[::-1] + gate_keys = list(range(1, num_two_level_gates + 1))[::-1] gate_info_dict = dict.fromkeys(gate_keys) - return(gate_info_dict) + return gate_info_dict def _partial_gray_code(num_qubits, two_level_output): """Returns a dictionary of partial gray code sequence for each two-level array. - + The input is when output from decomposition array output is non-expanded.""" # create empty dict @@ -124,7 +127,7 @@ def _partial_gray_code(num_qubits, two_level_output): # gray code sequence output as indices of binary sequence and strings # respectively - gray_code_index = _gray_code_sequence(num_qubits, 'index_values') + gray_code_index = _gray_code_sequence(num_qubits, "index_values") gray_code_string = _gray_code_sequence(num_qubits) # get the partial gray code sequence @@ -141,10 +144,11 @@ def _partial_gray_code(num_qubits, two_level_output): else: partial_gray_code = [ind1_pos_in_gray_code, ind2_pos_in_gray_code] - gate_key_dict[len(two_level_indices)-i] = gray_code_string[ - partial_gray_code[0]:partial_gray_code[1]+1] + gate_key_dict[len(two_level_indices) - i] = gray_code_string[ + partial_gray_code[0] : partial_gray_code[1] + 1 + ] - return(gate_key_dict) + return gate_key_dict def _split_partial_gray_code(gate_key_dict): @@ -152,7 +156,7 @@ def _split_partial_gray_code(gate_key_dict): two-level array gate of interest. The output is a list of dictionary of n-bit toffoli and another dictionary - for the gate needing to be decomposed. + for the gate needing to be decomposed. When the decomposed gates are added to the circuit, n-bit toffoli will be used twice - once in the correct order it is and then in a reversed order. @@ -171,4 +175,4 @@ def _split_partial_gray_code(gate_key_dict): two_level_of_int[key] = gate_key_dict[key] n_bit_toffoli_dict[key] = None output_of_separated_gray_code = [n_bit_toffoli_dict, two_level_of_int] - return(output_of_separated_gray_code) + return output_of_separated_gray_code diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index 3de9f63b..76fe1c7a 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -33,8 +33,8 @@ def test_two_level_full_output(num_qubits): @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_empty_dict_of_two_level_arrays(num_qubits): - """Check if empty dictionary is of the same length as the two-level array - output. + """Check if empty dictionary is of the same length as + the two-level array output. """ input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( @@ -46,8 +46,8 @@ def test_empty_dict_of_two_level_arrays(num_qubits): @pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) def test_len_partial_grey_code(num_qubits): - """Check if split gray code output is of the same length as the two-level array - output. + """Check if split gray code output is of the same length + as the two-level array output. """ input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) array_decompose = _decompose_to_two_level_arrays( @@ -134,3 +134,4 @@ def test_three_qubit_partial_grey_code_output(): 1: ["000", "001"], } assert func_output == expected_output + From 07cc5a4539c4aa05e14e5b1afc344dc10e87968d Mon Sep 17 00:00:00 2001 From: purva-thakre Date: Fri, 4 Feb 2022 10:02:47 -0600 Subject: [PATCH 8/8] tests for split partial --- .../test_general_decomposition.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/decomposition_functions/test_general_decomposition.py b/tests/decomposition_functions/test_general_decomposition.py index 76fe1c7a..1f0fcdd3 100644 --- a/tests/decomposition_functions/test_general_decomposition.py +++ b/tests/decomposition_functions/test_general_decomposition.py @@ -8,6 +8,7 @@ _decompose_to_two_level_arrays, _create_dict_for_two_level_arrays, _partial_gray_code, + _split_partial_gray_code, ) @@ -135,3 +136,133 @@ def test_three_qubit_partial_grey_code_output(): } assert func_output == expected_output + +@pytest.mark.parametrize("num_qubits", [2, 3, 4, 5]) +def test_len_split_grey_code(num_qubits): + """Check if length of both dict outputs of split gray code is equal to + the total number of two-level array gates. + + First dict is made up of n-bit toffoli and second is made up of + gate that needs decomposition. + """ + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False + ) + len_two_level_array = len(array_decompose) + func_out = _split_partial_gray_code( + _partial_gray_code(num_qubits, array_decompose) + ) + + # Checks if 2 separate dictionaries are always returned + assert len(func_out) == 2 + + # check length of each dictionary + assert len(func_out[0]) == len(array_decompose) + assert len(func_out[1]) == len(array_decompose) + + +def test_two_qubit_split_partial_grey_code(): + """Checks if output of split partial gray code function is correct + with expected output for two qubits.""" + num_qubits = 2 + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False + ) + split_output = _split_partial_gray_code( + _partial_gray_code(num_qubits, array_decompose) + ) + expected_toffoli_output = { + 6: None, + 5: None, + 4: ["01"], + 3: ["00"], + 2: ["00", "01"], + 1: None, + } + expected_gate_decom_out = { + 6: ["11", "10"], + 5: ["01", "11"], + 4: ["10", "11"], + 3: ["11", "01"], + 2: ["10", "11"], + 1: ["00", "01"], + } + assert expected_toffoli_output == split_output[0] + assert expected_gate_decom_out == split_output[1] + + +def test_three_qubit_split_partial_grey_code(): + """Checks if output of split partial gray code function is correct + with expected output for three qubits.""" + num_qubits = 3 + input_gate = rand_unitary(2 ** num_qubits, dims=[[2] * num_qubits] * 2) + array_decompose = _decompose_to_two_level_arrays( + input_gate, num_qubits, expand=False + ) + split_output = _split_partial_gray_code( + _partial_gray_code(num_qubits, array_decompose) + ) + expected_toffoli_output = { + 28: None, + 27: None, + 26: ["110"], + 25: ["111"], + 24: ["110", "111"], + 23: None, + 22: ["011", "010"], + 21: ["011"], + 20: ["011", "010", "110"], + 19: ["011", "010", "110", "111"], + 18: ["010"], + 17: None, + 16: ["010", "110"], + 15: ["010", "110", "111"], + 14: None, + 13: ["001", "011", "010"], + 12: ["001", "011"], + 11: ["001", "011", "010", "110"], + 10: ["001", "011", "010", "110", "111"], + 9: None, + 8: ["001"], + 7: ["000", "001", "011", "010"], + 6: ["000", "001", "011"], + 5: ["000", "001", "011", "010", "110"], + 4: ["000", "001", "011", "010", "110", "111"], + 3: ["000"], + 2: ["000", "001"], + 1: None, + } + expected_gate_decom_out = { + 28: ["110", "111"], + 27: ["111", "101"], + 26: ["101", "111"], + 25: ["100", "101"], + 24: ["100", "101"], + 23: ["101", "100"], + 22: ["111", "110"], + 21: ["110", "010"], + 20: ["101", "111"], + 19: ["100", "101"], + 18: ["111", "110"], + 17: ["010", "110"], + 16: ["101", "111"], + 15: ["100", "101"], + 14: ["011", "010"], + 13: ["111", "110"], + 12: ["110", "010"], + 11: ["101", "111"], + 10: ["100", "101"], + 9: ["001", "011"], + 8: ["010", "011"], + 7: ["111", "110"], + 6: ["110", "010"], + 5: ["101", "111"], + 4: ["100", "101"], + 3: ["011", "001"], + 2: ["010", "011"], + 1: ["000", "001"], + } + assert expected_toffoli_output == split_output[0] + assert expected_gate_decom_out == split_output[1]