Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds two qubit decomposition functions #82

Closed
wants to merge 1 commit into from

Conversation

purva-thakre
Copy link
Contributor

Circuit of Two-Level Unitaries Example

The output of the function is a tuple of the gate dictionary and two-level gates in order. The gate labels are arbitrary numbered U's which will hopefully change when a two-level unitary gate is added.

np.set_printoptions(precision=3)
two_qubit = rand_unitary(4,dims=[[2,2],[2,2]])

# Output from the function
dict_and_gate_list = decompose_two_qubit_to_two_level_unitary(two_qubit)

# appending the user_gates attribute to the quantum circuit of interest
quantum_circuit = QubitCircuit(2,reverse_states=False )
user_gates_from_output = dict_and_gate_list[0]
quantum_circuit.user_gates = user_gates_from_output

# add gates to the circuit and get circuit diagram
quantum_circuit.add_gates(dict_and_gate_list[-1])
quantum_circuit.png

download

# Comparison to the input
calculatedu = quantum_circuit.compute_unitary()
print(average_gate_fidelity(calculatedu,two_qubit))

return(user_gates, gate_list)


def decompose_two_qubit_to_CNOT_and_single_qubit_gates(input_gate):
Copy link
Contributor Author

@purva-thakre purva-thakre Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output of this function is not what it is expected to be. For this reason, all of the smaller gates for U6,...,U1 have been defined separately to check output of each as needed. A for loop will be created for this function to shorten the code definition after the output matches exactly to the input.

Need to recalculate the target and control qubits because they were different from what was expected for the two level circuit output function. The output of the two level output matches exactly to the input with new values for controls and targets.

Just because I know the correct control and targets does not imply how the controls and targets for CNOT and Pauli X of each gate should be corrected. The hope is to check and compare my calculations with the targets and controls from gray code function's output. The error in my calculations is due to not being consistent with the gray code sequence used for each gate (see ambiguity discussed below).

Issue with gray code - There's ambiguity as to how you map from 1 state to another as long as only 1 bit is changed at each step.

For example, when n = 2 and we want to get the gray code sequence by starting at 00 then either 10 or 01 are valid as next step in the sequence.

  1. 00 -> 01 -> 11 -> 10
  2. 00 -> 10 -> 11 -> 01

Now, gray code ordering function definition creates a gray code sequence via reflect method then determines the numpy array row/columns by backtracking.

Continuing above example, suppose we choose gray_code_sequence =[00 , 10 , 11 , 01].

The binary sequence for 2 qubits is binary_sequence= [00, 01, 10, 11] which can be re-written in terms of array indices as [0, 1, 2, 3].

Comparing gray_code_sequence to binary_sequence, the array indices for gray_code_sequence are [0, 2, 3, 1] which makes it easier to label the basis vectors of an array.

Copy link
Member

@BoxiLi BoxiLi Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue with gray code - There's ambiguity as to how you map from 1 state to another as long as only 1 bit is changed at each step.

I think if all qubits are the same, the physical resources shouldn't make a difference. Of course in real hardware qubits are not the same and gate implementation is not always symmetric so one way might be better than the other. But I think that is a very concrete scenario and we don't need to care about it now. It would be good enough to just define a convention like always go with the route for smaller binary number (01 < 10).

The binary sequence for 2 qubits is binary_sequence= [00, 01, 10, 11] which can be re-written in terms of array indices as [0, 1, 2, 3].

Not sure if this is helpful here, numpy has ravel_multi_index and unravel_index to compute between binary representation and matrix row/column index. (Actually, it works for arbitrary dimensions)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.

>>> np.ravel_multi_index([0,1,0],[2,2,2])
2

Copy link
Contributor Author

@purva-thakre purva-thakre Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the physical resources shouldn't make a difference

But the order that you map in does matter because the sequence has to be cyclic. When I did my calculations, I had tried to use the shortest method to go from one state another in the hope that the total gate number would decrease. As long as you choose a consistent method ( for this project, I have chosen reflect and prefix method).

I now think my error is not related to the gray code sequence but instead about the order of gates and/or controls, targets (see example below). I might have missed some information from the reference papers. Now, my plan is to at least have a working code for what was planned this week and then come back to correcting a few lines after I have figured out my error. This should also give you a chance to review the code logic etc.

Suppose I want to decompose the following matrix :

image

The non-trivial basis vectors are 01 and 10. Using the gray code generator, 01 -> 11 -> 10.

  • For 01 -> 11 : When qubit 2 is 1, qubit 1 is flipped. Thus, there's a CNOT gate added to the circuit with controls=[1], targets=[0], control_value=1
  • For 11 -> 10 : When qubit 1 is 1, qubit 2 is flipped. This is a controlled gate on target 0 with control value 1. But because this is the last steps in the sequence, the non-trivial sub-matrix has to be added to the circuit. The form of this is U6 whose decomposition is known in Lemma 5.1.
  • The last gate is to go back to state 01.

image

image

The circuit's unitary is calculated as shown below. This is obviously not the input but at least the non-trivial states are the same.

image

Copy link
Contributor Author

@purva-thakre purva-thakre Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.

>>> np.ravel_multi_index([0,1,0],[2,2,2])
2

Thanks ! I defined the functions for this yesterday. I'll keep them for now because they do work as expected. If it's preferred to use numpy, I'll make the changes then.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

image

This circuit makes sense. This is the same way how the SWAP gate was decomposed. Only with a unitary in the middle.

)


def _decompose_to_two_level_arrays(input_gate):
Copy link
Contributor Author

@purva-thakre purva-thakre Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will be discarded after a general function decomposing a n qubit gate to two level arrays is defined.

The plan is to iterate over the row and column indices in a for loop to avoid manually defining indices for the decomposition.

return(array_list)


def decompose_two_qubit_to_two_level_unitary(input_gate):
Copy link
Contributor Author

@purva-thakre purva-thakre Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. Once a general method for decomposition to two level unitaries exists, this function will be discarded.

Defined here for convenience of checking the output of a two qubit gate.

After the general decomposition method has been added, this two qubit module can be discarded completely. For now, it provides the smallest gate whose output could be compared to the output of the general method.

"U6": array_list[5],
}

U1_gate = Gate("U1", targets=[0, 1])
Copy link
Contributor Author

@purva-thakre purva-thakre Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think of better names for two-level unitary gates.

@purva-thakre
Copy link
Contributor Author

Closing this because the code has been added locally in the gray code ordering scheme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants