backend.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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 = """
  10. #include <pyopencl-complex.h>
  11. /*
  12. * Returns the nth number where a given digit
  13. * is cleared in the binary representation of the number
  14. */
  15. static int nth_cleared(int n, int target)
  16. {
  17. int mask = (1 << target) - 1;
  18. int not_mask = ~mask;
  19. return (n & mask) | ((n & not_mask) << 1);
  20. }
  21. ///////////////////////////////////////////////
  22. // KERNELS
  23. ///////////////////////////////////////////////
  24. /*
  25. * Applies a single qubit gate to the register.
  26. * The gate matrix must be given in the form:
  27. *
  28. * A B
  29. * C D
  30. */
  31. __kernel void apply_gate(
  32. __global cfloat_t *amplitudes,
  33. int target,
  34. cfloat_t A,
  35. cfloat_t B,
  36. cfloat_t C,
  37. cfloat_t D)
  38. {
  39. int const global_id = get_global_id(0);
  40. int const zero_state = nth_cleared(global_id, target);
  41. // int const zero_state = state & (~(1 << target)); // Could just be state
  42. int const one_state = zero_state | (1 << target);
  43. cfloat_t const zero_amp = amplitudes[zero_state];
  44. cfloat_t const one_amp = amplitudes[one_state];
  45. amplitudes[zero_state] = cfloat_add(cfloat_mul(A, zero_amp), cfloat_mul(B, one_amp));
  46. amplitudes[one_state] = cfloat_add(cfloat_mul(D, one_amp), cfloat_mul(C, zero_amp));
  47. }
  48. /*
  49. * Applies a controlled single qubit gate to the register.
  50. */
  51. __kernel void apply_controlled_gate(
  52. __global cfloat_t *amplitudes,
  53. int control,
  54. int target,
  55. cfloat_t A,
  56. cfloat_t B,
  57. cfloat_t C,
  58. cfloat_t D)
  59. {
  60. int const global_id = get_global_id(0);
  61. int const zero_state = nth_cleared(global_id, target);
  62. int const one_state = zero_state | (1 << target); // Set the target bit
  63. int const control_val_zero = (((1 << control) & zero_state) > 0) ? 1 : 0;
  64. int const control_val_one = (((1 << control) & one_state) > 0) ? 1 : 0;
  65. cfloat_t const zero_amp = amplitudes[zero_state];
  66. cfloat_t const one_amp = amplitudes[one_state];
  67. if (control_val_zero == 1)
  68. {
  69. amplitudes[zero_state] = cfloat_add(cfloat_mul(A, zero_amp), cfloat_mul(B, one_amp));
  70. }
  71. if (control_val_one == 1)
  72. {
  73. amplitudes[one_state] = cfloat_add(cfloat_mul(D, one_amp), cfloat_mul(C, zero_amp));
  74. }
  75. }
  76. /*
  77. * Applies a controlled-controlled single qubit gate to the register.
  78. * NOT MIGRATED
  79. */
  80. __kernel void apply_controlled_controlled_gate(
  81. __global cfloat_t *const amplitudes,
  82. __global cfloat_t *amps,
  83. int control1,
  84. int control2,
  85. int target,
  86. cfloat_t A,
  87. cfloat_t B,
  88. cfloat_t C,
  89. cfloat_t D)
  90. {
  91. int const state = get_global_id(0);
  92. cfloat_t const amp = amplitudes[state];
  93. int const zero_state = state & (~(1 << target));
  94. int const one_state = state | (1 << target);
  95. int const bit_val = (((1 << target) & state) > 0) ? 1 : 0;
  96. int const control1_val = (((1 << control1) & state) > 0) ? 1 : 0;
  97. int const control2_val = (((1 << control2) & state) > 0) ? 1 : 0;
  98. if (control1_val == 0 || control2_val == 0)
  99. {
  100. // Control is 0, don't apply gate
  101. amps[state] = amp;
  102. }
  103. else
  104. {
  105. // control is 1, apply gate.
  106. if (bit_val == 0)
  107. {
  108. // Bitval = 0
  109. amps[state] = cfloat_add(cfloat_mul(A, amp), cfloat_mul(B, amplitudes[one_state]));
  110. }
  111. else
  112. {
  113. amps[state] = cfloat_add(cfloat_mul(D, amp), cfloat_mul(C, amplitudes[zero_state]));
  114. }
  115. }
  116. }
  117. /*
  118. * Swaps the states of two qubits in the register
  119. * NOT MIGRATED
  120. */
  121. // __kernel void swap(
  122. // __global cfloat_t *const amplitudes,
  123. // __global cfloat_t *amps,
  124. // int first_qubit,
  125. // int second_qubit)
  126. // {
  127. // int const state = get_global_id(0);
  128. // int const first_bit_mask = 1 << first_qubit;
  129. // int const second_bit_mask = 1 << second_qubit;
  130. // int const new_second_bit = ((state & first_bit_mask) >> first_qubit) << second_qubit;
  131. // int const new_first_bit = ((state & second_bit_mask) >> second_qubit) << first_qubit;
  132. // int const new_state = (state & !first_bit_mask & !second_bit_mask) | new_first_bit | new_second_bit;
  133. // amps[new_state] = amplitudes[state];
  134. // }
  135. /**
  136. * Get a single amplitude
  137. */
  138. __kernel void get_single_amplitude(
  139. __global cfloat_t *const amplitudes,
  140. __global cfloat_t *out,
  141. int i)
  142. {
  143. out[0] = amplitudes[i];
  144. }
  145. /**
  146. * Calculates The Probabilities Of A State Vector
  147. */
  148. __kernel void calculate_probabilities(
  149. __global cfloat_t *const amplitudes,
  150. __global float *probabilities)
  151. {
  152. int const state = get_global_id(0);
  153. cfloat_t amp = amplitudes[state];
  154. probabilities[state] = cfloat_abs(cfloat_mul(amp, amp));
  155. }
  156. /**
  157. * Initializes a register to the value 1|0..100...0>
  158. * ^ target
  159. */
  160. __kernel void initialize_register(
  161. __global cfloat_t *amplitudes,
  162. int const target)
  163. {
  164. int const state = get_global_id(0);
  165. if (state == target)
  166. {
  167. amplitudes[state] = cfloat_new(1, 0);
  168. }
  169. else
  170. {
  171. amplitudes[state] = cfloat_new(0, 0);
  172. }
  173. }
  174. """
  175. class Backend:
  176. """
  177. A class for the OpenCL backend to the simulator.
  178. This class shouldn't be used directly, as many of the
  179. methods don't have the same input checking as the State
  180. class.
  181. """
  182. def __init__(self, num_qubits, dtype=np.complex64):
  183. """
  184. Initialize a new OpenCL Backend
  185. Takes an argument of the number of qubits to use
  186. in the register, and returns the backend.
  187. """
  188. self.num_qubits = num_qubits
  189. self.dtype = dtype
  190. self.context = cl.create_some_context()
  191. self.queue = cl.CommandQueue(self.context)
  192. self.program = cl.Program(self.context, kernel).build(options="-cl-no-signed-zeros -cl-mad-enable -cl-fast-relaxed-math")
  193. # Buffer for the state vector
  194. self.buffer = pycl_array.to_device(
  195. self.queue,
  196. np.eye(1, 2**num_qubits, dtype=dtype)
  197. )
  198. def apply_gate(self, gate, target):
  199. """Applies a gate to the quantum register"""
  200. self.program.apply_gate(
  201. self.queue,
  202. [int(self.buffer.shape[1] / 2)],
  203. None,
  204. self.buffer.data,
  205. np.int32(target),
  206. self.dtype(gate.a),
  207. self.dtype(gate.b),
  208. self.dtype(gate.c),
  209. self.dtype(gate.d)
  210. )
  211. def apply_controlled_gate(self, gate, control, target):
  212. """Applies a controlled gate to the quantum register"""
  213. self.program.apply_controlled_gate(
  214. self.queue,
  215. [int(self.buffer.shape[1] / 2)],
  216. None,
  217. self.buffer.data,
  218. np.int32(control),
  219. np.int32(target),
  220. self.dtype(gate.a),
  221. self.dtype(gate.b),
  222. self.dtype(gate.c),
  223. self.dtype(gate.d)
  224. )
  225. def measure(self, samples=1):
  226. """Measure the state of a register"""
  227. # This is a really horrible method that needs a rewrite - the memory
  228. # is attrocious
  229. probabilities = self.probabilities()
  230. # print(probabilities)
  231. # print(np.sum(self.amplitudes()))
  232. choices = np.random.choice(
  233. np.arange(0, 2**self.num_qubits),
  234. samples,
  235. p=probabilities
  236. )
  237. results = defaultdict(int)
  238. for i in choices:
  239. results[np.binary_repr(i, width=self.num_qubits)] += 1
  240. return dict(results)
  241. def qubit_probability(self, target):
  242. """Get the probability of a single qubit begin measured as '0'"""
  243. preamble = """
  244. #include <pyopencl-complex.h>
  245. float probability(int target, int i, cfloat_t amp) {
  246. if ((i & (1 << target )) != 0) {
  247. return 0;
  248. }
  249. // return 6.0;
  250. float abs = cfloat_abs(amp);
  251. return abs * abs;
  252. }
  253. """
  254. kernel = ReductionKernel(
  255. self.context,
  256. np.float,
  257. neutral = "0",
  258. reduce_expr="a + b",
  259. map_expr="probability(target, i, amps[i])",
  260. arguments="__global cfloat_t *amps, __global int target",
  261. preamble=preamble
  262. )
  263. return kernel(self.buffer, target).get()
  264. def measure_qubit(self, target, samples):
  265. probability_of_0 = self.qubit_probability(target)
  266. choices = np.random.choice(
  267. [0, 1],
  268. samples,
  269. p=[probability_of_0, 1-probability_of_0]
  270. )
  271. results = defaultdict(int)
  272. for i in choices:
  273. results[np.binary_repr(i, width=1)] += 1
  274. return dict(results)
  275. def single_amplitude(self, i):
  276. """Gets a single probability amplitude"""
  277. out = pycl_array.to_device(
  278. self.queue,
  279. np.empty(1, dtype=np.complex64)
  280. )
  281. self.program.get_single_amplitude(
  282. self.queue,
  283. (1, ),
  284. None,
  285. self.buffer.data,
  286. out.data,
  287. np.int32(i)
  288. )
  289. return out[0]
  290. def amplitudes(self):
  291. """Gets the probability amplitudes"""
  292. return self.buffer.get()
  293. def probabilities(self):
  294. """Gets the squared absolute value of each of the amplitudes"""
  295. out = pycl_array.to_device(
  296. self.queue,
  297. np.zeros(2**self.num_qubits, dtype=np.float32)
  298. )
  299. self.program.calculate_probabilities(
  300. self.queue,
  301. out.shape,
  302. None,
  303. self.buffer.data,
  304. out.data
  305. )
  306. return out.get()
  307. def release(self):
  308. self.buffer.base_data.release()