This is definitely a place where you'll struggle to use Stim. It's possible but it's not nice. It could still be worth your time just because you don't have to eg. manually write code to turn a circuit into a matching graph, but it'll be more tedious than branchless code.
Part 1: Simulating
The easiest thing to do is to use stim.TableauSimulator
. It gives you the ability to apply operations one by one driven by python code, which is more than flexible enough to do what you need.
Here is python code showing the general idea:
import stim
simulator = stim.TableauSimulator()
# Run first part of circuit.
simulator.do(stim.Circuit("""
H 0
CNOT 0 1
M 0
"""))
# Do something depending on the measurement result.
latest_measurement_result = simulator.current_measurement_record()[-1]
if latest_measurement_result:
simulator.do(stim.Circuit("""
# ...
"""))
# Run the rest of the circuit.
simulator.do(stim.Circuit("""
# ...
"""))
shot = simulator.current_measurement_record()
The reason Stim doesn't support branching in its circuit format is because branches break an algorithmic optimization where the tableau simulator is only used once for a reference sample, and then stim switches to using much faster error frame simulation. Consequently, you will find that getting thousands of samples by repeatedly using stim.TableauSimulator
is easily 100x slower than using circuit.compile_sampler().sample(shots)
. Hopefully it's still fast enough for your needs.
If you have less than 10 branches, a possible alternative would be to run simulations for each possible case and then postselect out the inconsistent ones where the hardcoded branch disagreed with the measurement result deciding whether or not it was taken. The performance difference can be large enough to overcome the losses from the postselections.
Part 2: Correcting
I'm assuming you're using PyMatching.
For each shot, you need to build the circuit that was actually run. This circuit will give you access to a detector error model (circuit.detector_error_model(decompose_errors=True)
) and the ability to convert the measurement results into detection events (circuit.compile_m2d_converter().convert(shot, append_observables=True)
). Convert the measurements into detection events + observable frame changes, and then run pymatching on the detector data, configured with the circuit's detector error model, and check whether or not it predicted the observable frame data.
If you only have a few possible cases, you will get a large benefit from grouping shots that took the same paths together. If you have a lot of possible cases, so that each case is hit less than 3 times on average, grouping isn't worth it.
It's likely that error model extraction will be your main bottleneck here. Stim is pretty quick at this, it takes it a third of a second or so to analyze the error model of a distance 25 surface code, but a third of a second per sample is awful.
I intend for this use case to get better in the future, but haven't got any concrete designs I'm happy with yet.