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