diff --git a/WORKSPACE b/WORKSPACE
index c6dd61c4a..5d05cb0f9 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,10 +1,12 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@pybind11_bazel//:python_configure.bzl", "python_configure")
+
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11.BUILD",
- sha256 = "d475978da0cdc2d43b73f30910786759d593a9d8ee05b1b6846d1eb16c6d2e0c",
- strip_prefix = "pybind11-2.11.1",
- urls = ["https://github.com/pybind/pybind11/archive/refs/tags/v2.11.1.tar.gz"],
+ sha256 = "bf8f242abd1abcd375d516a7067490fb71abd79519a282d22b6e4d19282185a7",
+ strip_prefix = "pybind11-2.12.0",
+ urls = ["https://github.com/pybind/pybind11/archive/refs/tags/v2.12.0.tar.gz"],
)
-load("@pybind11_bazel//:python_configure.bzl", "python_configure")
+
python_configure(name = "local_config_python")
diff --git a/dev/util_gen_stub_file.py b/dev/util_gen_stub_file.py
index 3bfabd0fe..26a34530c 100644
--- a/dev/util_gen_stub_file.py
+++ b/dev/util_gen_stub_file.py
@@ -94,6 +94,12 @@ def __init__(self):
def splay_signature(sig: str) -> List[str]:
+ # Maintain backwards compatibility with python 3.6
+ sig = sig.replace('list[', 'List[')
+ sig = sig.replace('dict[', 'Dict[')
+ sig = sig.replace('tuple[', 'Tuple[')
+ sig = sig.replace('set[', 'Set[')
+
assert sig.startswith('def')
out = []
diff --git a/doc/gates.md b/doc/gates.md
index 620fd8df4..74718d299 100644
--- a/doc/gates.md
+++ b/doc/gates.md
@@ -3111,7 +3111,7 @@ Decomposition (into H, S, CX, M, R):
### The 'SPP_DAG' Instruction
-The generalized S gate. Phases the -1 eigenspace of Pauli product observables by i.
+The generalized S_DAG gate. Phases the -1 eigenspace of Pauli product observables by -i.
Parens Arguments:
diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index eb995c9a9..d05fb9dc1 100644
--- a/doc/python_api_reference_vDev.md
+++ b/doc/python_api_reference_vDev.md
@@ -39,6 +39,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.Circuit.get_final_qubit_coordinates`](#stim.Circuit.get_final_qubit_coordinates)
- [`stim.Circuit.has_all_flows`](#stim.Circuit.has_all_flows)
- [`stim.Circuit.has_flow`](#stim.Circuit.has_flow)
+ - [`stim.Circuit.insert`](#stim.Circuit.insert)
- [`stim.Circuit.inverse`](#stim.Circuit.inverse)
- [`stim.Circuit.likeliest_error_sat_problem`](#stim.Circuit.likeliest_error_sat_problem)
- [`stim.Circuit.num_detectors`](#stim.Circuit.num_detectors)
@@ -2223,6 +2224,64 @@ def has_flow(
"""
```
+
+```python
+# stim.Circuit.insert
+
+# (in class stim.Circuit)
+def insert(
+ self,
+ index: int,
+ operation: Union[stim.CircuitInstruction, stim.Circuit],
+) -> None:
+ """Inserts an operation at the given index, pushing existing operations forward.
+
+ Note that, unlike when appending operations or parsing stim circuit files,
+ inserted operations aren't automatically fused into the preceding operation.
+ This is to avoid creating complicated situations where it's difficult to reason
+ about how the indices of operations change in response to insertions.
+
+ Args:
+ index: The index to insert at.
+
+ Must satisfy -len(circuit) <= index < len(circuit). Negative indices
+ are made non-negative by adding len(circuit) to them, so they refer to
+ indices relative to the end of the circuit instead of the start.
+
+ Instructions before the index are not shifted. Instructions that
+ were at or after the index are shifted forwards.
+ operation: The object to insert. This can be a single
+ stim.CircuitInstruction or an entire stim.Circuit.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit('''
+ ... H 0
+ ... S 1
+ ... X 2
+ ... ''')
+ >>> c.insert(1, stim.CircuitInstruction("Y", [3, 4, 5]))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ X 2
+ ''')
+ >>> c.insert(-1, stim.Circuit("S 999\nCX 0 1\nCZ 2 3"))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ S 999
+ CX 0 1
+ CZ 2 3
+ X 2
+ ''')
+ """
+```
+
```python
# stim.Circuit.inverse
@@ -2569,6 +2628,14 @@ def reference_sample(
Returns:
reference_sample: reference sample sampled from the given circuit.
+
+ Examples:
+ >>> import stim
+ >>> stim.Circuit('''
+ ... X 1
+ ... M 0 1
+ ... ''').reference_sample()
+ array([False, True])
"""
```
@@ -13832,15 +13899,18 @@ def state_vector(
>>> import numpy as np
>>> s = stim.TableauSimulator()
>>> s.x(2)
- >>> list(s.state_vector(endian='little'))
- [0j, 0j, 0j, 0j, (1+0j), 0j, 0j, 0j]
+ >>> s.state_vector(endian='little')
+ array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
- >>> list(s.state_vector(endian='big'))
- [0j, (1+0j), 0j, 0j, 0j, 0j, 0j, 0j]
+ >>> s.state_vector(endian='big')
+ array([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
>>> s.sqrt_x(1, 2)
- >>> list(s.state_vector())
- [(0.5+0j), 0j, -0.5j, 0j, 0.5j, 0j, (0.5+0j), 0j]
+ >>> s.state_vector()
+ array([0.5+0.j , 0. +0.j , 0. -0.5j, 0. +0.j , 0. +0.5j, 0. +0.j ,
+ 0.5+0.j , 0. +0.j ], dtype=complex64)
"""
```
diff --git a/doc/stim.pyi b/doc/stim.pyi
index 85b5e0b10..8d603dd77 100644
--- a/doc/stim.pyi
+++ b/doc/stim.pyi
@@ -1600,6 +1600,57 @@ class Circuit:
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
"""
+ def insert(
+ self,
+ index: int,
+ operation: Union[stim.CircuitInstruction, stim.Circuit],
+ ) -> None:
+ """Inserts an operation at the given index, pushing existing operations forward.
+
+ Note that, unlike when appending operations or parsing stim circuit files,
+ inserted operations aren't automatically fused into the preceding operation.
+ This is to avoid creating complicated situations where it's difficult to reason
+ about how the indices of operations change in response to insertions.
+
+ Args:
+ index: The index to insert at.
+
+ Must satisfy -len(circuit) <= index < len(circuit). Negative indices
+ are made non-negative by adding len(circuit) to them, so they refer to
+ indices relative to the end of the circuit instead of the start.
+
+ Instructions before the index are not shifted. Instructions that
+ were at or after the index are shifted forwards.
+ operation: The object to insert. This can be a single
+ stim.CircuitInstruction or an entire stim.Circuit.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit('''
+ ... H 0
+ ... S 1
+ ... X 2
+ ... ''')
+ >>> c.insert(1, stim.CircuitInstruction("Y", [3, 4, 5]))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ X 2
+ ''')
+ >>> c.insert(-1, stim.Circuit("S 999\nCX 0 1\nCZ 2 3"))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ S 999
+ CX 0 1
+ CZ 2 3
+ X 2
+ ''')
+ """
def inverse(
self,
) -> stim.Circuit:
@@ -1885,6 +1936,14 @@ class Circuit:
Returns:
reference_sample: reference sample sampled from the given circuit.
+
+ Examples:
+ >>> import stim
+ >>> stim.Circuit('''
+ ... X 1
+ ... M 0 1
+ ... ''').reference_sample()
+ array([False, True])
"""
def search_for_undetectable_logical_errors(
self,
@@ -10851,15 +10910,18 @@ class TableauSimulator:
>>> import numpy as np
>>> s = stim.TableauSimulator()
>>> s.x(2)
- >>> list(s.state_vector(endian='little'))
- [0j, 0j, 0j, 0j, (1+0j), 0j, 0j, 0j]
+ >>> s.state_vector(endian='little')
+ array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
- >>> list(s.state_vector(endian='big'))
- [0j, (1+0j), 0j, 0j, 0j, 0j, 0j, 0j]
+ >>> s.state_vector(endian='big')
+ array([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
>>> s.sqrt_x(1, 2)
- >>> list(s.state_vector())
- [(0.5+0j), 0j, -0.5j, 0j, 0.5j, 0j, (0.5+0j), 0j]
+ >>> s.state_vector()
+ array([0.5+0.j , 0. +0.j , 0. -0.5j, 0. +0.j , 0. +0.5j, 0. +0.j ,
+ 0.5+0.j , 0. +0.j ], dtype=complex64)
"""
def swap(
self,
diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi
index 85b5e0b10..8d603dd77 100644
--- a/glue/python/src/stim/__init__.pyi
+++ b/glue/python/src/stim/__init__.pyi
@@ -1600,6 +1600,57 @@ class Circuit:
True, there is technically still a 2^-256 chance the circuit doesn't have
the flow. This is lower than the chance of a cosmic ray flipping the result.
"""
+ def insert(
+ self,
+ index: int,
+ operation: Union[stim.CircuitInstruction, stim.Circuit],
+ ) -> None:
+ """Inserts an operation at the given index, pushing existing operations forward.
+
+ Note that, unlike when appending operations or parsing stim circuit files,
+ inserted operations aren't automatically fused into the preceding operation.
+ This is to avoid creating complicated situations where it's difficult to reason
+ about how the indices of operations change in response to insertions.
+
+ Args:
+ index: The index to insert at.
+
+ Must satisfy -len(circuit) <= index < len(circuit). Negative indices
+ are made non-negative by adding len(circuit) to them, so they refer to
+ indices relative to the end of the circuit instead of the start.
+
+ Instructions before the index are not shifted. Instructions that
+ were at or after the index are shifted forwards.
+ operation: The object to insert. This can be a single
+ stim.CircuitInstruction or an entire stim.Circuit.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit('''
+ ... H 0
+ ... S 1
+ ... X 2
+ ... ''')
+ >>> c.insert(1, stim.CircuitInstruction("Y", [3, 4, 5]))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ X 2
+ ''')
+ >>> c.insert(-1, stim.Circuit("S 999\nCX 0 1\nCZ 2 3"))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ S 999
+ CX 0 1
+ CZ 2 3
+ X 2
+ ''')
+ """
def inverse(
self,
) -> stim.Circuit:
@@ -1885,6 +1936,14 @@ class Circuit:
Returns:
reference_sample: reference sample sampled from the given circuit.
+
+ Examples:
+ >>> import stim
+ >>> stim.Circuit('''
+ ... X 1
+ ... M 0 1
+ ... ''').reference_sample()
+ array([False, True])
"""
def search_for_undetectable_logical_errors(
self,
@@ -10851,15 +10910,18 @@ class TableauSimulator:
>>> import numpy as np
>>> s = stim.TableauSimulator()
>>> s.x(2)
- >>> list(s.state_vector(endian='little'))
- [0j, 0j, 0j, 0j, (1+0j), 0j, 0j, 0j]
+ >>> s.state_vector(endian='little')
+ array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
- >>> list(s.state_vector(endian='big'))
- [0j, (1+0j), 0j, 0j, 0j, 0j, 0j, 0j]
+ >>> s.state_vector(endian='big')
+ array([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
>>> s.sqrt_x(1, 2)
- >>> list(s.state_vector())
- [(0.5+0j), 0j, -0.5j, 0j, 0.5j, 0j, (0.5+0j), 0j]
+ >>> s.state_vector()
+ array([0.5+0.j , 0. +0.j , 0. -0.5j, 0. +0.j , 0. +0.5j, 0. +0.j ,
+ 0.5+0.j , 0. +0.j ], dtype=complex64)
"""
def swap(
self,
diff --git a/glue/sample/src/sinter/_probability_util.py b/glue/sample/src/sinter/_probability_util.py
index 8e6ae807b..62cf0ed71 100644
--- a/glue/sample/src/sinter/_probability_util.py
+++ b/glue/sample/src/sinter/_probability_util.py
@@ -7,7 +7,16 @@
if TYPE_CHECKING:
import sinter
- from scipy.stats._stats_mstats_common import LinregressResult
+
+ # Go on a magical journey looking for scipy's linear regression type.
+ try:
+ from scipy.stats._stats_py import LinregressResult
+ except ImportError:
+ try:
+ from scipy.stats._stats_mstats_common import LinregressResult
+ except ImportError:
+ from scipy.stats import linregress
+ LinregressResult = type(linregress([0, 1], [0, 1]))
def log_binomial(*, p: Union[float, np.ndarray], n: int, hits: int) -> np.ndarray:
@@ -64,8 +73,8 @@ def log_binomial(*, p: Union[float, np.ndarray], n: int, hits: int) -> np.ndarra
result[p_clipped == 1] = -np.inf
# Multiply p**hits and (1-p)**misses onto the total, in log space.
- result[p_clipped != 0] += np.log(p_clipped[p_clipped != 0]) * hits
- result[p_clipped != 1] += np.log1p(-p_clipped[p_clipped != 1]) * misses
+ result[p_clipped != 0] += np.log(p_clipped[p_clipped != 0]) * float(hits)
+ result[p_clipped != 1] += np.log1p(-p_clipped[p_clipped != 1]) * float(misses)
# Multiply (n choose hits) onto the total, in log space.
log_n_choose_hits = log_factorial(n) - log_factorial(misses) - log_factorial(hits)
@@ -150,7 +159,10 @@ def least_squares_cost(*, xs: np.ndarray, ys: np.ndarray, intercept: float, slop
def least_squares_through_point(*, xs: np.ndarray, ys: np.ndarray, required_x: float, required_y: float) -> 'LinregressResult':
# Local import to reduce initial cost of importing sinter.
from scipy.optimize import leastsq
- from scipy.stats._stats_mstats_common import LinregressResult
+ from scipy.stats import linregress
+
+ # HACK: get scipy's linear regression result type
+ LinregressResult = type(linregress([0, 1], [0, 1]))
xs2 = xs - required_x
ys2 = ys - required_y
@@ -169,7 +181,11 @@ def err(intercept: float) -> float:
# Local import to reduce initial cost of importing sinter.
from scipy.optimize import leastsq
- from scipy.stats._stats_mstats_common import LinregressResult
+
+ # HACK: get scipy's linear regression result type
+ from scipy.stats import linregress
+ LinregressResult = type(linregress([0, 1], [0, 1]))
+
(best_intercept,), _ = leastsq(func=err, x0=0.0)
return LinregressResult(required_slope, best_intercept, None, None, None, intercept_stderr=False)
diff --git a/src/stim/circuit/circuit.cc b/src/stim/circuit/circuit.cc
index af651fb49..615a20e38 100644
--- a/src/stim/circuit/circuit.cc
+++ b/src/stim/circuit/circuit.cc
@@ -97,7 +97,24 @@ Circuit &Circuit::operator=(Circuit &&circuit) noexcept {
}
bool Circuit::operator==(const Circuit &other) const {
- return operations == other.operations && blocks == other.blocks;
+ if (operations.size() != other.operations.size() || blocks.size() != other.blocks.size()) {
+ return false;
+ }
+ for (size_t k = 0; k < operations.size(); k++) {
+ if (operations[k].gate_type == GateType::REPEAT && other.operations[k].gate_type == GateType::REPEAT) {
+ if (operations[k].repeat_block_rep_count() != other.operations[k].repeat_block_rep_count()) {
+ return false;
+ }
+ const auto &b1 = operations[k].repeat_block_body(*this);
+ const auto &b2 = other.operations[k].repeat_block_body(other);
+ if (b1 != b2) {
+ return false;
+ }
+ } else if (operations[k] != other.operations[k]) {
+ return false;
+ }
+ }
+ return true;
}
bool Circuit::operator!=(const Circuit &other) const {
return !(*this == other);
@@ -107,12 +124,16 @@ bool Circuit::approx_equals(const Circuit &other, double atol) const {
return false;
}
for (size_t k = 0; k < operations.size(); k++) {
- if (!operations[k].approx_equals(other.operations[k], atol)) {
- return false;
- }
- }
- for (size_t k = 0; k < blocks.size(); k++) {
- if (!blocks[k].approx_equals(other.blocks[k], atol)) {
+ if (operations[k].gate_type == GateType::REPEAT && other.operations[k].gate_type == GateType::REPEAT) {
+ if (operations[k].repeat_block_rep_count() != other.operations[k].repeat_block_rep_count()) {
+ return false;
+ }
+ const auto &b1 = operations[k].repeat_block_body(*this);
+ const auto &b2 = other.operations[k].repeat_block_body(other);
+ if (!b1.approx_equals(b2, atol)) {
+ return false;
+ }
+ } else if (!operations[k].approx_equals(other.operations[k], atol)) {
return false;
}
}
@@ -322,6 +343,61 @@ void Circuit::safe_append(
}
}
+void Circuit::safe_insert(size_t index, const CircuitInstruction &instruction) {
+ if (index > operations.size()) {
+ throw std::invalid_argument("index > operations.size()");
+ }
+ auto flags = GATE_DATA[instruction.gate_type].flags;
+ if (flags & GATE_IS_BLOCK) {
+ throw std::invalid_argument("Can't insert a block like a normal operation.");
+ }
+ instruction.validate();
+
+ // Copy arg/target data into this circuit's buffers.
+ CircuitInstruction copy = instruction;
+ copy.args = arg_buf.take_copy(copy.args);
+ copy.targets = target_buf.take_copy(copy.targets);
+ operations.insert(operations.begin() + index, copy);
+}
+
+void Circuit::safe_insert(size_t index, const Circuit &circuit) {
+ if (index > operations.size()) {
+ throw std::invalid_argument("index > operations.size()");
+ }
+
+ operations.insert(operations.begin() + index, circuit.operations.begin(), circuit.operations.end());
+
+ // Copy backing data over into this circuit.
+ for (size_t k = index; k < index + circuit.operations.size(); k++) {
+ if (operations[k].gate_type == GateType::REPEAT) {
+ blocks.push_back(operations[k].repeat_block_body(circuit));
+ auto repeat_count = operations[k].repeat_block_rep_count();
+ target_buf.append_tail(GateTarget{(uint32_t)(blocks.size() - 1)});
+ target_buf.append_tail(GateTarget{(uint32_t)(repeat_count & 0xFFFFFFFFULL)});
+ target_buf.append_tail(GateTarget{(uint32_t)(repeat_count >> 32)});
+ operations[k].targets = target_buf.commit_tail();
+ } else {
+ operations[k].targets = target_buf.take_copy(operations[k].targets);
+ operations[k].args = arg_buf.take_copy(operations[k].args);
+ }
+ }
+}
+
+void Circuit::safe_insert_repeat_block(size_t index, uint64_t repeat_count, const Circuit &block) {
+ if (repeat_count == 0) {
+ throw std::invalid_argument("Can't repeat 0 times.");
+ }
+ if (index > operations.size()) {
+ throw std::invalid_argument("index > operations.size()");
+ }
+ target_buf.append_tail(GateTarget{(uint32_t)blocks.size()});
+ target_buf.append_tail(GateTarget{(uint32_t)(repeat_count & 0xFFFFFFFFULL)});
+ target_buf.append_tail(GateTarget{(uint32_t)(repeat_count >> 32)});
+ blocks.push_back(block);
+ auto targets = target_buf.commit_tail();
+ operations.insert(operations.begin() + index, CircuitInstruction{GateType::REPEAT, {}, targets});
+}
+
void Circuit::safe_append_reversed_targets(
GateType gate, SpanRef targets, SpanRef args, bool reverse_in_pairs) {
if (reverse_in_pairs) {
diff --git a/src/stim/circuit/circuit.h b/src/stim/circuit/circuit.h
index e29ec888a..e8670e83e 100644
--- a/src/stim/circuit/circuit.h
+++ b/src/stim/circuit/circuit.h
@@ -119,6 +119,10 @@ struct Circuit {
/// Safely moves a repeat block to the end of the circuit.
void append_repeat_block(uint64_t repeat_count, Circuit &&body);
+ void safe_insert(size_t index, const CircuitInstruction &instruction);
+ void safe_insert_repeat_block(size_t index, uint64_t repeat_count, const Circuit &block);
+ void safe_insert(size_t index, const Circuit &circuit);
+
/// Appends the given gate, but with targets reversed.
void safe_append_reversed_targets(
GateType gate, SpanRef targets, SpanRef args, bool reverse_in_pairs);
diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc
index 4b9896192..608b60bff 100644
--- a/src/stim/circuit/circuit.pybind.cc
+++ b/src/stim/circuit/circuit.pybind.cc
@@ -178,6 +178,39 @@ std::string py_likeliest_error_sat_problem(const Circuit &self, int quantization
return stim::likeliest_error_sat_problem(dem, quantization, format);
}
+void circuit_insert(
+ Circuit &self,
+ pybind11::ssize_t &index,
+ pybind11::object &operation) {
+
+ if (index < 0) {
+ index += self.operations.size();
+ }
+ if (index < 0 || (uint64_t)index > self.operations.size()) {
+ std::stringstream ss;
+ ss << "Index is out of range. Need -len(circuit) <= index <= len(circuit).";
+ ss << "\n index=" << index;
+ ss << "\n len(circuit)=" << self.operations.size();
+ throw std::invalid_argument(ss.str());
+ }
+ if (pybind11::isinstance(operation)) {
+ const PyCircuitInstruction &v = pybind11::cast(operation);
+ self.safe_insert(index, v.as_operation_ref());
+ } else if (pybind11::isinstance(operation)) {
+ const CircuitRepeatBlock &v = pybind11::cast(operation);
+ self.safe_insert_repeat_block(index, v.repeat_count, v.body);
+ } else if (pybind11::isinstance(operation)) {
+ const Circuit &v = pybind11::cast(operation);
+ self.safe_insert(index, v);
+ } else {
+ std::stringstream ss;
+ ss << "Don't know how to insert an object of type ";
+ ss << pybind11::str(pybind11::module_::import("builtins").attr("type")(operation));
+ ss << "\nExpected a stim.CircuitInstruction, stim.CircuitRepeatBlock, or stim.Circuit.";
+ throw std::invalid_argument(ss.str());
+ }
+}
+
void circuit_append(
Circuit &self,
const pybind11::object &obj,
@@ -687,6 +720,14 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> import stim
+ >>> stim.Circuit('''
+ ... X 1
+ ... M 0 1
+ ... ''').reference_sample()
+ array([False, True])
)DOC")
.data());
@@ -1084,6 +1125,61 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ None:
+
+ Note that, unlike when appending operations or parsing stim circuit files,
+ inserted operations aren't automatically fused into the preceding operation.
+ This is to avoid creating complicated situations where it's difficult to reason
+ about how the indices of operations change in response to insertions.
+
+ Args:
+ index: The index to insert at.
+
+ Must satisfy -len(circuit) <= index < len(circuit). Negative indices
+ are made non-negative by adding len(circuit) to them, so they refer to
+ indices relative to the end of the circuit instead of the start.
+
+ Instructions before the index are not shifted. Instructions that
+ were at or after the index are shifted forwards.
+ operation: The object to insert. This can be a single
+ stim.CircuitInstruction or an entire stim.Circuit.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit('''
+ ... H 0
+ ... S 1
+ ... X 2
+ ... ''')
+ >>> c.insert(1, stim.CircuitInstruction("Y", [3, 4, 5]))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ X 2
+ ''')
+ >>> c.insert(-1, stim.Circuit("S 999\nCX 0 1\nCZ 2 3"))
+ >>> c
+ stim.Circuit('''
+ H 0
+ Y 3 4 5
+ S 1
+ S 999
+ CX 0 1
+ CZ 2 3
+ X 2
+ ''')
+ )DOC")
+ .data());
+
c.def(
"append_from_stim_program_text",
[](Circuit &self, const char *text) {
diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py
index 1cd4da5a2..148853ca1 100644
--- a/src/stim/circuit/circuit_pybind_test.py
+++ b/src/stim/circuit/circuit_pybind_test.py
@@ -1814,3 +1814,68 @@ def test_detecting_regions_mzz():
1: stim.PauliString("__Z"),
},
}
+
+
+def test_insert():
+ c = stim.Circuit()
+ with pytest.raises(ValueError, match='type'):
+ c.insert(0, object())
+ with pytest.raises(ValueError, match='index <'):
+ c.insert(1, stim.CircuitInstruction("H", [1]))
+ with pytest.raises(ValueError, match='index <'):
+ c.insert(-1, stim.CircuitInstruction("H", [1]))
+ c.insert(0, stim.CircuitInstruction("H", [1]))
+ assert c == stim.Circuit("""
+ H 1
+ """)
+
+ with pytest.raises(ValueError, match='index <'):
+ c.insert(2, stim.CircuitInstruction("S", [2]))
+ with pytest.raises(ValueError, match='index <'):
+ c.insert(-2, stim.CircuitInstruction("S", [2]))
+ c.insert(0, stim.CircuitInstruction("S", [2, 3]))
+ assert c == stim.Circuit("""
+ S 2 3
+ H 1
+ """)
+
+ c.insert(-1, stim.Circuit("H 5\nM 2"))
+ assert c == stim.Circuit("""
+ S 2 3
+ H 5
+ M 2
+ H 1
+ """)
+
+ c.insert(2, stim.Circuit("""
+ REPEAT 100 {
+ M 3
+ }
+ """))
+ assert c == stim.Circuit("""
+ S 2 3
+ H 5
+ REPEAT 100 {
+ M 3
+ }
+ M 2
+ H 1
+ """)
+
+ c.insert(2, stim.Circuit("""
+ REPEAT 100 {
+ M 3
+ }
+ """)[0])
+ assert c == stim.Circuit("""
+ S 2 3
+ H 5
+ REPEAT 100 {
+ M 3
+ }
+ REPEAT 100 {
+ M 3
+ }
+ M 2
+ H 1
+ """)
diff --git a/src/stim/gates/gate_data_pauli_product.cc b/src/stim/gates/gate_data_pauli_product.cc
index 498f9b68b..30505bf20 100644
--- a/src/stim/gates/gate_data_pauli_product.cc
+++ b/src/stim/gates/gate_data_pauli_product.cc
@@ -177,7 +177,7 @@ CX 2 1
.flags = (GateFlags)(GATE_TARGETS_PAULI_STRING | GATE_TARGETS_COMBINERS),
.category = "P_Generalized Pauli Product Gates",
.help = R"MARKDOWN(
-The generalized S gate. Phases the -1 eigenspace of Pauli product observables by i.
+The generalized S_DAG gate. Phases the -1 eigenspace of Pauli product observables by -i.
Parens Arguments:
diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc
index 84d019bca..61061d7c1 100644
--- a/src/stim/simulators/tableau_simulator.pybind.cc
+++ b/src/stim/simulators/tableau_simulator.pybind.cc
@@ -319,15 +319,18 @@ void stim_pybind::pybind_tableau_simulator_methods(
>>> import numpy as np
>>> s = stim.TableauSimulator()
>>> s.x(2)
- >>> list(s.state_vector(endian='little'))
- [0j, 0j, 0j, 0j, (1+0j), 0j, 0j, 0j]
+ >>> s.state_vector(endian='little')
+ array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
- >>> list(s.state_vector(endian='big'))
- [0j, (1+0j), 0j, 0j, 0j, 0j, 0j, 0j]
+ >>> s.state_vector(endian='big')
+ array([0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+ dtype=complex64)
>>> s.sqrt_x(1, 2)
- >>> list(s.state_vector())
- [(0.5+0j), 0j, -0.5j, 0j, 0.5j, 0j, (0.5+0j), 0j]
+ >>> s.state_vector()
+ array([0.5+0.j , 0. +0.j , 0. -0.5j, 0. +0.j , 0. +0.5j, 0. +0.j ,
+ 0.5+0.j , 0. +0.j ], dtype=complex64)
)DOC")
.data());