diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 4a197b909..373736162 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -264,6 +264,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.PauliString.from_numpy`](#stim.PauliString.from_numpy) - [`stim.PauliString.from_unitary_matrix`](#stim.PauliString.from_unitary_matrix) - [`stim.PauliString.iter_all`](#stim.PauliString.iter_all) + - [`stim.PauliString.pauli_indices`](#stim.PauliString.pauli_indices) - [`stim.PauliString.random`](#stim.PauliString.random) - [`stim.PauliString.sign`](#stim.PauliString.sign) - [`stim.PauliString.to_numpy`](#stim.PauliString.to_numpy) @@ -9068,6 +9069,50 @@ def iter_all( """ ``` + +```python +# stim.PauliString.pauli_indices + +# (in class stim.PauliString) +def pauli_indices( + self, + included_paulis: str = "XYZ", +) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the ascending indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ +``` + ```python # stim.PauliString.random diff --git a/doc/stim.pyi b/doc/stim.pyi index 7e476efcc..aa2c065b5 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -6982,6 +6982,43 @@ class PauliString: +_ZX +_ZZ """ + def pauli_indices( + self, + included_paulis: str = "XYZ", + ) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the ascending indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ @staticmethod def random( num_qubits: int, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 7e476efcc..aa2c065b5 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -6982,6 +6982,43 @@ class PauliString: +_ZX +_ZZ """ + def pauli_indices( + self, + included_paulis: str = "XYZ", + ) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the ascending indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ @staticmethod def random( num_qubits: int, diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index 3c335cee0..22160685f 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -636,6 +636,103 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla )DOC") .data()); + c.def( + "pauli_indices", + [](const FlexPauliString &self, const std::string &include) { + std::vector result; + size_t n64 = self.value.xs.num_u64_padded(); + bool keep_i = false; + bool keep_x = false; + bool keep_y = false; + bool keep_z = false; + for (char c : include) { + switch (c) { + case '_': + case 'I': + keep_i = true; + break; + case 'x': + case 'X': + keep_x = true; + break; + case 'y': + case 'Y': + keep_y = true; + break; + case 'z': + case 'Z': + keep_z = true; + break; + default: + throw std::invalid_argument("Invalid character in include string: " + std::string(1, c)); + } + } + for (size_t k = 0; k < n64; k++) { + uint64_t x = self.value.xs.u64[k]; + uint64_t z = self.value.zs.u64[k]; + uint64_t u = 0; + if (keep_i) { + u |= ~x & ~z; + } + if (keep_x) { + u |= x & ~z; + } + if (keep_y) { + u |= x & z; + } + if (keep_z) { + u |= ~x & z; + } + while (u) { + uint8_t v = std::countr_zero(u); + uint64_t q = k * 64 + v; + if (q >= self.value.num_qubits) { + return result; + } + result.push_back(q); + u &= u - 1; + } + } + return result; + }, + pybind11::arg("included_paulis") = "XYZ", + clean_doc_string(R"DOC( + @signature def pauli_indices(self, included_paulis: str = "XYZ") -> List[int]: + Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the ascending indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + )DOC") + .data()); + c.def( "commutes", [](const FlexPauliString &self, const FlexPauliString &other) { diff --git a/src/stim/stabilizers/pauli_string_pybind_test.py b/src/stim/stabilizers/pauli_string_pybind_test.py index 4efff759e..5058e6a55 100644 --- a/src/stim/stabilizers/pauli_string_pybind_test.py +++ b/src/stim/stabilizers/pauli_string_pybind_test.py @@ -899,3 +899,32 @@ def test_backwards_compatibility_init(): assert stim.PauliString(text="XYZ") == stim.PauliString("+XYZ") # noinspection PyArgumentList assert stim.PauliString(other=stim.PauliString("XYZ")) == stim.PauliString("+XYZ") + + +def test_pauli_indices(): + assert stim.PauliString().pauli_indices() == [] + assert stim.PauliString().pauli_indices("X") == [] + assert stim.PauliString().pauli_indices("I") == [] + assert stim.PauliString(5).pauli_indices() == [] + assert stim.PauliString(5).pauli_indices("X") == [] + assert stim.PauliString(5).pauli_indices("I") == [0, 1, 2, 3, 4] + assert stim.PauliString("X1000").pauli_indices() == [1000] + assert stim.PauliString("Y1000").pauli_indices() == [1000] + assert stim.PauliString("Z1000").pauli_indices() == [1000] + assert stim.PauliString("X1000").pauli_indices("YZ") == [] + assert stim.PauliString("Y1000").pauli_indices("XZ") == [] + assert stim.PauliString("Z1000").pauli_indices("XY") == [] + assert stim.PauliString("X1000").pauli_indices("X") == [1000] + assert stim.PauliString("Y1000").pauli_indices("Y") == [1000] + assert stim.PauliString("Z1000").pauli_indices("Z") == [1000] + + assert stim.PauliString("_XYZ").pauli_indices("x") == [1] + assert stim.PauliString("_XYZ").pauli_indices("X") == [1] + assert stim.PauliString("_XYZ").pauli_indices("y") == [2] + assert stim.PauliString("_XYZ").pauli_indices("Y") == [2] + assert stim.PauliString("_XYZ").pauli_indices("z") == [3] + assert stim.PauliString("_XYZ").pauli_indices("Z") == [3] + assert stim.PauliString("_XYZ").pauli_indices("I") == [0] + assert stim.PauliString("_XYZ").pauli_indices("_") == [0] + with pytest.raises(ValueError, match="Invalid character"): + assert stim.PauliString("_XYZ").pauli_indices("k")