Obtaining expectation values¶
Given a quasi- or standard probability distribution, it is possible to compute the
expectation value of diagonal operators directly from the distributions (or collections)
of distributions. This can be done using string representation for standard diagonal
operators such as I
, Z
, 0
or 1
, or via dictionaries for custom operators.
Let us first generate some quasi-distributions by mitigating 2- and 3-qubit GHZ circuits on a noisy-simulator.
import numpy as np
from qiskit import *
from qiskit_ibm_runtime.fake_provider import FakeAthensV2
import mthree
backend = FakeAthensV2()
ghz2 = QuantumCircuit(2)
ghz2.h(0)
ghz2.cx(0,1)
ghz2.measure_all()
trans_ghz2 = transpile(ghz2, backend)
ghz3 = QuantumCircuit(3)
ghz3.h(0)
ghz3.cx(0,1)
ghz3.cx(1,2)
ghz3.measure_all()
trans_ghz3 = transpile(ghz3, backend)
raw2 = backend.run(trans_ghz2, shots=4000).result().get_counts()
raw3 = backend.run(trans_ghz3, shots=4000).result().get_counts()
mit = mthree.M3Mitigation(backend)
mit.cals_from_system()
quasi2 = mit.apply_correction(raw2, [0,1], return_mitigation_overhead=True)
quasi3 = mit.apply_correction(raw3, [0,1,2], return_mitigation_overhead=True)
Now let us compute the expectaion values of these distributions for the default
case of Z
operators on each qubit:
print('GHZ2:', quasi2.expval())
print('GHZ3:', quasi3.expval())
GHZ2: 0.9794586896896362
GHZ3: 0.043783366680145264
The values are close to one and zero, respectively. We can use strings to repeat the above via:
print('GHZ2:', quasi2.expval('ZZ'))
print('GHZ3:', quasi3.expval('ZZZ'))
GHZ2: 0.9794586896896362
GHZ3: 0.043783366680145264
Replacing a Z
measurement with an I
on one of the qubits has the affect of changing the
sign for the \(|1>^{\otimes N}\) component:
print('GHZ2:', quasi2.expval('IZ'))
print('GHZ3:', quasi3.expval('ZIZ'))
GHZ2: 0.03425908088684082
GHZ3: 0.975365936756134
We can also pass lists of strings:
quasi3.expval_and_stddev(['ZZZ','ZIZ'])
(array([0.04378337, 0.97536594], dtype=float32), 0.01839476178816572)
Alternatively, users can specify their own custom diagonal operators using dictionaries. Here we form the projectors on the all ones and zeros states:
all_zeros_proj = {'000': 1}
all_ones_proj = {'111': 1}
quasi3.expval(all_zeros_proj)
0.5150302648544312
Like strings, one can pass an array of dicts:
quasi3.expval([all_zeros_proj, all_ones_proj])
array([0.51503026, 0.469589 ], dtype=float32)
We can verify that the projectors return the correct values:
p0s, p1s = quasi3.expval([all_zeros_proj, all_ones_proj])
np.allclose([p0s, p1s], [quasi3['000'], quasi3['111']])
True