Skip to content

Commit dbeec0e

Browse files
committed
Add stateful TableauSampler with deterministic seeding (#974)
Implements a stateful C++ TableauSampler wrapped with Pybind11 to enable reproducible generation of Clifford tableaus via a seedable mt19937_64 random number generator.
1 parent 79ae4f1 commit dbeec0e

7 files changed

Lines changed: 369 additions & 0 deletions

File tree

doc/stim.pyi

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11970,6 +11970,73 @@ class TableauIterator:
1197011970
) -> stim.Tableau:
1197111971
"""Returns the next iterated tableau.
1197211972
"""
11973+
class TableauSampler:
11974+
"""A tool for pseudo-random tableau sampling.
11975+
11976+
Seeds the random number generator once at initialization, then
11977+
produces a reproducible sequence of random tableaus via repeated
11978+
calls to `next_tableau()`.
11979+
11980+
Examples:
11981+
>>> import stim
11982+
>>> s = stim.TableauSampler(5, seed=42)
11983+
>>> t1 = s.next_tableau()
11984+
>>> t2 = s.next_tableau()
11985+
"""
11986+
def __init__(
11987+
self,
11988+
num_qubits: int,
11989+
*,
11990+
seed: int | None = None,
11991+
) -> None:
11992+
"""Creates a tableau sampler.
11993+
11994+
Args:
11995+
num_qubits: The number of qubits each sampled tableau acts on.
11996+
seed: PARTIALLY determines the sequence of sampled tableaus by
11997+
deterministically seeding the random number generator.
11998+
11999+
Must be None or an integer in range(2**64).
12000+
12001+
Defaults to None. When None, the prng is seeded from system
12002+
entropy.
12003+
12004+
When set to an integer, making the exact same series of calls
12005+
on the exact same machine with the exact same version of Stim
12006+
will produce the exact same sequence of tableaus.
12007+
12008+
CAUTION: the sequence produced by a specific seed *WILL NOT*
12009+
be consistent between versions of Stim. This restriction is
12010+
present to make it possible to have future optimizations to
12011+
the random sampling, and is enforced by introducing
12012+
intentional differences in the seeding strategy from version
12013+
to version.
12014+
12015+
CAUTION: the sequence produced by a specific seed *MAY NOT*
12016+
be consistent across machines that differ in the width of
12017+
supported SIMD instructions. For example, using the same seed
12018+
on a machine that supports AVX instructions and one that only
12019+
supports SSE instructions may produce different sequences.
12020+
12021+
Examples:
12022+
>>> import stim
12023+
>>> sampler = stim.TableauSampler(4, seed=12345)
12024+
>>> t = sampler.next_tableau()
12025+
"""
12026+
def next_tableau(
12027+
self,
12028+
) -> stim.Tableau:
12029+
"""Samples a uniformly random tableau.
12030+
12031+
Returns:
12032+
A uniformly random `stim.Tableau` over the sampler's `num_qubits`.
12033+
12034+
Examples:
12035+
>>> import stim
12036+
>>> sampler = stim.TableauSampler(2, seed=42)
12037+
>>> t1 = sampler.next_tableau()
12038+
>>> t2 = sampler.next_tableau()
12039+
"""
1197312040
class TableauSimulator:
1197412041
"""A stabilizer circuit simulator that tracks an inverse stabilizer tableau.
1197512042

file_lists/pybind_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ src/stim/stabilizers/pauli_string.pybind.cc
2727
src/stim/stabilizers/pauli_string_iter.pybind.cc
2828
src/stim/stabilizers/tableau.pybind.cc
2929
src/stim/stabilizers/tableau_iter.pybind.cc
30+
src/stim/stabilizers/tableau_sampler.pybind.cc

glue/python/src/stim/__init__.pyi

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11970,6 +11970,73 @@ class TableauIterator:
1197011970
) -> stim.Tableau:
1197111971
"""Returns the next iterated tableau.
1197211972
"""
11973+
class TableauSampler:
11974+
"""A tool for pseudo-random tableau sampling.
11975+
11976+
Seeds the random number generator once at initialization, then
11977+
produces a reproducible sequence of random tableaus via repeated
11978+
calls to `next_tableau()`.
11979+
11980+
Examples:
11981+
>>> import stim
11982+
>>> s = stim.TableauSampler(5, seed=42)
11983+
>>> t1 = s.next_tableau()
11984+
>>> t2 = s.next_tableau()
11985+
"""
11986+
def __init__(
11987+
self,
11988+
num_qubits: int,
11989+
*,
11990+
seed: int | None = None,
11991+
) -> None:
11992+
"""Creates a tableau sampler.
11993+
11994+
Args:
11995+
num_qubits: The number of qubits each sampled tableau acts on.
11996+
seed: PARTIALLY determines the sequence of sampled tableaus by
11997+
deterministically seeding the random number generator.
11998+
11999+
Must be None or an integer in range(2**64).
12000+
12001+
Defaults to None. When None, the prng is seeded from system
12002+
entropy.
12003+
12004+
When set to an integer, making the exact same series of calls
12005+
on the exact same machine with the exact same version of Stim
12006+
will produce the exact same sequence of tableaus.
12007+
12008+
CAUTION: the sequence produced by a specific seed *WILL NOT*
12009+
be consistent between versions of Stim. This restriction is
12010+
present to make it possible to have future optimizations to
12011+
the random sampling, and is enforced by introducing
12012+
intentional differences in the seeding strategy from version
12013+
to version.
12014+
12015+
CAUTION: the sequence produced by a specific seed *MAY NOT*
12016+
be consistent across machines that differ in the width of
12017+
supported SIMD instructions. For example, using the same seed
12018+
on a machine that supports AVX instructions and one that only
12019+
supports SSE instructions may produce different sequences.
12020+
12021+
Examples:
12022+
>>> import stim
12023+
>>> sampler = stim.TableauSampler(4, seed=12345)
12024+
>>> t = sampler.next_tableau()
12025+
"""
12026+
def next_tableau(
12027+
self,
12028+
) -> stim.Tableau:
12029+
"""Samples a uniformly random tableau.
12030+
12031+
Returns:
12032+
A uniformly random `stim.Tableau` over the sampler's `num_qubits`.
12033+
12034+
Examples:
12035+
>>> import stim
12036+
>>> sampler = stim.TableauSampler(2, seed=42)
12037+
>>> t1 = sampler.next_tableau()
12038+
>>> t2 = sampler.next_tableau()
12039+
"""
1197312040
class TableauSimulator:
1197412041
"""A stabilizer circuit simulator that tracks an inverse stabilizer tableau.
1197512042

