Sampling the tensor network state

The following code example illustrates how to sample the tensor network state (e.g., sampling the final quantum circuit state). The full code can be found in the NVIDIA/cuQuantum repository (here).

Headers and error handling

 7#include <cstdlib>
 8#include <cstdio>
 9#include <cassert>
10#include <complex>
11#include <vector>
12#include <iostream>
13
14#include <cuda_runtime.h>
15#include <cutensornet.h>
16
17#define HANDLE_CUDA_ERROR(x) \
18{ const auto err = x; \
19  if( err != cudaSuccess ) \
20  { printf("CUDA error %s in line %d\n", cudaGetErrorString(err), __LINE__); fflush(stdout); std::abort(); } \
21};
22
23#define HANDLE_CUTN_ERROR(x) \
24{ const auto err = x; \
25  if( err != CUTENSORNET_STATUS_SUCCESS ) \
26  { printf("cuTensorNet error %s in line %d\n", cutensornetGetErrorString(err), __LINE__); fflush(stdout); std::abort(); } \
27};
28
29
30int main(int argc, char **argv)
31{
32  constexpr std::size_t fp64size = sizeof(double);

Define the tensor network state and the desired number of output samples to generate

Let’s define a tensor network state corresponding to a 16-qubit quantum circuit and request to produce 100 output samples for the full qubit register.

36  // Quantum state configuration
37  const int64_t numSamples = 100;
38  const int32_t numQubits = 16;
39  const std::vector<int64_t> qubitDims(numQubits, 2); // qubit size
40  std::cout << "Quantum circuit: " << numQubits << " qubits; " << numSamples << " samples\n";

Initialize the cuTensorNet library handle

44  // Initialize the cuTensorNet library
45  HANDLE_CUDA_ERROR(cudaSetDevice(0));
46  cutensornetHandle_t cutnHandle;
47  HANDLE_CUTN_ERROR(cutensornetCreate(&cutnHandle));
48  std::cout << "Initialized cuTensorNet library on GPU 0\n";

Define quantum gates on GPU

52  // Define necessary quantum gate tensors in Host memory
53  const double invsq2 = 1.0 / std::sqrt(2.0);
54  //  Hadamard gate
55  const std::vector<std::complex<double>> h_gateH {{invsq2, 0.0},  {invsq2, 0.0},
56                                                   {invsq2, 0.0}, {-invsq2, 0.0}};
57  //  CX gate
58  const std::vector<std::complex<double>> h_gateCX {{1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0},
59                                                    {0.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0},
60                                                    {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {1.0, 0.0},
61                                                    {0.0, 0.0}, {0.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}};
62
63  // Copy quantum gates to Device memory
64  void *d_gateH{nullptr}, *d_gateCX{nullptr};
65  HANDLE_CUDA_ERROR(cudaMalloc(&d_gateH, 4 * (2 * fp64size)));
66  std::cout << "H gate buffer allocated on GPU: " << d_gateH << std::endl; //debug
67  HANDLE_CUDA_ERROR(cudaMalloc(&d_gateCX, 16 * (2 * fp64size)));
68  std::cout << "CX gate buffer allocated on GPU: " << d_gateCX << std::endl; //debug
69  std::cout << "Allocated quantum gate memory on GPU\n";
70  HANDLE_CUDA_ERROR(cudaMemcpy(d_gateH, h_gateH.data(), 4 * (2 * fp64size), cudaMemcpyHostToDevice));
71  HANDLE_CUDA_ERROR(cudaMemcpy(d_gateCX, h_gateCX.data(), 16 * (2 * fp64size), cudaMemcpyHostToDevice));
72  std::cout << "Copied quantum gates to GPU memory\n";

Create a pure tensor network state

Now let’s create a pure tensor network state for a 16-qubit quantum circuit.

76  // Create the initial quantum state
77  cutensornetState_t quantumState;
78  HANDLE_CUTN_ERROR(cutensornetCreateState(cutnHandle, CUTENSORNET_STATE_PURITY_PURE, numQubits, qubitDims.data(),
79                    CUDA_C_64F, &quantumState));
80  std::cout << "Created the initial quantum state\n";

Apply quantum gates

Let’s construct the GHZ quantum circuit by applying the corresponding quantum gates.

84  // Construct the quantum circuit state (apply quantum gates)
85  int64_t id;
86  HANDLE_CUTN_ERROR(cutensornetStateApplyTensor(cutnHandle, quantumState, 1, std::vector<int32_t>{{0}}.data(),
87                    d_gateH, nullptr, 1, 0, 1, &id));
88  for(int32_t i = 1; i < numQubits; ++i) {
89    HANDLE_CUTN_ERROR(cutensornetStateApplyTensor(cutnHandle, quantumState, 2, std::vector<int32_t>{{i-1,i}}.data(),
90                      d_gateCX, nullptr, 1, 0, 1, &id));
91  }
92  std::cout << "Applied quantum gates\n";

Create the tensor network state sampler

Once the quantum circuit has been constructed, let’s create the tensor network state sampler for the full qubit register (all qubits).

96  // Create the quantum circuit sampler
97  cutensornetStateSampler_t sampler;
98  HANDLE_CUTN_ERROR(cutensornetCreateSampler(cutnHandle, quantumState, numQubits, nullptr, &sampler));
99  std::cout << "Created the quantum circuit sampler\n";

Allocate the scratch buffer on GPU

103  // Query the free memory on Device
104  std::size_t freeSize {0}, totalSize {0};
105  HANDLE_CUDA_ERROR(cudaMemGetInfo(&freeSize, &totalSize));
106  const std::size_t scratchSize = (freeSize - (freeSize % 4096)) / 2; // use half of available memory with alignment
107  void *d_scratch {nullptr};
108  HANDLE_CUDA_ERROR(cudaMalloc(&d_scratch, scratchSize));
109  std::cout << "Allocated " << scratchSize << " bytes of scratch memory on GPU: "
110            << "[" << d_scratch << ":" << (void*)(((char*)(d_scratch))  + scratchSize) << ")\n";

Configure the tensor network state sampler

Now we can configure the tensor network state sampler by setting the number of hyper-samples to be used by the tensor network contraction path finder.

114  // Configure the quantum circuit sampler
115  const int32_t numHyperSamples = 8; // desired number of hyper samples used in the tensor network contraction path finder
116  HANDLE_CUTN_ERROR(cutensornetSamplerConfigure(cutnHandle, sampler,
117                    CUTENSORNET_SAMPLER_OPT_NUM_HYPER_SAMPLES, &numHyperSamples, sizeof(numHyperSamples)));

Prepare the tensor network state sampler

Let’s create a workspace descriptor and prepare the tensor network state sampler.

121  // Prepare the quantum circuit sampler
122  cutensornetWorkspaceDescriptor_t workDesc;
123  HANDLE_CUTN_ERROR(cutensornetCreateWorkspaceDescriptor(cutnHandle, &workDesc));
124  HANDLE_CUTN_ERROR(cutensornetSamplerPrepare(cutnHandle, sampler, scratchSize, workDesc, 0x0));
125  std::cout << "Prepared the quantum circuit state sampler\n";

Set up the workspace

Now we can set up the required workspace buffer.

129  // Attach the workspace buffer
130  int64_t worksize {0};
131  HANDLE_CUTN_ERROR(cutensornetWorkspaceGetMemorySize(cutnHandle,
132                                                      workDesc,
133                                                      CUTENSORNET_WORKSIZE_PREF_RECOMMENDED,
134                                                      CUTENSORNET_MEMSPACE_DEVICE,
135                                                      CUTENSORNET_WORKSPACE_SCRATCH,
136                                                      &worksize));
137  assert(worksize > 0);
138  if(worksize <= scratchSize) {
139    HANDLE_CUTN_ERROR(cutensornetWorkspaceSetMemory(cutnHandle, workDesc, CUTENSORNET_MEMSPACE_DEVICE,
140                      CUTENSORNET_WORKSPACE_SCRATCH, d_scratch, worksize));
141  }else{
142    std::cout << "ERROR: Insufficient workspace size on Device!\n";
143    std::abort();
144  }
145  std::cout << "Set the workspace buffer\n";

Perform sampling of the final quantum circuit state

Once everything had been set up, we perform sampling of the quantum circuit state and print the output samples.

149  // Sample the quantum circuit state
150  std::vector<int64_t> samples(numQubits * numSamples); // samples[SampleId][QubitId] reside in Host memory
151  HANDLE_CUTN_ERROR(cutensornetSamplerSample(cutnHandle, sampler, numSamples, workDesc, samples.data(), 0));
152  std::cout << "Performed quantum circuit state sampling\n";
153  std::cout << "Bit-string samples:\n";
154  for(int64_t i = 0; i < numSamples; ++i) {
155    for(int64_t j = 0; j < numQubits; ++j) std::cout << " " << samples[i * numQubits + j];
156    std::cout << std::endl;
157  }

Free resources

161  // Destroy the workspace descriptor
162  HANDLE_CUTN_ERROR(cutensornetDestroyWorkspaceDescriptor(workDesc));
163  std::cout << "Destroyed the workspace descriptor\n";
164
165  // Destroy the quantum circuit sampler
166  HANDLE_CUTN_ERROR(cutensornetDestroySampler(sampler));
167  std::cout << "Destroyed the quantum circuit state sampler\n";
168
169  // Destroy the quantum circuit state
170  HANDLE_CUTN_ERROR(cutensornetDestroyState(quantumState));
171  std::cout << "Destroyed the quantum circuit state\n";
172
173  HANDLE_CUDA_ERROR(cudaFree(d_scratch));
174  HANDLE_CUDA_ERROR(cudaFree(d_gateCX));
175  HANDLE_CUDA_ERROR(cudaFree(d_gateH));
176  std::cout << "Freed memory on GPU\n";
177
178  // Finalize the cuTensorNet library
179  HANDLE_CUTN_ERROR(cutensornetDestroy(cutnHandle));
180  std::cout << "Finalized the cuTensorNet library\n";
181
182  return 0;
183}