Fix #168: Remove Pauli measurements#498
Conversation
This commit implements the algorithm described in [BMBdF+21], Theorem 4.12 in Section 4.3: Removing Clifford vertices. The new method `Pattern.remove_pauli_measurements` removes almost all Pauli measurements on non-input nodes, and all of them if the pattern has flow, fixing TeamGraphix#168. [BMBdF+21] Miriam Backens, Hector Miller-Bakewell, Giovanni de Felice, Leo Lobski, and John van de Wetering, There and back again: A circuit extraction tale, Quantum, 2021, https://doi.org/10.22331/q-2021-03-25-421
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #498 +/- ##
==========================================
- Coverage 89.65% 89.01% -0.64%
==========================================
Files 48 49 +1
Lines 7015 7083 +68
==========================================
+ Hits 6289 6305 +16
- Misses 726 778 +52 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
matulni
left a comment
There was a problem hiding this comment.
Thanks a lot for this nice piece of work @thierry-martinez ! Code is super clear and new tool very useful!
Now that remove_pauli_nodes will become a fundamental part of the workflow, I'm even more unhappy that the transformation Pattern -> OpenGraph or Pattern -> XZCorrections does not preserve information about the LC fragment. Maybe it may be worth thinking about this...
There was a problem hiding this comment.
Should we remove Pattern.perform_pauli_measurements and eventually Pattern.remove_input_nodes?
There was a problem hiding this comment.
I've replaced every call to Pattern.perform_pauli_measurements with a call to Pattern.remove_pauli_measurements. I addition, I removed the Pattern.perform_pauli_measurements method and the measure_pauli function.
However, I believe that the method Pattern.remove_input_nodes is still useful, because remove_pauli_measurements only acts on non-input Pauli nodes.
Before deleting perform_pauli_measurements, I ran a few benchmarks:
def compare_perfs(rng: Generator) -> None:
nqubits = 10
depth = 10
patterns = [rand_circuit(nqubits, depth, rng, use_ccx=True, use_rzz=True).transpile().pattern for _ in range(10)]
for pattern in patterns:
pattern.remove_input_nodes()
print(f"{timeit(lambda: [pattern.copy().remove_pauli_measurements() for pattern in patterns], number=10)=}")
print(f"{timeit(lambda: [pattern.copy().perform_pauli_measurements() for pattern in patterns], number=10)=}")On my laptop, the results were:
remove_pauli_measurements: 11.7sperform_pauli_measurements: 27.9s
For fairness I copy each pattern before calling the method; using remove_pauli_measurements(copy=True) would be even faster.
I also benchmarked the example script qft_with_tn.py. The execution times were:
remove_pauli_measurements: 13.84sperform_pauli_measurements: 51.73s
| def test_step_4_no_flow() -> None: | ||
| pattern = Pattern(input_nodes=(0,), output_nodes=(0,), cmds=[Command.N(1), Command.E((0, 1)), Command.M(1)]) | ||
| standardized_pattern = StandardizedPattern.from_pattern(pattern) | ||
| cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) | ||
| standardized_pattern2 = remove_pauli_measurements(cut) | ||
| assert len(standardized_pattern2.m_list) == 1 |
There was a problem hiding this comment.
I'm not sure if I understand this test. Why are we supposed to have len(standardized_pattern2.m_list) == 1 ?
Why Command.M(1) is not eliminated ?
If I add Command.N(0) to the list, the same test won't pass because N(1) is eliminated:
pattern = Pattern(output_nodes=(0,), cmds=[Command.N(0), Command.N(1), Command.E((0, 1)), Command.M(1)])
standardized_pattern = StandardizedPattern.from_pattern(pattern)
cut = PauliPushingCut.from_standardized_pattern(standardized_pattern)
standardized_pattern2 = remove_pauli_measurements(cut)
assert len(standardized_pattern2.m_list) == 1 # FailsThere was a problem hiding this comment.
I've updated the docstring of try_pivot_x_with_output_node to specify that the X-measured node must be connected to an output node that is not also an input node.
The test now includes this comment in test_step_4_no_flow:
# This example tests the case of a pattern that contains a
# non-input X-measured node 1 which is connected to an output node
# 0, where the node 0 is also an input. In this situation Lemma
# 4.11 cannot be applied; this exercices the filtering implemented
# in the `try_pivot_x_with_output_node` method.…`PauliPushingCut`
CI fails with version 11.0.9: https://github.com/TeamGraphix/graphix/actions/runs/25607609586/job/75172334745 CI succeeds with version 10.33.4: https://github.com/TeamGraphix/graphix/actions/runs/25476469782/job/74750924624
| def remove_isolated_internal_nodes(self) -> None: | ||
| """Remove isolated internal nodes.""" | ||
| # Construct the list first since the graph should not be | ||
| # modified while enumerating isolated nodes. | ||
| for node in list(nx.isolates(self.graph)): | ||
| if node not in self.input_node_set and node not in self.output_node_set: | ||
| self._remove_node(node) |
There was a problem hiding this comment.
This method unconditionally removes isolated nodes, even if they are not Pauli-measured!
There was a problem hiding this comment.
This reproduces the current behavior of perform_pauli_measurements.
Unfortunately, the existing test suite does not contain a test that checks this behavior, but the following test would pass:
def test_isolated_nodes_non_pauli() -> None:
pattern = Pattern(cmds=[Command.N(0), Command.N(1), Command.M(0), Command.M(1, ANGLE_PI / 4)])
pattern.perform_pauli_measurements()
assert list(pattern) == []Note that I had to introduce an additional Pauli node 0 since perform_pauli_measurements exits early when there are no Pauli measurements.
I am not sure what the best course of action is. Eliminating isolated nodes seems like a useful optimization, regardless of whether nodes are Pauli or non-Pauli. Moreover, removing Pauli nodes can introduce isolated nodes, and since the procedure already has access to the graph and the logic needed to rebuild the pattern from that graph, it appears to be the appropriate place to perform this cleanup.
That said, I agree that it is somewhat counter-intuitive for the Pauli measurement removal routine to also delete non-Pauli nodes.
Incorporate Pauli results
This commit implements the algorithm described in [BMBdF+21], Theorem 4.12 in Section 4.3: Removing Clifford vertices.
The new method
Pattern.remove_pauli_measurementsremoves almost all Pauli measurements on non-input nodes, and all of them if the pattern has flow, fixing #168.[BMBdF+21] Miriam Backens, Hector Miller-Bakewell, Giovanni de Felice, Leo Lobski, and John van de Wetering, There and back again: A circuit extraction tale, Quantum, 2021,
https://doi.org/10.22331/q-2021-03-25-421