diff --git a/doc/file_format_stim_circuit.md b/doc/file_format_stim_circuit.md index d21fe9f0..c37bddd0 100644 --- a/doc/file_format_stim_circuit.md +++ b/doc/file_format_stim_circuit.md @@ -34,6 +34,17 @@ Each line is either blank, an instruction, a block initiator, or a block termina Also, each line may be indented with spacing characters and may end with a comment indicated by a hash (`#`). Comments and indentation are purely decorative; they carry no semantic significance. +Here is a formal definition of the above paragraph. +Entries like `/this/` are regular expressions. +Entries like `` are named expressions. +Entries like `'this'` are literal string expressions. +The `::=` operator means "defined as". +The `|` binary operator means "or". +The `?` suffix operator means "zero-or-one". +The `*` suffix operator means "zero-or-many". +Parens are used to group expressions. +Adjacent expressions are combined by concatenation. + ``` ::= * ::= ( | | )? ? '\n' @@ -42,13 +53,15 @@ Comments and indentation are purely decorative; they carry no semantic significa ``` An *instruction* is composed of a name, +then (introduced in stim v1.15) an optional tag inside square brackets, then an optional comma-separated list of arguments inside of parentheses, then a list of space-separated targets. For example, the line `X_ERROR(0.1) 5 6` is an instruction with a name (`X_ERROR`), one argument (`0.1`), and two targets (`5` and `6`). ``` - ::= ? + ::= ? ? + ::= '[' /[^\r\]\n]/* ']' ::= '(' ')' ::= /[ \t]*/ /[ \t]*/ (',' )? ::= /[ \t]+/ ? @@ -57,6 +70,14 @@ one argument (`0.1`), and two targets (`5` and `6`). An instruction *name* starts with a letter and then contains a series of letters, digits, and underscores. Names are case-insensitive. +An instruction *tag* is an arbitrary string enclosed by square brackets. +Certain characters cannot appear directly in the tag, and must instead be included using escape sequences. +The closing square bracket character `]` cannot appear directly, and is instead encoded using the escape sequence `\C`. +The carriage return character cannot appear directly, and is instead encoded using the escape sequence `\r`. +The line feed character cannot appear directly, and is instead encoded using the escape sequence `\n`. +The backslash character `\` cannot appear directly, and is instead encoded using the escape sequence `\B`. +(This backslash escape sequence differs from the common escape sequence `\\` because that sequence causes exponential explosions when escaping multiple times.) + An *argument* is a double precision floating point number. A *target* can either be a qubit target (a non-negative integer), @@ -124,6 +145,17 @@ Currently, control flow is limited to *repetition*. A circuit can contain `REPEAT K { ... }` blocks, which indicate that the block's instructions should be iterated over `K` times instead of just once. +### Tags + +Instruction tags have no effect on the function of a circuit. +In general, tools should attempt to propagate tags through circuit transformations and otherwise ignore them. +The intent is that users and tools can use tags to specify custom behavior that stim is not aware of. +For example, consider the tagged instruction `TICK[100ns]`. +In most situations, the `100ns` tag does nothing. +But if you are using a tool that adds noise to circuits, and it's programmed to look at tags to get hints about what +noise to add, then the `100ns` tag could be a message to that tool (specifying a duration, which the tool could use when +computing the probability argument of an inserted `DEPOLARIZE1` instruction). + ### Target Types There are four types of targets that can be given to instructions: diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 106ac282..d33e4840 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -83,6 +83,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.CircuitInstruction.gate_args_copy`](#stim.CircuitInstruction.gate_args_copy) - [`stim.CircuitInstruction.name`](#stim.CircuitInstruction.name) - [`stim.CircuitInstruction.num_measurements`](#stim.CircuitInstruction.num_measurements) + - [`stim.CircuitInstruction.tag`](#stim.CircuitInstruction.tag) - [`stim.CircuitInstruction.target_groups`](#stim.CircuitInstruction.target_groups) - [`stim.CircuitInstruction.targets_copy`](#stim.CircuitInstruction.targets_copy) - [`stim.CircuitRepeatBlock`](#stim.CircuitRepeatBlock) @@ -94,6 +95,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.CircuitRepeatBlock.name`](#stim.CircuitRepeatBlock.name) - [`stim.CircuitRepeatBlock.num_measurements`](#stim.CircuitRepeatBlock.num_measurements) - [`stim.CircuitRepeatBlock.repeat_count`](#stim.CircuitRepeatBlock.repeat_count) + - [`stim.CircuitRepeatBlock.tag`](#stim.CircuitRepeatBlock.tag) - [`stim.CircuitTargetsInsideInstruction`](#stim.CircuitTargetsInsideInstruction) - [`stim.CircuitTargetsInsideInstruction.__init__`](#stim.CircuitTargetsInsideInstruction.__init__) - [`stim.CircuitTargetsInsideInstruction.args`](#stim.CircuitTargetsInsideInstruction.args) @@ -4017,6 +4019,8 @@ def __init__( name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None, + *, + tag: str = "", ) -> None: """Creates or parses a `stim.CircuitInstruction`. @@ -4030,6 +4034,11 @@ def __init__( gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + tag: Defaults to "". A custom string attached to the instruction. For + example, for a TICK instruction, this could a string specifying an + amount of time which is used by custom code for adding noise to a + circuit. In general, stim will attempt to propagate tags across circuit + transformations but will otherwise completely ignore them. Examples: >>> import stim @@ -4039,6 +4048,9 @@ def __init__( >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) + + >>> print(stim.CircuitInstruction('I', [2], tag='100ns')) + I[100ns] 2 """ ``` @@ -4147,6 +4159,29 @@ def num_measurements( """ ``` + +```python +# stim.CircuitInstruction.tag + +# (in class stim.CircuitInstruction) +@property +def tag( + self, +) -> str: + """The custom tag attached to the instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + >>> stim.Circuit("H[test] 0")[0].tag + 'test' + >>> stim.Circuit("H 0")[0].tag + '' + """ +``` + ```python # stim.CircuitInstruction.target_groups @@ -4266,12 +4301,15 @@ def __init__( self, repeat_count: int, body: stim.Circuit, + *, + tag: str = '', ) -> None: """Initializes a `stim.CircuitRepeatBlock`. Args: repeat_count: The number of times to repeat the block. body: The body of the block, as a circuit. + tag: Defaults to empty. A custom string attached to the REPEAT instruction. """ ``` @@ -4407,6 +4445,39 @@ def repeat_count( """ ``` + +```python +# stim.CircuitRepeatBlock.tag + +# (in class stim.CircuitRepeatBlock) +@property +def tag( + self, +) -> str: + """The custom tag attached to the REPEAT instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + + >>> stim.Circuit(''' + ... REPEAT[test] 5 { + ... H 0 + ... } + ... ''')[0].tag + 'test' + + >>> stim.Circuit(''' + ... REPEAT 5 { + ... H 0 + ... } + ... ''')[0].tag + '' + """ +``` + ```python # stim.CircuitTargetsInsideInstruction @@ -6149,6 +6220,18 @@ def __init__( coords: List[float], ) -> None: """Creates a stim.DemTargetWithCoords. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0] + stim.DemTargetWithCoords(dem_target=stim.DemTarget('D0'), coords=[2, 3]) """ ``` @@ -6164,6 +6247,18 @@ def coords( """Returns the associated coordinate information as a list of floats. If there is no coordinate information, returns an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].coords + [2.0, 3.0] """ ``` @@ -6177,6 +6272,18 @@ def dem_target( self, ) -> stim.DemTarget: """Returns the actual DEM target as a `stim.DemTarget`. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].dem_target + stim.DemTarget('D0') """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index c6279d9e..b9e1c185 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -3118,6 +3118,8 @@ class CircuitInstruction: name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None, + *, + tag: str = "", ) -> None: """Creates or parses a `stim.CircuitInstruction`. @@ -3131,6 +3133,11 @@ class CircuitInstruction: gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + tag: Defaults to "". A custom string attached to the instruction. For + example, for a TICK instruction, this could a string specifying an + amount of time which is used by custom code for adding noise to a + circuit. In general, stim will attempt to propagate tags across circuit + transformations but will otherwise completely ignore them. Examples: >>> import stim @@ -3140,6 +3147,9 @@ class CircuitInstruction: >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) + + >>> print(stim.CircuitInstruction('I', [2], tag='100ns')) + I[100ns] 2 """ def __ne__( self, @@ -3204,6 +3214,22 @@ class CircuitInstruction: >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 """ + @property + def tag( + self, + ) -> str: + """The custom tag attached to the instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + >>> stim.Circuit("H[test] 0")[0].tag + 'test' + >>> stim.Circuit("H 0")[0].tag + '' + """ def target_groups( self, ) -> List[List[stim.GateTarget]]: @@ -3290,12 +3316,15 @@ class CircuitRepeatBlock: self, repeat_count: int, body: stim.Circuit, + *, + tag: str = '', ) -> None: """Initializes a `stim.CircuitRepeatBlock`. Args: repeat_count: The number of times to repeat the block. body: The body of the block, as a circuit. + tag: Defaults to empty. A custom string attached to the REPEAT instruction. """ def __ne__( self, @@ -3387,6 +3416,32 @@ class CircuitRepeatBlock: >>> repeat_block.repeat_count 5 """ + @property + def tag( + self, + ) -> str: + """The custom tag attached to the REPEAT instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + + >>> stim.Circuit(''' + ... REPEAT[test] 5 { + ... H 0 + ... } + ... ''')[0].tag + 'test' + + >>> stim.Circuit(''' + ... REPEAT 5 { + ... H 0 + ... } + ... ''')[0].tag + '' + """ class CircuitTargetsInsideInstruction: """Describes a range of targets within a circuit instruction. """ @@ -4781,6 +4836,18 @@ class DemTargetWithCoords: coords: List[float], ) -> None: """Creates a stim.DemTargetWithCoords. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0] + stim.DemTargetWithCoords(dem_target=stim.DemTarget('D0'), coords=[2, 3]) """ @property def coords( @@ -4789,12 +4856,36 @@ class DemTargetWithCoords: """Returns the associated coordinate information as a list of floats. If there is no coordinate information, returns an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].coords + [2.0, 3.0] """ @property def dem_target( self, ) -> stim.DemTarget: """Returns the actual DEM target as a `stim.DemTarget`. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].dem_target + stim.DemTarget('D0') """ class DetectorErrorModel: """An error model built out of independent error mechanics. diff --git a/glue/javascript/tableau_simulator.js.cc b/glue/javascript/tableau_simulator.js.cc index 7fb6d8e1..986d1d60 100644 --- a/glue/javascript/tableau_simulator.js.cc +++ b/glue/javascript/tableau_simulator.js.cc @@ -18,7 +18,7 @@ struct JsCircuitInstruction { } } operator CircuitInstruction() const { - return {gate_type, {}, targets}; + return {gate_type, {}, targets, ""}; } }; diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index c6279d9e..b9e1c185 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -3118,6 +3118,8 @@ class CircuitInstruction: name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None, + *, + tag: str = "", ) -> None: """Creates or parses a `stim.CircuitInstruction`. @@ -3131,6 +3133,11 @@ class CircuitInstruction: gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + tag: Defaults to "". A custom string attached to the instruction. For + example, for a TICK instruction, this could a string specifying an + amount of time which is used by custom code for adding noise to a + circuit. In general, stim will attempt to propagate tags across circuit + transformations but will otherwise completely ignore them. Examples: >>> import stim @@ -3140,6 +3147,9 @@ class CircuitInstruction: >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) + + >>> print(stim.CircuitInstruction('I', [2], tag='100ns')) + I[100ns] 2 """ def __ne__( self, @@ -3204,6 +3214,22 @@ class CircuitInstruction: >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 """ + @property + def tag( + self, + ) -> str: + """The custom tag attached to the instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + >>> stim.Circuit("H[test] 0")[0].tag + 'test' + >>> stim.Circuit("H 0")[0].tag + '' + """ def target_groups( self, ) -> List[List[stim.GateTarget]]: @@ -3290,12 +3316,15 @@ class CircuitRepeatBlock: self, repeat_count: int, body: stim.Circuit, + *, + tag: str = '', ) -> None: """Initializes a `stim.CircuitRepeatBlock`. Args: repeat_count: The number of times to repeat the block. body: The body of the block, as a circuit. + tag: Defaults to empty. A custom string attached to the REPEAT instruction. """ def __ne__( self, @@ -3387,6 +3416,32 @@ class CircuitRepeatBlock: >>> repeat_block.repeat_count 5 """ + @property + def tag( + self, + ) -> str: + """The custom tag attached to the REPEAT instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + + >>> stim.Circuit(''' + ... REPEAT[test] 5 { + ... H 0 + ... } + ... ''')[0].tag + 'test' + + >>> stim.Circuit(''' + ... REPEAT 5 { + ... H 0 + ... } + ... ''')[0].tag + '' + """ class CircuitTargetsInsideInstruction: """Describes a range of targets within a circuit instruction. """ @@ -4781,6 +4836,18 @@ class DemTargetWithCoords: coords: List[float], ) -> None: """Creates a stim.DemTargetWithCoords. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0] + stim.DemTargetWithCoords(dem_target=stim.DemTarget('D0'), coords=[2, 3]) """ @property def coords( @@ -4789,12 +4856,36 @@ class DemTargetWithCoords: """Returns the associated coordinate information as a list of floats. If there is no coordinate information, returns an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].coords + [2.0, 3.0] """ @property def dem_target( self, ) -> stim.DemTarget: """Returns the actual DEM target as a `stim.DemTarget`. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].dem_target + stim.DemTarget('D0') """ class DetectorErrorModel: """An error model built out of independent error mechanics. diff --git a/src/stim/circuit/circuit.cc b/src/stim/circuit/circuit.cc index 86b762f7..979aaa78 100644 --- a/src/stim/circuit/circuit.cc +++ b/src/stim/circuit/circuit.cc @@ -44,26 +44,27 @@ void fuse_data(SpanRef &dst, SpanRef src, Mo dst.ptr_end = src.ptr_end; } -Circuit::Circuit() : target_buf(), operations(), blocks() { +Circuit::Circuit() : target_buf(), arg_buf(), tag_buf(), operations(), blocks() { } Circuit::Circuit(const Circuit &circuit) : target_buf(circuit.target_buf.total_allocated()), arg_buf(circuit.arg_buf.total_allocated()), + tag_buf(circuit.tag_buf.total_allocated()), operations(circuit.operations), blocks(circuit.blocks) { // Keep local copy of operation data. for (auto &op : operations) { op.targets = target_buf.take_copy(op.targets); - } - for (auto &op : operations) { op.args = arg_buf.take_copy(op.args); + op.tag = tag_buf.take_copy(op.tag); } } Circuit::Circuit(Circuit &&circuit) noexcept : target_buf(std::move(circuit.target_buf)), arg_buf(std::move(circuit.arg_buf)), + tag_buf(std::move(circuit.tag_buf)), operations(std::move(circuit.operations)), blocks(std::move(circuit.blocks)) { } @@ -75,12 +76,12 @@ Circuit &Circuit::operator=(const Circuit &circuit) { // Keep local copy of operation data. target_buf = MonotonicBuffer(circuit.target_buf.total_allocated()); - for (auto &op : operations) { - op.targets = target_buf.take_copy(op.targets); - } arg_buf = MonotonicBuffer(circuit.arg_buf.total_allocated()); + tag_buf = MonotonicBuffer(circuit.tag_buf.total_allocated()); for (auto &op : operations) { + op.targets = target_buf.take_copy(op.targets); op.args = arg_buf.take_copy(op.args); + op.tag = tag_buf.take_copy(op.tag); } } return *this; @@ -92,6 +93,7 @@ Circuit &Circuit::operator=(Circuit &&circuit) noexcept { blocks = std::move(circuit.blocks); target_buf = std::move(circuit.target_buf); arg_buf = std::move(circuit.arg_buf); + tag_buf = std::move(circuit.tag_buf); } return *this; } @@ -201,7 +203,13 @@ template void circuit_read_single_operation(Circuit &circuit, char lead_char, SOURCE read_char) { int c = (int)lead_char; const auto &gate = read_gate_name(c, read_char); + std::string_view tail_tag; try { + read_tag(c, gate.name, read_char, circuit.tag_buf); + if (!circuit.tag_buf.tail.empty()) { + tail_tag = std::string_view(circuit.tag_buf.tail.ptr_start, circuit.tag_buf.tail.size()); + } + read_parens_arguments(c, gate.name, read_char, circuit.arg_buf); if (gate.flags & GATE_IS_BLOCK) { read_result_targets64_into(c, read_char, circuit); @@ -213,7 +221,7 @@ void circuit_read_single_operation(Circuit &circuit, char lead_char, SOURCE read if (c == '{') { throw std::invalid_argument("Unexpected '{'."); } - CircuitInstruction{gate.id, circuit.arg_buf.tail, circuit.target_buf.tail}.validate(); + CircuitInstruction(gate.id, circuit.arg_buf.tail, circuit.target_buf.tail, tail_tag).validate(); } } catch (const std::invalid_argument &ex) { circuit.target_buf.discard_tail(); @@ -221,7 +229,8 @@ void circuit_read_single_operation(Circuit &circuit, char lead_char, SOURCE read throw ex; } - circuit.operations.push_back({gate.id, circuit.arg_buf.commit_tail(), circuit.target_buf.commit_tail()}); + circuit.tag_buf.commit_tail(); + circuit.operations.push_back(CircuitInstruction(gate.id, circuit.arg_buf.commit_tail(), circuit.target_buf.commit_tail(), tail_tag)); } void Circuit::try_fuse_last_two_ops() { @@ -299,8 +308,26 @@ void Circuit::append_from_text(std::string_view text) { READ_CONDITION::READ_UNTIL_END_OF_FILE); } -void Circuit::safe_append(const CircuitInstruction &operation, bool block_fusion) { - safe_append(operation.gate_type, operation.targets, operation.args, block_fusion); +void Circuit::safe_append(CircuitInstruction operation, bool block_fusion) { + auto flags = GATE_DATA[operation.gate_type].flags; + if (flags & GATE_IS_BLOCK) { + throw std::invalid_argument("Can't append a block like a normal operation."); + } + + operation.validate(); + + // Ensure arg/target data is backed by coping it into this circuit's buffers. + operation.args = arg_buf.take_copy(operation.args); + operation.targets = target_buf.take_copy(operation.targets); + operation.tag = tag_buf.take_copy(operation.tag); + + if (!block_fusion && !operations.empty() && operations.back().can_fuse(operation)) { + // Extend targets of last gate. + fuse_data(operations.back().targets, operation.targets, target_buf); + } else { + // Add a fresh new operation with its own target data. + operations.push_back(operation); + } } void Circuit::safe_append_ua(std::string_view gate_name, const std::vector &targets, double singleton_arg) { @@ -312,7 +339,7 @@ void Circuit::safe_append_ua(std::string_view gate_name, const std::vector targets, SpanRef args, bool block_fusion) { - auto flags = GATE_DATA[gate_type].flags; - if (flags & GATE_IS_BLOCK) { - throw std::invalid_argument("Can't append a block like a normal operation."); - } - - CircuitInstruction to_add = {gate_type, args, targets}; - to_add.validate(); - - // Ensure arg/target data is backed by coping it into this circuit's buffers. - to_add.args = arg_buf.take_copy(to_add.args); - to_add.targets = target_buf.take_copy(to_add.targets); - - if (!block_fusion && !operations.empty() && operations.back().can_fuse(to_add)) { - // Extend targets of last gate. - fuse_data(operations.back().targets, to_add.targets, target_buf); - } else { - // Add a fresh new operation with its own target data. - operations.push_back(to_add); - } + safe_append(CircuitInstruction(gate.id, args, converted, "")); } void Circuit::safe_insert(size_t index, const CircuitInstruction &instruction) { @@ -365,6 +369,7 @@ void Circuit::safe_insert(size_t index, const CircuitInstruction &instruction) { CircuitInstruction copy = instruction; copy.args = arg_buf.take_copy(copy.args); copy.targets = target_buf.take_copy(copy.targets); + copy.tag = tag_buf.take_copy(copy.tag); operations.insert(operations.begin() + index, copy); // Fuse at boundaries. @@ -393,6 +398,7 @@ void Circuit::safe_insert(size_t index, const Circuit &circuit) { } else { operations[k].targets = target_buf.take_copy(operations[k].targets); operations[k].args = arg_buf.take_copy(operations[k].args); + operations[k].tag = tag_buf.take_copy(operations[k].tag); } } @@ -405,7 +411,7 @@ void Circuit::safe_insert(size_t index, const Circuit &circuit) { } } -void Circuit::safe_insert_repeat_block(size_t index, uint64_t repeat_count, const Circuit &block) { +void Circuit::safe_insert_repeat_block(size_t index, uint64_t repeat_count, const Circuit &block, std::string_view tag) { if (repeat_count == 0) { throw std::invalid_argument("Can't repeat 0 times."); } @@ -417,27 +423,27 @@ void Circuit::safe_insert_repeat_block(size_t index, uint64_t repeat_count, cons 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}); + operations.insert(operations.begin() + index, CircuitInstruction(GateType::REPEAT, {}, targets, tag)); } void Circuit::safe_append_reversed_targets( - GateType gate, SpanRef targets, SpanRef args, bool reverse_in_pairs) { + CircuitInstruction instruction, bool reverse_in_pairs) { if (reverse_in_pairs) { - if (targets.size() % 2 != 0) { + if (instruction.targets.size() % 2 != 0) { throw std::invalid_argument("targets.size() % 2 != 0"); } - for (size_t k = targets.size(); k;) { + for (size_t k = instruction.targets.size(); k;) { k -= 2; - target_buf.append_tail(targets[k]); - target_buf.append_tail(targets[k + 1]); + target_buf.append_tail(instruction.targets[k]); + target_buf.append_tail(instruction.targets[k + 1]); } } else { - for (size_t k = targets.size(); k-- > 0;) { - target_buf.append_tail(targets[k]); + for (size_t k = instruction.targets.size(); k-- > 0;) { + target_buf.append_tail(instruction.targets[k]); } } - CircuitInstruction to_add = {gate, args, target_buf.tail}; + CircuitInstruction to_add = instruction; try { to_add.validate(); } catch (const std::invalid_argument &ex) { @@ -448,8 +454,9 @@ void Circuit::safe_append_reversed_targets( // Commit reversed tail data. to_add.targets = target_buf.commit_tail(); - // Ensure arg data is backed by copying it into this circuit's buffers. + // Ensure arg/tag data is backed by copying it into this circuit's buffers. to_add.args = arg_buf.take_copy(to_add.args); + to_add.tag = tag_buf.take_copy(to_add.tag); if (!operations.empty() && operations.back().can_fuse(to_add)) { // Extend targets of last gate. @@ -469,29 +476,6 @@ void Circuit::append_from_file(FILE *file, bool stop_asap) { stop_asap ? READ_CONDITION::READ_AS_LITTLE_AS_POSSIBLE : READ_CONDITION::READ_UNTIL_END_OF_FILE); } -std::ostream &stim::operator<<(std::ostream &out, const CircuitInstruction &instruction) { - out << GATE_DATA[instruction.gate_type].name; - if (!instruction.args.empty()) { - out << '('; - bool first = true; - for (auto e : instruction.args) { - if (first) { - first = false; - } else { - out << ", "; - } - if (e > (double)INT64_MIN && e < (double)INT64_MAX && (int64_t)e == e) { - out << (int64_t)e; - } else { - out << e; - } - } - out << ')'; - } - write_targets(out, instruction.targets); - return out; -} - void stim::print_circuit(std::ostream &out, const Circuit &c, size_t indentation) { bool first = true; for (const auto &op : c.operations) { @@ -507,7 +491,13 @@ void stim::print_circuit(std::ostream &out, const Circuit &c, size_t indentation for (size_t k = 0; k < indentation; k++) { out << ' '; } - out << "REPEAT " << op.repeat_block_rep_count() << " {\n"; + out << "REPEAT"; + if (!op.tag.empty()) { + out << '['; + write_tag_escaped_string_to(op.tag, out); + out << ']'; + } + out << " " << op.repeat_block_rep_count() << " {\n"; print_circuit(out, c.blocks[op.targets[0].data], indentation + 4); out << '\n'; for (size_t k = 0; k < indentation; k++) { @@ -557,12 +547,12 @@ Circuit Circuit::operator*(uint64_t repetitions) const { throw std::invalid_argument("Fused repetition count is too large."); } Circuit copy; - copy.append_repeat_block(new_reps, operations[0].repeat_block_body(*this)); + copy.append_repeat_block(new_reps, operations[0].repeat_block_body(*this), ""); return copy; } Circuit result; - result.append_repeat_block(repetitions, *this); + result.append_repeat_block(repetitions, *this, ""); return result; } @@ -615,7 +605,8 @@ Circuit &Circuit::operator+=(const Circuit &other) { target_data[0].data += block_offset; } SpanRef arg_data = arg_buf.take_copy(op.args); - operations.push_back({op.gate_type, arg_data, target_data}); + std::string_view tag_data = tag_buf.take_copy(op.tag); + operations.push_back(CircuitInstruction(op.gate_type, arg_data, target_data, tag_data)); } return *this; @@ -739,18 +730,20 @@ Circuit Circuit::py_get_slice(int64_t start, int64_t step, int64_t slice_length) result.target_buf.append_tail(op.targets[1]); result.target_buf.append_tail(op.targets[2]); auto targets = result.target_buf.commit_tail(); + auto tag = result.tag_buf.take_copy(op.tag); result.blocks.push_back(op.repeat_block_body(*this)); - result.operations.push_back({op.gate_type, {}, targets}); + result.operations.push_back(CircuitInstruction(op.gate_type, {}, targets, tag)); } else { auto args = result.arg_buf.take_copy(op.args); auto targets = result.target_buf.take_copy(op.targets); - result.operations.push_back({op.gate_type, args, targets}); + auto tag = result.tag_buf.take_copy(op.tag); + result.operations.push_back({op.gate_type, args, targets, tag}); } } return result; } -void Circuit::append_repeat_block(uint64_t repeat_count, Circuit &&body) { +void Circuit::append_repeat_block(uint64_t repeat_count, Circuit &&body, std::string_view tag) { if (repeat_count == 0) { throw std::invalid_argument("Can't repeat 0 times."); } @@ -759,10 +752,10 @@ void Circuit::append_repeat_block(uint64_t repeat_count, Circuit &&body) { target_buf.append_tail(GateTarget{(uint32_t)(repeat_count >> 32)}); blocks.push_back(std::move(body)); auto targets = target_buf.commit_tail(); - operations.push_back({GateType::REPEAT, {}, targets}); + operations.push_back(CircuitInstruction(GateType::REPEAT, {}, targets, tag_buf.take_copy(tag))); } -void Circuit::append_repeat_block(uint64_t repeat_count, const Circuit &body) { +void Circuit::append_repeat_block(uint64_t repeat_count, const Circuit &body, std::string_view tag) { if (repeat_count == 0) { throw std::invalid_argument("Can't repeat 0 times."); } @@ -771,7 +764,7 @@ void Circuit::append_repeat_block(uint64_t repeat_count, const Circuit &body) { target_buf.append_tail(GateTarget{(uint32_t)(repeat_count >> 32)}); blocks.push_back(body); auto targets = target_buf.commit_tail(); - operations.push_back({GateType::REPEAT, {}, targets}); + operations.push_back(CircuitInstruction(GateType::REPEAT, {}, targets, tag_buf.take_copy(tag))); } const Circuit Circuit::aliased_noiseless_circuit() const { @@ -786,11 +779,11 @@ const Circuit Circuit::aliased_noiseless_circuit() const { auto &tail = result.target_buf.tail; tail.ptr_end = tail.ptr_start + op.targets.size(); memset(tail.ptr_start, 0, (tail.ptr_end - tail.ptr_start) * sizeof(GateTarget)); - result.operations.push_back(CircuitInstruction{GateType::MPAD, {}, result.target_buf.commit_tail()}); + result.operations.push_back(CircuitInstruction(GateType::MPAD, {}, result.target_buf.commit_tail(), op.tag)); result.try_fuse_last_two_ops(); } else { // Drop result flip probability. - result.operations.push_back({op.gate_type, {}, op.targets}); + result.operations.push_back(CircuitInstruction(op.gate_type, {}, op.targets, op.tag)); } } else if (!(flags & GATE_IS_NOISY)) { // Keep noiseless operations. @@ -818,21 +811,25 @@ Circuit Circuit::without_noise() const { auto &tail = result.target_buf.tail; tail.ptr_end = tail.ptr_start + op.targets.size(); memset(tail.ptr_start, 0, (tail.ptr_end - tail.ptr_start) * sizeof(GateTarget)); - result.operations.push_back(CircuitInstruction{GateType::MPAD, {}, result.target_buf.commit_tail()}); + auto tag = result.tag_buf.take_copy(op.tag); + result.operations.push_back(CircuitInstruction(GateType::MPAD, {}, result.target_buf.commit_tail(), tag)); } else { // Drop result flip probabilities. auto targets = result.target_buf.take_copy(op.targets); - result.safe_append(op.gate_type, targets, {}); + auto tag = result.tag_buf.take_copy(op.tag); + result.safe_append(CircuitInstruction(op.gate_type, {}, targets, tag)); } } else if (op.gate_type == GateType::REPEAT) { auto args = result.arg_buf.take_copy(op.args); auto targets = result.target_buf.take_copy(op.targets); - result.operations.push_back({op.gate_type, args, targets}); + auto tag = result.tag_buf.take_copy(op.tag); + result.operations.push_back({op.gate_type, args, targets, tag}); } else if (!(flags & GATE_IS_NOISY)) { // Keep noiseless operations. auto args = result.arg_buf.take_copy(op.args); auto targets = result.target_buf.take_copy(op.targets); - result.safe_append(op.gate_type, targets, args); + auto tag = result.tag_buf.take_copy(op.tag); + result.safe_append(CircuitInstruction(op.gate_type, args, targets, tag)); } // Because some operations are rewritten into others, and some become fusable due to @@ -870,7 +867,7 @@ void flattened_helper( coord_buffer[k] += cur_coordinate_shift[k]; } } - out.safe_append(op.gate_type, op.targets, coord_buffer); + out.safe_append(CircuitInstruction(op.gate_type, coord_buffer, op.targets, op.tag)); } } } @@ -888,6 +885,7 @@ Circuit Circuit::inverse(bool allow_weak_inverse) const { result.operations.reserve(operations.size()); result.target_buf.ensure_available(target_buf.total_allocated()); result.arg_buf.ensure_available(arg_buf.total_allocated()); + result.tag_buf.ensure_available(tag_buf.total_allocated()); size_t skip_reversing = 0; std::vector args_buf; @@ -896,7 +894,7 @@ Circuit Circuit::inverse(bool allow_weak_inverse) const { if (op.gate_type == GateType::REPEAT) { const auto &block = op.repeat_block_body(*this); uint64_t reps = op.repeat_block_rep_count(); - result.append_repeat_block(reps, block.inverse(allow_weak_inverse)); + result.append_repeat_block(reps, block.inverse(allow_weak_inverse), op.tag); continue; } @@ -951,7 +949,8 @@ Circuit Circuit::inverse(bool allow_weak_inverse) const { // Add inverse operation to inverse circuit. result.safe_append_reversed_targets( - gate_data.best_candidate_inverse_id, op.targets, args, gate_data.flags & GATE_TARGETS_PAIRS); + CircuitInstruction(gate_data.best_candidate_inverse_id, args, op.targets, op.tag), + gate_data.flags & GATE_TARGETS_PAIRS); } // Put the qubit coordinates in the original order. diff --git a/src/stim/circuit/circuit.h b/src/stim/circuit/circuit.h index f8268dea..63955c0f 100644 --- a/src/stim/circuit/circuit.h +++ b/src/stim/circuit/circuit.h @@ -43,6 +43,7 @@ struct Circuit { /// Backing data stores for variable-sized target data referenced by operations. MonotonicBuffer target_buf; MonotonicBuffer arg_buf; + MonotonicBuffer tag_buf; /// Operations in the circuit, from earliest to latest. std::vector operations; std::vector blocks; @@ -105,27 +106,24 @@ struct Circuit { Circuit &operator*=(uint64_t repetitions); /// Safely adds an operation at the end of the circuit, copying its data into the circuit's jagged data as needed. - void safe_append(const CircuitInstruction &operation, bool block_fusion = false); + void safe_append(CircuitInstruction operation, bool block_fusion = false); /// Safely adds an operation at the end of the circuit, copying its data into the circuit's jagged data as needed. void safe_append_ua(std::string_view gate_name, const std::vector &targets, double singleton_arg); /// Safely adds an operation at the end of the circuit, copying its data into the circuit's jagged data as needed. void safe_append_u( std::string_view gate_name, const std::vector &targets, const std::vector &args = {}); - /// Safely adds an operation at the end of the circuit, copying its data into the circuit's jagged data as needed. - void safe_append( - GateType gate_type, SpanRef targets, SpanRef args, bool block_fusion = false); /// Safely copies a repeat block to the end of the circuit. - void append_repeat_block(uint64_t repeat_count, const Circuit &body); + void append_repeat_block(uint64_t repeat_count, const Circuit &body, std::string_view tag); /// Safely moves a repeat block to the end of the circuit. - void append_repeat_block(uint64_t repeat_count, Circuit &&body); + void append_repeat_block(uint64_t repeat_count, Circuit &&body, std::string_view tag); 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_repeat_block(size_t index, uint64_t repeat_count, const Circuit &block, std::string_view tag); 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); + CircuitInstruction instruction, bool reverse_in_pairs); /// Resets the circuit back to an empty circuit. void clear(); @@ -354,6 +352,60 @@ double read_normal_double(int &c, SOURCE read_char) { return result; } +template +void read_tag(int &c, std::string_view name, SOURCE read_char, MonotonicBuffer &out) { + if (c != '[') { + return; + } + c = read_char(); + + while (c != ']') { + if (c == '\r' || c == '\n') { + std::stringstream ss; + ss << "A tag wasn't closed with ']' before the end of the line.\n"; + ss << "Hit a "; + if (c == '\r') { + ss << "carriage return character (0x0D)"; + } else { + ss << "line feed character (0x0A)"; + } + ss << " while trying to parse the tag of a '" << name << "' instruction.\n"; + ss << "In tags, use the escape sequence '\\r' for carriage returns and '\\n' for line feeds."; + throw std::invalid_argument(ss.str()); + } else if (c == '\\') { + c = read_char(); + switch (c) { + case 'n': + out.append_tail('\n'); + break; + case 'r': + out.append_tail('\r'); + break; + case 'B': + out.append_tail('\\'); + break; + case 'C': + out.append_tail(']'); + break; + default: + std::stringstream ss; + ss << "Unrecognized escape sequence '\\" << c << "'."; + ss << "\nKnown escape sequences are:"; + ss << "\n \\n: 0x0A (line feed)"; + ss << "\n \\r: 0x0D (carriage return)"; + ss << "\n \\B: 0x5C (backslash '\\')"; + ss << "\n \\C: 0x5D (closing square bracket ']')"; + throw std::invalid_argument(ss.str()); + } + } else { + out.append_tail(c); + } + c = read_char(); + } + + c = read_char(); +} + template void read_parens_arguments(int &c, std::string_view name, SOURCE read_char, MonotonicBuffer &out) { if (c != '(') { @@ -380,7 +432,6 @@ void read_parens_arguments(int &c, std::string_view name, SOURCE read_char, Mono } std::ostream &operator<<(std::ostream &out, const Circuit &c); -std::ostream &operator<<(std::ostream &out, const CircuitInstruction &op); void print_circuit(std::ostream &out, const Circuit &c, size_t indentation); } // namespace stim diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index dea61d64..727219f0 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -187,7 +187,7 @@ pybind11::object circuit_get_item(const Circuit &self, const pybind11::object &i auto &op = self.operations[index]; if (op.gate_type == GateType::REPEAT) { - return pybind11::cast(CircuitRepeatBlock{op.repeat_block_rep_count(), op.repeat_block_body(self)}); + return pybind11::cast(CircuitRepeatBlock{op.repeat_block_rep_count(), op.repeat_block_body(self), pybind11::str(op.tag)}); } std::vector targets; for (const auto &e : op.targets) { @@ -197,7 +197,7 @@ pybind11::object circuit_get_item(const Circuit &self, const pybind11::object &i for (const auto &e : op.args) { args.push_back(e); } - return pybind11::cast(PyCircuitInstruction(op.gate_type, targets, args)); + return pybind11::cast(PyCircuitInstruction(op.gate_type, targets, args, op.tag)); } pybind11::object circuit_pop(Circuit &self, pybind11::ssize_t index) { @@ -230,7 +230,7 @@ void circuit_insert(Circuit &self, pybind11::ssize_t &index, pybind11::object &o 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); + self.safe_insert_repeat_block(index, v.repeat_count, v.body, pybind11::cast(v.tag)); } else if (pybind11::isinstance(operation)) { const Circuit &v = pybind11::cast(operation); self.safe_insert(index, v); @@ -292,14 +292,19 @@ void circuit_append( } const PyCircuitInstruction &instruction = pybind11::cast(obj); - self.safe_append(instruction.gate_type, instruction.targets, instruction.gate_args); + self.safe_append(CircuitInstruction{ + instruction.gate_type, + instruction.gate_args, + instruction.targets, + pybind11::cast(instruction.tag), + }); } else if (pybind11::isinstance(obj)) { if (!raw_targets.empty() || !arg.is_none()) { throw std::invalid_argument("Can't specify `targets` or `arg` when appending a stim.CircuitRepeatBlock."); } const CircuitRepeatBlock &block = pybind11::cast(obj); - self.append_repeat_block(block.repeat_count, block.body); + self.append_repeat_block(block.repeat_count, block.body, pybind11::cast(block.tag)); } else { throw std::invalid_argument( "First argument of append_operation must be a str (a gate name), " diff --git a/src/stim/circuit/circuit.test.cc b/src/stim/circuit/circuit.test.cc index a3592888..8f876d12 100644 --- a/src/stim/circuit/circuit.test.cc +++ b/src/stim/circuit/circuit.test.cc @@ -251,6 +251,69 @@ TEST(circuit, parse_spp_dag) { ((SpanRef)std::vector{GateTarget::x(1), GateTarget::z(2)})); } +TEST(circuit, parse_tag) { + Circuit c; + + c = Circuit("H[test] 3 5"); + ASSERT_EQ(c.operations.size(), 1); + ASSERT_EQ(c.operations[0].gate_type, GateType::H); + ASSERT_EQ(c.operations[0].tag, "test"); + ASSERT_EQ(c.operations[0].targets.size(), 2); + ASSERT_EQ(c.operations[0].targets[0], GateTarget::qubit(3)); + ASSERT_EQ(c.operations[0].targets[1], GateTarget::qubit(5)); + + c = Circuit("H[] 3 5"); + ASSERT_EQ(c.operations.size(), 1); + ASSERT_EQ(c.operations[0].gate_type, GateType::H); + ASSERT_EQ(c.operations[0].tag, ""); + ASSERT_EQ(c.operations[0].targets.size(), 2); + + c = Circuit("H 3 5"); + ASSERT_EQ(c.operations.size(), 1); + ASSERT_EQ(c.operations[0].gate_type, GateType::H); + ASSERT_EQ(c.operations[0].tag, ""); + ASSERT_EQ(c.operations[0].targets.size(), 2); + + c = Circuit("H[test \\B\\C\\r\\n] 3 5"); + ASSERT_EQ(c.operations.size(), 1); + ASSERT_EQ(c.operations[0].gate_type, GateType::H); + ASSERT_EQ(c.operations[0].tag, "test \\]\r\n"); + ASSERT_EQ(c.operations[0].targets.size(), 2); + ASSERT_EQ(c.operations[0].str(), "H[test \\B\\C\\r\\n] 3 5"); + + c = Circuit(R"CIRCUIT( + X_ERROR[test](0.125) + X_ERROR[no_fuse](0.125) 1 + X_ERROR(0.125) 2 + X_ERROR[](0.125) 3 + REPEAT[looper] 5 { + CX[within] 0 1 + } + )CIRCUIT"); + ASSERT_EQ(c.operations.size(), 4); + ASSERT_EQ(c.operations[0].gate_type, GateType::X_ERROR); + ASSERT_EQ(c.operations[1].gate_type, GateType::X_ERROR); + ASSERT_EQ(c.operations[2].gate_type, GateType::X_ERROR); + ASSERT_EQ(c.operations[0].tag, "test"); + ASSERT_EQ(c.operations[1].tag, "no_fuse"); + ASSERT_EQ(c.operations[2].tag, ""); + ASSERT_EQ(c.operations[0].targets.size(), 0); + ASSERT_EQ(c.operations[1].targets.size(), 1); + ASSERT_EQ(c.operations[2].targets.size(), 2); + ASSERT_EQ(c.operations[1].targets[0], GateTarget::qubit(1)); + ASSERT_EQ(c.operations[2].targets[0], GateTarget::qubit(2)); + ASSERT_EQ(c.operations[2].targets[1], GateTarget::qubit(3)); + ASSERT_EQ(c.operations[3].gate_type, GateType::REPEAT); + ASSERT_EQ(c.operations[3].tag, "looper"); + ASSERT_EQ(c.operations[3].repeat_block_rep_count(), 5); + ASSERT_EQ(c.str(), R"CIRCUIT(X_ERROR[test](0.125) +X_ERROR[no_fuse](0.125) 1 +X_ERROR(0.125) 2 3 +REPEAT[looper] 5 { + CX[within] 0 1 +})CIRCUIT"); +} + TEST(circuit, parse_sweep_bits) { ASSERT_THROW({ Circuit("H sweep[0]"); }, std::invalid_argument); ASSERT_THROW({ Circuit("X sweep[0]"); }, std::invalid_argument); @@ -1060,14 +1123,14 @@ TEST(circuit, append_repeat_block) { Circuit b("X 0"); Circuit a("Y 0"); - c.append_repeat_block(100, b); + c.append_repeat_block(100, b, ""); ASSERT_EQ(c, Circuit(R"CIRCUIT( REPEAT 100 { X 0 } )CIRCUIT")); - c.append_repeat_block(200, a); + c.append_repeat_block(200, a, ""); ASSERT_EQ(c, Circuit(R"CIRCUIT( REPEAT 100 { X 0 @@ -1077,7 +1140,7 @@ TEST(circuit, append_repeat_block) { } )CIRCUIT")); - c.append_repeat_block(400, std::move(b)); + c.append_repeat_block(400, std::move(b), ""); ASSERT_TRUE(b.operations.empty()); ASSERT_FALSE(a.operations.empty()); ASSERT_EQ(c, Circuit(R"CIRCUIT( @@ -1092,8 +1155,8 @@ TEST(circuit, append_repeat_block) { } )CIRCUIT")); - ASSERT_THROW({ c.append_repeat_block(0, a); }, std::invalid_argument); - ASSERT_THROW({ c.append_repeat_block(0, std::move(a)); }, std::invalid_argument); + ASSERT_THROW({ c.append_repeat_block(0, a, ""); }, std::invalid_argument); + ASSERT_THROW({ c.append_repeat_block(0, std::move(a), ""); }, std::invalid_argument); } TEST(circuit, aliased_noiseless_circuit) { diff --git a/src/stim/circuit/circuit_instruction.cc b/src/stim/circuit/circuit_instruction.cc index ccd89abe..6d53c3e0 100644 --- a/src/stim/circuit/circuit_instruction.cc +++ b/src/stim/circuit/circuit_instruction.cc @@ -106,8 +106,8 @@ const Circuit &CircuitInstruction::repeat_block_body(const Circuit &host) const } CircuitInstruction::CircuitInstruction( - GateType gate_type, SpanRef args, SpanRef targets) - : gate_type(gate_type), args(args), targets(targets) { + GateType gate_type, SpanRef args, SpanRef targets, std::string_view tag) + : gate_type(gate_type), args(args), targets(targets), tag(tag) { } void CircuitInstruction::validate() const { @@ -295,14 +295,14 @@ uint64_t CircuitInstruction::count_measurement_results() const { bool CircuitInstruction::can_fuse(const CircuitInstruction &other) const { auto flags = GATE_DATA[gate_type].flags; - return gate_type == other.gate_type && args == other.args && !(flags & GATE_IS_NOT_FUSABLE); + return gate_type == other.gate_type && args == other.args && !(flags & GATE_IS_NOT_FUSABLE) && tag == other.tag; } bool CircuitInstruction::operator==(const CircuitInstruction &other) const { - return gate_type == other.gate_type && args == other.args && targets == other.targets; + return gate_type == other.gate_type && args == other.args && targets == other.targets && tag == other.tag; } bool CircuitInstruction::approx_equals(const CircuitInstruction &other, double atol) const { - if (gate_type != other.gate_type || targets != other.targets || args.size() != other.args.size()) { + if (gate_type != other.gate_type || targets != other.targets || args.size() != other.args.size() || tag != other.tag) { return false; } for (size_t k = 0; k < args.size(); k++) { @@ -317,6 +317,55 @@ bool CircuitInstruction::operator!=(const CircuitInstruction &other) const { return !(*this == other); } +std::ostream &stim::operator<<(std::ostream &out, const CircuitInstruction &instruction) { + out << GATE_DATA[instruction.gate_type].name; + if (!instruction.tag.empty()) { + out << '['; + write_tag_escaped_string_to(instruction.tag, out); + out << ']'; + } + if (!instruction.args.empty()) { + out << '('; + bool first = true; + for (auto e : instruction.args) { + if (first) { + first = false; + } else { + out << ", "; + } + if (e > (double)INT64_MIN && e < (double)INT64_MAX && (int64_t)e == e) { + out << (int64_t)e; + } else { + out << e; + } + } + out << ')'; + } + write_targets(out, instruction.targets); + return out; +} + +void stim::write_tag_escaped_string_to(std::string_view tag, std::ostream &out) { + for (char c : tag) { + switch (c) { + case '\n': + out << "\\n"; + break; + case '\r': + out << "\\r"; + break; + case '\\': + out << "\\B"; + break; + case ']': + out << "\\C"; + break; + default: + out << c; + } + } +} + std::string CircuitInstruction::str() const { std::stringstream s; s << *this; diff --git a/src/stim/circuit/circuit_instruction.h b/src/stim/circuit/circuit_instruction.h index 33caeb81..238b1a07 100644 --- a/src/stim/circuit/circuit_instruction.h +++ b/src/stim/circuit/circuit_instruction.h @@ -71,8 +71,12 @@ struct CircuitInstruction { /// Encoded data indicating the qubits and other targets acted on by the gate. SpanRef targets; + /// Arbitrary string associated with the instruction. + /// No effect on simulations or analysis steps within stim, but user code may use it. + std::string_view tag; + CircuitInstruction() = delete; - CircuitInstruction(GateType gate_type, SpanRef args, SpanRef targets); + CircuitInstruction(GateType gate_type, SpanRef args, SpanRef targets, std::string_view tag); /// Computes number of qubits, number of measurements, etc. CircuitStats compute_stats(const Circuit *host) const; @@ -143,6 +147,10 @@ struct CircuitInstruction { } }; +void write_tag_escaped_string_to(std::string_view tag, std::ostream &out); + +std::ostream &operator<<(std::ostream &out, const CircuitInstruction &op); + } // namespace stim #endif diff --git a/src/stim/circuit/circuit_instruction.pybind.cc b/src/stim/circuit/circuit_instruction.pybind.cc index fe5bf4a4..aa064f5e 100644 --- a/src/stim/circuit/circuit_instruction.pybind.cc +++ b/src/stim/circuit/circuit_instruction.pybind.cc @@ -9,16 +9,20 @@ using namespace stim; using namespace stim_pybind; PyCircuitInstruction::PyCircuitInstruction( - std::string_view name, const std::vector &init_targets, const std::vector &gate_args) - : gate_type(GATE_DATA.at(name).id), gate_args(gate_args) { + std::string_view name, std::span init_targets, std::span init_gate_args, pybind11::str tag) + : gate_type(GATE_DATA.at(name).id), tag(tag) { + for (const auto &obj : init_gate_args) { + gate_args.push_back(obj); + } for (const auto &obj : init_targets) { targets.push_back(obj_to_gate_target(obj)); } + as_operation_ref().validate(); } PyCircuitInstruction::PyCircuitInstruction( - GateType gate_type, std::vector targets, std::vector gate_args) - : gate_type(gate_type), targets(targets), gate_args(gate_args) { + GateType gate_type, std::vector targets, std::vector gate_args, pybind11::str tag) + : gate_type(gate_type), targets(targets), gate_args(gate_args), tag(tag) { as_operation_ref().validate(); } @@ -40,11 +44,12 @@ PyCircuitInstruction PyCircuitInstruction::from_instruction(CircuitInstruction i instruction.gate_type, targets, arguments, + instruction.tag, }; } bool PyCircuitInstruction::operator==(const PyCircuitInstruction &other) const { - return gate_type == other.gate_type && targets == other.targets && gate_args == other.gate_args; + return gate_type == other.gate_type && targets == other.targets && gate_args == other.gate_args && pybind11::cast(tag) == pybind11::cast(other.tag); } bool PyCircuitInstruction::operator!=(const PyCircuitInstruction &other) const { return !(*this == other); @@ -62,7 +67,12 @@ std::string PyCircuitInstruction::repr() const { } result << t.repr(); } - result << "], [" << comma_sep(gate_args) << "])"; + result << "], [" << comma_sep(gate_args) << "]"; + if (pybind11::cast(pybind11::bool_(tag))) { + result << ", tag="; + result << pybind11::repr(tag); + } + result << ")"; return result.str(); } @@ -77,6 +87,7 @@ CircuitInstruction PyCircuitInstruction::as_operation_ref() const { gate_type, gate_args, targets, + pybind11::cast(tag) }; } PyCircuitInstruction::operator CircuitInstruction() const { @@ -138,8 +149,8 @@ pybind11::class_ stim_pybind::pybind_circuit_instruction(p } void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind11::class_ &c) { c.def( - pybind11::init([](std::string_view name, pybind11::object targets, pybind11::object gate_args) -> PyCircuitInstruction { - if (targets.is_none() and gate_args.is_none()) { + pybind11::init([](std::string_view name, pybind11::object targets, pybind11::object gate_args, pybind11::str tag) -> PyCircuitInstruction { + if (targets.is_none() and gate_args.is_none() && !pybind11::cast(pybind11::bool_(tag))) { return PyCircuitInstruction::from_str(name); } std::vector conv_args; @@ -150,13 +161,15 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind if (!targets.is_none()) { conv_targets = pybind11::cast>(targets); } - return PyCircuitInstruction(name, conv_targets, conv_args); + return PyCircuitInstruction(name, conv_targets, conv_args, tag); }), pybind11::arg("name"), pybind11::arg("targets") = pybind11::none(), pybind11::arg("gate_args") = pybind11::none(), + pybind11::kw_only(), + pybind11::arg("tag") = "", clean_doc_string(R"DOC( - @signature def __init__(self, name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None) -> None: + @signature def __init__(self, name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None, *, tag: str = "") -> None: Creates or parses a `stim.CircuitInstruction`. Args: @@ -169,6 +182,11 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + tag: Defaults to "". A custom string attached to the instruction. For + example, for a TICK instruction, this could a string specifying an + amount of time which is used by custom code for adding noise to a + circuit. In general, stim will attempt to propagate tags across circuit + transformations but will otherwise completely ignore them. Examples: >>> import stim @@ -178,6 +196,9 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) + + >>> print(stim.CircuitInstruction('I', [2], tag='100ns')) + I[100ns] 2 )DOC") .data()); @@ -189,6 +210,26 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind )DOC") .data()); + c.def_property_readonly( + "tag", + [](PyCircuitInstruction &self) -> pybind11::str { + return self.tag; + }, + clean_doc_string(R"DOC( + The custom tag attached to the instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + >>> stim.Circuit("H[test] 0")[0].tag + 'test' + >>> stim.Circuit("H 0")[0].tag + '' + )DOC") + .data()); + c.def( "target_groups", &PyCircuitInstruction::target_groups, diff --git a/src/stim/circuit/circuit_instruction.pybind.h b/src/stim/circuit/circuit_instruction.pybind.h index 69907915..7ad703bd 100644 --- a/src/stim/circuit/circuit_instruction.pybind.h +++ b/src/stim/circuit/circuit_instruction.pybind.h @@ -27,11 +27,13 @@ struct PyCircuitInstruction { stim::GateType gate_type; std::vector targets; std::vector gate_args; + pybind11::str tag; + PyCircuitInstruction() = delete; PyCircuitInstruction( - std::string_view name, const std::vector &targets, const std::vector &gate_args); + std::string_view name, std::span targets, std::span gate_args, pybind11::str tag); PyCircuitInstruction( - stim::GateType gate_type, std::vector targets, std::vector gate_args); + stim::GateType gate_type, std::vector targets, std::vector gate_args, pybind11::str tag); static PyCircuitInstruction from_str(std::string_view text); static PyCircuitInstruction from_instruction(stim::CircuitInstruction instruction); diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index d6a8fec0..0c612bc1 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import io import pathlib import tempfile from typing import cast @@ -1912,3 +1912,397 @@ def test_to_tableau(): assert stim.Circuit("SPP Z0").to_tableau() == stim.Tableau.from_named_gate("S") assert stim.Circuit("SPP X0").to_tableau() == stim.Tableau.from_named_gate("SQRT_X") assert stim.Circuit("SPP_DAG Y0*Y1").to_tableau() == stim.Tableau.from_named_gate("SQRT_YY_DAG") + + +def test_circuit_tags(): + c = stim.Circuit(""" + H[test] 0 + """) + assert str(c) == "H[test] 0" + assert c[0].tag == 'test' + c.append(stim.CircuitInstruction('CX', [0, 1], tag='test2')) + assert c[1].tag == 'test2' + assert c == stim.Circuit(""" + H[test] 0 + CX[test2] 0 1 + """) + assert c != stim.Circuit(""" + H 0 + CX 0 1 + """) + + +def test_circuit_add_tags(): + assert stim.Circuit(""" + H[test] 0 + """) + stim.Circuit(""" + CX[test2] 0 1 + """) == stim.Circuit(""" + H[test] 0 + CX[test2] 0 1 + """) + + +def test_circuit_eq_tags(): + assert stim.CircuitInstruction("TICK", tag="a") == stim.CircuitInstruction("TICK", tag="a") + assert stim.CircuitInstruction("TICK", tag="a") != stim.CircuitInstruction("TICK", tag="b") + assert stim.CircuitRepeatBlock(1, stim.Circuit(), tag="a") == stim.CircuitRepeatBlock(1, stim.Circuit(), tag="a") + assert stim.CircuitRepeatBlock(1, stim.Circuit(), tag="a") != stim.CircuitRepeatBlock(1, stim.Circuit(), tag="b") + assert stim.Circuit(""" + H[test] 0 + """) == stim.Circuit(""" + H[test] 0 + """) + assert stim.Circuit(""" + H[test] 0 + """) != stim.Circuit(""" + H[test2] 0 + """) + assert stim.Circuit(""" + H[test] 0 + """) != stim.Circuit(""" + H 0 + """) + assert stim.Circuit(""" + H[] 0 + """) == stim.Circuit(""" + H 0 + """) + + +def test_circuit_get_item_tags(): + assert stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """)[1] == stim.CircuitInstruction("CX[test2] 1 2") + assert stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """)[2] == stim.CircuitRepeatBlock(3, stim.Circuit("M[test4](0.25) 4"), tag="test3") + assert stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """)[1:3] == stim.Circuit(""" + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """) + + +def test_tags_iadd(): + c = stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + """) + c += stim.Circuit(""" + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """) + assert c == stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """) + + +def test_tags_imul(): + c = stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + """) + c *= 2 + assert c == stim.Circuit(""" + REPEAT 2 { + H[test] 0 + CX[test2] 1 2 + } + """) + + +def test_tags_mul(): + c = stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + """) + assert c * 2 == stim.Circuit(""" + REPEAT 2 { + H[test] 0 + CX[test2] 1 2 + } + """) + + +def test_tags_append(): + c = stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + """) + c.append(stim.CircuitRepeatBlock(3, stim.Circuit(""" + M[test4](0.25) 4 + """), tag="test3")) + assert c == stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """) + + +def test_tags_append_from_stim_program_text(): + c = stim.Circuit() + c.append_from_stim_program_text(""" + H[test] 0 + CX[test2] 1 2 + """) + assert c == stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + """) + + +def test_tag_approx_equals(): + assert not stim.Circuit("H[test] 0").approx_equals(stim.Circuit("H[test2] 0"), atol=3) + assert stim.Circuit("H[test] 0").approx_equals(stim.Circuit("H[test] 0"), atol=3) + + +def test_tag_clear(): + c = stim.Circuit("H[test] 0") + c.clear() + assert c == stim.Circuit() + + +def test_tag_compile_samplers(): + c = stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + M[test3](0.25) 0 + DETECTOR[test4](1, 2) rec[-1] + """) + s = c.compile_detector_sampler() + assert 200 < np.sum(s.sample(shots=1000)) < 600 + s = c.compile_sampler() + assert 200 < np.sum(s.sample(shots=1000)) < 600 + _ = c.compile_m2d_converter() + + +def test_tag_detector_error_model(): + dem = stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + M[test3](0.25) 0 + DETECTOR[test4](1, 2) rec[-1] + """).detector_error_model() + # TODO: propagate tags into detector error model + assert dem == stim.DetectorErrorModel(""" + error(0.375) D0 + detector(1, 2) D0 + """) + + +def test_tag_copy(): + c = stim.Circuit(""" + H[test] 0 + CX[test2] 1 2 + REPEAT[test3] 3 { + M[test4](0.25) 4 + } + """).detector_error_model() + cc = c.copy() + assert cc is not c + assert cc == c + + +def test_tag_count_determined_measurements(): + assert stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + M[test3](0.25) 0 + DETECTOR[test4](1, 2) rec[-1] + """).count_determined_measurements() == 1 + + +def test_tag_decomposed(): + assert stim.Circuit(""" + RX[test1] 0 + X_ERROR[test2](0.25) 0 + MPP[test3](0.25) X0*Z1 + DETECTOR[test4](1, 2) rec[-1] + SPP[test5] Y0 + """).decomposed() == stim.Circuit(""" + R[test1] 0 + H[test1] 0 + X_ERROR[test2](0.25) 0 + H[test3] 0 + CX[test3] 1 0 + M[test3] 0 + CX[test3] 1 0 + H[test3] 0 + DETECTOR[test4](1, 2) rec[-1] + H[test5] 0 + S[test5] 0 + H[test5] 0 + S[test5] 0 0 0 + H[test5] 0 + S[test5] 0 + H[test5] 0 + S[test5] 0 0 + """) + + +def test_tag_detecting_regions(): + assert stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + TICK + M[test3](0.25) 0 + DETECTOR[test4](1, 2) rec[-1] + """).detecting_regions() == {stim.DemTarget('D0'): {0: stim.PauliString("Z")}} + + +def test_tag_diagram(): + # TODO: include tags in diagrams + assert str(stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + M[test3](0.25) 0 + DETECTOR[test4](1, 2) rec[-1] + """).diagram()) == """ + q0: -R-X_ERROR(0.25)-M(0.25):rec[0]-DETECTOR(1,2):D0=rec[0]- + """.strip() + + +def test_tag_flattened(): + assert stim.Circuit(""" + R[test1] 0 + REPEAT[test1.5] 2 { + H[test2] 0 + } + """).flattened() == stim.Circuit(""" + R[test1] 0 + H[test2] 0 + H[test2] 0 + """) + + +def test_tag_from_file(): + c = stim.Circuit.from_file(io.StringIO(""" + R[test1] 0 + REPEAT[test1.5] 2 { + H[test2] 0 + } + """)) + assert c == stim.Circuit(""" + R[test1] 0 + REPEAT[test1.5] 2 { + H[test2] 0 + } + """) + s = io.StringIO() + c.to_file(s) + s.seek(0) + assert s.read() == str(c) + '\n' + + +def test_tag_insert(): + c = stim.Circuit(""" + H[test1] 0 + S[test2] 0 + """) + c.insert(1, stim.CircuitInstruction("CX[test3] 0 1")) + assert c == stim.Circuit(""" + H[test1] 0 + CX[test3] 0 1 + S[test2] 0 + """) + + +def test_tag_fuse(): + c = stim.Circuit(""" + H[test1] 0 + H[test1] 0 + H[test2] 0 + H[test1] 0 + """) + assert len(c) == 3 + assert c[0].tag == "test1" + assert c[1].tag == "test2" + assert c[2].tag == "test1" + + +def test_tag_inverse(): + assert stim.Circuit(""" + S[test1] 0 + CX[test2] 0 1 + SPP[test3] X0*Y1 + REPEAT[test4] 2 { + H[test5] 0 + } + """).inverse() == stim.Circuit(""" + REPEAT[test4] 2 { + H[test5] 0 + } + SPP_DAG[test3] Y1*X0 + CX[test2] 0 1 + S_DAG[test1] 0 + """) + + +def test_tag_time_reversed_for_flows(): + c, _ = stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 + SQRT_X[test3] 0 + MY[test4] 0 + DETECTOR[test5] rec[-1] + """).time_reversed_for_flows([]) + assert c == stim.Circuit(""" + RY[test4] 0 + SQRT_X_DAG[test3] 0 + X_ERROR[test2](0.25) 0 + M[test1] 0 + DETECTOR[test5] rec[-1] + """) + + +def test_tag_with_inlined_feedback(): + assert stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 1 + MR[test3] 0 + CX[test4] rec[-1] 1 + M[test5] 1 + DETECTOR[test6] rec[-1] + """).with_inlined_feedback() == stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 1 + MR[test3] 0 + M[test5] 1 + DETECTOR[test6] rec[-2] rec[-1] + """) + + +def test_tag_without_noise(): + assert stim.Circuit(""" + R[test1] 0 + X_ERROR[test2](0.25) 0 1 + M[test3](0.25) 0 + DETECTOR[test4] rec[-1] + """).without_noise() == stim.Circuit(""" + R[test1] 0 + M[test3] 0 + DETECTOR[test4] rec[-1] + """) diff --git a/src/stim/circuit/circuit_repeat_block.pybind.cc b/src/stim/circuit/circuit_repeat_block.pybind.cc index cb736e6a..76e08dbe 100644 --- a/src/stim/circuit/circuit_repeat_block.pybind.cc +++ b/src/stim/circuit/circuit_repeat_block.pybind.cc @@ -22,8 +22,8 @@ using namespace stim; using namespace stim_pybind; -CircuitRepeatBlock::CircuitRepeatBlock(uint64_t repeat_count, stim::Circuit body) - : repeat_count(repeat_count), body(body) { +CircuitRepeatBlock::CircuitRepeatBlock(uint64_t repeat_count, stim::Circuit body, pybind11::str tag) + : repeat_count(repeat_count), body(body), tag(tag) { if (repeat_count == 0) { throw std::invalid_argument("Can't repeat 0 times."); } @@ -33,7 +33,7 @@ Circuit CircuitRepeatBlock::body_copy() { return body; } bool CircuitRepeatBlock::operator==(const CircuitRepeatBlock &other) const { - return repeat_count == other.repeat_count && body == other.body; + return repeat_count == other.repeat_count && body == other.body && pybind11::cast(tag) == pybind11::cast(other.tag); } bool CircuitRepeatBlock::operator!=(const CircuitRepeatBlock &other) const { return !(*this == other); @@ -72,15 +72,18 @@ pybind11::class_ stim_pybind::pybind_circuit_repeat_block(py void stim_pybind::pybind_circuit_repeat_block_methods(pybind11::module &m, pybind11::class_ &c) { c.def( - pybind11::init(), + pybind11::init(), pybind11::arg("repeat_count"), pybind11::arg("body"), + pybind11::kw_only(), + pybind11::arg("tag") = "", clean_doc_string(R"DOC( Initializes a `stim.CircuitRepeatBlock`. Args: repeat_count: The number of times to repeat the block. body: The body of the block, as a circuit. + tag: Defaults to empty. A custom string attached to the REPEAT instruction. )DOC") .data()); @@ -170,6 +173,36 @@ void stim_pybind::pybind_circuit_repeat_block_methods(pybind11::module &m, pybin )DOC") .data()); + c.def_property_readonly( + "tag", + [](CircuitRepeatBlock &self) -> pybind11::str { + return self.tag; + }, + clean_doc_string(R"DOC( + The custom tag attached to the REPEAT instruction. + + The tag is an arbitrary string. + The default tag, when none is specified, is the empty string. + + Examples: + >>> import stim + + >>> stim.Circuit(''' + ... REPEAT[test] 5 { + ... H 0 + ... } + ... ''')[0].tag + 'test' + + >>> stim.Circuit(''' + ... REPEAT 5 { + ... H 0 + ... } + ... ''')[0].tag + '' + )DOC") + .data()); + c.def( "body_copy", &CircuitRepeatBlock::body_copy, diff --git a/src/stim/circuit/circuit_repeat_block.pybind.h b/src/stim/circuit/circuit_repeat_block.pybind.h index 255ff116..2628d2d3 100644 --- a/src/stim/circuit/circuit_repeat_block.pybind.h +++ b/src/stim/circuit/circuit_repeat_block.pybind.h @@ -24,7 +24,8 @@ namespace stim_pybind { struct CircuitRepeatBlock { uint64_t repeat_count; stim::Circuit body; - CircuitRepeatBlock(uint64_t repeat_count, stim::Circuit body); + pybind11::str tag; + CircuitRepeatBlock(uint64_t repeat_count, stim::Circuit body, pybind11::str tag); stim::Circuit body_copy(); bool operator==(const CircuitRepeatBlock &other) const; bool operator!=(const CircuitRepeatBlock &other) const; diff --git a/src/stim/circuit/gate_decomposition.cc b/src/stim/circuit/gate_decomposition.cc index 47871cde..fdc46387 100644 --- a/src/stim/circuit/gate_decomposition.cc +++ b/src/stim/circuit/gate_decomposition.cc @@ -101,10 +101,10 @@ void stim::decompose_mpp_operation( return; } { - ConjugateBySelfInverse c1(CircuitInstruction{GateType::H, {}, h_xz}, do_instruction_callback); - ConjugateBySelfInverse c2(CircuitInstruction{GateType::H_YZ, {}, h_yz}, do_instruction_callback); - ConjugateBySelfInverse c3(CircuitInstruction{GateType::CX, {}, cnot}, do_instruction_callback); - do_instruction_callback(CircuitInstruction{GateType::M, mpp_op.args, meas}); + ConjugateBySelfInverse c1(CircuitInstruction(GateType::H, {}, h_xz, mpp_op.tag), do_instruction_callback); + ConjugateBySelfInverse c2(CircuitInstruction(GateType::H_YZ, {}, h_yz, mpp_op.tag), do_instruction_callback); + ConjugateBySelfInverse c3(CircuitInstruction(GateType::CX, {}, cnot, mpp_op.tag), do_instruction_callback); + do_instruction_callback(CircuitInstruction(GateType::M, mpp_op.args, meas, mpp_op.tag)); } h_xz.clear(); h_yz.clear(); @@ -119,7 +119,7 @@ void stim::decompose_mpp_operation( if (current.ref().has_no_pauli_terms()) { flush(); GateTarget t = GateTarget::qubit((uint32_t)current.sign); - do_instruction_callback(CircuitInstruction{GateType::MPAD, mpp_op.args, &t}); + do_instruction_callback(CircuitInstruction{GateType::MPAD, mpp_op.args, &t, mpp_op.tag}); continue; } @@ -166,7 +166,8 @@ static void decompose_spp_or_spp_dag_operation_helper( const std::function &do_instruction_callback, std::vector *h_xz_buf, std::vector *h_yz_buf, - std::vector *cnot_buf) { + std::vector *cnot_buf, + std::string_view tag) { h_xz_buf->clear(); h_yz_buf->clear(); cnot_buf->clear(); @@ -207,10 +208,10 @@ static void decompose_spp_or_spp_dag_operation_helper( bool sign = invert_sign ^ observable.sign; GateType g = sign ? GateType::S_DAG : GateType::S; { - ConjugateBySelfInverse c1(CircuitInstruction{GateType::H, {}, *h_xz_buf}, do_instruction_callback); - ConjugateBySelfInverse c2(CircuitInstruction{GateType::H_YZ, {}, *h_yz_buf}, do_instruction_callback); - ConjugateBySelfInverse c3(CircuitInstruction{GateType::CX, {}, *cnot_buf}, do_instruction_callback); - do_instruction_callback(CircuitInstruction{g, {}, &t}); + ConjugateBySelfInverse c1(CircuitInstruction(GateType::H, {}, *h_xz_buf, tag), do_instruction_callback); + ConjugateBySelfInverse c2(CircuitInstruction(GateType::H_YZ, {}, *h_yz_buf, tag), do_instruction_callback); + ConjugateBySelfInverse c3(CircuitInstruction(GateType::CX, {}, *cnot_buf, tag), do_instruction_callback); + do_instruction_callback(CircuitInstruction(g, {}, &t, tag)); } } @@ -236,7 +237,7 @@ void stim::decompose_spp_or_spp_dag_operation( size_t start = 0; while (accumulate_next_obs_terms_to_pauli_string_helper(spp_op, &start, &obs, &bits)) { decompose_spp_or_spp_dag_operation_helper( - obs, bits, invert_sign, do_instruction_callback, &h_xz_buf, &h_yz_buf, &cnot_buf); + obs, bits, invert_sign, do_instruction_callback, &h_xz_buf, &h_yz_buf, &cnot_buf, spp_op.tag); } } @@ -250,6 +251,7 @@ void stim::decompose_pair_instruction_into_disjoint_segments( inst.gate_type, inst.args, inst.targets.sub(num_flushed, cur_index), + inst.tag, }); used_as_control.clear(); num_flushed = cur_index; @@ -277,11 +279,12 @@ void stim::for_each_disjoint_target_segment_in_instruction_reversed( size_t cur_end = inst.targets.size(); size_t cur_start = inst.targets.size(); auto flush = [&]() { - callback(CircuitInstruction{ + callback(CircuitInstruction( inst.gate_type, inst.args, inst.targets.sub(cur_start, cur_end), - }); + inst.tag + )); workspace.clear(); cur_end = cur_start; }; @@ -309,7 +312,7 @@ void stim::for_each_combined_targets_group( size_t next_start = 1; while (true) { if (next_start >= inst.targets.size() || !inst.targets[next_start].is_combiner()) { - callback(CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, next_start)}); + callback(CircuitInstruction(inst.gate_type, inst.args, inst.targets.sub(start, next_start), inst.tag)); start = next_start; next_start = start + 1; if (next_start > inst.targets.size()) { diff --git a/src/stim/cmd/command_help.cc b/src/stim/cmd/command_help.cc index d0a600c6..1e09e061 100644 --- a/src/stim/cmd/command_help.cc +++ b/src/stim/cmd/command_help.cc @@ -248,7 +248,7 @@ void print_decomposition(Acc &out, const Gate &gate) { if (decomposition != nullptr) { std::stringstream undecomposed; auto decomp_targets = gate_decomposition_help_targets_for_gate_type(gate.id); - undecomposed << CircuitInstruction{gate.id, {}, decomp_targets}; + undecomposed << CircuitInstruction{gate.id, {}, decomp_targets, ""}; out << "Decomposition (into H, S, CX, M, R):\n"; out.change_indent(+4); @@ -270,7 +270,7 @@ void print_stabilizer_generators(Acc &out, const Gate &gate) { auto decomp_targets = gate_decomposition_help_targets_for_gate_type(gate.id); if (decomp_targets.size() > 2) { out << "Stabilizer Generators (for `"; - out << CircuitInstruction{gate.id, {}, decomp_targets}; + out << CircuitInstruction{gate.id, {}, decomp_targets, ""}; out << "`):\n"; } else { out << "Stabilizer Generators:\n"; diff --git a/src/stim/dem/detector_error_model.cc b/src/stim/dem/detector_error_model.cc index 707786d8..e7e8e65c 100644 --- a/src/stim/dem/detector_error_model.cc +++ b/src/stim/dem/detector_error_model.cc @@ -51,10 +51,10 @@ void DetectorErrorModel::append_dem_instruction(const DemInstruction &instructio } void DetectorErrorModel::append_repeat_block(uint64_t repeat_count, DetectorErrorModel &&body) { - DemTarget data[2]; + std::array data; data[0].data = repeat_count; data[1].data = blocks.size(); - auto stored_targets = target_buf.take_copy({&data[0], &data[2]}); + auto stored_targets = target_buf.take_copy(data); blocks.push_back(std::move(body)); instructions.push_back({{}, stored_targets, DemInstructionType::DEM_REPEAT_BLOCK}); } diff --git a/src/stim/gates/gates.test.cc b/src/stim/gates/gates.test.cc index 5d76180a..b76be9f6 100644 --- a/src/stim/gates/gates.test.cc +++ b/src/stim/gates/gates.test.cc @@ -94,7 +94,7 @@ bool is_decomposition_correct(const Gate &gate) { } Circuit original; - original.safe_append(gate.id, gate_decomposition_help_targets_for_gate_type(gate.id), {}); + original.safe_append(CircuitInstruction(gate.id, {}, gate_decomposition_help_targets_for_gate_type(gate.id), "")); uint32_t n = original.count_qubits(); Circuit epr; @@ -161,7 +161,7 @@ TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_correct, { std::vector targets = gate_decomposition_help_targets_for_gate_type(g.id); Circuit c; - c.safe_append(g.id, targets, {}); + c.safe_append(CircuitInstruction(g.id, {}, targets, "")); auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_if_circuit_has_stabilizer_flows(256, rng, c, flows); for (uint32_t fk = 0; fk < (uint32_t)flows.size(); fk++) { diff --git a/src/stim/mem/monotonic_buffer.h b/src/stim/mem/monotonic_buffer.h index 8b6998dc..a515e62e 100644 --- a/src/stim/mem/monotonic_buffer.h +++ b/src/stim/mem/monotonic_buffer.h @@ -116,11 +116,20 @@ struct MonotonicBuffer { /// Appends and commits data. /// Requires the tail to be empty, to avoid bugs where previously staged data is committed. - SpanRef take_copy(SpanRef data) { + std::span take_copy(std::span data) { assert(tail.size() == 0); append_tail(data); return commit_tail(); } + std::string_view take_copy(std::string_view data) { + if (data.empty()) { + return std::string_view(); + } + assert(tail.size() == 0); + append_tail(SpanRef(&data[0], &data[0] + data.size())); + SpanRef v = commit_tail(); + return {v.ptr_start, v.size()}; + } /// Adds a staged data item. void append_tail(T item) { diff --git a/src/stim/simulators/error_analyzer.cc b/src/stim/simulators/error_analyzer.cc index 7edb4431..8d626426 100644 --- a/src/stim/simulators/error_analyzer.cc +++ b/src/stim/simulators/error_analyzer.cc @@ -525,24 +525,24 @@ void ErrorAnalyzer::undo_MZ(const CircuitInstruction &dat) { void ErrorAnalyzer::undo_MRX(const CircuitInstruction &dat) { for (size_t k = dat.targets.size(); k-- > 0;) { auto q = dat.targets[k]; - undo_RX_with_context({GateType::RX, dat.args, &q}, "an X-basis demolition measurement (MRX)"); - undo_MX_with_context({GateType::MX, dat.args, &q}, "an X-basis demolition measurement (MRX)"); + undo_RX_with_context({GateType::RX, dat.args, &q, dat.tag}, "an X-basis demolition measurement (MRX)"); + undo_MX_with_context({GateType::MX, dat.args, &q, dat.tag}, "an X-basis demolition measurement (MRX)"); } } void ErrorAnalyzer::undo_MRY(const CircuitInstruction &dat) { for (size_t k = dat.targets.size(); k-- > 0;) { auto q = dat.targets[k]; - undo_RY_with_context({GateType::RY, dat.args, &q}, "a Y-basis demolition measurement (MRY)"); - undo_MY_with_context({GateType::MY, dat.args, &q}, "a Y-basis demolition measurement (MRY)"); + undo_RY_with_context({GateType::RY, dat.args, &q, dat.tag}, "a Y-basis demolition measurement (MRY)"); + undo_MY_with_context({GateType::MY, dat.args, &q, dat.tag}, "a Y-basis demolition measurement (MRY)"); } } void ErrorAnalyzer::undo_MRZ(const CircuitInstruction &dat) { for (size_t k = dat.targets.size(); k-- > 0;) { auto q = dat.targets[k]; - undo_RZ_with_context({GateType::R, dat.args, &q}, "a Z-basis demolition measurement (MR)"); - undo_MZ_with_context({GateType::M, dat.args, &q}, "a Z-basis demolition measurement (MR)"); + undo_RZ_with_context({GateType::R, dat.args, &q, dat.tag}, "a Z-basis demolition measurement (MR)"); + undo_MZ_with_context({GateType::M, dat.args, &q, dat.tag}, "a Z-basis demolition measurement (MR)"); } } @@ -1142,13 +1142,13 @@ void ErrorAnalyzer::run_loop(const Circuit &loop, uint64_t iterations) { if (remaining_shift.data > 0) { if (body.instructions.empty() || body.instructions.front().type != DemInstructionType::DEM_SHIFT_DETECTORS) { - auto shift_targets = body.target_buf.take_copy({&remaining_shift}); + auto shift_targets = body.target_buf.take_copy(SpanRef(&remaining_shift)); body.instructions.insert( body.instructions.begin(), DemInstruction{{}, shift_targets, DemInstructionType::DEM_SHIFT_DETECTORS}); } else { remaining_shift.data += body.instructions[0].target_data[0].data; - auto shift_targets = body.target_buf.take_copy({&remaining_shift}); + auto shift_targets = body.target_buf.take_copy(SpanRef(&remaining_shift)); body.instructions[0].target_data = shift_targets; } } @@ -1616,15 +1616,15 @@ void ErrorAnalyzer::add_error_combinations( } } -void ErrorAnalyzer::undo_MPP(const CircuitInstruction &target_data) { - size_t n = target_data.targets.size(); +void ErrorAnalyzer::undo_MPP(const CircuitInstruction &inst) { + size_t n = inst.targets.size(); std::vector reversed_targets(n); std::vector reversed_measure_targets; for (size_t k = 0; k < n; k++) { - reversed_targets[k] = target_data.targets[n - k - 1]; + reversed_targets[k] = inst.targets[n - k - 1]; } decompose_mpp_operation( - CircuitInstruction{GateType::MPP, target_data.args, reversed_targets}, + CircuitInstruction{GateType::MPP, inst.args, reversed_targets, inst.tag}, tracker.xs.size(), [&](const CircuitInstruction &inst) { if (inst.gate_type == GateType::M) { @@ -1633,7 +1633,7 @@ void ErrorAnalyzer::undo_MPP(const CircuitInstruction &target_data) { reversed_measure_targets.push_back(inst.targets[k]); } undo_MZ_with_context( - CircuitInstruction{GateType::M, inst.args, reversed_measure_targets}, + CircuitInstruction{GateType::M, inst.args, reversed_measure_targets, inst.tag}, "a Pauli product measurement (MPP)"); } else { undo_gate(inst); @@ -1641,15 +1641,15 @@ void ErrorAnalyzer::undo_MPP(const CircuitInstruction &target_data) { }); } -void ErrorAnalyzer::undo_SPP(const CircuitInstruction &target_data) { - size_t n = target_data.targets.size(); +void ErrorAnalyzer::undo_SPP(const CircuitInstruction &inst) { + size_t n = inst.targets.size(); std::vector reversed_targets(n); std::vector reversed_measure_targets; for (size_t k = 0; k < n; k++) { - reversed_targets[k] = target_data.targets[n - k - 1]; + reversed_targets[k] = inst.targets[n - k - 1]; } decompose_spp_or_spp_dag_operation( - CircuitInstruction{GateType::SPP, target_data.args, reversed_targets}, + CircuitInstruction{GateType::SPP, inst.args, reversed_targets, inst.tag}, tracker.xs.size(), false, [&](const CircuitInstruction &inst) { @@ -1659,47 +1659,47 @@ void ErrorAnalyzer::undo_SPP(const CircuitInstruction &target_data) { void ErrorAnalyzer::undo_MXX_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, inst.tag}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { undo_MX_with_context( - CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}}, + CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}, inst.tag}, "an X-basis pair measurement (MXX)"); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, inst.tag}); } void ErrorAnalyzer::undo_MYY_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, inst.tag}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { undo_MY_with_context( - CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}}, + CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}, inst.tag}, "a Y-basis pair measurement (MYY)"); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, inst.tag}); } void ErrorAnalyzer::undo_MZZ_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, inst.tag}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { undo_MZ_with_context( - CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}}, + CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}, inst.tag}, "a Z-basis pair measurement (MZ)"); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, inst.tag}); } void ErrorAnalyzer::undo_MXX(const CircuitInstruction &inst) { @@ -1711,7 +1711,7 @@ void ErrorAnalyzer::undo_MXX(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, tracker.xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, inst.tag}, tracker.xs.size(), [&](CircuitInstruction segment) { undo_MXX_disjoint_controls_segment(segment); }); } @@ -1725,7 +1725,7 @@ void ErrorAnalyzer::undo_MYY(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, tracker.xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, inst.tag}, tracker.xs.size(), [&](CircuitInstruction segment) { undo_MYY_disjoint_controls_segment(segment); }); } @@ -1739,7 +1739,7 @@ void ErrorAnalyzer::undo_MZZ(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, tracker.xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, inst.tag}, tracker.xs.size(), [&](CircuitInstruction segment) { undo_MZZ_disjoint_controls_segment(segment); }); } diff --git a/src/stim/simulators/error_analyzer.test.cc b/src/stim/simulators/error_analyzer.test.cc index 9b8ee699..cf675abb 100644 --- a/src/stim/simulators/error_analyzer.test.cc +++ b/src/stim/simulators/error_analyzer.test.cc @@ -308,8 +308,8 @@ TEST_EACH_WORD_SIZE_W(ErrorAnalyzer, unitary_gates_match_frame_simulator, { } for (const auto &gate : GATE_DATA.items) { if (gate.has_known_unitary_matrix()) { - e.undo_gate({gate.id, {}, data}); - f.do_gate({gate.inverse().id, {}, data}); + e.undo_gate(CircuitInstruction{gate.id, {}, data, ""}); + f.do_gate(CircuitInstruction{gate.inverse().id, {}, data, ""}); for (size_t q = 0; q < 16; q++) { bool xs[2]{}; bool zs[2]{}; diff --git a/src/stim/simulators/error_matcher.cc b/src/stim/simulators/error_matcher.cc index 7d986908..d937205d 100644 --- a/src/stim/simulators/error_matcher.cc +++ b/src/stim/simulators/error_matcher.cc @@ -127,7 +127,7 @@ void ErrorMatcher::err_xyz(const CircuitInstruction &op, uint32_t target_flags) cur_loc.instruction_targets.target_range_start = k; cur_loc.instruction_targets.target_range_end = k + 1; resolve_paulis_into(&op.targets[k], target_flags, cur_loc.flipped_pauli_product); - err_atom({op.gate_type, a, &t[k]}); + err_atom(CircuitInstruction{op.gate_type, a, &t[k], op.tag}); cur_loc.flipped_pauli_product.clear(); } } @@ -188,9 +188,9 @@ void ErrorMatcher::err_heralded_pauli_channel_1(const CircuitInstruction &op) { void ErrorMatcher::err_pauli_channel_1(const CircuitInstruction &op) { const auto &a = op.args; const auto &t = op.targets; - err_xyz(CircuitInstruction{GateType::X_ERROR, &a[0], t}, TARGET_PAULI_X_BIT); - err_xyz(CircuitInstruction{GateType::Y_ERROR, &a[1], t}, TARGET_PAULI_X_BIT | TARGET_PAULI_Z_BIT); - err_xyz(CircuitInstruction{GateType::Z_ERROR, &a[2], t}, TARGET_PAULI_Z_BIT); + err_xyz(CircuitInstruction{GateType::X_ERROR, &a[0], t, op.tag}, TARGET_PAULI_X_BIT); + err_xyz(CircuitInstruction{GateType::Y_ERROR, &a[1], t, op.tag}, TARGET_PAULI_X_BIT | TARGET_PAULI_Z_BIT); + err_xyz(CircuitInstruction{GateType::Z_ERROR, &a[2], t, op.tag}, TARGET_PAULI_Z_BIT); } void ErrorMatcher::err_pauli_channel_2(const CircuitInstruction &op) { @@ -200,9 +200,9 @@ void ErrorMatcher::err_pauli_channel_2(const CircuitInstruction &op) { // Buffers and pointers into them. std::array pair; double p = 0; - CircuitInstruction pair_effect = {GateType::E, &p, pair}; - CircuitInstruction first_effect = {GateType::E, &p, &pair[0]}; - CircuitInstruction second_effect = {GateType::E, &p, &pair[1]}; + CircuitInstruction pair_effect = {GateType::E, &p, pair, op.tag}; + CircuitInstruction first_effect = {GateType::E, &p, &pair[0], op.tag}; + CircuitInstruction second_effect = {GateType::E, &p, &pair[1], op.tag}; for (size_t k = 0; k < t.size(); k += 2) { cur_loc.instruction_targets.target_range_start = k; @@ -261,7 +261,7 @@ void ErrorMatcher::err_m(const CircuitInstruction &op, uint32_t obs_mask) { cur_loc.instruction_targets.target_range_end = end; cur_loc.flipped_measurement.measurement_record_index = error_analyzer.tracker.num_measurements_in_past - 1; resolve_paulis_into(slice, obs_mask, cur_loc.flipped_measurement.measured_observable); - err_atom({op.gate_type, a, slice}); + err_atom(CircuitInstruction{op.gate_type, a, slice, op.tag}); cur_loc.flipped_measurement.measurement_record_index = UINT64_MAX; cur_loc.flipped_measurement.measured_observable.clear(); @@ -332,13 +332,13 @@ void ErrorMatcher::rev_process_instruction(const CircuitInstruction &op) { case GateType::HERALDED_ERASE: { float p = op.args[0] / 4; std::array spread{p, p, p, p}; - err_heralded_pauli_channel_1({op.gate_type, spread, op.targets}); + err_heralded_pauli_channel_1(CircuitInstruction{op.gate_type, spread, op.targets, op.tag}); break; } case GateType::DEPOLARIZE1: { float p = op.args[0]; std::array spread{p, p, p}; - err_pauli_channel_1({op.gate_type, spread, op.targets}); + err_pauli_channel_1(CircuitInstruction{op.gate_type, spread, op.targets, op.tag}); break; } case GateType::PAULI_CHANNEL_2: @@ -347,7 +347,7 @@ void ErrorMatcher::rev_process_instruction(const CircuitInstruction &op) { case GateType::DEPOLARIZE2: { float p = op.args[0]; std::array spread{p, p, p, p, p, p, p, p, p, p, p, p, p, p, p}; - err_pauli_channel_2({op.gate_type, spread, op.targets}); + err_pauli_channel_2(CircuitInstruction{op.gate_type, spread, op.targets, op.tag}); break; } case GateType::MPP: diff --git a/src/stim/simulators/frame_simulator.inl b/src/stim/simulators/frame_simulator.inl index 0a87b3a0..662362cb 100644 --- a/src/stim/simulators/frame_simulator.inl +++ b/src/stim/simulators/frame_simulator.inl @@ -830,43 +830,43 @@ void FrameSimulator::do_HERALDED_ERASE(const CircuitInstruction &inst) { template void FrameSimulator::do_MXX_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - do_MX(CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}}); + do_MX(CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); } template void FrameSimulator::do_MYY_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - do_MY(CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}}); + do_MY(CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); } template void FrameSimulator::do_MZZ_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - do_MZ(CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}}); + do_MZ(CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); } template diff --git a/src/stim/simulators/frame_simulator.perf.cc b/src/stim/simulators/frame_simulator.perf.cc index 868e8732..2a0d98e1 100644 --- a/src/stim/simulators/frame_simulator.perf.cc +++ b/src/stim/simulators/frame_simulator.perf.cc @@ -32,7 +32,7 @@ BENCHMARK(FrameSimulator_depolarize1_100Kqubits_1Ksamples_per1000) { for (uint32_t k = 0; k < stats.num_qubits; k++) { targets.push_back(GateTarget{k}); } - CircuitInstruction op_data{GateType::DEPOLARIZE1, &probability, targets}; + CircuitInstruction op_data{GateType::DEPOLARIZE1, &probability, targets, ""}; benchmark_go([&]() { sim.do_DEPOLARIZE1(op_data); }) @@ -52,7 +52,7 @@ BENCHMARK(FrameSimulator_depolarize2_100Kqubits_1Ksamples_per1000) { for (uint32_t k = 0; k < stats.num_qubits; k++) { targets.push_back({k}); } - CircuitInstruction op_data{GateType::DEPOLARIZE2, &probability, targets}; + CircuitInstruction op_data{GateType::DEPOLARIZE2, &probability, targets, ""}; benchmark_go([&]() { sim.do_DEPOLARIZE2(op_data); @@ -72,7 +72,7 @@ BENCHMARK(FrameSimulator_hadamard_100Kqubits_1Ksamples) { for (uint32_t k = 0; k < stats.num_qubits; k++) { targets.push_back({k}); } - CircuitInstruction op_data{GateType::H, {}, targets}; + CircuitInstruction op_data{GateType::H, {}, targets, ""}; benchmark_go([&]() { sim.do_H_XZ(op_data); @@ -92,7 +92,7 @@ BENCHMARK(FrameSimulator_CX_100Kqubits_1Ksamples) { for (uint32_t k = 0; k < stats.num_qubits; k++) { targets.push_back({k}); } - CircuitInstruction op_data{GateType::CX, {}, targets}; + CircuitInstruction op_data{GateType::CX, {}, targets, ""}; benchmark_go([&]() { sim.do_ZCX(op_data); diff --git a/src/stim/simulators/frame_simulator.test.cc b/src/stim/simulators/frame_simulator.test.cc index c01df1b3..72d05815 100644 --- a/src/stim/simulators/frame_simulator.test.cc +++ b/src/stim/simulators/frame_simulator.test.cc @@ -75,7 +75,7 @@ bool is_bulk_frame_operation_consistent_with_tableau(const Gate &gate) { auto test_value = PauliString::random(circuit_stats.num_qubits, rng); PauliStringRef test_value_ref(test_value); sim.set_frame(k, test_value); - sim.do_gate({gate.id, {}, targets}); + sim.do_gate({gate.id, {}, targets, ""}); for (size_t k2 = 0; k2 < targets.size(); k2 += num_targets) { size_t target_buf[2]; if (num_targets == 1) { @@ -112,7 +112,7 @@ bool is_output_possible_promising_no_bare_resets(const Circuit &circuit, const s if (op.gate_type == GateType::M) { for (auto qf : op.targets) { tableau_sim.sign_bias = output[out_p] ? -1 : +1; - tableau_sim.do_MZ({GateType::M, {}, &qf}); + tableau_sim.do_MZ(CircuitInstruction{GateType::M, {}, &qf, ""}); if (output[out_p] != tableau_sim.measurement_record.storage.back()) { pass = false; } diff --git a/src/stim/simulators/graph_simulator.cc b/src/stim/simulators/graph_simulator.cc index 55e46fbb..4df7e8b5 100644 --- a/src/stim/simulators/graph_simulator.cc +++ b/src/stim/simulators/graph_simulator.cc @@ -12,8 +12,8 @@ GraphSimulator::GraphSimulator(size_t num_qubits) void GraphSimulator::do_1q_gate(GateType gate, size_t qubit) { GateTarget t = GateTarget::qubit(qubit); - x2outs.ref().do_instruction(CircuitInstruction{gate, {}, &t}); - z2outs.ref().do_instruction(CircuitInstruction{gate, {}, &t}); + x2outs.ref().do_instruction(CircuitInstruction{gate, {}, &t, ""}); + z2outs.ref().do_instruction(CircuitInstruction{gate, {}, &t, ""}); paulis.xs[qubit] ^= z2outs.sign; paulis.zs[qubit] ^= x2outs.sign; x2outs.sign = 0; @@ -398,7 +398,7 @@ void GraphSimulator::output_pauli_layer(Circuit &out, bool to_hs_xyz) const { auto f = [&](GateType g, int k) { if (!groups[k].empty()) { - out.safe_append(g, groups[k], {}); + out.safe_append(CircuitInstruction(g, {}, groups[k], "")); } }; f(GateType::X, 0b01); @@ -428,7 +428,7 @@ void GraphSimulator::output_clifford_layer(Circuit &out, bool to_hs_xyz) const { } } else { if (!groups[k].empty()) { - out.safe_append(g, groups[k], {}); + out.safe_append(CircuitInstruction(g, {}, groups[k], "")); } } }; @@ -440,7 +440,7 @@ void GraphSimulator::output_clifford_layer(Circuit &out, bool to_hs_xyz) const { for (size_t k = 0; k < 3; k++) { if (!shs[k].empty()) { std::sort(shs[k].begin(), shs[k].end()); - out.safe_append(k == 1 ? GateType::H : GateType::S, shs[k], {}); + out.safe_append(CircuitInstruction(k == 1 ? GateType::H : GateType::S, {}, shs[k], "")); } } } @@ -454,9 +454,9 @@ Circuit GraphSimulator::to_circuit(bool to_hs_xyz) const { targets.push_back(GateTarget::qubit(q)); } if (!targets.empty()) { - out.safe_append(GateType::RX, targets, {}); + out.safe_append(CircuitInstruction(GateType::RX, {}, targets, "")); } - out.safe_append(GateType::TICK, {}, {}); + out.safe_append(CircuitInstruction(GateType::TICK, {}, {}, "")); bool has_cz = false; for (size_t q = 0; q < num_qubits; q++) { @@ -468,12 +468,12 @@ Circuit GraphSimulator::to_circuit(bool to_hs_xyz) const { } } if (!targets.empty()) { - out.safe_append(GateType::CZ, targets, {}); + out.safe_append(CircuitInstruction(GateType::CZ, {}, targets, "")); } has_cz |= !targets.empty(); } if (has_cz) { - out.safe_append(GateType::TICK, {}, {}); + out.safe_append(CircuitInstruction(GateType::TICK, {}, {}, "")); } output_clifford_layer(out, to_hs_xyz); diff --git a/src/stim/simulators/graph_simulator.test.cc b/src/stim/simulators/graph_simulator.test.cc index d5b79ba0..c5cec14e 100644 --- a/src/stim/simulators/graph_simulator.test.cc +++ b/src/stim/simulators/graph_simulator.test.cc @@ -261,7 +261,7 @@ TEST(graph_simulator, all_unitary_gates_work) { continue; } Circuit circuit; - circuit.safe_append(gate.id, (gate.flags & GATE_TARGETS_PAIRS) ? t2 : t1, {}); + circuit.safe_append(CircuitInstruction(gate.id, {}, (gate.flags & GATE_TARGETS_PAIRS) ? t2 : t1, "")); for (size_t k = 0; k < 20; k++) { expect_graph_sim_effect_matches_tableau_sim(GraphSimulator::random_state(8, rng), circuit); } diff --git a/src/stim/simulators/matched_error.pybind.cc b/src/stim/simulators/matched_error.pybind.cc index 81c2fb7f..8d4a5555 100644 --- a/src/stim/simulators/matched_error.pybind.cc +++ b/src/stim/simulators/matched_error.pybind.cc @@ -384,6 +384,18 @@ void stim_pybind::pybind_dem_target_with_coords_methods( }, clean_doc_string(R"DOC( Returns the actual DEM target as a `stim.DemTarget`. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].dem_target + stim.DemTarget('D0') )DOC") .data()); @@ -394,6 +406,18 @@ void stim_pybind::pybind_dem_target_with_coords_methods( Returns the associated coordinate information as a list of floats. If there is no coordinate information, returns an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0].coords + [2.0, 3.0] )DOC") .data()); @@ -414,6 +438,18 @@ void stim_pybind::pybind_dem_target_with_coords_methods( pybind11::arg("coords"), clean_doc_string(R"DOC( Creates a stim.DemTargetWithCoords. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 1 + ... X_ERROR(0.25) 0 1 + ... M 0 1 + ... DETECTOR(2, 3) rec[-1] rec[-2] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].dem_error_terms[0] + stim.DemTargetWithCoords(dem_target=stim.DemTarget('D0'), coords=[2, 3]) )DOC") .data()); c.def("__repr__", DemTargetWithCoords_repr); diff --git a/src/stim/simulators/sparse_rev_frame_tracker.cc b/src/stim/simulators/sparse_rev_frame_tracker.cc index 569766a8..40d8fcdc 100644 --- a/src/stim/simulators/sparse_rev_frame_tracker.cc +++ b/src/stim/simulators/sparse_rev_frame_tracker.cc @@ -333,15 +333,15 @@ void SparseUnsignedRevFrameTracker::handle_z_gauges(const CircuitInstruction &da handle_gauge(zs[q].range(), dat, GateTarget::z(q)); } } -void SparseUnsignedRevFrameTracker::undo_MPP(const CircuitInstruction &target_data) { - size_t n = target_data.targets.size(); +void SparseUnsignedRevFrameTracker::undo_MPP(const CircuitInstruction &inst) { + size_t n = inst.targets.size(); std::vector reversed_targets(n); std::vector reversed_measure_targets; for (size_t k = 0; k < n; k++) { - reversed_targets[k] = target_data.targets[n - k - 1]; + reversed_targets[k] = inst.targets[n - k - 1]; } decompose_mpp_operation( - CircuitInstruction{target_data.gate_type, target_data.args, reversed_targets}, + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, inst.tag}, xs.size(), [&](const CircuitInstruction &inst) { if (inst.gate_type == GateType::M) { @@ -349,22 +349,22 @@ void SparseUnsignedRevFrameTracker::undo_MPP(const CircuitInstruction &target_da for (size_t k = inst.targets.size(); k--;) { reversed_measure_targets.push_back(inst.targets[k]); } - undo_MZ({GateType::M, inst.args, reversed_measure_targets}); + undo_MZ({GateType::M, inst.args, reversed_measure_targets, inst.tag}); } else { undo_gate(inst); } }); } -void SparseUnsignedRevFrameTracker::undo_SPP(const CircuitInstruction &target_data) { - size_t n = target_data.targets.size(); +void SparseUnsignedRevFrameTracker::undo_SPP(const CircuitInstruction &inst) { + size_t n = inst.targets.size(); std::vector reversed_targets(n); std::vector reversed_measure_targets; for (size_t k = 0; k < n; k++) { - reversed_targets[k] = target_data.targets[n - k - 1]; + reversed_targets[k] = inst.targets[n - k - 1]; } decompose_spp_or_spp_dag_operation( - CircuitInstruction{target_data.gate_type, target_data.args, reversed_targets}, + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, inst.tag}, xs.size(), false, [&](const CircuitInstruction &inst) { @@ -445,41 +445,41 @@ void SparseUnsignedRevFrameTracker::undo_MZ(const CircuitInstruction &dat) { void SparseUnsignedRevFrameTracker::undo_MXX_disjoint_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - undo_MX(CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}}); + undo_MX(CircuitInstruction{GateType::MX, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + undo_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); } void SparseUnsignedRevFrameTracker::undo_MYY_disjoint_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - undo_MY(CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}}); + undo_MY(CircuitInstruction{GateType::MY, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + undo_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); } void SparseUnsignedRevFrameTracker::undo_MZZ_disjoint_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); // Record measurement results. for (size_t k = 0; k < inst.targets.size(); k += 2) { - undo_MZ(CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}}); + undo_MZ(CircuitInstruction{GateType::M, inst.args, SpanRef{&inst.targets[k]}, ""}); } // Untransform from single qubit measurements back to 2 qubit measurements. - undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + undo_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); } void SparseUnsignedRevFrameTracker::undo_MXX(const CircuitInstruction &inst) { @@ -491,7 +491,7 @@ void SparseUnsignedRevFrameTracker::undo_MXX(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, ""}, xs.size(), [&](CircuitInstruction segment) { undo_MXX_disjoint_segment(segment); }); } @@ -505,7 +505,7 @@ void SparseUnsignedRevFrameTracker::undo_MYY(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, ""}, xs.size(), [&](CircuitInstruction segment) { undo_MYY_disjoint_segment(segment); }); } @@ -519,7 +519,7 @@ void SparseUnsignedRevFrameTracker::undo_MZZ(const CircuitInstruction &inst) { } decompose_pair_instruction_into_disjoint_segments( - {inst.gate_type, inst.args, reversed_targets}, xs.size(), [&](CircuitInstruction segment) { + CircuitInstruction{inst.gate_type, inst.args, reversed_targets, ""}, xs.size(), [&](CircuitInstruction segment) { undo_MZZ_disjoint_segment(segment); }); } diff --git a/src/stim/simulators/sparse_rev_frame_tracker.test.cc b/src/stim/simulators/sparse_rev_frame_tracker.test.cc index 0101a8ce..58de3477 100644 --- a/src/stim/simulators/sparse_rev_frame_tracker.test.cc +++ b/src/stim/simulators/sparse_rev_frame_tracker.test.cc @@ -166,7 +166,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, fuzz_all_unitary_gates_vs_t for (size_t k = 0; k < n; k++) { targets.push_back(n - k + 1); } - tracker_gate.undo_gate({gate.id, {}, qubit_targets(targets)}); + tracker_gate.undo_gate(CircuitInstruction{gate.id, {}, qubit_targets(targets), ""}); tracker_tableau.undo_tableau(gate.tableau(), targets); EXPECT_EQ(tracker_gate, tracker_tableau) << gate.name; } @@ -177,19 +177,19 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, RX, { SparseUnsignedRevFrameTracker actual(0, 0, 0); actual = _tracker_from_pauli_string("III"); - actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2})}); + actual.undo_RX(CircuitInstruction{GateType::RX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("XXX"); - actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2})}); + actual.undo_RX(CircuitInstruction{GateType::RX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IXI")); actual = _tracker_from_pauli_string("XIZ"); - ASSERT_THROW({ actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIZ")); actual = _tracker_from_pauli_string("YIX"); - ASSERT_THROW({ actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RX({GateType::RX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIX")); }) @@ -197,19 +197,19 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, RY, { SparseUnsignedRevFrameTracker actual(0, 0, 0); actual = _tracker_from_pauli_string("III"); - actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2})}); + actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("YYY"); - actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2})}); + actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IYI")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("XIY"); - ASSERT_THROW({ actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RY({GateType::RY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIY")); }) @@ -217,19 +217,19 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, RZ, { SparseUnsignedRevFrameTracker actual(0, 0, 0); actual = _tracker_from_pauli_string("III"); - actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2})}); + actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("ZZZ"); - actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2})}); + actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IZI")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("ZIX"); - ASSERT_THROW({ actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_RZ({GateType::R, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("ZIX")); }) @@ -238,20 +238,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MX, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); + actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("XXX"); actual.num_measurements_in_past = 2; - actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); + actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("XXX")); actual = _tracker_from_pauli_string("XIZ"); - ASSERT_THROW({ actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIZ")); actual = _tracker_from_pauli_string("YIX"); - ASSERT_THROW({ actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIX")); }) @@ -260,20 +260,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MY, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); + actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("YYY"); actual.num_measurements_in_past = 2; - actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); + actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("YYY")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("XIY"); - ASSERT_THROW({ actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIY")); }) @@ -282,20 +282,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MZ, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); + actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("ZZZ"); actual.num_measurements_in_past = 2; - actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); + actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("ZZZ")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("ZIX"); - ASSERT_THROW({ actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("ZIX")); }) @@ -304,20 +304,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MRX, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); + actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("XXX"); actual.num_measurements_in_past = 2; - actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); + actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IXI")); actual = _tracker_from_pauli_string("XIZ"); - ASSERT_THROW({ actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIZ")); actual = _tracker_from_pauli_string("YIX"); - ASSERT_THROW({ actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIX")); }) @@ -326,20 +326,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MRY, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); + actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("YYY"); actual.num_measurements_in_past = 2; - actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); + actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IYI")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("XIY"); - ASSERT_THROW({ actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("XIY")); }) @@ -348,20 +348,20 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, MRZ, { actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; - actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); + actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("ZZZ"); actual.num_measurements_in_past = 2; - actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); + actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("IZI")); actual = _tracker_from_pauli_string("YIZ"); - ASSERT_THROW({ actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("YIZ")); actual = _tracker_from_pauli_string("ZIX"); - ASSERT_THROW({ actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); }, std::invalid_argument); + ASSERT_THROW({ actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); }, std::invalid_argument); ASSERT_EQ(actual, _tracker_from_pauli_string("ZIX")); }) @@ -371,67 +371,67 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_from_measurement, actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); + actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("XII")); actual = _tracker_from_pauli_string("XII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2})}); + actual.undo_MX({GateType::MX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); + actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("YII")); actual = _tracker_from_pauli_string("YII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2})}); + actual.undo_MY({GateType::MY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); + actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("ZII")); actual = _tracker_from_pauli_string("ZII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2})}); + actual.undo_MZ({GateType::M, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("III")); actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); + actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("XII")); actual = _tracker_from_pauli_string("XII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2})}); + actual.undo_MRX({GateType::MRX, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("XII")); actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); + actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("YII")); actual = _tracker_from_pauli_string("YII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2})}); + actual.undo_MRY({GateType::MRY, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("YII")); actual = _tracker_from_pauli_string("III"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); + actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("ZII")); actual = _tracker_from_pauli_string("ZII"); actual.num_measurements_in_past = 2; actual.rec_bits[0].xor_item(DemTarget::observable_id(0)); - actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2})}); + actual.undo_MRZ({GateType::MR, {}, qubit_targets({0, 2}), ""}); ASSERT_EQ(actual, _tracker_from_pauli_string("ZII")); }) @@ -442,14 +442,14 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("XII"); actual.num_measurements_in_past = 12; - actual.undo_ZCX({GateType::CX, {}, targets}); + actual.undo_ZCX({GateType::CX, {}, targets, ""}); expected = _tracker_from_pauli_string("XII"); expected.num_measurements_in_past = 12; ASSERT_EQ(actual, expected); actual = _tracker_from_pauli_string("YII"); actual.num_measurements_in_past = 12; - actual.undo_ZCX({GateType::CX, {}, targets}); + actual.undo_ZCX({GateType::CX, {}, targets, ""}); expected = _tracker_from_pauli_string("YII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -457,7 +457,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("ZII"); actual.num_measurements_in_past = 12; - actual.undo_ZCX({GateType::CX, {}, targets}); + actual.undo_ZCX({GateType::CX, {}, targets, ""}); expected = _tracker_from_pauli_string("ZII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -465,7 +465,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("XII"); actual.num_measurements_in_past = 12; - actual.undo_ZCY({GateType::CY, {}, targets}); + actual.undo_ZCY({GateType::CY, {}, targets, ""}); expected = _tracker_from_pauli_string("XII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -473,14 +473,14 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("YII"); actual.num_measurements_in_past = 12; - actual.undo_ZCY({GateType::CY, {}, targets}); + actual.undo_ZCY({GateType::CY, {}, targets, ""}); expected = _tracker_from_pauli_string("YII"); expected.num_measurements_in_past = 12; ASSERT_EQ(actual, expected); actual = _tracker_from_pauli_string("ZII"); actual.num_measurements_in_past = 12; - actual.undo_ZCY({GateType::CY, {}, targets}); + actual.undo_ZCY({GateType::CY, {}, targets, ""}); expected = _tracker_from_pauli_string("ZII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -488,7 +488,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("XII"); actual.num_measurements_in_past = 12; - actual.undo_ZCZ({GateType::CZ, {}, targets}); + actual.undo_ZCZ({GateType::CZ, {}, targets, ""}); expected = _tracker_from_pauli_string("XII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -496,7 +496,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("YII"); actual.num_measurements_in_past = 12; - actual.undo_ZCZ({GateType::CZ, {}, targets}); + actual.undo_ZCZ({GateType::CZ, {}, targets, ""}); expected = _tracker_from_pauli_string("YII"); expected.num_measurements_in_past = 12; expected.rec_bits[7].xor_item(DemTarget::observable_id(0)); @@ -504,7 +504,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, actual = _tracker_from_pauli_string("ZII"); actual.num_measurements_in_past = 12; - actual.undo_ZCZ({GateType::CZ, {}, targets}); + actual.undo_ZCZ({GateType::CZ, {}, targets, ""}); expected = _tracker_from_pauli_string("ZII"); expected.num_measurements_in_past = 12; ASSERT_EQ(actual, expected); @@ -594,7 +594,7 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, mpad, { actual = _tracker_from_pauli_string("IIZ"); actual.num_measurements_in_past = 2; actual.rec_bits[1].xor_item(DemTarget::relative_detector_id(5)); - actual.undo_MPAD({GateType::MRY, {}, qubit_targets({0})}); + actual.undo_MPAD({GateType::MRY, {}, qubit_targets({0}), ""}); expected = _tracker_from_pauli_string("IIZ"); expected.num_measurements_in_past = 1; diff --git a/src/stim/simulators/tableau_simulator.h b/src/stim/simulators/tableau_simulator.h index f9c974a9..89a22816 100644 --- a/src/stim/simulators/tableau_simulator.h +++ b/src/stim/simulators/tableau_simulator.h @@ -284,7 +284,7 @@ void perform_pauli_errors_via_correlated_errors( const CircuitInstruction &target_data, RESET_FLAG reset_flag, ELSE_CORR else_corr) { double target_p{}; GateTarget target_t[Q]; - CircuitInstruction data{GateType::E, {&target_p}, {&target_t[0], &target_t[Q]}}; + CircuitInstruction data{GateType::E, {&target_p}, {&target_t[0], &target_t[Q]}, ""}; for (size_t k = 0; k < target_data.targets.size(); k += Q) { reset_flag(); double used_probability = 0; diff --git a/src/stim/simulators/tableau_simulator.inl b/src/stim/simulators/tableau_simulator.inl index 516b4a0a..73cb8a48 100644 --- a/src/stim/simulators/tableau_simulator.inl +++ b/src/stim/simulators/tableau_simulator.inl @@ -89,7 +89,7 @@ void TableauSimulator::postselect_helper( unique_targets_vec.insert(unique_targets_vec.end(), unique_targets.begin(), unique_targets.end()); size_t finished = 0; - do_gate({basis_change_gate, {}, unique_targets_vec}); + do_gate({basis_change_gate, {}, unique_targets_vec, ""}); { uint8_t old_bias = sign_bias; sign_bias = desired_result ? -1 : +1; @@ -104,7 +104,7 @@ void TableauSimulator::postselect_helper( } sign_bias = old_bias; } - do_gate({basis_change_gate, {}, unique_targets_vec}); + do_gate({basis_change_gate, {}, unique_targets_vec, ""}); if (finished < targets.size()) { std::stringstream msg; @@ -231,7 +231,7 @@ void TableauSimulator::do_MX(const CircuitInstruction &target_data) { template void TableauSimulator::do_MXX_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); // Ensure measurement observables are collapsed. collapse_x(inst.targets, 2); @@ -248,13 +248,13 @@ void TableauSimulator::do_MXX_disjoint_controls_segment(const CircuitInstruct noisify_new_measurements(inst.args, inst.targets.size() / 2); // Untransform from single qubit measurements back to 2 qubit measurements. - do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets}); + do_ZCX(CircuitInstruction{GateType::CX, {}, inst.targets, ""}); } template void TableauSimulator::do_MYY_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); // Ensure measurement observables are collapsed. collapse_y(inst.targets, 2); @@ -271,13 +271,13 @@ void TableauSimulator::do_MYY_disjoint_controls_segment(const CircuitInstruct noisify_new_measurements(inst.args, inst.targets.size() / 2); // Untransform from single qubit measurements back to 2 qubit measurements. - do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets}); + do_ZCY(CircuitInstruction{GateType::CY, {}, inst.targets, ""}); } template void TableauSimulator::do_MZZ_disjoint_controls_segment(const CircuitInstruction &inst) { // Transform from 2 qubit measurements to single qubit measurements. - do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); // Ensure measurement observables are collapsed. collapse_z(inst.targets, 2); @@ -294,7 +294,7 @@ void TableauSimulator::do_MZZ_disjoint_controls_segment(const CircuitInstruct noisify_new_measurements(inst.args, inst.targets.size() / 2); // Untransform from single qubit measurements back to 2 qubit measurements. - do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets}); + do_XCZ(CircuitInstruction{GateType::XCZ, {}, inst.targets, ""}); } template @@ -388,7 +388,7 @@ bool TableauSimulator::measure_pauli_string(const PauliStringRef pauli_str measurement_record.record_result(std::bernoulli_distribution(p)(rng)); } else { targets.pop_back(); - do_MPP(CircuitInstruction{GateType::MPP, &p, targets}); + do_MPP(CircuitInstruction{GateType::MPP, &p, targets, ""}); } return measurement_record.lookback(1); } @@ -998,7 +998,7 @@ void TableauSimulator::do_HERALDED_PAULI_CHANNEL_1(const CircuitInstruction & } RareErrorIterator::for_samples(ht, nt, rng, [&](size_t target) { measurement_record.storage[offset + target] = true; - do_PAULI_CHANNEL_1(CircuitInstruction{GateType::PAULI_CHANNEL_1, conditionals, &inst.targets[target]}); + do_PAULI_CHANNEL_1(CircuitInstruction{GateType::PAULI_CHANNEL_1, conditionals, &inst.targets[target], ""}); }); } @@ -1201,14 +1201,14 @@ void TableauSimulator::collapse_x(SpanRef targets, size_t s // Only pay the cost of transposing if collapsing is needed. if (!unique_collapse_targets.empty()) { std::vector collapse_targets(unique_collapse_targets.begin(), unique_collapse_targets.end()); - do_H_XZ({GateType::H, {}, collapse_targets}); + do_H_XZ({GateType::H, {}, collapse_targets, ""}); { TableauTransposedRaii temp_transposed(inv_state); for (auto q : collapse_targets) { collapse_qubit_z(q.data, temp_transposed); } } - do_H_XZ({GateType::H, {}, collapse_targets}); + do_H_XZ({GateType::H, {}, collapse_targets, ""}); } } @@ -1227,14 +1227,14 @@ void TableauSimulator::collapse_y(SpanRef targets, size_t s // Only pay the cost of transposing if collapsing is needed. if (!unique_collapse_targets.empty()) { std::vector collapse_targets(unique_collapse_targets.begin(), unique_collapse_targets.end()); - do_H_YZ({GateType::H_YZ, {}, collapse_targets}); + do_H_YZ({GateType::H_YZ, {}, collapse_targets, ""}); { TableauTransposedRaii temp_transposed(inv_state); for (auto q : collapse_targets) { collapse_qubit_z(q.data, temp_transposed); } } - do_H_YZ({GateType::H_YZ, {}, collapse_targets}); + do_H_YZ({GateType::H_YZ, {}, collapse_targets, ""}); } } @@ -1431,9 +1431,9 @@ std::pair> TableauSimulator::measure_kickback_z(GateTarg template std::pair> TableauSimulator::measure_kickback_y(GateTarget target) { - do_H_YZ({GateType::H, {}, &target}); + do_H_YZ({GateType::H, {}, &target, ""}); auto result = measure_kickback_z(target); - do_H_YZ({GateType::H, {}, &target}); + do_H_YZ({GateType::H, {}, &target, ""}); if (result.second.num_qubits) { // Also conjugate the kickback by H_YZ. result.second.xs[target.qubit_value()] ^= result.second.zs[target.qubit_value()]; @@ -1443,9 +1443,9 @@ std::pair> TableauSimulator::measure_kickback_y(GateTarg template std::pair> TableauSimulator::measure_kickback_x(GateTarget target) { - do_H_XZ({GateType::H, {}, &target}); + do_H_XZ({GateType::H, {}, &target, ""}); auto result = measure_kickback_z(target); - do_H_XZ({GateType::H, {}, &target}); + do_H_XZ({GateType::H, {}, &target, ""}); if (result.second.num_qubits) { // Also conjugate the kickback by H_XZ. result.second.xs[target.qubit_value()].swap_with(result.second.zs[target.qubit_value()]); @@ -1467,7 +1467,7 @@ int8_t TableauSimulator::peek_observable_expectation(const PauliString &ob state.ensure_large_enough_for_qubits(n + 1); GateTarget anc{n}; if (observable.sign) { - state.do_X({GateType::X, {}, &anc}); + state.do_X({GateType::X, {}, &anc, ""}); } observable.ref().for_each_active_pauli([&](size_t q) { int p = observable.xs[q] + (observable.zs[q] << 1); @@ -1478,14 +1478,14 @@ int8_t TableauSimulator::peek_observable_expectation(const PauliString &ob } else if (p == 3) { c2_type = GateType::YCX; } - state.do_gate({c2_type, {}, targets}); + state.do_gate({c2_type, {}, targets, ""}); }); // Use simulator features to determines if the measurement is deterministic. if (!state.is_deterministic_z(anc.data)) { return 0; } - state.do_MZ({GateType::M, {}, &anc}); + state.do_MZ({GateType::M, {}, &anc, ""}); return state.measurement_record.storage.back() ? -1 : +1; } diff --git a/src/stim/simulators/tableau_simulator.perf.cc b/src/stim/simulators/tableau_simulator.perf.cc index 9534be30..b345fa50 100644 --- a/src/stim/simulators/tableau_simulator.perf.cc +++ b/src/stim/simulators/tableau_simulator.perf.cc @@ -27,7 +27,7 @@ BENCHMARK(TableauSimulator_CX_10Kqubits) { for (uint32_t k = 0; k < (uint32_t)num_qubits; k++) { targets.push_back(GateTarget{k}); } - CircuitInstruction op_data{GateType::CX, {}, targets}; + CircuitInstruction op_data{GateType::CX, {}, targets, ""}; benchmark_go([&]() { sim.do_ZCX(op_data); diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc index 61061d7c..db99db54 100644 --- a/src/stim/simulators/tableau_simulator.pybind.cc +++ b/src/stim/simulators/tableau_simulator.pybind.cc @@ -108,7 +108,7 @@ PyCircuitInstruction build_single_qubit_gate_instruction_ensure_size( // Note: quadratic behavior. self.ensure_large_enough_for_qubits(max_q + 1); - return PyCircuitInstruction(gate_type, targets, gate_args_vec); + return PyCircuitInstruction(gate_type, targets, gate_args_vec, ""); } template @@ -1518,7 +1518,7 @@ void stim_pybind::pybind_tableau_simulator_methods( [](TableauSimulator &self, uint32_t target) { self.ensure_large_enough_for_qubits(target + 1); GateTarget g{target}; - self.do_MZ(CircuitInstruction{GateType::M, {}, &g}); + self.do_MZ(CircuitInstruction{GateType::M, {}, &g, ""}); return (bool)self.measurement_record.storage.back(); }, pybind11::arg("target"), diff --git a/src/stim/simulators/tableau_simulator.test.cc b/src/stim/simulators/tableau_simulator.test.cc index 2f256df6..219329be 100644 --- a/src/stim/simulators/tableau_simulator.test.cc +++ b/src/stim/simulators/tableau_simulator.test.cc @@ -37,16 +37,16 @@ struct OpDat { OpDat(std::vector u) : targets(qubit_targets(u)) { } operator CircuitInstruction() const { - return {(GateType)0, {}, targets}; + return {(GateType)0, {}, targets, ""}; } }; TEST_EACH_WORD_SIZE_W(TableauSimulator, identity, { auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 1); ASSERT_EQ(s.measurement_record.storage, (std::vector{})); - s.do_MZ({GateType::Z, {}, qubit_targets({0})}); + s.do_MZ({GateType::Z, {}, qubit_targets({0}), ""}); ASSERT_EQ(s.measurement_record.storage, (std::vector{false})); - s.do_MZ({GateType::Z, {}, qubit_targets({0 | TARGET_INVERTED_BIT})}); + s.do_MZ({GateType::Z, {}, qubit_targets({0 | TARGET_INVERTED_BIT}), ""}); ASSERT_EQ(s.measurement_record.storage, (std::vector{false, true})); }) @@ -318,10 +318,10 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, unitary_gates_consistent_with_tableau_da const auto &inverse_op_tableau = gate.inverse().tableau(); if (inverse_op_tableau.num_qubits == 2) { - sim.do_gate({gate.id, {}, qubit_targets({7, 4})}); + sim.do_gate({gate.id, {}, qubit_targets({7, 4}), ""}); t.inplace_scatter_prepend(inverse_op_tableau, {7, 4}); } else { - sim.do_gate({gate.id, {}, qubit_targets({5})}); + sim.do_gate({gate.id, {}, qubit_targets({5}), ""}); t.inplace_scatter_prepend(inverse_op_tableau, {5}); } EXPECT_EQ(sim.inv_state, t) << gate.name; @@ -334,8 +334,8 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, certain_errors_consistent_with_gates, { GateTarget targets[]{GateTarget{0}}; double p0 = 0.0; double p1 = 1.0; - CircuitInstruction d0{(GateType)0, {&p0}, {targets}}; - CircuitInstruction d1{(GateType)0, {&p1}, {targets}}; + CircuitInstruction d0{(GateType)0, {&p0}, {targets}, ""}; + CircuitInstruction d1{(GateType)0, {&p1}, {targets}, ""}; sim1.do_X_ERROR(d1); sim2.do_X(d0); @@ -410,7 +410,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_vector_sim, { sim_vec = sim_tab.to_vector_sim(); ASSERT_TRUE(sim_tab.to_vector_sim().approximate_equals(sim_vec, true)); - sim_tab.do_gate({GateType::XCX, {}, qubit_targets({4, 7})}); + sim_tab.do_gate({GateType::XCX, {}, qubit_targets({4, 7}), ""}); sim_vec.apply(GateType::XCX, 4, 7); ASSERT_TRUE(sim_tab.to_vector_sim().approximate_equals(sim_vec, true)); }) diff --git a/src/stim/stabilizers/pauli_string_ref.inl b/src/stim/stabilizers/pauli_string_ref.inl index 7fcd5f65..49bcd14c 100644 --- a/src/stim/stabilizers/pauli_string_ref.inl +++ b/src/stim/stabilizers/pauli_string_ref.inl @@ -602,7 +602,7 @@ void PauliStringRef::undo_instruction(const CircuitInstruction &inst) { buf_targets.insert(buf_targets.end(), inst.targets.begin(), inst.targets.end()); std::reverse(buf_targets.begin(), buf_targets.end()); decompose_spp_or_spp_dag_operation( - CircuitInstruction{inst.gate_type, {}, buf_targets}, + CircuitInstruction{inst.gate_type, {}, buf_targets, inst.tag}, num_qubits, false, [&](CircuitInstruction sub_inst) { diff --git a/src/stim/stabilizers/pauli_string_ref.test.cc b/src/stim/stabilizers/pauli_string_ref.test.cc index a51ad40e..d5619ecb 100644 --- a/src/stim/stabilizers/pauli_string_ref.test.cc +++ b/src/stim/stabilizers/pauli_string_ref.test.cc @@ -31,7 +31,7 @@ void check_pauli_string_do_instruction_agrees_with_tableau_sim(Gate gate, Tablea GateTarget::qubit(8), GateTarget::qubit(5), }; - CircuitInstruction inst{gate.id, {}, targets}; + CircuitInstruction inst{gate.id, {}, targets, ""}; std::vector> before; for (size_t k = 0; k < 16; k++) { diff --git a/src/stim/util_top/circuit_flow_generators.inl b/src/stim/util_top/circuit_flow_generators.inl index de8b45df..129c41e8 100644 --- a/src/stim/util_top/circuit_flow_generators.inl +++ b/src/stim/util_top/circuit_flow_generators.inl @@ -206,7 +206,7 @@ void CircuitFlowGeneratorSolver::undo_feedback_capable_instruction(CircuitIns xor_item_into_sorted_vec(t, table[r].measurements); } } else if (f1 && f2) { - CircuitInstruction sub_inst = CircuitInstruction{inst.gate_type, {}, inst.targets.sub(k, k + 2)}; + CircuitInstruction sub_inst = CircuitInstruction{inst.gate_type, {}, inst.targets.sub(k, k + 2), inst.tag}; for (auto &row : table) { row.input.ref().undo_instruction(sub_inst); } @@ -347,7 +347,7 @@ void CircuitFlowGeneratorSolver::undo_instruction(CircuitInstruction inst) { buf_targets.insert(buf_targets.end(), inst.targets.begin(), inst.targets.end()); std::reverse(buf_targets.begin(), buf_targets.end()); decompose_mpp_operation( - CircuitInstruction{inst.gate_type, {}, buf_targets}, num_qubits, [&](CircuitInstruction sub_inst) { + CircuitInstruction{inst.gate_type, {}, buf_targets, inst.tag}, num_qubits, [&](CircuitInstruction sub_inst) { undo_instruction(sub_inst); }); break; diff --git a/src/stim/util_top/circuit_inverse_qec.cc b/src/stim/util_top/circuit_inverse_qec.cc index 83d4acc5..ec94d8f2 100644 --- a/src/stim/util_top/circuit_inverse_qec.cc +++ b/src/stim/util_top/circuit_inverse_qec.cc @@ -48,7 +48,7 @@ void CircuitFlowReverser::do_rp_mrp_instruction(const CircuitInstruction &inst) // Undo the gate, ignoring measurement noise. rev.undo_gate(segment); - inverted_circuit.safe_append_reversed_targets(g.best_candidate_inverse_id, segment.targets, {}, false); + inverted_circuit.safe_append_reversed_targets(CircuitInstruction(g.best_candidate_inverse_id, {}, segment.targets, inst.tag), false); // Measurement noise becomes noise-after-reset in the reversed circuit. if (!inst.args.empty()) { @@ -62,7 +62,7 @@ void CircuitFlowReverser::do_rp_mrp_instruction(const CircuitInstruction &inst) } else { throw std::invalid_argument("Don't know how to invert " + inst.str()); } - inverted_circuit.safe_append_reversed_targets(ejected_noise, segment.targets, segment.args, false); + inverted_circuit.safe_append_reversed_targets(CircuitInstruction(ejected_noise, segment.args, segment.targets, inst.tag), false); } }); } @@ -87,7 +87,7 @@ void CircuitFlowReverser::do_m2r_instruction(const CircuitInstruction &inst) { if (!dont_turn_measurements_into_resets && rev.xs[q].empty() && rev.zs[q].empty() && rev.rec_bits.contains(rev.num_measurements_in_past - 1) && inst.args.empty()) { // Noiseless measurements with past-dependence and no future-dependence become resets. - inverted_circuit.safe_append(reset, &t, inst.args); + inverted_circuit.safe_append(CircuitInstruction(reset, inst.args, &t, inst.tag)); } else { // Measurements that aren't turned into resets need to be re-indexed. auto f = rev.rec_bits.find(rev.num_measurements_in_past - 1); @@ -97,10 +97,10 @@ void CircuitFlowReverser::do_m2r_instruction(const CircuitInstruction &inst) { } } num_new_measurements++; - inverted_circuit.safe_append(g.best_candidate_inverse_id, &t, inst.args); + inverted_circuit.safe_append(CircuitInstruction(g.best_candidate_inverse_id, inst.args, &t, inst.tag)); } - rev.undo_gate(CircuitInstruction{g.id, {}, &t}); + rev.undo_gate(CircuitInstruction{g.id, {}, &t, inst.tag}); } } @@ -119,7 +119,7 @@ void CircuitFlowReverser::do_measuring_instruction(const CircuitInstruction &ins num_new_measurements++; } inverted_circuit.safe_append_reversed_targets( - g.best_candidate_inverse_id, inst.targets, inst.args, g.flags & GATE_TARGETS_PAIRS); + CircuitInstruction(g.best_candidate_inverse_id, inst.args, inst.targets, inst.tag), g.flags & GATE_TARGETS_PAIRS); rev.undo_gate(inst); } @@ -137,7 +137,7 @@ void CircuitFlowReverser::do_simple_instruction(const CircuitInstruction &inst) Gate g = GATE_DATA[inst.gate_type]; rev.undo_gate(inst); inverted_circuit.safe_append_reversed_targets( - g.best_candidate_inverse_id, inst.targets, inst.args, g.flags & GATE_TARGETS_PAIRS); + CircuitInstruction(g.best_candidate_inverse_id, inst.args, inst.targets, inst.tag), g.flags & GATE_TARGETS_PAIRS); } void CircuitFlowReverser::flush_detectors_and_observables() { @@ -165,12 +165,13 @@ void CircuitFlowReverser::flush_detectors_and_observables() { for (auto e : d.second) { buf.push_back(GateTarget::rec((int32_t)e - (int32_t)num_new_measurements)); } - inverted_circuit.safe_append(out_gate, buf, out_args); + inverted_circuit.safe_append(CircuitInstruction(out_gate, out_args, buf, d2tag[d.first])); terms_to_erase.push_back(d.first); } for (auto e : terms_to_erase) { d2coords.erase(e); d2ms.erase(e); + d2tag.erase(e); } } @@ -178,6 +179,7 @@ void CircuitFlowReverser::do_instruction(const CircuitInstruction &inst) { switch (inst.gate_type) { case GateType::DETECTOR: { rev.undo_gate(inst); + d2tag[DemTarget::relative_detector_id(rev.num_detectors_in_past)] = inst.tag; auto &v = d2coords[DemTarget::relative_detector_id(rev.num_detectors_in_past)]; for (size_t k = 0; k < inst.args.size(); k++) { v.push_back(inst.args[k] + (k < coord_shifts.size() ? coord_shifts[k] : 0)); @@ -186,6 +188,7 @@ void CircuitFlowReverser::do_instruction(const CircuitInstruction &inst) { } case GateType::OBSERVABLE_INCLUDE: rev.undo_gate(inst); + d2tag[DemTarget::observable_id((uint64_t)inst.args[0])] = inst.tag; break; case GateType::TICK: case GateType::I: @@ -271,10 +274,11 @@ void CircuitFlowReverser::do_instruction(const CircuitInstruction &inst) { qubit_coords_circuit.arg_buf.append_tail( inst.args[k] + (k < coord_shifts.size() ? coord_shifts[k] : 0)); } - qubit_coords_circuit.operations.push_back({ + qubit_coords_circuit.operations.push_back(CircuitInstruction{ inst.gate_type, qubit_coords_circuit.arg_buf.commit_tail(), qubit_coords_circuit.target_buf.take_copy(inst.targets), + inst.tag, }); break; case GateType::SHIFT_COORDS: diff --git a/src/stim/util_top/circuit_inverse_qec.h b/src/stim/util_top/circuit_inverse_qec.h index 3bdeb1c6..579e620a 100644 --- a/src/stim/util_top/circuit_inverse_qec.h +++ b/src/stim/util_top/circuit_inverse_qec.h @@ -21,6 +21,7 @@ struct CircuitFlowReverser { size_t num_new_measurements; Circuit inverted_circuit; + std::map d2tag; std::map> d2coords; std::vector coord_buf; std::vector coord_shifts; diff --git a/src/stim/util_top/circuit_inverse_unitary.cc b/src/stim/util_top/circuit_inverse_unitary.cc index f771989c..3da615e7 100644 --- a/src/stim/util_top/circuit_inverse_unitary.cc +++ b/src/stim/util_top/circuit_inverse_unitary.cc @@ -13,7 +13,7 @@ Circuit stim::circuit_inverse_unitary(const Circuit &unitary_circuit) { auto s = op.targets.ptr_start; const auto &inv_gate = gate_data.inverse(); for (size_t k = op.targets.size(); k > 0; k -= step) { - inverted.safe_append(inv_gate.id, {s + k - step, s + k}, op.args); + inverted.safe_append(CircuitInstruction(inv_gate.id, op.args, {s + k - step, s + k}, op.tag)); } }); return inverted; diff --git a/src/stim/util_top/circuit_vs_amplitudes.cc b/src/stim/util_top/circuit_vs_amplitudes.cc index 124c3357..45721c91 100644 --- a/src/stim/util_top/circuit_vs_amplitudes.cc +++ b/src/stim/util_top/circuit_vs_amplitudes.cc @@ -46,22 +46,24 @@ Circuit stim::stabilizer_state_vector_to_circuit( Circuit recorded; auto apply = [&](GateType gate_type, uint32_t target) { sim.apply(gate_type, target); - recorded.safe_append( + recorded.safe_append(CircuitInstruction( gate_type, + {}, std::vector{ GateTarget::qubit(little_endian ? target : (num_qubits - target - 1)), }, - {}); + "")); }; auto apply2 = [&](GateType gate_type, uint32_t target, uint32_t target2) { sim.apply(gate_type, target, target2); - recorded.safe_append( + recorded.safe_append(CircuitInstruction( gate_type, + {}, std::vector{ GateTarget::qubit(little_endian ? target : (num_qubits - target - 1)), GateTarget::qubit(little_endian ? target2 : (num_qubits - target2 - 1)), }, - {}); + "")); }; // Move biggest amplitude to start of state vector. diff --git a/src/stim/util_top/circuit_vs_tableau.inl b/src/stim/util_top/circuit_vs_tableau.inl index 63f688a0..614951e1 100644 --- a/src/stim/util_top/circuit_vs_tableau.inl +++ b/src/stim/util_top/circuit_vs_tableau.inl @@ -97,7 +97,7 @@ Circuit tableau_to_circuit_mpp_method(const Tableau &tableau, bool skip_sign) } assert(!targets.empty()); targets.pop_back(); - result.safe_append(GateType::MPP, targets, {}); + result.safe_append(CircuitInstruction(GateType::MPP, {}, targets, "")); targets.clear(); } @@ -120,13 +120,13 @@ Circuit tableau_to_circuit_mpp_method(const Tableau &tableau, bool skip_sign) } } if (!targets_x.empty()) { - result.safe_append(GateType::CX, targets_x, {}); + result.safe_append(CircuitInstruction(GateType::CX, {}, targets_x, "")); } if (!targets_y.empty()) { - result.safe_append(GateType::CY, targets_y, {}); + result.safe_append(CircuitInstruction(GateType::CY, {}, targets_y, "")); } if (!targets_z.empty()) { - result.safe_append(GateType::CZ, targets_z, {}); + result.safe_append(CircuitInstruction(GateType::CZ, {}, targets_z, "")); } } @@ -139,12 +139,12 @@ Circuit tableau_to_circuit_elimination_method(const Tableau &tableau) { Circuit recorded_circuit; auto apply = [&](GateType gate_type, uint32_t target) { remaining.inplace_scatter_append(GATE_DATA[gate_type].tableau(), {target}); - recorded_circuit.safe_append(gate_type, std::vector{GateTarget::qubit(target)}, {}); + recorded_circuit.safe_append(CircuitInstruction(gate_type, {}, std::vector{GateTarget::qubit(target)}, "")); }; auto apply2 = [&](GateType gate_type, uint32_t target, uint32_t target2) { remaining.inplace_scatter_append(GATE_DATA[gate_type].tableau(), {target, target2}); recorded_circuit.safe_append( - gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); + CircuitInstruction(gate_type, {}, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, "")); }; auto x_out = [&](size_t inp, size_t out) { const auto &p = remaining.xs[inp]; diff --git a/src/stim/util_top/count_determined_measurements.inl b/src/stim/util_top/count_determined_measurements.inl index b7d7f587..9e770270 100644 --- a/src/stim/util_top/count_determined_measurements.inl +++ b/src/stim/util_top/count_determined_measurements.inl @@ -22,7 +22,7 @@ uint64_t count_determined_measurements(const Circuit &circuit) { for (const auto &t : inst.targets) { assert(t.is_qubit_target()); result += sim.peek_z(t.qubit_value()) != 0; - sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}}); + sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}, ""}); } break; } @@ -33,7 +33,7 @@ uint64_t count_determined_measurements(const Circuit &circuit) { for (const auto &t : inst.targets) { assert(t.is_qubit_target()); result += sim.peek_x(t.qubit_value()) != 0; - sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}}); + sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}, ""}); } break; } @@ -44,7 +44,7 @@ uint64_t count_determined_measurements(const Circuit &circuit) { for (const auto &t : inst.targets) { assert(t.is_qubit_target()); result += sim.peek_y(t.qubit_value()) != 0; - sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}}); + sim.do_gate(CircuitInstruction{inst.gate_type, {}, {&t}, ""}); } break; } @@ -68,7 +68,7 @@ uint64_t count_determined_measurements(const Circuit &circuit) { obs_buffer.xs[q1] = 0; obs_buffer.zs[q0] = 0; obs_buffer.zs[q1] = 0; - sim.do_gate(CircuitInstruction{inst.gate_type, {}, inst.targets.sub(k, k + 2)}); + sim.do_gate(CircuitInstruction{inst.gate_type, {}, inst.targets.sub(k, k + 2), ""}); } break; } @@ -90,7 +90,7 @@ uint64_t count_determined_measurements(const Circuit &circuit) { obs_buffer.xs.clear(); obs_buffer.zs.clear(); - sim.do_gate({inst.gate_type, {}, inst.targets.sub(start, end)}); + sim.do_gate({inst.gate_type, {}, inst.targets.sub(start, end), ""}); start = end; } break; diff --git a/src/stim/util_top/export_quirk_url.cc b/src/stim/util_top/export_quirk_url.cc index 703ac952..4514c6a3 100644 --- a/src/stim/util_top/export_quirk_url.cc +++ b/src/stim/util_top/export_quirk_url.cc @@ -11,11 +11,11 @@ static void for_each_target_group( return for_each_combined_targets_group(instruction, callback); } else if (g.flags & GATE_TARGETS_PAIRS) { for (size_t k = 0; k < instruction.targets.size(); k += 2) { - callback({instruction.gate_type, instruction.args, instruction.targets.sub(k, k + 2)}); + callback({instruction.gate_type, instruction.args, instruction.targets.sub(k, k + 2), instruction.tag}); } } else if (g.flags & GATE_IS_SINGLE_QUBIT_GATE) { for (GateTarget t : instruction.targets) { - callback({instruction.gate_type, instruction.args, &t}); + callback({instruction.gate_type, instruction.args, &t, instruction.tag}); } } else { callback(instruction); diff --git a/src/stim/util_top/has_flow.inl b/src/stim/util_top/has_flow.inl index b106bdd5..1dfff0f8 100644 --- a/src/stim/util_top/has_flow.inl +++ b/src/stim/util_top/has_flow.inl @@ -62,7 +62,7 @@ bool _sample_if_noiseless_circuit_has_stabilizer_flow( for (int32_t m : flow.measurements) { std::array targets{ measurement_index_to_target(m, num_measurements, flow), GateTarget::qubit(num_qubits)}; - augmented_circuit.safe_append(GateType::CX, targets, {}); + augmented_circuit.safe_append(CircuitInstruction(GateType::CX, {}, targets, "")); } augmented_circuit.safe_append_u("M", {num_qubits}, {}); @@ -115,7 +115,7 @@ std::vector check_if_circuit_has_unsigned_stabilizer_flows( for (int32_t m : flow.measurements) { targets.push_back(measurement_index_to_target(m, stats.num_measurements, flow)); } - rev.undo_DETECTOR(CircuitInstruction{GateType::DETECTOR, {}, targets}); + rev.undo_DETECTOR(CircuitInstruction{GateType::DETECTOR, {}, targets, ""}); } // Undo the circuit. diff --git a/src/stim/util_top/simplified_circuit.cc b/src/stim/util_top/simplified_circuit.cc index bd2b1d89..64088690 100644 --- a/src/stim/util_top/simplified_circuit.cc +++ b/src/stim/util_top/simplified_circuit.cc @@ -19,7 +19,7 @@ struct Simplifier { : num_qubits(num_qubits), yield(init_yield), used(num_qubits) { } - void do_xcz(SpanRef targets) { + void do_xcz(SpanRef targets, std::string_view tag) { if (targets.empty()) { return; } @@ -29,7 +29,7 @@ struct Simplifier { qs_buf.push_back(targets[k + 1]); qs_buf.push_back(targets[k]); } - yield(CircuitInstruction{GateType::CX, {}, qs_buf}); + yield(CircuitInstruction{GateType::CX, {}, qs_buf, tag}); } void simplify_potentially_overlapping_1q_instruction(const CircuitInstruction &inst) { @@ -39,7 +39,7 @@ struct Simplifier { for (size_t k = 0; k < inst.targets.size(); k++) { auto t = inst.targets[k]; if (t.has_qubit_value() && used[t.qubit_value()]) { - CircuitInstruction disjoint = CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, k)}; + CircuitInstruction disjoint = CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, k), inst.tag}; simplify_disjoint_1q_instruction(disjoint); used.clear(); start = k; @@ -49,7 +49,7 @@ struct Simplifier { } } simplify_disjoint_1q_instruction( - CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, inst.targets.size())}); + CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, inst.targets.size()), inst.tag}); } void simplify_potentially_overlapping_2q_instruction(const CircuitInstruction &inst) { @@ -60,7 +60,7 @@ struct Simplifier { auto a = inst.targets[k]; auto b = inst.targets[k + 1]; if ((a.has_qubit_value() && used[a.qubit_value()]) || (b.has_qubit_value() && used[b.qubit_value()])) { - CircuitInstruction disjoint = CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, k)}; + CircuitInstruction disjoint = CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, k), inst.tag}; simplify_disjoint_2q_instruction(disjoint); used.clear(); start = k; @@ -73,7 +73,7 @@ struct Simplifier { } } simplify_disjoint_2q_instruction( - CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, inst.targets.size())}); + CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, inst.targets.size()), inst.tag}); } void simplify_disjoint_1q_instruction(const CircuitInstruction &inst) { @@ -84,129 +84,129 @@ struct Simplifier { // Do nothing. break; case GateType::X: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::Y: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::Z: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::C_XYZ: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::C_ZYX: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::H: - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::H_XY: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::H_YZ: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::S: - yield({GateType::S, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::SQRT_X: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::SQRT_X_DAG: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::SQRT_Y: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::SQRT_Y_DAG: - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::S_DAG: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::MX: - yield({GateType::H, {}, ts}); - yield({GateType::M, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::M, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::MY: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::M, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::M, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::M: - yield({GateType::M, {}, ts}); + yield({GateType::M, {}, ts, inst.tag}); break; case GateType::MRX: - yield({GateType::H, {}, ts}); - yield({GateType::M, {}, ts}); - yield({GateType::R, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::M, {}, ts, inst.tag}); + yield({GateType::R, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::MRY: - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::S, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::M, {}, ts}); - yield({GateType::R, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::M, {}, ts, inst.tag}); + yield({GateType::R, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::MR: - yield({GateType::M, {}, ts}); - yield({GateType::R, {}, ts}); + yield({GateType::M, {}, ts, inst.tag}); + yield({GateType::R, {}, ts, inst.tag}); break; case GateType::RX: - yield({GateType::R, {}, ts}); - yield({GateType::H, {}, ts}); + yield({GateType::R, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); break; case GateType::RY: - yield({GateType::R, {}, ts}); - yield({GateType::H, {}, ts}); - yield({GateType::S, {}, ts}); + yield({GateType::R, {}, ts, inst.tag}); + yield({GateType::H, {}, ts, inst.tag}); + yield({GateType::S, {}, ts, inst.tag}); break; case GateType::R: - yield({GateType::R, {}, ts}); + yield({GateType::R, {}, ts, inst.tag}); break; default: @@ -236,174 +236,174 @@ struct Simplifier { switch (inst.gate_type) { case GateType::CX: - yield({GateType::CX, {}, ts}); + yield({GateType::CX, {}, ts, inst.tag}); break; case GateType::XCZ: - do_xcz(ts); + do_xcz(ts, inst.tag); break; case GateType::XCX: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs1_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); break; case GateType::XCY: - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::S, {}, qs2_buf}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); break; case GateType::YCX: - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); break; case GateType::YCY: - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::YCZ: - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); - do_xcz(ts); - yield({GateType::S, {}, qs1_buf}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + do_xcz(ts, inst.tag); + yield({GateType::S, {}, qs1_buf, inst.tag}); break; case GateType::CY: - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::S, {}, qs2_buf}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); break; case GateType::CZ: - yield({GateType::H, {}, qs2_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); break; case GateType::SQRT_XX: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs_buf, inst.tag}); break; case GateType::SQRT_XX_DAG: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs_buf, inst.tag}); break; case GateType::SQRT_YY: - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::SQRT_YY_DAG: - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs1_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::H, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs1_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::H, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); break; case GateType::SQRT_ZZ: - yield({GateType::H, {}, qs2_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::SQRT_ZZ_DAG: - yield({GateType::H, {}, qs2_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::SWAP: - yield({GateType::CX, {}, ts}); - do_xcz(ts); - yield({GateType::CX, {}, ts}); + yield({GateType::CX, {}, ts, inst.tag}); + do_xcz(ts, inst.tag); + yield({GateType::CX, {}, ts, inst.tag}); break; case GateType::ISWAP: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - do_xcz(ts); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + do_xcz(ts, inst.tag); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::ISWAP_DAG: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - do_xcz(ts); - yield({GateType::H, {}, qs2_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + do_xcz(ts, inst.tag); + yield({GateType::H, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::CXSWAP: - do_xcz(ts); - yield({GateType::CX, {}, ts}); + do_xcz(ts, inst.tag); + yield({GateType::CX, {}, ts, inst.tag}); break; case GateType::SWAPCX: - yield({GateType::CX, {}, ts}); - do_xcz(ts); + yield({GateType::CX, {}, ts, inst.tag}); + do_xcz(ts, inst.tag); break; case GateType::CZSWAP: - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - do_xcz(ts); - yield({GateType::H, {}, qs2_buf}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + do_xcz(ts, inst.tag); + yield({GateType::H, {}, qs2_buf, inst.tag}); break; case GateType::MXX: - yield({GateType::CX, {}, ts}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::M, {}, qs1_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::M, {}, qs1_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); break; case GateType::MYY: - yield({GateType::S, {}, qs_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::S, {}, qs2_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::M, {}, qs1_buf}); - yield({GateType::H, {}, qs1_buf}); - yield({GateType::CX, {}, ts}); - yield({GateType::S, {}, qs_buf}); + yield({GateType::S, {}, qs_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::S, {}, qs2_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::M, {}, qs1_buf, inst.tag}); + yield({GateType::H, {}, qs1_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::S, {}, qs_buf, inst.tag}); break; case GateType::MZZ: - yield({GateType::CX, {}, ts}); - yield({GateType::M, {}, qs2_buf}); - yield({GateType::CX, {}, ts}); + yield({GateType::CX, {}, ts, inst.tag}); + yield({GateType::M, {}, qs2_buf, inst.tag}); + yield({GateType::CX, {}, ts, inst.tag}); break; default: @@ -477,7 +477,7 @@ Circuit stim::simplified_circuit(const Circuit &circuit) { for (auto inst : circuit.operations) { if (inst.gate_type == GateType::REPEAT) { output.append_repeat_block( - inst.repeat_block_rep_count(), simplified_circuit(inst.repeat_block_body(circuit))); + inst.repeat_block_rep_count(), simplified_circuit(inst.repeat_block_body(circuit)), inst.tag); } else { simplifier.simplify_instruction(inst); } diff --git a/src/stim/util_top/simplified_circuit.test.cc b/src/stim/util_top/simplified_circuit.test.cc index 831c0fc1..88a7a346 100644 --- a/src/stim/util_top/simplified_circuit.test.cc +++ b/src/stim/util_top/simplified_circuit.test.cc @@ -26,7 +26,7 @@ bool is_simplification_correct(const Gate &gate) { } Circuit original; - original.safe_append(gate.id, gate_decomposition_help_targets_for_gate_type(gate.id), args); + original.safe_append(CircuitInstruction(gate.id, args, gate_decomposition_help_targets_for_gate_type(gate.id), "")); Circuit simplified = simplified_circuit(original); if (gate.h_s_cx_m_r_decomposition == nullptr) { diff --git a/src/stim/util_top/stabilizers_to_tableau.inl b/src/stim/util_top/stabilizers_to_tableau.inl index c1751c2e..5abb7620 100644 --- a/src/stim/util_top/stabilizers_to_tableau.inl +++ b/src/stim/util_top/stabilizers_to_tableau.inl @@ -49,8 +49,8 @@ Tableau stabilizers_to_tableau( target = target.ref().after(elimination_instructions); if (num_qubits > 0) { GateTarget t = GateTarget::qubit(num_qubits - 1); - elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t}); - elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t}); + elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t, ""}); + elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t, ""}); } Tableau inverse = circuit_to_tableau(elimination_instructions, false, false, false, true); target.ref().for_each_active_pauli([&](size_t q) { @@ -113,7 +113,7 @@ Tableau stabilizers_to_tableau( if (buf_xs[pivot][k]) { GateType g = buf_zs[pivot][k] ? GateType::H_YZ : GateType::H; GateTarget t = GateTarget::qubit(pivot); - CircuitInstruction instruction{g, {}, &t}; + CircuitInstruction instruction{g, {}, &t, ""}; elimination_instructions.safe_append(instruction); size_t q = pivot; simd_bits_range_ref xs1 = buf_xs[q]; @@ -143,7 +143,7 @@ Tableau stabilizers_to_tableau( if (p && q != pivot) { std::array targets{GateTarget::qubit(pivot), GateTarget::qubit(q)}; GateType g = p == 1 ? GateType::XCX : p == 2 ? GateType::XCZ : GateType::XCY; - CircuitInstruction instruction{g, {}, targets}; + CircuitInstruction instruction{g, {}, targets, ""}; elimination_instructions.safe_append(instruction); size_t q1 = targets[0].qubit_value(); size_t q2 = targets[1].qubit_value(); @@ -185,7 +185,7 @@ Tableau stabilizers_to_tableau( // Move pivot to diagonal. if (pivot != used) { std::array targets{GateTarget::qubit(pivot), GateTarget::qubit(used)}; - CircuitInstruction instruction{GateType::SWAP, {}, targets}; + CircuitInstruction instruction{GateType::SWAP, {}, targets, ""}; elimination_instructions.safe_append(instruction); buf_xs[pivot].swap_with(buf_xs[used]); buf_zs[pivot].swap_with(buf_zs[used]); @@ -194,7 +194,7 @@ Tableau stabilizers_to_tableau( // Fix sign. if (buf_signs[k]) { GateTarget t = GateTarget::qubit(used); - CircuitInstruction instruction{GateType::X, {}, &t}; + CircuitInstruction instruction{GateType::X, {}, &t, ""}; elimination_instructions.safe_append(instruction); buf_signs ^= buf_zs[used]; } @@ -213,8 +213,8 @@ Tableau stabilizers_to_tableau( if (num_qubits > 0) { // Force size of resulting tableau to be correct. GateTarget t = GateTarget::qubit(num_qubits - 1); - elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t}); - elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t}); + elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t, ""}); + elimination_instructions.safe_append(CircuitInstruction{GateType::X, {}, &t, ""}); } if (invert) { diff --git a/src/stim/util_top/stabilizers_vs_amplitudes.inl b/src/stim/util_top/stabilizers_vs_amplitudes.inl index a85cf22f..86d6b4ed 100644 --- a/src/stim/util_top/stabilizers_vs_amplitudes.inl +++ b/src/stim/util_top/stabilizers_vs_amplitudes.inl @@ -61,12 +61,12 @@ Tableau unitary_to_tableau(const std::vector> auto apply = [&](GateType gate_type, uint32_t target) { sim.apply(gate_type, target); - recorded_circuit.safe_append(gate_type, std::vector{GateTarget::qubit(target)}, {}); + recorded_circuit.safe_append(CircuitInstruction(gate_type, {}, std::vector{GateTarget::qubit(target)}, "")); }; auto apply2 = [&](GateType gate_type, uint32_t target, uint32_t target2) { sim.apply(gate_type, target, target2); recorded_circuit.safe_append( - gate_type, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, {}); + CircuitInstruction(gate_type, {}, std::vector{GateTarget::qubit(target), GateTarget::qubit(target2)}, "")); }; // Undo the permutation and also single-qubit phases. diff --git a/src/stim/util_top/transform_without_feedback.cc b/src/stim/util_top/transform_without_feedback.cc index 21f75218..1b93dd40 100644 --- a/src/stim/util_top/transform_without_feedback.cc +++ b/src/stim/util_top/transform_without_feedback.cc @@ -62,7 +62,7 @@ struct WithoutFeedbackHelper { void undo_feedback_capable_pcp_operation(const CircuitInstruction &op) { for (size_t k = op.targets.size(); k > 0;) { k -= 2; - CircuitInstruction op_piece = {op.gate_type, op.args, {&op.targets[k], &op.targets[k + 2]}}; + CircuitInstruction op_piece = {op.gate_type, op.args, {&op.targets[k], &op.targets[k + 2]}, op.tag}; auto t1 = op.targets[k]; auto t2 = op.targets[k + 1]; auto b1 = t1.is_measurement_record_target(); @@ -92,6 +92,7 @@ struct WithoutFeedbackHelper { op_piece.gate_type, reversed_semi_flattened_output.arg_buf.take_copy(op_piece.args), reversed_semi_flattened_output.target_buf.take_copy(op_piece.targets), + op_piece.tag, }); } tracker.undo_gate(op_piece); @@ -104,6 +105,7 @@ struct WithoutFeedbackHelper { GateType::OBSERVABLE_INCLUDE, reversed_semi_flattened_output.arg_buf.commit_tail(), reversed_semi_flattened_output.target_buf.take_copy(e.second.range()), + op.tag, }); } } @@ -118,7 +120,7 @@ struct WithoutFeedbackHelper { for (size_t rep = 0; rep < reps; rep++) { reversed_semi_flattened_output.clear(); undo_circuit(loop); - tmp.append_repeat_block(1, std::move(reversed_semi_flattened_output)); + tmp.append_repeat_block(1, std::move(reversed_semi_flattened_output), op.tag); } reversed_semi_flattened_output = std::move(tmp); } @@ -151,7 +153,7 @@ struct WithoutFeedbackHelper { tracker.num_measurements_in_past += op.count_measurement_results(); if (op.gate_type == GateType::REPEAT) { - result.append_repeat_block(op.repeat_block_rep_count(), build_output(op.repeat_block_body(reversed))); + result.append_repeat_block(op.repeat_block_rep_count(), build_output(op.repeat_block_body(reversed)), op.tag); continue; } @@ -169,7 +171,7 @@ struct WithoutFeedbackHelper { reversed_semi_flattened_output.target_buf.append_tail( GateTarget::rec((int64_t)m - (int64_t)tracker.num_measurements_in_past)); } - result.safe_append(op.gate_type, reversed_semi_flattened_output.target_buf.tail, op.args); + result.safe_append(CircuitInstruction(op.gate_type, op.args, reversed_semi_flattened_output.target_buf.tail, op.tag)); reversed_semi_flattened_output.target_buf.discard_tail(); continue; @@ -186,16 +188,18 @@ Circuit circuit_with_identical_adjacent_loops_fused(const Circuit &circuit) { Circuit result; Circuit growing_loop; uint64_t loop_reps = 0; + std::string_view loop_tag; auto flush_loop = [&]() { if (loop_reps > 0) { growing_loop = circuit_with_identical_adjacent_loops_fused(growing_loop); if (loop_reps > 1) { - result.append_repeat_block(loop_reps, std::move(growing_loop)); + result.append_repeat_block(loop_reps, std::move(growing_loop), loop_tag); } else if (loop_reps == 1) { result += growing_loop; } } + loop_tag = ""; loop_reps = 0; }; for (const auto &op : circuit.operations) { @@ -215,6 +219,7 @@ Circuit circuit_with_identical_adjacent_loops_fused(const Circuit &circuit) { if (is_loop) { growing_loop = op.repeat_block_body(circuit); loop_reps = op.repeat_block_rep_count(); + loop_tag = op.tag; continue; }