Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- #386, #433: Added `Statevec.fidelity` and `Statevec.isclose` methods for pure-state fidelity computation and equality check up to global phase.

- #447: `Pattern.perform_pauli_pushing` which calls `StandardizedPattern.perform_pauli_pushing`.

### Fixed

- #429
Expand All @@ -20,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- #438: `ComplexUnit.try_from` now uses `cmath.isclose` for float comparison and has optional parameters `rel_tol` and `abs_tol`.

- #440, #441: `Statevec.nqubits` now returns the correct value.

### Changed

- #181, #423: Structural separation of Pauli measurements
Expand Down
45 changes: 40 additions & 5 deletions graphix/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from dataclasses import dataclass
from types import MappingProxyType
from typing import TYPE_CHECKING
from warnings import warn

import networkx as nx

Expand Down Expand Up @@ -244,10 +245,38 @@ def extract_graph(self) -> nx.Graph[int]:
graph.add_edge(u, v)
return graph

def perform_pauli_pushing(self, leave_nodes: set[Node] | None = None) -> Self:
"""Move all Pauli measurements before the other measurements (except nodes in `leave_nodes`)."""
if leave_nodes is None:
leave_nodes = set()
def perform_pauli_pushing(self, leave_nodes: AbstractSet[Node] | None = None, *, stacklevel: int = 1) -> Self:
"""Move Pauli measurements before the other measurements.

Parameters
----------
leave_nodes : AbstractSet[Node], optional
Nodes that should not be moved. This constraint only
applies to Pauli nodes and has no effect on non-Pauli nodes.
stacklevel : int, optional
Stack level to use for warnings. Defaults to 1, meaning that warnings
are reported at this function's call site.

Returns
-------
Pattern
The pattern in which Pauli measurements have been moved
before the other measurements.
"""
self._warn_non_inferred_pauli_measurements(stacklevel=stacklevel + 1)

if leave_nodes:
leave_non_pauli_nodes = [
cmd.node
for cmd in self.m_list
if not isinstance(cmd.measurement, PauliMeasurement) and cmd.node in leave_nodes
]
if leave_non_pauli_nodes:
warn(
f"`leave_nodes` contains nodes that are not Pauli: {leave_non_pauli_nodes}. The constraint has no effect on these nodes.",
stacklevel=stacklevel + 1,
)

shift_domains: dict[int, set[int]] = {}

def expand_domain(domain: AbstractSet[int]) -> set[int]:
Expand All @@ -268,7 +297,7 @@ def expand_domain(domain: AbstractSet[int]) -> set[int]:
for cmd in self.m_list:
s_domain = expand_domain(cmd.s_domain)
t_domain = expand_domain(cmd.t_domain)
if not isinstance(cmd.measurement, PauliMeasurement) or cmd.node in leave_nodes:
if not isinstance(cmd.measurement, PauliMeasurement) or (leave_nodes and cmd.node in leave_nodes):
non_pauli_list.append(
command.M(node=cmd.node, measurement=cmd.measurement, s_domain=s_domain, t_domain=t_domain)
)
Expand Down Expand Up @@ -592,6 +621,12 @@ def extract_xzcorrections(self) -> XZCorrections[Measurement]:
og, x_corr, z_corr
) # Raises a `XZCorrectionsError` if the input dictionaries are not well formed.

def _warn_non_inferred_pauli_measurements(self, stacklevel: int) -> None:
for m in self.m_list:
if isinstance(m.measurement, BlochMeasurement) and m.measurement.try_to_pauli() is not None:
warn("Pattern with non-inferred Pauli measurements.", stacklevel=stacklevel + 1)
return


def _add_correction_domain(domain_dict: dict[Node, set[Node]], node: Node, domain: set[Node]) -> None:
"""Merge a correction domain into ``domain_dict`` for ``node``.
Expand Down
51 changes: 49 additions & 2 deletions graphix/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from graphix import command, optimization
from graphix.clifford import Clifford
from graphix.command import Command, CommandKind
from graphix.command import Command, CommandKind, Node
from graphix.flow.exceptions import FlowError
from graphix.fundamentals import Axis, Plane, Sign
from graphix.graphsim import GraphState
Expand Down Expand Up @@ -1690,6 +1690,53 @@ def to_bloch(self) -> Pattern:
"""
return self.map(lambda m: m.to_bloch())

