Expectation Value#

expectation_value(state_prep, shots, return_dict=False, post_processor=None)[source]#

Estimates the expectation value from a sampling kernel.

The expectation_value function allows to estimate the expectation value from a sampling kernel — a Python function that receives only classical arguments and returns arbitrary values. Any QuantumVariables in the return are automatically measured and decoded; classical values are interleaved in-place.

Note

When used inside jaspify() with terminal_sampling=True, the same restrictions apply as for sample(): kernels that return classical values are rejected, and kernels whose quantum state depends on mid-circuit measurement outcomes may produce invalid results. Use terminal_sampling=False (the default) for those cases. See terminal_sampling() for details.

Parameters:
sampling_kernelcallable

A sampling kernel — a function receiving only classical arguments and returning one or more QuantumVariables, classical measurement results, or a mixture of both. The function must not receive quantum arguments because a quantum value would need to be copied for each sampling iteration, which is prohibited by the no-cloning theorem.

shotsint or jax.core.Tracer

The amount of samples to take to compute the expectation value.

post_processorcallable, optional

A classical Jax traceable function to apply to the results directly after measuring. By default no post processing is applied.

Returns:
callable

A function returning a Jax array containing the expectation value.

Raises:
Exception

Tried to sample from sampling kernel taking a quantum value

Examples

We prepare the state

\[\ket{\psi_k} = \frac{1}{\sqrt{2}} \left(\ket{0}\ket{0}\ket{\text{False}} + \ket{k}\ket{k}\ket{\text{True}}\right)\]
from qrisp import *
from qrisp.jasp import *

def sampling_kernel(k):
    a = QuantumFloat(4)
    b = QuantumFloat(4)

    qbl = QuantumBool()
    h(qbl)

    with control(qbl[0]):
        a[:] = k

    cx(a, b)

    return a, b

And compute the expectation value of the QuantumFloats

@jaspify
def main(k):

    ev_function = expectation_value(sampling_kernel, shots = 50)

    return ev_function(k)

print(main(3))
# Yields e.g.
# [1.44 1.44]

The true value 1.5 is not reached exactly because of shot noise — the printed value fluctuates around 1.5 from run to run. To improve the approximation, feel free to increase the shots!

To demonstrate the post_processor keyword we define a simple post processing function

def post_processor(x, y):
    return x*y

@jaspify
def main(k):

    ev_function = expectation_value(state_prep, shots = 50,
                                     post_processor = post_processor)

    return ev_function(k)

print(main(3))
# Yields e.g.
# 4.86

This result is expected because the inputs of post_processor are either (0,0) or (3,3) with 50% probability, so the expectation value is

\[4.5 = \frac{3\cdot 3 + 0\cdot 0}{2}\]

As with the previous example, the printed value fluctuates around this expectation from run to run because of shot noise.

Expectation Value for Hamiltonians#

Estimating the expectation value of a Hamiltonian for a state is enabled by the respective .expectation_value methods for QubitOperators and FermionicOperators.

For example, we prepare the state

\[\ket{\psi_{\theta}} = (\cos(\theta/2)\ket{0}+\sin(\theta/2)\ket{1})^{\otimes 2}\]
from qrisp import *
from qrisp.operators import Z
import numpy as np

def state_prep(theta):
    qv = QuantumFloat(2)

    ry(theta,qv)

    return qv

And compute the expectation value of the Hamiltonian \(H=Z_0Z_1\) for the state \(\ket{\psi_{\theta}}\)

@jaspify(terminal_sampling=True)
def main():

    H = Z(0)*Z(1)

    ev_function = H.expectation_value(state_prep, precision = 0.01)

    return ev_function(np.pi/2)

print(main())
# Yields: 0.010126265783222899