Browse Source

Speed up some things to do with benchmarking

Adam Kelly 5 years ago
parent
commit
85a5a72972
7 changed files with 2695 additions and 141 deletions
  1. 146 0
      benchmark/Visualize.ipynb
  2. 159 0
      benchmark/benchmark.py
  3. 2315 0
      benchmark/benchmark_data.csv
  4. 0 118
      benchmarking.py
  5. 22 0
      examples/qft.py
  6. 25 15
      qcgpu/backend.py
  7. 28 8
      qcgpu/state.py

File diff suppressed because it is too large
+ 146 - 0
benchmark/Visualize.ipynb


+ 159 - 0
benchmark/benchmark.py

@@ -0,0 +1,159 @@
+import click
+import time
+import random
+import statistics
+import csv
+import os.path
+import math
+
+from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
+from qiskit.wrapper import load_qasm_file
+from qiskit import QISKitError, execute, Aer
+
+from projectq import MainEngine
+import projectq.ops as ops
+from projectq.backends import Simulator
+
+import qcgpu
+
+# Implementation of the Quantum Fourier Transform
+def construct_circuit(num_qubits):
+    q = QuantumRegister(num_qubits)
+    circ = QuantumCircuit(q)
+
+    # Quantum Fourier Transform
+    for j in range(num_qubits):
+        for k in range(j):
+            circ.cu1(math.pi/float(2**(j-k)), q[j], q[k])
+        circ.h(q[j])
+
+    return circ
+
+
+# Benchmarking functions
+qiskit_backend = Aer.get_backend('statevector_simulator')
+eng = MainEngine(backend=Simulator(), engine_list=[])
+
+# Setup the OpenCL Device
+qcgpu.backend.create_context()
+
+def bench_qiskit(qc):
+    start = time.time()
+    job_sim = execute(qc, qiskit_backend)
+    sim_result = job_sim.result()
+    return time.time() - start
+
+def bench_qcgpu(num_qubits):
+    start = time.time()
+    state = qcgpu.State(num_qubits)
+
+    for j in range(num_qubits):
+        for k in range(j):
+            state.cu1(j, k, math.pi/float(2**(j-k)))
+        state.h(j)
+
+    state.backend.queue.finish()
+    return time.time() - start
+
+def bench_projectq(num_qubits):
+    start = time.time()
+
+    q = eng.allocate_qureg(num_qubits)
+
+    for j in range(num_qubits):
+        for k in range(j):
+            ops.CRz(math.pi / float(2**(j-k))) | (q[j], q[k])
+    ops.H | q[j]
+    eng.flush()
+
+    t = time.time() - start
+    # measure to get rid of runtime error message
+    for j in q:
+        ops.Measure | j
+
+    return t
+    
+
+# Reporting
+def create_csv(filename):
+    file_exists = os.path.isfile(filename)
+    csvfile = open(filename, 'a')
+   
+    headers = ['name', 'num_qubits', 'time']
+    writer = csv.DictWriter(csvfile, delimiter=',', lineterminator='\n',fieldnames=headers)
+
+    if not file_exists:
+        writer.writeheader()  # file doesn't exist yet, write a header
+
+    return writer
+
+def write_csv(writer, data):
+    writer.writerow(data)
+
+
+
+@click.command()
+@click.option('--samples', default=5, help='Number of samples to take for each qubit.')
+@click.option('--qubits', default=5, help='How many qubits you want to test for')
+@click.option('--out', default='benchmark_data.csv', help='Where to store the CSV output of each test')
+@click.option('--single', default=False, help='Only run the benchmark for a single amount of qubits, and print an analysis')
+def benchmark(samples, qubits, out, single):
+    if single:
+        # functions = bench_qcgpu, bench_qiskit, bench_projectq
+        functions = bench_projectq, 
+        times = {f.__name__: [] for f in functions}
+
+        names = []
+        means = []
+
+        qc = construct_circuit(qubits)
+        # Run the benchmarks
+        for i in range(samples):
+            progress = (i) / (samples)
+            if samples > 1:
+                print("\rProgress: [{0:50s}] {1:.1f}%".format('#' * int(progress * 50), progress*100), end="", flush=True)
+
+            func = random.choice(functions)
+            if func.__name__ != 'bench_qiskit':
+                t = func(qubits)
+            else:
+                t = func(qc)
+            times[func.__name__].append(t)
+
+        print('')
+
+        for name, numbers in times.items():
+            print('FUNCTION:', name, 'Used', len(numbers), 'times')
+            print('\tMEDIAN', statistics.median(numbers))
+            print('\tMEAN  ', statistics.mean(numbers))
+            if len(numbers) > 1:
+                print('\tSTDEV ', statistics.stdev(numbers))
+
+        return
+
+    
+
+    functions = bench_qcgpu, bench_qiskit, bench_projectq
+    # times = {f.__name__: [] for f in functions}
+    writer = create_csv(out)
+
+    for n in range(23, qubits):
+        # Progress counter
+        progress = (n+1) / (qubits)
+        print("\rProgress: [{0:50s}] {1:.1f}%".format('#' * int(progress * 50), progress*100), end="", flush=True)
+
+        # Construct the circuit
+        qc = construct_circuit(n+1)
+
+        # Run the benchmarks
+        for i in range(samples):
+            func = random.choice(functions)
+            if func.__name__ != 'bench_qiskit':
+                t = func(n + 1)
+            else:
+                t = func(qc)
+            # times[func.__name__].append(t)
+            write_csv(writer, {'name': func.__name__, 'num_qubits': n+1, 'time': t})
+
+if __name__ == '__main__':
+    benchmark()

