Skip to content

Commit

Permalink
Add additional tests of has_flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Strilanc committed Nov 26, 2023
1 parent 8d3c609 commit f0fbfda
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 28 deletions.
20 changes: 15 additions & 5 deletions doc/python_api_reference_vDev.md
Original file line number Diff line number Diff line change
Expand Up @@ -1892,7 +1892,7 @@ def has_flow(
*,
start: Optional[stim.PauliString] = None,
end: Optional[stim.PauliString] = None,
measurements: Iterable[Union[int, stim.GateTarget]] = (),
measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
"""Determines if the circuit has a stabilizer flow or not.
Expand All @@ -1908,10 +1908,6 @@ def has_flow(
A flow like P -> IDENTITY means that the circuit measures P.
A flow like IDENTITY -> IDENTITY means that the circuit contains a detector.
Stim's gate documentation includes the stabilizer flows of each gate.
See Appendix A of https://arxiv.org/abs/2302.02192 for more information on how
flows are defined.
Args:
start: The input into the flow at the start of the circuit. Defaults to None
(the identity Pauli string).
Expand All @@ -1930,6 +1926,13 @@ def has_flow(
Returns:
True if the circuit has the given flow; False otherwise.
References:
Stim's gate documentation includes the stabilizer flows of each gate.
Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
defined and provides a circuit construction for experimentally verifying
their presence.
Examples:
>>> import stim
Expand Down Expand Up @@ -1976,6 +1979,13 @@ def has_flow(
... unsigned=True,
... )
True
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
positive, and a 0% chance of a false negative. So, when the method returns
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
"""
```

Expand Down
20 changes: 15 additions & 5 deletions doc/stim.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ class Circuit:
*,
start: Optional[stim.PauliString] = None,
end: Optional[stim.PauliString] = None,
measurements: Iterable[Union[int, stim.GateTarget]] = (),
measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
"""Determines if the circuit has a stabilizer flow or not.
Expand All @@ -1332,10 +1332,6 @@ class Circuit:
A flow like P -> IDENTITY means that the circuit measures P.
A flow like IDENTITY -> IDENTITY means that the circuit contains a detector.
Stim's gate documentation includes the stabilizer flows of each gate.
See Appendix A of https://arxiv.org/abs/2302.02192 for more information on how
flows are defined.
Args:
start: The input into the flow at the start of the circuit. Defaults to None
(the identity Pauli string).
Expand All @@ -1354,6 +1350,13 @@ class Circuit:
Returns:
True if the circuit has the given flow; False otherwise.
References:
Stim's gate documentation includes the stabilizer flows of each gate.
Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
defined and provides a circuit construction for experimentally verifying
their presence.
Examples:
>>> import stim
Expand Down Expand Up @@ -1400,6 +1403,13 @@ class Circuit:
... unsigned=True,
... )
True
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
positive, and a 0% chance of a false negative. So, when the method returns
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
"""
def inverse(
self,
Expand Down
20 changes: 15 additions & 5 deletions glue/python/src/stim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ class Circuit:
*,
start: Optional[stim.PauliString] = None,
end: Optional[stim.PauliString] = None,
measurements: Iterable[Union[int, stim.GateTarget]] = (),
measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
"""Determines if the circuit has a stabilizer flow or not.
Expand All @@ -1332,10 +1332,6 @@ class Circuit:
A flow like P -> IDENTITY means that the circuit measures P.
A flow like IDENTITY -> IDENTITY means that the circuit contains a detector.
Stim's gate documentation includes the stabilizer flows of each gate.
See Appendix A of https://arxiv.org/abs/2302.02192 for more information on how
flows are defined.
Args:
start: The input into the flow at the start of the circuit. Defaults to None
(the identity Pauli string).
Expand All @@ -1354,6 +1350,13 @@ class Circuit:
Returns:
True if the circuit has the given flow; False otherwise.
References:
Stim's gate documentation includes the stabilizer flows of each gate.
Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
defined and provides a circuit construction for experimentally verifying
their presence.
Examples:
>>> import stim
Expand Down Expand Up @@ -1400,6 +1403,13 @@ class Circuit:
... unsigned=True,
... )
True
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
positive, and a 0% chance of a false negative. So, when the method returns
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
"""
def inverse(
self,
Expand Down
20 changes: 15 additions & 5 deletions src/stim/circuit/circuit.pybind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2276,7 +2276,7 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
pybind11::arg("measurements") = pybind11::none(),
pybind11::arg("unsigned") = false,
clean_doc_string(R"DOC(
@signature def has_flow(self, *, start: Optional[stim.PauliString] = None, end: Optional[stim.PauliString] = None, measurements: Iterable[Union[int, stim.GateTarget]] = (), unsigned: bool = False) -> bool:
@signature def has_flow(self, *, start: Optional[stim.PauliString] = None, end: Optional[stim.PauliString] = None, measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None, unsigned: bool = False) -> bool:
Determines if the circuit has a stabilizer flow or not.
A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
Expand All @@ -2290,10 +2290,6 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
A flow like P -> IDENTITY means that the circuit measures P.
A flow like IDENTITY -> IDENTITY means that the circuit contains a detector.
Stim's gate documentation includes the stabilizer flows of each gate.
See Appendix A of https://arxiv.org/abs/2302.02192 for more information on how
flows are defined.
Args:
start: The input into the flow at the start of the circuit. Defaults to None
(the identity Pauli string).
Expand All @@ -2312,6 +2308,13 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
Returns:
True if the circuit has the given flow; False otherwise.
References:
Stim's gate documentation includes the stabilizer flows of each gate.
Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
defined and provides a circuit construction for experimentally verifying
their presence.
Examples:
>>> import stim
Expand Down Expand Up @@ -2358,6 +2361,13 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
... unsigned=True,
... )
True
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
positive, and a 0% chance of a false negative. So, when the method returns
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
)DOC")
.data());

Expand Down
90 changes: 90 additions & 0 deletions src/stim/circuit/circuit_pybind_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1573,3 +1573,93 @@ def test_detslice_filter_coords_flexibility():
assert str(d1) == str(d3)
assert str(d1) == str(d4)
assert str(d1) == str(d5)


def test_has_flow_ry():
c = stim.Circuit("""
RY 0
""")
assert c.has_flow(end=stim.PauliString("Y"))
assert not c.has_flow(end=stim.PauliString("-Y"))
assert not c.has_flow(end=stim.PauliString("X"))
assert c.has_flow(end=stim.PauliString("Y"), unsigned=True)
assert not c.has_flow(end=stim.PauliString("X"), unsigned=True)
assert c.has_flow(end=stim.PauliString("-Y"), unsigned=True)


def test_has_flow_cxs():
c = stim.Circuit("""
CX 0 1
S 0
""")

assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"))
assert c.has_flow(start=stim.PauliString("Y_"), end=stim.PauliString("-XX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-XX"))

assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"), unsigned=True)
assert c.has_flow(start=stim.PauliString("Y_"), end=stim.PauliString("-XX"), unsigned=True)
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-XX"), unsigned=True)


def test_has_flow_cxm():
c = stim.Circuit("""
CX 0 1
M 1
""")
assert c.has_flow(end=stim.PauliString("_Z"), measurements=[0])
assert c.has_flow(start=stim.PauliString("ZZ"), measurements=[0])
assert c.has_flow(start=stim.PauliString("ZZ"), end=stim.PauliString("_Z"))
assert c.has_flow(start=stim.PauliString("XX"), end=stim.PauliString("X_"))
assert c.has_flow(end=stim.PauliString("_Z"), measurements=[0], unsigned=True)
assert c.has_flow(start=stim.PauliString("ZZ"), measurements=[0], unsigned=True)
assert c.has_flow(start=stim.PauliString("ZZ"), end=stim.PauliString("_Z"), unsigned=True)
assert c.has_flow(start=stim.PauliString("XX"), end=stim.PauliString("X_"), unsigned=True)


def test_has_flow_lattice_surgery():
c = stim.Circuit("""
# Lattice surgery CNOT with feedback.
RX 2
MZZ 2 0
MXX 2 1
MZ 2
CX rec[-1] 1 rec[-3] 1
CZ rec[-2] 0
S 0
""")
assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"))
assert c.has_flow(start=stim.PauliString("Z_"), end=stim.PauliString("Z_"))
assert c.has_flow(start=stim.PauliString("_X"), end=stim.PauliString("_X"))
assert c.has_flow(start=stim.PauliString("_Z"), end=stim.PauliString("ZZ"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))

assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"), unsigned=True)


def test_has_flow_lattice_surgery_without_feedback():
c = stim.Circuit("""
# Lattice surgery CNOT without feedback.
RX 2
MZZ 2 0
MXX 2 1
MZ 2
S 0
""")
assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"), measurements=[1])
assert c.has_flow(start=stim.PauliString("Z_"), end=stim.PauliString("Z_"))
assert c.has_flow(start=stim.PauliString("_X"), end=stim.PauliString("_X"))
assert c.has_flow(start=stim.PauliString("_Z"), end=stim.PauliString("ZZ"), measurements=[0, 2])
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))

assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"))
assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"), unsigned=True, measurements=[1])
21 changes: 13 additions & 8 deletions src/stim/circuit/stabilizer_flow.inl
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,21 @@ std::ostream &operator<<(std::ostream &out, const StabilizerFlow<W> &flow) {
template <size_t W>
std::vector<bool> check_if_circuit_has_unsigned_stabilizer_flows(const Circuit &circuit, SpanRef<const StabilizerFlow<W>> flows) {
auto stats = circuit.compute_stats();
SparseUnsignedRevFrameTracker rev(stats.num_qubits, stats.num_measurements, flows.size(), false);
size_t num_qubits = stats.num_qubits;
for (const auto &flow : flows) {
num_qubits = std::max(num_qubits, flow.input.num_qubits);
num_qubits = std::max(num_qubits, flow.output.num_qubits);
}
SparseUnsignedRevFrameTracker rev(num_qubits, stats.num_measurements, flows.size(), false);

// Add end of flows into frames.
for (size_t f = 0; f < flows.size(); f++) {
const auto &flow = flows[f];
for (size_t q = 0; q < flow.input.num_qubits; q++) {
if (flow.input.xs[q]) {
for (size_t q = 0; q < flow.output.num_qubits; q++) {
if (flow.output.xs[q]) {
rev.xs[q].xor_item(DemTarget::relative_detector_id(f));
}
if (flow.input.zs[q]) {
if (flow.output.zs[q]) {
rev.zs[q].xor_item(DemTarget::relative_detector_id(f));
}
}
Expand All @@ -205,11 +210,11 @@ std::vector<bool> check_if_circuit_has_unsigned_stabilizer_flows(const Circuit &
// Remove start of flows from frames.
for (size_t f = 0; f < flows.size(); f++) {
const auto &flow = flows[f];
for (size_t q = 0; q < flow.output.num_qubits; q++) {
if (flow.output.xs[q]) {
for (size_t q = 0; q < flow.input.num_qubits; q++) {
if (flow.input.xs[q]) {
rev.xs[q].xor_item(DemTarget::relative_detector_id(f));
}
if (flow.output.zs[q]) {
if (flow.input.zs[q]) {
rev.zs[q].xor_item(DemTarget::relative_detector_id(f));
}
}
Expand All @@ -222,7 +227,7 @@ std::vector<bool> check_if_circuit_has_unsigned_stabilizer_flows(const Circuit &
result[t.val()] = false;
}
}
for (const auto &zs : rev.xs) {
for (const auto &zs : rev.zs) {
for (const auto &t : zs) {
result[t.val()] = false;
}
Expand Down
14 changes: 14 additions & 0 deletions src/stim/circuit/stabilizer_flow.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,18 @@ TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_unsigned_stabilizer_
StabilizerFlow<W>::from_str("-___Z -> +___Z"),
});
ASSERT_EQ(results, (std::vector<bool>{1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1}));
});

TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_unsigned_stabilizer_flows_historical_failure, {
auto results = check_if_circuit_has_unsigned_stabilizer_flows<W>(
Circuit(R"CIRCUIT(
CX 0 1
S 0
)CIRCUIT"),
std::vector<StabilizerFlow<W>>{
StabilizerFlow<W>::from_str("X_ -> YX"),
StabilizerFlow<W>::from_str("Y_ -> XX"),
StabilizerFlow<W>::from_str("X_ -> XX"),
});
ASSERT_EQ(results, (std::vector<bool>{1, 1, 0}));
})

0 comments on commit f0fbfda

Please sign in to comment.