Browse Source

Deploy QCGPU/qcgpu to github.com/QCGPU/qcgpu.git:gh-pages

Deployment Bot (from Travis CI) 6 years ago
parent
commit
08cf218e57

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = false
+insert_final_newline = false

+ 258 - 0
.gitignore

@@ -0,0 +1,258 @@
+
+# Created by https://www.gitignore.io/api/c,c++,cuda,linux,macos,windows
+
+### C ###
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+### C++ ###
+# Prerequisites
+
+# Compiled Object files
+*.slo
+
+# Precompiled Headers
+
+# Compiled Dynamic libraries
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+
+# Executables
+
+### CUDA ###
+*.i
+*.ii
+*.gpu
+*.ptx
+*.cubin
+*.fatbin
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+.vscode
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+# pytest
+.pytest_cache
+
+# End of https://www.gitignore.io/api/c,c++,cuda,linux,macos,windows

+ 41 - 0
.travis.yml

@@ -0,0 +1,41 @@
+language: generic
+os: osx
+
+# language: python
+# python:
+#   - "2.7.15"
+#   - "3.6.6"
+#   - "3.7-dev"
+
+# dist: trusty
+
+# addons:
+#   apt:
+#     packages:
+#       - opencl-headers
+#       - ocl-icd-opencl-dev
+#       - fglrx
+
+git:
+  depth: false
+
+install:
+    # There is sometimes issues with the install order so do this bad boi first
+  - pip3 install pybind11
+  - pip3 install -r requirements.txt
+  - pip3 install -e .
+
+script:
+  - python3 setup.py test
+
+after_success:
+  - make html
+
+deploy:
+  provider: pages
+  skip-cleanup: true
+  github-token: $GITHUB_TOKEN  # Set in travis-ci.org dashboard, marked secure
+  keep-history: true
+  local-dir: ./ 
+  on:
+    branch: master

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Adam Kelly
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 19 - 0
Makefile

@@ -0,0 +1,19 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+SOURCEDIR     = docs
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

+ 41 - 0
README.rst

@@ -0,0 +1,41 @@
+QCGPU
+=====
+
+.. image:: https://img.shields.io/travis/QCGPU/qcgpu.svg?style=for-the-badge   
+   :alt: Travis (.org)   
+   :target: https://travis-ci.org/QCGPU/qcgpu
+.. image:: https://img.shields.io/pypi/v/qcgpu.svg?style=for-the-badge
+   :target: https://pypi.python.org/pypi/qcgpu
+   :alt: PyPi Version
+.. image:: https://img.shields.io/pypi/l/qcgpu.svg?style=for-the-badge
+   :target: https://pypi.python.org/pypi/qcgpu/
+   :alt: License
+.. image:: https://img.shields.io/github/stars/qcgpu/qcgpu.svg?style=for-the-badge&label=Stars
+   :alt: GitHub stars
+   :target: https://github.com/QCGPU/qcgpu
+
+Open Source, High Performance & Hardware Accelerated, Quantum Computer Simulator.
+Read the `research paper`_.
+
+.. _`research paper`: https://arxiv.org/abs/1805.00988
+
+**Features:**
+
+* Written with OpenCL. Accelerated your simuations with GPUs and other accelerators, while still running cross device and cross platform.
+* Simulation of arbitrary quantum circuits
+* Includes example algorthm implementations
+* Support for arbitrary gate creation/application, with many built in.
+
+Installing
+==========
+
+This library is distributed on PyPI_ and can be installed using pip::
+
+    pip install qcgpu
+
+For more information read the full installing docs, :ref:`installing`.
+
+TODO: Fix this link
+
+.. _PyPI: https://pypi.python.org/pypi/qcgpu
+

+ 56 - 0
benchmark.py

@@ -0,0 +1,56 @@
+# This script is to run the benchmarks.
+import numpy as np
+import sys
+import csv
+import time
+import qcgpu
+import os.path
+
+results_file = 'benchmark_results.csv'
+
+def bench_qcgpu(n, depth):
+    state = qcgpu.State(n)
+
+    h = qcgpu.gate.h()
+    x = qcgpu.gate.x()
+    sqrt_x = qcgpu.gate.sqrt_x()
+
+    start = time.time()
+
+    for level in range(depth):
+        for q in range(n):
+    
+            state.apply_gate(h, q)
+            state.apply_gate(sqrt_x, q)
+
+            if q != 0:
+                state.apply_controlled_gate(x, q, 0)
+        
+    runtime = time.time() - start
+    return {'name': 'qcgpu', 'num_qubits': n, 'depth': depth, 'time': runtime}
+
+def create_csv(filename):
+    file_exists = os.path.isfile(filename)
+    csvfile = open(filename, 'a')
+   
+    headers = ['name', 'num_qubits', 'depth', 'time']
+    writer = csv.DictWriter(csvfile, delimiter=',', lineterminator='\n',fieldnames=headers)
+
+    if not file_exists:
+        writer.writeheader()  # file doesn't exist yet, write a header
+
+    # writer.writerow({'TimeStamp': dic['ts'], 'light': dic['light'], 'Proximity': dic['prox']}
+
+    return writer
+
+def write_csv(writer, data):
+    print("Qubits: " + str(data['num_qubits']) + ", Depth: " + str(data['depth']) + ", Time: " + str(data['depth']))
+    writer.writerow(data)
+
+writer = create_csv(results_file)
+for i in range(2, 25):
+    for d in [5,10,15,20]:
+        data = bench_qcgpu(i, d)
+        write_csv(writer, data)
+
+