File diff suppressed because it is too large
+ 2315 - 0
benchmark/benchmark_data.csv


+ 0 - 118
benchmarking.py

@@ -1,118 +0,0 @@
-# Reporting Imports
-import time
-import random
-import statistics
-
-# QCGPU Implementation
-import qcgpu
-
-def bench_qcgpu(n, depth):
-    h = qcgpu.gate.h()
-    x = qcgpu.gate.x()
-    t = qcgpu.gate.t()
-
-    state = qcgpu.State(n)
-
-    start = time.time()
-
-    for level in range(depth):
-        for q in range(n):
-    
-            state.apply_gate(h, q)
-            state.apply_gate(t, q)
-
-            if q != 0:
-                state.apply_controlled_gate(x, q, 0)
-
-    return time.time() - start
-
-# Qiskit Implementation
-from qiskit import ClassicalRegister, QuantumRegister
-from qiskit import QuantumCircuit, execute
-
-def bench_qiskit(n, depth):
-    q = QuantumRegister(n)
-    c = ClassicalRegister(n)
-    qc = QuantumCircuit(q, c)
-    
-    start = time.time()
-
-    for level in range(depth):
-        for i in range(n):
-            qc.h(q[i])
-            qc.t(q[i])
-
-            if i != 0:
-                qc.cx(q[i], q[0])
-
-    job_sim = execute(qc, "local_statevector_simulator")
-
-    job_sim.result()
-    
-    return time.time() - start
-
-# ProjectQ Implementation
-from projectq import MainEngine
-import projectq.ops as ops
-from projectq.backends import Simulator
-from projectq.types import Qureg
-
-def bench_projectq(n, depth):
-    eng = MainEngine(backend=Simulator(gate_fusion=True), engine_list=[])
-    qbits = eng.allocate_qureg(n)
-
-    start = time.time()
-
-    for level in range(depth):
-        for q in qbits:
-            ops.H | q
-            ops.T | q
-            if q != qbits[0]:
-                ops.CNOT | (q, qbits[0])
-
-    for q in qbits:
-        ops.Measure | q
-    return time.time() - start
-
-# Reporting
-
-functions = bench_qcgpu, bench_qiskit#, bench_projectq
-# functions = bench_qiskit,
-
-times = {f.__name__: [] for f in functions}
-
-names = []
-means = []
-
-samples = 100
-for i in range(samples):  # adjust accordingly so whole thing takes a few sec
-    progress = i / samples
-    print("\rProgress: [{0:50s}] {1:.1f}%".format('#' * int(progress * 50), progress*100), end="", flush=True)
-    func = random.choice(functions)
-    t = func(20,10)
-    times[func.__name__].append(t)
-
-print('')
-
-for name, numbers in times.items():
-    print('FUNCTION:', name, 'Used', len(numbers), 'times')
-    print('\tMEDIAN', statistics.median(numbers))
-    print('\tMEAN  ', statistics.mean(numbers))
-    print('\tSTDEV ', statistics.stdev(numbers))
-    means.append(statistics.mean(numbers))
-    names.append(name)
-
-# Graphing
-
-import numpy as np
-import matplotlib.pyplot as plt
-
-index = np.arange(len(names))
-print(index)
-
-plt.bar(index, means)
-plt.xlabel('Function')
-plt.ylabel('Time (s)')
-plt.xticks(index, names)
-plt.title('Performance')
-plt.show()

