# Computing expectation value of a Pauli string on stabilizer states

I need some help on stim, where I'm trying to compute expectation values of Pauli strings. Hopefully I did not overlook on the documentation an implementation of this method.

# Problem Statement

Given a generic Pauli string $$O$$ acting on $$N$$ qubits and a stabilizer state $$\rho$$ on $$N$$ qubits, compute within stim the expectation value $$$$\langle O\rangle \equiv \mathrm{Tr}(O \rho).$$$$ Since $$O$$ is a Pauli string, $$\langle O\rangle \in \{0,+1,-1\}$$.

# Tentative Solution

To be concrete, within this question I fix $$N=4$$, and I want to compute $$\mathrm{Tr}(X_1 Z_3 \rho)$$. Furthermore, I specify I work on the c++ library, within which my state is contained in an instance of TableauSimulator:

...
using namespace stim;
using namespace std;

mt19937_64 rng(1); // Random generator with SEED=1
MeasureRecord record; // Measurement records
TableauSimulator Psi(ref(rng),4,0,record);
// 0 is the unbiased-condition of the output sign for non-deterministic measurements

...


Now, I know I can apply a measurement gate MPP X1Z3. If this measurement is deterministic (equivalently, if $$X1Z3$$ commute with $$\rho$$), the measurement readout gives $$\langle O\rangle$$, which is either $$+1$$ or $$-1$$. If, instead, the measurement is non-deterministic (equivalently, if $$X1Z3$$ anticommute with $$\rho$$), $$\langle O\rangle=0$$. Given the above, I tried the following. I initialize two new TableauSimulator

TableauSimulator PsiPlus = Psi;
TableauSimulator PsiMinus = Psi;


and applied to them PsiPlus the gate MPP X1Z3, whereas in PsiMinus the gate MPP !X1Z3. Then, since in general $$$$\langle O\rangle = \mathrm{Tr}(O\rho) = \mathrm{Tr}\left(\frac{1+O}{2}\rho\right) -\mathrm{Tr}\left(\frac{1-O}{2}\rho\right) = p(+1) - p(-1),$$$$ I expect the difference:

int aveO = PsiPlus.measurement_record.storage[last_entry]-PsiMinus.measurement_record.storage[last_entry];


where last_entry is the index of the last measurement (respectively of MPP X1Z3 for PsiPlus and MPP !X1Z3 for PsiMinus) should be the required value $$\langle O\rangle$$.

Problems There are problems with the above approach/ideas/implementation. It works if the measurement is deterministic, but it in general it doesn't for non-deterministic measurements (which is the main issue to solve). I think the reason is, since both states refer to the same random generator, the randomness in PsiPlus and PsiMinus are inequivalent, leading to different results (e.g. different internally drawn random numbers). Furthermore, the operations required can be probably reduced. Lastly, I conclude with a remark. For single site measurements, the above issue do not figure in the present release of stim, as there is a method TableauSimulator.is_deterministic_x (and similarly for y,z) which should exactly check if the outcome of MX (MY,MZ) is deterministic or not. If a similar method would be present for generic MPP, probably a solution would still be easily implementable. In pseudo-code:

int aveO;
if (Psi.is_deterministic_mpp(X1Z3,{1,3}) {
TableauSimulator PsiM = Psi;
PsiM.apply_mpp(X1Z3,{1,3});
aveO = PsiM.measurement_record.storage[last_item];
}


Still, I'm not an expect in HPC, but probably there are smarter way to implement the computation of expectation values.

You can use stim.TableauSimulator.peek_observable_expectation to compute the expected value of Pauli product observables without affecting the simulator's state:

import stim
s = stim.TableauSimulator()
s.h(0)
s.cnot(0, 1)
obs = stim.PauliString('-YY')
expect = s.peek_observable_expectation(obs)
print('peek', obs, '=', expect)


prints:

peek -YY = +1

• I'm using stim in python now; eventually I was planning to move to C++ for speed and because I have other C++ tools for decoders... From your comment it seems you favor the python version. If the C++ version is not guaranteed to be stable does it make sense to even start using it? Oct 14, 2021 at 18:16
• @unknown If you like C++ you can use the C++ API, just pin to a specific commit instead of to the main branch so that I don't break you when I make changes. Personally I use the Python API as much as possible because Python is faster to write. There's some speed loss, but it's usually not the bottleneck step in my case. Oct 14, 2021 at 18:41
• @CraigGidney Thank you for the in depth answer! Oct 15, 2021 at 6:44