def perform_pauli_pushing(
self,
leave_nodes: AbstractSet[Node] | None = None,
copy: bool = False,
standardize: bool = False,
*,
stacklevel: int = 1,
) -> Pattern:
"""Move Pauli measurements before the other measurements.

Parameters
----------
leave_nodes : AbstractSet[Node], optional
Nodes that should not be moved. This constraint only
applies to Pauli nodes and has no effect on non-Pauli nodes.
copy : bool, optional
If ``True``, the current pattern remains unchanged and a
new pattern is returned. The default is ``False``, meaning
that changes are performed in place.
standardize: bool, optional
If ``True``, the pattern is returned in standardized form.
The default is ``False``: the nodes are prepared on a
need-by-need basis, minimizing space usage.
stacklevel : int, optional
Stack level to use for warnings. Defaults to 1, meaning that warnings
are reported at this function's call site.

Returns
-------
Pattern
The pattern in which Pauli measurements have been moved
before the other measurements. If ``copy`` is ``False``,
the result is ``self``.

Notes
-----
This function relies on :func:`StandardizedPattern.perform_pauli_pushing`.
"""
standardized_pattern = optimization.StandardizedPattern.from_pattern(self).perform_pauli_pushing(
leave_nodes, stacklevel=stacklevel + 1
)
pattern = standardized_pattern.to_pattern() if standardize else standardized_pattern.to_space_optimal_pattern()
if copy:
return pattern
self.__seq = pattern.__seq
return self


class PatternError(Exception):
"""Exception subclass to handle pattern errors."""
Expand Down Expand Up @@ -1772,7 +1819,7 @@ def measure_pauli(pattern: Pattern, *, ignore_pauli_with_deps: bool = False, sta
pat = Pattern()
standardized_pattern = optimization.StandardizedPattern.from_pattern(pattern)
if not ignore_pauli_with_deps:
standardized_pattern = standardized_pattern.perform_pauli_pushing()
standardized_pattern = standardized_pattern.perform_pauli_pushing(stacklevel=stacklevel + 1)
output_nodes = set(pattern.output_nodes)
graph = standardized_pattern.extract_graph()
graph_state = GraphState(nodes=graph.nodes, edges=graph.edges, vops=standardized_pattern.c_dict)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,23 @@ def test_extract_xzc_easy_example(self) -> None:
assert xzc.z_corrections == xzc_ref.z_corrections
assert xzc.partial_order_layers == xzc_ref.partial_order_layers

def test_perform_pauli_pushing(self) -> None:
original_pattern = Pattern(
input_nodes=[0], cmds=[N(1), E((1, 0)), N(2), E((1, 2)), M(1, Measurement.XY(0.1)), M(0)]
)
pattern = original_pattern.copy()
pauli_pushed_pattern = pattern.perform_pauli_pushing(copy=True)
assert pattern == original_pattern
assert list(pauli_pushed_pattern) == [N(1), E((0, 1)), M(0), N(2), E((1, 2)), M(1, Measurement.XY(0.1))]
pattern.perform_pauli_pushing()
assert pattern == pauli_pushed_pattern
assert original_pattern.perform_pauli_pushing(leave_nodes={0}, copy=True) == original_pattern
with pytest.warns(UserWarning, match="`leave_nodes` contains nodes that are not Pauli"):
original_pattern.perform_pauli_pushing(leave_nodes={1}, copy=True)
with pytest.warns(UserWarning, match="Pattern with non-inferred Pauli measurements."):
original_pattern.to_bloch().perform_pauli_pushing()
assert original_pattern.perform_pauli_pushing(copy=True, standardize=True).is_standard()


def cp(circuit: Circuit, theta: Angle, control: int, target: int) -> None:
"""Controlled rotation gate, decomposed.""" # noqa: D401
Expand Down