+ 22 - 0
examples/qft.py

@@ -0,0 +1,22 @@
+"""
+QFT
+=====================
+
+This is an implementation of the quantum Fourier transform.
+"""
+
+import qcgpu
+import math
+
+def qft():
+    print('start')
+    state = qcgpu.State(24)
+    num_qubits = state.num_qubits
+
+    for j in range(num_qubits):
+        for k in range(j):
+            state.cu1(j, k, math.pi/float(2**(j-k)))
+        state.h(j)
+
+if __name__== "__main__":
+    qft()

+ 25 - 15
qcgpu/backend.py

@@ -6,7 +6,6 @@ import pyopencl.array as pycl_array
 from pyopencl.reduction import ReductionKernel
 from collections import defaultdict
 
-
 # Get the OpenCL kernel
 kernel = """
 #include <pyopencl-complex.h>
@@ -224,6 +223,10 @@ __kernel void collapse(
 }
 """
 
+# Setup the OpenCL Context here to not prompt every execution
+context = None
+program = None
+
 
 class Backend:
     """
@@ -234,7 +237,11 @@ class Backend:
     class.
     """
 
+    # @profile
     def __init__(self, num_qubits, dtype=np.complex64):
+        if not context:
+            create_context()
+        
         """
         Initialize a new OpenCL Backend
 
@@ -244,9 +251,7 @@ class Backend:
         self.num_qubits = num_qubits
         self.dtype = dtype
 
-        self.context = cl.create_some_context()
-        self.queue = cl.CommandQueue(self.context)
-        self.program = cl.Program(self.context, kernel).build(options="-cl-no-signed-zeros -cl-mad-enable -cl-fast-relaxed-math")
+        self.queue = cl.CommandQueue(context)
 
         # Buffer for the state vector
         self.buffer = pycl_array.to_device(
@@ -256,10 +261,9 @@ class Backend:
 
     def apply_gate(self, gate, target):
         """Applies a gate to the quantum register"""
-
-        self.program.apply_gate(
+        program.apply_gate(
             self.queue,
-            [int(self.buffer.shape[1] / 2)],
+            [int(2**self.num_qubits / 2)],
             None,
             self.buffer.data,
             np.int32(target),
@@ -272,9 +276,9 @@ class Backend:
     def apply_controlled_gate(self, gate, control, target):
         """Applies a controlled gate to the quantum register"""
 
-        self.program.apply_controlled_gate(
+        program.apply_controlled_gate(
             self.queue,
-            [int(self.buffer.shape[1] / 2)],
+            [int(2**self.num_qubits / 2)],
             None,
             self.buffer.data,
             np.int32(control),
@@ -327,7 +331,7 @@ class Backend:
         
 
         kernel = ReductionKernel(
-            self.context, 
+            context, 
             np.float, 
             neutral = "0",
             reduce_expr="a + b",
@@ -349,10 +353,10 @@ class Backend:
             outcome = '1'
             norm = 1 / np.sqrt(1 - probability_of_0)
 
-        self.program.collapse(
+        program.collapse(
             self.queue,
-            [int(self.buffer.shape[1])],
-            # self.buffer.shape,
+            [int(2**self.num_qubits)],
+            # 2**self.num_qubits,
             None,
             self.buffer.data,
             np.int32(target),
@@ -383,7 +387,7 @@ class Backend:
             np.empty(1, dtype=np.complex64)
         )
 
-        self.program.get_single_amplitude(
+        program.get_single_amplitude(
             self.queue, 
             (1, ), 
             None, 
@@ -405,7 +409,7 @@ class Backend:
             np.zeros(2**self.num_qubits, dtype=np.float32)
         )
 
-        self.program.calculate_probabilities(
+        program.calculate_probabilities(
             self.queue,
             out.shape,
             None,
@@ -417,3 +421,9 @@ class Backend:
         
     def release(self):
         self.buffer.base_data.release()
+    
+def create_context():
+    global context
+    global program
+    context = cl.create_some_context()
+    program = cl.Program(context, kernel).build(options="-cl-no-signed-zeros -cl-mad-enable -cl-fast-relaxed-math")

+ 28 - 8
qcgpu/state.py

@@ -67,7 +67,8 @@ class State:
                 is to be applied to.
         """
         if not isinstance(target, int) or target < 0:
