# Exact Probabilities of Outcomes for Clifford Circuits with Mid-Circuit Measurements Using Stim

I am trying to find the exact probabilities of specific measurement outcomes for Clifford circuits with mid-circuit measurements. Essentially, I am looking for a function that takes an arbitrary Clifford circuit and a specific measurement outcome for all mid-circuit measurements, and returns the exact probability of finding that outcome.

The code I would like to run should be as efficient as possible, so I wanted to use Stim to calculate the probabilities. Does anyone know how to do this?

Many thanks.

In a stabilizer circuit that doesn't allow classically-controlled Cliffords feedback, every outcome that is possible is equally likely. Also, when a measurement is random, it's always 50/50 random. So really you're just trying to figure out whether the result is possible at all and, if it is, how many measurements have random results.

You can use the tableau simulator to do this. Iterate over the circuit's instructions, simulating them one by one. When you encounter a measurement, use stim.TableauSimulator.peek_z to determine if the result is random and then use stim.TableauSimulator.postselect_z to force the correct measurement result (if possible).

If all postselections succeed, the result is possible and has probability 2**-num_measurements_that_were_random.

A few caveats:

• This assumes the circuit contains no noise operations.
• You need to handle every kind of measurement that appears in the circuit (M, MY, MRX, etc).
• If the circuit contains resets, you need to put in more work, because they are collapsing operations which affect peek_z's result but without telling you how. Even if you make the effects visible by replacing each R with MR you have the problem that you don't know what the desired measurement results for those operations is. What you can do is instead replace each R with a SWAP that swaps the target qubit for a fresh unused ancilla qubit. This avoids a collapse being simulated by the simulator, so the expectations from peek_z remain purely a function of the previous measurements.

Here's the version that works if no resets are present:

from typing import List
import stim

def outcome_probability(circuit: stim.Circuit, desired_results: List[bool]) -> float:
# Note: assumes no noise in circuit
# Note: assumes all measurements are single-qubit Z basis measuremet ('M').
assert len(desired_results) == circuit.num_measurements

random_results = 0
s = stim.TableauSimulator()

iter_desired = iter(desired_results)
for instruction in circuit.flattened():
if instruction.name == 'M':
for t in instruction.targets_copy():
expectaction = s.peek_z(t.value)
desired = next(iter_desired)
if expectaction == 0:
random_results += 1
elif expectaction != (-1 if desired else +1):
return 0  # Impossible!
s.postselect_z(t.value, desired_value=desired)
else:
assert instruction.name not in [
"R", "RX", "RY",
"MX", "MY", "MR", "MRX", "MRY",
"MPP", "X_ERROR", "Y_ERROR", "Z_ERROR",
"DEPOLARIZE1", "DEPOLARIZE2", "MPP", "E",
"PAULI_CHANNEL_1", "PAULI_CHANNEL_2"]
c = stim.Circuit()
c.append(instruction)
s.do_circuit(c)

return 2**-random_results  # Possible.

c = stim.Circuit("""
H 0 2
CX 0 1
CX 2 1
M 0 1 2
""")

print(outcome_probability(c, desired_results=[False, False, True]))
# 0

print(outcome_probability(c, desired_results=[True, False, True]))
# 0.25
$$$$
`