-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Constructing Cliffords from quantum circuits with other Cliffords #9169
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
Changes from all commits
064735e
f8c0067
055d5ed
899b111
42ecdc2
c5189b6
194e308
b98bc62
66a18d4
14d5bd5
0f7990f
449cfcd
e779ff9
17f7ce0
e5b9850
d9d9c17
704d385
468d4ed
4effb64
76fb841
bb02719
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| features: | ||
| - | | ||
| Extending :class:`~CollectCliffords` transpiler pass to collect and combine blocks | ||
| of "clifford gates" into :class:`qiskit.quantum_info.Clifford` objects, where the | ||
| "clifford gates" may now also include objects of type :class:`.LinearFunction`, | ||
| :class:`qiskit.quantum_info.Clifford`, and :class:`~qiskit.circuit.library.PauliGate`. | ||
| As an example:: | ||
|
|
||
| from qiskit.circuit import QuantumCircuit | ||
| from qiskit.circuit.library import LinearFunction, PauliGate | ||
| from qiskit.quantum_info.operators import Clifford | ||
| from qiskit.transpiler.passes import CollectCliffords | ||
| from qiskit.transpiler import PassManager | ||
|
|
||
| # Create a Clifford | ||
| cliff_circuit = QuantumCircuit(2) | ||
| cliff_circuit.cx(0, 1) | ||
| cliff_circuit.h(0) | ||
| cliff = Clifford(cliff_circuit) | ||
|
|
||
| # Create a linear function | ||
| lf = LinearFunction([[0, 1], [1, 0]]) | ||
|
|
||
| # Create a pauli gate | ||
| pauli_gate = PauliGate("XYZ") | ||
|
|
||
| # Create a quantum circuit with the above and also simple clifford gates. | ||
| qc = QuantumCircuit(4) | ||
| qc.cz(0, 1) | ||
| qc.append(cliff, [0, 1]) | ||
| qc.h(0) | ||
| qc.append(lf, [0, 2]) | ||
| qc.append(pauli_gate, [0, 2, 1]) | ||
| qc.x(2) | ||
|
|
||
| # Run CollectCliffords transpiler pass | ||
| qct = PassManager(CollectCliffords()).run(qc) | ||
|
|
||
| All the gates will be collected and combined into a single Clifford. Thus the final | ||
| circuit consists of a single Clifford object. | ||
|
|
||
| fixes: | ||
| - | | ||
| Restoring the functionality to construct :class:`qiskit.quantum_info.Clifford` | ||
| objects from quantum circuits containing other :class:`qiskit.quantum_info.Clifford` | ||
| objects. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| import numpy as np | ||
|
|
||
| from qiskit.circuit import QuantumCircuit, Gate | ||
| from qiskit.circuit.library import LinearFunction, PauliGate | ||
| from qiskit.converters import dag_to_circuit, circuit_to_dag | ||
| from qiskit.dagcircuit import DAGOpNode | ||
| from qiskit.transpiler.passes import HighLevelSynthesis | ||
|
|
@@ -536,6 +537,122 @@ def test_do_not_merge_conditional_gates(self): | |
| # Make sure that the condition on the middle gate is not lost | ||
| self.assertIsNotNone(qct.data[1].operation.condition) | ||
|
|
||
| def test_collect_with_cliffords(self): | ||
| """Make sure that collecting Clifford gates and replacing them by Clifford | ||
| works correctly when the gates include other cliffords.""" | ||
|
|
||
| # Create a Clifford over 2 qubits | ||
| cliff_circuit = QuantumCircuit(2) | ||
| cliff_circuit.cx(0, 1) | ||
| cliff_circuit.h(0) | ||
| cliff = Clifford(cliff_circuit) | ||
|
|
||
| qc = QuantumCircuit(3) | ||
| qc.h(0) | ||
| qc.append(cliff, [1, 0]) | ||
| qc.cx(1, 2) | ||
|
|
||
| # Collect clifford gates from the circuit (in this case all the gates must be collected). | ||
| qct = PassManager(CollectCliffords()).run(qc) | ||
| self.assertEqual(len(qct.data), 1) | ||
|
|
||
| # Make sure that the operator for the initial quantum circuit is equivalent to the | ||
| # operator for the collected clifford. | ||
| op1 = Operator(qc) | ||
| op2 = Operator(qct) | ||
| self.assertTrue(op1.equiv(op2)) | ||
|
Comment on lines
+561
to
+563
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests use (Comment repeats for all the tests here.)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ouch, your comment points to a real problem with The problem is that Cliffords are only defined up to a global phase: while "in the real world" The above in code: print the value So right now replacing a block of Clifford gates by a Clifford may drop the global phase, which is indeed the problem to "the transpiler maintaining the global phase". That is, the Note: I do believe that when optimizing Cliffords over a subset of qubits the problem is still only with global and not local phases. BTW, I thought there was an effort to incorporate the full phase into the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I don't think we should replace
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds like a sensible plan to me. If the phase non-equivalence is already in the pass and in Terra 0.22, then there's no massive rush to fix it now before the release, and anyway, we can treat it as a bug fix. Let's open a new issue and discuss the right way forwards. |
||
|
|
||
| def test_collect_with_linear_functions(self): | ||
| """Make sure that collecting Clifford gates and replacing them by Clifford | ||
| works correctly when the gates include LinearFunctions.""" | ||
|
|
||
| # Create a linear function over 2 qubits | ||
| lf = LinearFunction([[0, 1], [1, 0]]) | ||
|
|
||
| qc = QuantumCircuit(3) | ||
| qc.h(0) | ||
| qc.append(lf, [1, 0]) | ||
| qc.cx(1, 2) | ||
|
|
||
| # Collect clifford gates from the circuit (in this case all the gates must be collected). | ||
| qct = PassManager(CollectCliffords()).run(qc) | ||
| self.assertEqual(len(qct.data), 1) | ||
|
|
||
| # Make sure that the operator for the initial quantum circuit is equivalent to the | ||
| # operator for the collected clifford. | ||
| op1 = Operator(qc) | ||
| op2 = Operator(qct) | ||
| self.assertTrue(op1.equiv(op2)) | ||
|
|
||
| def test_collect_with_pauli_gates(self): | ||
| """Make sure that collecting Clifford gates and replacing them by Clifford | ||
| works correctly when the gates include PauliGates.""" | ||
|
|
||
| # Create a pauli gate over 2 qubits | ||
| pauli_gate = PauliGate("XY") | ||
|
|
||
| qc = QuantumCircuit(3) | ||
| qc.h(0) | ||
| qc.append(pauli_gate, [1, 0]) | ||
| qc.cx(1, 2) | ||
|
|
||
| # Collect clifford gates from the circuit (in this case all the gates must be collected). | ||
| qct = PassManager(CollectCliffords()).run(qc) | ||
| self.assertEqual(len(qct.data), 1) | ||
|
|
||
| # Make sure that the operator for the initial quantum circuit is equivalent to the | ||
| # operator for the collected clifford. | ||
| op1 = Operator(qc) | ||
| op2 = Operator(qct) | ||
| self.assertTrue(op1.equiv(op2)) | ||
|
|
||
| def test_collect_with_all_types(self): | ||
| """Make sure that collecting Clifford gates and replacing them by Clifford | ||
| works correctly when the gates include all possible clifford gate types.""" | ||
|
|
||
| cliff_circuit0 = QuantumCircuit(1) | ||
| cliff_circuit0.h(0) | ||
| cliff0 = Clifford(cliff_circuit0) | ||
|
|
||
| cliff_circuit1 = QuantumCircuit(2) | ||
| cliff_circuit1.cz(0, 1) | ||
| cliff_circuit1.s(1) | ||
| cliff1 = Clifford(cliff_circuit1) | ||
|
|
||
| lf1 = LinearFunction([[0, 1], [1, 1]]) | ||
| lf2 = LinearFunction([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) | ||
|
|
||
| pauli_gate1 = PauliGate("X") | ||
| pauli_gate2 = PauliGate("YZX") | ||
|
|
||
| qc = QuantumCircuit(3) | ||
| qc.h(0) | ||
| qc.cx(0, 1) | ||
| qc.append(cliff0, [1]) | ||
| qc.cy(0, 1) | ||
|
|
||
| # not a clifford gate (separating the circuit) | ||
| qc.rx(np.pi / 2, 0) | ||
|
|
||
| qc.append(pauli_gate2, [0, 2, 1]) | ||
| qc.append(lf2, [2, 1, 0]) | ||
| qc.x(0) | ||
| qc.append(pauli_gate1, [1]) | ||
| qc.append(lf1, [1, 0]) | ||
| qc.h(2) | ||
| qc.append(cliff1, [1, 2]) | ||
|
|
||
| # Collect clifford gates from the circuit (we should get two Clifford blocks separated by | ||
| # the RX gate). | ||
| qct = PassManager(CollectCliffords()).run(qc) | ||
| self.assertEqual(len(qct.data), 3) | ||
|
|
||
| # Make sure that the operator for the initial quantum circuit is equivalent to the | ||
| # operator for the circuit with the collected cliffords. | ||
| op1 = Operator(qc) | ||
| op2 = Operator(qct) | ||
| self.assertTrue(op1.equiv(op2)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
Uh oh!
There was an error while loading. Please reload this page.