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

Add then method for composing two Clifford tableaux #4096

Merged
merged 23 commits into from
May 20, 2021

Conversation

BichengYing
Copy link
Collaborator

@BichengYing BichengYing commented May 9, 2021

The preparation step for making general Clifford Gates #3639.

The basic logic of implementation is tracking the transformation of all generators of Pauli group. Namely, list the transform of first tableau:U_1X_iU_1' = r_i \Prod_j X_j^{t_(i,j)} Z_i^{s_(i,j)} and list the same thing for second tableau U_2, then combining them.

With this method, it will allow us to build other unnamed Clifford Gates by composition.

@BichengYing BichengYing requested review from cduck, vtomole and a team as code owners May 9, 2021 23:34
@BichengYing BichengYing requested a review from mpharrigan May 9, 2021 23:34
@google-cla google-cla bot added the cla: yes Makes googlebot stop complaining. label May 9, 2021
Copy link
Collaborator

@mpharrigan mpharrigan left a comment

Choose a reason for hiding this comment

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

I can't speak to code correctness, I assume you've gotten that part right. Added some style points. Could you also expand on this PRs description: how will this contribute towards the linked issue?

cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau_test.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
@BichengYing
Copy link
Collaborator Author

I can't speak to code correctness, I assume you've gotten that part right. Added some style points. Could you also expand on this PRs description: how will this contribute towards the linked issue?

Add more context in the description. Addressed the most comment and rename the function according to the Cyqn meeting votes. PTAL, thanks.

@BichengYing BichengYing changed the title Add merged_with method for two Clifford tableau Add then method for composing two Clifford tableaux May 13, 2021
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

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

This looks good, I still have to parse the phase correction logic.

cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
BichengYing and others added 2 commits May 14, 2021 17:23
@BichengYing
Copy link
Collaborator Author

BichengYing commented May 15, 2021

This looks good, I still have to parse the phase correction logic.

The steps were modified by myself. (Different from Qiskit did by killing one-for loop) So Let me add an example here for the record about the phase correction. Each step in the code are corresponding to following case:
image

Copy link
Collaborator

@smitsanghavi smitsanghavi left a comment

Choose a reason for hiding this comment

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

Looks good to me. Added a minor comment regarding the array types.

cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
@BichengYing
Copy link
Collaborator Author

Looks good to me. Added a minor comment regarding the array types.

Hi Smit, I have tried the NumPy matrix multiplication with bool type. I don't think it is equivalent to matrix multiplication with int type then do the modulo 2. Try this simple example:

t = np.array([[1, 0], [1,1]], dtype=bool)
z = np.array([[1, 1], [1,0]], dtype=bool)
print((t @ z).astype(int))
print(np.mod(t.astype(int) @ z.astype(int), 2))

The first one is an all 1 matrix and the second one is not.

@smitsanghavi
Copy link
Collaborator

The first one is an all 1 matrix and the second one is not.

Thanks! That makes sense. The bool one is doing OR instead of + in the matrix multiplication.

cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
prev_row_sum += m2[i]
prev_row_sum = np.mod(prev_row_sum, 2)
swap_phase -= np.sum(prev_row_sum[: self.n] * prev_row_sum[self.n :]) # XZ => -iY
phase[k] = (phase[k] + (swap_phase % 4) / 2) % 2
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add more explanatory comments to these lines? A reference would be great too. It is really not obvious to me that why we are adding observables (rows) together for example to count the Ys..

Copy link
Contributor

Choose a reason for hiding this comment

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

I get the %4, counting 1j phases for each ZX and the 1j * 1j for -1 for XZ. But the way we combine the second tableaux is just hard to parse from the code itself, which I think should be a criteria for readable code, or at least we should have a ref, so that someone can understand it from there. The original Aaronson and Gottesman paper did not talk about combining the tableaux.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, add more sentences. But it is really hard for me to describe it precisely. The key idea is global phase is the only scalar so that we can always move that to the beginning. So we can safely compute the phase of each component whenever we like then add them at last. We have lots of components to generate phases (phase[k] + (swap_phase % 4) / 2) % 2 the first phase[k] is the phase we generate by composing the two tableaux without adjusting the order etc(note like origin table may have +/- phase for each stabilizer).

I don't know a good reference for it. @Strilanc Do you know any good references for it?

Copy link
Contributor

Choose a reason for hiding this comment

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