src/stim/py/stim.pybind.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "stim/stabilizers/tableau.h"
4444
#include "stim/stabilizers/tableau.pybind.h"
4545
#include "stim/stabilizers/tableau_iter.pybind.h"
46+
#include "stim/stabilizers/tableau_sampler.pybind.h"
4647

4748
#define xstr_literal(s) str_literal(s)
4849
#define str_literal(s) #s
@@ -594,6 +595,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) {
594595
auto c_pauli_string_iter = pybind_pauli_string_iter(m);
595596
auto c_tableau = pybind_tableau(m);
596597
auto c_tableau_iter = pybind_tableau_iter(m);
598+
auto c_tableau_sampler = pybind_tableau_sampler(m);
597599

598600
auto c_circuit_gate_target = pybind_circuit_gate_target(m);
599601
auto c_gate_data = pybind_gate_data(m);
@@ -633,6 +635,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) {
633635
pybind_circuit_methods_extra(m, c_circuit);
634636

635637
pybind_tableau_iter_methods(m, c_tableau_iter);
638+
pybind_tableau_sampler_methods(m, c_tableau_sampler);
636639
pybind_dem_sampler_methods(m, c_dem_sampler);
637640

638641
pybind_detector_error_model_instruction_methods(m, c_detector_error_model_instruction);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "stim/stabilizers/tableau_sampler.pybind.h"
16+
17+
#include "stim/py/base.pybind.h"
18+
19+
using namespace stim;
20+
using namespace stim_pybind;
21+
22+
TableauSampler::TableauSampler(size_t num_qubits, std::mt19937_64 &&rng)
23+
: num_qubits(num_qubits), rng(std::move(rng)) {
24+
}
25+
26+
Tableau<MAX_BITWORD_WIDTH> TableauSampler::next_tableau() {
27+
return Tableau<MAX_BITWORD_WIDTH>::random(num_qubits, rng);
28+
}
29+
30+
std::string TableauSampler::repr() const {
31+
std::stringstream result;
32+
result << "stim.TableauSampler(num_qubits=";
33+
result << num_qubits;
34+
result << ")";
35+
return result.str();
36+
}
37+
38+
pybind11::class_<TableauSampler> stim_pybind::pybind_tableau_sampler(pybind11::module &m) {
39+
return pybind11::class_<TableauSampler>(
40+
m,
41+
"TableauSampler",
42+
clean_doc_string(R"DOC(
43+
A tool for pseudo-random tableau sampling.
44+
45+
Seeds the random number generator once at initialization, then
46+
produces a reproducible sequence of random tableaus via repeated
47+
calls to `next_tableau()`.
48+
49+
Examples:
50+
>>> import stim
51+
>>> s = stim.TableauSampler(5, seed=42)
52+
>>> t1 = s.next_tableau()
53+
>>> t2 = s.next_tableau()
54+
)DOC")
55+
.data());
56+
}
57+
58+
TableauSampler stim_pybind::py_init_tableau_sampler(size_t num_qubits, const pybind11::object &seed) {
59+
return TableauSampler(num_qubits, make_py_seeded_rng(seed));
60+
}
61+
62+
void stim_pybind::pybind_tableau_sampler_methods(
63+
pybind11::module &m, pybind11::class_<TableauSampler> &c) {
64+
c.def(
65+
pybind11::init(&py_init_tableau_sampler),
66+
pybind11::arg("num_qubits"),
67+
pybind11::kw_only(),
68+
pybind11::arg("seed") = pybind11::none(),
69+
clean_doc_string(R"DOC(
70+
Creates a tableau sampler.
71+
72+
Args:
73+
num_qubits: The number of qubits each sampled tableau acts on.
74+
seed: PARTIALLY determines the sequence of sampled tableaus by
75+
deterministically seeding the random number generator.
76+
77+
Must be None or an integer in range(2**64).
78+
79+
Defaults to None. When None, the prng is seeded from system
80+
entropy.
81+
82+
When set to an integer, making the exact same series of calls
83+
on the exact same machine with the exact same version of Stim
84+
will produce the exact same sequence of tableaus.
85+
86+
CAUTION: the sequence produced by a specific seed *WILL NOT*
87+
be consistent between versions of Stim. This restriction is
88+
present to make it possible to have future optimizations to
89+
the random sampling, and is enforced by introducing
90+
intentional differences in the seeding strategy from version
91+
to version.
92+
93+
CAUTION: the sequence produced by a specific seed *MAY NOT*
94+
be consistent across machines that differ in the width of
95+
supported SIMD instructions. For example, using the same seed
96+
on a machine that supports AVX instructions and one that only
97+
supports SSE instructions may produce different sequences.
98+
99+
Examples:
100+
>>> import stim
101+
>>> sampler = stim.TableauSampler(4, seed=12345)
102+
>>> t = sampler.next_tableau()
103+
)DOC")
104+
.data());
105+
106+
c.def(
107+
"next_tableau",
108+
[](TableauSampler &self) {
109+
return self.next_tableau();
110+
},
111+
clean_doc_string(R"DOC(
112+
Samples a uniformly random tableau.
113+
114+
Returns:
115+
A uniformly random `stim.Tableau` over the sampler's `num_qubits`.
116+
117+
Examples:
118+
>>> import stim
119+
>>> sampler = stim.TableauSampler(2, seed=42)
120+
>>> t1 = sampler.next_tableau()
121+
>>> t2 = sampler.next_tableau()
122+
)DOC")
123+
.data());
124+
125+
c.def(
126+
"__repr__",
127+
&TableauSampler::repr,
128+
"Returns a string representation of the `stim.TableauSampler`.");
129+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef _STIM_STABILIZERS_TABLEAU_SAMPLER_PYBIND_H
16+
#define _STIM_STABILIZERS_TABLEAU_SAMPLER_PYBIND_H
17+
18+
#include <pybind11/pybind11.h>
19+
20+
#include "stim/stabilizers/tableau.h"
21+
22+
namespace stim_pybind {
23+
24+
struct TableauSampler {
25+
size_t num_qubits;
26+
std::mt19937_64 rng;
27+
TableauSampler() = delete;
28+
TableauSampler(const TableauSampler &) = delete;
29+
TableauSampler(TableauSampler &&) = default;
30+
TableauSampler(size_t num_qubits, std::mt19937_64 &&rng);
31+
stim::Tableau<stim::MAX_BITWORD_WIDTH> next_tableau();
32+
std::string repr() const;
33+
};
34+
35+
pybind11::class_<TableauSampler> pybind_tableau_sampler(pybind11::module &m);
36+
void pybind_tableau_sampler_methods(pybind11::module &m, pybind11::class_<TableauSampler> &c);
37+
TableauSampler py_init_tableau_sampler(size_t num_qubits, const pybind11::object &seed);
38+
39+
} // namespace stim_pybind
40+
41+
#endif

0 commit comments

Comments
 (0)