backend.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import os
  2. import random
  3. import numpy as np
  4. import pyopencl as cl
  5. import pyopencl.array as pycl_array
  6. from pyopencl.reduction import ReductionKernel
  7. from collections import defaultdict
  8. # Get the OpenCL kernel
  9. kernel_path = os.path.join(
  10. os.path.dirname(__file__),
  11. "kernels/brute-force.cl"
  12. )
  13. kernel = open(kernel_path, "r").read()
  14. class Backend:
  15. """
  16. A class for the OpenCL backend to the simulator.
  17. This class shouldn't be used directly, as many of the
  18. methods don't have the same input checking as the State
  19. class.
  20. """
  21. def __init__(self, num_qubits, dtype=np.complex64):
  22. """
  23. Initialize a new OpenCL Backend
  24. Takes an argument of the number of qubits to use
  25. in the register, and returns the backend.
  26. """
  27. self.num_qubits = num_qubits
  28. self.dtype = dtype
  29. self.context = cl.create_some_context()
  30. self.queue = cl.CommandQueue(self.context)
  31. self.program = cl.Program(self.context, kernel).build(options="-cl-no-signed-zeros -cl-mad-enable -cl-fast-relaxed-math")
  32. # Buffer for the state vector
  33. self.buffer = pycl_array.to_device(
  34. self.queue,
  35. np.eye(1, 2**num_qubits, dtype=dtype)
  36. )
  37. def apply_gate(self, gate, target):
  38. """Applies a gate to the quantum register"""
  39. self.program.apply_gate(
  40. self.queue,
  41. [int(self.buffer.shape[1] / 2)],
  42. None,
  43. self.buffer.data,
  44. np.int32(target),
  45. self.dtype(gate.a),
  46. self.dtype(gate.b),
  47. self.dtype(gate.c),
  48. self.dtype(gate.d)
  49. )
  50. def apply_controlled_gate(self, gate, control, target):
  51. """Applies a controlled gate to the quantum register"""
  52. self.program.apply_controlled_gate(
  53. self.queue,
  54. [int(self.buffer.shape[1] / 2)],
  55. None,
  56. self.buffer.data,
  57. np.int32(control),
  58. np.int32(target),
  59. self.dtype(gate.a),
  60. self.dtype(gate.b),
  61. self.dtype(gate.c),
  62. self.dtype(gate.d)
  63. )
  64. def measure(self, samples=1):
  65. """Measure the state of a register"""
  66. # This is a really horrible method that needs a rewrite - the memory
  67. # is attrocious
  68. probabilities = self.probabilities()
  69. choices = np.random.choice(
  70. np.arange(0, 2**self.num_qubits),
  71. samples,
  72. p=probabilities
  73. )
  74. results = defaultdict(int)
  75. for i in choices:
  76. results[np.binary_repr(i, width=self.num_qubits)] += 1
  77. return dict(results)
  78. def qubit_probability(self, target):
  79. """Get the probability of a single qubit begin measured as '0'"""
  80. preamble = """
  81. #include <pyopencl-complex.h>
  82. float probability(int target, int i, cfloat_t amp) {
  83. if ((i & (1 << target )) != 0) {
  84. return 0;
  85. }
  86. // return 6.0;
  87. float abs = cfloat_abs(amp);
  88. return abs * abs;
  89. }
  90. """
  91. kernel = ReductionKernel(
  92. self.context,
  93. np.float,
  94. neutral = "0",
  95. reduce_expr="a + b",
  96. map_expr="probability(target, i, amps[i])",
  97. arguments="__global cfloat_t *amps, __global int target",
  98. preamble=preamble
  99. )
  100. return kernel(self.buffer, target).get()
  101. def measure_qubit(self, target, samples):
  102. probability_of_0 = self.qubit_probability(target)
  103. choices = np.random.choice(
  104. [0, 1],
  105. samples,
  106. p=[probability_of_0, 1-probability_of_0]
  107. )
  108. results = defaultdict(int)
  109. for i in choices:
  110. results[np.binary_repr(i, width=1)] += 1
  111. return dict(results)
  112. def single_amplitude(self, i):
  113. """Gets a single probability amplitude"""
  114. out = pycl_array.to_device(
  115. self.queue,
  116. np.empty(1, dtype=np.complex64)
  117. )
  118. self.program.get_single_amplitude(
  119. self.queue,
  120. (1, ),
  121. None,
  122. self.buffer.data,
  123. out.data,
  124. np.int32(i)
  125. )
  126. return out[0]
  127. def amplitudes(self):
  128. """Gets the probability amplitudes"""
  129. return self.buffer.get()
  130. def probabilities(self):
  131. """Gets the squared absolute value of each of the amplitudes"""
  132. out = pycl_array.to_device(
  133. self.queue,
  134. np.zeros(2**self.num_qubits, dtype=np.float32)
  135. )
  136. self.program.calculate_probabilities(
  137. self.queue,
  138. self.buffer.shape,
  139. None,
  140. self.buffer.data,
  141. out.data
  142. )
  143. return out.get()
  144. def release(self):
  145. self.buffer.base_data.release()