-
Notifications
You must be signed in to change notification settings - Fork 3k
Fix TemplateOptimization dropping the global_phase on substitution
#15943
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 6 commits
a4c77b9
514c953
bdeeb62
82ebf1e
05c0b67
4da2d77
3b0b469
4b67060
51fcdde
893c47d
12b6284
a925da7
bd1833a
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,21 @@ | ||
| --- | ||
| fixes: | ||
| - | | ||
| Fixed :class:`.TemplateOptimization` silently dropping or miscalculating the | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
| ``global_phase`` of circuits and templates during substitution. There were three | ||
| independent bugs: | ||
|
|
||
| 1. :func:`.circuit_to_dagdependency` did not copy ``global_phase`` when converting | ||
| a template :class:`.QuantumCircuit` to a :class:`.DAGDependency`, so any phase | ||
| carried by the template was lost before matching began. | ||
|
|
||
| 2. :class:`.TemplateSubstitution` constructed a new :class:`.DAGDependency` for the | ||
| optimized circuit without copying the original circuit's ``global_phase``, causing | ||
| it to be silently reset to zero whenever at least one substitution was applied. | ||
|
|
||
| 3. When a template carries a nonzero ``global_phase`` ``phi_T`` (meaning its gate | ||
| content alone implements ``e^{-i*phi_T} * I`` while the full operator is ``I``), | ||
| each substitution was not subtracting ``phi_T`` from the output circuit's | ||
| ``global_phase``, producing a result that was not operator-equivalent to the input. | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
|
|
||
| Fixes `#14537 <https://github.com/Qiskit/qiskit/issues/14537>`__. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -737,7 +737,7 @@ def test_consecutive_templates_do_not_apply(self): | |
| qc.swap(0, 1) | ||
| qc.h(0) | ||
| qc_opt = pm.run(qc) | ||
| self.assertTrue(Operator(qc) == Operator(qc_opt)) | ||
| self.assertEqual(Operator(qc), Operator(qc_opt)) | ||
|
Cryoris marked this conversation as resolved.
|
||
|
|
||
| def test_clifford_templates(self): | ||
| """Tests TemplateOptimization pass on several larger examples.""" | ||
|
|
@@ -758,10 +758,109 @@ def test_clifford_templates(self): | |
| seed=seed, | ||
| ) | ||
| qc_opt = pm.run(qc) | ||
| self.assertTrue(Operator(qc) == Operator(qc_opt)) | ||
| self.assertEqual(Operator(qc), Operator(qc_opt)) | ||
| # All of these gates are in the commutation library, i.e. the cache should not be used | ||
| self.assertEqual(scc.num_cached_entries(), 0) | ||
|
|
||
| def test_circuit_global_phase_preserved_after_template_match(self): | ||
| """Test that circuit global_phase survives template optimization (#14537).""" | ||
| qr = QuantumRegister(2, "qr") | ||
| circuit_in = QuantumCircuit(qr) | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.global_phase = np.pi / 4 | ||
|
|
||
| template = QuantumCircuit(QuantumRegister(2, "qrc")) | ||
| template.cx(0, 1) | ||
| template.cx(0, 1) | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
|
|
||
| result = PassManager(TemplateOptimization([template])).run(circuit_in) | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
|
|
||
| self.assertAlmostEqual(float(result.global_phase), np.pi / 4) | ||
|
|
||
| def test_template_nonzero_global_phase_applied_to_circuit(self): | ||
|
Contributor
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. Could we use the example from the issue here, to have a proper regression test? 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. Changed to the 4-gate example in the issue. Changed the comment for that test accordingly, as now there is no substitution happening, so the comment below this is not relevant anymore.
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. Actually, my bad. Just checked that there is partial substitution happening, of course. |
||
| """Test that template global_phase is applied to the circuit per match (#14537). | ||
|
|
||
| HSHSHS has gate unitary e^{i*pi/4} * I; with global_phase = -pi/4 the full | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
| operator is I. After the six gates are cancelled, the output circuit must | ||
| carry global_phase = pi/4 so that Operator(input) == Operator(output). | ||
| """ | ||
| template = QuantumCircuit(1) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.global_phase = -np.pi / 4 | ||
|
|
||
| qr = QuantumRegister(1, "qr") | ||
| circuit_in = QuantumCircuit(qr) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
|
|
||
| result = PassManager(TemplateOptimization([template])).run(circuit_in) | ||
|
|
||
| # All gates cancelled; global_phase must be pi/4 to match the gate unitary. | ||
| self.assertAlmostEqual(float(result.global_phase) % (2 * np.pi), np.pi / 4) | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
| self.assertEqual(result.count_ops(), {}) | ||
| # Operator equivalence confirms the full unitary (including phase) is preserved. | ||
| self.assertEqual(Operator(circuit_in), Operator(result)) | ||
|
|
||
| def test_circuit_and_template_both_have_nonzero_global_phase(self): | ||
|
Contributor
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. This tests the same thing as the previous test, right? Do we need it?
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. I think given that the previous test was changed to 4 gates and therefore |
||
| """Test that circuit and template global phases both contribute to the result (#14537).""" | ||
| template = QuantumCircuit(1) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.h(0) | ||
| template.s(0) | ||
| template.global_phase = -np.pi / 4 | ||
|
|
||
| qr = QuantumRegister(1, "qr") | ||
| circuit_in = QuantumCircuit(qr) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
| circuit_in.h(qr[0]) | ||
| circuit_in.s(qr[0]) | ||
| circuit_in.global_phase = np.pi / 3 | ||
|
|
||
| result = PassManager(TemplateOptimization([template])).run(circuit_in) | ||
|
|
||
| # All gates cancelled; total phase = circuit phase + template compensation | ||
| # = pi/3 + pi/4 = 7*pi/12. | ||
| self.assertAlmostEqual(float(result.global_phase) % (2 * np.pi), 7 * np.pi / 12) | ||
| self.assertEqual(result.count_ops(), {}) | ||
| self.assertEqual(Operator(circuit_in), Operator(result)) | ||
|
|
||
| def test_circuit_global_phase_preserved_with_multiple_template_matches(self): | ||
|
Cryoris marked this conversation as resolved.
Outdated
|
||
| """Test that circuit global_phase is preserved when a template matches multiple times (#14537).""" | ||
| qr = QuantumRegister(2, "qr") | ||
| circuit_in = QuantumCircuit(qr) | ||
| # Two independent pairs of CX gates — the template will match twice. | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.cx(qr[0], qr[1]) | ||
| circuit_in.global_phase = np.pi / 3 | ||
|
|
||
| template = QuantumCircuit(QuantumRegister(2, "qrc")) | ||
| template.cx(0, 1) | ||
| template.cx(0, 1) | ||
|
|
||
| result = PassManager(TemplateOptimization([template])).run(circuit_in) | ||
|
|
||
| # All gates are cancelled; global_phase must be exactly pi/3. | ||
| self.assertAlmostEqual(float(result.global_phase), np.pi / 3) | ||
| self.assertEqual(result.count_ops(), {}) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
Uh oh!
There was an error while loading. Please reload this page.