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());