So, I found this, where Theorem 36 describes a vectorized solution: https://arxiv.org/pdf/2009.03218.pdf, it was so small, I implemented it, but the code is not working yet, it fails on some of the random circuit tests, I probably made a mistake somewhere, at least I thoroughly reviewed your tests, and they do seem to be sound :)

       m1 = self.matrix().astype(int)
        m2 = second.matrix().astype(int)

        lmbda = np.zeros((2 * self.n, 2 * self.n))
        lmbda[:self.n, self.n:] = np.eye(self.n)
        p1 = np.diag(m1 @ lmbda @ m1.T)
        m2Lm2T = m2 @ lmbda @ m2.T
        p2 = np.diag(m2Lm2T)

        s1 = self.rs.astype(int)
        s2 = second.rs.astype(int)

        merged_m = np.mod(m1 @ m2, 2)
        p12 = np.mod(np.diag(merged_m @ lmbda @ merged_m.T), 2)
        assert np.allclose(p12, np.mod(p1 + m1 @ p2, 2)), f"expected: {p12}, got: {p1 + m1 @ p2}"

        signs = np.mod(s1 + p1 * (m1 @ p2) + m1 @ s2 + np.diag(
            m1 @ np.tril(np.outer(p2, p2.T) + m2Lm2T, -1) @ m1.T), 2)

        merged_tableau = CliffordTableau(num_qubits=self.n)
        merged_tableau.xs = merged_m[:, :self.n]
        merged_tableau.zs = merged_m[:, self.n:]
        merged_tableau.rs = signs

Copy link
Collaborator Author

@BichengYing BichengYing May 18, 2021

Choose a reason for hiding this comment

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

Thanks for finding this reference! I spent some time figuring out how to implement it correctly. In coding, it is not as clean as the equations. The ugly part is they use i^a(-1)^bX^cZ^d format, where a,b,c,d is a boolean type. We need to convert to this format first, do the computation in that domain, then convert it back. In both conversions, counting the number of y gates is inevitable. Check this modification based on your code:

        m1 = self.matrix().astype(int)
        m2 = second.matrix().astype(int)

        num_ys1 = np.sum(m1[:, : self.n] * m1[:, self.n :], axis=1)
        num_ys2 = np.sum(m2[:, : self.n] * m2[:, self.n :], axis=1)

        p1 = np.mod(num_ys1, 2)
        p2 = np.mod(num_ys2, 2)

        s1 = self.rs.astype(int) + np.mod(num_ys1, 4) // 2
        s2 = second.rs.astype(int) + np.mod(num_ys2, 4) // 2

        lmbda = np.zeros((2 * self.n, 2 * self.n))
        lmbda[: self.n, self.n :] = np.eye(self.n)
        m2Lm2T = m2 @ lmbda @ m2.T

        m_12 = np.mod(m1.dot(m2), 2)
        p_12 = np.mod(p1 + m1.dot(p2), 2)
        s_12 = (
            s1
            + m1.dot(s2)
            + p1 * m1.dot(p2)
            + np.diag(m1 @ np.tril(np.outer(p2, p2.T) + m2Lm2T, -1) @ m1.T)
        )
        num_ys12 = np.sum(m_12[:, : self.n] * m_12[:, self.n :], axis=1)
        merged_phase = np.mod(p_12 + 2*s_12 - num_ys12, 4) // 2
        merged_m = m_12

        merged_tableau = CliffordTableau(num_qubits=self.n)
        merged_tableau.xs = merged_m[:, : self.n]
        merged_tableau.zs = merged_m[:, self.n :]
        merged_tableau.rs = merged_phase

Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice! I'm impressed how you deeply understood all of this :)
You are right, the Aaronson & Gottesman representation's r is not the same as the s vector in the Gosset paper's representation. In order to calculate with the Gosset method, we need to "remove" the "signs" that come from the PauliStrings that have even number of Ys in them.

I'd be curious to see which implementation is faster. I started doing a bit of benchmarking by modifying the end of your then test:

    n_qubits = 400
    t_final, t_op, expected_t = _three_identical_table(n_qubits)
    seq_op = random_circuit(num_ops=1000, num_qubits=n_qubits)
    for i, (op, args) in enumerate(seq_op):
            t_op = cirq.CliffordTableau(n_qubits)
            op(t_op, *args)
            t_final = t_final.then(t_op)
            op(expected_t, *args)

    assert expected_t == t_final

And started to measure based on n_qubits.
On my machine

qubits new old
100 6.56 30.8
200 61.8 105.74
300 204.04 276.61
400 511.63 670

Which seems like the first version is slower at first blink, but it seems to be growing slower (with an exponent of ~2 instead of ~3.2)...but we need more datapoints. Both should be O(n^3), so these measurements are probably heavily influenced by vectorization and constant factors.

