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
A few caveats:
- This assumes the circuit contains no noise operations.
- You need to handle every kind of measurement that appears in the circuit (
- 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
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
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!
assert instruction.name not in [
"R", "RX", "RY",
"MX", "MY", "MR", "MRX", "MRY",
"MPP", "X_ERROR", "Y_ERROR", "Z_ERROR",
"DEPOLARIZE1", "DEPOLARIZE2", "MPP", "E",
c = stim.Circuit()
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]))
print(outcome_probability(c, desired_results=[True, False, True]))