diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index a27e89e30..e263f9d15 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -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. @@ -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). @@ -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 @@ -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. """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index d7afb4204..291954718 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -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. @@ -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). @@ -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 @@ -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, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index d7afb4204..291954718 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -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. @@ -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). @@ -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 @@ -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, diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 3c2ec87c4..4382ed526 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -2276,7 +2276,7 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ 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 @@ -2290,10 +2290,6 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ 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). @@ -2312,6 +2308,13 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> import stim @@ -2358,6 +2361,13 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ &flow) { template std::vector check_if_circuit_has_unsigned_stabilizer_flows(const Circuit &circuit, SpanRef> 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)); } } @@ -205,11 +210,11 @@ std::vector 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)); } } @@ -222,7 +227,7 @@ std::vector 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; } diff --git a/src/stim/circuit/stabilizer_flow.test.cc b/src/stim/circuit/stabilizer_flow.test.cc index 2e0c15290..887249abb 100644 --- a/src/stim/circuit/stabilizer_flow.test.cc +++ b/src/stim/circuit/stabilizer_flow.test.cc @@ -93,4 +93,18 @@ TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_unsigned_stabilizer_ StabilizerFlow::from_str("-___Z -> +___Z"), }); ASSERT_EQ(results, (std::vector{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( + Circuit(R"CIRCUIT( + CX 0 1 + S 0 + )CIRCUIT"), + std::vector>{ + StabilizerFlow::from_str("X_ -> YX"), + StabilizerFlow::from_str("Y_ -> XX"), + StabilizerFlow::from_str("X_ -> XX"), + }); + ASSERT_EQ(results, (std::vector{1, 1, 0})); })