backend.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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. */
  79. __kernel void apply_controlled_controlled_gate(
  80. __global cfloat_t *amplitudes,
  81. int control,
  82. int control_2,
  83. int target,
  84. cfloat_t A,
  85. cfloat_t B,
  86. cfloat_t C,
  87. cfloat_t D)
  88. {
  89. int const global_id = get_global_id(0);
  90. int const zero_state = nth_cleared(global_id, target);
  91. int const one_state = zero_state | (1 << target); // Set the target bit
  92. int const control_val_zero = (((1 << control) & zero_state) > 0) ? 1 : 0;
  93. int const control_val_one = (((1 << control) & one_state) > 0) ? 1 : 0;
  94. int const control_val_two_zero = (((1 << control_2) & zero_state) > 0) ? 1 : 0;
  95. int const control_val_two_one = (((1 << control_2) & one_state) > 0) ? 1 : 0;
  96. cfloat_t const zero_amp = amplitudes[zero_state];
  97. cfloat_t const one_amp = amplitudes[one_state];
  98. if (control_val_zero == 1 && control_val_two_zero == 1)
  99. {
  100. amplitudes[zero_state] = cfloat_add(cfloat_mul(A, zero_amp), cfloat_mul(B, one_amp));
  101. }
  102. if (control_val_one == 1 && control_val_two_one == 1)
  103. {
  104. amplitudes[one_state] = cfloat_add(cfloat_mul(D, one_amp), cfloat_mul(C, zero_amp));
  105. }
  106. }
  107. /*
  108. * Swaps the states of two qubits in the register
  109. * NOT MIGRATED
  110. */
  111. // __kernel void swap(
  112. // __global cfloat_t *const amplitudes,
  113. // __global cfloat_t *amps,
  114. // int first_qubit,
  115. // int second_qubit)
  116. // {
  117. // int const state = get_global_id(0);
  118. // int const first_bit_mask = 1 << first_qubit;
  119. // int const second_bit_mask = 1 << second_qubit;
  120. // int const new_second_bit = ((state & first_bit_mask) >> first_qubit) << second_qubit;
  121. // int const new_first_bit = ((state & second_bit_mask) >> second_qubit) << first_qubit;
  122. // int const new_state = (state & !first_bit_mask & !second_bit_mask) | new_first_bit | new_second_bit;
  123. // amps[new_state] = amplitudes[state];
  124. // }
  125. /**
  126. * Get a single amplitude
  127. */
  128. __kernel void get_single_amplitude(
  129. __global cfloat_t *const amplitudes,
  130. __global cfloat_t *out,
  131. int i)
  132. {
  133. out[0] = amplitudes[i];
  134. }
  135. /**
  136. * Calculates The Probabilities Of A State Vector
  137. */
  138. __kernel void calculate_probabilities(
  139. __global cfloat_t *const amplitudes,
  140. __global float *probabilities)
  141. {
  142. int const state = get_global_id(0);
  143. cfloat_t amp = amplitudes[state];
  144. probabilities[state] = cfloat_abs(cfloat_mul(amp, amp));
  145. }
  146. /**
  147. * Initializes a register to the value 1|0..100...0>
  148. * ^ target
  149. */
  150. __kernel void initialize_register(
  151. __global cfloat_t *amplitudes,
  152. int const target)
  153. {
  154. int const state = get_global_id(0);
  155. if (state == target)
  156. {
  157. amplitudes[state] = cfloat_new(1, 0);
  158. }
  159. else
  160. {
  161. amplitudes[state] = cfloat_new(0, 0);
  162. }
  163. }
  164. /**
  165. * Collapses a qubit in the register
  166. */
  167. __kernel void collapse(
  168. __global cfloat_t *amplitudes,
  169. int const target,
  170. int const outcome,
  171. float const norm)
  172. {
  173. int const state = get_global_id(0);
  174. if (((state >> target) & 1) == outcome) {
  175. amplitudes[state] = cfloat_mul(amplitudes[state], cfloat_new(norm, 0.0));
  176. }
  177. else
  178. {
  179. amplitudes[state] = cfloat_new(0.0, 0.0);
  180. }
  181. }
  182. """
  183. # Setup the OpenCL Context here to not prompt every execution
  184. context = None
  185. program = None
  186. class Backend:
  187. """
  188. A class for the OpenCL backend to the simulator.
  189. This class shouldn't be used directly, as many of the
  190. methods don't have the same input checking as the State
  191. class.
  192. """
  193. # @profile
  194. def __init__(self, num_qubits, dtype=np.complex64):
  195. if not context:
  196. create_context()
  197. """
  198. Initialize a new OpenCL Backend
  199. Takes an argument of the number of qubits to use
  200. in the register, and returns the backend.
  201. """
  202. self.num_qubits = num_qubits
  203. self.dtype = dtype
  204. self.queue = cl.CommandQueue(context)
  205. # Buffer for the state vector
  206. self.buffer = pycl_array.to_device(
  207. self.queue,
  208. np.eye(1, 2**num_qubits, dtype=dtype)
  209. )
  210. def apply_gate(self, gate, target):
  211. """Applies a gate to the quantum register"""
  212. program.apply_gate(
  213. self.queue,
  214. [int(2**self.num_qubits / 2)],
  215. None,
  216. self.buffer.data,
  217. np.int32(target),
  218. self.dtype(gate.a),
  219. self.dtype(gate.b),
  220. self.dtype(gate.c),
  221. self.dtype(gate.d)
  222. )
  223. def apply_controlled_gate(self, gate, control, target):
  224. """Applies a controlled gate to the quantum register"""
  225. program.apply_controlled_gate(
  226. self.queue,
  227. [int(2**self.num_qubits / 2)],
  228. None,
  229. self.buffer.data,
  230. np.int32(control),
  231. np.int32(target),
  232. self.dtype(gate.a),
  233. self.dtype(gate.b),
  234. self.dtype(gate.c),
  235. self.dtype(gate.d)
  236. )
  237. def apply_controlled_controlled_gate(self, gate, control1, control2, target):
  238. """Applies a controlled controlled gate (such as a toffoli gate) to the quantum register"""
  239. program.apply_controlled_controlled_gate(
  240. self.queue,
  241. [int(2**self.num_qubits / 2)],
  242. None,
  243. self.buffer.data,
  244. np.int32(control1),
  245. np.int32(control2),
  246. np.int32(target),
  247. self.dtype(gate.a),
  248. self.dtype(gate.b),
  249. self.dtype(gate.c),
  250. self.dtype(gate.d)
  251. )
  252. def seed(self, val):
  253. random.seed(val)
  254. def measure(self, samples=1):
  255. """Measure the state of a register"""
  256. # This is a really horrible method that needs a rewrite - the memory
  257. # is attrocious
  258. probabilities = self.probabilities()
  259. # print(probabilities)
  260. # print(np.sum(self.amplitudes()))
  261. choices = np.random.choice(
  262. np.arange(0, 2**self.num_qubits),
  263. samples,
  264. p=probabilities
  265. )
  266. results = defaultdict(int)
  267. for i in choices:
  268. results[np.binary_repr(i, width=self.num_qubits)] += 1
  269. return dict(results)
  270. def measure_first(self, num, samples):
  271. probabilities = self.probabilities()
  272. # print(probabilities)
  273. # print(np.sum(self.amplitudes()))
  274. choices = np.random.choice(
  275. np.arange(0, 2**self.num_qubits),
  276. samples,
  277. p=probabilities
  278. )
  279. results = defaultdict(int)
  280. for i in choices:
  281. key = np.binary_repr(i, width=self.num_qubits)[-num:]
  282. results[key] += 1
  283. return dict(results)
  284. def qubit_probability(self, target):
  285. """Get the probability of a single qubit begin measured as '0'"""
  286. preamble = """
  287. #include <pyopencl-complex.h>
  288. float probability(int target, int i, cfloat_t amp) {
  289. if ((i & (1 << target )) != 0) {
  290. return 0;
  291. }
  292. // return 6.0;
  293. float abs = cfloat_abs(amp);
  294. return abs * abs;
  295. }
  296. """
  297. kernel = ReductionKernel(
  298. context,
  299. np.float,
  300. neutral = "0",
  301. reduce_expr="a + b",
  302. map_expr="probability(target, i, amps[i])",
  303. arguments="__global cfloat_t *amps, __global int target",
  304. preamble=preamble
  305. )
  306. return kernel(self.buffer, target).get()
  307. def reset(self, target):
  308. probability_of_0 = self.qubit_probability(target)
  309. norm = 1 / np.sqrt(probability_of_0)
  310. program.collapse(
  311. self.queue,
  312. [int(2**self.num_qubits)],
  313. # 2**self.num_qubits,
  314. None,
  315. self.buffer.data,
  316. np.int32(target),
  317. np.int32(0),
  318. np.float32(norm)
  319. )
  320. def measure_collapse(self, target):
  321. probability_of_0 = self.qubit_probability(target)
  322. random_number = random.random()
  323. if random_number <= probability_of_0:
  324. outcome = '0'
  325. norm = 1 / np.sqrt(probability_of_0)
  326. else:
  327. outcome = '1'
  328. norm = 1 / np.sqrt(1 - probability_of_0)
  329. program.collapse(
  330. self.queue,
  331. [int(2**self.num_qubits)],
  332. # 2**self.num_qubits,
  333. None,
  334. self.buffer.data,
  335. np.int32(target),
  336. np.int32(outcome),
  337. np.float32(norm)
  338. )
  339. return outcome
  340. def measure_qubit(self, target, samples):
  341. probability_of_0 = self.qubit_probability(target)
  342. choices = np.random.choice(
  343. [0, 1],
  344. samples,
  345. p=[probability_of_0, 1-probability_of_0]
  346. )
  347. results = defaultdict(int)
  348. for i in choices:
  349. results[np.binary_repr(i, width=1)] += 1
  350. return dict(results)
  351. def single_amplitude(self, i):
  352. """Gets a single probability amplitude"""
  353. out = pycl_array.to_device(
  354. self.queue,
  355. np.empty(1, dtype=np.complex64)
  356. )
  357. program.get_single_amplitude(
  358. self.queue,
  359. (1, ),
  360. None,
  361. self.buffer.data,
  362. out.data,
  363. np.int32(i)
  364. )
  365. return out[0]
  366. def amplitudes(self):
  367. """Gets the probability amplitudes"""
  368. return self.buffer.get()
  369. def probabilities(self):
  370. """Gets the squared absolute value of each of the amplitudes"""
  371. out = pycl_array.to_device(
  372. self.queue,
  373. np.zeros(2**self.num_qubits, dtype=np.float32)
  374. )
  375. program.calculate_probabilities(
  376. self.queue,
  377. out.shape,
  378. None,
  379. self.buffer.data,
  380. out.data
  381. )
  382. return out.get()
  383. def release(self):
  384. self.buffer.base_data.release()
  385. def create_context():
  386. global context
  387. global program
  388. context = cl.create_some_context()
  389. program = cl.Program(context, kernel).build(options="-cl-no-signed-zeros -cl-mad-enable -cl-fast-relaxed-math")