diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index 517845184..38c116751 100644
--- a/doc/python_api_reference_vDev.md
+++ b/doc/python_api_reference_vDev.md
@@ -385,9 +385,11 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.gate_data`](#stim.gate_data)
- [`stim.main`](#stim.main)
- [`stim.read_shot_data_file`](#stim.read_shot_data_file)
+- [`stim.target_combined_paulis`](#stim.target_combined_paulis)
- [`stim.target_combiner`](#stim.target_combiner)
- [`stim.target_inv`](#stim.target_inv)
- [`stim.target_logical_observable_id`](#stim.target_logical_observable_id)
+- [`stim.target_pauli`](#stim.target_pauli)
- [`stim.target_rec`](#stim.target_rec)
- [`stim.target_relative_detector_id`](#stim.target_relative_detector_id)
- [`stim.target_separator`](#stim.target_separator)
@@ -13446,6 +13448,40 @@ def read_shot_data_file(
"""
```
+
+```python
+# stim.target_combined_paulis
+
+# (at top-level in the stim module)
+def target_combined_paulis(
+ paulis: Union[stim.PauliString, List[stim.GateTarget]],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a list of targets encoding a pauli product for instructions like MPP.
+
+ Args:
+ paulis: The paulis to encode into the targets. This can be a
+ `stim.PauliString` or a list of pauli targets from `stim.target_x`,
+ `stim.target_pauli`, etc.
+ invert: Defaults to False. If True, the product is inverted (like "!X2*Y3").
+ Note that this is in addition to any inversions specified by the
+ `paulis` argument.
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... *stim.target_combined_paulis(stim.PauliString("-XYZ")),
+ ... *stim.target_combined_paulis([stim.target_x(2), stim.target_y(5)]),
+ ... *stim.target_combined_paulis([stim.target_z(9)], invert=True),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP !X0*Y1*Z2 X2*Y5 !Z9
+ ''')
+ """
+```
+
```python
# stim.target_combiner
@@ -13530,6 +13566,52 @@ def target_logical_observable_id(
"""
```
+
+```python
+# stim.target_pauli
+
+# (at top-level in the stim module)
+def target_pauli(
+ qubit_index: int,
+ pauli: Union[str, int],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a pauli target that can be passed into `stim.Circuit.append`.
+
+ Args:
+ qubit_index: The qubit that the Pauli applies to.
+ pauli: The pauli gate to use. This can either be a string identifying the
+ pauli by name ("x", "X", "y", "Y", "z", or "Z") or an integer following
+ the convention (1=X, 2=Y, 3=Z). Setting this argument to "I" or to
+ 0 will return a qubit target instead of a pauli target.
+ invert: Defaults to False. If True, the target is inverted (like "!X10"),
+ indicating that, for example, measurement results should be inverted).
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... stim.target_pauli(2, "X"),
+ ... stim.target_combiner(),
+ ... stim.target_pauli(3, "y", invert=True),
+ ... stim.target_pauli(5, 3),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ ''')
+
+ >>> circuit.append("M", [
+ ... stim.target_pauli(7, "I"),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ M 7
+ ''')
+ """
+```
+
```python
# stim.target_rec
diff --git a/doc/stim.pyi b/doc/stim.pyi
index f6a6d5105..3a8214ccc 100644
--- a/doc/stim.pyi
+++ b/doc/stim.pyi
@@ -10525,6 +10525,33 @@ def read_shot_data_file(
array([[False, False, False, False],
[False, True, False, True]])
"""
+def target_combined_paulis(
+ paulis: Union[stim.PauliString, List[stim.GateTarget]],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a list of targets encoding a pauli product for instructions like MPP.
+
+ Args:
+ paulis: The paulis to encode into the targets. This can be a
+ `stim.PauliString` or a list of pauli targets from `stim.target_x`,
+ `stim.target_pauli`, etc.
+ invert: Defaults to False. If True, the product is inverted (like "!X2*Y3").
+ Note that this is in addition to any inversions specified by the
+ `paulis` argument.
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... *stim.target_combined_paulis(stim.PauliString("-XYZ")),
+ ... *stim.target_combined_paulis([stim.target_x(2), stim.target_y(5)]),
+ ... *stim.target_combined_paulis([stim.target_z(9)], invert=True),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP !X0*Y1*Z2 X2*Y5 !Z9
+ ''')
+ """
def target_combiner(
) -> stim.GateTarget:
"""Returns a target combiner that can be used to build Pauli products.
@@ -10588,6 +10615,45 @@ def target_logical_observable_id(
error(0.25) L13
''')
"""
+def target_pauli(
+ qubit_index: int,
+ pauli: Union[str, int],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a pauli target that can be passed into `stim.Circuit.append`.
+
+ Args:
+ qubit_index: The qubit that the Pauli applies to.
+ pauli: The pauli gate to use. This can either be a string identifying the
+ pauli by name ("x", "X", "y", "Y", "z", or "Z") or an integer following
+ the convention (1=X, 2=Y, 3=Z). Setting this argument to "I" or to
+ 0 will return a qubit target instead of a pauli target.
+ invert: Defaults to False. If True, the target is inverted (like "!X10"),
+ indicating that, for example, measurement results should be inverted).
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... stim.target_pauli(2, "X"),
+ ... stim.target_combiner(),
+ ... stim.target_pauli(3, "y", invert=True),
+ ... stim.target_pauli(5, 3),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ ''')
+
+ >>> circuit.append("M", [
+ ... stim.target_pauli(7, "I"),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ M 7
+ ''')
+ """
def target_rec(
lookback_index: int,
) -> stim.GateTarget:
diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi
index f6a6d5105..3a8214ccc 100644
--- a/glue/python/src/stim/__init__.pyi
+++ b/glue/python/src/stim/__init__.pyi
@@ -10525,6 +10525,33 @@ def read_shot_data_file(
array([[False, False, False, False],
[False, True, False, True]])
"""
+def target_combined_paulis(
+ paulis: Union[stim.PauliString, List[stim.GateTarget]],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a list of targets encoding a pauli product for instructions like MPP.
+
+ Args:
+ paulis: The paulis to encode into the targets. This can be a
+ `stim.PauliString` or a list of pauli targets from `stim.target_x`,
+ `stim.target_pauli`, etc.
+ invert: Defaults to False. If True, the product is inverted (like "!X2*Y3").
+ Note that this is in addition to any inversions specified by the
+ `paulis` argument.
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... *stim.target_combined_paulis(stim.PauliString("-XYZ")),
+ ... *stim.target_combined_paulis([stim.target_x(2), stim.target_y(5)]),
+ ... *stim.target_combined_paulis([stim.target_z(9)], invert=True),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP !X0*Y1*Z2 X2*Y5 !Z9
+ ''')
+ """
def target_combiner(
) -> stim.GateTarget:
"""Returns a target combiner that can be used to build Pauli products.
@@ -10588,6 +10615,45 @@ def target_logical_observable_id(
error(0.25) L13
''')
"""
+def target_pauli(
+ qubit_index: int,
+ pauli: Union[str, int],
+ invert: bool = False,
+) -> stim.GateTarget:
+ """Returns a pauli target that can be passed into `stim.Circuit.append`.
+
+ Args:
+ qubit_index: The qubit that the Pauli applies to.
+ pauli: The pauli gate to use. This can either be a string identifying the
+ pauli by name ("x", "X", "y", "Y", "z", or "Z") or an integer following
+ the convention (1=X, 2=Y, 3=Z). Setting this argument to "I" or to
+ 0 will return a qubit target instead of a pauli target.
+ invert: Defaults to False. If True, the target is inverted (like "!X10"),
+ indicating that, for example, measurement results should be inverted).
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... stim.target_pauli(2, "X"),
+ ... stim.target_combiner(),
+ ... stim.target_pauli(3, "y", invert=True),
+ ... stim.target_pauli(5, 3),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ ''')
+
+ >>> circuit.append("M", [
+ ... stim.target_pauli(7, "I"),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ M 7
+ ''')
+ """
def target_rec(
lookback_index: int,
) -> stim.GateTarget:
diff --git a/src/stim/py/stim.pybind.cc b/src/stim/py/stim.pybind.cc
index 0c34f7ff1..df3c8f067 100644
--- a/src/stim/py/stim.pybind.cc
+++ b/src/stim/py/stim.pybind.cc
@@ -92,6 +92,96 @@ GateTarget target_z(const pybind11::object &qubit, bool invert) {
return GateTarget::z(pybind11::cast(qubit), invert);
}
+std::vector target_combined_paulis(const pybind11::object &paulis, bool invert) {
+ std::vector result;
+ if (pybind11::isinstance(paulis)) {
+ const FlexPauliString &ps = pybind11::cast(paulis);
+ if (ps.imag) {
+ std::stringstream ss;
+ ss << "Imaginary sign: paulis=";
+ ss << paulis;
+ throw std::invalid_argument(ss.str());
+ }
+ invert ^= ps.value.sign;
+ for (size_t q = 0; q < ps.value.num_qubits; q++) {
+ bool x = ps.value.xs[q];
+ bool z = ps.value.zs[q];
+ if (x | z) {
+ result.push_back(GateTarget::pauli_xz(q, x, z));
+ result.push_back(GateTarget::combiner());
+ }
+ }
+ } else {
+ for (const auto &h : paulis) {
+ if (pybind11::isinstance(h)) {
+ GateTarget g = pybind11::cast(h);
+ if (g.pauli_type() != 'I') {
+ if (g.is_inverted_result_target()) {
+ invert ^= true;
+ g.data ^= TARGET_INVERTED_BIT;
+ }
+ result.push_back(g);
+ result.push_back(GateTarget::combiner());
+ continue;
+ }
+
+ }
+
+ std::stringstream ss;
+ ss << "Expected a pauli string or iterable of stim.GateTarget but got this when iterating: ";
+ ss << h;
+ throw std::invalid_argument(ss.str());
+ }
+ }
+
+ if (result.empty()) {
+ std::stringstream ss;
+ ss << "Identity pauli product: paulis=";
+ ss << paulis;
+ throw std::invalid_argument(ss.str());
+ }
+ result.pop_back();
+ if (invert) {
+ result[0].data ^= TARGET_INVERTED_BIT;
+ }
+ return result;
+}
+
+GateTarget target_pauli(uint32_t qubit_index, const pybind11::object &pauli, bool invert) {
+ if ((qubit_index & TARGET_VALUE_MASK) != qubit_index) {
+ std::stringstream ss;
+ ss << "qubit_index=" << qubit_index << " is too large. Maximum qubit index is " << TARGET_VALUE_MASK << ".";
+ throw std::invalid_argument(ss.str());
+ }
+ if (pybind11::isinstance(pauli)) {
+ std::string p = pybind11::cast(pauli);
+ if (p == "X" || p == "x") {
+ return GateTarget::x(qubit_index, invert);
+ } else if (p == "Y" || p == "y") {
+ return GateTarget::y(qubit_index, invert);
+ } else if (p == "Z" || p == "z") {
+ return GateTarget::z(qubit_index, invert);
+ } else if (p == "I") {
+ return GateTarget::qubit(qubit_index, invert);
+ }
+ } else if (pybind11::isinstance(pauli)) {
+ uint8_t p = pybind11::cast(pauli);
+ if (p == 1) {
+ return GateTarget::x(qubit_index, invert);
+ } else if (p == 2) {
+ return GateTarget::y(qubit_index, invert);
+ } else if (p == 3) {
+ return GateTarget::z(qubit_index, invert);
+ } else if (p == 0) {
+ return GateTarget::qubit(qubit_index, invert);
+ }
+ }
+
+ std::stringstream ss;
+ ss << "Expected pauli in [0, 1, 2, 3, *'IXYZxyz'] but got pauli=" << pauli;
+ throw std::invalid_argument(ss.str());
+}
+
GateTarget target_sweep_bit(uint32_t qubit) {
return GateTarget::sweep_bit(qubit);
}
@@ -299,6 +389,82 @@ void top_level(pybind11::module &m) {
)DOC")
.data());
+ m.def(
+ "target_pauli",
+ &target_pauli,
+ pybind11::arg("qubit_index"),
+ pybind11::arg("pauli"),
+ pybind11::arg("invert") = false,
+ clean_doc_string(R"DOC(
+ @signature def target_pauli(qubit_index: int, pauli: Union[str, int], invert: bool = False) -> stim.GateTarget:
+ Returns a pauli target that can be passed into `stim.Circuit.append`.
+
+ Args:
+ qubit_index: The qubit that the Pauli applies to.
+ pauli: The pauli gate to use. This can either be a string identifying the
+ pauli by name ("x", "X", "y", "Y", "z", or "Z") or an integer following
+ the convention (1=X, 2=Y, 3=Z). Setting this argument to "I" or to
+ 0 will return a qubit target instead of a pauli target.
+ invert: Defaults to False. If True, the target is inverted (like "!X10"),
+ indicating that, for example, measurement results should be inverted).
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... stim.target_pauli(2, "X"),
+ ... stim.target_combiner(),
+ ... stim.target_pauli(3, "y", invert=True),
+ ... stim.target_pauli(5, 3),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ ''')
+
+ >>> circuit.append("M", [
+ ... stim.target_pauli(7, "I"),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP X2*!Y3 Z5
+ M 7
+ ''')
+ )DOC")
+ .data());
+
+ m.def(
+ "target_combined_paulis",
+ &target_combined_paulis,
+ pybind11::arg("paulis"),
+ pybind11::arg("invert") = false,
+ clean_doc_string(R"DOC(
+ @signature def target_combined_paulis(paulis: Union[stim.PauliString, List[stim.GateTarget]], invert: bool = False) -> stim.GateTarget:
+ Returns a list of targets encoding a pauli product for instructions like MPP.
+
+ Args:
+ paulis: The paulis to encode into the targets. This can be a
+ `stim.PauliString` or a list of pauli targets from `stim.target_x`,
+ `stim.target_pauli`, etc.
+ invert: Defaults to False. If True, the product is inverted (like "!X2*Y3").
+ Note that this is in addition to any inversions specified by the
+ `paulis` argument.
+
+ Examples:
+ >>> import stim
+ >>> circuit = stim.Circuit()
+ >>> circuit.append("MPP", [
+ ... *stim.target_combined_paulis(stim.PauliString("-XYZ")),
+ ... *stim.target_combined_paulis([stim.target_x(2), stim.target_y(5)]),
+ ... *stim.target_combined_paulis([stim.target_z(9)], invert=True),
+ ... ])
+ >>> circuit
+ stim.Circuit('''
+ MPP !X0*Y1*Z2 X2*Y5 !Z9
+ ''')
+ )DOC")
+ .data());
+
m.def(
"target_sweep_bit",
&target_sweep_bit,
diff --git a/src/stim/py/stim_pybind_test.py b/src/stim/py/stim_pybind_test.py
index 34db65338..22b1a9eb3 100644
--- a/src/stim/py/stim_pybind_test.py
+++ b/src/stim/py/stim_pybind_test.py
@@ -197,3 +197,92 @@ def test_target_methods_accept_gate_targets():
with pytest.raises(ValueError):
stim.target_z(stim.target_sweep_bit(4))
+
+
+def test_target_pauli():
+ assert stim.target_pauli(2, "I") == stim.GateTarget(2)
+ assert stim.target_pauli(2, "X") == stim.target_x(2)
+ assert stim.target_pauli(2, "Y") == stim.target_y(2)
+ assert stim.target_pauli(2, "Z") == stim.target_z(2)
+ assert stim.target_pauli(5, "x") == stim.target_x(5)
+ assert stim.target_pauli(2, "y") == stim.target_y(2)
+ assert stim.target_pauli(2, "z") == stim.target_z(2)
+ assert stim.target_pauli(2, 0) == stim.GateTarget(2)
+ assert stim.target_pauli(2, 1) == stim.target_x(2)
+ assert stim.target_pauli(2, 2) == stim.target_y(2)
+ assert stim.target_pauli(2, 3) == stim.target_z(2)
+ assert stim.target_pauli(2, 3, True) == stim.target_z(2, True)
+ assert stim.target_pauli(qubit_index=2, pauli=3, invert=True) == stim.target_z(2, True)
+
+ with pytest.raises(ValueError, match="too large"):
+ stim.target_pauli(2**31, 'X')
+ with pytest.raises(ValueError, match="Expected pauli"):
+ stim.target_pauli(5, 'F')
+
+
+def test_target_combined_paulis():
+ assert stim.target_combined_paulis(stim.PauliString("XYZ")) == [
+ stim.target_x(0),
+ stim.target_combiner(),
+ stim.target_y(1),
+ stim.target_combiner(),
+ stim.target_z(2),
+ ]
+
+ assert stim.target_combined_paulis(stim.PauliString("X"), True) == [
+ stim.target_x(0, True),
+ ]
+
+ assert stim.target_combined_paulis(stim.PauliString("-XYIZ")) == [
+ stim.target_x(0, invert=True),
+ stim.target_combiner(),
+ stim.target_y(1),
+ stim.target_combiner(),
+ stim.target_z(3),
+ ]
+
+ assert stim.target_combined_paulis(stim.PauliString("-XYIZ"), True) == [
+ stim.target_x(0),
+ stim.target_combiner(),
+ stim.target_y(1),
+ stim.target_combiner(),
+ stim.target_z(3),
+ ]
+
+ assert stim.target_combined_paulis([stim.target_x(5), stim.target_z(9)]) == [
+ stim.target_x(5),
+ stim.target_combiner(),
+ stim.target_z(9),
+ ]
+
+ assert stim.target_combined_paulis([stim.target_x(5, True), stim.target_z(9)]) == [
+ stim.target_x(5, True),
+ stim.target_combiner(),
+ stim.target_z(9),
+ ]
+ assert stim.target_combined_paulis([stim.target_x(5), stim.target_z(9, True)]) == [
+ stim.target_x(5, True),
+ stim.target_combiner(),
+ stim.target_z(9),
+ ]
+ assert stim.target_combined_paulis([stim.target_x(5), stim.target_z(9)], True) == [
+ stim.target_x(5, True),
+ stim.target_combiner(),
+ stim.target_z(9),
+ ]
+ assert stim.target_combined_paulis([stim.target_y(4)]) == [
+ stim.target_y(4),
+ ]
+
+ with pytest.raises(ValueError, match="Expected a pauli string"):
+ stim.target_combined_paulis([stim.target_rec(-2)])
+ with pytest.raises(ValueError, match="Expected a pauli string"):
+ stim.target_combined_paulis([object()])
+ with pytest.raises(ValueError, match="Identity pauli product"):
+ stim.target_combined_paulis([])
+ with pytest.raises(ValueError, match="Identity pauli product"):
+ stim.target_combined_paulis(stim.PauliString(0))
+ with pytest.raises(ValueError, match="Identity pauli product"):
+ stim.target_combined_paulis(stim.PauliString(10))
+ with pytest.raises(ValueError, match="Imaginary"):
+ stim.target_combined_paulis(stim.PauliString("iX"))