From d0775fad4fb054572f1a4cc209cd1050b3fb7fd1 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 30 Nov 2023 00:49:09 -0800 Subject: [PATCH] Refactor `stim_pybind::PyPaulString` -> `stim::FlexPauliString` (#675) - Also fix `sinter.Task.strong_id` not correctly checking for circuit not being set - Fix `stim.PauliString.__init__` specifying pybind11 overloads instead of manually doing resolution. - This notably improved the documentation of the method. - Force positional arg on the init argument, but silently maintain backwards compatibility with old keyword - Fix `stim.PauliString.from_unitary_matrix` signature typo `float` -> `complex` - Fix `stim.PauliString.before` signature typo `after` -> `before` --- doc/python_api_reference_vDev.md | 110 ++-- doc/stim.pyi | 110 ++-- file_lists/source_files_no_main | 1 + file_lists/test_files | 1 + glue/python/src/stim/__init__.pyi | 110 ++-- glue/sample/src/sinter/_task.py | 2 +- src/stim.h | 1 + src/stim/circuit/circuit.pybind.cc | 16 +- src/stim/circuit/stabilizer_flow.inl | 8 +- src/stim/dem/detector_error_model.pybind.cc | 2 +- src/stim/simulators/frame_simulator.pybind.cc | 6 +- .../simulators/tableau_simulator.pybind.cc | 20 +- src/stim/stabilizers/conversions.inl | 8 +- src/stim/stabilizers/flex_pauli_string.cc | 196 +++++++ src/stim/stabilizers/flex_pauli_string.h | 60 ++ .../stabilizers/flex_pauli_string.test.cc | 11 + src/stim/stabilizers/pauli_string.pybind.cc | 515 +++++++----------- src/stim/stabilizers/pauli_string.pybind.h | 40 +- .../stabilizers/pauli_string_iter.pybind.cc | 4 +- .../stabilizers/pauli_string_pybind_test.py | 22 +- src/stim/stabilizers/tableau.pybind.cc | 40 +- 21 files changed, 637 insertions(+), 646 deletions(-) create mode 100644 src/stim/stabilizers/flex_pauli_string.cc create mode 100644 src/stim/stabilizers/flex_pauli_string.h create mode 100644 src/stim/stabilizers/flex_pauli_string.test.cc diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 17b0e1aae..ee5b43b49 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -8029,90 +8029,48 @@ def __imul__( # stim.PauliString.__init__ # (in class stim.PauliString) -@staticmethod def __init__( - *args, - **kwargs, -): - """Overloaded function. - - 1. __init__(self: stim.PauliString, num_qubits: int) -> None - - Creates an identity Pauli string over the given number of qubits. - - Examples: - >>> import stim - >>> p = stim.PauliString(5) - >>> print(p) - +_____ - - Args: - num_qubits: The number of qubits the Pauli string acts on. - - - 2. __init__(self: stim.PauliString, text: str) -> None - - Creates a stim.PauliString from a text string. - - The string can optionally start with a sign ('+', '-', 'i', '+i', or '-i'). - The rest of the string should be characters from '_IXYZ' where - '_' and 'I' mean identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means - Pauli Z. - - Examples: - >>> import stim - >>> print(stim.PauliString("YZ")) - +YZ - >>> print(stim.PauliString("+IXYZ")) - +_XYZ - >>> print(stim.PauliString("-___X_")) - -___X_ - >>> print(stim.PauliString("iX")) - +iX - - Args: - text: A text description of the Pauli string's contents, such as "+XXX" or - "-_YX" or "-iZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY". - - Returns: - The created stim.PauliString. - + self, + arg: Union[None, int, str, stim.PauliString, Iterable[Union[int, 'Literal["_", "I", "X", "Y", "Z"]']]] = None, + /, +) -> None: + """Initializes a stim.PauliString from the given argument. - 3. __init__(self: stim.PauliString, copy: stim.PauliString) -> None + When given a string, the string is parsed as a pauli string. The string can + optionally start with a sign ('+', '-', 'i', '+i', or '-i'). The rest of the + string should be characters from '_IXYZ' where '_' and 'I' mean identity, 'X' + means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. - Creates a copy of a stim.PauliString. + Arguments: + arg [position-only]: This can be a variety of types, including: + None (default): initializes an empty Pauli string. + int: initializes an identity Pauli string of the given length. + str: initializes by parsing the given text. + stim.PauliString: initializes a copy of the given Pauli string. + Iterable: initializes by interpreting each item as a Pauli. + Each item can be a single-qubit Pauli string (like "X"), + or an integer. Integers use the convention 0=I, 1=X, 2=Y, 3=Z. Examples: >>> import stim - >>> a = stim.PauliString("YZ") - >>> b = stim.PauliString(a) - >>> b is a - False - >>> b == a - True - Args: - copy: The pauli string to make a copy of. + >>> stim.PauliString("-XYZ") + stim.PauliString("-XYZ") + >>> stim.PauliString() + stim.PauliString("+") - 4. __init__(self: stim.PauliString, pauli_indices: List[int]) -> None + >>> stim.PauliString(5) + stim.PauliString("+_____") - Creates a stim.PauliString from a list of integer pauli indices. + >>> stim.PauliString(stim.PauliString("XX")) + stim.PauliString("+XX") - The indexing scheme that is used is: - 0 -> I - 1 -> X - 2 -> Y - 3 -> Z + >>> stim.PauliString([0, 1, 3, 2]) + stim.PauliString("+_XZY") - Examples: - >>> import stim - >>> stim.PauliString([0, 1, 2, 3, 0, 3]) - stim.PauliString("+_XYZ_Z") - - Args: - pauli_indices: A sequence of integers from 0 to 3 (inclusive) indicating - paulis. + >>> stim.PauliString("X" for _ in range(4)) + stim.PauliString("+XXXX") """ ``` @@ -8482,19 +8440,19 @@ def after( # (in class stim.PauliString) @overload -def after( +def before( self, operation: Union[stim.Circuit, stim.CircuitInstruction], ) -> stim.PauliString: pass @overload -def after( +def before( self, operation: stim.Tableau, targets: Iterable[int], ) -> stim.PauliString: pass -def after( +def before( self, operation: Union[stim.Circuit, stim.Tableau, stim.CircuitInstruction], targets: Optional[Iterable[int]] = None, @@ -8658,7 +8616,7 @@ def from_numpy( # (in class stim.PauliString) @staticmethod def from_unitary_matrix( - matrix: Iterable[Iterable[float]], + matrix: Iterable[Iterable[Union[int, float, complex]]], *, endian: str = 'little', unsigned: bool = False, diff --git a/doc/stim.pyi b/doc/stim.pyi index 89ed69b39..697608bdc 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -6155,90 +6155,48 @@ class PauliString: Returns: The mutated Pauli string. """ - @staticmethod def __init__( - *args, - **kwargs, - ): - """Overloaded function. - - 1. __init__(self: stim.PauliString, num_qubits: int) -> None - - Creates an identity Pauli string over the given number of qubits. - - Examples: - >>> import stim - >>> p = stim.PauliString(5) - >>> print(p) - +_____ - - Args: - num_qubits: The number of qubits the Pauli string acts on. - - - 2. __init__(self: stim.PauliString, text: str) -> None - - Creates a stim.PauliString from a text string. - - The string can optionally start with a sign ('+', '-', 'i', '+i', or '-i'). - The rest of the string should be characters from '_IXYZ' where - '_' and 'I' mean identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means - Pauli Z. - - Examples: - >>> import stim - >>> print(stim.PauliString("YZ")) - +YZ - >>> print(stim.PauliString("+IXYZ")) - +_XYZ - >>> print(stim.PauliString("-___X_")) - -___X_ - >>> print(stim.PauliString("iX")) - +iX - - Args: - text: A text description of the Pauli string's contents, such as "+XXX" or - "-_YX" or "-iZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY". - - Returns: - The created stim.PauliString. - + self, + arg: Union[None, int, str, stim.PauliString, Iterable[Union[int, 'Literal["_", "I", "X", "Y", "Z"]']]] = None, + /, + ) -> None: + """Initializes a stim.PauliString from the given argument. - 3. __init__(self: stim.PauliString, copy: stim.PauliString) -> None + When given a string, the string is parsed as a pauli string. The string can + optionally start with a sign ('+', '-', 'i', '+i', or '-i'). The rest of the + string should be characters from '_IXYZ' where '_' and 'I' mean identity, 'X' + means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. - Creates a copy of a stim.PauliString. + Arguments: + arg [position-only]: This can be a variety of types, including: + None (default): initializes an empty Pauli string. + int: initializes an identity Pauli string of the given length. + str: initializes by parsing the given text. + stim.PauliString: initializes a copy of the given Pauli string. + Iterable: initializes by interpreting each item as a Pauli. + Each item can be a single-qubit Pauli string (like "X"), + or an integer. Integers use the convention 0=I, 1=X, 2=Y, 3=Z. Examples: >>> import stim - >>> a = stim.PauliString("YZ") - >>> b = stim.PauliString(a) - >>> b is a - False - >>> b == a - True - - Args: - copy: The pauli string to make a copy of. + >>> stim.PauliString("-XYZ") + stim.PauliString("-XYZ") - 4. __init__(self: stim.PauliString, pauli_indices: List[int]) -> None + >>> stim.PauliString() + stim.PauliString("+") - Creates a stim.PauliString from a list of integer pauli indices. + >>> stim.PauliString(5) + stim.PauliString("+_____") - The indexing scheme that is used is: - 0 -> I - 1 -> X - 2 -> Y - 3 -> Z + >>> stim.PauliString(stim.PauliString("XX")) + stim.PauliString("+XX") - Examples: - >>> import stim - >>> stim.PauliString([0, 1, 2, 3, 0, 3]) - stim.PauliString("+_XYZ_Z") + >>> stim.PauliString([0, 1, 3, 2]) + stim.PauliString("+_XZY") - Args: - pauli_indices: A sequence of integers from 0 to 3 (inclusive) indicating - paulis. + >>> stim.PauliString("X" for _ in range(4)) + stim.PauliString("+XXXX") """ def __itruediv__( self, @@ -6517,19 +6475,19 @@ class PauliString: string before the operation. """ @overload - def after( + def before( self, operation: Union[stim.Circuit, stim.CircuitInstruction], ) -> stim.PauliString: pass @overload - def after( + def before( self, operation: stim.Tableau, targets: Iterable[int], ) -> stim.PauliString: pass - def after( + def before( self, operation: Union[stim.Circuit, stim.Tableau, stim.CircuitInstruction], targets: Optional[Iterable[int]] = None, @@ -6671,7 +6629,7 @@ class PauliString: """ @staticmethod def from_unitary_matrix( - matrix: Iterable[Iterable[float]], + matrix: Iterable[Iterable[Union[int, float, complex]]], *, endian: str = 'little', unsigned: bool = False, diff --git a/file_lists/source_files_no_main b/file_lists/source_files_no_main index ffaf8a6ed..27511b6ea 100644 --- a/file_lists/source_files_no_main +++ b/file_lists/source_files_no_main @@ -86,3 +86,4 @@ src/stim/simulators/sparse_rev_frame_tracker.cc src/stim/simulators/transform_without_feedback.cc src/stim/simulators/vector_simulator.cc src/stim/stabilizers/conversions.cc +src/stim/stabilizers/flex_pauli_string.cc diff --git a/file_lists/test_files b/file_lists/test_files index 44306ccfc..51d1ffdef 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -71,6 +71,7 @@ src/stim/simulators/tableau_simulator.test.cc src/stim/simulators/transform_without_feedback.test.cc src/stim/simulators/vector_simulator.test.cc src/stim/stabilizers/conversions.test.cc +src/stim/stabilizers/flex_pauli_string.test.cc src/stim/stabilizers/pauli_string.test.cc src/stim/stabilizers/pauli_string_iter.test.cc src/stim/stabilizers/tableau.test.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 89ed69b39..697608bdc 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -6155,90 +6155,48 @@ class PauliString: Returns: The mutated Pauli string. """ - @staticmethod def __init__( - *args, - **kwargs, - ): - """Overloaded function. - - 1. __init__(self: stim.PauliString, num_qubits: int) -> None - - Creates an identity Pauli string over the given number of qubits. - - Examples: - >>> import stim - >>> p = stim.PauliString(5) - >>> print(p) - +_____ - - Args: - num_qubits: The number of qubits the Pauli string acts on. - - - 2. __init__(self: stim.PauliString, text: str) -> None - - Creates a stim.PauliString from a text string. - - The string can optionally start with a sign ('+', '-', 'i', '+i', or '-i'). - The rest of the string should be characters from '_IXYZ' where - '_' and 'I' mean identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means - Pauli Z. - - Examples: - >>> import stim - >>> print(stim.PauliString("YZ")) - +YZ - >>> print(stim.PauliString("+IXYZ")) - +_XYZ - >>> print(stim.PauliString("-___X_")) - -___X_ - >>> print(stim.PauliString("iX")) - +iX - - Args: - text: A text description of the Pauli string's contents, such as "+XXX" or - "-_YX" or "-iZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY". - - Returns: - The created stim.PauliString. - + self, + arg: Union[None, int, str, stim.PauliString, Iterable[Union[int, 'Literal["_", "I", "X", "Y", "Z"]']]] = None, + /, + ) -> None: + """Initializes a stim.PauliString from the given argument. - 3. __init__(self: stim.PauliString, copy: stim.PauliString) -> None + When given a string, the string is parsed as a pauli string. The string can + optionally start with a sign ('+', '-', 'i', '+i', or '-i'). The rest of the + string should be characters from '_IXYZ' where '_' and 'I' mean identity, 'X' + means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. - Creates a copy of a stim.PauliString. + Arguments: + arg [position-only]: This can be a variety of types, including: + None (default): initializes an empty Pauli string. + int: initializes an identity Pauli string of the given length. + str: initializes by parsing the given text. + stim.PauliString: initializes a copy of the given Pauli string. + Iterable: initializes by interpreting each item as a Pauli. + Each item can be a single-qubit Pauli string (like "X"), + or an integer. Integers use the convention 0=I, 1=X, 2=Y, 3=Z. Examples: >>> import stim - >>> a = stim.PauliString("YZ") - >>> b = stim.PauliString(a) - >>> b is a - False - >>> b == a - True - - Args: - copy: The pauli string to make a copy of. + >>> stim.PauliString("-XYZ") + stim.PauliString("-XYZ") - 4. __init__(self: stim.PauliString, pauli_indices: List[int]) -> None + >>> stim.PauliString() + stim.PauliString("+") - Creates a stim.PauliString from a list of integer pauli indices. + >>> stim.PauliString(5) + stim.PauliString("+_____") - The indexing scheme that is used is: - 0 -> I - 1 -> X - 2 -> Y - 3 -> Z + >>> stim.PauliString(stim.PauliString("XX")) + stim.PauliString("+XX") - Examples: - >>> import stim - >>> stim.PauliString([0, 1, 2, 3, 0, 3]) - stim.PauliString("+_XYZ_Z") + >>> stim.PauliString([0, 1, 3, 2]) + stim.PauliString("+_XZY") - Args: - pauli_indices: A sequence of integers from 0 to 3 (inclusive) indicating - paulis. + >>> stim.PauliString("X" for _ in range(4)) + stim.PauliString("+XXXX") """ def __itruediv__( self, @@ -6517,19 +6475,19 @@ class PauliString: string before the operation. """ @overload - def after( + def before( self, operation: Union[stim.Circuit, stim.CircuitInstruction], ) -> stim.PauliString: pass @overload - def after( + def before( self, operation: stim.Tableau, targets: Iterable[int], ) -> stim.PauliString: pass - def after( + def before( self, operation: Union[stim.Circuit, stim.Tableau, stim.CircuitInstruction], targets: Optional[Iterable[int]] = None, @@ -6671,7 +6629,7 @@ class PauliString: """ @staticmethod def from_unitary_matrix( - matrix: Iterable[Iterable[float]], + matrix: Iterable[Iterable[Union[int, float, complex]]], *, endian: str = 'little', unsigned: bool = False, diff --git a/glue/sample/src/sinter/_task.py b/glue/sample/src/sinter/_task.py index cf4e4a215..c358bde3b 100644 --- a/glue/sample/src/sinter/_task.py +++ b/glue/sample/src/sinter/_task.py @@ -183,7 +183,7 @@ def strong_id_value(self) -> Dict[str, Any]: >>> task.strong_id_value() {'circuit': 'H 0', 'decoder': 'pymatching', 'decoder_error_model': '', 'postselection_mask': None, 'json_metadata': None} """ - if self.decoder is None: + if self.circuit is None: raise ValueError("Can't compute strong_id until `circuit` is set.") if self.decoder is None: raise ValueError("Can't compute strong_id until `decoder` is set.") diff --git a/src/stim.h b/src/stim.h index 98fcb5ac5..bddc097cf 100644 --- a/src/stim.h +++ b/src/stim.h @@ -99,6 +99,7 @@ #include "stim/simulators/transform_without_feedback.h" #include "stim/simulators/vector_simulator.h" #include "stim/stabilizers/conversions.h" +#include "stim/stabilizers/flex_pauli_string.h" #include "stim/stabilizers/pauli_string.h" #include "stim/stabilizers/pauli_string_iter.h" #include "stim/stabilizers/pauli_string_ref.h" diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 47d3d0181..167e8fe40 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -101,7 +101,7 @@ void circuit_append( // Maintain backwards compatibility to when there was always exactly one argument. pybind11::object used_arg; - if (!arg.is(pybind11::none())) { + if (!arg.is_none()) { used_arg = arg; } else if (backwards_compat && GATE_DATA.at(gate_name).arg_count == 1) { used_arg = pybind11::make_tuple(0.0); @@ -219,13 +219,13 @@ uint64_t obj_to_abs_detector_id(const pybind11::handle &obj, bool fail) { throw std::invalid_argument(ss.str()); } -PyPauliString arg_to_pauli_string(const pybind11::object &arg) { +FlexPauliString arg_to_pauli_string(const pybind11::object &arg) { if (arg.is_none()) { - return PyPauliString(PauliString(0)); - } else if (pybind11::isinstance(arg)) { - return pybind11::cast(arg); + return FlexPauliString(PauliString(0)); + } else if (pybind11::isinstance(arg)) { + return pybind11::cast(arg); } else if (pybind11::isinstance(arg)) { - return PyPauliString::from_text(pybind11::cast(arg).c_str()); + return FlexPauliString::from_text(pybind11::cast(arg).c_str()); } else { throw std::invalid_argument( "Don't know how to get a stim.PauliString from " + pybind11::cast(pybind11::repr(arg))); @@ -287,8 +287,8 @@ StabilizerFlow args_to_flow( flow = StabilizerFlow::from_str( pybind11::cast(shorthand).c_str(), num_circuit_measurements); } else { - PyPauliString in = arg_to_pauli_string(start); - PyPauliString out = arg_to_pauli_string(end); + FlexPauliString in = arg_to_pauli_string(start); + FlexPauliString out = arg_to_pauli_string(end); if (in.imag != out.imag) { throw std::invalid_argument( "The requested flow '" + in.str() + " -> " + out.str() + diff --git a/src/stim/circuit/stabilizer_flow.inl b/src/stim/circuit/stabilizer_flow.inl index 6183f16d7..57c651818 100644 --- a/src/stim/circuit/stabilizer_flow.inl +++ b/src/stim/circuit/stabilizer_flow.inl @@ -2,8 +2,8 @@ #include "stim/circuit/circuit.h" #include "stim/circuit/stabilizer_flow.h" #include "stim/simulators/frame_simulator_util.h" -#include "stim/simulators/tableau_simulator.h" #include "stim/simulators/sparse_rev_frame_tracker.h" +#include "stim/simulators/tableau_simulator.h" namespace stim { @@ -68,7 +68,8 @@ std::vector sample_if_circuit_has_stabilizer_flows( return result; } -inline bool parse_rec_allowing_non_negative(std::string_view rec, size_t num_measurements_for_non_neg, GateTarget *out) { +inline bool parse_rec_allowing_non_negative( + std::string_view rec, size_t num_measurements_for_non_neg, GateTarget *out) { if (rec.size() < 6 || rec[0] != 'r' || rec[1] != 'e' || rec[2] != 'c' || rec[3] != '[' || rec.back() != ']') { throw std::invalid_argument(""); // Caught and given a message below. } @@ -227,7 +228,8 @@ std::ostream &operator<<(std::ostream &out, const StabilizerFlow &flow) { } template -std::vector check_if_circuit_has_unsigned_stabilizer_flows(const Circuit &circuit, SpanRef> flows) { +std::vector check_if_circuit_has_unsigned_stabilizer_flows( + const Circuit &circuit, SpanRef> flows) { auto stats = circuit.compute_stats(); size_t num_qubits = stats.num_qubits; for (const auto &flow : flows) { diff --git a/src/stim/dem/detector_error_model.pybind.cc b/src/stim/dem/detector_error_model.pybind.cc index f4186504d..5169a7e77 100644 --- a/src/stim/dem/detector_error_model.pybind.cc +++ b/src/stim/dem/detector_error_model.pybind.cc @@ -40,7 +40,7 @@ std::string stim_pybind::detector_error_model_repr(const DetectorErrorModel &sel } std::vector python_arg_to_instruction_arguments(const pybind11::object &arg) { - if (arg.is(pybind11::none())) { + if (arg.is_none()) { return {}; } try { diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index f4c18e70c..56e6795a8 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -59,12 +59,12 @@ pybind11::object peek_pauli_flips(const FrameSimulator &self, const pybind11: py_index_to_optional_size_t(py_instance_index, self.batch_size, "instance_index", "batch_size"); if (instance_index.has_value()) { - return pybind11::cast(PyPauliString(self.get_frame(*instance_index))); + return pybind11::cast(FlexPauliString(self.get_frame(*instance_index))); } - std::vector result; + std::vector result; for (size_t k = 0; k < self.batch_size; k++) { - result.push_back(PyPauliString(self.get_frame(k))); + result.push_back(FlexPauliString(self.get_frame(k))); } return pybind11::cast(std::move(result)); } diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc index ed1199962..f1ce8b978 100644 --- a/src/stim/simulators/tableau_simulator.pybind.cc +++ b/src/stim/simulators/tableau_simulator.pybind.cc @@ -33,8 +33,8 @@ void do_obj(TableauSimulator &self, const pybind11::object &obj) { } else if (pybind11::isinstance(obj)) { const CircuitRepeatBlock &block = pybind11::cast(obj); self.expand_do_circuit(block.body, block.repeat_count); - } else if (pybind11::isinstance(obj)) { - const PyPauliString &pauli_string = pybind11::cast(obj); + } else if (pybind11::isinstance(obj)) { + const FlexPauliString &pauli_string = pybind11::cast(obj); self.ensure_large_enough_for_qubits(pauli_string.value.num_qubits); self.paulis(pauli_string.value); } else if (pybind11::isinstance(obj)) { @@ -333,7 +333,7 @@ void stim_pybind::pybind_tableau_simulator_methods( "canonical_stabilizers", [](const TableauSimulator &self) { auto stabilizers = self.canonical_stabilizers(); - std::vector result; + std::vector result; result.reserve(stabilizers.size()); for (auto &s : stabilizers) { result.emplace_back(std::move(s), false); @@ -451,7 +451,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "do_pauli_string", - [](TableauSimulator &self, PyPauliString &pauli_string) { + [](TableauSimulator &self, FlexPauliString &pauli_string) { self.ensure_large_enough_for_qubits(pauli_string.value.num_qubits); self.paulis(pauli_string.value); }, @@ -1315,7 +1315,7 @@ void stim_pybind::pybind_tableau_simulator_methods( "peek_bloch", [](TableauSimulator &self, size_t target) { self.ensure_large_enough_for_qubits(target + 1); - return PyPauliString(self.peek_bloch(target)); + return FlexPauliString(self.peek_bloch(target)); }, pybind11::arg("target"), clean_doc_string(R"DOC( @@ -1365,7 +1365,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "peek_observable_expectation", - [](const TableauSimulator &self, const PyPauliString &observable) -> int8_t { + [](const TableauSimulator &self, const FlexPauliString &observable) -> int8_t { if (observable.imag) { throw std::invalid_argument( "Observable isn't Hermitian; it has imaginary sign. Need observable.sign in [1, -1]."); @@ -1421,7 +1421,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "measure_observable", [](TableauSimulator &self, - const PyPauliString &observable, + const FlexPauliString &observable, double flip_probability) -> bool { if (observable.imag) { throw std::invalid_argument( @@ -1468,7 +1468,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "postselect_observable", - [](TableauSimulator &self, const PyPauliString &observable, bool desired_value) { + [](TableauSimulator &self, const FlexPauliString &observable, bool desired_value) { if (observable.imag) { throw std::invalid_argument( "Observable isn't Hermitian; it has imaginary sign. Need observable.sign in [1, -1]."); @@ -1888,7 +1888,7 @@ void stim_pybind::pybind_tableau_simulator_methods( if (result.second.num_qubits == 0) { return pybind11::make_tuple(result.first, pybind11::none()); } - return pybind11::make_tuple(result.first, PyPauliString(result.second)); + return pybind11::make_tuple(result.first, FlexPauliString(result.second)); }, pybind11::arg("target"), clean_doc_string(R"DOC( @@ -1953,7 +1953,7 @@ void stim_pybind::pybind_tableau_simulator_methods( bool allow_underconstrained) { std::vector> converted_stabilizers; for (const auto &stabilizer : stabilizers) { - const PyPauliString &p = pybind11::cast(stabilizer); + const FlexPauliString &p = pybind11::cast(stabilizer); if (p.imag) { throw std::invalid_argument("Stabilizers can't have imaginary sign."); } diff --git a/src/stim/stabilizers/conversions.inl b/src/stim/stabilizers/conversions.inl index 0787e2174..e93d37428 100644 --- a/src/stim/stabilizers/conversions.inl +++ b/src/stim/stabilizers/conversions.inl @@ -1,8 +1,8 @@ #include "stim/probability_util.h" +#include "stim/simulators/graph_simulator.h" #include "stim/simulators/tableau_simulator.h" #include "stim/simulators/vector_simulator.h" #include "stim/stabilizers/conversions.h" -#include "stim/simulators/graph_simulator.h" namespace stim { @@ -242,7 +242,8 @@ Circuit tableau_to_circuit_elimination_method(const Tableau &tableau) { }; auto apply2 = [&](GateType gate_type, uint32_t target, uint32_t target2) { remaining.inplace_scatter_append(GATE_DATA[gate_type].tableau(), {target, target2}); - recorded_circuit.safe_append(gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); + recorded_circuit.safe_append( + gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); }; auto x_out = [&](size_t inp, size_t out) { const auto &p = remaining.xs[inp]; @@ -407,7 +408,8 @@ Tableau unitary_to_tableau(const std::vector> }; auto apply2 = [&](GateType gate_type, uint32_t target, uint32_t target2) { sim.apply(gate_type, target, target2); - recorded_circuit.safe_append(gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); + recorded_circuit.safe_append( + gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); }; // Undo the permutation and also single-qubit phases. diff --git a/src/stim/stabilizers/flex_pauli_string.cc b/src/stim/stabilizers/flex_pauli_string.cc new file mode 100644 index 000000000..2f3d4b91c --- /dev/null +++ b/src/stim/stabilizers/flex_pauli_string.cc @@ -0,0 +1,196 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stim/stabilizers/flex_pauli_string.h" + +using namespace stim; + +FlexPauliString::FlexPauliString(size_t num_qubits) : value(num_qubits), imag(false) { +} + +FlexPauliString::FlexPauliString(const PauliStringRef val, bool imag) : value(val), imag(imag) { +} + +FlexPauliString::FlexPauliString(PauliString &&val, bool imag) : value(std::move(val)), imag(imag) { +} + +FlexPauliString FlexPauliString::operator+(const FlexPauliString &rhs) const { + FlexPauliString copy = *this; + copy += rhs; + return copy; +} + +FlexPauliString &FlexPauliString::operator+=(const FlexPauliString &rhs) { + if (&rhs == this) { + *this *= 2; + return *this; + } + + size_t n = value.num_qubits; + value.ensure_num_qubits(value.num_qubits + rhs.value.num_qubits, 1.1); + for (size_t k = 0; k < rhs.value.num_qubits; k++) { + value.xs[k + n] = rhs.value.xs[k]; + value.zs[k + n] = rhs.value.zs[k]; + } + *this *= rhs.get_phase(); + return *this; +} + +FlexPauliString FlexPauliString::operator*(std::complex scale) const { + FlexPauliString copy = *this; + copy *= scale; + return copy; +} + +FlexPauliString FlexPauliString::operator/(const std::complex &scale) const { + FlexPauliString copy = *this; + copy /= scale; + return copy; +} + +FlexPauliString FlexPauliString::operator*(size_t power) const { + FlexPauliString copy = *this; + copy *= power; + return copy; +} + +FlexPauliString &FlexPauliString::operator*=(size_t power) { + switch (power & 3) { + case 0: + imag = false; + value.sign = false; + break; + case 1: + break; + case 2: + value.sign = imag; + imag = false; + break; + case 3: + value.sign ^= imag; + break; + } + + value = PauliString::from_func(value.sign, value.num_qubits * power, [&](size_t k) { + return "_XZY"[value.xs[k % value.num_qubits] + 2 * value.zs[k % value.num_qubits]]; + }); + return *this; +} + +FlexPauliString &FlexPauliString::operator/=(const std::complex &rhs) { + if (rhs == std::complex{+1, 0}) { + return *this; + } else if (rhs == std::complex{-1, 0}) { + return *this *= std::complex{-1, 0}; + } else if (rhs == std::complex{0, 1}) { + return *this *= std::complex{0, -1}; + } else if (rhs == std::complex{0, -1}) { + return *this *= std::complex{0, +1}; + } + throw std::invalid_argument("divisor not in (1, -1, 1j, -1j)"); +} + +FlexPauliString &FlexPauliString::operator*=(std::complex scale) { + if (scale == std::complex(-1)) { + value.sign ^= true; + } else if (scale == std::complex(0, 1)) { + value.sign ^= imag; + imag ^= true; + } else if (scale == std::complex(0, -1)) { + imag ^= true; + value.sign ^= imag; + } else if (scale != std::complex(1)) { + throw std::invalid_argument("phase factor not in [1, -1, 1, 1j]"); + } + return *this; +} + +bool FlexPauliString::operator==(const FlexPauliString &other) const { + return value == other.value && imag == other.imag; +} + +bool FlexPauliString::operator!=(const FlexPauliString &other) const { + return !(*this == other); +} + +std::complex FlexPauliString::get_phase() const { + std::complex result{value.sign ? -1.0f : +1.0f}; + if (imag) { + result *= std::complex{0, 1}; + } + return result; +} + +FlexPauliString FlexPauliString::operator*(const FlexPauliString &rhs) const { + FlexPauliString copy = *this; + copy *= rhs; + return copy; +} + +FlexPauliString &FlexPauliString::operator*=(const FlexPauliString &rhs) { + value.ensure_num_qubits(rhs.value.num_qubits, 1.1); + if (rhs.value.num_qubits < value.num_qubits) { + FlexPauliString copy = rhs; + copy.value.ensure_num_qubits(value.num_qubits, 1.0); + *this *= copy; + return *this; + } + + uint8_t log_i = value.ref().inplace_right_mul_returning_log_i_scalar(rhs.value.ref()); + if (log_i & 2) { + value.sign ^= true; + } + if (log_i & 1) { + *this *= std::complex{0, 1}; + } + if (rhs.imag) { + *this *= std::complex{0, 1}; + } + return *this; +} + +std::ostream &stim::operator<<(std::ostream &out, const FlexPauliString &v) { + out << "+-"[v.value.sign]; + if (v.imag) { + out << 'i'; + } + for (size_t k = 0; k < v.value.num_qubits; k++) { + out << "_XZY"[v.value.xs[k] + 2 * v.value.zs[k]]; + } + return out; +} + +std::string FlexPauliString::str() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +FlexPauliString FlexPauliString::from_text(std::string_view text) { + std::complex factor{1, 0}; + int offset = 0; + if (text.starts_with('i')) { + factor = {0, 1}; + offset = 1; + } else if (text.starts_with("-i")) { + factor = {0, -1}; + offset = 2; + } else if (text.starts_with("+i")) { + factor = {0, 1}; + offset = 2; + } + FlexPauliString value{PauliString::from_str(text.substr(offset)), false}; + value *= factor; + return value; +} diff --git a/src/stim/stabilizers/flex_pauli_string.h b/src/stim/stabilizers/flex_pauli_string.h new file mode 100644 index 000000000..df3894842 --- /dev/null +++ b/src/stim/stabilizers/flex_pauli_string.h @@ -0,0 +1,60 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _STIM_STABILIZERS_FLEX_PAULI_STRING_H +#define _STIM_STABILIZERS_FLEX_PAULI_STRING_H + +#include + +#include "stim/mem/sparse_xor_vec.h" +#include "stim/stabilizers/pauli_string.h" + +namespace stim { + +/// This is the backing class for the python stim.PauliString. +/// It's more flexible than the C++ stim::PauliString. +/// For example, it allows imaginary signs. +struct FlexPauliString { + stim::PauliString value; + bool imag; + + FlexPauliString(size_t num_qubits); + FlexPauliString(const stim::PauliStringRef val, bool imag = false); + FlexPauliString(stim::PauliString &&val, bool imag = false); + + static FlexPauliString from_text(std::string_view text); + std::complex get_phase() const; + + FlexPauliString operator+(const FlexPauliString &rhs) const; + FlexPauliString &operator+=(const FlexPauliString &rhs); + + FlexPauliString operator*(size_t power) const; + FlexPauliString operator*(std::complex scale) const; + FlexPauliString operator*(const FlexPauliString &rhs) const; + FlexPauliString operator/(const std::complex &divisor) const; + + FlexPauliString &operator*=(size_t power); + FlexPauliString &operator*=(std::complex scale); + FlexPauliString &operator*=(const FlexPauliString &rhs); + FlexPauliString &operator/=(const std::complex &divisor); + + bool operator==(const FlexPauliString &other) const; + bool operator!=(const FlexPauliString &other) const; + std::string str() const; +}; +std::ostream &operator<<(std::ostream &out, const FlexPauliString &v); + +} // namespace stim + +#endif diff --git a/src/stim/stabilizers/flex_pauli_string.test.cc b/src/stim/stabilizers/flex_pauli_string.test.cc new file mode 100644 index 000000000..59d7cdbdd --- /dev/null +++ b/src/stim/stabilizers/flex_pauli_string.test.cc @@ -0,0 +1,11 @@ +#include "stim/stabilizers/flex_pauli_string.h" + +#include "gtest/gtest.h" + +using namespace stim; + +TEST(flex_pauli_string, mul) { + FlexPauliString p1 = FlexPauliString::from_text("iXYZ"); + FlexPauliString p2 = FlexPauliString::from_text("i__Z"); + ASSERT_EQ(p1 * p2, FlexPauliString::from_text("-XY_")); +} diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index 1283fc1b7..b0951d322 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -14,29 +14,17 @@ #include "stim/stabilizers/pauli_string.h" -#include "pauli_string_iter.h" #include "stim/circuit/circuit_instruction.pybind.h" #include "stim/py/base.pybind.h" #include "stim/py/numpy.pybind.h" #include "stim/stabilizers/pauli_string.pybind.h" +#include "stim/stabilizers/pauli_string_iter.h" #include "stim/stabilizers/tableau.h" using namespace stim; using namespace stim_pybind; -PyPauliString::PyPauliString(const PauliStringRef val, bool imag) : value(val), imag(imag) { -} - -PyPauliString::PyPauliString(PauliString &&val, bool imag) : value(std::move(val)), imag(imag) { -} - -PyPauliString PyPauliString::operator+(const PyPauliString &rhs) const { - PyPauliString copy = *this; - copy += rhs; - return copy; -} - -pybind11::object PyPauliString::to_unitary_matrix(const std::string &endian) const { +pybind11::object flex_pauli_string_to_unitary_matrix(const stim::FlexPauliString &ps, const std::string &endian) { bool little_endian; if (endian == "little") { little_endian = true; @@ -45,7 +33,7 @@ pybind11::object PyPauliString::to_unitary_matrix(const std::string &endian) con } else { throw std::invalid_argument("endian not in ['little', 'big']"); } - size_t q = value.num_qubits; + size_t q = ps.value.num_qubits; if (q >= 32) { throw std::invalid_argument("Too many qubits."); } @@ -57,19 +45,19 @@ pybind11::object PyPauliString::to_unitary_matrix(const std::string &endian) con x <<= 1; z <<= 1; if (little_endian) { - x |= value.xs[q - k - 1]; - z |= value.zs[q - k - 1]; + x |= ps.value.xs[q - k - 1]; + z |= ps.value.zs[q - k - 1]; } else { - x |= value.xs[k]; - z |= value.zs[k]; + x |= ps.value.xs[k]; + z |= ps.value.zs[k]; } } uint8_t start_phase = 0; start_phase += std::popcount(x & z); - if (imag) { + if (ps.imag) { start_phase += 1; } - if (value.sign) { + if (ps.value.sign) { start_phase += 2; } for (size_t col = 0; col < n; col++) { @@ -97,123 +85,30 @@ pybind11::object PyPauliString::to_unitary_matrix(const std::string &endian) con return pybind11::array_t>({sn, sn}, {sn * itemsize, itemsize}, buffer, free_when_done); } -PyPauliString &PyPauliString::operator+=(const PyPauliString &rhs) { - if (&rhs == this) { - *this *= 2; - return *this; - } - - size_t n = value.num_qubits; - value.ensure_num_qubits(value.num_qubits + rhs.value.num_qubits, 1.1); - for (size_t k = 0; k < rhs.value.num_qubits; k++) { - value.xs[k + n] = rhs.value.xs[k]; - value.zs[k + n] = rhs.value.zs[k]; - } - *this *= rhs.get_phase(); - return *this; -} - -PyPauliString PyPauliString::operator*(std::complex scale) const { - PyPauliString copy = *this; - copy *= scale; - return copy; -} - -PyPauliString PyPauliString::operator/(const std::complex &scale) const { - PyPauliString copy = *this; - copy /= scale; - return copy; -} - -PyPauliString PyPauliString::operator*(const pybind11::object &rhs) const { - PyPauliString copy = *this; - copy *= rhs; - return copy; -} - -PyPauliString PyPauliString::operator*(size_t power) const { - PyPauliString copy = *this; - copy *= power; - return copy; -} - -PyPauliString &PyPauliString::operator*=(size_t power) { - switch (power & 3) { - case 0: - imag = false; - value.sign = false; - break; - case 1: - break; - case 2: - value.sign = imag; - imag = false; - break; - case 3: - value.sign ^= imag; - break; - } - - value = PauliString::from_func(value.sign, value.num_qubits * power, [&](size_t k) { - return "_XZY"[value.xs[k % value.num_qubits] + 2 * value.zs[k % value.num_qubits]]; - }); - return *this; -} - -PyPauliString &PyPauliString::operator*=(const pybind11::object &rhs) { - if (pybind11::isinstance(rhs)) { - return *this *= pybind11::cast(rhs); +FlexPauliString &flex_pauli_string_obj_imul(FlexPauliString &lhs, const pybind11::object &rhs) { + if (pybind11::isinstance(rhs)) { + return lhs *= pybind11::cast(rhs); } else if (rhs.equal(pybind11::cast(std::complex{+1, 0}))) { - return *this; + return lhs; } else if (rhs.equal(pybind11::cast(std::complex{-1, 0}))) { - return *this *= std::complex{-1, 0}; + return lhs *= std::complex{-1, 0}; } else if (rhs.equal(pybind11::cast(std::complex{0, 1}))) { - return *this *= std::complex{0, 1}; + return lhs *= std::complex{0, 1}; } else if (rhs.equal(pybind11::cast(std::complex{0, -1}))) { - return *this *= std::complex{0, -1}; + return lhs *= std::complex{0, -1}; } else if (pybind11::isinstance(rhs)) { pybind11::ssize_t k = pybind11::int_(rhs); if (k >= 0) { - return *this *= (pybind11::size_t)k; + return lhs *= (pybind11::size_t)k; } } throw std::out_of_range("need isinstance(rhs, (stim.PauliString, int)) or rhs in (1, -1, 1j, -1j)"); } -PyPauliString &PyPauliString::operator/=(const std::complex &rhs) { - if (rhs == std::complex{+1, 0}) { - return *this; - } else if (rhs == std::complex{-1, 0}) { - return *this *= std::complex{-1, 0}; - } else if (rhs == std::complex{0, 1}) { - return *this *= std::complex{0, -1}; - } else if (rhs == std::complex{0, -1}) { - return *this *= std::complex{0, +1}; - } - throw std::invalid_argument("divisor not in (1, -1, 1j, -1j)"); -} - -PyPauliString &PyPauliString::operator*=(std::complex scale) { - if (scale == std::complex(-1)) { - value.sign ^= true; - } else if (scale == std::complex(0, 1)) { - value.sign ^= imag; - imag ^= true; - } else if (scale == std::complex(0, -1)) { - imag ^= true; - value.sign ^= imag; - } else if (scale != std::complex(1)) { - throw std::invalid_argument("phase factor not in [1, -1, 1, 1j]"); - } - return *this; -} - -bool PyPauliString::operator==(const PyPauliString &other) const { - return value == other.value && imag == other.imag; -} - -bool PyPauliString::operator!=(const PyPauliString &other) const { - return !(*this == other); +FlexPauliString flex_pauli_string_obj_mul(const FlexPauliString &lhs, const pybind11::object &rhs) { + FlexPauliString copy = lhs; + flex_pauli_string_obj_imul(copy, rhs); + return copy; } size_t numpy_to_size(const pybind11::object &numpy_array, size_t expected_size) { @@ -268,67 +163,7 @@ size_t numpy_pair_to_size( return n2; } -std::complex PyPauliString::get_phase() const { - std::complex result{value.sign ? -1.0f : +1.0f}; - if (imag) { - result *= std::complex{0, 1}; - } - return result; -} - -PyPauliString PyPauliString::operator*(const PyPauliString &rhs) const { - PyPauliString copy = *this; - copy *= rhs; - return copy; -} - -PyPauliString &PyPauliString::operator*=(const PyPauliString &rhs) { - value.ensure_num_qubits(rhs.value.num_qubits, 1.1); - if (rhs.value.num_qubits < value.num_qubits) { - PyPauliString copy = rhs; - copy.value.ensure_num_qubits(value.num_qubits, 1.0); - *this *= copy; - return *this; - } - - uint8_t log_i = value.ref().inplace_right_mul_returning_log_i_scalar(rhs.value.ref()); - if (log_i & 2) { - value.sign ^= true; - } - if (log_i & 1) { - *this *= std::complex{0, 1}; - } - if (rhs.imag) { - *this *= std::complex{0, 1}; - } - return *this; -} - -std::string PyPauliString::str() const { - auto sub = value.str(); - if (imag) { - sub = sub.substr(0, 1) + "i" + sub.substr(1); - } - return sub; -} -PyPauliString PyPauliString::from_text(const char *text) { - std::complex factor{1, 0}; - int offset = 0; - if (text[0] == 'i') { - factor = {0, 1}; - offset = 1; - } else if (text[0] == '-' && text[1] == 'i') { - factor = {0, -1}; - offset = 2; - } else if (text[0] == '+' && text[1] == 'i') { - factor = {0, 1}; - offset = 2; - } - PyPauliString value{PauliString::from_str(text + offset), false}; - value *= factor; - return value; -} -PyPauliString PyPauliString::from_unitary_matrix( +FlexPauliString flex_pauli_string_from_unitary_matrix( const pybind11::object &matrix, const std::string &endian, bool ignore_sign) { bool little_endian; if (endian == "little") { @@ -443,7 +278,8 @@ PyPauliString PyPauliString::from_unitary_matrix( } uint8_t leftover_phase = phases[0] + std::popcount(x & z); - PyPauliString result(PauliString(q), (leftover_phase & 1) != 0); + FlexPauliString result(q); + result.imag = (leftover_phase & 1) != 0; result.value.sign = (leftover_phase & 2) != 0; auto &rx = result.value.xs.u64[0]; auto &rz = result.value.zs.u64[0]; @@ -467,8 +303,8 @@ PyPauliString PyPauliString::from_unitary_matrix( return result; } -pybind11::class_ stim_pybind::pybind_pauli_string(pybind11::module &m) { - return pybind11::class_( +pybind11::class_ stim_pybind::pybind_pauli_string(pybind11::module &m) { + return pybind11::class_( m, "PauliString", clean_doc_string(R"DOC( @@ -487,115 +323,134 @@ pybind11::class_ stim_pybind::pybind_pauli_string(pybind11::modul .data()); } -void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::class_ &c) { +void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::class_ &c) { c.def( - pybind11::init([](size_t num_qubits) { - PyPauliString result{PauliString(num_qubits), false}; - return result; - }), - pybind11::arg("num_qubits"), - clean_doc_string(R"DOC( - Creates an identity Pauli string over the given number of qubits. - - Examples: - >>> import stim - >>> p = stim.PauliString(5) - >>> print(p) - +_____ - - Args: - num_qubits: The number of qubits the Pauli string acts on. - )DOC") - .data()); - - c.def( - pybind11::init(&PyPauliString::from_text), - pybind11::arg("text"), - clean_doc_string(R"DOC( - Creates a stim.PauliString from a text string. + pybind11::init( + [](const pybind11::object &arg, + const pybind11::object &num_qubits, + const pybind11::object &text, + const pybind11::object &other, + const pybind11::object &pauli_indices) -> FlexPauliString { + size_t count = 0; + count += !arg.is_none(); + count += !num_qubits.is_none(); + count += !text.is_none(); + count += !other.is_none(); + count += !pauli_indices.is_none(); + if (count > 1) { + throw std::invalid_argument("Multiple arguments specified."); + } + if (count == 0) { + return FlexPauliString(0); + } - The string can optionally start with a sign ('+', '-', 'i', '+i', or '-i'). - The rest of the string should be characters from '_IXYZ' where - '_' and 'I' mean identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means - Pauli Z. + const auto &num_qubits_or = pybind11::isinstance(arg) ? arg : num_qubits; + if (!num_qubits_or.is_none()) { + return FlexPauliString(pybind11::cast(num_qubits_or)); + } - Examples: - >>> import stim - >>> print(stim.PauliString("YZ")) - +YZ - >>> print(stim.PauliString("+IXYZ")) - +_XYZ - >>> print(stim.PauliString("-___X_")) - -___X_ - >>> print(stim.PauliString("iX")) - +iX + const auto &text_or = pybind11::isinstance(arg) ? arg : text; + if (!text_or.is_none()) { + return FlexPauliString::from_text(pybind11::cast(text_or)); + } - Args: - text: A text description of the Pauli string's contents, such as "+XXX" or - "-_YX" or "-iZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZY". + const auto &other_or = pybind11::isinstance(arg) ? arg : other; + if (!other_or.is_none()) { + return pybind11::cast(other_or); + } - Returns: - The created stim.PauliString. - )DOC") - .data()); + const auto &pauli_indices_or = pybind11::isinstance(arg) ? arg : pauli_indices; + if (!pauli_indices_or.is_none()) { + std::vector ps; + for (const pybind11::handle &h : pauli_indices_or) { + int64_t v = -1; + if (pybind11::isinstance(h)) { + try { + v = pybind11::cast(h); + } catch (const pybind11::cast_error &) { + } + } else if (pybind11::isinstance(h)) { + std::string s = pybind11::cast(h); + if (s == "I" || s == "_") { + v = 0; + } else if (s == "X" || s == "x") { + v = 1; + } else if (s == "Y" || s == "y") { + v = 2; + } else if (s == "Z" || s == "z") { + v = 3; + } + } + if (v >= 0 && v < 4) { + ps.push_back((uint8_t)v); + } else { + throw std::invalid_argument( + "Don't know how to convert " + pybind11::cast(pybind11::repr(h)) + + " into a pauli.\n" + "Expected something from {0, 1, 2, 3, 'I', 'X', 'Y', 'Z', '_'}."); + } + } + FlexPauliString result(ps.size()); + for (size_t k = 0; k < ps.size(); k++) { + uint8_t p = ps[k]; + p ^= p >> 1; + result.value.xs[k] = p & 1; + result.value.zs[k] = p & 2; + } + return result; + } - c.def( - pybind11::init([](const PyPauliString &other) { - PyPauliString copy = other; - return copy; - }), - pybind11::arg("copy"), + throw std::invalid_argument( + "Don't know how to initialize a stim.PauliString from " + + pybind11::cast(pybind11::repr(arg))); + }), + pybind11::arg("arg") = pybind11::none(), + pybind11::pos_only(), + // These are no longer needed, and hidden from documentation, but are included to guarantee backwards + // compatibility. + pybind11::arg("num_qubits") = pybind11::none(), + pybind11::arg("text") = pybind11::none(), + pybind11::arg("other") = pybind11::none(), + pybind11::arg("pauli_indices") = pybind11::none(), clean_doc_string(R"DOC( - Creates a copy of a stim.PauliString. + @signature def __init__(self, arg: Union[None, int, str, stim.PauliString, Iterable[Union[int, 'Literal["_", "I", "X", "Y", "Z"]']]] = None, /) -> None: + Initializes a stim.PauliString from the given argument. + + When given a string, the string is parsed as a pauli string. The string can + optionally start with a sign ('+', '-', 'i', '+i', or '-i'). The rest of the + string should be characters from '_IXYZ' where '_' and 'I' mean identity, 'X' + means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. + + Arguments: + arg [position-only]: This can be a variety of types, including: + None (default): initializes an empty Pauli string. + int: initializes an identity Pauli string of the given length. + str: initializes by parsing the given text. + stim.PauliString: initializes a copy of the given Pauli string. + Iterable: initializes by interpreting each item as a Pauli. + Each item can be a single-qubit Pauli string (like "X"), + or an integer. Integers use the convention 0=I, 1=X, 2=Y, 3=Z. Examples: >>> import stim - >>> a = stim.PauliString("YZ") - >>> b = stim.PauliString(a) - >>> b is a - False - >>> b == a - True - Args: - copy: The pauli string to make a copy of. - )DOC") - .data()); + >>> stim.PauliString("-XYZ") + stim.PauliString("-XYZ") - c.def( - pybind11::init([](const std::vector &pauli_indices) { - return PyPauliString( - PauliString::from_func( - false, - pauli_indices.size(), - [&](size_t i) { - pybind11::ssize_t p = pauli_indices[i]; - if (p < 0 || p > 3) { - throw std::invalid_argument( - "Expected a pauli index (0->I, 1->X, 2->Y, 3->Z) but got " + std::to_string(p)); - } - return "_XYZ"[p]; - }), - false); - }), - pybind11::arg("pauli_indices"), - clean_doc_string(R"DOC( - Creates a stim.PauliString from a list of integer pauli indices. + >>> stim.PauliString() + stim.PauliString("+") - The indexing scheme that is used is: - 0 -> I - 1 -> X - 2 -> Y - 3 -> Z + >>> stim.PauliString(5) + stim.PauliString("+_____") - Examples: - >>> import stim - >>> stim.PauliString([0, 1, 2, 3, 0, 3]) - stim.PauliString("+_XYZ_Z") + >>> stim.PauliString(stim.PauliString("XX")) + stim.PauliString("+XX") - Args: - pauli_indices: A sequence of integers from 0 to 3 (inclusive) indicating - paulis. + >>> stim.PauliString([0, 1, 3, 2]) + stim.PauliString("+_XZY") + + >>> stim.PauliString("X" for _ in range(4)) + stim.PauliString("+XXXX") )DOC") .data()); @@ -603,7 +458,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla "random", [](size_t num_qubits, bool allow_imaginary) { auto rng = make_py_seeded_rng(pybind11::none()); - return PyPauliString( + return FlexPauliString( PauliString::random(num_qubits, rng), allow_imaginary ? (rng() & 1) : false); }, pybind11::arg("num_qubits"), @@ -639,7 +494,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "to_tableau", - [](const PyPauliString &self) { + [](const FlexPauliString &self) { return Tableau::from_pauli_string(self.value); }, clean_doc_string(R"DOC( @@ -687,7 +542,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "to_unitary_matrix", - &PyPauliString::to_unitary_matrix, + &flex_pauli_string_to_unitary_matrix, pybind11::kw_only(), pybind11::arg("endian"), clean_doc_string(R"DOC( @@ -717,13 +572,13 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def_static( "from_unitary_matrix", - &PyPauliString::from_unitary_matrix, + &flex_pauli_string_from_unitary_matrix, pybind11::arg("matrix"), pybind11::kw_only(), pybind11::arg("endian") = "little", pybind11::arg("unsigned") = false, clean_doc_string(R"DOC( - @signature def from_unitary_matrix(matrix: Iterable[Iterable[float]], *, endian: str = 'little', unsigned: bool = False) -> stim.PauliString: + @signature def from_unitary_matrix(matrix: Iterable[Iterable[Union[int, float, complex]]], *, endian: str = 'little', unsigned: bool = False) -> stim.PauliString: Creates a stim.PauliString from the unitary matrix of a Pauli group member. Args: @@ -774,7 +629,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "commutes", - [](const PyPauliString &self, const PyPauliString &other) { + [](const FlexPauliString &self, const FlexPauliString &other) { return self.value.ref().commutes(other.value.ref()); }, pybind11::arg("other"), @@ -810,19 +665,19 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla )DOC") .data()); - c.def("__str__", &PyPauliString::str, "Returns a text description."); + c.def("__str__", &FlexPauliString::str, "Returns a text description."); c.def( "__repr__", - [](const PyPauliString &self) { + [](const FlexPauliString &self) { return "stim.PauliString(\"" + self.str() + "\")"; }, "Returns valid python code evaluating to an equivalent `stim.PauliString`."); c.def_property( "sign", - &PyPauliString::get_phase, - [](PyPauliString &self, std::complex new_sign) { + &FlexPauliString::get_phase, + [](FlexPauliString &self, std::complex new_sign) { if (new_sign == std::complex(1)) { self.value.sign = false; self.imag = false; @@ -860,7 +715,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__len__", - [](const PyPauliString &self) { + [](const FlexPauliString &self) { return self.value.num_qubits; }, clean_doc_string(R"DOC( @@ -870,7 +725,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def_property_readonly( "weight", - [](const PyPauliString &self) { + [](const FlexPauliString &self) { return self.value.ref().weight(); }, clean_doc_string(R"DOC( @@ -891,7 +746,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "extended_product", - [](const PyPauliString &self, const PyPauliString &other) { + [](const FlexPauliString &self, const FlexPauliString &other) { return std::make_tuple(std::complex(1, 0), self * other); }, pybind11::arg("other"), @@ -954,7 +809,8 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla .data()); c.def( - pybind11::self * pybind11::object(), + "__mul__", + &flex_pauli_string_obj_mul, pybind11::arg("rhs"), clean_doc_string(R"DOC( Right-multiplies the Pauli string. @@ -1009,11 +865,11 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__rmul__", - [](const PyPauliString &self, const pybind11::object &lhs) { - if (pybind11::isinstance(lhs)) { - return pybind11::cast(lhs) * self; + [](const FlexPauliString &self, const pybind11::object &lhs) { + if (pybind11::isinstance(lhs)) { + return pybind11::cast(lhs) * self; } - return self * lhs; + return flex_pauli_string_obj_mul(self, lhs); }, pybind11::arg("lhs"), clean_doc_string(R"DOC( @@ -1067,7 +923,8 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla .data()); c.def( - pybind11::self *= pybind11::object(), + "__imul__", + &flex_pauli_string_obj_imul, pybind11::arg("rhs"), clean_doc_string(R"DOC( Inplace right-multiplies the Pauli string. @@ -1114,7 +971,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__itruediv__", - &PyPauliString::operator/=, + &FlexPauliString::operator/=, pybind11::is_operator(), pybind11::arg("rhs"), clean_doc_string(R"DOC( @@ -1141,7 +998,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__truediv__", - &PyPauliString::operator/, + &FlexPauliString::operator/, pybind11::is_operator(), pybind11::arg("rhs"), clean_doc_string(R"DOC( @@ -1166,8 +1023,8 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__neg__", - [](const PyPauliString &self) { - PyPauliString result = self; + [](const FlexPauliString &self) { + FlexPauliString result = self; result.value.sign ^= 1; return result; }, @@ -1187,8 +1044,8 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "copy", - [](const PyPauliString &self) { - PyPauliString copy = self; + [](const FlexPauliString &self) { + FlexPauliString copy = self; return copy; }, clean_doc_string(R"DOC( @@ -1209,7 +1066,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "to_numpy", - [](const PyPauliString &self, bool bit_packed) { + [](const FlexPauliString &self, bool bit_packed) { return pybind11::make_tuple( simd_bits_to_numpy(self.value.xs, self.value.num_qubits, bit_packed), simd_bits_to_numpy(self.value.zs, self.value.num_qubits, bit_packed)); @@ -1273,12 +1130,12 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla [](const pybind11::object &xs, const pybind11::object &zs, const pybind11::object &sign, - const pybind11::object &num_qubits) -> PyPauliString { + const pybind11::object &num_qubits) -> FlexPauliString { size_t n = numpy_pair_to_size(xs, zs, num_qubits); - PyPauliString result{PauliString(n)}; + FlexPauliString result(n); memcpy_bits_from_numpy_to_simd(n, xs, result.value.xs); memcpy_bits_from_numpy_to_simd(n, zs, result.value.zs); - result *= sign; + flex_pauli_string_obj_imul(result, sign); return result; }, pybind11::kw_only(), @@ -1335,8 +1192,8 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__pos__", - [](const PyPauliString &self) { - PyPauliString copy = self; + [](const FlexPauliString &self) { + FlexPauliString copy = self; return copy; }, clean_doc_string(R"DOC( @@ -1355,7 +1212,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__setitem__", - [](PyPauliString &self, pybind11::ssize_t index, const pybind11::object &arg_new_pauli) { + [](FlexPauliString &self, pybind11::ssize_t index, const pybind11::object &arg_new_pauli) { if (index < 0) { index += self.value.num_qubits; } @@ -1425,9 +1282,9 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "after", - [](const PyPauliString &self, + [](const FlexPauliString &self, const pybind11::object &operation, - const pybind11::object &targets) -> PyPauliString { + const pybind11::object &targets) -> FlexPauliString { PauliString result(0); if (pybind11::isinstance(operation)) { if (!targets.is_none()) { @@ -1453,7 +1310,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla throw std::invalid_argument( "Don't know how to apply " + pybind11::cast(pybind11::repr(operation))); } - return PyPauliString(result, self.imag); + return FlexPauliString(result, self.imag); }, pybind11::arg("operation"), pybind11::arg("targets") = pybind11::none(), @@ -1495,9 +1352,9 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "before", - [](const PyPauliString &self, + [](const FlexPauliString &self, const pybind11::object &operation, - const pybind11::object &targets) -> PyPauliString { + const pybind11::object &targets) -> FlexPauliString { PauliString result(0); if (pybind11::isinstance(operation)) { if (!targets.is_none()) { @@ -1523,14 +1380,14 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla throw std::invalid_argument( "Don't know how to apply " + pybind11::cast(pybind11::repr(operation))); } - return PyPauliString(result, self.imag); + return FlexPauliString(result, self.imag); }, pybind11::arg("operation"), pybind11::arg("targets") = pybind11::none(), clean_doc_string(R"DOC( - @overload def after(self, operation: Union[stim.Circuit, stim.CircuitInstruction]) -> stim.PauliString: - @overload def after(self, operation: stim.Tableau, targets: Iterable[int]) -> stim.PauliString: - @signature def after(self, operation: Union[stim.Circuit, stim.Tableau, stim.CircuitInstruction], targets: Optional[Iterable[int]] = None) -> stim.PauliString: + @overload def before(self, operation: Union[stim.Circuit, stim.CircuitInstruction]) -> stim.PauliString: + @overload def before(self, operation: stim.Tableau, targets: Iterable[int]) -> stim.PauliString: + @signature def before(self, operation: Union[stim.Circuit, stim.Tableau, stim.CircuitInstruction], targets: Optional[Iterable[int]] = None) -> stim.PauliString: Returns the result of conjugating the Pauli string by an operation. Args: @@ -1565,10 +1422,10 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla c.def( "__getitem__", - [](const PyPauliString &self, const pybind11::object &index_or_slice) -> pybind11::object { + [](const FlexPauliString &self, const pybind11::object &index_or_slice) -> pybind11::object { pybind11::ssize_t start, step, slice_length; if (normalize_index_or_slice(index_or_slice, self.value.num_qubits, &start, &step, &slice_length)) { - return pybind11::cast(PyPauliString(self.value.py_get_slice(start, step, slice_length))); + return pybind11::cast(FlexPauliString(self.value.py_get_slice(start, step, slice_length))); } else { return pybind11::cast(self.value.py_get_item(start)); } @@ -1607,11 +1464,11 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla .data()); c.def(pybind11::pickle( - [](const PyPauliString &self) -> pybind11::str { + [](const FlexPauliString &self) -> pybind11::str { return self.str(); }, [](const pybind11::str &d) { - return PyPauliString::from_text(pybind11::cast(d).data()); + return FlexPauliString::from_text(pybind11::cast(d).data()); })); c.def_static( diff --git a/src/stim/stabilizers/pauli_string.pybind.h b/src/stim/stabilizers/pauli_string.pybind.h index 72fab0d3a..eaf18a626 100644 --- a/src/stim/stabilizers/pauli_string.pybind.h +++ b/src/stim/stabilizers/pauli_string.pybind.h @@ -18,46 +18,12 @@ #include #include -#include "stim/stabilizers/pauli_string.h" +#include "stim/stabilizers/flex_pauli_string.h" namespace stim_pybind { -struct PyPauliString { - stim::PauliString value; - bool imag; - - PyPauliString(const stim::PauliStringRef val, bool imag = false); - PyPauliString(stim::PauliString &&val, bool imag = false); - - static PyPauliString from_text(const char *c); - std::complex get_phase() const; - - PyPauliString operator+(const PyPauliString &rhs) const; - PyPauliString &operator+=(const PyPauliString &rhs); - - PyPauliString operator*(const pybind11::object &rhs) const; - PyPauliString operator*(size_t power) const; - PyPauliString operator*(std::complex scale) const; - PyPauliString operator*(const PyPauliString &rhs) const; - PyPauliString operator/(const std::complex &divisor) const; - - PyPauliString &operator*=(const pybind11::object &rhs); - PyPauliString &operator*=(size_t power); - PyPauliString &operator*=(std::complex scale); - PyPauliString &operator*=(const PyPauliString &rhs); - PyPauliString &operator/=(const std::complex &divisor); - - pybind11::object to_unitary_matrix(const std::string &endian) const; - static PyPauliString from_unitary_matrix( - const pybind11::object &matrix, const std::string &endian, bool ignore_sign); - - bool operator==(const PyPauliString &other) const; - bool operator!=(const PyPauliString &other) const; - std::string str() const; -}; - -pybind11::class_ pybind_pauli_string(pybind11::module &m); -void pybind_pauli_string_methods(pybind11::module &m, pybind11::class_ &c); +pybind11::class_ pybind_pauli_string(pybind11::module &m); +void pybind_pauli_string_methods(pybind11::module &m, pybind11::class_ &c); } // namespace stim_pybind diff --git a/src/stim/stabilizers/pauli_string_iter.pybind.cc b/src/stim/stabilizers/pauli_string_iter.pybind.cc index ea7a7ee80..ecab5a435 100644 --- a/src/stim/stabilizers/pauli_string_iter.pybind.cc +++ b/src/stim/stabilizers/pauli_string_iter.pybind.cc @@ -65,11 +65,11 @@ void stim_pybind::pybind_pauli_string_iter_methods( c.def( "__next__", - [](PauliStringIterator &self) -> PyPauliString { + [](PauliStringIterator &self) -> FlexPauliString { if (!self.iter_next()) { throw pybind11::stop_iteration(); } - return PyPauliString(self.result); + return FlexPauliString(self.result); }, clean_doc_string(R"DOC( Returns the next iterated pauli string. diff --git a/src/stim/stabilizers/pauli_string_pybind_test.py b/src/stim/stabilizers/pauli_string_pybind_test.py index 632f72d36..2b178749e 100644 --- a/src/stim/stabilizers/pauli_string_pybind_test.py +++ b/src/stim/stabilizers/pauli_string_pybind_test.py @@ -448,7 +448,7 @@ def test_init_list(): _ = stim.PauliString([-1]) with pytest.raises(ValueError, match="pauli"): _ = stim.PauliString([4]) - with pytest.raises(TypeError): + with pytest.raises(ValueError): _ = stim.PauliString([2**500]) @@ -876,3 +876,23 @@ def test_iter_reusable(): vs2 = list(v) assert vs1 == vs2 assert len(vs1) == 4**2 + + +def test_backwards_compatibility_init(): + assert stim.PauliString() == stim.PauliString("+") + assert stim.PauliString(5) == stim.PauliString("+_____") + assert stim.PauliString([1, 2, 3]) == stim.PauliString("+XYZ") + assert stim.PauliString("XYZ") == stim.PauliString("+XYZ") + assert stim.PauliString(stim.PauliString("XYZ")) == stim.PauliString("+XYZ") + assert stim.PauliString("X" for _ in range(4)) == stim.PauliString("+XXXX") + + # These keywords have been removed from the documentation and the .pyi, but + # their functionality needs to be maintained for backwards compatibility. + # noinspection PyArgumentList + assert stim.PauliString(num_qubits=5) == stim.PauliString("+_____") + # noinspection PyArgumentList + assert stim.PauliString(pauli_indices=[1, 2, 3]) == stim.PauliString("+XYZ") + # noinspection PyArgumentList + assert stim.PauliString(text="XYZ") == stim.PauliString("+XYZ") + # noinspection PyArgumentList + assert stim.PauliString(other=stim.PauliString("XYZ")) == stim.PauliString("+XYZ") diff --git a/src/stim/stabilizers/tableau.pybind.cc b/src/stim/stabilizers/tableau.pybind.cc index ff622fb0a..05c15880f 100644 --- a/src/stim/stabilizers/tableau.pybind.cc +++ b/src/stim/stabilizers/tableau.pybind.cc @@ -631,7 +631,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self) { - return PyPauliString(self.to_pauli_string()); + return FlexPauliString(self.to_pauli_string()); }, clean_doc_string(R"DOC( Return a Pauli string equivalent to the tableau. @@ -1059,7 +1059,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_= self.num_qubits) { throw std::invalid_argument("target >= len(tableau)"); } - return PyPauliString(self.xs[target]); + return FlexPauliString(self.xs[target]); }, pybind11::arg("target"), clean_doc_string(R"DOC( @@ -1168,7 +1168,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_= self.num_qubits) { throw std::invalid_argument("target >= len(tableau)"); } - return PyPauliString(self.y_output(target)); + return FlexPauliString(self.y_output(target)); }, pybind11::arg("target"), clean_doc_string(R"DOC( @@ -1197,7 +1197,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_= self.num_qubits) { throw std::invalid_argument("target >= len(tableau)"); } - return PyPauliString(self.zs[target]); + return FlexPauliString(self.zs[target]); }, pybind11::arg("target"), clean_doc_string(R"DOC( @@ -1457,7 +1457,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, size_t input_index, bool skip_sign) { - return PyPauliString(self.inverse_x_output(input_index, skip_sign)); + return FlexPauliString(self.inverse_x_output(input_index, skip_sign)); }, pybind11::arg("input_index"), pybind11::kw_only(), @@ -1496,7 +1496,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, size_t input_index, bool skip_sign) { - return PyPauliString(self.inverse_y_output(input_index, skip_sign)); + return FlexPauliString(self.inverse_y_output(input_index, skip_sign)); }, pybind11::arg("input_index"), pybind11::kw_only(), @@ -1535,7 +1535,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, size_t input_index, bool skip_sign) { - return PyPauliString(self.inverse_z_output(input_index, skip_sign)); + return FlexPauliString(self.inverse_z_output(input_index, skip_sign)); }, pybind11::arg("input_index"), pybind11::kw_only(), @@ -1595,7 +1595,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &xs, const std::vector &zs) { + [](const std::vector &xs, const std::vector &zs) { size_t n = xs.size(); if (n != zs.size()) { throw std::invalid_argument("len(xs) != len(zs)"); @@ -1824,8 +1824,8 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, const PyPauliString &pauli_string) { - PyPauliString result{self(pauli_string.value)}; + [](const Tableau &self, const FlexPauliString &pauli_string) { + FlexPauliString result{self(pauli_string.value)}; if (pauli_string.imag) { result *= std::complex(0, 1); } @@ -1913,26 +1913,26 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self) { pybind11::dict d; - std::vector xs; - std::vector zs; + std::vector xs; + std::vector zs; for (size_t q = 0; q < self.num_qubits; q++) { - xs.push_back(PyPauliString(self.xs[q])); + xs.push_back(FlexPauliString(self.xs[q])); } for (size_t q = 0; q < self.num_qubits; q++) { - zs.push_back(PyPauliString(self.zs[q])); + zs.push_back(FlexPauliString(self.zs[q])); } d["xs"] = xs; d["zs"] = zs; return d; }, [](const pybind11::dict &d) { - std::vector xs; - std::vector zs; + std::vector xs; + std::vector zs; for (const auto &e : d["xs"]) { - xs.push_back(pybind11::cast(e)); + xs.push_back(pybind11::cast(e)); } for (const auto &e : d["zs"]) { - zs.push_back(pybind11::cast(e)); + zs.push_back(pybind11::cast(e)); } size_t n = xs.size(); @@ -1965,7 +1965,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_> converted_stabilizers; for (const auto &stabilizer : stabilizers) { - const PyPauliString &p = pybind11::cast(stabilizer); + const FlexPauliString &p = pybind11::cast(stabilizer); if (p.imag) { throw std::invalid_argument("Stabilizers can't have imaginary sign."); } @@ -2227,7 +2227,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ &self, bool canonical) { auto stabilizers = self.stabilizers(canonical); - std::vector result; + std::vector result; result.reserve(stabilizers.size()); for (auto &s : stabilizers) { result.emplace_back(std::move(s), false);