diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f40251f..b21e2154d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Empty outputs layer removed from flow's partial order. - Flow well-formedness check does not trigger false negative for flows on open graphs without outputs. +- #561: Fixed #555. Method `Pattern.draw` computes Pauli flow from pattern. + ### Changed - #452: Use `uv` for dependency management diff --git a/graphix/pattern.py b/graphix/pattern.py index ddfebcf3d..3c2fcaaca 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1525,7 +1525,7 @@ def draw( - ``DrawPatternAnnotations.XZCorrections``: show the pattern's XZ-corrections. - ``None``: show the underlying open graph only. flow_from_pattern : bool, default=True - If ``True``, the command sequence of the pattern is used to derive flow or gflow structure. If ``False``, only the underlying opengraph is used. + If ``True``, the command sequence of the pattern is used to derive a flow structure. If ``False``, only the underlying open graph is used. options : Unpack[DrawKwargs] Options controlling graph visualization. See :class:`VisualizationOptions`. stacklevel : int, optional @@ -1535,37 +1535,36 @@ def draw( Raises ------ PatternError - If the underlying opengraph does not have flow. + If the underlying open graph does not have flow. Notes ----- - If ``flow_from_pattern==True`` but the pattern is not compatible with a gflow, an attempt to be extract the flow from the underlying open graph will be made while warning the user. + If ``flow_from_pattern==True`` but the pattern is not compatible with a causal flow, gflow, or Pauli flow, an attempt to be extract the flow from the underlying open graph will be made while warning the user. """ if annotations is None: og = self.extract_opengraph() - gv = GraphVisualizer.from_opengraph(og=og, **options) + gv = GraphVisualizer.from_opengraph(og, **options) else: + xzcorrections = self.extract_xzcorrections() match annotations: case DrawPatternAnnotations.Flow: flow: PauliFlow[Measurement] | None = None if flow_from_pattern: try: - xz_corrections = self.extract_xzcorrections().downcast_bloch() - except TypeError: - pass - else: + # We first try to extract a causal flow because the plotting + # algorithm for causal flows yields nicer plots. + flow = xzcorrections.downcast_bloch().to_causal_flow() + except (TypeError, FlowError): try: - flow = xz_corrections.to_causal_flow() + # If the pattern is not consistent with a Pauli flow, + # it won't be consistent with a gflow. + flow = xzcorrections.to_pauli_flow() except FlowError: - try: - flow = xz_corrections.to_gflow() - except FlowError: - warn( - "The pattern is not consistent with a causal flow or a gflow. An attempt to be extract the flow from the underlying open graph will be made.", - stacklevel=stacklevel, - ) - + warn( + "The pattern is not consistent with a flow. An attempt to be extract the flow from the underlying open graph will be made.", + stacklevel=stacklevel, + ) if flow is None: og = self.extract_opengraph() try: @@ -1581,11 +1580,10 @@ def draw( "The pattern's open graph does not have Pauli flow. Consider setting the `annotations` parameter to `None` or `DrawPatternAnnotations.XZCorrections`." ) - gv = GraphVisualizer.from_flow(flow=flow, **options) + gv = GraphVisualizer.from_flow(flow, **options) case DrawPatternAnnotations.XZCorrections: - xzcorrections = self.extract_xzcorrections() - gv = GraphVisualizer.from_xzcorrections(xz_corr=xzcorrections, **options) + gv = GraphVisualizer.from_xzcorrections(xzcorrections, **options) gv.visualize() diff --git a/tests/baseline/test_draw_graph_reference_pauli_flow_False.png b/tests/baseline/test_draw_graph_reference_pauli_flow_False.png new file mode 100644 index 000000000..d8dacc8ef Binary files /dev/null and b/tests/baseline/test_draw_graph_reference_pauli_flow_False.png differ diff --git a/tests/baseline/test_draw_graph_reference_pauli_flow_True.png b/tests/baseline/test_draw_graph_reference_pauli_flow_True.png new file mode 100644 index 000000000..26ff5393d Binary files /dev/null and b/tests/baseline/test_draw_graph_reference_pauli_flow_True.png differ diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 598082bc8..60df85614 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -199,6 +199,19 @@ def test_og() -> None: pattern.draw(annotations=None) +@pytest.mark.usefixtures("mock_plot") +def test_non_determinist() -> None: + pattern = Pattern( + input_nodes=[0], + cmds=[command.N(1), command.E((0, 1)), command.M(0)], + ) + with pytest.warns( + UserWarning, + match="The pattern is not consistent with a flow. An attempt to be extract the flow from the underlying open graph will be made.", + ): + pattern.draw() + + # Compare with baseline/test_draw_graph_reference.png # Update baseline by running: pytest --mpl-generate-path=tests/baseline @pytest.mark.usefixtures("mock_plot") @@ -242,9 +255,9 @@ def test_xzcorr_draw() -> Figure: @pytest.mark.usefixtures("mock_plot") -@pytest.mark.parametrize("flow_and_not_pauli_presimulate", [False, True]) +@pytest.mark.parametrize("flow_from_pattern_and_to_bloch", [False, True]) @pytest.mark.mpl_image_compare -def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: +def test_draw_graph_reference(flow_from_pattern_and_to_bloch: bool) -> Figure: circuit = Circuit(3) circuit.cnot(0, 1) circuit.cnot(2, 1) @@ -252,16 +265,24 @@ def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: circuit.x(2) circuit.cnot(2, 1) pattern = circuit.transpile().pattern - if flow_and_not_pauli_presimulate: - # Pauli flow extraction from pattern is not implemented yet; - # therefore, the pattern should not contain Pauli measurements - # to have causal flow. + if flow_from_pattern_and_to_bloch: pattern = pattern.to_bloch() else: pattern = pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.standardize() pattern.draw( - flow_from_pattern=flow_and_not_pauli_presimulate, node_distance=(1, 1), measurement_labels=True, legend=False + flow_from_pattern=flow_from_pattern_and_to_bloch, node_distance=(1, 1), measurement_labels=True, legend=False ) return plt.gcf() + + +@pytest.mark.usefixtures("mock_plot") +@pytest.mark.parametrize("flow_from_pattern", [False, True]) +@pytest.mark.mpl_image_compare +def test_draw_graph_reference_pauli_flow(flow_from_pattern: bool) -> Figure: + circuit = Circuit(2) + circuit.rzz(0, 1, 0.3) + pattern = circuit.transpile().pattern.infer_pauli_measurements() + pattern.draw(flow_from_pattern=flow_from_pattern, node_distance=(1, 1), measurement_labels=True, legend=False) + return plt.gcf()