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():