From f556295f3d2f286f30a04f334004ab5778050636 Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Thu, 7 Dec 2023 11:14:53 -0800 Subject: [PATCH 1/7] add apply_pauli_errors --- doc/python_api_reference_vDev.md | 38 +++++++++ doc/stim.pyi | 30 +++++++ glue/python/src/stim/__init__.pyi | 30 +++++++ src/stim/simulators/frame_simulator.pybind.cc | 85 +++++++++++++++++++ .../simulators/frame_simulator_pybind_test.py | 46 ++++++++++ 5 files changed, 229 insertions(+) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index ee5b43b49..e0c51fa63 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -172,6 +172,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.ExplainedError.dem_error_terms`](#stim.ExplainedError.dem_error_terms) - [`stim.FlipSimulator`](#stim.FlipSimulator) - [`stim.FlipSimulator.__init__`](#stim.FlipSimulator.__init__) + - [`stim.FlipSimulator.apply_pauli_errors`](#stim.FlipSimulator.apply_pauli_errors) - [`stim.FlipSimulator.batch_size`](#stim.FlipSimulator.batch_size) - [`stim.FlipSimulator.do`](#stim.FlipSimulator.do) - [`stim.FlipSimulator.get_detector_flips`](#stim.FlipSimulator.get_detector_flips) @@ -6131,6 +6132,43 @@ def __init__( """ ``` + +```python +# stim.FlipSimulator.apply_pauli_errors + +# (in class stim.FlipSimulator) +def set_pauli_flip( + self, + *, + pauli: Union[str, int], + mask: np.ndarray, +) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + mask: a bool array of shape (qubit, simulation instance) + The pauli error is only applied to qubits q and simulation indices k + where mask[q, k] == True + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.apply_pauli_errors( + >>> pauli='X', + >>> mask=np.asarray([[True, False],[False, False],[True, True]]), + >>> ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_X"), stim.PauliString("+__X")] + """ +``` + ```python # stim.FlipSimulator.batch_size diff --git a/doc/stim.pyi b/doc/stim.pyi index 697608bdc..099956156 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4655,6 +4655,36 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ + def set_pauli_flip( + self, + *, + pauli: Union[str, int], + mask: np.ndarray, + ) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + mask: a bool array of shape (qubit, simulation instance) + The pauli error is only applied to qubits q and simulation indices k + where mask[q, k] == True + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.apply_pauli_errors( + >>> pauli='X', + >>> mask=np.asarray([[True, False],[False, False],[True, True]]), + >>> ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_X"), stim.PauliString("+__X")] + """ @property def batch_size( self, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 697608bdc..099956156 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4655,6 +4655,36 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ + def set_pauli_flip( + self, + *, + pauli: Union[str, int], + mask: np.ndarray, + ) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + mask: a bool array of shape (qubit, simulation instance) + The pauli error is only applied to qubits q and simulation indices k + where mask[q, k] == True + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.apply_pauli_errors( + >>> pauli='X', + >>> mask=np.asarray([[True, False],[False, False],[True, True]]), + >>> ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_X"), stim.PauliString("+__X")] + """ @property def batch_size( self, diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index 56e6795a8..44b88f8e6 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -770,4 +770,89 @@ void stim_pybind::pybind_frame_simulator_methods( [stim.PauliString("+YX__")] )DOC") .data()); + + c.def( + "apply_pauli_errors", + [](FrameSimulator &self, + const pybind11::object &pauli, + const pybind11::object &mask + ) { + uint8_t p = 255; + try { + p = pybind11::cast(pauli); + } catch (const pybind11::cast_error &) { + try { + std::string s = pybind11::cast(pauli); + if (s == "X") { + p = 1; + } else if (s == "Y") { + p = 3; + } else if (s == "Z") { + p = 2; + } else if (s == "I" || s == "_") { + p = 0; + } + } catch (const pybind11::cast_error &) { + } + } + bool flip_x_part = p & 1; + bool flip_z_part = p & 2; + + if (pybind11::isinstance>(mask)) { + // can use pybind11::isinstance> + // to check for dense c_ordered array for copying, if this is too slow for you + const pybind11::array_t &arr = pybind11::cast>(mask); + if (arr.ndim() == 2) { + //pybind11::ssize_t??? + size_t major = arr.shape(0); + size_t minor = arr.shape(1); + //size_t major_stride = arr.strides(0); + //size_t minor_stride = arr.strides(1); + auto u = arr.unchecked<2>(); + for (size_t i = 0; i < major; i++){ + for (size_t j = 0; j < minor; j++){ + auto b = u.data(i, j); + self.x_table[i][j] ^= *b & flip_x_part; + self.z_table[i][j] ^= *b & flip_z_part; + } + } + } else { + throw std::invalid_argument( + "apply_pauli_errors currently only supports a mask that is a 2D bool numpy array"); + } + } else { + throw std::invalid_argument( + "apply_pauli_errors currently only supports a mask that is a 2D bool numpy array"); + } + }, + pybind11::kw_only(), + pybind11::arg("pauli"), + pybind11::arg("mask"), + clean_doc_string(R"DOC( + @signature def set_pauli_flip(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: + Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + mask: a bool array of shape (qubit, simulation instance) + The pauli error is only applied to qubits q and simulation indices k + where mask[q, k] == True + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.apply_pauli_errors( + >>> pauli='X', + >>> mask=np.asarray([[True, False],[False, False],[True, True]]), + >>> ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_X"), stim.PauliString("+__X")] + )DOC") + .data()); } diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 945d7acfc..18e0f648c 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -216,6 +216,52 @@ def test_set_pauli_flip(): stim.PauliString('XZ___'), ] +def test_apply_pauli_errors(): + sim = stim.FlipSimulator( + batch_size=2, + num_qubits=3, + disable_stabilizer_randomization=True, + ) + sim.apply_pauli_errors( + pauli='X', + mask=np.asarray([ + [True, False], + [False, False], + [True, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+X_X"), + stim.PauliString("+__X") + ] + sim.apply_pauli_errors( + pauli='Z', + mask=np.asarray([ + [True, True], + [True, False], + [False, False]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+YZX"), + stim.PauliString("+Z_X") + ] + sim.apply_pauli_errors( + pauli='Y', + mask=np.asarray([ + [True, False], + [False, True], + [False, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+_ZX"), + stim.PauliString("+ZYZ") + ] + def test_repro_heralded_pauli_channel_1_bug(): circuit = stim.Circuit(""" From 62c0c1a69c8ff5233a35c1185ad043269814b5ff Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Thu, 7 Dec 2023 13:33:04 -0800 Subject: [PATCH 2/7] bittwidling, also doctests --- doc/python_api_reference_vDev.md | 13 ++++--- doc/stim.pyi | 13 ++++--- glue/python/src/stim/__init__.pyi | 13 ++++--- src/stim/simulators/frame_simulator.pybind.cc | 20 +++++----- .../simulators/frame_simulator_pybind_test.py | 39 +++++++++++++++++++ 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index e0c51fa63..a4b62d067 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -6137,33 +6137,34 @@ def __init__( # stim.FlipSimulator.apply_pauli_errors # (in class stim.FlipSimulator) -def set_pauli_flip( +def apply_pauli_errors( self, *, pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Sets the pauli flip on a given qubit in a given simulation instance. + """Applies a pauli over all qubits in all simulation indices, filtered by mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array of shape (qubit, simulation instance) + mask: a bool array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True Examples: >>> import stim + >>> import numpy as np >>> sim = stim.FlipSimulator( ... batch_size=2, ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) >>> sim.apply_pauli_errors( - >>> pauli='X', - >>> mask=np.asarray([[True, False],[False, False],[True, True]]), - >>> ) + ... pauli='X', + ... mask=np.asarray([[True, False],[False, False],[True, True]]), + ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] """ diff --git a/doc/stim.pyi b/doc/stim.pyi index 099956156..ca1b6b983 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4655,33 +4655,34 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ - def set_pauli_flip( + def apply_pauli_errors( self, *, pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Sets the pauli flip on a given qubit in a given simulation instance. + """Applies a pauli over all qubits in all simulation indices, filtered by mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array of shape (qubit, simulation instance) + mask: a bool array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True Examples: >>> import stim + >>> import numpy as np >>> sim = stim.FlipSimulator( ... batch_size=2, ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) >>> sim.apply_pauli_errors( - >>> pauli='X', - >>> mask=np.asarray([[True, False],[False, False],[True, True]]), - >>> ) + ... pauli='X', + ... mask=np.asarray([[True, False],[False, False],[True, True]]), + ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] """ diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 099956156..ca1b6b983 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4655,33 +4655,34 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ - def set_pauli_flip( + def apply_pauli_errors( self, *, pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Sets the pauli flip on a given qubit in a given simulation instance. + """Applies a pauli over all qubits in all simulation indices, filtered by mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array of shape (qubit, simulation instance) + mask: a bool array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True Examples: >>> import stim + >>> import numpy as np >>> sim = stim.FlipSimulator( ... batch_size=2, ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) >>> sim.apply_pauli_errors( - >>> pauli='X', - >>> mask=np.asarray([[True, False],[False, False],[True, True]]), - >>> ) + ... pauli='X', + ... mask=np.asarray([[True, False],[False, False],[True, True]]), + ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] """ diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index 44b88f8e6..f9a10c1a6 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -786,17 +786,18 @@ void stim_pybind::pybind_frame_simulator_methods( if (s == "X") { p = 1; } else if (s == "Y") { - p = 3; - } else if (s == "Z") { p = 2; + } else if (s == "Z") { + p = 3; } else if (s == "I" || s == "_") { p = 0; } } catch (const pybind11::cast_error &) { } } - bool flip_x_part = p & 1; + bool flip_z_part = p & 2; + bool flip_x_part = 6 >> p & 1; // 0b0110 >> p & 0b0001 if (pybind11::isinstance>(mask)) { // can use pybind11::isinstance> @@ -829,28 +830,29 @@ void stim_pybind::pybind_frame_simulator_methods( pybind11::arg("pauli"), pybind11::arg("mask"), clean_doc_string(R"DOC( - @signature def set_pauli_flip(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: - Sets the pauli flip on a given qubit in a given simulation instance. + @signature def apply_pauli_errors(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: + Applies a pauli over all qubits in all simulation indices, filtered by mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array of shape (qubit, simulation instance) + mask: a bool array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True Examples: >>> import stim + >>> import numpy as np >>> sim = stim.FlipSimulator( ... batch_size=2, ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) >>> sim.apply_pauli_errors( - >>> pauli='X', - >>> mask=np.asarray([[True, False],[False, False],[True, True]]), - >>> ) + ... pauli='X', + ... mask=np.asarray([[True, False],[False, False],[True, True]]), + ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] )DOC") diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 18e0f648c..537adb7e4 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -261,6 +261,45 @@ def test_apply_pauli_errors(): stim.PauliString("+_ZX"), stim.PauliString("+ZYZ") ] + sim.apply_pauli_errors( + pauli=1, # X + mask=np.asarray([ + [False, False], + [False, False], + [True, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+_Z_"), + stim.PauliString("+ZYY") + ] + sim.apply_pauli_errors( + pauli=2, # Y + mask=np.asarray([ + [False, True], + [False, True], + [False, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+_Z_"), + stim.PauliString("+X__") + ] + sim.apply_pauli_errors( + pauli=3, # Z + mask=np.asarray([ + [False, False], + [True, False], + [False, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+___"), + stim.PauliString("+Y__") + ] def test_repro_heralded_pauli_channel_1_bug(): From 73c3d7947cdff7bf4edc446917c340c4d1126f4c Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Thu, 7 Dec 2023 13:43:40 -0800 Subject: [PATCH 3/7] fix pybind tests --- .../simulators/frame_simulator_pybind_test.py | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 537adb7e4..87fa2baf2 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -261,44 +261,51 @@ def test_apply_pauli_errors(): stim.PauliString("+_ZX"), stim.PauliString("+ZYZ") ] + + # do it again with ints + sim = stim.FlipSimulator( + batch_size=2, + num_qubits=3, + disable_stabilizer_randomization=True, + ) sim.apply_pauli_errors( - pauli=1, # X + pauli=1, mask=np.asarray([ - [False, False], + [True, False], [False, False], [True, True]] ), ) peek = sim.peek_pauli_flips() assert peek == [ - stim.PauliString("+_Z_"), - stim.PauliString("+ZYY") + stim.PauliString("+X_X"), + stim.PauliString("+__X") ] sim.apply_pauli_errors( - pauli=2, # Y + pauli=3, mask=np.asarray([ - [False, True], - [False, True], - [False, True]] + [True, True], + [True, False], + [False, False]] ), ) peek = sim.peek_pauli_flips() assert peek == [ - stim.PauliString("+_Z_"), - stim.PauliString("+X__") + stim.PauliString("+YZX"), + stim.PauliString("+Z_X") ] sim.apply_pauli_errors( - pauli=3, # Z + pauli=2, mask=np.asarray([ - [False, False], [True, False], + [False, True], [False, True]] ), ) peek = sim.peek_pauli_flips() assert peek == [ - stim.PauliString("+___"), - stim.PauliString("+Y__") + stim.PauliString("+_ZX"), + stim.PauliString("+ZYZ") ] From 4044b76a6fcbcce062e9a756c464bf4dbc3dcc70 Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Fri, 8 Dec 2023 09:30:08 -0800 Subject: [PATCH 4/7] craig review --- doc/python_api_reference_vDev.md | 61 +++++++++------- doc/stim.pyi | 41 ++++++----- glue/python/src/stim/__init__.pyi | 41 ++++++----- src/stim/simulators/frame_simulator.pybind.cc | 73 +++++++++++-------- .../simulators/frame_simulator_pybind_test.py | 59 +++++++++++++-- 5 files changed, 176 insertions(+), 99 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index a4b62d067..081c06d0b 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -172,8 +172,8 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.ExplainedError.dem_error_terms`](#stim.ExplainedError.dem_error_terms) - [`stim.FlipSimulator`](#stim.FlipSimulator) - [`stim.FlipSimulator.__init__`](#stim.FlipSimulator.__init__) - - [`stim.FlipSimulator.apply_pauli_errors`](#stim.FlipSimulator.apply_pauli_errors) - [`stim.FlipSimulator.batch_size`](#stim.FlipSimulator.batch_size) + - [`stim.FlipSimulator.broadcast_pauli_errors`](#stim.FlipSimulator.broadcast_pauli_errors) - [`stim.FlipSimulator.do`](#stim.FlipSimulator.do) - [`stim.FlipSimulator.get_detector_flips`](#stim.FlipSimulator.get_detector_flips) - [`stim.FlipSimulator.get_measurement_flips`](#stim.FlipSimulator.get_measurement_flips) @@ -6132,12 +6132,34 @@ def __init__( """ ``` - + ```python -# stim.FlipSimulator.apply_pauli_errors +# stim.FlipSimulator.batch_size # (in class stim.FlipSimulator) -def apply_pauli_errors( +@property +def batch_size( + self, +) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FlipSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ +``` + + +```python +# stim.FlipSimulator.broadcast_pauli_errors + +# (in class stim.FlipSimulator) +def broadcast_pauli_errors( self, *, pauli: Union[str, int], @@ -6149,7 +6171,7 @@ def apply_pauli_errors( pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array with shape (qubit, simulation_instance) + mask: a np.bool_ array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True @@ -6161,34 +6183,19 @@ def apply_pauli_errors( ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) - >>> sim.apply_pauli_errors( + >>> sim.broadcast_pauli_errors( ... pauli='X', ... mask=np.asarray([[True, False],[False, False],[True, True]]), ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] - """ -``` - - -```python -# stim.FlipSimulator.batch_size - -# (in class stim.FlipSimulator) -@property -def batch_size( - self, -) -> int: - """Returns the number of instances being simulated by the simulator. - Examples: - >>> import stim - >>> sim = stim.FlipSimulator(batch_size=256) - >>> sim.batch_size - 256 - >>> sim = stim.FlipSimulator(batch_size=42) - >>> sim.batch_size - 42 + >>> sim.broadcast_pauli_errors( + ... pauli='Z', + ... mask=np.asarray([[False, True],[False, False],[True, True]]), + ... ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")] """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index ca1b6b983..67c2f3677 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4655,7 +4655,22 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ - def apply_pauli_errors( + @property + def batch_size( + self, + ) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FlipSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ + def broadcast_pauli_errors( self, *, pauli: Union[str, int], @@ -4667,7 +4682,7 @@ class FlipSimulator: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array with shape (qubit, simulation_instance) + mask: a np.bool_ array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True @@ -4679,27 +4694,19 @@ class FlipSimulator: ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) - >>> sim.apply_pauli_errors( + >>> sim.broadcast_pauli_errors( ... pauli='X', ... mask=np.asarray([[True, False],[False, False],[True, True]]), ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] - """ - @property - def batch_size( - self, - ) -> int: - """Returns the number of instances being simulated by the simulator. - Examples: - >>> import stim - >>> sim = stim.FlipSimulator(batch_size=256) - >>> sim.batch_size - 256 - >>> sim = stim.FlipSimulator(batch_size=42) - >>> sim.batch_size - 42 + >>> sim.broadcast_pauli_errors( + ... pauli='Z', + ... mask=np.asarray([[False, True],[False, False],[True, True]]), + ... ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")] """ def do( self, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index ca1b6b983..67c2f3677 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4655,7 +4655,22 @@ class FlipSimulator: >>> import stim >>> sim = stim.FlipSimulator(batch_size=256) """ - def apply_pauli_errors( + @property + def batch_size( + self, + ) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FlipSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ + def broadcast_pauli_errors( self, *, pauli: Union[str, int], @@ -4667,7 +4682,7 @@ class FlipSimulator: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array with shape (qubit, simulation_instance) + mask: a np.bool_ array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True @@ -4679,27 +4694,19 @@ class FlipSimulator: ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) - >>> sim.apply_pauli_errors( + >>> sim.broadcast_pauli_errors( ... pauli='X', ... mask=np.asarray([[True, False],[False, False],[True, True]]), ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] - """ - @property - def batch_size( - self, - ) -> int: - """Returns the number of instances being simulated by the simulator. - Examples: - >>> import stim - >>> sim = stim.FlipSimulator(batch_size=256) - >>> sim.batch_size - 256 - >>> sim = stim.FlipSimulator(batch_size=42) - >>> sim.batch_size - 42 + >>> sim.broadcast_pauli_errors( + ... pauli='Z', + ... mask=np.asarray([[False, True],[False, False],[True, True]]), + ... ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")] """ def do( self, diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index f9a10c1a6..f521cad5c 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -772,7 +772,7 @@ void stim_pybind::pybind_frame_simulator_methods( .data()); c.def( - "apply_pauli_errors", + "broadcast_pauli_errors", [](FrameSimulator &self, const pybind11::object &pauli, const pybind11::object &mask @@ -780,6 +780,9 @@ void stim_pybind::pybind_frame_simulator_methods( uint8_t p = 255; try { p = pybind11::cast(pauli); + if (p >= 4) { + throw pybind11::cast_error(); + } } catch (const pybind11::cast_error &) { try { std::string s = pybind11::cast(pauli); @@ -791,53 +794,53 @@ void stim_pybind::pybind_frame_simulator_methods( p = 3; } else if (s == "I" || s == "_") { p = 0; + } else { + throw pybind11::cast_error(); } } catch (const pybind11::cast_error &) { + throw std::invalid_argument( + "broadcast_pauli_errors only accepts pauli arguments in ['I', '_', 'X', 'Y', 'Z', 0,1,2,3]"); } } bool flip_z_part = p & 2; - bool flip_x_part = 6 >> p & 1; // 0b0110 >> p & 0b0001 - - if (pybind11::isinstance>(mask)) { - // can use pybind11::isinstance> - // to check for dense c_ordered array for copying, if this is too slow for you - const pybind11::array_t &arr = pybind11::cast>(mask); - if (arr.ndim() == 2) { - //pybind11::ssize_t??? - size_t major = arr.shape(0); - size_t minor = arr.shape(1); - //size_t major_stride = arr.strides(0); - //size_t minor_stride = arr.strides(1); - auto u = arr.unchecked<2>(); - for (size_t i = 0; i < major; i++){ - for (size_t j = 0; j < minor; j++){ - auto b = u.data(i, j); - self.x_table[i][j] ^= *b & flip_x_part; - self.z_table[i][j] ^= *b & flip_z_part; - } - } - } else { - throw std::invalid_argument( - "apply_pauli_errors currently only supports a mask that is a 2D bool numpy array"); - } - } else { - throw std::invalid_argument( - "apply_pauli_errors currently only supports a mask that is a 2D bool numpy array"); + bool flip_x_part = (0b0110 >> p) & 1; // parity of 2 bit number + + + if (!pybind11::isinstance>(mask)) { + throw std::invalid_argument( + "broadcast_pauli_errors can only accept mask that is a 2D array of np.bool_"); + } + const pybind11::array_t &arr = pybind11::cast>(mask); + + if (arr.ndim() != 2) { + throw std::invalid_argument( + "broadcast_pauli_errors can only accept mask that is a 2D array of np.bool_"); + } + + size_t major = arr.shape(0); + size_t minor = arr.shape(1); + auto u = arr.unchecked<2>(); + for (size_t i = 0; i < major; i++){ + for (size_t j = 0; j < minor; j++){ + auto b = u.data(i, j); + self.x_table[i][j] ^= *b & flip_x_part; + self.z_table[i][j] ^= *b & flip_z_part; } + } }, pybind11::kw_only(), pybind11::arg("pauli"), pybind11::arg("mask"), clean_doc_string(R"DOC( - @signature def apply_pauli_errors(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: + @signature def broadcast_pauli_errors(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: Applies a pauli over all qubits in all simulation indices, filtered by mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a bool array with shape (qubit, simulation_instance) + mask: a np.bool_ array with shape (qubit, simulation_instance) The pauli error is only applied to qubits q and simulation indices k where mask[q, k] == True @@ -849,12 +852,20 @@ void stim_pybind::pybind_frame_simulator_methods( ... num_qubits=3, ... disable_stabilizer_randomization=True, ... ) - >>> sim.apply_pauli_errors( + >>> sim.broadcast_pauli_errors( ... pauli='X', ... mask=np.asarray([[True, False],[False, False],[True, True]]), ... ) >>> sim.peek_pauli_flips() [stim.PauliString("+X_X"), stim.PauliString("+__X")] + + >>> sim.broadcast_pauli_errors( + ... pauli='Z', + ... mask=np.asarray([[False, True],[False, False],[True, True]]), + ... ) + >>> sim.peek_pauli_flips() + [stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")] + )DOC") .data()); } diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 87fa2baf2..8ceb96283 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -216,13 +216,13 @@ def test_set_pauli_flip(): stim.PauliString('XZ___'), ] -def test_apply_pauli_errors(): +def test_broadcast_pauli_errors(): sim = stim.FlipSimulator( batch_size=2, num_qubits=3, disable_stabilizer_randomization=True, ) - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli='X', mask=np.asarray([ [True, False], @@ -235,7 +235,7 @@ def test_apply_pauli_errors(): stim.PauliString("+X_X"), stim.PauliString("+__X") ] - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli='Z', mask=np.asarray([ [True, True], @@ -248,7 +248,7 @@ def test_apply_pauli_errors(): stim.PauliString("+YZX"), stim.PauliString("+Z_X") ] - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli='Y', mask=np.asarray([ [True, False], @@ -261,6 +261,19 @@ def test_apply_pauli_errors(): stim.PauliString("+_ZX"), stim.PauliString("+ZYZ") ] + sim.broadcast_pauli_errors( + pauli='I', + mask=np.asarray([ + [True, True], + [False, True], + [True, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+_ZX"), + stim.PauliString("+ZYZ") + ] # do it again with ints sim = stim.FlipSimulator( @@ -268,7 +281,7 @@ def test_apply_pauli_errors(): num_qubits=3, disable_stabilizer_randomization=True, ) - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli=1, mask=np.asarray([ [True, False], @@ -281,7 +294,7 @@ def test_apply_pauli_errors(): stim.PauliString("+X_X"), stim.PauliString("+__X") ] - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli=3, mask=np.asarray([ [True, True], @@ -294,7 +307,7 @@ def test_apply_pauli_errors(): stim.PauliString("+YZX"), stim.PauliString("+Z_X") ] - sim.apply_pauli_errors( + sim.broadcast_pauli_errors( pauli=2, mask=np.asarray([ [True, False], @@ -307,6 +320,38 @@ def test_apply_pauli_errors(): stim.PauliString("+_ZX"), stim.PauliString("+ZYZ") ] + sim.broadcast_pauli_errors( + pauli=0, + mask=np.asarray([ + [True, True], + [False, True], + [True, True]] + ), + ) + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+_ZX"), + stim.PauliString("+ZYZ") + ] + + with pytest.raises(Exception): + sim.broadcast_pauli_errors( + pauli='whoops', + mask=np.asarray([ + [True, True], + [False, True], + [True, True]] + ), + ) + with pytest.raises(Exception): + sim.broadcast_pauli_errors( + pauli=4, + mask=np.asarray([ + [True, True], + [False, True], + [True, True]] + ), + ) def test_repro_heralded_pauli_channel_1_bug(): From 2feae5a2efac3b84fcc923147971de3e50943ab6 Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Fri, 8 Dec 2023 12:41:54 -0800 Subject: [PATCH 5/7] prevent running with scisors --- doc/python_api_reference_vDev.md | 18 +++++-- doc/stim.pyi | 18 +++++-- glue/python/src/stim/__init__.pyi | 18 +++++-- src/stim/simulators/frame_simulator.pybind.cc | 26 +++++++-- .../simulators/frame_simulator_pybind_test.py | 54 ++++++++++++++++++- 5 files changed, 116 insertions(+), 18 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 081c06d0b..4e9c3d6a1 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -6165,15 +6165,25 @@ def broadcast_pauli_errors( pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Applies a pauli over all qubits in all simulation indices, filtered by mask. + """Applies a pauli error to all qubits in all instances, filtered by a mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a np.bool_ array with shape (qubit, simulation_instance) - The pauli error is only applied to qubits q and simulation indices k - where mask[q, k] == True + mask: A 2d numpy array specifying where to apply errors. The first axis + is qubits, the second axis is simulation instances. The first axis + can have a length less than the current number of qubits (or more, + which adds qubits to the simulation). The length of the second axis + must match the simulator's `batch_size`. The array must satisfy + + mask.dtype == np.bool_ + len(mask.shape) == 2 + mask.shape[1] == flip_sim.batch_size + + The error is only applied to qubit q in instance k when + + mask[q, k] == True. Examples: >>> import stim diff --git a/doc/stim.pyi b/doc/stim.pyi index 67c2f3677..6a7ff42f8 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4676,15 +4676,25 @@ class FlipSimulator: pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Applies a pauli over all qubits in all simulation indices, filtered by mask. + """Applies a pauli error to all qubits in all instances, filtered by a mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a np.bool_ array with shape (qubit, simulation_instance) - The pauli error is only applied to qubits q and simulation indices k - where mask[q, k] == True + mask: A 2d numpy array specifying where to apply errors. The first axis + is qubits, the second axis is simulation instances. The first axis + can have a length less than the current number of qubits (or more, + which adds qubits to the simulation). The length of the second axis + must match the simulator's `batch_size`. The array must satisfy + + mask.dtype == np.bool_ + len(mask.shape) == 2 + mask.shape[1] == flip_sim.batch_size + + The error is only applied to qubit q in instance k when + + mask[q, k] == True. Examples: >>> import stim diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 67c2f3677..6a7ff42f8 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4676,15 +4676,25 @@ class FlipSimulator: pauli: Union[str, int], mask: np.ndarray, ) -> None: - """Applies a pauli over all qubits in all simulation indices, filtered by mask. + """Applies a pauli error to all qubits in all instances, filtered by a mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a np.bool_ array with shape (qubit, simulation_instance) - The pauli error is only applied to qubits q and simulation indices k - where mask[q, k] == True + mask: A 2d numpy array specifying where to apply errors. The first axis + is qubits, the second axis is simulation instances. The first axis + can have a length less than the current number of qubits (or more, + which adds qubits to the simulation). The length of the second axis + must match the simulator's `batch_size`. The array must satisfy + + mask.dtype == np.bool_ + len(mask.shape) == 2 + mask.shape[1] == flip_sim.batch_size + + The error is only applied to qubit q in instance k when + + mask[q, k] == True. Examples: >>> import stim diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index f521cad5c..4ac38ec36 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -820,6 +820,14 @@ void stim_pybind::pybind_frame_simulator_methods( size_t major = arr.shape(0); size_t minor = arr.shape(1); + + if (minor != self.batch_size) { + throw std::invalid_argument( + "broadcast_pauli_errors can only accept mask that has minor shape equal to the batch_size"); + } + + self.ensure_safe_to_do_circuit_with_stats(CircuitStats{.num_qubits=(uint32_t)major}); + auto u = arr.unchecked<2>(); for (size_t i = 0; i < major; i++){ for (size_t j = 0; j < minor; j++){ @@ -834,15 +842,25 @@ void stim_pybind::pybind_frame_simulator_methods( pybind11::arg("mask"), clean_doc_string(R"DOC( @signature def broadcast_pauli_errors(self, *, pauli: Union[str, int], mask: np.ndarray) -> None: - Applies a pauli over all qubits in all simulation indices, filtered by mask. + Applies a pauli error to all qubits in all instances, filtered by a mask. Args: pauli: The pauli, specified as an integer or string. Uses the convention 0=I, 1=X, 2=Y, 3=Z. Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. - mask: a np.bool_ array with shape (qubit, simulation_instance) - The pauli error is only applied to qubits q and simulation indices k - where mask[q, k] == True + mask: A 2d numpy array specifying where to apply errors. The first axis + is qubits, the second axis is simulation instances. The first axis + can have a length less than the current number of qubits (or more, + which adds qubits to the simulation). The length of the second axis + must match the simulator's `batch_size`. The array must satisfy + + mask.dtype == np.bool_ + len(mask.shape) == 2 + mask.shape[1] == flip_sim.batch_size + + The error is only applied to qubit q in instance k when + + mask[q, k] == True. Examples: >>> import stim diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 8ceb96283..63f1dd7d9 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -334,7 +334,7 @@ def test_broadcast_pauli_errors(): stim.PauliString("+ZYZ") ] - with pytest.raises(Exception): + with pytest.raises(ValueError, match='pauli'): sim.broadcast_pauli_errors( pauli='whoops', mask=np.asarray([ @@ -343,7 +343,7 @@ def test_broadcast_pauli_errors(): [True, True]] ), ) - with pytest.raises(Exception): + with pytest.raises(ValueError, match='pauli'): sim.broadcast_pauli_errors( pauli=4, mask=np.asarray([ @@ -352,6 +352,56 @@ def test_broadcast_pauli_errors(): [True, True]] ), ) + with pytest.raises(ValueError, match='batch_size'): + sim.broadcast_pauli_errors( + pauli='X', + mask=np.asarray([ + [True, True,True], + [False, True, True], + [True, True, True]] + ), + ) + with pytest.raises(ValueError, match='batch_size'): + sim.broadcast_pauli_errors( + pauli='X', + mask=np.asarray([ + [True], + [False], + [True]] + ), + ) + sim = stim.FlipSimulator( + batch_size=2, + num_qubits=3, + disable_stabilizer_randomization=True, + ) + sim.broadcast_pauli_errors( + pauli='X', + mask=np.asarray([ + [True, False], + [False, False], + [True, True], + [True, True]] + ), + ) # automatically expands the qubit basis + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+X_XX"), + stim.PauliString("+__XX") + ] + sim.broadcast_pauli_errors( + pauli='X', + mask=np.asarray([ + [True, False], + [False, False], + ] + ), + ) # tolerates fewer qubits in mask than in simulator + peek = sim.peek_pauli_flips() + assert peek == [ + stim.PauliString("+__XX"), + stim.PauliString("+__XX") + ] def test_repro_heralded_pauli_channel_1_bug(): From 582ade04342517955c19668f3660225c035be7ee Mon Sep 17 00:00:00 2001 From: Matt McEwen Date: Fri, 8 Dec 2023 13:05:29 -0800 Subject: [PATCH 6/7] ...regen_docs --- doc/python_api_reference_vDev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 4e9c3d6a1..9905cd76c 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -6180,7 +6180,7 @@ def broadcast_pauli_errors( mask.dtype == np.bool_ len(mask.shape) == 2 mask.shape[1] == flip_sim.batch_size - + The error is only applied to qubit q in instance k when mask[q, k] == True. From 03c4306e57bc970be4c153fd67a26e99fa50cf8a Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 10 Dec 2023 15:46:31 -0800 Subject: [PATCH 7/7] Finish up --- src/stim/simulators/frame_simulator.pybind.cc | 120 ++++++++---------- .../simulators/frame_simulator_pybind_test.py | 10 +- 2 files changed, 55 insertions(+), 75 deletions(-) diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index 4ac38ec36..6d503756f 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -33,6 +33,32 @@ std::optional py_index_to_optional_size_t( return (size_t)i; } +uint8_t pybind11_object_to_pauli_ixyz(const pybind11::object &obj) { + if (pybind11::isinstance(obj)) { + std::string s = pybind11::cast(obj); + if (s == "X") { + return 1; + } else if (s == "Y") { + return 2; + } else if (s == "Z") { + return 3; + } else if (s == "I" || s == "_") { + return 0; + } + } else if (pybind11::isinstance(obj)) { + uint8_t v = 255; + try { + v = pybind11::cast(obj); + } catch (const pybind11::cast_error &) { + } + if (v < 4) { + return (uint8_t)v; + } + } + + throw std::invalid_argument("Need pauli in ['I', 'X', 'Y', 'Z', 0, 1, 2, 3, '_']."); +} + pybind11::class_> stim_pybind::pybind_frame_simulator(pybind11::module &m) { return pybind11::class_>( m, @@ -374,27 +400,7 @@ void stim_pybind::pybind_frame_simulator_methods( const pybind11::object &pauli, int64_t qubit_index, int64_t instance_index) { - uint8_t p = 255; - try { - p = pybind11::cast(pauli); - } catch (const pybind11::cast_error &) { - try { - std::string s = pybind11::cast(pauli); - if (s == "X") { - p = 1; - } else if (s == "Y") { - p = 2; - } else if (s == "Z") { - p = 3; - } else if (s == "I" || s == "_") { - p = 0; - } - } catch (const pybind11::cast_error &) { - } - } - if (p > 3) { - throw std::invalid_argument("Expected pauli in [0, 1, 2, 3, '_', 'I', 'X', 'Y', 'Z']"); - } + uint8_t p = pybind11_object_to_pauli_ixyz(pauli); if (instance_index < 0) { instance_index += self.batch_size; } @@ -409,6 +415,7 @@ void stim_pybind::pybind_frame_simulator_methods( stats.num_qubits = qubit_index + 1; self.ensure_safe_to_do_circuit_with_stats(stats); } + p ^= p >> 1; self.x_table[qubit_index][instance_index] = (p & 1) != 0; self.z_table[qubit_index][instance_index] = (p & 2) != 0; @@ -773,67 +780,40 @@ void stim_pybind::pybind_frame_simulator_methods( c.def( "broadcast_pauli_errors", - [](FrameSimulator &self, - const pybind11::object &pauli, - const pybind11::object &mask - ) { - uint8_t p = 255; - try { - p = pybind11::cast(pauli); - if (p >= 4) { - throw pybind11::cast_error(); - } - } catch (const pybind11::cast_error &) { - try { - std::string s = pybind11::cast(pauli); - if (s == "X") { - p = 1; - } else if (s == "Y") { - p = 2; - } else if (s == "Z") { - p = 3; - } else if (s == "I" || s == "_") { - p = 0; - } else { - throw pybind11::cast_error(); - } - } catch (const pybind11::cast_error &) { - throw std::invalid_argument( - "broadcast_pauli_errors only accepts pauli arguments in ['I', '_', 'X', 'Y', 'Z', 0,1,2,3]"); - } - } - - bool flip_z_part = p & 2; - bool flip_x_part = (0b0110 >> p) & 1; // parity of 2 bit number - + [](FrameSimulator &self, const pybind11::object &pauli, const pybind11::object &mask) { + uint8_t p = pybind11_object_to_pauli_ixyz(pauli); if (!pybind11::isinstance>(mask)) { - throw std::invalid_argument( - "broadcast_pauli_errors can only accept mask that is a 2D array of np.bool_"); + throw std::invalid_argument("Need isinstance(mask, np.ndarray) and mask.dtype == np.bool_"); } const pybind11::array_t &arr = pybind11::cast>(mask); if (arr.ndim() != 2) { throw std::invalid_argument( - "broadcast_pauli_errors can only accept mask that is a 2D array of np.bool_"); + "Need a 2d mask (first axis is qubits, second axis is simulation instances). Need len(mask.shape) " + "== 2."); } - size_t major = arr.shape(0); - size_t minor = arr.shape(1); - - if (minor != self.batch_size) { - throw std::invalid_argument( - "broadcast_pauli_errors can only accept mask that has minor shape equal to the batch_size"); + pybind11::ssize_t s_mask_num_qubits = arr.shape(0); + pybind11::ssize_t s_mask_batch_size = arr.shape(1); + if ((uint64_t)s_mask_batch_size != self.batch_size) { + throw std::invalid_argument("Need mask.shape[1] == flip_sim.batch_size"); } + if (s_mask_num_qubits > UINT32_MAX) { + throw std::invalid_argument("Mask exceeds maximum number of simulated qubits."); + } + uint32_t mask_num_qubits = (uint32_t)s_mask_num_qubits; + uint32_t mask_batch_size = (uint32_t)s_mask_batch_size; - self.ensure_safe_to_do_circuit_with_stats(CircuitStats{.num_qubits=(uint32_t)major}); - + self.ensure_safe_to_do_circuit_with_stats(CircuitStats{.num_qubits = mask_num_qubits}); auto u = arr.unchecked<2>(); - for (size_t i = 0; i < major; i++){ - for (size_t j = 0; j < minor; j++){ - auto b = u.data(i, j); - self.x_table[i][j] ^= *b & flip_x_part; - self.z_table[i][j] ^= *b & flip_z_part; + bool p_x = (0b0110 >> p) & 1; // parity of 2 bit number + bool p_z = p & 2; + for (size_t i = 0; i < mask_num_qubits; i++) { + for (size_t j = 0; j < mask_batch_size; j++) { + bool b = *u.data(i, j); + self.x_table[i][j] ^= b & p_x; + self.z_table[i][j] ^= b & p_z; } } }, diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 63f1dd7d9..0078a8d8e 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -191,15 +191,15 @@ def test_set_pauli_flip(): stim.PauliString('XZ_'), ] - with pytest.raises(ValueError, match='Expected pauli'): + with pytest.raises(ValueError, match='pauli'): sim.set_pauli_flip(-1, qubit_index=0, instance_index=0) - with pytest.raises(ValueError, match='Expected pauli'): + with pytest.raises(ValueError, match='pauli'): sim.set_pauli_flip(4, qubit_index=0, instance_index=0) - with pytest.raises(ValueError, match='Expected pauli'): + with pytest.raises(ValueError, match='pauli'): sim.set_pauli_flip('R', qubit_index=0, instance_index=0) - with pytest.raises(ValueError, match='Expected pauli'): + with pytest.raises(ValueError, match='pauli'): sim.set_pauli_flip('XY', qubit_index=0, instance_index=0) - with pytest.raises(ValueError, match='Expected pauli'): + with pytest.raises(ValueError, match='pauli'): sim.set_pauli_flip(object(), qubit_index=0, instance_index=0) with pytest.raises(IndexError, match='instance_index'):