-            raise ValueError("target must be an int > 0")
+            print(target)
+            raise ValueError("target must be an int >= 0")
 
         # TODO: Check that gate is correct
 
@@ -80,10 +81,10 @@ class State:
 
     def apply_controlled_gate(self, gate, control, target):
         if not isinstance(target, int) or target < 0:
-            raise ValueError("target must be an int > 0")
+            raise ValueError("target must be an int >= 0")
         
         if not isinstance(control, int) or control < 0:
-            raise ValueError("control must be an int > 0")
+            raise ValueError("control must be an int >= 0")
 
         # TODO: Check that gate is correct
 
@@ -104,9 +105,6 @@ class State:
     def probabilities(self):
         return self.backend.probabilities()[0]
 
-    def flush(self):
-        self.backend.release()
-
     def __repr__(self):
         """A string representation of the state"""
 
@@ -153,7 +151,7 @@ class State:
             [c, d]
         ])
         self.apply_gate(qcgpu.Gate(gate_matrix), target)
-
+    
     def u1(self, target, lda):
         self.u(target, 0, 0, lda)
 
@@ -161,4 +159,26 @@ class State:
         self.u(target, np.pi / 2, phi, lda)
 
     def u3(self, target, theta, phi, lda):
-        self.u(target, theta, phi, lda)
+        self.u(target, theta, phi, lda)
+
+    def cu(self, control, target, theta, phi, lda):
+        a = np.exp(-1j * (phi + lda) / 2) * np.cos(theta / 2)
+        b = - np.exp(-1j * (phi - lda) / 2) * np.sin(theta / 2)
+        c = np.exp(1j * (phi - lda) / 2) * np.sin(theta / 2)    
+        d = np.exp(1j * (phi + lda) / 2) * np.cos(theta / 2)
+    
+        gate_matrix = np.array([
+            [a, b],
+            [c, d]
+        ])
+        self.apply_controlled_gate(qcgpu.Gate(gate_matrix), control, target)
+
+
+    def cu1(self, control, target, lda):
+        self.cu(control, target, 0, 0, lda)
+
+    def cu2(self, control, target, phi, lda):
+        self.cu(control, target, np.pi / 2, phi, lda)
+
+    def cu3(self, control, target, theta, phi, lda):
+        self.cu(control, target, theta, phi, lda)