-
Notifications
You must be signed in to change notification settings - Fork 64
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
Conversation
return(user_gates, gate_list) | ||
|
||
|
||
def decompose_two_qubit_to_CNOT_and_single_qubit_gates(input_gate): |
There was a problem hiding this comment.
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.
- 00 -> 01 -> 11 -> 10
- 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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :
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.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
) | ||
|
||
|
||
def _decompose_to_two_level_arrays(input_gate): |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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]) |
There was a problem hiding this comment.
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.
Closing this because the code has been added locally in the gray code ordering scheme. |
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.