So - I like the Gosset version a bit more because we have a nice paper + proof describing it and it looks vectorized, so it should be faster - but we need to confirm it. I think speed does matter here, if anybody will use this for QEC, they will use large tableaux.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to close this off, the new method is slightly faster but not significantly - my previous data was messed up with manual datataking - this one was automated:

qubits new old
100 24.93 30.93
200 163.97 147.83
300 350.11 378.99
400 807.15 928.34
500 1679.39 1776.07
1000 18750.73 19171.23

Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

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

LGTM with final nits

cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Show resolved Hide resolved
cirq-core/cirq/qis/clifford_tableau.py Outdated Show resolved Hide resolved
@balopat balopat added the automerge Tells CirqBot to sync and merge this PR. (If it's running.) label May 20, 2021
@CirqBot CirqBot added the front_of_queue_automerge CirqBot uses this label to indicate (and remember) what's being merged next. label May 20, 2021
@CirqBot CirqBot merged commit 7d1e168 into quantumlib:master May 20, 2021
@CirqBot CirqBot removed automerge Tells CirqBot to sync and merge this PR. (If it's running.) front_of_queue_automerge CirqBot uses this label to indicate (and remember) what's being merged next. labels May 20, 2021
CirqBot pushed a commit that referenced this pull request May 21, 2021
Another preparation step for making general Clifford Gates #3639.

The implementation relies on the `then` method (composing two Clifford tableaux) check the code in #4096.
This branch is based on the branch used in #4096. Hence, there are a few `then` related codes, which should be orthogonal of this PR.
NoureldinYosri pushed a commit to NoureldinYosri/Cirq that referenced this pull request May 25, 2021
The preparation step for making general Clifford Gates quantumlib#3639.

The basic logic of implementation is tracking the transformation of all generators of Pauli group. Namely, list the transform of first tableau:`U_1X_iU_1' = r_i \Prod_j X_j^{t_(i,j)} Z_i^{s_(i,j)}` and list the same thing for second tableau `U_2`, then combining them.

With this method, it will allow us to build other unnamed Clifford Gates by composition.
NoureldinYosri pushed a commit to NoureldinYosri/Cirq that referenced this pull request May 25, 2021
Another preparation step for making general Clifford Gates quantumlib#3639.

The implementation relies on the `then` method (composing two Clifford tableaux) check the code in quantumlib#4096.
This branch is based on the branch used in quantumlib#4096. Hence, there are a few `then` related codes, which should be orthogonal of this PR.
@BichengYing BichengYing deleted the single_clifford branch August 13, 2021 06:19
CirqBot pushed a commit that referenced this pull request Feb 22, 2022
Add initial Clifford Gate with multiple qubits. Compared with SingleQubitCliffordGate, it has fewer functionalities since we cannot enumerate all of them with PauliGates and several special single qubit properties like Bloch rotation no longer exist. Anyway, it provides several basic interactions:
1. It uses Clifford tableau as underlying data representation (different from the state representation).
2. It can be constructed from a tableau or list of operations (`_has_stabilizer_effect_` only). All Clifford gates can be built through \{S, H, CNOT\}, so we can construct any Clifford Gate from the list of operations. We just cannot pre-define it.
3. Decomposing into several basic operations.
4. Get unitary matrix through decomposing (we cannot do this in a reverse way from unitary to Clifford gate :( ).
5. Know how to interact with ActOnCliffordTableauArgs, i.e. it should be able to use with CliffordTableau simulator (Looks like we don't have that in cirq yet?  @daxfohl will add that? see #4639 and #4748.).

This PR is part of efforts for #3639. Context: this PR doesn't introduce any new algorithms but the key methods are already implemented in #4183 and #4096.
95-martin-orion pushed a commit to 95-martin-orion/Cirq that referenced this pull request Mar 2, 2022
Add initial Clifford Gate with multiple qubits. Compared with SingleQubitCliffordGate, it has fewer functionalities since we cannot enumerate all of them with PauliGates and several special single qubit properties like Bloch rotation no longer exist. Anyway, it provides several basic interactions:
1. It uses Clifford tableau as underlying data representation (different from the state representation).
2. It can be constructed from a tableau or list of operations (`_has_stabilizer_effect_` only). All Clifford gates can be built through \{S, H, CNOT\}, so we can construct any Clifford Gate from the list of operations. We just cannot pre-define it.
3. Decomposing into several basic operations.
4. Get unitary matrix through decomposing (we cannot do this in a reverse way from unitary to Clifford gate :( ).
5. Know how to interact with ActOnCliffordTableauArgs, i.e. it should be able to use with CliffordTableau simulator (Looks like we don't have that in cirq yet?  @daxfohl will add that? see quantumlib#4639 and quantumlib#4748.).

This PR is part of efforts for quantumlib#3639. Context: this PR doesn't introduce any new algorithms but the key methods are already implemented in quantumlib#4183 and quantumlib#4096.
rht pushed a commit to rht/Cirq that referenced this pull request May 1, 2023
The preparation step for making general Clifford Gates quantumlib#3639.

The basic logic of implementation is tracking the transformation of all generators of Pauli group. Namely, list the transform of first tableau:`U_1X_iU_1' = r_i \Prod_j X_j^{t_(i,j)} Z_i^{s_(i,j)}` and list the same thing for second tableau `U_2`, then combining them.

With this method, it will allow us to build other unnamed Clifford Gates by composition.
rht pushed a commit to rht/Cirq that referenced this pull request May 1, 2023
Another preparation step for making general Clifford Gates quantumlib#3639.

The implementation relies on the `then` method (composing two Clifford tableaux) check the code in quantumlib#4096.
This branch is based on the branch used in quantumlib#4096. Hence, there are a few `then` related codes, which should be orthogonal of this PR.
rht pushed a commit to rht/Cirq that referenced this pull request May 1, 2023
Add initial Clifford Gate with multiple qubits. Compared with SingleQubitCliffordGate, it has fewer functionalities since we cannot enumerate all of them with PauliGates and several special single qubit properties like Bloch rotation no longer exist. Anyway, it provides several basic interactions:
1. It uses Clifford tableau as underlying data representation (different from the state representation).
2. It can be constructed from a tableau or list of operations (`_has_stabilizer_effect_` only). All Clifford gates can be built through \{S, H, CNOT\}, so we can construct any Clifford Gate from the list of operations. We just cannot pre-define it.
3. Decomposing into several basic operations.
4. Get unitary matrix through decomposing (we cannot do this in a reverse way from unitary to Clifford gate :( ).
5. Know how to interact with ActOnCliffordTableauArgs, i.e. it should be able to use with CliffordTableau simulator (Looks like we don't have that in cirq yet?  @daxfohl will add that? see quantumlib#4639 and quantumlib#4748.).

This PR is part of efforts for quantumlib#3639. Context: this PR doesn't introduce any new algorithms but the key methods are already implemented in quantumlib#4183 and quantumlib#4096.
harry-phasecraft pushed a commit to PhaseCraft/Cirq that referenced this pull request Oct 31, 2024
The preparation step for making general Clifford Gates quantumlib#3639.

The basic logic of implementation is tracking the transformation of all generators of Pauli group. Namely, list the transform of first tableau:`U_1X_iU_1' = r_i \Prod_j X_j^{t_(i,j)} Z_i^{s_(i,j)}` and list the same thing for second tableau `U_2`, then combining them.

With this method, it will allow us to build other unnamed Clifford Gates by composition.
harry-phasecraft pushed a commit to PhaseCraft/Cirq that referenced this pull request Oct 31, 2024
Another preparation step for making general Clifford Gates quantumlib#3639.

The implementation relies on the `then` method (composing two Clifford tableaux) check the code in quantumlib#4096.
This branch is based on the branch used in quantumlib#4096. Hence, there are a few `then` related codes, which should be orthogonal of this PR.
harry-phasecraft pushed a commit to PhaseCraft/Cirq that referenced this pull request Oct 31, 2024
Add initial Clifford Gate with multiple qubits. Compared with SingleQubitCliffordGate, it has fewer functionalities since we cannot enumerate all of them with PauliGates and several special single qubit properties like Bloch rotation no longer exist. Anyway, it provides several basic interactions:
1. It uses Clifford tableau as underlying data representation (different from the state representation).
2. It can be constructed from a tableau or list of operations (`_has_stabilizer_effect_` only). All Clifford gates can be built through \{S, H, CNOT\}, so we can construct any Clifford Gate from the list of operations. We just cannot pre-define it.
3. Decomposing into several basic operations.
4. Get unitary matrix through decomposing (we cannot do this in a reverse way from unitary to Clifford gate :( ).
5. Know how to interact with ActOnCliffordTableauArgs, i.e. it should be able to use with CliffordTableau simulator (Looks like we don't have that in cirq yet?  @daxfohl will add that? see quantumlib#4639 and quantumlib#4748.).

This PR is part of efforts for quantumlib#3639. Context: this PR doesn't introduce any new algorithms but the key methods are already implemented in quantumlib#4183 and quantumlib#4096.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Makes googlebot stop complaining.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants