Skip to content
Open
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
14 changes: 10 additions & 4 deletions graphix/circ_ext/extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ class CliffordMap:
output_nodes: Sequence[int]

@staticmethod
def from_focused_flow(flow: PauliFlow[Measurement]) -> CliffordMap:
def from_focused_flow(flow: PauliFlow[Measurement], *, stacklevel: int = 1) -> CliffordMap:
"""Extract a Clifford map from a focused Pauli flow.

This routine associates two Pauli strings (one per generator of the Pauli group, X and Z) to each input node in ``flow.og``.
Expand All @@ -453,6 +453,9 @@ def from_focused_flow(flow: PauliFlow[Measurement]) -> CliffordMap:
----------
flow : PauliFlow[Measurement]
A focused Pauli flow.
stacklevel : int, optional
Stack level to use for warnings. Defaults to 1, meaning that warnings
are reported at this function's call site.

Returns
-------
Expand All @@ -467,7 +470,7 @@ def from_focused_flow(flow: PauliFlow[Measurement]) -> CliffordMap:
[1] Simmons, 2021 (arXiv:2109.05654).
"""
z_map = clifford_z_map_from_focused_flow(flow)
x_map = clifford_x_map_from_focused_flow(flow)
x_map = clifford_x_map_from_focused_flow(flow, stacklevel=stacklevel + 1)
return CliffordMap(x_map, z_map, flow.og.input_nodes, flow.og.output_nodes)

def to_tableau(self) -> MatGF2:
Expand Down Expand Up @@ -648,7 +651,7 @@ def clifford_z_map_from_focused_flow(flow: PauliFlow[Measurement]) -> tuple[Paul
)


def clifford_x_map_from_focused_flow(flow: PauliFlow[Measurement]) -> tuple[PauliString, ...]:
def clifford_x_map_from_focused_flow(flow: PauliFlow[Measurement], *, stacklevel: int = 1) -> tuple[PauliString, ...]:
r"""Extract the images of the X generators of a Clifford map from a focused Pauli flow.

The resulting Pauli string is given by the correction set of a focused flow of the extended open graph.
Expand All @@ -657,6 +660,9 @@ def clifford_x_map_from_focused_flow(flow: PauliFlow[Measurement]) -> tuple[Paul
----------
flow : PauliFlow[Measurement]
A focused Pauli flow.
stacklevel : int, optional
Stack level to use for warnings. Defaults to 1, meaning that warnings
are reported at this function's call site.

Returns
-------
Expand All @@ -676,7 +682,7 @@ def clifford_x_map_from_focused_flow(flow: PauliFlow[Measurement]) -> tuple[Paul
og_extended, ancillary_inputs_map = extend_input(og)

# Here it's crucial to not infer Pauli measurements to avoid converting measurements inadvertently.
flow_extended = og_extended.to_pauliflow()
flow_extended = og_extended.to_pauliflow(stacklevel=stacklevel + 1)

# `flow_extended` is guaranteed to be focused if `flow` is focused.
# This function assumes that `flow` is focused and does not check it.
Expand Down
10 changes: 8 additions & 2 deletions graphix/flow/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,11 +967,17 @@ def extraction_pauli_strings(self: PauliFlow[Measurement]) -> dict[int, PauliStr
raise ValueError("Flow is not focused.")
return {node: extraction_ps_from_corrected_node(self, node) for node in self.correction_function}

def extract_circuit(self: PauliFlow[Measurement]) -> ExtractionResult:
def extract_circuit(self: PauliFlow[Measurement], *, stacklevel: int = 1) -> ExtractionResult:
"""Extract a circuit from a flow.

This routine assumes that the flow ``self`` is focused (see Notes).

Parameters
----------
stacklevel : int, optional
Stack level to use for warnings. Defaults to 1, meaning that warnings
are reported at this function's call site.

Returns
-------
ExtractionResult
Expand All @@ -991,7 +997,7 @@ def extract_circuit(self: PauliFlow[Measurement]) -> ExtractionResult:
if self.og.output_cliffords:
raise NotImplementedError("Circuit extraction is not supported for open graphs with Clifford decorations.")
pexp_dag = PauliExponentialDAG.from_focused_flow(self)
clifford_map = CliffordMap.from_focused_flow(self)
clifford_map = CliffordMap.from_focused_flow(self, stacklevel=stacklevel + 1)

return ExtractionResult(pexp_dag=pexp_dag, clifford_map=clifford_map)

Expand Down
16 changes: 13 additions & 3 deletions graphix/opengraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,13 +549,16 @@ def to_pauliflow_or_none(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> Pau

Example
-------
>>> import warnings
>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.XZ(0.5), 1: Measurement.XZ(0.5)}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.to_pauliflow()
>>> with warnings.catch_warnings():
... warnings.filterwarnings("ignore", message="Open graph with non-inferred Pauli measurements.")
... og.to_pauliflow()
Traceback (most recent call last):
...
graphix.opengraph.OpenGraphError: The open graph does not have a Pauli flow.
Expand Down Expand Up @@ -632,6 +635,7 @@ def to_circuit(

Examples
--------
>>> import warnings
>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
Expand All @@ -641,14 +645,20 @@ def to_circuit(
... output_nodes=(2, 5, 8),
... measurements=dict.fromkeys((0, 1, 3, 4, 6, 7), Measurement.XY(angle=0)),
... )
>>> og.to_circuit()
>>> with warnings.catch_warnings():
... warnings.filterwarnings("ignore", message="Open graph with non-inferred Pauli measurements.")
... og.to_circuit()
Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)])
>>> # The default compilation passes do not exploit the lower depth of the Pauli flow
>>> # compared the gflow.
>>> og.infer_pauli_measurements().to_circuit()
Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)])
"""
return self.to_pauliflow(stacklevel=stacklevel + 1).extract_circuit().to_circuit(pexp_cp=pexp_cp, cm_cp=cm_cp)
return (
self.to_pauliflow(stacklevel=stacklevel + 1)
.extract_circuit(stacklevel=stacklevel + 1)
.to_circuit(pexp_cp=pexp_cp, cm_cp=cm_cp)
)

def compose(self, other: OpenGraph[_AM_co], mapping: Mapping[int, int]) -> tuple[OpenGraph[_AM_co], dict[int, int]]:
r"""Compose two open graphs by merging subsets of nodes from ``self`` and ``other``, and relabeling the nodes of ``other`` that were not merged.
Expand Down
2 changes: 1 addition & 1 deletion graphix/remove_pauli_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def remove_isolated_internal_nodes(self, *, stacklevel: int = 1) -> None:
# 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:
if node not in self.pauli_measurements:
if self.node_specs[node].pauli_measurement is None:
warn("Non-Pauli measurement on an isolated node was removed.", stacklevel=stacklevel + 1)
self._remove_node(node)

Expand Down
12 changes: 6 additions & 6 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,19 @@ class ReverseDependency:
@nox.parametrize(
"package",
[
ReverseDependency("https://github.com/thierry-martinez/graphix-symbolic", branch="in-place_methods"),
ReverseDependency("https://github.com/thierry-martinez/graphix-stim-backend", branch="rename-simulate"),
ReverseDependency("https://github.com/thierry-martinez/graphix-symbolic", branch="suppress_warnings"),
ReverseDependency("https://github.com/thierry-martinez/graphix-stim-backend", branch="suppress_warnings"),
ReverseDependency("https://github.com/TeamGraphix/graphix-qasm-parser"),
ReverseDependency(
"https://github.com/thierry-martinez/graphix-ibmq", doctest_modules=False, branch="rename-simulate"
"https://github.com/thierry-martinez/graphix-ibmq", doctest_modules=False, branch="suppress_warnings"
),
ReverseDependency("https://github.com/thierry-martinez/graphix-stim-compiler", branch="rename-simulate"),
ReverseDependency("https://github.com/thierry-martinez/graphix-pyzx", branch="rename-simulate"),
ReverseDependency("https://github.com/thierry-martinez/graphix-stim-compiler", branch="suppress_warnings"),
ReverseDependency("https://github.com/thierry-martinez/graphix-pyzx", branch="suppress_warnings"),
ReverseDependency(
"https://github.com/thierry-martinez/veriphix",
doctest_modules=False,
install_target=".[dev]",
branch="rename-simulate",
branch="suppress_warnings",
),
ReverseDependency("https://github.com/thierry-martinez/graphix-mqtbench", branch="rename-simulate"),
],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_branch_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_random_branch_selector_without_pr_calc(fx_rng: Generator, backend: _Bac
"tensornetwork",
],
)
@pytest.mark.parametrize("outcome", itertools.product([0, 1], repeat=3))
@pytest.mark.parametrize("outcome", tuple(itertools.product([0, 1], repeat=3)))
def test_fixed_branch_selector(backend: _BackendLiteral, outcome: list[Outcome]) -> None:
results1: dict[int, Outcome] = dict(enumerate(outcome[:-1]))
results2: dict[int, Outcome] = {2: outcome[2]}
Expand Down
2 changes: 2 additions & 0 deletions tests/test_circ_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ def test_extract_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None:
circuit_ref = rand_circuit(nqubits, depth, rng, use_ccx=False)
pattern = circuit_ref.transpile().pattern

pattern.infer_pauli_measurements()
circuit = pattern.to_opengraph().to_circuit()

s_ref = circuit.simulate(rng=rng).state
Expand Down Expand Up @@ -531,6 +532,7 @@ def test_extract_og(self, test_case: OpenGraph[Measurement], fx_rng: Generator)
assert state.isclose(state_ref)

@pytest.mark.parametrize("infer_pauli", [True, False])
@pytest.mark.filterwarnings("ignore:Open graph with non-inferred Pauli measurements.")
def test_extract_og_infer_pauli(self, infer_pauli: bool, fx_rng: Generator) -> None:
og: OpenGraph[Measurement] = OpenGraph(
graph=nx.Graph([(0, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 6), (5, 7)]),
Expand Down
26 changes: 14 additions & 12 deletions tests/test_clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,20 @@ def test_repr(self, c: Clifford) -> None:

@pytest.mark.parametrize(
("c", "p"),
itertools.product(
Clifford,
(
Pauli(sym, u)
for sym in IXYZ_VALUES
for u in (
ComplexUnit.from_properties(sign=Sign.PLUS, is_imag=False),
ComplexUnit.from_properties(sign=Sign.MINUS, is_imag=False),
ComplexUnit.from_properties(sign=Sign.PLUS, is_imag=True),
ComplexUnit.from_properties(sign=Sign.MINUS, is_imag=True),
)
),
tuple(
itertools.product(
Clifford,
(
Pauli(sym, u)
for sym in IXYZ_VALUES
for u in (
ComplexUnit.from_properties(sign=Sign.PLUS, is_imag=False),
ComplexUnit.from_properties(sign=Sign.MINUS, is_imag=False),
ComplexUnit.from_properties(sign=Sign.PLUS, is_imag=True),
ComplexUnit.from_properties(sign=Sign.MINUS, is_imag=True),
)
),
)
),
)
def test_measure(self, c: Clifford, p: Pauli) -> None:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@


class TestCliffordDB:
@pytest.mark.parametrize(("i", "j"), itertools.product(range(24), range(3)))
@pytest.mark.parametrize(("i", "j"), tuple(itertools.product(range(24), range(3))))
def test_measure(self, i: int, j: int) -> None:
pauli = CLIFFORD[j + 1]
arr = CLIFFORD[i].conjugate().T @ pauli @ CLIFFORD[i]
sym, sgn = CLIFFORD_MEASURE[i][j]
arr_ = complex(sgn) * Ops.from_ixyz(sym)
assert np.allclose(arr, arr_)

@pytest.mark.parametrize(("i", "j"), itertools.product(range(24), range(24)))
@pytest.mark.parametrize(("i", "j"), tuple(itertools.product(range(24), range(24))))
def test_multiplication(self, i: int, j: int) -> None:
op = CLIFFORD[i] @ CLIFFORD[j]
assert Clifford.try_from_matrix(op) == Clifford(CLIFFORD_MUL[i][j])
Expand Down
2 changes: 1 addition & 1 deletion tests/test_density_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ def test_measure(self, outcome: Outcome) -> None:
assert np.allclose(backend.state.rho, expected_matrix)


@pytest.mark.parametrize("permutation", itertools.permutations(range(3)))
@pytest.mark.parametrize("permutation", tuple(itertools.permutations(range(3))))
def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None:
nqubits = len(permutation)
dm = DensityMatrix(rand_state_vector(nqubits, fx_rng))
Expand Down
4 changes: 2 additions & 2 deletions tests/test_fundamentals.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def test_from_properties(self) -> None:
assert ComplexUnit.from_properties(sign=Sign.MINUS) == ComplexUnit.MINUS_ONE
assert ComplexUnit.from_properties(sign=Sign.MINUS, is_imag=True) == ComplexUnit.MINUS_J

@pytest.mark.parametrize(("sign", "is_imag"), itertools.product([Sign.PLUS, Sign.MINUS], [True, False]))
@pytest.mark.parametrize(("sign", "is_imag"), tuple(itertools.product([Sign.PLUS, Sign.MINUS], [True, False])))
def test_properties(self, sign: Sign, is_imag: bool) -> None:
assert ComplexUnit.from_properties(sign=sign, is_imag=is_imag).sign == sign
assert ComplexUnit.from_properties(sign=sign, is_imag=is_imag).is_imag == is_imag
Expand All @@ -114,7 +114,7 @@ def test_str(self) -> None:
assert str(ComplexUnit.MINUS_ONE) == "-1"
assert str(ComplexUnit.MINUS_J) == "-1j"

@pytest.mark.parametrize(("lhs", "rhs"), itertools.product(ComplexUnit, ComplexUnit))
@pytest.mark.parametrize(("lhs", "rhs"), tuple(itertools.product(ComplexUnit, ComplexUnit)))
def test_mul_self(self, lhs: ComplexUnit, rhs: ComplexUnit) -> None:
assert complex(lhs * rhs) == complex(lhs) * complex(rhs)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_isclose(self) -> None:
assert m1.isclose(m2, abs_tol=0.1)


@pytest.mark.parametrize("pauli", PauliMeasurement)
@pytest.mark.parametrize("pauli", tuple(PauliMeasurement))
def test_pauli_to_bloch(pauli: PauliMeasurement) -> None:
bloch = pauli.to_bloch()
pauli_back = bloch.to_pauli_or_none()
Expand Down
9 changes: 5 additions & 4 deletions tests/test_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def test_pauli_measurement_random_circuit_all_paulis(self, fx_bg: PCG64, jumps:
if cmd.kind == CommandKind.M and cmd.node not in input_node_set
)

@pytest.mark.parametrize("pm", PauliMeasurement)
@pytest.mark.parametrize("pm", tuple(PauliMeasurement))
def test_pauli_measurement_single(self, pm: PauliMeasurement) -> None:
pattern = Pattern(input_nodes=[0, 1])
pattern.add(E(nodes=(0, 1)))
Expand All @@ -272,7 +272,7 @@ def test_pauli_measurement_single(self, pm: PauliMeasurement) -> None:
state_ref = pattern_ref.simulate(branch_selector=branch_selector)
assert state.isclose(state_ref)

def test_pauli_measurement(self) -> None:
def test_pauli_measurement(self, fx_rng: Generator) -> None:
# test pattern is obtained from 3-qubit QFT with pauli measurement
circuit = Circuit(3)
for i in range(3):
Expand All @@ -296,8 +296,8 @@ def test_pauli_measurement(self) -> None:
assert isolated_nodes == set()
pattern.minimize_space()
pattern_opt.minimize_space()
state = pattern.simulate()
state_opt = pattern.simulate()
state = pattern.simulate(rng=fx_rng)
state_opt = pattern.simulate(rng=fx_rng)
assert state.isclose(state_opt)

@pytest.mark.parametrize("jumps", range(1, 6))
Expand Down Expand Up @@ -675,6 +675,7 @@ def test_compose_6(self, fx_bg: PCG64, jumps: int) -> None:
assert s.isclose(s_compose)

# Test warning composition after standardization
@pytest.mark.filterwarnings("ignore:Non-Pauli measurement on an isolated node was removed.")
def test_compose_7(self, fx_rng: Generator) -> None:
alpha = 2 * ANGLE_PI * fx_rng.random()

Expand Down
12 changes: 6 additions & 6 deletions tests/test_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ def test_axis(self) -> None:

@pytest.mark.parametrize(
("u", "p"),
itertools.product(ComplexUnit, Pauli),
tuple(itertools.product(ComplexUnit, Pauli)),
)
def test_unit_mul(self, u: ComplexUnit, p: Pauli) -> None:
assert np.allclose((u * p).matrix, complex(u) * p.matrix)

@pytest.mark.parametrize(
("a", "b"),
itertools.product(Pauli, Pauli),
tuple(itertools.product(Pauli, Pauli)),
)
def test_matmul(self, a: Pauli, b: Pauli) -> None:
assert np.allclose((a @ b).matrix, a.matrix @ b.matrix)

@pytest.mark.parametrize("p", Pauli.iterate(symbol_only=True))
@pytest.mark.parametrize("p", tuple(Pauli.iterate(symbol_only=True)))
def test_repr(self, p: Pauli) -> None:
pstr = f"Pauli.{p.symbol.name}"
assert repr(p) == pstr
Expand All @@ -45,7 +45,7 @@ def test_repr(self, p: Pauli) -> None:
assert repr(-1 * p) == f"-{pstr}"
assert repr(-1j * p) == f"-1j * {pstr}"

@pytest.mark.parametrize("p", Pauli.iterate(symbol_only=True))
@pytest.mark.parametrize("p", tuple(Pauli.iterate(symbol_only=True)))
def test_str(self, p: Pauli) -> None:
pstr = p.symbol.name
assert str(p) == pstr
Expand All @@ -54,7 +54,7 @@ def test_str(self, p: Pauli) -> None:
assert str(-1 * p) == f"-{pstr}"
assert str(-1j * p) == f"-1j * {pstr}"

@pytest.mark.parametrize("p", Pauli)
@pytest.mark.parametrize("p", tuple(Pauli))
def test_neg(self, p: Pauli) -> None:
pneg = -p
assert pneg == -p
Expand Down Expand Up @@ -95,7 +95,7 @@ def test_iter_meta(self) -> None:
assert all(False for _ in it)
assert all(False for _ in it_)

@pytest.mark.parametrize(("p", "b"), itertools.product(Pauli.iterate(symbol_only=True), [0, 1]))
@pytest.mark.parametrize(("p", "b"), tuple(itertools.product(Pauli.iterate(symbol_only=True), [0, 1])))
def test_eigenstate(self, p: Pauli, b: int) -> None:
ev = float(Sign.plus_if(b == 0)) if p != Pauli.I else 1
evec = p.eigenstate(b).to_statevector_numpy()
Expand Down
1 change: 1 addition & 0 deletions tests/test_remove_pauli_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def all_bloch_measurement_or_input_node(input_nodes: Iterable[Node], measurement


def check_pattern(pattern: Pattern, rng: Generator) -> None:
pattern.infer_pauli_measurements()
standardized_pattern = StandardizedPattern.from_pattern(pattern)
cut = PauliPushingCut.from_standardizedpattern(standardized_pattern)
standardized_pattern2 = remove_pauli_measurements(cut)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_tnsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ def test_coef_state(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None:

assert abs(coef_tn) == pytest.approx(abs(coef_sv))

@pytest.mark.parametrize(("nqubits", "jumps"), itertools.product(range(2, 6), range(1, 6)))
@pytest.mark.parametrize("nqubits", range(2, 6))
@pytest.mark.parametrize("jumps", range(1, 6))
def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int, fx_rng: Generator) -> None:
rng = Generator(fx_bg.jumped(jumps))
circuit = rand_circuit(nqubits, 3, rng)
Expand Down
Loading
Loading