Computing Expectation Values

CUDA-Q provides generic library functions enabling one to compute expectation values of quantum spin operators with respect to a parameterized CUDA-Q kernel. Let’s take a look at an example of this:

# The example here shows a simple use case for the `cudaq::observe``
# function in computing expected values of provided spin operators.

import cudaq
from cudaq import spin


@cudaq.kernel
def kernel(theta: float):
    qvector = cudaq.qvector(2)
    x(qvector[0])
    ry(theta, qvector[1])
    x.ctrl(qvector[1], qvector[0])


spin_operator = 5.907 - 2.1433 * spin.x(0) * spin.x(1) - 2.1433 * spin.y(
    0) * spin.y(1) + .21829 * spin.z(0) - 6.125 * spin.z(1)

# Pre-computed angle that minimizes the energy expectation of the `spin_operator`.
angle = 0.59

energy = cudaq.observe(kernel, spin_operator, angle).expectation()
print(f"Energy is {energy}")

Here we define a parameterized CUDA-Q kernel that takes an angle, theta, as a single input. This angle becomes the argument of a single ry rotation.

We define a Hamiltonian operator via the CUDA-Q cudaq.SpinOperator type.

CUDA-Q provides a generic function cudaq.observe. This function takes as input three arguments. The first two argument are a parameterized kernel and the cudaq.SpinOperator whose expectation value we wish to compute. The final arguments are the runtime parameters at which we evaluate the parameterized kernel.

// Compile and run with:
// ```
// nvq++ expectation_values.cpp -o d2.x && ./d2.x
// ```

#include <cudaq.h>
#include <cudaq/algorithm.h>

// The example here shows a simple use case for the `cudaq::observe`
// function in computing expected values of provided spin_ops.

struct ansatz {
  auto operator()(double theta) __qpu__ {
    cudaq::qvector q(2);
    x(q[0]);
    ry(theta, q[1]);
    x<cudaq::ctrl>(q[1], q[0]);
  }
};

int main() {

  // Build up your spin op algebraically
  using namespace cudaq::spin;
  cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) +
                     .21829 * z(0) - 6.125 * z(1);

  // Observe takes the kernel, the spin_op, and the concrete
  // parameters for the kernel
  double energy = cudaq::observe(ansatz{}, h, .59);
  printf("Energy is %lf\n", energy);
  return 0;
}

Here we define a parameterized CUDA-Q kernel, a callable type named ansatz that takes as input a single angle theta. This angle becomes the argument of a single ry rotation.

In host code, we define a Hamiltonian operator via the CUDA-Q spin_op type. CUDA-Q provides a generic function cudaq::observe. This function takes as input three terms. The first two terms are a parameterized kernel and the spin_op whose expectation value we wish to compute. The last term contains the runtime parameters at which we evaluate the parameterized kernel.

The return type of this function is an cudaq::observe_result which contains all the data from the execution, but is trivially convertible to a double, resulting in the expectation value we are interested in.

To compile and execute this code, we run the following:

nvq++ expectation_values.cpp -o exp_vals.x
./exp_vals.x