diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index 3b7e34cf..12cf5e62 100644
--- a/doc/python_api_reference_vDev.md
+++ b/doc/python_api_reference_vDev.md
@@ -188,6 +188,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.FlipSimulator.__init__`](#stim.FlipSimulator.__init__)
- [`stim.FlipSimulator.batch_size`](#stim.FlipSimulator.batch_size)
- [`stim.FlipSimulator.broadcast_pauli_errors`](#stim.FlipSimulator.broadcast_pauli_errors)
+ - [`stim.FlipSimulator.clear`](#stim.FlipSimulator.clear)
- [`stim.FlipSimulator.copy`](#stim.FlipSimulator.copy)
- [`stim.FlipSimulator.do`](#stim.FlipSimulator.do)
- [`stim.FlipSimulator.get_detector_flips`](#stim.FlipSimulator.get_detector_flips)
@@ -198,7 +199,6 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.FlipSimulator.num_observables`](#stim.FlipSimulator.num_observables)
- [`stim.FlipSimulator.num_qubits`](#stim.FlipSimulator.num_qubits)
- [`stim.FlipSimulator.peek_pauli_flips`](#stim.FlipSimulator.peek_pauli_flips)
- - [`stim.FlipSimulator.reset`](#stim.FlipSimulator.reset)
- [`stim.FlipSimulator.set_pauli_flip`](#stim.FlipSimulator.set_pauli_flip)
- [`stim.FlippedMeasurement`](#stim.FlippedMeasurement)
- [`stim.FlippedMeasurement.__init__`](#stim.FlippedMeasurement.__init__)
@@ -7343,6 +7343,42 @@ def broadcast_pauli_errors(
"""
```
+
+```python
+# stim.FlipSimulator.clear
+
+# (in class stim.FlipSimulator)
+def clear(
+ self,
+) -> None:
+ """Clears the simulator's state, so it can be reused for another simulation.
+
+ This clears the measurement flip history, clears the detector flip history,
+ and zeroes the observable flip state. It also resets all qubits to |0>. If
+ stabilizer randomization is disabled, this zeros all pauli flip data. Otherwise
+ it randomizes all pauli flips to be I or Z with equal probability.
+
+ Behind the scenes, this doesn't free memory or resize the simulator. So,
+ repeating the same simulation with calls to `clear` in between will be faster
+ than allocating a new simulator each time (by avoiding re-allocations).
+
+ Examples:
+ >>> import stim
+ >>> sim = stim.FlipSimulator(batch_size=256)
+ >>> sim.do(stim.Circuit("M(0.1) 9"))
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (1, 256)
+
+ >>> sim.clear()
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (0, 256)
+ """
+```
+
```python
# stim.FlipSimulator.copy
@@ -7856,38 +7892,6 @@ def peek_pauli_flips(
"""
```
-
-```python
-# stim.FlipSimulator.reset
-
-# (in class stim.FlipSimulator)
-def reset(
- self,
-) -> None:
- """Resets the simulator's state, so it can be reused for another simulation.
-
- This empties the measurement flip history, empties the detector flip history,
- and zeroes the observable flip state. It also resets all qubits to |0>. If
- stabilizer randomization is disabled, this zeros all pauli flips data. Otherwise
- it randomizes all pauli flips to be I or Z with equal probability.
-
- Examples:
- >>> import stim
- >>> sim = stim.FlipSimulator(batch_size=256)
- >>> sim.do(stim.Circuit("M(0.1) 9"))
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (1, 256)
-
- >>> sim.reset()
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (0, 256)
- """
-```
-
```python
# stim.FlipSimulator.set_pauli_flip
diff --git a/doc/stim.pyi b/doc/stim.pyi
index 2ffe7d47..a71b2598 100644
--- a/doc/stim.pyi
+++ b/doc/stim.pyi
@@ -5723,6 +5723,35 @@ class FlipSimulator:
>>> sim.peek_pauli_flips()
[stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")]
"""
+ def clear(
+ self,
+ ) -> None:
+ """Clears the simulator's state, so it can be reused for another simulation.
+
+ This clears the measurement flip history, clears the detector flip history,
+ and zeroes the observable flip state. It also resets all qubits to |0>. If
+ stabilizer randomization is disabled, this zeros all pauli flip data. Otherwise
+ it randomizes all pauli flips to be I or Z with equal probability.
+
+ Behind the scenes, this doesn't free memory or resize the simulator. So,
+ repeating the same simulation with calls to `clear` in between will be faster
+ than allocating a new simulator each time (by avoiding re-allocations).
+
+ Examples:
+ >>> import stim
+ >>> sim = stim.FlipSimulator(batch_size=256)
+ >>> sim.do(stim.Circuit("M(0.1) 9"))
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (1, 256)
+
+ >>> sim.clear()
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (0, 256)
+ """
def copy(
self,
*,
@@ -6166,31 +6195,6 @@ class FlipSimulator:
>>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization
['+', 'Z', '_']
"""
- def reset(
- self,
- ) -> None:
- """Resets the simulator's state, so it can be reused for another simulation.
-
- This empties the measurement flip history, empties the detector flip history,
- and zeroes the observable flip state. It also resets all qubits to |0>. If
- stabilizer randomization is disabled, this zeros all pauli flips data. Otherwise
- it randomizes all pauli flips to be I or Z with equal probability.
-
- Examples:
- >>> import stim
- >>> sim = stim.FlipSimulator(batch_size=256)
- >>> sim.do(stim.Circuit("M(0.1) 9"))
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (1, 256)
-
- >>> sim.reset()
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (0, 256)
- """
def set_pauli_flip(
self,
pauli: Union[str, int],
diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi
index 2ffe7d47..a71b2598 100644
--- a/glue/python/src/stim/__init__.pyi
+++ b/glue/python/src/stim/__init__.pyi
@@ -5723,6 +5723,35 @@ class FlipSimulator:
>>> sim.peek_pauli_flips()
[stim.PauliString("+X_Y"), stim.PauliString("+Z_Y")]
"""
+ def clear(
+ self,
+ ) -> None:
+ """Clears the simulator's state, so it can be reused for another simulation.
+
+ This clears the measurement flip history, clears the detector flip history,
+ and zeroes the observable flip state. It also resets all qubits to |0>. If
+ stabilizer randomization is disabled, this zeros all pauli flip data. Otherwise
+ it randomizes all pauli flips to be I or Z with equal probability.
+
+ Behind the scenes, this doesn't free memory or resize the simulator. So,
+ repeating the same simulation with calls to `clear` in between will be faster
+ than allocating a new simulator each time (by avoiding re-allocations).
+
+ Examples:
+ >>> import stim
+ >>> sim = stim.FlipSimulator(batch_size=256)
+ >>> sim.do(stim.Circuit("M(0.1) 9"))
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (1, 256)
+
+ >>> sim.clear()
+ >>> sim.num_qubits
+ 10
+ >>> sim.get_measurement_flips().shape
+ (0, 256)
+ """
def copy(
self,
*,
@@ -6166,31 +6195,6 @@ class FlipSimulator:
>>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization
['+', 'Z', '_']
"""
- def reset(
- self,
- ) -> None:
- """Resets the simulator's state, so it can be reused for another simulation.
-
- This empties the measurement flip history, empties the detector flip history,
- and zeroes the observable flip state. It also resets all qubits to |0>. If
- stabilizer randomization is disabled, this zeros all pauli flips data. Otherwise
- it randomizes all pauli flips to be I or Z with equal probability.
-
- Examples:
- >>> import stim
- >>> sim = stim.FlipSimulator(batch_size=256)
- >>> sim.do(stim.Circuit("M(0.1) 9"))
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (1, 256)
-
- >>> sim.reset()
- >>> sim.num_qubits
- 10
- >>> sim.get_measurement_flips().shape
- (0, 256)
- """
def set_pauli_flip(
self,
pauli: Union[str, int],
diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc
index ff1a072c..abb686d8 100644
--- a/src/stim/simulators/frame_simulator.pybind.cc
+++ b/src/stim/simulators/frame_simulator.pybind.cc
@@ -956,18 +956,22 @@ void stim_pybind::pybind_frame_simulator_methods(
.data());
c.def(
- "reset",
+ "clear",
[](FrameSimulator &self) {
self.reset_all();
},
clean_doc_string(R"DOC(
- Resets the simulator's state, so it can be reused for another simulation.
+ Clears the simulator's state, so it can be reused for another simulation.
- This empties the measurement flip history, empties the detector flip history,
+ This clears the measurement flip history, clears the detector flip history,
and zeroes the observable flip state. It also resets all qubits to |0>. If
- stabilizer randomization is disabled, this zeros all pauli flips data. Otherwise
+ stabilizer randomization is disabled, this zeros all pauli flip data. Otherwise
it randomizes all pauli flips to be I or Z with equal probability.
+ Behind the scenes, this doesn't free memory or resize the simulator. So,
+ repeating the same simulation with calls to `clear` in between will be faster
+ than allocating a new simulator each time (by avoiding re-allocations).
+
Examples:
>>> import stim
>>> sim = stim.FlipSimulator(batch_size=256)
@@ -977,7 +981,7 @@ void stim_pybind::pybind_frame_simulator_methods(
>>> sim.get_measurement_flips().shape
(1, 256)
- >>> sim.reset()
+ >>> sim.clear()
>>> sim.num_qubits
10
>>> sim.get_measurement_flips().shape
diff --git a/src/stim/util_top/circuit_inverse_qec.cc b/src/stim/util_top/circuit_inverse_qec.cc
index 1fae1717..83d4acc5 100644
--- a/src/stim/util_top/circuit_inverse_qec.cc
+++ b/src/stim/util_top/circuit_inverse_qec.cc
@@ -124,6 +124,15 @@ void CircuitFlowReverser::do_measuring_instruction(const CircuitInstruction &ins
rev.undo_gate(inst);
}
+void CircuitFlowReverser::do_feedback_capable_instruction(const CircuitInstruction &inst) {
+ for (GateTarget t : inst.targets) {
+ if (t.is_measurement_record_target()) {
+ throw std::invalid_argument("Time-reversing feedback isn't supported yet. Found feedback in: " + inst.str());
+ }
+ }
+ do_simple_instruction(inst);
+}
+
void CircuitFlowReverser::do_simple_instruction(const CircuitInstruction &inst) {
Gate g = GATE_DATA[inst.gate_type];
rev.undo_gate(inst);
@@ -207,13 +216,8 @@ void CircuitFlowReverser::do_instruction(const CircuitInstruction &inst) {
case GateType::ISWAP_DAG:
case GateType::XCX:
case GateType::XCY:
- case GateType::XCZ:
case GateType::YCX:
case GateType::YCY:
- case GateType::YCZ:
- case GateType::CX:
- case GateType::CY:
- case GateType::CZ:
case GateType::H:
case GateType::H_XY:
case GateType::H_YZ:
@@ -229,6 +233,13 @@ void CircuitFlowReverser::do_instruction(const CircuitInstruction &inst) {
case GateType::HERALDED_PAULI_CHANNEL_1:
do_simple_instruction(inst);
return;
+ case GateType::XCZ:
+ case GateType::YCZ:
+ case GateType::CX:
+ case GateType::CY:
+ case GateType::CZ:
+ do_feedback_capable_instruction(inst);
+ break;
case GateType::MRX:
case GateType::MRY:
case GateType::MR:
diff --git a/src/stim/util_top/circuit_inverse_qec.h b/src/stim/util_top/circuit_inverse_qec.h
index 7ceaf147..3bdeb1c6 100644
--- a/src/stim/util_top/circuit_inverse_qec.h
+++ b/src/stim/util_top/circuit_inverse_qec.h
@@ -37,6 +37,7 @@ struct CircuitFlowReverser {
void do_m2r_instruction(const CircuitInstruction &inst);
void do_measuring_instruction(const CircuitInstruction &inst);
void do_simple_instruction(const CircuitInstruction &inst);
+ void do_feedback_capable_instruction(const CircuitInstruction &inst);
void flush_detectors_and_observables();
void do_instruction(const CircuitInstruction &inst);
diff --git a/src/stim/util_top/circuit_inverse_qec_test.py b/src/stim/util_top/circuit_inverse_qec_test.py
index b558da13..f6ea6233 100644
--- a/src/stim/util_top/circuit_inverse_qec_test.py
+++ b/src/stim/util_top/circuit_inverse_qec_test.py
@@ -1,3 +1,4 @@
+import pytest
import stim
@@ -151,3 +152,14 @@ def test_measurement_ordering_3():
assert circuit.has_all_flows(flows, unsigned=True)
new_circuit, new_flows = circuit.time_reversed_for_flows(flows)
assert new_circuit.has_all_flows(new_flows, unsigned=True)
+
+
+def test_feedback():
+ c = stim.Circuit("""
+ R 1
+ M 1
+ CX rec[-1] 0
+ """)
+ with pytest.raises(ValueError):
+ c.time_reversed_for_flows([stim.Flow("Z0 -> Z0")])
+ # TODO: once feedback is supported verify the inv flow is correct