Generic Library Functions

One of the primary goals of the CUDA Quantum platform is to build up a robust, widely-applicable, cudaq:: namespace of generic, algorithmic primitive functions. By generic, we mean that these functions are ultimately templated on the input CUDA Quantum kernel expression, implying that algorithmic function definitions are applicable to a wide-range of input quantum code. This characteristic of quantum algorithm development is ubiquitous. One often designs algorithms that are general with regards to a quantum oracle, or a state-preparation step, just to name a few examples. CUDA Quantum enables this via generic cudaq:: functions that take any CUDA Quantum kernel expression as input.

Let’s take a look at the first couple examples of this already implemented in CUDA Quantum. The first function we provide is cudaq::sample(...), which takes any kernel as input (with certain characteristics, see the Specification) and samples the kernel’s resultant state over a number of shots, returning a map of observed measurement bit strings to the corresponding number of times that configuration was observed. Using this function is straightforward:

auto myFirstKernel_Toffoli_111_input = [](cudaq::qspan<> threeQubits) __qpu__ {
  // Alias the 3 qubits
  auto& q = threeQubits[0];
  auto& r = threeQubits[1];
  auto& s = threeQubits[2];
  // Create 101
  x (q, s);
  // Manual decomposition of x(q, r, s);
  // i.e., could have also written
  // x<cudaq::ctrl>(q, r, s);
  h(s);
  x<cudaq::ctrl>(r, s);
  t<cudaq::adj>(s);
  x<cudaq::ctrl>(q, s);
  t(s);
  x<cudaq::ctrl>(r,s);
  t<cudaq::adj>(s);
  x<cudaq::ctrl>(q,s);
  t(r); t(s);
  x<cudaq::ctrl>(q, r);
  t(q); t<cudaq::adj>(r);
  x<cudaq::ctrl>(q, r);
};

// cudaq::sample takes entry point kernels as input
auto entryPointKernel = [&]() __qpu__ {
  cudaq::qreg<3> q;
  myFirstKernel_Toffoli_111_input(q);
  mz(q);
};

// Sample the state produced by this kernel
// dump the counts to stdout
auto counts = cudaq::sample(entryPointKernel);
counts.dump();
// prints { 011:1000 }

If your CUDA Quantum kernel takes classical data as input, then those runtime values must be provided to the cudaq::sample function as trailing arguments

auto ghz = [](int N) __qpu__ {
  cudaq::qreg q(N);
  h(q[0]);
  for (int i = 0; i < N - 1; i++) {
    x<cudaq::ctrl>(q[i], q[i + 1]);
  }
  mz(q);
};

auto counts = cudaq::sample(ghz, 5); // note runtime arguments
for (auto& [bits, count] : counts) {
  std::cout << "Observed " << bits << ":" << count "\n";
}
// prints
// Observed 11111:505
// Observed 00000:495

Another useful CUDA Quantum function in the variational context is cudaq::observe(...). This function takes any kernel expression (with suitable characteristics noted in the Specification), a user-provided cudaq::spin_op defining a general quantum mechanical spin operator, and any kernel runtime arguments to return the expected value of the spin operator with respect to the kernel ansatz at the provided runtime parameters. It can be used in the following manner:

auto ansatz = [](double theta) __qpu__ {
  ... Define your parameterized kernel ...
  ... No measures, as they are dictated by the spin_op ...
};

using namespace cudaq::spin;
cudaq::spin_op H = ...;
auto exp_val = cudaq::observe(ansatz, H, /* theta */ M_PI / 2.0);