# How to perform encoding and syndrome measurement in stim

I can generate the encoding circuit of a stabilizer code and can read it into stim. For example for the $$[[5,1,3]]$$ code :

 circuit=stim.Circuit()
circuit.append_operation("H",[1])
circuit.append_operation("CY",[0,4])
circuit.append_operation("H",[2])
circuit.append_operation("CX",[1,4])
circuit.append_operation("H",[3])
circuit.append_operation("CZ",[2,0])
circuit.append_operation("CZ",[2,1])
circuit.append_operation("CX",[2,4])
circuit.append_operation("H",[4])
circuit.append_operation("CZ",[3,0])
circuit.append_operation("CZ",[3,2])
circuit.append_operation("CY",[3,4])


To check this I'd like to encode a random qubit then measure the syndrome for the 4 stabilizers; if everything is correct the syndrome should always be $$(0,0,0,0)$$.

First step : the "data" qubit is placed on qubit 4 (numbering starts from 0). So the input to the encoder is $$(q0=0,q1=0,q2=0,q3=0,q4=d0)$$. $$k=1$$ for this code so there's only one data qubit. How would I initialize the input to be of that form?

Second step : I have 4 stabilizers which are just Pauli strings of length 5. I'd like to measure the syndromes and place the result on 4 ancilla qubits. How would I do that and then check that the syndromes are 0?

• Are the measurements assumed to be noiseless? Oct 14 at 3:00
• @CraigGidney yes for now Oct 14 at 3:16
• You can get a more succinct program for the circuit via print(repr(circuit)), by the way. Oct 14 at 3:17
• the initialization is literally R 0 1 2 3 4 5 6 7 8 ... for logical 0 or RX 0 1 2 3 4 5 6 7 8 ... for logical plus. Oct 14 at 17:02
• Appendix D of the paper ( arxiv.org/abs/2108.10457 ) has the complete 2x6 honeycomb circuit as a stim circuit. Oct 14 at 17:05

You can use the MPP instruction to measure a Pauli product. For example, if one of the prepared stabilizers is $$X_1 \cdot Z_2 \cdot Y_3 = +1$$ then you can do:

# [... encoding circuit ... ]

# measure stabilizer
MPP X1*Z2*Y3
# and claim it's supposed to have a deterministic result
DETECTOR rec[-1]


If you now sample the detectors of the circuit via circuit.compile_detector_sampler().sample(shots=10) you should get back a numpy array filled with 0s, indicating the system has been prepared into an eigenstate of the stabilizer. If you instead see a 50/50 mix of 0s and 1s, something is wrong.

The next step would be to do that for each stabilizer, and then add noise and confirm that you see the stabilizers flipping:

# [... encoding circuit ... ]

# phase damp qubit 3, potentially flipping the stabilizer
Z_ERROR(0.1) 3

# measure stabilizer
MPP X1*Z2*Y3
# and claim it's supposed to have a deterministic result
DETECTOR rec[-1]


Beware that adding Z 3 is not the same thing as adding Z_ERROR(1) 3. Detectors compute what the expected value is supposed to be, and report deviations from that value arising from noise. Z 3 is part of the expected value calculation, whereas Z_ERROR(1) 3 is part of the noise.

You can use MPP(wrong_result_probability) to make the measurement result itself noisy (with no effect on the qubits).

You may also want to decompose the compound measurement into some underlying gateset, and make each of the individual operations noisy. Stim won't do that for you, but you can of course tell it the decomposed measurement's circuit as well as the noise mechanism instructions around each of its pieces.

• What about the initialization part? the first 4 qubits have to be in state 0. Also how would I place the syndrome values on ancilla qubits? this would be needed for decoding later Oct 14 at 3:19
• @unknown Why would you want the syndrome stored in qubits instead of stored in the measurement record? If your measurements are noiseless the distinction between decomposing it into a form using an ancilla and a form not using an ancilla is irrelevant. Oct 14 at 3:21
• you might have a point there...i'll revisit when I get to the decoding part Oct 14 at 3:23

I was able to setup the encoding and syndrome check for the example $$[[5,1,3]]$$ code.

import stim
circuit=stim.Circuit('''
RZ 0 1 2 3
RZ 4

H 0
CY 0 4
H 1
CX 1 4
H 2
CZ 2 0
CZ 2 1
CX 2 4
H 3
CZ 3 0
CZ 3 2
CY 3 4

MPP Y0*Z1*Z3*Y4 X1*Z2*Z3*X4 Z0*Z1*X2*X4 Z0*Z2*Y3*Y4

''')
sampler = circuit.compile_sampler()
print(sampler.sample(shots=8))


The results :

[[1 0 0 0]
[0 0 0 0]
[0 0 0 0]
[0 0 0 1]
[1 0 0 0]
[1 0 0 0]
[1 0 0 1]
[0 0 0 1]]


The results for the second and third stabilizers are ok (always 0). The first and fourth stabilizers have a problem; they should also be 0. This is the encoded 0 state. The encoded 1 state is what you would get if you change the second line in the circuit from RZ 4 to RX 4. The problem happens in both cases. I think the problem might be related to the $$Y$$ gate; the problem stabilizers have it the ones that are ok do not. "My" $$Y$$ gate is just $$XZ$$ not $$\imath XZ$$ so it's orthogonal and unitary but not hermitian. I have my reasons to stay with this convention. Is there way to work with the real (not complex) version of $$Y$$ in stim; this might solve the problem.

Fixed circuit : (replaces CY with CX and CZ) :

import stim
circuit=stim.Circuit('''
RZ 0 1 2 3
RX 4

H 0
CX 0 4
CZ 0 4
H 1
CX 1 4
H 2
CZ 2 0
CZ 2 1
CX 2 4
H 3
CZ 3 0
CZ 3 2
CX 3 4
CZ 3 4

MPP Y0*Z1*Z3*Y4 X1*Z2*Z3*X4 Z0*Z1*X2*X4 Z0*Z2*Y3*Y4

''')
sampler = circuit.compile_sampler()
print(sampler.sample(shots=8))

• You shouldn't use X*Z instead of Y in your stabilizers because it has imaginary eigenvalues. It's not Hermitian. It's not an observable. That being said, as long as you always use two of them the imaginaries combine into a negation, flipping the measurement result. You can account for that with a ! like MPP !Y0*Z1*Z3*Y4. But it actually looks like something more serious is wrong, because you're seeing varying values. I think you haven't correctly prepared the state. Oct 15 at 18:58
• Another thing you can do to help with this problem is to use DETECTORs. Add DETECTOR rec[-4] through DETECTOR rec[-1] at the end of your circuit and then compile_detector_sampler() instead of compile_sampler(). The benefit is that detectors figure out if the associated measurement is supposed to be true or false, and tell you when it deviates from that, so you only have to care about getting your circuit correct up to Paulis. E.g. you don't have to pay attention to things like "did I use an S gate instead of an inverse S gate"; the detectors make it work. Oct 15 at 19:00
• The way I define the codes $Y=XZ$ is much nicer to work with (everything is real and orthogonal). I found a fix for the problem : I changed CY a b to CX a b CZ a b. The syndrome check works now Oct 15 at 19:10
• I re-emphasize the dangers of working with "observables" or "stabilizers" that are not Hermitian. X*Z has real entries but its eigenvalues are imaginary. I'll assume you know what you're doing, but be very careful. You will be performing unitary operations on your system when you "measure" those. Oct 15 at 19:25
• Here's a concrete example. Repeatedly projecting the "observable" X*Z is not idempotent: algassert.com/… Oct 15 at 19:33