Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow sparse pauli syntax in stim.PauliString.__init__ #695

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions doc/python_api_reference_vDev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")
"""
```

Expand Down
44 changes: 31 additions & 13 deletions doc/stim.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
44 changes: 31 additions & 13 deletions glue/python/src/stim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
31 changes: 20 additions & 11 deletions src/stim/circuit/circuit.pybind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2335,20 +2335,19 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
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
Expand Down Expand Up @@ -2389,6 +2388,16 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_<Ci
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
Expand Down
25 changes: 9 additions & 16 deletions src/stim/circuit/stabilizer_flow.inl
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -104,22 +105,14 @@ PauliString<W> 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<W> result = PauliString<W>::from_str(text);
if (negate) {
result.sign ^= 1;
}
auto flex = FlexPauliString::from_text(text);
*imag_out = flex.imag;

PauliString<W> 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;
}

Expand Down
2 changes: 2 additions & 0 deletions src/stim/mem/simd_bits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <size_t W>
Expand Down
5 changes: 5 additions & 0 deletions src/stim/mem/simd_bits.inl
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ size_t simd_bits<W>::popcnt() const {
return simd_bits_range_ref<W>(*this).popcnt();
}

template <size_t W>
uint64_t simd_bits<W>::as_u64() const {
return simd_bits_range_ref<W>(*this).as_u64();
}

template <size_t W>
size_t simd_bits<W>::countr_zero() const {
return simd_bits_range_ref<W>(*this).countr_zero();
Expand Down
3 changes: 3 additions & 0 deletions src/stim/mem/simd_bits_range_ref.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ struct simd_bits_range_ref {
bool operator!=(const simd_bits_range_ref<W> &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) {
Expand Down
16 changes: 16 additions & 0 deletions src/stim/mem/simd_bits_range_ref.inl
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,20 @@ bool simd_bits_range_ref<W>::intersects(const simd_bits_range_ref<W> other) cons
return v != 0;
}

template <size_t W>
uint64_t simd_bits_range_ref<W>::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
24 changes: 24 additions & 0 deletions src/stim/mem/simd_bits_range_ref.test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<W> data(1024);
simd_bits_range_ref<W> 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);
}
})
Loading
Loading