+ 186 - 0
docs/conf.py

@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = u'QCGPU'
+copyright = u'2018, Adam Kelly'
+author = u'Adam Kelly'
+
+# The short X.Y version
+version = u''
+# The full version, including alpha/beta/rc tags
+release = u'0.1.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.mathjax',
+    'sphinx.ext.ifconfig',
+    'sphinx.ext.githubpages',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself.  Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'QCGPUdoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    #
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    #
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    #
+    # 'preamble': '',
+
+    # Latex figure (float) alignment
+    #
+    # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'QCGPU.tex', u'QCGPU Documentation',
+     u'Adam Kelly', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'qcgpu', u'QCGPU Documentation',
+     [author], 1)
+]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'QCGPU', u'QCGPU Documentation',
+     author, 'QCGPU', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+
+# -- Extension configuration -------------------------------------------------
+
+# -- Options for intersphinx extension ---------------------------------------
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}

+ 14 - 0
docs/index.rst

@@ -0,0 +1,14 @@
+.. include:: ../README.rst
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+   
+   installing
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`

+ 52 - 0
docs/installing.rst

@@ -0,0 +1,52 @@
+.. _installing:
+
+Installing
+==========
+
+There are a few things you have to do to install QCGPU.
+
+Prerequisites
+-------------
+
+-  `OpenCL`_ (Ensure that an OpenCL implementation is installed for your
+   platform and that ``clinfo`` or some other diagnostic command will
+   run).
+-  `Python`_ (Version 2.7 or later).
+
+Installing from PyPI
+--------------------
+
+This library is distributed on `PyPI`_ and can be installed using pip:
+
+.. code:: bash
+
+   $ pip install qcgpu
+
+If you run into any issues, you should try installing from source.
+
+Installing from Source
+----------------------
+
+You can install QCGPU from the source. First, clone the repository off
+GitHub:
+
+.. code:: bash
+
+   $ git clone https://github.com/qcgpu/qcgpu
+
+Then you will need to ``cd`` into the directory, and install the
+requirements.
+
+.. code:: bash
+
+   $ pip install -r requirements.txt
+
+And finally you can install:
+
+.. code:: bash
+
+   $ python setup.py install
+
+.. _OpenCL: https://www.khronos.org/opencl/
+.. _Python: https://www.python.org/
+.. _PyPI: https://pypi.python.org/pypi/qcgpu

+ 27 - 0
examples/bell_state.py

@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+"""
+Bell State / EPR Pair
+=====================
+
+The Bell State, also known as the EPR pair (after Einstein, Podosky and Rosen)
+is the simplest example of entanglement.
+
+The Bell State is defined as the maximally entangled quantum state of two qubits.
+"""
+
+def bell_state():
+    import qcgpu
+
+    print("Creating Bell State")
+
+    state = qcgpu.state(2)
+
+    state.h(0)
+    state.cx(0, 1)
+
+    print("Measurement Results:")
+    print(state.measure(samples = 1000))
+
+if __name__== "__main__":
+  bell_state()

+ 52 - 0
examples/bernstein_vazirani.py

@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+"""
+Bernstein-Vazirani Algorithm
+============================
+
+This algorithm finds a hidden integer :math:`a \in \{ 0, 1\}^n` from
+an oracle :math:`f_a` which returns a bit :math:`a \cdot x \equiv \sum_i a_i x_i \mod 2`
+for an input :math:`x \in \{0,1\}^n`.
+
+A classical oracle returns :math:`f_a(x) = a \dot x \mod 2`, while the quantum oracle
+must be queried with superpositions of input :math:`x`'s.
+
+To solve this problem classically, the hidden integer can be found by checking the
+oracle with the inputs :math:`x = 1,2,\dots,2^i,2^{n-1}`, where each
+query reveals the :math:`i`th bit of :math:`a` (:math:`a_i`).
+This is the optimal classical solution, and is :math:`O(n)`. Using a quantum oracle and the
+Bernstein-Vazirani algorithm, :math:`a` can be found with just one query to the oracle.
+
+The Algorithm
+-------------
+
+1. Initialize :math:`n` qubits in the state :math:`\lvert 0, \dots, 0\rangle`.
+2. Apply the Hadamard gate :math:`H` to each qubit.
+3. Apply the inner product oracle.
+4. Apply the Hadamard gate :math:`H` to each qubit.
+5. Measure the register
+
+From this procedure, we find that the registers measured value is equal to that of
+the original hidden integer.
+"""
+
+def bernstein_vazirani():
+    import qcgpu
+
+    a = 101 # The hidden integer, bitstring is 1100101
+
+    register = qcgpu.State(16) # Create a new quantum register
+
+    register.apply_all(qcgpu.gate.h()) # Apply a hadamard gate to each qubit
+
+    # Apply the inner products oracle
+    for i in range(16):
+        if a & (1 << i) != 0:
+            register.z(i)
+
+    register.apply_all(qcgpu.gate.h()) # Apply a hadamard gate to each qubit
+
+    register.measure(samples=1000) # Measure the register (sample 1000 times)
+
+if __name__== "__main__":
+  bernstein_vazirani()

+ 35 - 0
make.bat

@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=docs
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.http://sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd

+ 263 - 0
notebooks/Benchmarking.ipynb

@@ -0,0 +1,263 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "import time\n",
+    "import pandas as pd\n",
+    "import numpy as np\n",
+    "\n",
+    "# QCGPU Imports\n",
+    "import qcgpu\n",
+    "\n",
+    "# ProjectQ Imports\n",
+    "from projectq import MainEngine\n",
+    "import projectq.ops as ops\n",
+    "from projectq.backends import Simulator\n",
+    "from projectq.types import Qureg\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def bench_qcgpu(n, depth):\n",
+    "    state = qcgpu.State(n)\n",
+    "\n",
+    "    h = qcgpu.gate.h()\n",
+    "    x = qcgpu.gate.x()\n",
+    "    sqrt_x = qcgpu.gate.sqrt_x()\n",
+    "\n",
+    "    print('started')\n",
+    "    start = time.time()\n",
+    "\n",
+    "    for level in range(depth):\n",
+    "        for q in range(n):\n",
+    "    \n",
+    "            state.apply_gate(h, q)\n",
+    "            state.apply_gate(sqrt_x, q)\n",
+    "\n",
+    "            if q != 0:\n",
+    "                state.apply_controlled_gate(x, q, 0)\n",
+    "        \n",
+    "    runtime = time.time() - start\n",
+    "    print('ended: ', runtime)\n",
+    "    return runtime\n",
+    "    \n",
+    "def bench_projectq(n, depth):\n",
+    "    eng = MainEngine(backend=Simulator(gate_fusion=True), engine_list=[])\n",
+    "    qbits = eng.allocate_qureg(n)\n",
+    "\n",
+    "    print('started')\n",
+    "    start = time.time()\n",
+    "\n",
+    "    for level in range(depth):\n",
+    "        print(depth-level)\n",
+    "        for q in qbits:\n",
+    "            ops.H | q\n",
+    "            ops.SqrtX | q\n",
+    "            if q != qbits[0]:\n",
+    "                ops.CNOT | (q, qbits[0])\n",
+    "\n",
+    "    runtime = time.time() - start\n",
+    "    print('ended: ', runtime)\n",
+    "\n",
+    "    for q in qbits:\n",
+    "        ops.Measure | q\n",
+    "    eng.flush()\n",
+    "    return runtime\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "started\n",
+      "('ended: ', 202.95192885398865)\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "202.95192885398865"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bench_qcgpu(28,100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "started\n",
+      "100\n",
+      "99\n",
+      "98\n",
+      "97\n",
+      "96\n",
+      "95\n",
+      "94\n",
+      "93\n",
+      "92\n",
+      "91\n",
+      "90\n",
+      "89\n",
+      "88\n",
+      "87\n",
+      "86\n",
+      "85\n",
+      "84\n",
+      "83\n",
+      "82\n",
+      "81\n",
+      "80\n",
+      "79\n",
+      "78\n",
+      "77\n",
+      "76\n",
+      "75\n",
+      "74\n",
+      "73\n",
+      "72\n",
+      "71\n",
+      "70\n",
+      "69\n",
+      "68\n",
+      "67\n",
+      "66\n",
+      "65\n",
+      "64\n",
+      "63\n",
+      "62\n",
+      "61\n",
+      "60\n",
+      "59\n",
+      "58\n",
+      "57\n",
+      "56\n",
+      "55\n",
+      "54\n",
+      "53\n",
+      "52\n",
+      "51\n",
+      "50\n",
+      "49\n",
+      "48\n",
+      "47\n",
+      "46\n",
+      "45\n",
+      "44\n",
+      "43\n",
+      "42\n",
+      "41\n",
+      "40\n",
+      "39\n",
+      "38\n",
+      "37\n",
+      "36\n",
+      "35\n",
+      "34\n",
+      "33\n",
+      "32\n",
+      "31\n",
+      "30\n",
+      "29\n",
+      "28\n",
+      "27\n",
+      "26\n",
+      "25\n",
+      "24\n",
+      "23\n",
+      "22\n",
+      "21\n",
+      "20\n",
+      "19\n",
+      "18\n",
+      "17\n",
+      "16\n",
+      "15\n",
+      "14\n",
+      "13\n",
+      "12\n",
+      "11\n",
+      "10\n",
+      "9\n",
+      "8\n",
+      "7\n",
+      "6\n",
+      "5\n",
+      "4\n",
+      "3\n",
+      "2\n",
+      "1\n",
+      "('ended: ', 971.3228080272675)\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "971.3228080272675"
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bench_projectq(28,100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 2",
+   "language": "python",
+   "name": "python2"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.15rc1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

+ 74 - 0
notebooks/In-Progress Experiments.ipynb

@@ -0,0 +1,74 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import qcgpu\n",
+    "\n",
+    "state = qcgpu.State(28)\n",
+    "\n",
+    "state.apply_all(qcgpu.gate.h())\n",
+    "\n",
+    "# state"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "1.0"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "state.measure_qubit(0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 2",
+   "language": "python",
+   "name": "python2"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.15rc1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

+ 134 - 0
notebooks/Verification.ipynb

@@ -0,0 +1,134 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Verification\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import qcgpu\n",
+    "from projectq import MainEngine\n",
+    "import projectq.ops as ops\n",
+    "from projectq.backends import Simulator, CircuitDrawer"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def qcgpu_amplitudes(n, depth):\n",
+    "    state = qcgpu.State(n)\n",
+    "\n",
+    "    h = qcgpu.gate.h()\n",
+    "    x = qcgpu.gate.x()\n",
+    "    t = qcgpu.gate.t()\n",
+    "\n",
+    "    for level in range(depth):\n",
+    "        for q in range(n):\n",
+    "    \n",
+    "            state.apply_gate(h, q)\n",
+    "            state.apply_gate(t, q)\n",
+    "\n",
+    "            if q != 0:\n",
+    "                state.apply_controlled_gate(x, q, 0)\n",
+    "        \n",
+    "    return state.amplitudes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 58,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def projectq_amplitudes(n, depth):\n",
+    "#     drawing_engine = CircuitDrawer()\n",
+    "#     eng = MainEngine(drawing_engine)\n",
+    "    sim = Simulator(gate_fusion=True)\n",
+    "    eng = MainEngine(backend=sim, engine_list=[])\n",
+    "    qbits = eng.allocate_qureg(n)\n",
+    "\n",
+    "    for level in range(depth):\n",
+    "        for q in qbits:\n",
+    "            ops.H | q\n",
+    "            ops.T | q\n",
+    "            if q != qbits[0]:\n",
+    "                ops.CNOT | (q, qbits[0])\n",
+    "\n",
+    "\n",
+    "    eng.flush()\n",
+    "    amplitudes = sim.cheat()[1]\n",
+    "    for q in qbits:\n",
+    "        ops.Measure | q\n",
+    "   \n",
+    "    return amplitudes\n",
+    "#     return drawing_engine.get_latex()\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 70,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 70,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "def verify(n, depth):\n",
+    "    amps_qcgpu = qcgpu_amplitudes(n,depth)\n",
+    "    amps_projectq = np.transpose(np.array([projectq_amplitudes(n,depth)]))\n",
+    "#     print(amps_qcgpu)\n",
+    "#     print(amps_projectq)\n",
+    "    return np.allclose(amps_qcgpu, amps_projectq, rtol=1e-04)\n",
+    "                        \n",
+    "verify(25,15)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 2",
+   "language": "python",
+   "name": "python2"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.15rc1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

+ 24 - 0
notebooks/ipython_cell_input.py

@@ -0,0 +1,24 @@
+def bench_qcgpu(n, depth):
+    state = qcgpu.State(n)
+
+    h = qcgpu.gate.h()
+    x = qcgpu.gate.x()
+    sqrt_x = qcgpu.gate.sqrt_x()
+
+    print('started')
+    start = time.time()
+
+    for level in range(depth):
+        for q in range(n):
+    
+            state.apply_gate(h, q)
+            state.apply_gate(sqrt_x, q)
+
+            if q != 0:
+                state.apply_controlled_gate(x, q, 0)
+        
+    runtime = time.time() - start
+    print('ended: ', runtime)
+    return runtime
+
+bench_qcgpu(26,5)

+ 16 - 0
notebooks/out.csv

@@ -0,0 +1,16 @@
+,num_qubits,depth,time
+0,2,10,0.2940499782562256
+1,2,20,0.2940499782562256
+2,2,30,0.2940499782562256
+3,3,10,0.2940499782562256
+4,3,20,0.2940499782562256
+5,3,30,0.2940499782562256
+6,4,10,0.2940499782562256
+7,4,20,0.2940499782562256
+8,4,30,0.2940499782562256
+9,5,10,0.2940499782562256
+10,5,20,0.2940499782562256
+11,5,30,0.2940499782562256
+12,6,10,0.2940499782562256
+13,6,20,0.2940499782562256
+14,6,30,0.2940499782562256

+ 89 - 0
notebooks/settings.json

@@ -0,0 +1,89 @@
+{
+    "control": {
+        "shadow": false, 
+        "size": 0.1
+    }, 
+    "gate_shadow": true, 
+    "gates": {
+        "AllocateQubitGate": {
+            "allocate_at_zero": false, 
+            "draw_id": false, 
+            "height": 0.15, 
+            "offset": 0.1, 
+            "pre_offset": 0.1, 
+            "width": 0.2
+        }, 
+        "DeallocateQubitGate": {
+            "height": 0.15, 
+            "offset": 0.2, 
+            "pre_offset": 0.1, 
+            "width": 0.2
+        }, 
+        "EntangleGate": {
+            "offset": 0.2, 
+            "pre_offset": 0.2, 
+            "width": 1.8
+        }, 
+        "HGate": {
+            "offset": 0.3, 
+            "pre_offset": 0.1, 
+            "width": 0.5
+        }, 
+        "MeasureGate": {
+            "height": 0.5, 
+            "offset": 0.2, 
+            "pre_offset": 0.2, 
+            "width": 0.75
+        }, 
+        "Ph": {
+            "height": 0.8, 
+            "offset": 0.3, 
+            "pre_offset": 0.2, 
+            "width": 1.0
+        }, 
+        "Rx": {
+            "height": 0.8, 
+            "offset": 0.3, 
+            "pre_offset": 0.2, 
+            "width": 1.0
+        }, 
+        "Ry": {
+            "height": 0.8, 
+            "offset": 0.3, 
+            "pre_offset": 0.2, 
+            "width": 1.0
+        }, 
+        "Rz": {
+            "height": 0.8, 
+            "offset": 0.3, 
+            "pre_offset": 0.2, 
+            "width": 1.0
+        }, 
+        "SqrtSwapGate": {
+            "height": 0.35, 
+            "offset": 0.1, 
+            "width": 0.35
+        }, 
+        "SqrtXGate": {
+            "offset": 0.3, 
+            "pre_offset": 0.1, 
+            "width": 0.7
+        }, 
+        "SwapGate": {
+            "height": 0.35, 
+            "offset": 0.1, 
+            "width": 0.35
+        }, 
+        "XGate": {
+            "height": 0.35, 
+            "offset": 0.1, 
+            "width": 0.35
+        }
+    }, 
+    "lines": {
+        "double_classical": true, 
+        "double_lines_sep": 0.04, 
+        "init_quantum": true, 
+        "style": "very thin"
+    }
+}

+ 2 - 0
qcgpu/__init__.py

@@ -0,0 +1,2 @@
+import qcgpu.gate
+from qcgpu.state import State

+ 140 - 0
qcgpu/backend.py

@@ -0,0 +1,140 @@
+import os
+import random
+import numpy as np
+import pyopencl as cl
+import pyopencl.array as pycl_array
+from pyopencl.reduction import ReductionKernel
+
+# Get the OpenCL kernel
+kernel_path = os.path.join(
+    os.path.dirname(__file__),
+    "kernels/brute-force.cl"
+)
+kernel = open(kernel_path, "r").read()
+
+
+class Backend:
+    """
+    A class for the OpenCL backend to the simulator.
+
+    This class shouldn't be used directly, as many of the
+    methods don't have the same input checking as the State
+    class.
+    """
+
+    def __init__(self, num_qubits, dtype=np.complex64):
+        """
+        Initialize a new OpenCL Backend
+
+        Takes an argument of the number of qubits to use
+        in the register, and returns the 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()
+
+        # Buffer for the state vector
+        self.buffer = pycl_array.to_device(
+            self.queue,
+            np.eye(2**num_qubits, 1, dtype=dtype)
+        )
+
+    def apply_gate(self, gate, target):
+        """Applies a gate to the quantum register"""
+
+        self.program.apply_gate(
+            self.queue,
+            [int(self.buffer.shape[0] / 2)],
+            None,
+            self.buffer.data,
+            np.int32(target),
+            self.dtype(gate.a),
+            self.dtype(gate.b),
+            self.dtype(gate.c),
+            self.dtype(gate.d)
+        )
+
+    def apply_controlled_gate(self, gate, control, target):
+        """Applies a controlled gate to the quantum register"""
+
+        self.program.apply_controlled_gate(
+            self.queue,
+            [int(self.buffer.shape[0] / 2)],
+            None,
+            self.buffer.data,
+            np.int32(control),
+            np.int32(target),
+            self.dtype(gate.a),
+            self.dtype(gate.b),
+            self.dtype(gate.c),
+            self.dtype(gate.d)
+        )
+    
+    def qubit_probability(self, target):
+        """Get the probability of a single qubit begin measured as '0'"""
+
+        preamble = """
+        #include <pyopencl-complex.h>
+
+        float probability(int target, int i, cfloat_t amp) {
+            if ((i & (1 << target )) != 0) {
+                return 0;
+            }
+            // return 6.0;
+            float abs = cfloat_abs(amp);
+            return abs * abs;
+        }
+        """
+        
+
+        kernel = ReductionKernel(
+            self.context, 
+            np.float, 
+            neutral = "0",
+            reduce_expr="a + b",
+            map_expr="probability(target, i, amps[i])",
+            arguments="__global cfloat_t *amps, __global int target",
+            preamble=preamble
+        )
+
+        return kernel(self.buffer, target).get()
+
+    def measure_qubit(self, target, samples):
+        probability_of_0 = self.qubit_probability(target)
+
+        total = 0
+
+        for i in range(samples):
+            outcome = 1 if random.random() > probability_of_0 else 0
+            total = total + outcome
+            
+        
+        return total
+
+
+    def amplitudes(self):
+        """Gets the probability amplitudes"""
+        return self.buffer.get()
+    
+    def probabilities(self):
+        """Gets the squared absolute value of each of the amplitudes"""
+        out = pycl_array.to_device(
+            self.queue,
+            np.zeros(2**self.num_qubits, dtype=np.float32)
+        )
+
+        self.program.calculate_probabilities(
+            self.queue,
+            self.buffer.shape,
+            None,
+            self.buffer.data,
+            out.data
+        )
+
+        return out.get()
+        
+    def release(self):
+        self.buffer.base_data.release()

+ 45 - 0
qcgpu/gate.py

@@ -0,0 +1,45 @@
+import numpy as np
+
+class Gate:
+    def __init__(self, gate):
+        gate = np.array(gate)
+        
+        if gate.shape != (2, 2):
+            raise ValueError(
+                "Gate is not a 2x2 matrix. " + 
+                "For larger gates, please decompose into 2x2 matrices " +
+                "and/or use the controlled gate functionality."
+            )
+
+        # Check the gate is unitary
+        if (not np.allclose(np.eye(gate.shape[0]), np.dot(gate.conjugate().transpose(), gate))):
+            raise ValueError("gate is not unitary.")
+
+        self.a = complex(gate[0, 0])
+        self.b = complex(gate[0, 1])
+        self.c = complex(gate[1, 0])
+        self.d = complex(gate[1, 1])
+
+    def __repr__(self):
+        return '[{:.4f}, {:.4f}]\n[{:.4f}, {:.4f}]'.format(self.a, self.b, self.c, self.d)
+
+def h():
+    return Gate(np.array([[1, 1], [1, -1]]) / np.sqrt(2))
+
+def x():
+    return Gate(np.array([[0, 1], [1, 0]]))
+
+def y():
+    return Gate(np.array([[0, -1j], [1j, 0]]))
+
+def z():
+    return Gate(np.array([[1, 0], [0, -1]]))
+
+def s():
+    return Gate(np.array([[1, 0], [0, 1j]]))
+
+def t():
+    return Gate(np.array([[1, 0], [0, np.exp(np.pi * 1j / 4)]]))
+
+def sqrt_x():
+    return Gate(0.5 * np.array([[1+1j, 1-1j], [1-1j, 1+1j]]))

+ 182 - 0
qcgpu/kernels/brute-force.cl

@@ -0,0 +1,182 @@
+#include <pyopencl-complex.h>
+
+/*
+ * Returns the nth number where a given digit
+ * is cleared in the binary representation of the number
+ */
+static int nth_cleared(int n, int target)
+{
+    int mask = (1 << target) - 1;
+    int not_mask = ~mask;
+
+    return (n & mask) | ((n & not_mask) << 1);
+}
+
+///////////////////////////////////////////////
+// KERNELS
+///////////////////////////////////////////////
+
+/*
+ * Applies a single qubit gate to the register.
+ * The gate matrix must be given in the form:
+ *
+ *  A B
+ *  C D
+ */
+__kernel void apply_gate(
+    __global cfloat_t *amplitudes,
+    int target,
+    cfloat_t A,
+    cfloat_t B,
+    cfloat_t C,
+    cfloat_t D)
+{
+    int const global_id = get_global_id(0);
+
+    int const zero_state = nth_cleared(global_id, target);
+
+    // int const zero_state = state & (~(1 << target)); // Could just be state
+    int const one_state = zero_state | (1 << target);
+
+    cfloat_t const zero_amp = amplitudes[zero_state];
+    cfloat_t const one_amp = amplitudes[one_state];
+
+    amplitudes[zero_state] = cfloat_add(cfloat_mul(A, zero_amp), cfloat_mul(B, one_amp));
+    amplitudes[one_state] = cfloat_add(cfloat_mul(D, one_amp), cfloat_mul(C, zero_amp));
+}
+
+/*
+ * Applies a controlled single qubit gate to the register.
+ */
+__kernel void apply_controlled_gate(
+    __global cfloat_t *amplitudes,
+    int control,
+    int target,
+    cfloat_t A,
+    cfloat_t B,
+    cfloat_t C,
+    cfloat_t D)
+{
+    int const global_id = get_global_id(0);
+    int const zero_state = nth_cleared(global_id, target);
+    int const one_state = zero_state | (1 << target); // Set the target bit
+
+    int const control_val_zero = (((1 << control) & zero_state) > 0) ? 1 : 0;
+    int const control_val_one = (((1 << control) & one_state) > 0) ? 1 : 0;
+
+    cfloat_t const zero_amp = amplitudes[zero_state];
+    cfloat_t const one_amp = amplitudes[one_state];
+
+    if (control_val_zero == 1)
+    {
+        amplitudes[zero_state] = cfloat_add(cfloat_mul(A, zero_amp), cfloat_mul(B, one_amp));
+    }
+
+    if (control_val_one == 1)
+    {
+        amplitudes[one_state] = cfloat_add(cfloat_mul(D, one_amp), cfloat_mul(C, zero_amp));
+    }
+
+
+}
+
+/*
+ * Applies a controlled-controlled single qubit gate to the register.
+ * NOT MIGRATED
+ */
+__kernel void apply_controlled_controlled_gate(
+    __global cfloat_t *const amplitudes,
+    __global cfloat_t *amps,
+    int control1,
+    int control2,
+    int target,
+    cfloat_t A,
+    cfloat_t B,
+    cfloat_t C,
+    cfloat_t D)
+{
+    int const state = get_global_id(0);
+    cfloat_t const amp = amplitudes[state];
+
+    int const zero_state = state & (~(1 << target));
+    int const one_state = state | (1 << target);
+
+    int const bit_val = (((1 << target) & state) > 0) ? 1 : 0;
+    int const control1_val = (((1 << control1) & state) > 0) ? 1 : 0;
+    int const control2_val = (((1 << control2) & state) > 0) ? 1 : 0;
+
+    if (control1_val == 0 || control2_val == 0)
+    {
+        // Control is 0, don't apply gate
+        amps[state] = amp;
+    }
+    else
+    {
+        // control is 1, apply gate.
+        if (bit_val == 0)
+        {
+            // Bitval = 0
+            amps[state] = cfloat_add(cfloat_mul(A, amp), cfloat_mul(B, amplitudes[one_state]));
+        }
+        else
+        {
+            amps[state] = cfloat_add(cfloat_mul(D, amp), cfloat_mul(C, amplitudes[zero_state]));
+        }
+    }
+}
+
+/*
+ * Swaps the states of two qubits in the register
+ * NOT MIGRATED
+ */
+// __kernel void swap(
+//     __global cfloat_t *const amplitudes,
+//     __global cfloat_t *amps,
+//     int first_qubit,
+//     int second_qubit)
+// {
+//     int const state = get_global_id(0);
+
+//     int const first_bit_mask = 1 << first_qubit;
+//     int const second_bit_mask = 1 << second_qubit;
+
+//     int const new_second_bit = ((state & first_bit_mask) >> first_qubit) << second_qubit;
+//     int const new_first_bit = ((state & second_bit_mask) >> second_qubit) << first_qubit;
+
+//     int const new_state = (state & !first_bit_mask & !second_bit_mask) | new_first_bit | new_second_bit;
+
+//     amps[new_state] = amplitudes[state];
+// }
+
+
+/**
+ * Calculates The Probabilities Of A State Vector
+ */
+__kernel void calculate_probabilities(
+    __global cfloat_t *const amplitudes,
+    __global float *probabilities)
+{
+    int const state = get_global_id(0);
+    cfloat_t amp = amplitudes[state];
+
+    probabilities[state] = cfloat_abs(cfloat_mul(amp, amp));
+}
+
+/**
+ * Initializes a register to the value 1|0..100...0>
+ *                                          ^ target
+ */
+__kernel void initialize_register(
+    __global cfloat_t *amplitudes,
+    int const target)
+{
+    int const state = get_global_id(0);
+    if (state == target)
+    {
+        amplitudes[state] = cfloat_new(1, 0);
+    }
+    else
+    {
+        amplitudes[state] = cfloat_new(0, 0);
+    }
+}

+ 57 - 0
qcgpu/state.py

@@ -0,0 +1,57 @@
+from qcgpu.backend import Backend
+import pyopencl as cl
+import numpy as np
+
+class State:
+    """Represents the state of a quantum register"""
+    def __init__(self, num_qubits):
+        if not isinstance(num_qubits, int):
+            raise ValueError("num_qubits must be an int")
+        if num_qubits <= 0:
+            raise ValueError("num_qubits must be a positive integer")
+
+        self.num_qubits = num_qubits
+        self.backend = Backend(num_qubits)
+
+    def apply_gate(self, gate, target):
+        if not isinstance(target, int) or target < 0:
+            raise ValueError("target must be an int > 0")
+
+        # TODO: Check that gate is correct
+
+        self.backend.apply_gate(gate, target)
+
+
+    def apply_all(self, gate):
+        # TODO: Check that gate is correct
+        for i in range(self.num_qubits):
+            self.apply_gate(gate, i)
+
+    def apply_controlled_gate(self, gate, control, target):
+        if not isinstance(target, int) or target < 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")
+
+        # TODO: Check that gate is correct
+
+        self.backend.apply_controlled_gate(gate, control, target)
+
+    def measure_qubit(self, target, samples=1):
+        return self.backend.measure_qubit(target, samples)
+
+    def amplitudes(self):
+        return self.backend.amplitudes()
+    
+    def probabilities(self):
+        return self.backend.probabilities()
+
+    def flush(self):
+        self.backend.release()
+
+    def __repr__(self):
+        """A string representation of the state"""
+
+        # TODO: Finish this method
+        return np.array_str(self.backend.buffer)

+ 6 - 0
requirements.txt

@@ -0,0 +1,6 @@
+Mako==1.0.7
+numpy==1.15.2
+pybind11==2.2.4
+pyopencl==2018.2
+scipy==1.1.0
+Sphinx==1.8.1

+ 5 - 0
setup.cfg

@@ -0,0 +1,5 @@
+[aliases]
+test=pytest
+
+[tool:pytest]
+addopts = --verbose

+ 23 - 0
setup.py

@@ -0,0 +1,23 @@
+import setuptools
+
+with open("README.rst", "r") as fh:
+    long_description = fh.read()
+
+setuptools.setup(
+    name="qcgpu",
+    version="0.0.1",
+    author="Adam Kelly",
+    author_email="adamkelly2201@gmail.com",
+    description="An OpenCL based quantum computer simulator",
+    long_description=long_description,
+    long_description_content_type="text/rst",
+    url="https://github.com/qcgpu/qcgpu",
+    packages=setuptools.find_packages(),
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+    ],
+    setup_requires=['pytest-runner', 'mako', 'pyopencl', 'pybind11', 'numpy'],
+    tests_require=["pytest"]
+)

+ 34 - 0
tests/test_gate.py

@@ -0,0 +1,34 @@
+import pytest
+from qcgpu.gate import Gate, h, x, y, z, s, t, sqrt_x
+import numpy as np
+
+def test_gate_creation():
+    Gate(np.array([[0, 1], [1, 0]])) # A clearly unitary gate
+    h()
+    x()
+    y()
+    z()
+    s()
+    t()
+    sqrt_x()
+
+def test_using_list():
+    return Gate([[1, 0], [0, 1]])
+
+def test_non_unitary_gate_creation_fails():
+    # A clearly non unitary gate
+    with pytest.raises(Exception):
+        return Gate(np.array([[12, 33], [-7j, 1]]))
+
+def test_large_gate_creation_fails():
+    # A gate that is not 2x2
+    with pytest.raises(Exception):
+        return Gate(np.ones(4))
+
+def test_using_scalar_fails():
+    with pytest.raises(Exception):
+        return Gate(2)
+
+def test_using_string_fails():
+    with pytest.raises(Exception):
+        return Gate("this should fail")

+ 12 - 0
tests/test_state.py

@@ -0,0 +1,12 @@
+from qcgpu import State
+import pytest
+
+def test_state_creation():
+    # Any machine should be able to handle 14 qubits
+    for i in range(1, 15):
+        State(i)
+
+def test_state_with_no_qubits_fails():
+    with pytest.raises(Exception):
+        State(0)
+