From c2637f7e5885af8e335076c8aa1ec27f54c10fd4 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Wed, 14 Feb 2024 19:12:01 -0800 Subject: [PATCH] Allow sparse pauli syntax in `stim.PauliString.__init__` (#695) - For example, `stim.PauliString("X2*Y5") == stim.PauliString("__X__Y")` - Also allow sparse pauli syntax in `stim.has_flow` - Add `stim::simd_bits{_range_ref}.as_u64` convenience method - Refactor `stim::PauliString::safe_accumulate_pauli_term` into `{right,left}_mul_pauli` --- doc/python_api_reference_vDev.md | 44 +++-- doc/stim.pyi | 44 +++-- glue/python/src/stim/__init__.pyi | 44 +++-- src/stim/circuit/circuit.pybind.cc | 31 ++-- src/stim/circuit/stabilizer_flow.inl | 25 +-- src/stim/mem/simd_bits.h | 2 + src/stim/mem/simd_bits.inl | 5 + src/stim/mem/simd_bits_range_ref.h | 3 + src/stim/mem/simd_bits_range_ref.inl | 16 ++ src/stim/mem/simd_bits_range_ref.test.cc | 24 +++ src/stim/stabilizers/flex_pauli_string.cc | 164 ++++++++++++++++-- .../stabilizers/flex_pauli_string.test.cc | 67 +++++++ src/stim/stabilizers/pauli_string.h | 4 +- src/stim/stabilizers/pauli_string.inl | 13 +- src/stim/stabilizers/pauli_string.pybind.cc | 13 +- src/stim/stabilizers/pauli_string.test.cc | 147 ++++++++++++---- .../stabilizers/pauli_string_pybind_test.py | 3 + 17 files changed, 529 insertions(+), 120 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index b922eceab..517845184 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -1907,20 +1907,19 @@ def has_flow( a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and the CNOT flows implemented by the circuit involve these measurements. - A flow like P -> Q means that the circuit transforms P into Q. - A flow like 1 -> P means that the circuit prepares P. - A flow like P -> 1 means that the circuit measures P. - A flow like 1 -> 1 means that the circuit contains a detector. + A flow like P -> Q means the circuit transforms P into Q. + A flow like 1 -> P means the circuit prepares P. + A flow like P -> 1 means the circuit measures P. + A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR). Args: - shorthand: Specifies the flow as a short string like "IX -> -YZ xor rec[1]". + shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]". The text must contain "->" to separate the input pauli string from the - output pauli string. Each pauli string should be a sequence of - characters from "_IXYZ" (or else just "1" to indicate the empty Pauli - string) optionally prefixed by "+" or "-". Measurements are included - by appending " xor rec[k]" for each measurement index k. Indexing uses - the python convention where non-negative indices index from the start - and negative indices index from the end. + output pauli string. Measurements are included by appending + " xor rec[k]" for each measurement index k. Indexing uses the python + convention where non-negative indices index from the start and negative + indices index from the end. The pauli strings are parsed as if by + `stim.PauliString.__init__`. start: The input into the flow at the start of the circuit. Defaults to None (the identity Pauli string). When specified, this should be a `stim.PauliString`, or a `str` (which will be parsed using @@ -1961,6 +1960,16 @@ def has_flow( False >>> m.has_flow('Z -> I xor rec[-1]') True + >>> m.has_flow('Z -> rec[-1]') + True + + >>> cx58 = stim.Circuit('CX 5 8') + >>> cx58.has_flow('X5 -> X5*X8') + True + >>> cx58.has_flow('X_ -> XX') + False + >>> cx58.has_flow('_____X___ -> _____X__X') + True >>> stim.Circuit(''' ... RY 0 @@ -8094,8 +8103,11 @@ def __init__( 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. + string should be either a dense pauli string or a sparse pauli string. A dense + pauli string is made up of characters from '_IXYZ' where '_' and 'I' mean + identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. A sparse + pauli string is a series of integers seperated by '*' and prefixed by 'I', 'X', + 'Y', or 'Z'. Arguments: arg [position-only]: This can be a variety of types, including: @@ -8127,6 +8139,12 @@ def __init__( >>> stim.PauliString("X" for _ in range(4)) stim.PauliString("+XXXX") + + >>> stim.PauliString("-X2*Y6") + stim.PauliString("-__X___Y") + + >>> stim.PauliString("X6*Y6") + stim.PauliString("+i______Z") """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index 32d471208..f6a6d5105 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -1328,20 +1328,19 @@ class Circuit: a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and the CNOT flows implemented by the circuit involve these measurements. - A flow like P -> Q means that the circuit transforms P into Q. - A flow like 1 -> P means that the circuit prepares P. - A flow like P -> 1 means that the circuit measures P. - A flow like 1 -> 1 means that the circuit contains a detector. + A flow like P -> Q means the circuit transforms P into Q. + A flow like 1 -> P means the circuit prepares P. + A flow like P -> 1 means the circuit measures P. + A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR). Args: - shorthand: Specifies the flow as a short string like "IX -> -YZ xor rec[1]". + shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]". The text must contain "->" to separate the input pauli string from the - output pauli string. Each pauli string should be a sequence of - characters from "_IXYZ" (or else just "1" to indicate the empty Pauli - string) optionally prefixed by "+" or "-". Measurements are included - by appending " xor rec[k]" for each measurement index k. Indexing uses - the python convention where non-negative indices index from the start - and negative indices index from the end. + output pauli string. Measurements are included by appending + " xor rec[k]" for each measurement index k. Indexing uses the python + convention where non-negative indices index from the start and negative + indices index from the end. The pauli strings are parsed as if by + `stim.PauliString.__init__`. start: The input into the flow at the start of the circuit. Defaults to None (the identity Pauli string). When specified, this should be a `stim.PauliString`, or a `str` (which will be parsed using @@ -1382,6 +1381,16 @@ class Circuit: False >>> m.has_flow('Z -> I xor rec[-1]') True + >>> m.has_flow('Z -> rec[-1]') + True + + >>> cx58 = stim.Circuit('CX 5 8') + >>> cx58.has_flow('X5 -> X5*X8') + True + >>> cx58.has_flow('X_ -> XX') + False + >>> cx58.has_flow('_____X___ -> _____X__X') + True >>> stim.Circuit(''' ... RY 0 @@ -6212,8 +6221,11 @@ class PauliString: 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. + string should be either a dense pauli string or a sparse pauli string. A dense + pauli string is made up of characters from '_IXYZ' where '_' and 'I' mean + identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. A sparse + pauli string is a series of integers seperated by '*' and prefixed by 'I', 'X', + 'Y', or 'Z'. Arguments: arg [position-only]: This can be a variety of types, including: @@ -6245,6 +6257,12 @@ class PauliString: >>> stim.PauliString("X" for _ in range(4)) stim.PauliString("+XXXX") + + >>> stim.PauliString("-X2*Y6") + stim.PauliString("-__X___Y") + + >>> stim.PauliString("X6*Y6") + stim.PauliString("+i______Z") """ def __itruediv__( self, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 32d471208..f6a6d5105 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -1328,20 +1328,19 @@ class Circuit: a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and the CNOT flows implemented by the circuit involve these measurements. - A flow like P -> Q means that the circuit transforms P into Q. - A flow like 1 -> P means that the circuit prepares P. - A flow like P -> 1 means that the circuit measures P. - A flow like 1 -> 1 means that the circuit contains a detector. + A flow like P -> Q means the circuit transforms P into Q. + A flow like 1 -> P means the circuit prepares P. + A flow like P -> 1 means the circuit measures P. + A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR). Args: - shorthand: Specifies the flow as a short string like "IX -> -YZ xor rec[1]". + shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]". The text must contain "->" to separate the input pauli string from the - output pauli string. Each pauli string should be a sequence of - characters from "_IXYZ" (or else just "1" to indicate the empty Pauli - string) optionally prefixed by "+" or "-". Measurements are included - by appending " xor rec[k]" for each measurement index k. Indexing uses - the python convention where non-negative indices index from the start - and negative indices index from the end. + output pauli string. Measurements are included by appending + " xor rec[k]" for each measurement index k. Indexing uses the python + convention where non-negative indices index from the start and negative + indices index from the end. The pauli strings are parsed as if by + `stim.PauliString.__init__`. start: The input into the flow at the start of the circuit. Defaults to None (the identity Pauli string). When specified, this should be a `stim.PauliString`, or a `str` (which will be parsed using @@ -1382,6 +1381,16 @@ class Circuit: False >>> m.has_flow('Z -> I xor rec[-1]') True + >>> m.has_flow('Z -> rec[-1]') + True + + >>> cx58 = stim.Circuit('CX 5 8') + >>> cx58.has_flow('X5 -> X5*X8') + True + >>> cx58.has_flow('X_ -> XX') + False + >>> cx58.has_flow('_____X___ -> _____X__X') + True >>> stim.Circuit(''' ... RY 0 @@ -6212,8 +6221,11 @@ class PauliString: 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. + string should be either a dense pauli string or a sparse pauli string. A dense + pauli string is made up of characters from '_IXYZ' where '_' and 'I' mean + identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. A sparse + pauli string is a series of integers seperated by '*' and prefixed by 'I', 'X', + 'Y', or 'Z'. Arguments: arg [position-only]: This can be a variety of types, including: @@ -6245,6 +6257,12 @@ class PauliString: >>> stim.PauliString("X" for _ in range(4)) stim.PauliString("+XXXX") + + >>> stim.PauliString("-X2*Y6") + stim.PauliString("-__X___Y") + + >>> stim.PauliString("X6*Y6") + stim.PauliString("+i______Z") """ def __itruediv__( self, diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 167e8fe40..6ee717ed5 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -2335,20 +2335,19 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ Q means that the circuit transforms P into Q. - A flow like 1 -> P means that the circuit prepares P. - A flow like P -> 1 means that the circuit measures P. - A flow like 1 -> 1 means that the circuit contains a detector. + A flow like P -> Q means the circuit transforms P into Q. + A flow like 1 -> P means the circuit prepares P. + A flow like P -> 1 means the circuit measures P. + A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR). Args: - shorthand: Specifies the flow as a short string like "IX -> -YZ xor rec[1]". + shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]". The text must contain "->" to separate the input pauli string from the - output pauli string. Each pauli string should be a sequence of - characters from "_IXYZ" (or else just "1" to indicate the empty Pauli - string) optionally prefixed by "+" or "-". Measurements are included - by appending " xor rec[k]" for each measurement index k. Indexing uses - the python convention where non-negative indices index from the start - and negative indices index from the end. + output pauli string. Measurements are included by appending + " xor rec[k]" for each measurement index k. Indexing uses the python + convention where non-negative indices index from the start and negative + indices index from the end. The pauli strings are parsed as if by + `stim.PauliString.__init__`. start: The input into the flow at the start of the circuit. Defaults to None (the identity Pauli string). When specified, this should be a `stim.PauliString`, or a `str` (which will be parsed using @@ -2389,6 +2388,16 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> m.has_flow('Z -> I xor rec[-1]') True + >>> m.has_flow('Z -> rec[-1]') + True + + >>> cx58 = stim.Circuit('CX 5 8') + >>> cx58.has_flow('X5 -> X5*X8') + True + >>> cx58.has_flow('X_ -> XX') + False + >>> cx58.has_flow('_____X___ -> _____X__X') + True >>> stim.Circuit(''' ... RY 0 diff --git a/src/stim/circuit/stabilizer_flow.inl b/src/stim/circuit/stabilizer_flow.inl index 57c651818..74c81d93b 100644 --- a/src/stim/circuit/stabilizer_flow.inl +++ b/src/stim/circuit/stabilizer_flow.inl @@ -4,6 +4,7 @@ #include "stim/simulators/frame_simulator_util.h" #include "stim/simulators/sparse_rev_frame_tracker.h" #include "stim/simulators/tableau_simulator.h" +#include "stim/stabilizers/flex_pauli_string.h" namespace stim { @@ -104,22 +105,14 @@ PauliString parse_non_empty_pauli_string_allowing_i(std::string_view text, bo throw std::invalid_argument("Got an ambiguously blank pauli string. Use '1' for the empty Pauli string."); } - bool negate = false; - if (text.starts_with('i')) { - *imag_out = true; - text = text.substr(1); - } else if (text.starts_with("-i")) { - negate = true; - *imag_out = true; - text = text.substr(2); - } else if (text.starts_with("+i")) { - *imag_out = true; - text = text.substr(2); - } - PauliString result = PauliString::from_str(text); - if (negate) { - result.sign ^= 1; - } + auto flex = FlexPauliString::from_text(text); + *imag_out = flex.imag; + + PauliString result(flex.value.num_qubits); + size_t nb = std::min(flex.value.xs.num_u8_padded(), result.xs.num_u8_padded()); + memcpy(result.xs.u8, flex.value.xs.u8, nb); + memcpy(result.zs.u8, flex.value.zs.u8, nb); + result.sign = flex.value.sign; return result; } diff --git a/src/stim/mem/simd_bits.h b/src/stim/mem/simd_bits.h index dd0b170e0..f00795f95 100644 --- a/src/stim/mem/simd_bits.h +++ b/src/stim/mem/simd_bits.h @@ -156,6 +156,8 @@ struct simd_bits { inline size_t num_bits_padded() const { return num_simd_words * W; } + + uint64_t as_u64() const; }; template diff --git a/src/stim/mem/simd_bits.inl b/src/stim/mem/simd_bits.inl index 73d1343f1..532b9ee29 100644 --- a/src/stim/mem/simd_bits.inl +++ b/src/stim/mem/simd_bits.inl @@ -308,6 +308,11 @@ size_t simd_bits::popcnt() const { return simd_bits_range_ref(*this).popcnt(); } +template +uint64_t simd_bits::as_u64() const { + return simd_bits_range_ref(*this).as_u64(); +} + template size_t simd_bits::countr_zero() const { return simd_bits_range_ref(*this).countr_zero(); diff --git a/src/stim/mem/simd_bits_range_ref.h b/src/stim/mem/simd_bits_range_ref.h index 376e1fe2f..b5c468e61 100644 --- a/src/stim/mem/simd_bits_range_ref.h +++ b/src/stim/mem/simd_bits_range_ref.h @@ -81,6 +81,9 @@ struct simd_bits_range_ref { bool operator!=(const simd_bits_range_ref &other) const; /// Determines whether or not any of the bits in the referenced range are non-zero. bool not_zero() const; + /// Treats the referenced range as an unsigned integer, and returns it as a uint64_t. + /// If the integer is too large to fit, an exception is raised. + uint64_t as_u64() const; /// Returns a reference to a given bit within the referenced range. inline bit_ref operator[](size_t k) { diff --git a/src/stim/mem/simd_bits_range_ref.inl b/src/stim/mem/simd_bits_range_ref.inl index 29b74f8d2..23da3fd3e 100644 --- a/src/stim/mem/simd_bits_range_ref.inl +++ b/src/stim/mem/simd_bits_range_ref.inl @@ -247,4 +247,20 @@ bool simd_bits_range_ref::intersects(const simd_bits_range_ref other) cons return v != 0; } +template +uint64_t simd_bits_range_ref::as_u64() const { + size_t n64 = num_u64_padded(); + for (size_t k = 1; k < n64; k++) { + if (u64[k]) { + throw std::invalid_argument("Too large to fit into a uint64_t."); + } + } + size_t n1 = std::min(num_bits_padded(), (size_t)64); + uint64_t v = 0; + for (size_t k = 0; k < n1; k++) { + v ^= uint64_t{(*this)[k]} << k; + } + return v; +} + } // namespace stim diff --git a/src/stim/mem/simd_bits_range_ref.test.cc b/src/stim/mem/simd_bits_range_ref.test.cc index 2af444dab..f3bcd3aa7 100644 --- a/src/stim/mem/simd_bits_range_ref.test.cc +++ b/src/stim/mem/simd_bits_range_ref.test.cc @@ -401,3 +401,27 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, prefix_ref, { prefix[0] = true; ASSERT_TRUE(data[0]); }) + +TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, as_u64, { + simd_bits data(1024); + simd_bits_range_ref ref(data); + ASSERT_EQ(data.as_u64(), 0); + ASSERT_EQ(ref.as_u64(), 0); + + ref[63] = 1; + ASSERT_EQ(data.as_u64(), uint64_t{1} << 63); + ASSERT_EQ(ref.as_u64(), uint64_t{1} << 63); + ASSERT_EQ(data.word_range_ref(0, 0).as_u64(), 0); + ASSERT_EQ(data.word_range_ref(0, 1).as_u64(), uint64_t{1} << 63); + ASSERT_EQ(data.word_range_ref(0, 2).as_u64(), uint64_t{1} << 63); + ASSERT_EQ(data.word_range_ref(1, 1).as_u64(), 0); + ASSERT_EQ(data.word_range_ref(1, 2).as_u64(), 0); + + ref[64] = 1; + ASSERT_THROW({ data.as_u64(); }, std::invalid_argument); + ASSERT_THROW({ data.word_range_ref(0, 2).as_u64(); }, std::invalid_argument); + ASSERT_THROW({ ref.as_u64(); }, std::invalid_argument); + if (data.num_simd_words > 2) { + ASSERT_EQ(data.word_range_ref(2, 1).as_u64(), 0); + } +}) diff --git a/src/stim/stabilizers/flex_pauli_string.cc b/src/stim/stabilizers/flex_pauli_string.cc index 2f3d4b91c..82a758457 100644 --- a/src/stim/stabilizers/flex_pauli_string.cc +++ b/src/stim/stabilizers/flex_pauli_string.cc @@ -177,20 +177,154 @@ std::string FlexPauliString::str() const { return ss.str(); } +static size_t parse_size_of_pauli_string_shorthand_if_sparse(std::string_view text) { + uint64_t cur_index = 0; + bool has_cur_index = false; + size_t num_qubits = 0; + + auto flush = [&]() { + if (has_cur_index) { + num_qubits = std::max(num_qubits, (size_t)cur_index + 1); + if (cur_index == UINT64_MAX || num_qubits <= cur_index) { + throw std::invalid_argument(""); + } + cur_index = 0; + has_cur_index = false; + } + }; + + for (char c : text) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + has_cur_index = true; + cur_index = mul_saturate(cur_index, 10); + cur_index = add_saturate(cur_index, c - '0'); + break; + default: + flush(); + break; + } + } + flush(); + return num_qubits; +} + +static void parse_sparse_pauli_string(std::string_view text, FlexPauliString *out) { + uint64_t cur_index = 0; + bool has_cur_index = false; + char cur_pauli = '\0'; + + auto flush = [&]() { + if (cur_pauli == '\0' || !has_cur_index || cur_index > out->value.num_qubits) { + throw std::invalid_argument(""); + } + out->value.right_mul_pauli( + GateTarget::pauli_xz(cur_index, cur_pauli == 'X' || cur_pauli == 'Y', cur_pauli == 'Z' || cur_pauli == 'Y'), + &out->imag); + has_cur_index = false; + cur_pauli = '\0'; + cur_index = 0; + }; + + for (char c : text) { + switch (c) { + case '*': + flush(); + break; + case 'I': + case 'x': + case 'X': + case 'y': + case 'Y': + case 'z': + case 'Z': + if (cur_pauli != '\0') { + throw std::invalid_argument(""); + } + cur_pauli = toupper(c); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (cur_pauli == '\0') { + throw std::invalid_argument(""); + } + has_cur_index = true; + cur_index = mul_saturate(cur_index, 10); + cur_index = add_saturate(cur_index, c - '0'); + break; + default: + throw std::invalid_argument(""); + } + } + flush(); +} + 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; + bool negated = false; + bool imaginary = false; + if (text.starts_with("-")) { + negated = true; + text = text.substr(1); + } else if (text.starts_with("+")) { + text = text.substr(1); + } + if (text.starts_with("i")) { + imaginary = true; + text = text.substr(1); + } + + size_t sparse_size = parse_size_of_pauli_string_shorthand_if_sparse(text); + size_t num_qubits = sparse_size > 0 ? sparse_size : text.size(); + FlexPauliString result(num_qubits); + result.imag = imaginary; + result.value.sign = negated; + if (sparse_size > 0) { + try { + parse_sparse_pauli_string(text, &result); + } catch (const std::invalid_argument &) { + throw std::invalid_argument("Not a valid Pauli string shorthand: '" + std::string(text) + "'"); + } + } else { + for (size_t k = 0; k < text.size(); k++) { + switch (text[k]) { + case 'I': + case '_': + break; + case 'x': + case 'X': + result.value.xs[k] = true; + break; + case 'y': + case 'Y': + result.value.xs[k] = true; + result.value.zs[k] = true; + break; + case 'z': + case 'Z': + result.value.zs[k] = true; + break; + default: + throw std::invalid_argument("Not a valid Pauli string shorthand: '" + std::string(text) + "'"); + } + } + } + + return result; } diff --git a/src/stim/stabilizers/flex_pauli_string.test.cc b/src/stim/stabilizers/flex_pauli_string.test.cc index 59d7cdbdd..6e7b5f600 100644 --- a/src/stim/stabilizers/flex_pauli_string.test.cc +++ b/src/stim/stabilizers/flex_pauli_string.test.cc @@ -9,3 +9,70 @@ TEST(flex_pauli_string, mul) { FlexPauliString p2 = FlexPauliString::from_text("i__Z"); ASSERT_EQ(p1 * p2, FlexPauliString::from_text("-XY_")); } + +TEST(flex_pauli_string, from_text) { + auto f = FlexPauliString::from_text("-iIXYZ_xyz"); + ASSERT_EQ(f.value.num_qubits, 8); + ASSERT_EQ(f.imag, true); + ASSERT_EQ(f.value.sign, true); + ASSERT_EQ(f.value.xs.as_u64(), 0b01100110); + ASSERT_EQ(f.value.zs.as_u64(), 0b11001100); + + f = FlexPauliString::from_text("iX"); + ASSERT_EQ(f.value.num_qubits, 1); + ASSERT_EQ(f.imag, true); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b1); + ASSERT_EQ(f.value.zs.as_u64(), 0b0); + + f = FlexPauliString::from_text("Y"); + ASSERT_EQ(f.value.num_qubits, 1); + ASSERT_EQ(f.imag, false); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b1); + ASSERT_EQ(f.value.zs.as_u64(), 0b1); + + f = FlexPauliString::from_text("+Z"); + ASSERT_EQ(f.value.num_qubits, 1); + ASSERT_EQ(f.imag, false); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b0); + ASSERT_EQ(f.value.zs.as_u64(), 0b1); + + f = FlexPauliString::from_text("X8"); + ASSERT_EQ(f.value.num_qubits, 9); + ASSERT_EQ(f.imag, false); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b100000000); + ASSERT_EQ(f.value.zs.as_u64(), 0b000000000); + + f = FlexPauliString::from_text("X8*Y2"); + ASSERT_EQ(f.value.num_qubits, 9); + ASSERT_EQ(f.imag, false); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b100000100); + ASSERT_EQ(f.value.zs.as_u64(), 0b000000100); + + f = FlexPauliString::from_text("X8*Y2*X8"); + ASSERT_EQ(f.value.num_qubits, 9); + ASSERT_EQ(f.imag, false); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b000000100); + ASSERT_EQ(f.value.zs.as_u64(), 0b000000100); + + f = FlexPauliString::from_text("X8*Y2*Y8"); + ASSERT_EQ(f.value.num_qubits, 9); + ASSERT_EQ(f.imag, true); + ASSERT_EQ(f.value.sign, false); + ASSERT_EQ(f.value.xs.as_u64(), 0b000000100); + ASSERT_EQ(f.value.zs.as_u64(), 0b100000100); + + f = FlexPauliString::from_text("Y8*Y2*X8"); + ASSERT_EQ(f.value.num_qubits, 9); + ASSERT_EQ(f.imag, true); + ASSERT_EQ(f.value.sign, true); + ASSERT_EQ(f.value.xs.as_u64(), 0b000000100); + ASSERT_EQ(f.value.zs.as_u64(), 0b100000100); + + ASSERT_EQ(FlexPauliString::from_text("X1"), FlexPauliString::from_text("_X")); +} diff --git a/src/stim/stabilizers/pauli_string.h b/src/stim/stabilizers/pauli_string.h index 8ba6435c9..540ba0bf3 100644 --- a/src/stim/stabilizers/pauli_string.h +++ b/src/stim/stabilizers/pauli_string.h @@ -132,7 +132,9 @@ struct PauliString { /// avoid quadratic overheads from constant slight expansions. void ensure_num_qubits(size_t min_num_qubits, double resize_pad_factor); - void safe_accumulate_pauli_term(GateTarget t, bool *imag); + void mul_pauli_term(GateTarget t, bool *imag, bool right_mul); + void left_mul_pauli(GateTarget t, bool *imag); + void right_mul_pauli(GateTarget t, bool *imag); }; /// Writes a string describing the given Pauli string to an output stream. diff --git a/src/stim/stabilizers/pauli_string.inl b/src/stim/stabilizers/pauli_string.inl index a07b184f0..e53c37819 100644 --- a/src/stim/stabilizers/pauli_string.inl +++ b/src/stim/stabilizers/pauli_string.inl @@ -166,7 +166,7 @@ void PauliString::ensure_num_qubits(size_t min_num_qubits, double resize_pad_ } template -void PauliString::safe_accumulate_pauli_term(GateTarget t, bool *imag) { +void PauliString::mul_pauli_term(GateTarget t, bool *imag, bool right_mul) { auto q = t.qubit_value(); ensure_num_qubits(q + 1, 1.25); bool x2 = (bool)(t.data & TARGET_PAULI_X_BIT); @@ -188,6 +188,17 @@ void PauliString::safe_accumulate_pauli_term(GateTarget t, bool *imag) { sign ^= (*imag ^ old_x1 ^ old_z1 ^ x1z2) & anti_commutes; sign ^= (bool)(t.data & TARGET_INVERTED_BIT); *imag ^= anti_commutes; + sign ^= right_mul && anti_commutes; +} + +template +void PauliString::left_mul_pauli(GateTarget t, bool *imag) { + mul_pauli_term(t, imag, false); +} + +template +void PauliString::right_mul_pauli(GateTarget t, bool *imag) { + mul_pauli_term(t, imag, true); } template diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index b0951d322..3c335cee0 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -418,8 +418,11 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla 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. + string should be either a dense pauli string or a sparse pauli string. A dense + pauli string is made up of characters from '_IXYZ' where '_' and 'I' mean + identity, 'X' means Pauli X, 'Y' means Pauli Y, and 'Z' means Pauli Z. A sparse + pauli string is a series of integers seperated by '*' and prefixed by 'I', 'X', + 'Y', or 'Z'. Arguments: arg [position-only]: This can be a variety of types, including: @@ -451,6 +454,12 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla >>> stim.PauliString("X" for _ in range(4)) stim.PauliString("+XXXX") + + >>> stim.PauliString("-X2*Y6") + stim.PauliString("-__X___Y") + + >>> stim.PauliString("X6*Y6") + stim.PauliString("+i______Z") )DOC") .data()); diff --git a/src/stim/stabilizers/pauli_string.test.cc b/src/stim/stabilizers/pauli_string.test.cc index 3467a387d..ca98262af 100644 --- a/src/stim/stabilizers/pauli_string.test.cc +++ b/src/stim/stabilizers/pauli_string.test.cc @@ -18,7 +18,6 @@ #include "stim/circuit/circuit.h" #include "stim/mem/simd_word.test.h" -#include "stim/stabilizers/tableau.h" #include "stim/test_util.test.h" using namespace stim; @@ -633,117 +632,195 @@ TEST_EACH_WORD_SIZE_W(pauli_string, before_tableau, { std::invalid_argument); }) -TEST_EACH_WORD_SIZE_W(pauli_string, safe_accumulate_pauli_term, { +TEST_EACH_WORD_SIZE_W(pauli_string, left_mul_pauli, { PauliString p(0); bool imag = false; - p.safe_accumulate_pauli_term(GateTarget::x(5), &imag); + p.left_mul_pauli(GateTarget::x(5), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("_____X")); - p.safe_accumulate_pauli_term(GateTarget::x(5), &imag); + p.left_mul_pauli(GateTarget::x(5), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("______")); - p.safe_accumulate_pauli_term(GateTarget::x(5), &imag); + p.left_mul_pauli(GateTarget::x(5), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("_____X")); - p.safe_accumulate_pauli_term(GateTarget::z(5), &imag); + p.left_mul_pauli(GateTarget::z(5), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("_____Y")); - p.safe_accumulate_pauli_term(GateTarget::z(5), &imag); + p.left_mul_pauli(GateTarget::z(5), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("_____X")); - p.safe_accumulate_pauli_term(GateTarget::y(5), &imag); + p.left_mul_pauli(GateTarget::y(5), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-_____Z")); - p.safe_accumulate_pauli_term(GateTarget::y(15), &imag); + p.left_mul_pauli(GateTarget::y(15), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-_____Z_________Y")); - p.safe_accumulate_pauli_term(GateTarget::y(15, true), &imag); + p.left_mul_pauli(GateTarget::y(15, true), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("_____Z__________")); }) -TEST_EACH_WORD_SIZE_W(pauli_string, safe_accumulate_pauli_term_mul_table, { +TEST_EACH_WORD_SIZE_W(pauli_string, left_mul_pauli_mul_table, { PauliString p(1); bool imag = false; p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-I")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("I")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("I")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-Z")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("Y")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("Z")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("I")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-X")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::x(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::x(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("-Y")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::y(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::y(0), &imag); ASSERT_EQ(imag, 1); ASSERT_EQ(p, PauliString("X")); p = PauliString(1); imag = false; - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); - p.safe_accumulate_pauli_term(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); + p.left_mul_pauli(GateTarget::z(0), &imag); + ASSERT_EQ(imag, 0); + ASSERT_EQ(p, PauliString("I")); +}) + +TEST_EACH_WORD_SIZE_W(pauli_string, right_mul_pauli_mul_table, { + PauliString p(1); + bool imag = false; + + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::x(0), &imag); + p.right_mul_pauli(GateTarget::y(0), &imag); + p.right_mul_pauli(GateTarget::z(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("+I")); + + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::z(0), &imag); + p.right_mul_pauli(GateTarget::y(0), &imag); + p.right_mul_pauli(GateTarget::x(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("-I")); + + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::x(0), &imag); + p.right_mul_pauli(GateTarget::x(0), &imag); + ASSERT_EQ(imag, 0); + ASSERT_EQ(p, PauliString("I")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::x(0), &imag); + p.right_mul_pauli(GateTarget::y(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("Z")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::x(0), &imag); + p.right_mul_pauli(GateTarget::z(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("-Y")); + + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::y(0), &imag); + p.right_mul_pauli(GateTarget::x(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("-Z")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::y(0), &imag); + p.right_mul_pauli(GateTarget::y(0), &imag); + ASSERT_EQ(imag, 0); + ASSERT_EQ(p, PauliString("I")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::y(0), &imag); + p.right_mul_pauli(GateTarget::z(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("X")); + + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::z(0), &imag); + p.right_mul_pauli(GateTarget::x(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("Y")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::z(0), &imag); + p.right_mul_pauli(GateTarget::y(0), &imag); + ASSERT_EQ(imag, 1); + ASSERT_EQ(p, PauliString("-X")); + p = PauliString(1); + imag = false; + p.right_mul_pauli(GateTarget::z(0), &imag); + p.right_mul_pauli(GateTarget::z(0), &imag); ASSERT_EQ(imag, 0); ASSERT_EQ(p, PauliString("I")); }) diff --git a/src/stim/stabilizers/pauli_string_pybind_test.py b/src/stim/stabilizers/pauli_string_pybind_test.py index 2b178749e..4efff759e 100644 --- a/src/stim/stabilizers/pauli_string_pybind_test.py +++ b/src/stim/stabilizers/pauli_string_pybind_test.py @@ -66,6 +66,9 @@ def test_from_str(): assert p[0] == 1 assert p.sign == -1j + assert stim.PauliString("X5*Y10") == stim.PauliString("_____X____Y") + assert stim.PauliString("X5*Y5") == stim.PauliString("iZ5") + def test_equality(): assert not (stim.PauliString(4) == None)