├── .gitignore ├── LICENSE ├── README.md ├── deprecated └── qcbm.ipynb ├── docs ├── 1804.04168.pdf └── images │ └── qcbm.png ├── notebooks ├── qcbm_advanced.ipynb └── qcbm_gaussian.ipynb ├── program.py ├── pytest.ini ├── qcbm ├── __init__.py ├── blocks.py ├── contexts.py ├── dataset.py ├── mmd.py ├── qcbm.py ├── qclibs.py ├── structure.py ├── tests.py ├── testsuit.py ├── train.py └── utils.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *.dat 4 | *.swp 5 | *.o 6 | *.so 7 | *.pyf 8 | *.eps 9 | *.out 10 | output* 11 | *.aux 12 | *.log 13 | *.synctex.gz 14 | *.npy 15 | *.dvi 16 | *.log 17 | *data/ 18 | *build/ 19 | dist/ 20 | *.mod 21 | .cache/ 22 | _*.pdf 23 | *.tex 24 | *.json 25 | _*.png 26 | .ipynb_checkpoints 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Leo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum Circuit Born Machine - the Demo 2 | Gradient based training of Quantum Circuit Born Machine (QCBM) 3 | 4 | ## Table of Contents 5 | This project contains 6 | 7 | * `notebooks/qcbm_gaussian.ipynb` (or [online](https://drive.google.com/file/d/1LfvWuM8rUPOtdWFRbUhSyjn35ndR7OW6/view?usp=sharing)), basic tutorial of training 6 bit Gaussian distribution using QCBM, 8 | * `notebooks/qcbm_advanced.ipynb` (or [online](https://drive.google.com/file/d/18EDWlGacQMONJ1sxhGlQ9M3aQ5619B8F/view?usp=sharing)), an advanced tutorial, 9 | * `qcbm` folder, a simple python project for productivity purpose. 10 | 11 | ![](docs/images/qcbm.png) 12 | 13 | ## Setup Guide 14 | Set up your python environment 15 | 16 | * python 3.6 17 | * install python libraries 18 | 19 | If you want to read notebooks only and do not want to use features like [`projectq`](https://github.com/ProjectQ-Framework/ProjectQ), having `numpy`, `scipy` and `matplotlib` is enough. 20 | To access advanced features, you should install `fire`, `projectq` and `climin`. 21 | ```bash 22 | $ conda install -c conda-forge pybind11 23 | $ pip install -r requirements.txt 24 | ``` 25 | 26 | Clone this repository [https://github.com/GiggleLiu/QuantumCircuitBornMachine.git](https://github.com/GiggleLiu/QuantumCircuitBornMachine.git) to your local host. 27 | 28 | ### Access online materials 29 | 1. Sign up and sign in [Google drive](https://drive.google.com/) 30 | 2. Connect Google drive with [Google Colaboratory](https://colab.research.google.com) 31 | - right click on google drive page 32 | - More 33 | - Connect more apps 34 | - search "Colaboratory" and "CONNECT" 35 | 3. You can make a copy of notebook to your google drive (File Menu) to save your edits. 36 | 37 | Also, we have provided a Julia code [here](https://quantumbfs.github.io/Yao.jl/latest/tutorial/QCBM/). 38 | 39 | 40 | ## Run Bar-and-Stripes Demo on Your Localhost 41 | 42 | ```bash 43 | $ ./program.py checkgrad # check the correctness of gradient 44 | $ ./program.py statgrad # check gradient will not vanish as layer index increase. 45 | $ ./program.py vcircuit # visualize circuit using ProjectQ 46 | $ ./program.py train # train and save data. 47 | $ ./program.py vpdf # see bar stripe dataset PDF 48 | $ ./program.py generate # generate bar and stripes using trainned circuit. 49 | ``` 50 | 51 | ## Documentations 52 | 53 | * paper: Differentiable Learning of Quantum Circuit Born Machine ([pdf](docs/1804.04168.pdf)), [arXiv:1804.04168](https://arxiv.org/abs/1804.04168), Jin-Guo Liu, Lei Wang 54 | * slides: [online](https://docs.google.com/presentation/d/1ZNysy-MUlkPfuxUtZMz_Sd8Mz43oC6y7FcyeGGMaQoU/edit?usp=sharing) 55 | 56 | ## Citation 57 | 58 | If you use this code for your research, please cite our paper: 59 | 60 | ``` 61 | @article{Liu2018, 62 | author = {Jin-Guo Liu and Lei Wang}, 63 | title = {Differentiable Learning of Quantum Circuit Born Machine}, 64 | year = {2018}, 65 | eprint = {arXiv:1804.04168}, 66 | url = {https://arxiv.org/abs/1804.04168} 67 | } 68 | ``` 69 | 70 | ## Authors 71 | 72 | * Jin-Guo Liu 73 | * Lei Wang 74 | -------------------------------------------------------------------------------- /deprecated/qcbm.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "import scipy.sparse as sps" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "# Dataset, binary data and continuous data" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": { 27 | "collapsed": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "def binary_basis(geometry):\n", 32 | " num_bit = np.prod(geometry)\n", 33 | " M = 2**num_bit\n", 34 | " x = np.arange(M)\n", 35 | " return unpacknbits(x[:,None], num_bit).reshape((-1,)+geometry)\n", 36 | "\n", 37 | "def unpacknbits(arr, nbit, axis=-1):\n", 38 | " '''unpack numbers to bits.'''\n", 39 | " nd = np.ndim(arr)\n", 40 | " if axis < 0:\n", 41 | " axis = nd + axis\n", 42 | " return (((arr & (1 << np.arange(nbit - 1, -1, -1)).reshape([-1] + [1] * (nd - axis - 1)))) > 0).astype('int8')\n", 43 | "\n", 44 | "def packnbits(arr, axis=-1):\n", 45 | " '''pack bits to numbers.'''\n", 46 | " nd = np.ndim(arr)\n", 47 | " nbit = np.shape(arr)[axis]\n", 48 | " if axis < 0:\n", 49 | " axis = nd + axis\n", 50 | " return (arr * (1 << np.arange(nbit - 1, -1, -1)).reshape([-1] + [1] * (nd - axis - 1))\\\n", 51 | " ).sum(axis=axis, keepdims=True).astype('int')" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 3, 57 | "metadata": { 58 | "collapsed": true 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "def barstripe_pdf(geometry):\n", 63 | " '''get bar and stripes PDF'''\n", 64 | " x = binary_basis(geometry)\n", 65 | " pl = is_bs(x)\n", 66 | " return pl/pl.sum()\n", 67 | "\n", 68 | "def is_bs(samples):\n", 69 | " '''a sample is a bar or a stripe.'''\n", 70 | " return (np.abs(np.diff(samples,axis=-1)).sum(axis=(1,2))==0\\\n", 71 | " )|((np.abs(np.diff(samples, axis=1)).sum(axis=(1,2)))==0)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 17, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "data": { 81 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX0AAAD8CAYAAACb4nSYAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJztnX+sbFd13z9rZu5cx3b5EfNICYY+\nE9wkTkMIfXWC8qMEF2KiBqeKkUwi1ZVckVYgUSUtNYpKEpqoIqpwGsX9gQqEkjaGOEnzCk4cipOo\npdT4mR82xji8mB9+GPCjNgYD786v3T/O2TNnzuxz9r7v3nP22nP3V3p68+Pce9fs2Xudtb/ru9cS\nYwwZGRkZGUcDg9gGZGRkZGT0h+z0MzIyMo4QstPPyMjIOELITj8jIyPjCCE7/YyMjIwjhOz0MzIy\nMo4QstPPyMjIOELITj8jIyPjCCE7/YyMjIwjhFFsA+p42tOeZo4fPx7bjIyMjIykcPfdd3/ZGHPM\nd506p3/8+HFOnToV24yMjIyMpCAinw25LtM7GRkZGUcI2elnZGRkHCFkp5+RkZFxhJCdfkZGRsYR\nQnb6GRkZGUcIQU5fRK4WkQdE5LSI3Oh4/0dF5MMiMhORa2vvXS8inyr/XX9YhmdkZGRk7B9epy8i\nQ+Bm4GXAFcArReSK2mWfA/4R8N9qP/utwC8BPwBcCfySiDz14GZnZGRkZJwPQiL9K4HTxpgHjTET\n4BbgmuoFxpjPGGPuARa1n/1x4H3GmEeNMY8B7wOuPgS7N/CFx7/Jm//0AR48+0QXvz4YXzs35Y8+\n+nnvdR///ON85HOP9WDRwTFfGN5910PMF2m01vzoQ1/h3jOPxzYjCNP5gnff9RCLRMb27s8+yv1f\n+GpsM4Jwbjrn9049hK8l7F9+6Wt86NOP9mRVM97+gU/znnse7vzvhDj9ZwIPVZ6fKV8LQdDPisir\nROSUiJw6e/Zs4K9ex9mv7fGbd5zm01/++nn9/GHhj+/9Iq+95aN86avnWq970598kl977/09WXUw\nnPrMo7zu9+/hrs/EXxgh+LX3foJfv/2Tsc0IwgdOf5nX/f493Pv5NG5Sv3TyPt78vr+MbUYQ/vyB\nR/gXt97Dpx5pDwR/8/2f4hf/8N6erGrGf/ngZ/nT+77U+d8JcfrieC00LAn6WWPMW4wxJ4wxJ44d\n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\nCOH/A07WvQZaRkpKAAAAAElFTkSuQmCC\n", 82 | "text/plain": [ 83 | "
" 84 | ] 85 | }, 86 | "metadata": {}, 87 | "output_type": "display_data" 88 | } 89 | ], 90 | "source": [ 91 | "geometry = (2,3)\n", 92 | "pl2 = barstripe_pdf(geometry)\n", 93 | "plt.plot(pl2)\n", 94 | "plt.show()" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "# Build Circuits\n", 102 | "## Building Blocks" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 19, 108 | "metadata": { 109 | "collapsed": true 110 | }, 111 | "outputs": [], 112 | "source": [ 113 | "###### Pauli Matrices ########\n", 114 | "I2 = sps.eye(2).tocsr()\n", 115 | "sx = sps.csr_matrix([[0,1],[1,0.]])\n", 116 | "sy = sps.csr_matrix([[0,-1j],[1j,0.]])\n", 117 | "sz = sps.csr_matrix([[1,0],[0,-1.]])\n", 118 | "\n", 119 | "p0 = (sz + I2) / 2\n", 120 | "p1 = (-sz + I2) / 2\n", 121 | "\n", 122 | "# single bit rotation matrices\n", 123 | "def _ri(si, theta):\n", 124 | " return np.cos(theta/2.)*I2 - 1j*np.sin(theta/2.)*si\n", 125 | "\n", 126 | "def rot(t1, t2, t3):\n", 127 | " '''\n", 128 | " a general rotation gate rz(t3)rx(r2)rz(t1).\n", 129 | "\n", 130 | " Args:\n", 131 | " t1, t2, t3 (float): three angles.\n", 132 | "\n", 133 | " Returns:\n", 134 | " 2x2 csr_matrix: rotation matrix.\n", 135 | " '''\n", 136 | " return _ri(sz, t3).dot(_ri(sx, t2)).dot(_ri(sz, t1))\n", 137 | "\n", 138 | "# multiple bit construction\n", 139 | "def CNOT(ibit, jbit, n):\n", 140 | " '''\n", 141 | " CNOT gate\n", 142 | " \n", 143 | " Args:\n", 144 | " ibit (int): control bit.\n", 145 | " jbit (int): controled bit.\n", 146 | " n (int): total number of qubits.\n", 147 | " '''\n", 148 | " res = _([p0, I2], [ibit, jbit], n)\n", 149 | " res = res + _([p1, sx], [ibit, jbit], n)\n", 150 | " return res\n", 151 | "\n", 152 | "def _(ops, locs, n):\n", 153 | " '''\n", 154 | " Compile operators into specific Hilbert space.\n", 155 | "\n", 156 | " notice the big end are high loc bits!\n", 157 | "\n", 158 | " Args:\n", 159 | " ops (list): list of single bit operators.\n", 160 | " locs (list): list of positions.\n", 161 | " n (int): total number of bits.\n", 162 | "\n", 163 | " Returns:\n", 164 | " csr_matrix: resulting matrix.\n", 165 | " '''\n", 166 | " if np.ndim(locs) == 0:\n", 167 | " locs = [locs]\n", 168 | " if not isinstance(ops, (list, tuple)):\n", 169 | " ops = [ops]\n", 170 | " locs = np.asarray(locs)\n", 171 | " locs = n - locs\n", 172 | " order = np.argsort(locs)\n", 173 | " locs = np.concatenate([[0], locs[order], [n + 1]])\n", 174 | " return _wrap_identity([ops[i] for i in order], np.diff(locs) - 1)\n", 175 | "\n", 176 | "\n", 177 | "def _wrap_identity(data_list, num_bit_list):\n", 178 | " if len(num_bit_list) != len(data_list) + 1:\n", 179 | " raise Exception()\n", 180 | "\n", 181 | " res = sps.eye(2**num_bit_list[0])\n", 182 | " for data, nbit in zip(data_list, num_bit_list[1:]):\n", 183 | " res = sps.kron(res, data)\n", 184 | " res = sps.kron(res, sps.eye(2**nbit, dtype='complex128'))\n", 185 | " return res\n", 186 | "\n", 187 | "\n", 188 | "def initial_wf(num_bit):\n", 189 | " '''initial wave function |00...0>.'''\n", 190 | " wf = np.zeros(2**num_bit, dtype='complex128')\n", 191 | " wf[0] = 1.\n", 192 | " return wf" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 20, 198 | "metadata": { 199 | "collapsed": true 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "class ArbituaryRotation(object):\n", 204 | " def __init__(self, num_bit):\n", 205 | " self.num_bit = num_bit\n", 206 | " self.mask = np.array([True] * (3*num_bit), dtype='bool')\n", 207 | "\n", 208 | " @property\n", 209 | " def num_param(self):\n", 210 | " return self.mask.sum()\n", 211 | "\n", 212 | " def tocsr(self, theta_list):\n", 213 | " '''transform this block to csr_matrix.'''\n", 214 | " theta_list_ = np.zeros(3*self.num_bit)\n", 215 | " theta_list_[self.mask] = theta_list\n", 216 | " rots = [rot(*ths) for ths in theta_list_.reshape([self.num_bit,3])]\n", 217 | " res = [_([r], [i], self.num_bit) for i,r in enumerate(rots)]\n", 218 | " return res\n", 219 | "\n", 220 | "class CNOTEntangler(object):\n", 221 | " def __init__(self, num_bit, pairs):\n", 222 | " self.num_bit = num_bit\n", 223 | " self.pairs = pairs\n", 224 | "\n", 225 | " @property\n", 226 | " def num_param(self):\n", 227 | " return 0\n", 228 | "\n", 229 | " def tocsr(self, theta_list):\n", 230 | " '''transform this block to csr_matrix.'''\n", 231 | " i, j = self.pairs[0]\n", 232 | " res = CNOT(i, j, self.num_bit)\n", 233 | " for i, j in self.pairs[1:]:\n", 234 | " res = CNOT(i,j,self.num_bit).dot(res)\n", 235 | " res.eliminate_zeros()\n", 236 | " return [res]" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 21, 242 | "metadata": { 243 | "collapsed": true 244 | }, 245 | "outputs": [], 246 | "source": [ 247 | "class BlockQueue(list):\n", 248 | " '''\n", 249 | " Block Queue that keep track of theta_list changing history, for fast update.\n", 250 | " '''\n", 251 | " def __init__(self, *args):\n", 252 | " list.__init__(self, *args)\n", 253 | " self.theta_last = None\n", 254 | " self.memo = None\n", 255 | "\n", 256 | " def __call__(self, qureg, theta_list):\n", 257 | " # cache? if theta_list change <= 1 parameters, then don't touch memory.\n", 258 | " remember = self.theta_last is None or (abs(self.theta_last-theta_list)>1e-12).sum() > 1\n", 259 | "\n", 260 | " mats = []\n", 261 | " theta_last = self.theta_last\n", 262 | " if remember:\n", 263 | " self.theta_last = theta_list.copy()\n", 264 | "\n", 265 | " qureg_ = qureg\n", 266 | " for iblock, block in enumerate(self):\n", 267 | " # generate or use a block matrix\n", 268 | " num_param = block.num_param\n", 269 | " theta_i, theta_list = np.split(theta_list, [num_param])\n", 270 | " if theta_last is not None:\n", 271 | " theta_o, theta_last = np.split(theta_last, [num_param])\n", 272 | " if self.memo is not None and (num_param==0 or np.abs(theta_i-theta_o).max()<1e-12):\n", 273 | " # use data cached in memory\n", 274 | " mat = self.memo[iblock]\n", 275 | " else:\n", 276 | " if self.memo is not None and not remember:\n", 277 | " # update the changed gate, but not touching memory.\n", 278 | " mat = _rot_tocsr_update1(block, self.memo[iblock], theta_o, theta_i)\n", 279 | " else:\n", 280 | " # regenerate one\n", 281 | " mat = block.tocsr(theta_i)\n", 282 | " for mat_i in mat:\n", 283 | " qureg_ = mat_i.dot(qureg_)\n", 284 | " mats.append(mat)\n", 285 | "\n", 286 | " if remember:\n", 287 | " # cache data\n", 288 | " self.memo = mats\n", 289 | " # update register\n", 290 | " qureg[...] = qureg_\n", 291 | " np.testing.assert_(len(theta_list)==0)\n", 292 | " \n", 293 | " @property\n", 294 | " def num_bit(self):\n", 295 | " return self[0].num_bit\n", 296 | "\n", 297 | " @property\n", 298 | " def num_param(self):\n", 299 | " return sum([b.num_param for b in self])\n", 300 | "\n", 301 | "def _rot_tocsr_update1(layer, old, theta_old, theta_new):\n", 302 | " '''\n", 303 | " rotation layer csr_matrix update method.\n", 304 | " \n", 305 | " Args:\n", 306 | " layer (ArbituaryRotation): rotatio layer.\n", 307 | " old (csr_matrix): old matrices.\n", 308 | " theta_old (1darray): old parameters.\n", 309 | " theta_new (1darray): new parameters.\n", 310 | "\n", 311 | " Returns:\n", 312 | " csr_matrix: new rotation matrices after the theta changed.\n", 313 | " '''\n", 314 | " idiff_param = np.where(abs(theta_old-theta_new)>1e-12)[0].item()\n", 315 | " idiff = np.where(layer.mask)[0][idiff_param]\n", 316 | "\n", 317 | " # get rotation parameters\n", 318 | " isite = idiff//3\n", 319 | " theta_list_ = np.zeros(3*layer.num_bit)\n", 320 | " theta_list_[layer.mask] = theta_new\n", 321 | " \n", 322 | " new = old[:]\n", 323 | " new[isite] = _(rot(*theta_list_[isite*3:isite*3+3]), isite, layer.num_bit)\n", 324 | " return new\n", 325 | " \n", 326 | "def get_demo_circuit(num_bit, depth, pairs):\n", 327 | " blocks = []\n", 328 | " # build circuit\n", 329 | " for idepth in range(depth+1):\n", 330 | " blocks.append(ArbituaryRotation(num_bit))\n", 331 | " if idepth!=depth:\n", 332 | " blocks.append(CNOTEntangler(num_bit, pairs))\n", 333 | "\n", 334 | " # set leading and trailing Rz to disabled\n", 335 | " blocks[0].mask[::3] = False\n", 336 | " blocks[-1].mask[2::3] = False\n", 337 | " return BlockQueue(blocks)" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": 22, 343 | "metadata": { 344 | "collapsed": true 345 | }, 346 | "outputs": [], 347 | "source": [ 348 | "def get_nn_pairs(geometry):\n", 349 | " '''define pairs that cnot gates will apply.'''\n", 350 | " num_bit = np.prod(geometry)\n", 351 | " if len(geometry) == 2:\n", 352 | " nrow, ncol = geometry\n", 353 | " res = []\n", 354 | " for ij in range(num_bit):\n", 355 | " i, j = ij // ncol, ij % ncol\n", 356 | " res.extend([(ij, i_ * ncol + j_)\n", 357 | " for i_, j_ in [((i + 1) % nrow, j), (i, (j + 1) % ncol)]])\n", 358 | " return res\n", 359 | " elif len(geometry) == 1:\n", 360 | " res = []\n", 361 | " for inth in range(2):\n", 362 | " for i in range(inth, num_bit, 2):\n", 363 | " res = res + [(i, i_ % num_bit) for i_ in range(i + 1, i + 2)]\n", 364 | " return res\n", 365 | " else:\n", 366 | " raise NotImplementedError('')" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": 23, 372 | "metadata": {}, 373 | "outputs": [ 374 | { 375 | "name": "stdout", 376 | "output_type": "stream", 377 | "text": [ 378 | "[<__main__.ArbituaryRotation object at 0x14787a5fd710>, <__main__.CNOTEntangler object at 0x14787a5fd7b8>, <__main__.ArbituaryRotation object at 0x14787a5fd438>, <__main__.CNOTEntangler object at 0x14787a5fd7f0>, <__main__.ArbituaryRotation object at 0x14787a5fd5c0>]\n" 379 | ] 380 | } 381 | ], 382 | "source": [ 383 | "depth = 2\n", 384 | "geometry = (6,)\n", 385 | "\n", 386 | "num_bit = np.prod(geometry)\n", 387 | "pairs = get_nn_pairs(geometry)\n", 388 | "circuit = get_demo_circuit(num_bit, depth, pairs)\n", 389 | "print(circuit)" 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": 24, 395 | "metadata": { 396 | "scrolled": true 397 | }, 398 | "outputs": [ 399 | { 400 | "name": "stdout", 401 | "output_type": "stream", 402 | "text": [ 403 | "[6.123234e-17-1.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 404 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 405 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 406 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 407 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 408 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 409 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 410 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 411 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 412 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 413 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 414 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 415 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 416 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 417 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j\n", 418 | " 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j 0.000000e+00+0.j]\n" 419 | ] 420 | } 421 | ], 422 | "source": [ 423 | "theta_list = np.zeros(circuit.num_param)\n", 424 | "theta_list[1] = np.pi\n", 425 | "wf = initial_wf(num_bit)\n", 426 | "circuit(wf, theta_list)\n", 427 | "\n", 428 | "print(wf)" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": {}, 434 | "source": [ 435 | "# Build The Gradient training framework for Born Machine" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 25, 441 | "metadata": { 442 | "collapsed": true 443 | }, 444 | "outputs": [], 445 | "source": [ 446 | "class BornMachine(object):\n", 447 | " '''\n", 448 | " Born Machine,\n", 449 | "\n", 450 | " Args:\n", 451 | " circuit (BlockQueue): the circuit architechture.\n", 452 | " batch_size (int|None): introducing sampling error, None for no sampling error.\n", 453 | " '''\n", 454 | " def __init__(self, circuit, mmd, p_data, batch_size=None):\n", 455 | " self.circuit = circuit\n", 456 | " self.mmd = mmd\n", 457 | " self.p_data = p_data\n", 458 | " self.batch_size = batch_size\n", 459 | "\n", 460 | " @property\n", 461 | " def depth(self):\n", 462 | " return (len(self.circuit)-1)//2\n", 463 | "\n", 464 | " def pdf(self, theta_list):\n", 465 | " '''get probability distribution function'''\n", 466 | " wf = initial_wf(self.circuit.num_bit)\n", 467 | " self.circuit(wf, theta_list)\n", 468 | " pl = np.abs(wf)**2\n", 469 | " # introducing sampling error\n", 470 | " if self.batch_size is not None:\n", 471 | " pl = prob_from_sample(sample_from_prob(np.arange(len(pl)), pl, self.batch_size),\n", 472 | " len(pl), False)\n", 473 | " return pl\n", 474 | "\n", 475 | " def mmd_loss(self, theta_list):\n", 476 | " '''get the loss'''\n", 477 | " # get and cahe probability distritbution of Born Machine\n", 478 | " self._prob = self.pdf(theta_list)\n", 479 | " # use wave function to get mmd loss\n", 480 | " return self.mmd(self._prob, self.p_data)\n", 481 | "\n", 482 | " def gradient(self, theta_list):\n", 483 | " '''\n", 484 | " cheat and get gradient.\n", 485 | " '''\n", 486 | " prob = self.pdf(theta_list)\n", 487 | " grad = []\n", 488 | " for i in range(len(theta_list)):\n", 489 | " # pi/2 phase\n", 490 | " theta_list[i] += np.pi/2.\n", 491 | " prob_pos = self.pdf(theta_list)\n", 492 | " # -pi/2 phase\n", 493 | " theta_list[i] -= np.pi\n", 494 | " prob_neg = self.pdf(theta_list)\n", 495 | " # recover\n", 496 | " theta_list[i] += np.pi/2.\n", 497 | "\n", 498 | " grad_pos = self.mmd.kernel_expect(prob, prob_pos) - self.mmd.kernel_expect(prob, prob_neg)\n", 499 | " grad_neg = self.mmd.kernel_expect(self.p_data, prob_pos) - self.mmd.kernel_expect(self.p_data, prob_neg)\n", 500 | " grad.append(grad_pos - grad_neg)\n", 501 | " return np.array(grad)\n", 502 | "\n", 503 | " def gradient_numerical(self, theta_list, delta=1e-2):\n", 504 | " '''\n", 505 | " numerical differenciation.\n", 506 | " '''\n", 507 | " grad = []\n", 508 | " for i in range(len(theta_list)):\n", 509 | " theta_list[i] += delta/2.\n", 510 | " loss_pos = self.mmd_loss(theta_list)\n", 511 | " theta_list[i] -= delta\n", 512 | " loss_neg = self.mmd_loss(theta_list)\n", 513 | " theta_list[i] += delta/2.\n", 514 | "\n", 515 | " grad_i = (loss_pos - loss_neg)/delta\n", 516 | " grad.append(grad_i)\n", 517 | " return np.array(grad)\n", 518 | " \n", 519 | "def sample_from_prob(x, pl, num_sample):\n", 520 | " '''\n", 521 | " sample x from probability.\n", 522 | " '''\n", 523 | " pl = 1. / pl.sum() * pl\n", 524 | " indices = np.arange(len(x))\n", 525 | " res = np.random.choice(indices, num_sample, p=pl)\n", 526 | " return np.array([x[r] for r in res])\n", 527 | "\n", 528 | "\n", 529 | "def prob_from_sample(dataset, hndim, packbits):\n", 530 | " '''\n", 531 | " emperical probability from data.\n", 532 | " '''\n", 533 | " if packbits:\n", 534 | " dataset = packnbits(dataset).ravel()\n", 535 | " p_data = np.bincount(dataset, minlength=hndim)\n", 536 | " p_data = p_data / float(np.sum(p_data))\n", 537 | " return p_data" 538 | ] 539 | }, 540 | { 541 | "cell_type": "markdown", 542 | "metadata": {}, 543 | "source": [ 544 | "# MMD Loss Function" 545 | ] 546 | }, 547 | { 548 | "cell_type": "code", 549 | "execution_count": 26, 550 | "metadata": { 551 | "collapsed": true 552 | }, 553 | "outputs": [], 554 | "source": [ 555 | "class RBFMMD2(object):\n", 556 | " def __init__(self, sigma_list, basis):\n", 557 | " self.sigma_list = sigma_list\n", 558 | " self.basis = basis\n", 559 | " self.K = mix_rbf_kernel(basis, basis, self.sigma_list)\n", 560 | "\n", 561 | " def __call__(self, px, py):\n", 562 | " '''\n", 563 | " Args:\n", 564 | " px (1darray, default=None): probability for data set x, used only when self.is_exact==True.\n", 565 | " py (1darray, default=None): same as px, but for data set y.\n", 566 | "\n", 567 | " Returns:\n", 568 | " float, loss.\n", 569 | " '''\n", 570 | " pxy = px-py\n", 571 | " return self.kernel_expect(pxy, pxy)\n", 572 | "\n", 573 | " def kernel_expect(self, px, py):\n", 574 | " return px.dot(self.K).dot(py)\n", 575 | "\n", 576 | "def mix_rbf_kernel(x, y, sigma_list):\n", 577 | " ndim = x.ndim\n", 578 | " if ndim == 1:\n", 579 | " exponent = np.abs(x[:, None] - y[None, :])**2\n", 580 | " elif ndim == 2:\n", 581 | " exponent = ((x[:, None, :] - y[None, :, :])**2).sum(axis=2)\n", 582 | " else:\n", 583 | " raise\n", 584 | " K = 0.0\n", 585 | " for sigma in sigma_list:\n", 586 | " gamma = 1.0 / (2 * sigma)\n", 587 | " K = K + np.exp(-gamma * exponent)\n", 588 | " return K" 589 | ] 590 | }, 591 | { 592 | "cell_type": "code", 593 | "execution_count": 27, 594 | "metadata": { 595 | "collapsed": true 596 | }, 597 | "outputs": [], 598 | "source": [ 599 | "def load_barstripe(geometry, depth):\n", 600 | " '''3 x 3 bar and stripes.'''\n", 601 | " num_bit = np.prod(geometry)\n", 602 | "\n", 603 | " # standard circuit\n", 604 | " pairs = get_nn_pairs(geometry)\n", 605 | " circuit = get_demo_circuit(num_bit, depth, pairs)\n", 606 | "\n", 607 | " # bar and stripe\n", 608 | " p_bs = barstripe_pdf(geometry)\n", 609 | "\n", 610 | " # mmd loss\n", 611 | " mmd = RBFMMD2(sigma_list=[0.5,1,2,4], basis=binary_basis((num_bit,)))\n", 612 | "\n", 613 | " # Born Machine\n", 614 | " bm = BornMachine(circuit, mmd, p_bs)\n", 615 | " return bm" 616 | ] 617 | }, 618 | { 619 | "cell_type": "code", 620 | "execution_count": 30, 621 | "metadata": {}, 622 | "outputs": [ 623 | { 624 | "name": "stdout", 625 | "output_type": "stream", 626 | "text": [ 627 | "0.10912834837939642\n" 628 | ] 629 | } 630 | ], 631 | "source": [ 632 | "depth = 4\n", 633 | "np.random.seed(2)\n", 634 | "\n", 635 | "bm = load_barstripe(geometry, depth)\n", 636 | "theta_list = np.random.random(bm.circuit.num_param)*2*np.pi\n", 637 | "\n", 638 | "print(bm.mmd_loss(theta_list))" 639 | ] 640 | }, 641 | { 642 | "cell_type": "code", 643 | "execution_count": 31, 644 | "metadata": {}, 645 | "outputs": [ 646 | { 647 | "name": "stdout", 648 | "output_type": "stream", 649 | "text": [ 650 | "[ 1.07932375e-02 5.23353936e-03 -8.94735027e-03 -1.22808658e-03\n", 651 | " 7.34646954e-03 3.76654432e-03 2.71817992e-02 -2.23601194e-03\n", 652 | " -2.27839701e-02 -6.91220541e-03 5.10069885e-03 -1.27592444e-02\n", 653 | " 1.57912827e-03 7.20107172e-04 -1.74986844e-03 3.41278303e-03\n", 654 | " -1.42311830e-02 5.96123089e-03 2.03191175e-03 -8.95279875e-03\n", 655 | " -2.60954943e-03 -5.74649158e-03 2.82203223e-03 -1.97177952e-03\n", 656 | " -1.86504456e-03 -1.30923764e-02 7.37189555e-03 4.17172566e-03\n", 657 | " -3.38442841e-03 6.44492103e-03 7.17053954e-03 1.51925480e-02\n", 658 | " -3.56108689e-03 7.44084075e-04 3.58691486e-04 -8.46230893e-04\n", 659 | " 4.77805972e-03 -1.91900025e-03 7.55088172e-03 1.05937046e-02\n", 660 | " 2.00790125e-03 7.90649541e-03 7.66929549e-03 -1.58358076e-02\n", 661 | " 1.55342533e-02 2.52968796e-02 -2.47725625e-03 -1.98173896e-02\n", 662 | " 2.66229079e-05 4.33049173e-03 -2.83356469e-03 -8.62518122e-03\n", 663 | " 1.20520725e-03 1.30931536e-02 7.95482604e-03 -3.01180314e-02\n", 664 | " 8.60723630e-03 -9.65504172e-03 2.28045794e-02 -9.98723474e-03\n", 665 | " -1.07840092e-02 -2.29531143e-02 5.64496019e-03 1.04333038e-02\n", 666 | " 3.89202729e-03 -9.41239316e-03 1.43033842e-03 4.14785032e-03\n", 667 | " -1.33691906e-03 -1.66344402e-02 4.32310292e-03 4.61166829e-03\n", 668 | " -9.36671452e-04 8.79166985e-03 2.24355818e-04 -6.96491641e-03\n", 669 | " 1.51569099e-03 -1.08972714e-02]\n", 670 | "[ 1.07932054e-02 5.23349643e-03 -8.94704697e-03 -1.22807483e-03\n", 671 | " 7.34648428e-03 3.76658124e-03 2.71817518e-02 -2.23598266e-03\n", 672 | " -2.27837622e-02 -6.91214823e-03 5.10099187e-03 -1.27591669e-02\n", 673 | " 1.57910723e-03 7.20048174e-04 -1.74984326e-03 3.41282076e-03\n", 674 | " -1.42311411e-02 5.96126993e-03 2.03170384e-03 -8.95307945e-03\n", 675 | " -2.60971201e-03 -5.74641364e-03 2.82198223e-03 -1.97176564e-03\n", 676 | " -1.86498766e-03 -1.30924457e-02 7.37166701e-03 4.17202512e-03\n", 677 | " -3.38438264e-03 6.44498718e-03 7.17043836e-03 1.51925360e-02\n", 678 | " -3.56099290e-03 7.44129926e-04 3.58607519e-04 -8.46280028e-04\n", 679 | " 4.77819804e-03 -1.91889725e-03 7.55075323e-03 1.05937069e-02\n", 680 | " 2.00797934e-03 7.90650527e-03 7.66902881e-03 -1.58356788e-02\n", 681 | " 1.55341611e-02 2.52966967e-02 -2.47711371e-03 -1.98171798e-02\n", 682 | " 2.67131301e-05 4.33049651e-03 -2.83350035e-03 -8.62518444e-03\n", 683 | " 1.20529512e-03 1.30931289e-02 7.95466918e-03 -3.01177447e-02\n", 684 | " 8.60708168e-03 -9.65500375e-03 2.28044445e-02 -9.98719627e-03\n", 685 | " -1.07840756e-02 -2.29528259e-02 5.64499284e-03 1.04332015e-02\n", 686 | " 3.89203865e-03 -9.41238538e-03 1.43033403e-03 4.14789741e-03\n", 687 | " -1.33691140e-03 -1.66343317e-02 4.32303130e-03 4.61171111e-03\n", 688 | " -9.36663851e-04 8.79165404e-03 2.24366194e-04 -6.96496871e-03\n", 689 | " 1.51568482e-03 -1.08972472e-02]\n" 690 | ] 691 | } 692 | ], 693 | "source": [ 694 | "g1 = bm.gradient(theta_list)\n", 695 | "print(g1)\n", 696 | "g2 = bm.gradient_numerical(theta_list)\n", 697 | "print(g2)" 698 | ] 699 | }, 700 | { 701 | "cell_type": "markdown", 702 | "metadata": {}, 703 | "source": [ 704 | "# Different Training Strategies" 705 | ] 706 | }, 707 | { 708 | "cell_type": "code", 709 | "execution_count": 32, 710 | "metadata": { 711 | "collapsed": true 712 | }, 713 | "outputs": [], 714 | "source": [ 715 | " def train(bm, theta_list, method, max_iter=1000, popsize=50, step_rate=0.1):\n", 716 | " '''train a Born Machine.'''\n", 717 | " step = [0]\n", 718 | " def callback(x, *args, **kwargs):\n", 719 | " step[0] += 1\n", 720 | " print('step = %d, loss = %s'%(step[0], bm.mmd_loss(x)))\n", 721 | " \n", 722 | " theta_list = np.array(theta_list)\n", 723 | " if method == 'Adam':\n", 724 | " from climin import Adam\n", 725 | " optimizer = Adam(wrt=theta_list, fprime=bm.gradient,step_rate=step_rate)\n", 726 | " for info in optimizer:\n", 727 | " callback(theta_list)\n", 728 | " if step[0] == max_iter:\n", 729 | " break\n", 730 | " return bm.mmd_loss(theta_list), theta_list\n", 731 | " else:\n", 732 | " from scipy.optimize import minimize\n", 733 | " res = minimize(bm.mmd_loss, x0=theta_list,\n", 734 | " method=method, jac = bm.gradient, tol=1e-12,\n", 735 | " options={'maxiter': max_iter, 'disp': 0, 'gtol':1e-10, 'ftol':0},\n", 736 | " callback=callback,\n", 737 | " )\n", 738 | " return res.fun, res.x" 739 | ] 740 | }, 741 | { 742 | "cell_type": "code", 743 | "execution_count": 33, 744 | "metadata": {}, 745 | "outputs": [ 746 | { 747 | "name": "stdout", 748 | "output_type": "stream", 749 | "text": [ 750 | "step = 1, loss = 0.21268315180514008\n", 751 | "step = 2, loss = 0.08979787904633829\n", 752 | "step = 3, loss = 0.045239408272263415\n", 753 | "step = 4, loss = 0.0319567463303877\n", 754 | "step = 5, loss = 0.014244764719906206\n", 755 | "step = 6, loss = 0.008397834437896594\n", 756 | "step = 7, loss = 0.005594198817760249\n", 757 | "step = 8, loss = 0.0035356451139186952\n", 758 | "step = 9, loss = 0.0018474418666711379\n", 759 | "step = 10, loss = 0.0016284635802293161\n", 760 | "step = 11, loss = 0.0012383183337018196\n", 761 | "step = 12, loss = 0.0010288230114687451\n", 762 | "step = 13, loss = 0.0008540045900669738\n", 763 | "step = 14, loss = 0.0006560573346776953\n", 764 | "step = 15, loss = 0.00048621976264252327\n", 765 | "step = 16, loss = 0.0003477521667421844\n", 766 | "step = 17, loss = 0.00024623753601417235\n", 767 | "step = 18, loss = 0.00021370595732652582\n", 768 | "step = 19, loss = 0.00016751733793579133\n", 769 | "step = 20, loss = 0.00014500158499851105\n", 770 | "step = 21, loss = 0.00013674627438064655\n" 771 | ] 772 | }, 773 | { 774 | "data": { 775 | "image/png": "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\n", 776 | "text/plain": [ 777 | "
" 778 | ] 779 | }, 780 | "metadata": {}, 781 | "output_type": "display_data" 782 | } 783 | ], 784 | "source": [ 785 | "np.random.seed(2)\n", 786 | "depth = 4\n", 787 | "\n", 788 | "bm = load_barstripe((2, 2), depth)\n", 789 | "theta_list = np.random.random(bm.circuit.num_param)*2*np.pi\n", 790 | "loss, theta_list = train(bm, theta_list, 'L-BFGS-B', max_iter=20, popsize=10)\n", 791 | "pl = bm.pdf(theta_list)\n", 792 | "\n", 793 | "# display\n", 794 | "plt.plot(bm.p_data)\n", 795 | "plt.plot(pl)\n", 796 | "plt.legend(['Data', 'Gradient Born Machine'])\n", 797 | "plt.show()" 798 | ] 799 | }, 800 | { 801 | "cell_type": "code", 802 | "execution_count": null, 803 | "metadata": { 804 | "collapsed": true 805 | }, 806 | "outputs": [], 807 | "source": [] 808 | } 809 | ], 810 | "metadata": { 811 | "kernelspec": { 812 | "display_name": "Python 3", 813 | "language": "python", 814 | "name": "python3" 815 | }, 816 | "language_info": { 817 | "codemirror_mode": { 818 | "name": "ipython", 819 | "version": 3 820 | }, 821 | "file_extension": ".py", 822 | "mimetype": "text/x-python", 823 | "name": "python", 824 | "nbconvert_exporter": "python", 825 | "pygments_lexer": "ipython3", 826 | "version": "3.6.4" 827 | } 828 | }, 829 | "nbformat": 4, 830 | "nbformat_minor": 2 831 | } 832 | -------------------------------------------------------------------------------- /docs/1804.04168.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiggleLiu/QuantumCircuitBornMachine/cb0648843210a249392b3600577eb69455b78b3d/docs/1804.04168.pdf -------------------------------------------------------------------------------- /docs/images/qcbm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiggleLiu/QuantumCircuitBornMachine/cb0648843210a249392b3600577eb69455b78b3d/docs/images/qcbm.png -------------------------------------------------------------------------------- /notebooks/qcbm_gaussian.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 0, 6 | "metadata": { 7 | "colab": { 8 | "autoexec": { 9 | "startup": false, 10 | "wait_interval": 0 11 | } 12 | }, 13 | "colab_type": "code", 14 | "collapsed": true, 15 | "id": "hHqdDh5Bl_uF" 16 | }, 17 | "outputs": [], 18 | "source": [ 19 | "import numpy as np\n", 20 | "import matplotlib.pyplot as plt\n", 21 | "import scipy.sparse as sps" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": { 27 | "colab_type": "text", 28 | "id": "9Po0tv6fl_uR" 29 | }, 30 | "source": [ 31 | "# training set\n", 32 | "Our training set is a simple Gaussian distribution" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 20, 38 | "metadata": { 39 | "colab": { 40 | "autoexec": { 41 | "startup": false, 42 | "wait_interval": 0 43 | }, 44 | "base_uri": "https://localhost:8080/", 45 | "height": 348 46 | }, 47 | "colab_type": "code", 48 | "executionInfo": { 49 | "elapsed": 1081, 50 | "status": "ok", 51 | "timestamp": 1523077605597, 52 | "user": { 53 | "displayName": "刘金国", 54 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 55 | "userId": "116824001998056121289" 56 | }, 57 | "user_tz": -480 58 | }, 59 | "id": "Ds9tMSqAl_uU", 60 | "outputId": "0340dbd8-8e0a-441a-e443-5dcf50ec8162" 61 | }, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "image/png": "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\nG9+UywAAAABJRU5ErkJggg==\n", 66 | "text/plain": [ 67 | "" 68 | ] 69 | }, 70 | "metadata": { 71 | "tags": [] 72 | }, 73 | "output_type": "display_data" 74 | } 75 | ], 76 | "source": [ 77 | "def gaussian_pdf(num_bit, mu, sigma):\n", 78 | " '''get gaussian distribution function'''\n", 79 | " x = np.arange(2**num_bit)\n", 80 | " pl = 1. / np.sqrt(2 * np.pi * sigma**2) * \\\n", 81 | " np.exp(-(x - mu)**2 / (2. * sigma**2))\n", 82 | " return pl/pl.sum()\n", 83 | "\n", 84 | "num_bit = 6\n", 85 | "pg = gaussian_pdf(num_bit, mu=2**5-0.5, sigma=2**4)\n", 86 | "plt.plot(pg)\n", 87 | "plt.show()" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": { 93 | "colab_type": "text", 94 | "id": "99B1jfKfl_ud" 95 | }, 96 | "source": [ 97 | "# Build Circuits\n", 98 | "## Building Blocks\n", 99 | "Define matrix representations of operations using scipy sparse matrices.\n", 100 | "They are grouped to become a layer in a circuit, this layer can be `ArbitraryRotation` or `CNOTEntangler`.\n", 101 | "Which are used as our basic building blocks of our `Born Machines`." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 0, 107 | "metadata": { 108 | "colab": { 109 | "autoexec": { 110 | "startup": false, 111 | "wait_interval": 0 112 | } 113 | }, 114 | "colab_type": "code", 115 | "collapsed": true, 116 | "id": "kYr3mt1rl_uf" 117 | }, 118 | "outputs": [], 119 | "source": [ 120 | "###### Pauli Matrices ########\n", 121 | "I2 = sps.eye(2).tocsr()\n", 122 | "sx = sps.csr_matrix([[0,1],[1,0.]])\n", 123 | "sy = sps.csr_matrix([[0,-1j],[1j,0.]])\n", 124 | "sz = sps.csr_matrix([[1,0],[0,-1.]])\n", 125 | "\n", 126 | "p0 = (sz + I2) / 2 #projection operator to |0><0|\n", 127 | "p1 = (-sz + I2) / 2\n", 128 | "\n", 129 | "# single bit rotation matrices\n", 130 | "def _ri(si, theta):\n", 131 | " return np.cos(theta/2.)*I2 - 1j*np.sin(theta/2.)*si\n", 132 | "\n", 133 | "def rot(t1, t2, t3):\n", 134 | " '''\n", 135 | " a general rotation gate rz(t3)rx(r2)rz(t1).\n", 136 | "\n", 137 | " Args:\n", 138 | " t1, t2, t3 (float): three angles.\n", 139 | "\n", 140 | " Returns:\n", 141 | " 2x2 csr_matrix: rotation matrix.\n", 142 | " '''\n", 143 | " return _ri(sz, t3).dot(_ri(sx, t2)).dot(_ri(sz, t1))\n", 144 | "\n", 145 | "# multiple bit construction\n", 146 | "def CNOT(ibit, jbit, n):\n", 147 | " '''\n", 148 | " CNOT gate\n", 149 | " \n", 150 | " Args:\n", 151 | " ibit (int): control bit.\n", 152 | " jbit (int): controled bit.\n", 153 | " n (int): total number of qubits.\n", 154 | " \n", 155 | " Returns:\n", 156 | " 4x4 csr_matrix: CNOT matrix.\n", 157 | " '''\n", 158 | " res = _([p0, I2], [ibit, jbit], n)\n", 159 | " res = res + _([p1, sx], [ibit, jbit], n)\n", 160 | " return res\n", 161 | "\n", 162 | "def _(ops, locs, n):\n", 163 | " '''\n", 164 | " Compile operators into specific Hilbert space.\n", 165 | "\n", 166 | " notice the last bit is the high end bit!\n", 167 | "\n", 168 | " Args:\n", 169 | " ops (list): list of single bit operators.\n", 170 | " locs (list): list of positions.\n", 171 | " n (int): total number of bits.\n", 172 | "\n", 173 | " Returns:\n", 174 | " csr_matrix: resulting matrix.\n", 175 | " '''\n", 176 | " if np.ndim(locs) == 0:\n", 177 | " locs = [locs]\n", 178 | " if not isinstance(ops, (list, tuple)):\n", 179 | " ops = [ops]\n", 180 | " locs = np.asarray(locs)\n", 181 | " locs = n - locs\n", 182 | " order = np.argsort(locs)\n", 183 | " locs = np.concatenate([[0], locs[order], [n + 1]])\n", 184 | " return _wrap_identity([ops[i] for i in order], np.diff(locs) - 1)\n", 185 | "\n", 186 | "\n", 187 | "def _wrap_identity(data_list, num_bit_list):\n", 188 | " if len(num_bit_list) != len(data_list) + 1:\n", 189 | " raise Exception()\n", 190 | "\n", 191 | " res = sps.eye(2**num_bit_list[0])\n", 192 | " for data, nbit in zip(data_list, num_bit_list[1:]):\n", 193 | " res = sps.kron(res, data)\n", 194 | " res = sps.kron(res, sps.eye(2**nbit, dtype='complex128'))\n", 195 | " return res\n", 196 | "\n", 197 | "\n", 198 | "def initial_wf(num_bit):\n", 199 | " '''initial wave function |00...0>.'''\n", 200 | " wf = np.zeros(2**num_bit, dtype='complex128')\n", 201 | " wf[0] = 1.\n", 202 | " return wf" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 0, 208 | "metadata": { 209 | "colab": { 210 | "autoexec": { 211 | "startup": false, 212 | "wait_interval": 0 213 | } 214 | }, 215 | "colab_type": "code", 216 | "collapsed": true, 217 | "id": "1MBVIfw3l_ul" 218 | }, 219 | "outputs": [], 220 | "source": [ 221 | "class ArbitraryRotation(object):\n", 222 | " '''Arbitrary rotation gate'''\n", 223 | " def __init__(self, num_bit):\n", 224 | " self.num_bit = num_bit\n", 225 | " # mask is used to filter out some irrelevant (marked False) parameters\n", 226 | " self.mask = np.array([True] * (3*num_bit), dtype='bool')\n", 227 | "\n", 228 | " @property\n", 229 | " def num_param(self):\n", 230 | " return self.mask.sum()\n", 231 | "\n", 232 | " def tocsr(self, theta_list):\n", 233 | " '''transform this block to a sequence of csr_matrices.'''\n", 234 | " theta_list_ = np.zeros(3*self.num_bit)\n", 235 | " theta_list_[self.mask] = theta_list\n", 236 | " rots = [rot(*ths) for ths in theta_list_.reshape([self.num_bit,3])]\n", 237 | " res = [_([r], [i], self.num_bit) for i,r in enumerate(rots)]\n", 238 | " return res\n", 239 | "\n", 240 | "class CNOTEntangler(object):\n", 241 | " '''\n", 242 | " CNOT Entangler Layer.\n", 243 | " \n", 244 | " Args:\n", 245 | " pairs (list): a list of tuples to represent connections.\n", 246 | " '''\n", 247 | " def __init__(self, num_bit, pairs):\n", 248 | " self.num_bit = num_bit\n", 249 | " self.pairs = pairs\n", 250 | "\n", 251 | " @property\n", 252 | " def num_param(self):\n", 253 | " return 0\n", 254 | "\n", 255 | " def tocsr(self, theta_list):\n", 256 | " '''transform this block to a sequence of csr_matrices.'''\n", 257 | " i, j = self.pairs[0]\n", 258 | " res = CNOT(i, j, self.num_bit)\n", 259 | " for i, j in self.pairs[1:]:\n", 260 | " res = CNOT(i,j,self.num_bit).dot(res)\n", 261 | " res.eliminate_zeros()\n", 262 | " return [res]" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": { 268 | "colab_type": "text", 269 | "id": "8dR6z_Sjl_uo" 270 | }, 271 | "source": [ 272 | "# Circuit\n", 273 | "A circuit is consist of queued layers, here this data structure is `BlockQueue`. A `BlockQueue` uses `theta_last` and `memo` attributes to cache sparse operators, this will accelerate simulation significantly." 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 0, 279 | "metadata": { 280 | "colab": { 281 | "autoexec": { 282 | "startup": false, 283 | "wait_interval": 0 284 | } 285 | }, 286 | "colab_type": "code", 287 | "collapsed": true, 288 | "id": "ax0MxiMSl_up" 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "class BlockQueue(list):\n", 293 | " '''\n", 294 | " Block Queue that keep track of theta_list changing history, for fast update.\n", 295 | " \n", 296 | " Attributes:\n", 297 | " theta_last (1darray): the cached circuit parameters.\n", 298 | " memo (list): cached sparse matrices for layers.\n", 299 | " '''\n", 300 | " def __init__(self, *args):\n", 301 | " list.__init__(self, *args)\n", 302 | " self.theta_last = None\n", 303 | " self.memo = None\n", 304 | "\n", 305 | " def __call__(self, qureg, theta_list):\n", 306 | " '''\n", 307 | " Apply operations on vector basis: qureg. This operation changes vector inplace.\n", 308 | " \n", 309 | " Args:\n", 310 | " qureg (1darray): vector basis.\n", 311 | " theta_list (1darray): circuit parameters:\n", 312 | " '''\n", 313 | " # cache? if theta_list change <= 1 parameters, then don't touch memory.\n", 314 | " remember = self.theta_last is None or (abs(self.theta_last-theta_list)>1e-12).sum() > 1\n", 315 | "\n", 316 | " mats = []\n", 317 | " theta_last = self.theta_last\n", 318 | " if remember:\n", 319 | " self.theta_last = theta_list.copy()\n", 320 | "\n", 321 | " qureg_ = qureg\n", 322 | " for iblock, block in enumerate(self):\n", 323 | " # generate or use a block matrix\n", 324 | " num_param = block.num_param\n", 325 | " theta_i, theta_list = np.split(theta_list, [num_param])\n", 326 | " if theta_last is not None:\n", 327 | " theta_o, theta_last = np.split(theta_last, [num_param])\n", 328 | " if self.memo is not None and (num_param==0 or np.abs(theta_i-theta_o).max()<1e-12):\n", 329 | " # use data cached in memory\n", 330 | " mat = self.memo[iblock]\n", 331 | " else:\n", 332 | " if self.memo is not None and not remember:\n", 333 | " # update the changed gate, but not touching memory.\n", 334 | " mat = _rot_tocsr_update1(block, self.memo[iblock], theta_o, theta_i)\n", 335 | " else:\n", 336 | " # regenerate one\n", 337 | " mat = block.tocsr(theta_i)\n", 338 | " for mat_i in mat:\n", 339 | " qureg_ = mat_i.dot(qureg_)\n", 340 | " mats.append(mat)\n", 341 | "\n", 342 | " if remember:\n", 343 | " # cache data\n", 344 | " self.memo = mats\n", 345 | " # update register\n", 346 | " qureg[...] = qureg_\n", 347 | " np.testing.assert_(len(theta_list)==0)\n", 348 | " \n", 349 | " @property\n", 350 | " def num_bit(self):\n", 351 | " return self[0].num_bit\n", 352 | "\n", 353 | " @property\n", 354 | " def num_param(self):\n", 355 | " return sum([b.num_param for b in self])\n", 356 | "\n", 357 | "def _rot_tocsr_update1(layer, old, theta_old, theta_new):\n", 358 | " '''\n", 359 | " rotation layer csr_matrices update method.\n", 360 | " \n", 361 | " Args:\n", 362 | " layer (ArbitraryRotation): rotation layer.\n", 363 | " old (csr_matrix): old matrices.\n", 364 | " theta_old (1darray): old parameters.\n", 365 | " theta_new (1darray): new parameters.\n", 366 | "\n", 367 | " Returns:\n", 368 | " list of csr_matrix: new rotation matrices after the theta changed.\n", 369 | " '''\n", 370 | " idiff_param = np.where(abs(theta_old-theta_new)>1e-12)[0].item()\n", 371 | " idiff = np.where(layer.mask)[0][idiff_param]\n", 372 | "\n", 373 | " # get rotation parameters\n", 374 | " isite = idiff//3\n", 375 | " theta_list_ = np.zeros(3*layer.num_bit)\n", 376 | " theta_list_[layer.mask] = theta_new\n", 377 | " \n", 378 | " new = old[:]\n", 379 | " new[isite] = _(rot(*theta_list_[isite*3:isite*3+3]), isite, layer.num_bit)\n", 380 | " return new" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 0, 386 | "metadata": { 387 | "colab": { 388 | "autoexec": { 389 | "startup": false, 390 | "wait_interval": 0 391 | } 392 | }, 393 | "colab_type": "code", 394 | "collapsed": true, 395 | "id": "pPa44pmWl_ur" 396 | }, 397 | "outputs": [], 398 | "source": [ 399 | "def get_nn_pairs(num_bit):\n", 400 | " '''get nearest neighbor pairs.'''\n", 401 | " res = []\n", 402 | " for inth in range(2):\n", 403 | " for i in range(inth, num_bit, 2):\n", 404 | " res = res + [(i, i_ % num_bit) for i_ in range(i + 1, i + 2)]\n", 405 | " return res\n", 406 | "\n", 407 | "def get_demo_circuit(num_bit, depth, pairs):\n", 408 | " '''Get the circuit used for demo'''\n", 409 | " blocks = []\n", 410 | " # build circuit\n", 411 | " for idepth in range(depth+1):\n", 412 | " blocks.append(ArbitraryRotation(num_bit))\n", 413 | " if idepth!=depth:\n", 414 | " blocks.append(CNOTEntangler(num_bit, pairs))\n", 415 | "\n", 416 | " # set leading and trailing Rz to disabled\n", 417 | " blocks[0].mask[::3] = False\n", 418 | " blocks[-1].mask[2::3] = False\n", 419 | " return BlockQueue(blocks)" 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": 25, 425 | "metadata": { 426 | "colab": { 427 | "autoexec": { 428 | "startup": false, 429 | "wait_interval": 0 430 | }, 431 | "base_uri": "https://localhost:8080/", 432 | "height": 55 433 | }, 434 | "colab_type": "code", 435 | "executionInfo": { 436 | "elapsed": 1228, 437 | "status": "ok", 438 | "timestamp": 1523077612959, 439 | "user": { 440 | "displayName": "刘金国", 441 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 442 | "userId": "116824001998056121289" 443 | }, 444 | "user_tz": -480 445 | }, 446 | "id": "ksYpVLV9l_ut", 447 | "outputId": "e3e2d50f-5a3e-466c-952a-6aee8e114adf" 448 | }, 449 | "outputs": [ 450 | { 451 | "name": "stdout", 452 | "output_type": "stream", 453 | "text": [ 454 | "[<__main__.ArbitraryRotation object at 0x7f45778f8668>, <__main__.CNOTEntangler object at 0x7f45778f86d8>, <__main__.ArbitraryRotation object at 0x7f45778f8710>, <__main__.CNOTEntangler object at 0x7f45778f8748>, <__main__.ArbitraryRotation object at 0x7f45778f8780>, <__main__.CNOTEntangler object at 0x7f45778f87b8>, <__main__.ArbitraryRotation object at 0x7f45778f87f0>, <__main__.CNOTEntangler object at 0x7f45778f8828>, <__main__.ArbitraryRotation object at 0x7f45778f8860>, <__main__.CNOTEntangler object at 0x7f45778f8898>, <__main__.ArbitraryRotation object at 0x7f45778f88d0>, <__main__.CNOTEntangler object at 0x7f45778f8908>, <__main__.ArbitraryRotation object at 0x7f45778f8940>]\n" 455 | ] 456 | } 457 | ], 458 | "source": [ 459 | "# let's see how this circuit look like\n", 460 | "depth = 6\n", 461 | "pairs = get_nn_pairs(num_bit)\n", 462 | "circuit = get_demo_circuit(num_bit, depth, pairs)\n", 463 | "print(circuit)" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 26, 469 | "metadata": { 470 | "colab": { 471 | "autoexec": { 472 | "startup": false, 473 | "wait_interval": 0 474 | }, 475 | "base_uri": "https://localhost:8080/", 476 | "height": 404 477 | }, 478 | "colab_type": "code", 479 | "executionInfo": { 480 | "elapsed": 1375, 481 | "status": "ok", 482 | "timestamp": 1523077614782, 483 | "user": { 484 | "displayName": "刘金国", 485 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 486 | "userId": "116824001998056121289" 487 | }, 488 | "user_tz": -480 489 | }, 490 | "id": "iXCJDkXUl_ux", 491 | "outputId": "542a73d8-5ffb-4548-b323-302f8cb55765" 492 | }, 493 | "outputs": [ 494 | { 495 | "name": "stdout", 496 | "output_type": "stream", 497 | "text": [ 498 | "[0.70710678+0.j 0. +0.j 0. +0.j\n", 499 | " 0. +0.j 0. +0.j 0. +0.j\n", 500 | " 0. +0.j 0. +0.j 0. +0.j\n", 501 | " 0. +0.j 0. +0.j 0. +0.j\n", 502 | " 0. +0.j 0. +0.j 0. +0.j\n", 503 | " 0. +0.j 0. +0.j 0. +0.j\n", 504 | " 0. +0.j 0. +0.j 0. +0.j\n", 505 | " 0. +0.j 0. +0.j 0. +0.j\n", 506 | " 0. +0.j 0. +0.j 0. +0.j\n", 507 | " 0. +0.j 0. +0.j 0. +0.j\n", 508 | " 0. +0.j 0. +0.j 0. -0.70710678j\n", 509 | " 0. +0.j 0. +0.j 0. +0.j\n", 510 | " 0. +0.j 0. +0.j 0. +0.j\n", 511 | " 0. +0.j 0. +0.j 0. +0.j\n", 512 | " 0. +0.j 0. +0.j 0. +0.j\n", 513 | " 0. +0.j 0. +0.j 0. +0.j\n", 514 | " 0. +0.j 0. +0.j 0. +0.j\n", 515 | " 0. +0.j 0. +0.j 0. +0.j\n", 516 | " 0. +0.j 0. +0.j 0. +0.j\n", 517 | " 0. +0.j 0. +0.j 0. +0.j\n", 518 | " 0. +0.j 0. +0.j 0. +0.j\n", 519 | " 0. +0.j ]\n" 520 | ] 521 | } 522 | ], 523 | "source": [ 524 | "# let's see how this circuit works\n", 525 | "# rotating the last bit along x axis by pi/2.\n", 526 | "theta_list = np.zeros(circuit.num_param)\n", 527 | "theta_list[-1] = np.pi/2.\n", 528 | "\n", 529 | "wf = initial_wf(num_bit)\n", 530 | "circuit(wf, theta_list)\n", 531 | "\n", 532 | "# now you see the last bit is the high end bit!\n", 533 | "print(wf)" 534 | ] 535 | }, 536 | { 537 | "cell_type": "markdown", 538 | "metadata": { 539 | "colab_type": "text", 540 | "id": "ZyeNb3A3l_uz" 541 | }, 542 | "source": [ 543 | "# MMD Loss Function\n", 544 | "As a reminder\n", 545 | "\\begin{align}\n", 546 | " \\mathcal{L} =& \\left\\|\\sum_{x} p_\\theta(x) \\phi(x)- \\sum_{x} \\pi(x) \\phi(x) \\right\\|^2 \\\\\n", 547 | " =&\\langle K(x,y)\\rangle_{x\\sim p_\\theta, y\\sim p_\\theta}-2\\langle K(x,y)\\rangle_{x\\sim p_\\theta,y\\sim \\pi}+\\langle K(x, y)\\rangle_{x\\sim \\pi,y\\sim \\pi}\n", 548 | "\\end{align}\n" 549 | ] 550 | }, 551 | { 552 | "cell_type": "code", 553 | "execution_count": 0, 554 | "metadata": { 555 | "colab": { 556 | "autoexec": { 557 | "startup": false, 558 | "wait_interval": 0 559 | } 560 | }, 561 | "colab_type": "code", 562 | "collapsed": true, 563 | "id": "rNaRWyALl_u0" 564 | }, 565 | "outputs": [], 566 | "source": [ 567 | "class RBFMMD2(object):\n", 568 | " '''\n", 569 | " MMD^2 with RBF (Gaussian) kernel.\n", 570 | " \n", 571 | " Args:\n", 572 | " sigma_list (list): a list of bandwidths.\n", 573 | " basis (1darray): defininng space.\n", 574 | " \n", 575 | " Attributes:\n", 576 | " K (2darray): full kernel matrix, notice the Hilbert is countable.\n", 577 | " '''\n", 578 | " def __init__(self, sigma_list, basis):\n", 579 | " self.sigma_list = sigma_list\n", 580 | " self.basis = basis\n", 581 | " self.K = mix_rbf_kernel(basis, basis, self.sigma_list)\n", 582 | "\n", 583 | " def __call__(self, px, py):\n", 584 | " '''\n", 585 | " Args:\n", 586 | " px (1darray, default=None): probability for data set x, used only when self.is_exact==True.\n", 587 | " py (1darray, default=None): same as px, but for data set y.\n", 588 | "\n", 589 | " Returns:\n", 590 | " float: loss.\n", 591 | " '''\n", 592 | " pxy = px-py\n", 593 | " return self.kernel_expect(pxy, pxy)\n", 594 | "\n", 595 | " def kernel_expect(self, px, py):\n", 596 | " '''\n", 597 | " expectation value of kernel function.\n", 598 | " \n", 599 | " Args:\n", 600 | " px (1darray): the first PDF.\n", 601 | " py (1darray): the second PDF.\n", 602 | " \n", 603 | " Returns:\n", 604 | " float: kernel expectation.\n", 605 | " '''\n", 606 | " return px.dot(self.K).dot(py)\n", 607 | "\n", 608 | "def mix_rbf_kernel(x, y, sigma_list):\n", 609 | " '''\n", 610 | " multi-RBF kernel.\n", 611 | " \n", 612 | " Args:\n", 613 | " x (1darray|2darray): the collection of samples A.\n", 614 | " x (1darray|2darray): the collection of samples B.\n", 615 | " sigma_list (list): a list of bandwidths.\n", 616 | " \n", 617 | " Returns:\n", 618 | " 2darray: kernel matrix.\n", 619 | " '''\n", 620 | " ndim = x.ndim\n", 621 | " if ndim == 1:\n", 622 | " exponent = np.abs(x[:, None] - y[None, :])**2\n", 623 | " elif ndim == 2:\n", 624 | " exponent = ((x[:, None, :] - y[None, :, :])**2).sum(axis=2)\n", 625 | " else:\n", 626 | " raise\n", 627 | " K = 0.0\n", 628 | " for sigma in sigma_list:\n", 629 | " gamma = 1.0 / (2 * sigma)\n", 630 | " K = K + np.exp(-gamma * exponent)\n", 631 | " return K" 632 | ] 633 | }, 634 | { 635 | "cell_type": "code", 636 | "execution_count": 39, 637 | "metadata": { 638 | "colab": { 639 | "autoexec": { 640 | "startup": false, 641 | "wait_interval": 0 642 | }, 643 | "base_uri": "https://localhost:8080/", 644 | "height": 52 645 | }, 646 | "colab_type": "code", 647 | "executionInfo": { 648 | "elapsed": 900, 649 | "status": "ok", 650 | "timestamp": 1523078667244, 651 | "user": { 652 | "displayName": "刘金国", 653 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 654 | "userId": "116824001998056121289" 655 | }, 656 | "user_tz": -480 657 | }, 658 | "id": "rqgpurrWjasl", 659 | "outputId": "e0aef7f3-d598-4e15-e16d-7c518fa02711" 660 | }, 661 | "outputs": [ 662 | { 663 | "name": "stdout", 664 | "output_type": "stream", 665 | "text": [ 666 | "Exact Match -> MMD = 0.0000\n", 667 | "Not Match -> MMD = 0.0223\n" 668 | ] 669 | } 670 | ], 671 | "source": [ 672 | "hndim = 2**num_bit\n", 673 | "# mmd loss\n", 674 | "mmd = RBFMMD2(sigma_list=[0.25,4], basis=np.arange(2**num_bit))\n", 675 | "\n", 676 | "# when exact match, MMD loss should be 0, e.g. two gaussians\n", 677 | "print('Exact Match -> MMD = %.4f'%mmd(pg, pg))\n", 678 | "# when not match, loss is not zero\n", 679 | "prand = np.random.random(len(pg))\n", 680 | "print('Not Match -> MMD = %.4f'%mmd(prand/prand.sum(), pg))" 681 | ] 682 | }, 683 | { 684 | "cell_type": "markdown", 685 | "metadata": { 686 | "colab_type": "text", 687 | "id": "He6uVgqzl_u2" 688 | }, 689 | "source": [ 690 | "# Build The Gradient training framework for Born Machine\n", 691 | "The gradient of MMD loss is\n", 692 | "\\begin{eqnarray}\n", 693 | " \\frac{\\partial \\mathcal{L}}{\\partial \\theta_l^i} &=&\\langle{K(x,y)}\\rangle_{x\\sim p_{\\theta^+}, y\\sim p_\\theta}-\\langle {K(x,y)}\\rangle_{x\\sim p_{\\theta^-},y\\sim p_\\theta}\\\\\n", 694 | " &-&\\langle K(x,y)\\rangle_{x\\sim p_{\\theta^+},y\\sim \\pi}+\\langle {K(x,y)}\\rangle_{x\\sim p_{\\theta^-},y\\sim \\pi}.\n", 695 | "\\end{eqnarray}" 696 | ] 697 | }, 698 | { 699 | "cell_type": "code", 700 | "execution_count": 0, 701 | "metadata": { 702 | "colab": { 703 | "autoexec": { 704 | "startup": false, 705 | "wait_interval": 0 706 | } 707 | }, 708 | "colab_type": "code", 709 | "collapsed": true, 710 | "id": "3tRLpcrQl_u3" 711 | }, 712 | "outputs": [], 713 | "source": [ 714 | "class QCBM(object):\n", 715 | " '''\n", 716 | " Quantum Circuit Born Machine framework,\n", 717 | "\n", 718 | " Args:\n", 719 | " circuit (BlockQueue): the circuit architechture.\n", 720 | " mmd (RBFMMD2): maximum mean discrepancy.\n", 721 | " p_data (1darray): data probability distribution in computation basis.\n", 722 | " batch_size (int|None): introducing sampling error, None for no sampling error.\n", 723 | " '''\n", 724 | " def __init__(self, circuit, mmd, p_data, batch_size=None):\n", 725 | " self.circuit = circuit\n", 726 | " self.mmd = mmd\n", 727 | " self.p_data = p_data\n", 728 | " self.batch_size = batch_size\n", 729 | "\n", 730 | " @property\n", 731 | " def depth(self):\n", 732 | " '''defined by the number of entanglers'''\n", 733 | " return (len(self.circuit)-1)//2\n", 734 | "\n", 735 | " def pdf(self, theta_list):\n", 736 | " '''\n", 737 | " get probability distribution function.\n", 738 | " \n", 739 | " Args:\n", 740 | " theta_list (1darray): circuit parameters.\n", 741 | " \n", 742 | " Returns:\n", 743 | " 1darray: probability distribution function.\n", 744 | " '''\n", 745 | " wf = initial_wf(self.circuit.num_bit)\n", 746 | " self.circuit(wf, theta_list)\n", 747 | " pl = np.abs(wf)**2\n", 748 | " # introducing sampling error\n", 749 | " if self.batch_size is not None:\n", 750 | " pl = prob_from_sample(sample_from_prob(np.arange(len(pl)), pl, self.batch_size),\n", 751 | " len(pl))\n", 752 | " return pl\n", 753 | "\n", 754 | " def mmd_loss(self, theta_list):\n", 755 | " '''get the loss'''\n", 756 | " # get and cahe probability distritbution of Born Machine\n", 757 | " self._prob = self.pdf(theta_list)\n", 758 | " # use wave function to get mmd loss\n", 759 | " return self.mmd(self._prob, self.p_data)\n", 760 | "\n", 761 | " def gradient(self, theta_list):\n", 762 | " '''\n", 763 | " cheat and get gradient.\n", 764 | " '''\n", 765 | " prob = self.pdf(theta_list)\n", 766 | " grad = []\n", 767 | " for i in range(len(theta_list)):\n", 768 | " # pi/2 phase\n", 769 | " theta_list[i] += np.pi/2.\n", 770 | " prob_pos = self.pdf(theta_list)\n", 771 | " # -pi/2 phase\n", 772 | " theta_list[i] -= np.pi\n", 773 | " prob_neg = self.pdf(theta_list)\n", 774 | " # recover\n", 775 | " theta_list[i] += np.pi/2.\n", 776 | "\n", 777 | " grad_pos = self.mmd.kernel_expect(prob, prob_pos) - self.mmd.kernel_expect(prob, prob_neg)\n", 778 | " grad_neg = self.mmd.kernel_expect(self.p_data, prob_pos) - self.mmd.kernel_expect(self.p_data, prob_neg)\n", 779 | " grad.append(grad_pos - grad_neg)\n", 780 | " return np.array(grad)\n", 781 | "\n", 782 | " def gradient_numerical(self, theta_list, delta=1e-2):\n", 783 | " '''\n", 784 | " numerical differenciation.\n", 785 | " '''\n", 786 | " grad = []\n", 787 | " for i in range(len(theta_list)):\n", 788 | " theta_list[i] += delta/2.\n", 789 | " loss_pos = self.mmd_loss(theta_list)\n", 790 | " theta_list[i] -= delta\n", 791 | " loss_neg = self.mmd_loss(theta_list)\n", 792 | " theta_list[i] += delta/2.\n", 793 | "\n", 794 | " grad_i = (loss_pos - loss_neg)/delta\n", 795 | " grad.append(grad_i)\n", 796 | " return np.array(grad)\n", 797 | " \n", 798 | "def sample_from_prob(x, pl, num_sample):\n", 799 | " '''\n", 800 | " sample x ~ pl.\n", 801 | " '''\n", 802 | " pl = 1. / pl.sum() * pl\n", 803 | " indices = np.arange(len(x))\n", 804 | " res = np.random.choice(indices, num_sample, p=pl)\n", 805 | " return np.array([x[r] for r in res])\n", 806 | "\n", 807 | "\n", 808 | "def prob_from_sample(dataset, hndim):\n", 809 | " '''\n", 810 | " emperical probability from data.\n", 811 | " '''\n", 812 | " p_data = np.bincount(dataset, minlength=hndim)\n", 813 | " p_data = p_data / float(np.sum(p_data))\n", 814 | " return p_data" 815 | ] 816 | }, 817 | { 818 | "cell_type": "code", 819 | "execution_count": 0, 820 | "metadata": { 821 | "colab": { 822 | "autoexec": { 823 | "startup": false, 824 | "wait_interval": 0 825 | } 826 | }, 827 | "colab_type": "code", 828 | "collapsed": true, 829 | "id": "rhdRmpOgl_u5" 830 | }, 831 | "outputs": [], 832 | "source": [ 833 | "# Born Machine\n", 834 | "bm = QCBM(circuit, mmd, pg, batch_size=None) # exact version" 835 | ] 836 | }, 837 | { 838 | "cell_type": "code", 839 | "execution_count": 42, 840 | "metadata": { 841 | "colab": { 842 | "autoexec": { 843 | "startup": false, 844 | "wait_interval": 0 845 | }, 846 | "base_uri": "https://localhost:8080/", 847 | "height": 35 848 | }, 849 | "colab_type": "code", 850 | "executionInfo": { 851 | "elapsed": 727, 852 | "status": "ok", 853 | "timestamp": 1523078674207, 854 | "user": { 855 | "displayName": "刘金国", 856 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 857 | "userId": "116824001998056121289" 858 | }, 859 | "user_tz": -480 860 | }, 861 | "id": "fsOlbsLBl_u8", 862 | "outputId": "557124c6-687a-44d1-ccca-c4eb5d7d887e" 863 | }, 864 | "outputs": [ 865 | { 866 | "name": "stdout", 867 | "output_type": "stream", 868 | "text": [ 869 | "MMD loss for Initial Circuit = 0.0440\n" 870 | ] 871 | } 872 | ], 873 | "source": [ 874 | "theta_list = np.random.random(bm.circuit.num_param)*2*np.pi\n", 875 | "print('MMD loss for Initial Circuit = %.4f'%bm.mmd_loss(theta_list))" 876 | ] 877 | }, 878 | { 879 | "cell_type": "code", 880 | "execution_count": 43, 881 | "metadata": { 882 | "colab": { 883 | "autoexec": { 884 | "startup": false, 885 | "wait_interval": 0 886 | }, 887 | "base_uri": "https://localhost:8080/", 888 | "height": 686 889 | }, 890 | "colab_type": "code", 891 | "executionInfo": { 892 | "elapsed": 2344, 893 | "status": "ok", 894 | "timestamp": 1523078677878, 895 | "user": { 896 | "displayName": "刘金国", 897 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 898 | "userId": "116824001998056121289" 899 | }, 900 | "user_tz": -480 901 | }, 902 | "id": "I-OXkiy6l_vB", 903 | "outputId": "c9a71940-ed8b-4644-c205-bedf7619c088" 904 | }, 905 | "outputs": [ 906 | { 907 | "name": "stdout", 908 | "output_type": "stream", 909 | "text": [ 910 | "[ 0.00128546 0.00291719 0.00723749 -0.00212555 -0.00346394 -0.02016816\n", 911 | " -0.00328782 -0.00331141 -0.00163575 0.00344951 -0.00569198 0.00640275\n", 912 | " -0.01087395 0.02290944 0.01064659 0.00080618 0.00987338 -0.00198704\n", 913 | " -0.00293285 -0.0020108 0.01406099 0.01219367 -0.0071598 -0.00827463\n", 914 | " -0.00176098 -0.00610079 -0.01336093 -0.00128863 -0.01534401 0.00430679\n", 915 | " 0.00263345 0.00169888 -0.00967233 -0.00678989 0.00091382 0.01800348\n", 916 | " -0.00441233 -0.01295716 0.00563317 -0.00734346 -0.00115724 0.01418793\n", 917 | " 0.01608993 -0.00843482 0.01623441 0.00693906 -0.00473672 0.0004105\n", 918 | " -0.00503149 -0.00539715 0.00390338 0.00445442 -0.00231173 0.00106615\n", 919 | " -0.00127588 -0.01838359 -0.00504569 0.01889813 -0.01501975 -0.01206399\n", 920 | " 0.01178966 0.00371226 -0.00705478 0.02368941 -0.02082921 0.00739589\n", 921 | " -0.0021577 -0.01477283 0.00244763 -0.00204842 0.00652822 -0.00984969\n", 922 | " 0.02341545 -0.0020058 0.02421392 0.0110414 0.00014127 0.01062275\n", 923 | " -0.00643447 -0.01108307 -0.00313949 -0.02724891 -0.01278688 0.02732687\n", 924 | " 0.01152765 0.01059827 -0.01195812 -0.00862762 0.00084189 0.00983612\n", 925 | " 0.00131466 -0.00928871 0.00178275 0.01016269 -0.00956343 -0.00202157\n", 926 | " 0.00352308 -0.002335 0.00360078 0.00273339 -0.00488223 0.00591172\n", 927 | " 0.00146351 0.00280222 0.00017502 -0.0029901 0.00436573 -0.0085621\n", 928 | " -0.00592689 -0.01484778 -0.00706659 0.00452008 0.00867807 0.00718175]\n", 929 | "[ 0.00128542 0.00291719 0.00723746 -0.00212556 -0.00346392 -0.02016801\n", 930 | " -0.00328784 -0.00331133 -0.00163568 0.00344948 -0.00569199 0.00640267\n", 931 | " -0.01087391 0.02290925 0.01064655 0.00080618 0.00987343 -0.00198707\n", 932 | " -0.00293278 -0.00201083 0.0140608 0.01219353 -0.00715968 -0.00827451\n", 933 | " -0.00176097 -0.00610067 -0.01336084 -0.00128863 -0.01534388 0.00430675\n", 934 | " 0.00263338 0.00169886 -0.00967222 -0.00678978 0.00091391 0.01800338\n", 935 | " -0.00441234 -0.01295712 0.00563318 -0.0073434 -0.00115716 0.01418772\n", 936 | " 0.01608972 -0.00843474 0.01623432 0.00693902 -0.00473666 0.0004105\n", 937 | " -0.00503148 -0.00539708 0.00390345 0.00445426 -0.00231182 0.00106609\n", 938 | " -0.00127601 -0.0183835 -0.00504567 0.01889794 -0.01501969 -0.01206383\n", 939 | " 0.01178963 0.00371229 -0.00705485 0.02368918 -0.0208291 0.00739584\n", 940 | " -0.00215777 -0.01477268 0.00244772 -0.00204841 0.00652816 -0.00984968\n", 941 | " 0.02341519 -0.00200574 0.02421365 0.01104129 0.00014131 0.01062264\n", 942 | " -0.00643433 -0.0110831 -0.00313947 -0.02724875 -0.01278672 0.02732672\n", 943 | " 0.01152756 0.01059819 -0.01195809 -0.00862755 0.00084186 0.00983597\n", 944 | " 0.00131462 -0.00928857 0.00178273 0.01016249 -0.00956329 -0.00202151\n", 945 | " 0.00352312 -0.00233488 0.00360066 0.00273341 -0.00488221 0.00591174\n", 946 | " 0.00146349 0.00280217 0.00017502 -0.00299004 0.0043657 -0.00856198\n", 947 | " -0.00592664 -0.01484749 -0.00706653 0.00451995 0.008678 0.00718163]\n" 948 | ] 949 | } 950 | ], 951 | "source": [ 952 | "# gradient test\n", 953 | "# these two gradients should match.\n", 954 | "g1 = bm.gradient(theta_list)\n", 955 | "print(g1)\n", 956 | "g2 = bm.gradient_numerical(theta_list)\n", 957 | "print(g2)" 958 | ] 959 | }, 960 | { 961 | "cell_type": "markdown", 962 | "metadata": { 963 | "colab_type": "text", 964 | "id": "u7m6D8jBl_vE" 965 | }, 966 | "source": [ 967 | "# Training\n", 968 | "In this basic introduction, I prepaired classical training method like `Adam` and `L-BFGS-B` (as well as other methods supported by scipy)." 969 | ] 970 | }, 971 | { 972 | "cell_type": "code", 973 | "execution_count": 0, 974 | "metadata": { 975 | "colab": { 976 | "autoexec": { 977 | "startup": false, 978 | "wait_interval": 0 979 | } 980 | }, 981 | "colab_type": "code", 982 | "collapsed": true, 983 | "id": "jtvCimfRl_vF" 984 | }, 985 | "outputs": [], 986 | "source": [ 987 | " def train(bm, theta_list, method, max_iter=1000, step_rate=0.1):\n", 988 | " '''\n", 989 | " train a Born Machine.\n", 990 | " \n", 991 | " Args:\n", 992 | " bm (QCBM): quantum circuit born machine training strategy.\n", 993 | " theta_list (1darray): initial parameters.\n", 994 | " method ('Adam'|'L-BFGS-B'):\n", 995 | " * L-BFGS-B: efficient, but not noise tolerant.\n", 996 | " * Adam: noise tolerant.\n", 997 | " max_iter (int): maximum allowed number of iterations.\n", 998 | " step_rate (float): learning rate for Adam optimizer.\n", 999 | " \n", 1000 | " Returns:\n", 1001 | " (float, 1darray): final loss and parameters.\n", 1002 | " '''\n", 1003 | " step = [0]\n", 1004 | " def callback(x, *args, **kwargs):\n", 1005 | " step[0] += 1\n", 1006 | " print('step = %d, loss = %s'%(step[0], bm.mmd_loss(x)))\n", 1007 | " \n", 1008 | " theta_list = np.array(theta_list)\n", 1009 | " if method == 'Adam':\n", 1010 | " try:\n", 1011 | " from climin import Adam\n", 1012 | " except:\n", 1013 | " !pip install git+https://github.com/BRML/climin.git\n", 1014 | " from climin import Adam\n", 1015 | " optimizer = Adam(wrt=theta_list, fprime=bm.gradient,step_rate=step_rate)\n", 1016 | " for info in optimizer:\n", 1017 | " callback(theta_list)\n", 1018 | " if step[0] == max_iter:\n", 1019 | " break\n", 1020 | " return bm.mmd_loss(theta_list), theta_list\n", 1021 | " else:\n", 1022 | " from scipy.optimize import minimize\n", 1023 | " res = minimize(bm.mmd_loss, x0=theta_list,\n", 1024 | " method=method, jac = bm.gradient, tol=1e-12,\n", 1025 | " options={'maxiter': max_iter, 'disp': 0, 'gtol':1e-10, 'ftol':0},\n", 1026 | " callback=callback,\n", 1027 | " )\n", 1028 | " return res.fun, res.x" 1029 | ] 1030 | }, 1031 | { 1032 | "cell_type": "code", 1033 | "execution_count": 45, 1034 | "metadata": { 1035 | "colab": { 1036 | "autoexec": { 1037 | "startup": false, 1038 | "wait_interval": 0 1039 | }, 1040 | "base_uri": "https://localhost:8080/", 1041 | "height": 718 1042 | }, 1043 | "colab_type": "code", 1044 | "executionInfo": { 1045 | "elapsed": 22631, 1046 | "status": "ok", 1047 | "timestamp": 1523078706459, 1048 | "user": { 1049 | "displayName": "刘金国", 1050 | "photoUrl": "//lh3.googleusercontent.com/-lDAT81T3HSE/AAAAAAAAAAI/AAAAAAAAAgw/eH3JEob7M1Y/s50-c-k-no/photo.jpg", 1051 | "userId": "116824001998056121289" 1052 | }, 1053 | "user_tz": -480 1054 | }, 1055 | "id": "-AqBAGQll_vH", 1056 | "outputId": "4a9a1d31-4eb9-4e58-c287-f94e1362d9db" 1057 | }, 1058 | "outputs": [ 1059 | { 1060 | "name": "stdout", 1061 | "output_type": "stream", 1062 | "text": [ 1063 | "step = 1, loss = 0.013677884412702809\n", 1064 | "step = 2, loss = 0.00733783537136784\n", 1065 | "step = 3, loss = 0.005332945663816636\n", 1066 | "step = 4, loss = 0.003750332057020234\n", 1067 | "step = 5, loss = 0.0026965146539037884\n", 1068 | "step = 6, loss = 0.0017881527416103668\n", 1069 | "step = 7, loss = 0.0012967270420159629\n", 1070 | "step = 8, loss = 0.0009887531047653789\n", 1071 | "step = 9, loss = 0.0008432600976612844\n", 1072 | "step = 10, loss = 0.0005702653905143802\n", 1073 | "step = 11, loss = 0.0004177149601075345\n", 1074 | "step = 12, loss = 0.000305415677667581\n", 1075 | "step = 13, loss = 0.00024104037264618868\n", 1076 | "step = 14, loss = 0.00017974944146808468\n", 1077 | "step = 15, loss = 0.00014187031341621125\n", 1078 | "step = 16, loss = 0.00010255374177388627\n", 1079 | "step = 17, loss = 7.403483740163813e-05\n", 1080 | "step = 18, loss = 5.507592226980723e-05\n", 1081 | "step = 19, loss = 4.1980470411244044e-05\n", 1082 | "step = 20, loss = 3.289794920927741e-05\n", 1083 | "step = 21, loss = 2.105999023050527e-05\n" 1084 | ] 1085 | }, 1086 | { 1087 | "data": { 1088 | "image/png": "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\nEF7i/wNIxieui59+3wAAAABJRU5ErkJggg==\n", 1089 | "text/plain": [ 1090 | "" 1091 | ] 1092 | }, 1093 | "metadata": { 1094 | "tags": [] 1095 | }, 1096 | "output_type": "display_data" 1097 | } 1098 | ], 1099 | "source": [ 1100 | "# random initial parameter\n", 1101 | "theta_list = np.random.random(bm.circuit.num_param)*2*np.pi\n", 1102 | "\n", 1103 | "loss, theta_list = train(bm, theta_list, 'L-BFGS-B', max_iter=20)\n", 1104 | "pl = bm.pdf(theta_list)\n", 1105 | "\n", 1106 | "# display training result\n", 1107 | "plt.plot(bm.p_data)\n", 1108 | "plt.plot(pl)\n", 1109 | "plt.legend(['Data', 'Quantum Circuit Born Machine'])\n", 1110 | "plt.show()" 1111 | ] 1112 | }, 1113 | { 1114 | "cell_type": "code", 1115 | "execution_count": 0, 1116 | "metadata": { 1117 | "colab": { 1118 | "autoexec": { 1119 | "startup": false, 1120 | "wait_interval": 0 1121 | } 1122 | }, 1123 | "colab_type": "code", 1124 | "collapsed": true, 1125 | "id": "X334bsZyl_vK" 1126 | }, 1127 | "outputs": [], 1128 | "source": [] 1129 | } 1130 | ], 1131 | "metadata": { 1132 | "colab": { 1133 | "collapsed_sections": [], 1134 | "default_view": {}, 1135 | "name": "qcbm_gaussian.ipynb", 1136 | "provenance": [], 1137 | "version": "0.3.2", 1138 | "views": {} 1139 | }, 1140 | "kernelspec": { 1141 | "display_name": "Python 3", 1142 | "language": "python", 1143 | "name": "python3" 1144 | }, 1145 | "language_info": { 1146 | "codemirror_mode": { 1147 | "name": "ipython", 1148 | "version": 3 1149 | }, 1150 | "file_extension": ".py", 1151 | "mimetype": "text/x-python", 1152 | "name": "python", 1153 | "nbconvert_exporter": "python", 1154 | "pygments_lexer": "ipython3", 1155 | "version": "3.6.4" 1156 | } 1157 | }, 1158 | "nbformat": 4, 1159 | "nbformat_minor": 1 1160 | } 1161 | -------------------------------------------------------------------------------- /program.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Learning 2 x 3 bar and stripe using Born Machine. 4 | ''' 5 | import numpy as np 6 | import pdb, os, fire 7 | import scipy.sparse as sps 8 | 9 | from qcbm import train 10 | from qcbm.testsuit import load_barstripe 11 | 12 | np.random.seed(2) 13 | try: 14 | os.mkdir('data') 15 | except: 16 | pass 17 | 18 | # the testcase used in this program. 19 | depth = 7 20 | geometry = (2,3) 21 | bm = load_barstripe(geometry, depth, structure='chowliu-tree') 22 | 23 | class UI(): 24 | def checkgrad(self): 25 | '''check the correctness of our gradient.''' 26 | theta_list = np.random.random(bm.circuit.num_param)*2*np.pi 27 | g1 = bm.gradient(theta_list) 28 | g2 = bm.gradient_numerical(theta_list) 29 | error_rate = np.abs(g1-g2).sum()/np.abs(g1).sum() 30 | print('Error Rate = %.4e'%error_rate) 31 | 32 | def train(self): 33 | '''train this circuit.''' 34 | theta_list = np.random.random(bm.circuit.num_param)*2*np.pi 35 | loss, theta_list = train(bm, theta_list, 'L-BFGS-B', max_iter=100) 36 | # save 37 | np.savetxt('data/loss-cl.dat', bm._loss_histo) 38 | np.savetxt('data/theta-cl.dat', theta_list) 39 | 40 | def vcircuit(self): 41 | '''visualize circuit of Born Machine.''' 42 | from qcbm import ProjectQContext 43 | bm.context = ProjectQContext 44 | bm.viz() 45 | 46 | def vpdf(self): 47 | '''visualize probability densitys''' 48 | import matplotlib.pyplot as plt 49 | from qcbm.dataset import barstripe_pdf 50 | pl0 = barstripe_pdf(geometry) 51 | plt.plot(pl0) 52 | try: 53 | theta_list = np.loadtxt('data/theta-cl.dat') 54 | pl = bm.pdf(theta_list) 55 | plt.plot(pl) 56 | except: 57 | print('Warning, No Born Machine Data') 58 | plt.legend(['Data', 'Born Machine']) 59 | plt.show() 60 | 61 | def generate(self): 62 | '''show generated samples for bar and stripes''' 63 | from qcbm.dataset import binary_basis 64 | from qcbm.utils import sample_from_prob 65 | import matplotlib.pyplot as plt 66 | # generate samples 67 | size = (7,5) 68 | try: 69 | theta_list = np.loadtxt('data/theta-cl.dat') 70 | except: 71 | print('run `./program.py train` before generating data!') 72 | return 73 | pl = bm.pdf(theta_list) 74 | indices = np.random.choice(np.arange(len(pl)), np.prod(size), p=pl) 75 | samples = binary_basis(geometry)[indices] 76 | 77 | # show 78 | fig = plt.figure(figsize=(5,4)) 79 | gs = plt.GridSpec(*size) 80 | for i in range(size[0]): 81 | for j in range(size[1]): 82 | plt.subplot(gs[i,j]).imshow(samples[i*size[1]+j], vmin=0, vmax=1) 83 | plt.axis('equal') 84 | plt.axis('off') 85 | plt.show() 86 | 87 | def statgrad(self): 88 | '''layerwise gradient statistics''' 89 | import matplotlib.pyplot as plt 90 | nsample = 10 91 | 92 | # calculate 93 | grad_stat = [[] for i in range(depth+1)] 94 | for i in range(nsample): 95 | print('running %s-th random parameter'%i) 96 | theta_list = np.random.random(bm.circuit.num_param)*2*np.pi 97 | loss = bm.mmd_loss(theta_list) 98 | grad = bm.gradient(theta_list) 99 | loc = 0 100 | for i, layer in enumerate(bm.circuit[::2]): 101 | grad_stat[i] = np.append(grad_stat[i], grad[loc:loc+layer.num_param]) 102 | loc += layer.num_param 103 | 104 | # get mean amplitude, expect first and last layer, they have less parameters. 105 | var_list = [] 106 | for grads in grad_stat[1:-1]: 107 | var_list.append(np.abs(grads).mean()) 108 | 109 | plt.figure(figsize=(5,4)) 110 | plt.plot(range(1,depth), var_list) 111 | plt.ylabel('Gradient Std. Err.') 112 | plt.xlabel('Depth') 113 | plt.ylim(0,np.max(var_list)*1.2) 114 | plt.show() 115 | 116 | if __name__ == '__main__': 117 | fire.Fire(UI) 118 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = qcbm/tests.py 3 | -------------------------------------------------------------------------------- /qcbm/__init__.py: -------------------------------------------------------------------------------- 1 | from .qcbm import QCBM 2 | from .blocks import get_demo_circuit 3 | from .contexts import ProjectQContext, ScipyContext 4 | from .mmd import RBFMMD2 5 | from .train import train 6 | 7 | from . import utils, testsuit, qclibs, structure, blocks, dataset 8 | -------------------------------------------------------------------------------- /qcbm/blocks.py: -------------------------------------------------------------------------------- 1 | '''quantum circuit building blocks.''' 2 | import numpy as np 3 | from functools import reduce 4 | try: 5 | from projectq.ops import * 6 | except: 7 | print('warning: fail to import projectq') 8 | 9 | from . import qclibs 10 | 11 | class CircuitBlock(object): 12 | ''' 13 | the building block of a circuit. This is an abstract class. 14 | ''' 15 | def __init__(self, num_bit): 16 | self.num_bit = num_bit 17 | 18 | def __call__(self, qureg, theta_list): 19 | ''' 20 | build a quantum circuit. 21 | 22 | Args: 23 | theta_list (1darray, len=3*num*bit*(depth+1)): parameters in this quantum circuit, here, depth equals to the number of entanglement operations. 24 | 25 | Return: 26 | remaining theta_list 27 | ''' 28 | pass 29 | 30 | @property 31 | def num_param(self): 32 | ''' 33 | number of parameters it consume. 34 | ''' 35 | pass 36 | 37 | def tocsr(self, theta_list): 38 | ''' 39 | build this block into a sequence of csr_matrices. 40 | 41 | Args: 42 | theta_list (1darray): parameters, 43 | 44 | Returns: 45 | list: a list of csr_matrices, apply them on a vector to perform operation. 46 | ''' 47 | pass 48 | 49 | class BlockQueue(list): 50 | ''' 51 | BlockQueue is a sequence of CircuitBlock instances. 52 | ''' 53 | @property 54 | def num_bit(self): 55 | return self[0].num_bit 56 | 57 | @property 58 | def num_param(self): 59 | return sum([b.num_param for b in self]) 60 | 61 | def __call__(self, qureg, theta_list): 62 | for block in self: 63 | theta_i, theta_list = np.split(theta_list, [block.num_param]) 64 | block(qureg, theta_i) 65 | np.testing.assert_(len(theta_list)==0) 66 | 67 | def __str__(self): 68 | return '\n'.join([str(b) for b in self]) 69 | 70 | 71 | class CleverBlockQueue(BlockQueue): 72 | ''' 73 | Clever Block Queue that keep track of theta_list changing history, for fast update. 74 | ''' 75 | def __init__(self, *args): 76 | list.__init__(self, *args) 77 | self.theta_last = None 78 | self.memo = None 79 | 80 | def __call__(self, qureg, theta_list): 81 | if not isinstance(qureg, np.ndarray): 82 | return super(CleverBlockQueue, self).__call__(qureg, theta_list) 83 | # cache? if theta_list change <= 1 parameters, then don't touch memory. 84 | remember = self.theta_last is None or (abs(self.theta_last-theta_list)>1e-12).sum() > 1 85 | 86 | mats = [] 87 | theta_last = self.theta_last 88 | if remember: 89 | self.theta_last = theta_list.copy() 90 | 91 | qureg_ = qureg 92 | for iblock, block in enumerate(self): 93 | # generate or use a block matrix 94 | num_param = block.num_param 95 | theta_i, theta_list = np.split(theta_list, [num_param]) 96 | if theta_last is not None: 97 | theta_o, theta_last = np.split(theta_last, [num_param]) 98 | if self.memo is not None and (num_param==0 or np.abs(theta_i-theta_o).max()<1e-12): 99 | # use data cached in memory 100 | mat = self.memo[iblock] 101 | else: 102 | if self.memo is not None and not remember: 103 | # update the changed gate, but not touching memory. 104 | mat = _rot_tocsr_update1(block, self.memo[iblock], theta_o, theta_i) 105 | else: 106 | # regenerate one 107 | mat = block.tocsr(theta_i) 108 | for mat_i in mat: 109 | qureg_ = mat_i.dot(qureg_) 110 | mats.append(mat) 111 | 112 | if remember: 113 | # cache data 114 | self.memo = mats 115 | # update register 116 | qureg[...] = qureg_ 117 | np.testing.assert_(len(theta_list)==0) 118 | 119 | 120 | class ArbituaryRotation(CircuitBlock): 121 | def __init__(self, num_bit): 122 | super(ArbituaryRotation, self).__init__(num_bit) 123 | self.mask = np.array([True] * (3*num_bit), dtype='bool') 124 | 125 | def __call__(self, qureg, theta_list): 126 | gates = [Rz, Rx, Rz] 127 | theta_list_ = np.zeros(self.num_bit*3) 128 | theta_list_[self.mask] = theta_list 129 | for i, (theta, mask) in enumerate(zip(theta_list_, self.mask)): 130 | ibit, igate = i//3, i%3 131 | if mask: 132 | gate = gates[igate](theta) 133 | gate | qureg[ibit] 134 | 135 | def __str__(self): 136 | return 'Rotate[%d]'%(self.num_param) 137 | 138 | @property 139 | def num_param(self): 140 | return self.mask.sum() 141 | 142 | def tocsr(self, theta_list): 143 | '''transform this block to csr_matrix.''' 144 | theta_list_ = np.zeros(3*self.num_bit) 145 | theta_list_[self.mask] = theta_list 146 | rots = [qclibs.rot(*ths) for ths in theta_list_.reshape([self.num_bit,3])] 147 | res = [qclibs._([rot], [i], self.num_bit) for i,rot in enumerate(rots)] 148 | return res 149 | 150 | class CNOTEntangler(CircuitBlock): 151 | def __init__(self, num_bit, pairs): 152 | super(CNOTEntangler, self).__init__(num_bit) 153 | self.pairs = pairs 154 | 155 | def __str__(self): 156 | pair_str = ','.join(['%d-%d'%(i,j) for i,j in self.pairs]) 157 | return 'CNOT(%s)'%(pair_str) 158 | 159 | def __call__(self, qureg, *args, **kwargs): 160 | for pair in self.pairs: 161 | CNOT | (qureg[pair[0]], qureg[pair[1]]) 162 | 163 | @property 164 | def num_param(self): 165 | return 0 166 | 167 | def tocsr(self, theta_list): 168 | '''transform this block to csr_matrix.''' 169 | i, j = self.pairs[0] 170 | res = qclibs.CNOT(i, j, self.num_bit) 171 | for i, j in self.pairs[1:]: 172 | res = qclibs.CNOT(i,j,self.num_bit).dot(res) 173 | res.eliminate_zeros() 174 | return [res] 175 | 176 | 177 | def _rot_tocsr_update1(rot, old, theta_old, theta_new): 178 | ''' 179 | rotation layer csr_matrix update method. 180 | 181 | Args: 182 | rot (ArbituaryRotation): rotatio layer. 183 | old (csr_matrix): old matrices. 184 | theta_old (1darray): old parameters. 185 | theta_new (1darray): new parameters. 186 | 187 | Returns: 188 | csr_matrix: new rotation matrices after the theta changed. 189 | ''' 190 | idiff_param = np.where(abs(theta_old-theta_new)>1e-12)[0].item() 191 | idiff = np.where(rot.mask)[0][idiff_param] 192 | 193 | # get rotation parameters 194 | isite = idiff//3 195 | theta_list_ = np.zeros(3*rot.num_bit) 196 | theta_list_[rot.mask] = theta_new 197 | 198 | new = old[:] 199 | new[isite] = qclibs._(qclibs.rot(*theta_list_[isite*3:isite*3+3]), isite, rot.num_bit) 200 | return new 201 | 202 | def get_demo_circuit(num_bit, depth, pairs): 203 | blocks = [] 204 | # build circuit 205 | for idepth in range(depth+1): 206 | blocks.append(ArbituaryRotation(num_bit)) 207 | if idepth!=depth: 208 | blocks.append(CNOTEntangler(num_bit, pairs)) 209 | 210 | # set leading and trailing Rz to disabled 211 | blocks[0].mask[::3] = False 212 | blocks[-1].mask[2::3] = False 213 | return CleverBlockQueue(blocks) 214 | -------------------------------------------------------------------------------- /qcbm/contexts.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Context for applying gates. 3 | ''' 4 | 5 | import numpy as np 6 | import os 7 | 8 | try: 9 | from projectq.cengines import MainEngine 10 | from projectq.backends import CircuitDrawer, Simulator, IBMBackend 11 | from projectq.ops import Measure 12 | except: 13 | print('warning: fail to import projectq') 14 | 15 | from .utils import openfile 16 | 17 | TEX_FOLDER = 'data' 18 | TEX_FILENAME = '_temp.tex' 19 | 20 | class ProjectQContext(object): 21 | ''' 22 | Context for running circuits. 23 | 24 | Args: 25 | num_bit (int): number of bits in register. 26 | task ('ibm'|'draw'|'simulate'): task that decide the environment type. 27 | ibm_config (dict): extra arguments for IBM backend. 28 | ''' 29 | 30 | def __init__(self, num_bit, task, ibm_config=None): 31 | self.task = task 32 | self.num_bit = num_bit 33 | self.ibm_config = ibm_config 34 | 35 | def __enter__(self): 36 | ''' 37 | Enter context, 38 | 39 | Attributes: 40 | eng (MainEngine): main engine. 41 | backend ('graphical' or 'simulate'): backend used. 42 | qureg (Qureg): quantum register. 43 | ''' 44 | if self.task=='ibm': 45 | import projectq.setups.ibm 46 | else: 47 | import projectq.setups.default 48 | 49 | # create a main compiler engine with a specific backend: 50 | if self.task == 'draw': 51 | self.backend = CircuitDrawer() 52 | # locations = {0: 0, 1: 1, 2: 2, 3:3} # swap order of lines 0-1-2. 53 | # self.backend.set_qubit_locations(locations) 54 | elif self.task == 'simulate': 55 | print('ProjecQ simulation in training can be slow, since in scipy context, we cached a lot gates.') 56 | self.backend = Simulator() 57 | elif self.task == 'ibm': 58 | # choose device 59 | device = self.ibm_config.get('device', 'ibmqx2' if self.num_bit<=5 else 'ibmqx5') 60 | # check data 61 | if self.ibm_config is None: 62 | raise 63 | if device == 'ibmqx5': 64 | device_num_bit = 16 65 | else: 66 | device_num_bit = 5 67 | if device_num_bit < self.num_bit: 68 | raise AttributeError('device %s has not enough qubits for %d bit simulation!'%(device, self.num_bit)) 69 | 70 | self.backend = IBMBackend(use_hardware=True, num_runs=self.ibm_config['num_runs'], 71 | user=self.ibm_config['user'], 72 | password=self.ibm_config['password'], 73 | device=device, verbose=True) 74 | else: 75 | raise ValueError('engine %s not defined' % self.task) 76 | self.eng = MainEngine(self.backend) 77 | # initialize register 78 | self.qureg = self.eng.allocate_qureg(self.num_bit) 79 | return self 80 | 81 | def __exit__(self, exc_type, exc_val, traceback): 82 | ''' 83 | exit, meanwhile cheat and get wave function. 84 | 85 | Attributes: 86 | wf (1darray): for 'simulate' task, the wave function vector. 87 | res (1darray): for 'ibm' task, the measurements output. 88 | ''' 89 | if traceback is not None: 90 | return False 91 | if self.task == 'draw': 92 | self._viz_circuit() 93 | elif self.task == 'simulate': 94 | self.eng.flush() 95 | order, qvec = self.backend.cheat() 96 | self.wf = np.array(qvec) 97 | order = [order[i] for i in range(len(self.qureg))] 98 | self.wf = np.transpose(self.wf.reshape([2]*len(self.qureg), order='F'), axes=order).ravel(order='F') 99 | Measure | self.qureg 100 | self.eng.flush() 101 | elif self.task == 'ibm': 102 | Measure | self.qureg 103 | self.eng.flush() 104 | self.res = self.backend.get_probabilities(self.qureg) 105 | else: 106 | raise 107 | return self 108 | 109 | def _viz_circuit(self): 110 | Measure | self.qureg 111 | self.eng.flush() 112 | # print latex code to draw the circuit: 113 | s = self.backend.get_latex() 114 | 115 | # save graph to latex file 116 | os.chdir(TEX_FOLDER) 117 | with open(TEX_FILENAME, 'w') as f: 118 | f.write(s) 119 | 120 | # texfile = os.path.join(folder, 'circuit-%d.tex'%bench_id) 121 | pdffile = TEX_FILENAME[:-3]+'pdf' 122 | os.system('pdflatex %s'%TEX_FILENAME) 123 | openfile(pdffile) 124 | 125 | 126 | class ScipyContext(object): 127 | ''' 128 | Scipy context for running circuits. 129 | 130 | Args: 131 | num_bit (int): number of bits in register. 132 | ibm_config (dict): extra arguments for IBM backend. 133 | ''' 134 | 135 | def __init__(self, num_bit, *args, **kwargs): 136 | self.num_bit = num_bit 137 | 138 | def __enter__(self): 139 | ''' 140 | Enter context, 141 | 142 | Attributes: 143 | eng (MainEngine): main engine. 144 | backend ('graphical' or 'simulate'): backend used. 145 | qureg (Qureg): quantum register. 146 | ''' 147 | self.qureg = np.zeros(2**self.num_bit, dtype='complex128') 148 | self.qureg[0] = 1 149 | return self 150 | 151 | def __exit__(self, exc_type, exc_val, traceback): 152 | ''' 153 | exit, meanwhile cheat and get wave function. 154 | 155 | Attributes: 156 | wf (1darray): for 'simulate' task, the wave function vector. 157 | res (1darray): for 'ibm' task, the measurements output. 158 | ''' 159 | if traceback is not None: 160 | return False 161 | self.wf = self.qureg 162 | return self 163 | -------------------------------------------------------------------------------- /qcbm/dataset.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .utils import packnbits, unpacknbits 4 | 5 | def digit_basis(geometry): 6 | num_bit = np.prod(geometry) 7 | M = 2**num_bit 8 | x = np.arange(M) 9 | return x 10 | 11 | def binary_basis(geometry): 12 | num_bit = np.prod(geometry) 13 | M = 2**num_bit 14 | x = np.arange(M) 15 | return unpacknbits(x[:,None], num_bit).reshape((-1,)+geometry) 16 | 17 | def gaussian_pdf(geometry, mu, sigma): 18 | '''get gaussian distribution function''' 19 | x = digit_basis(geometry) 20 | pl = 1. / np.sqrt(2 * np.pi * sigma**2) * \ 21 | np.exp(-(x - mu)**2 / (2. * sigma**2)) 22 | return pl/pl.sum() 23 | 24 | def barstripe_pdf(geometry): 25 | '''get bar and stripes PDF''' 26 | x = binary_basis(geometry) 27 | pl = is_bs(x) 28 | return pl/pl.sum() 29 | 30 | def is_bs(samples): 31 | '''a sample is a bar or a stripe.''' 32 | return (np.abs(np.diff(samples,axis=-1)).sum(axis=(1,2))==0)|((np.abs(np.diff(samples, axis=1)).sum(axis=(1,2)))==0) 33 | -------------------------------------------------------------------------------- /qcbm/mmd.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class RBFMMD2(object): 4 | def __init__(self, sigma_list, num_bit, is_binary): 5 | self.sigma_list = sigma_list 6 | self.num_bit = num_bit 7 | self.is_binary = is_binary 8 | self.basis = np.arange(2**num_bit,dtype='int32') 9 | self.K = mix_rbf_kernel(self.basis, self.basis, self.sigma_list, is_binary) 10 | 11 | def __call__(self, px, py): 12 | ''' 13 | Args: 14 | px (1darray, default=None): probability for data set x, used only when self.is_exact==True. 15 | py (1darray, default=None): same as px, but for data set y. 16 | 17 | Returns: 18 | float, loss. 19 | ''' 20 | pxy = px-py 21 | return self.kernel_expect(pxy, pxy) 22 | 23 | def kernel_expect(self, px, py): 24 | res = px.dot(self.K.dot(py)) 25 | return res 26 | 27 | def mix_rbf_kernel(x, y, sigma_list, is_binary): 28 | if is_binary: 29 | dx2 = np.zeros([len(x)]*2, dtype='int64') 30 | num_bit = int(np.round(np.log(len(x))/np.log(2))) 31 | for i in range(num_bit): 32 | dx2 += (x[:,None]>>i)&1 != (y>>i)&1 33 | else: 34 | dx2 = (x[:, None] - y)**2 35 | return _mix_rbf_kernel_d(dx2, sigma_list) 36 | 37 | def _mix_rbf_kernel_d(dx2, sigma_list): 38 | K = 0.0 39 | for sigma in sigma_list: 40 | gamma = 1.0 / (2 * sigma) 41 | K = K + np.exp(-gamma * dx2) 42 | return K 43 | -------------------------------------------------------------------------------- /qcbm/qcbm.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Quantum Circuit Born Machine training framework. 3 | ''' 4 | 5 | import numpy as np 6 | 7 | from .contexts import ScipyContext, ProjectQContext 8 | 9 | class QCBM(object): 10 | ''' 11 | Quantum Circuit Born Machine, 12 | 13 | Args: 14 | circuit (BlockQueue): the circuit architechture. 15 | batch_size (int|None): introducing sampling error, None for no sampling error. 16 | ''' 17 | def __init__(self, circuit, mmd, p_data, batch_size=None): 18 | self.circuit = circuit 19 | self.mmd = mmd 20 | self.p_data = p_data 21 | self.batch_size = batch_size 22 | self.context = ScipyContext 23 | self._loss_histo = [] 24 | 25 | @property 26 | def depth(self): 27 | return (len(self.circuit)-1)//2 28 | 29 | def viz(self, theta_list=None): 30 | '''visualize this Born Machine''' 31 | if not self.context == ProjectQContext: 32 | raise AttributeError('Can not visualize, unless you use projectQ context.') 33 | if theta_list is None: 34 | theta_list = np.zeros(self.circuit.num_param) 35 | with self.context( self.circuit.num_bit, 'draw') as cc: 36 | self.circuit(cc.qureg, theta_list) 37 | 38 | def pdf(self, theta_list): 39 | '''get probability distribution function''' 40 | with self.context( self.circuit.num_bit, 'simulate') as cc: 41 | self.circuit(cc.qureg, theta_list) 42 | pl = np.abs(cc.wf)**2 43 | # introducing sampling error 44 | if self.batch_size is not None: 45 | pl = prob_from_sample(sample_from_prob(np.arange(len(pl)), pl, self.batch_size), 46 | len(pl), False) 47 | return pl 48 | 49 | def mmd_loss(self, theta_list): 50 | '''get the loss''' 51 | # get probability distritbution of Born Machine 52 | self._prob = self.pdf(theta_list) 53 | # use wave function to get mmd loss 54 | loss = self.mmd(self._prob, self.p_data) 55 | self._loss_histo.append(loss) 56 | return loss 57 | 58 | def gradient(self, theta_list): 59 | ''' 60 | cheat and get gradient. 61 | ''' 62 | # for stability consern, we do not use the cached probability output. 63 | prob = self.pdf(theta_list) 64 | # for performance consern in real training, prob can be reused! 65 | #prob = self._prob 66 | 67 | grad = [] 68 | for i in range(len(theta_list)): 69 | theta_list_ = theta_list.copy() 70 | # pi/2 phase 71 | theta_list_[i] += np.pi/2. 72 | prob_pos = self.pdf(theta_list_) 73 | # -pi/2 phase 74 | theta_list_[i] -= np.pi 75 | prob_neg = self.pdf(theta_list_) 76 | 77 | grad_pos = self.mmd.kernel_expect(prob, prob_pos) - self.mmd.kernel_expect(prob, prob_neg) 78 | grad_neg = self.mmd.kernel_expect(self.p_data, prob_pos) - self.mmd.kernel_expect(self.p_data, prob_neg) 79 | grad.append(grad_pos - grad_neg) 80 | 81 | return np.array(grad) 82 | 83 | def gradient_numerical(self, theta_list, delta=1e-2): 84 | ''' 85 | numerical differenciation. 86 | ''' 87 | grad = [] 88 | for i in range(len(theta_list)): 89 | theta_list[i] += delta/2. 90 | loss_pos = self.mmd_loss(theta_list) 91 | theta_list[i] -= delta 92 | loss_neg = self.mmd_loss(theta_list) 93 | theta_list[i] += delta/2. 94 | 95 | grad_i = (loss_pos - loss_neg)/delta 96 | grad.append(grad_i) 97 | return np.array(grad) 98 | -------------------------------------------------------------------------------- /qcbm/qclibs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | elementary library for quantum computation. 3 | 4 | the scipy sparse matrix version. 5 | ''' 6 | 7 | import scipy as np 8 | import pdb 9 | import scipy.sparse as sps 10 | from scipy.sparse import kron 11 | 12 | ###### Pauli Matrices ######## 13 | 14 | I2 = sps.eye(2).tocsr() 15 | sx = sps.csr_matrix([[0,1],[1,0.]]) 16 | sy = sps.csr_matrix([[0,-1j],[1j,0.]]) 17 | sz = sps.csr_matrix([[1,0],[0,-1.]]) 18 | 19 | p0 = (sz + I2) / 2 20 | p1 = (-sz + I2) / 2 21 | h = (sx + sz) / np.sqrt(2.) 22 | sxyz = [I2, sx, sy, sz] 23 | 24 | # single bit rotation matrices 25 | 26 | def _ri(si, theta): 27 | return np.cos(theta/2.)*I2 - 1j*np.sin(theta/2.)*si 28 | 29 | def rx(theta): 30 | return _ri(sx, theta) 31 | 32 | def ry(theta): 33 | return _ri(sy, theta) 34 | 35 | def rz(theta): 36 | return _ri(sz, theta) 37 | 38 | def rot(t1, t2, t3): 39 | ''' 40 | a general rotation gate rz(t3)rx(r2)rz(t1). 41 | 42 | Args: 43 | t1, t2, t3 (float): three angles. 44 | 45 | Returns: 46 | 2x2 csr_matrix: rotation matrix. 47 | ''' 48 | return rz(t3).dot(rx(t2)).dot(rz(t1)) 49 | 50 | # multiple bit construction 51 | 52 | def CNOT(ibit, jbit, n): 53 | res = _([p0, I2], [ibit, jbit], n) 54 | res = res + _([p1, sx], [ibit, jbit], n) 55 | return res 56 | 57 | def _(ops, locs, n): 58 | ''' 59 | Put operators in a circuit and compile them. 60 | 61 | notice the big end are high loc bits! 62 | 63 | Args: 64 | ops (list): list of single bit operators. 65 | locs (list): list of positions. 66 | n (int): total number of bits. 67 | 68 | Returns: 69 | csr_matrix: resulting matrix. 70 | ''' 71 | if np.ndim(locs) == 0: 72 | locs = [locs] 73 | if not isinstance(ops, (list, tuple)): 74 | ops = [ops] 75 | locs = np.asarray(locs) 76 | locs = n - locs 77 | order = np.argsort(locs) 78 | locs = np.concatenate([[0], locs[order], [n + 1]]) 79 | return _wrap_identity([ops[i] for i in order], np.diff(locs) - 1) 80 | 81 | 82 | def _wrap_identity(data_list, num_bit_list): 83 | if len(num_bit_list) != len(data_list) + 1: 84 | raise Exception() 85 | 86 | res = sps.eye(2**num_bit_list[0]) 87 | for data, nbit in zip(data_list, num_bit_list[1:]): 88 | res = kron(res, data) 89 | res = kron(res, sps.eye(2**nbit, dtype='complex128')) 90 | return res 91 | -------------------------------------------------------------------------------- /qcbm/structure.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Structures for entangle layers. 3 | ''' 4 | 5 | import numpy as np 6 | from scipy.sparse.csgraph import minimum_spanning_tree 7 | 8 | def chowliu_tree(pdata): 9 | ''' 10 | generate chow-liu tree. 11 | 12 | Args: 13 | pdata (1darray): empirical distribution in dataset 14 | 15 | Returns: 16 | list: entangle pairs. 17 | ''' 18 | X = mutual_information(pdata) 19 | Tcsr = -minimum_spanning_tree(-X) 20 | Tcoo = Tcsr.tocoo() 21 | pairs = list(zip(Tcoo.row, Tcoo.col)) 22 | print('Chow-Liu tree pairs = %s'%pairs) 23 | return pairs 24 | 25 | def random_tree(num_bit): 26 | ''' 27 | generate random tree. 28 | ''' 29 | X = np.random.random([num_bit, num_bit]) 30 | Tcsr = -minimum_spanning_tree(-X) 31 | Tcoo = Tcsr.tocoo() 32 | pairs = list(zip(Tcoo.row, Tcoo.col)) 33 | print('Random tree pairs = %s'%pairs) 34 | return pairs 35 | 36 | def nearest_neighbor(geometry): 37 | ''' 38 | generate nearest neighbor pairs. 39 | 40 | Args: 41 | geometry (tuple): square lattice size. 42 | ''' 43 | num_bit = np.prod(geometry) 44 | if len(geometry) == 2: 45 | nrow, ncol = geometry 46 | res = [] 47 | for ij in range(num_bit): 48 | i, j = ij // ncol, ij % ncol 49 | res.extend([(ij, i_ * ncol + j_) 50 | for i_, j_ in [((i + 1) % nrow, j), (i, (j + 1) % ncol)]]) 51 | return res 52 | elif len(geometry) == 1: 53 | res = [] 54 | for inth in range(2): 55 | for i in range(inth, num_bit, 2): 56 | res = res + [(i, i_ % num_bit) for i_ in range(i + 1, i + 2)] 57 | return res 58 | else: 59 | raise NotImplementedError('') 60 | 61 | def mutual_information(pdata): 62 | ''' 63 | calculate mutual information I = \sum\limits_{x,y} p(x,y) log[p(x,y)/p(x)/p(y)] 64 | 65 | Args: 66 | pdata (1darray): empirical distribution in dataset 67 | 68 | Returns: 69 | 2darray: mutual information table. 70 | ''' 71 | sl = [0, 1] # possible states 72 | d = len(sl) # number of possible states 73 | num_bit = int(np.round(np.log(len(pdata))/np.log(2))) 74 | basis = np.arange(2**num_bit, dtype='uint32') 75 | 76 | pxy = np.zeros([num_bit, num_bit, d, d]) 77 | px = np.zeros([num_bit, d]) 78 | pdata2d = np.broadcast_to(pdata[:,None], (len(pdata), num_bit)) 79 | pdata3d = np.broadcast_to(pdata[:,None,None], (len(pdata), num_bit, num_bit)) 80 | offsets = np.arange(num_bit-1,-1,-1) 81 | 82 | for s_i in sl: 83 | mask_i = (basis[:,None]>>offsets)&1 == s_i 84 | px[:,s_i] = np.ma.array(pdata2d, mask=~mask_i).sum(axis=0) 85 | for s_j in sl: 86 | mask_j = (basis[:,None]>>offsets)&1 == s_j 87 | pxy[:,:,s_i,s_j] = np.ma.array(pdata3d, mask=~(mask_i[:,None,:]&mask_j[:,:,None])).sum(axis=0) 88 | 89 | # mutual information 90 | pratio = pxy/np.maximum(px[:,None,:,None]*px[None,:,None,:], 1e-15) 91 | for i in range(num_bit): 92 | pratio[i, i] = 1 93 | I = (pxy*np.log(pratio)).sum(axis=(2,3)) 94 | return I 95 | 96 | 97 | -------------------------------------------------------------------------------- /qcbm/tests.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import dec, assert_, assert_raises,\ 3 | assert_almost_equal, assert_allclose 4 | import matplotlib.pyplot as plt 5 | import pdb, os 6 | import scipy.sparse as sps 7 | 8 | from .blocks import get_demo_circuit 9 | from .structure import nearest_neighbor 10 | from .dataset import gaussian_pdf, barstripe_pdf 11 | from .contexts import ProjectQContext, ScipyContext 12 | from .mmd import RBFMMD2 13 | from .train import train 14 | from .testsuit import load_gaussian, load_barstripe 15 | from .qclibs import rot, CNOT, ry, I2 16 | 17 | def test_dataset(): 18 | geometry = (3,3) 19 | pl2 = barstripe_pdf(geometry) 20 | assert_((pl2>1e-5).sum()==14) 21 | 22 | def test_bm(): 23 | depth = 2 24 | np.random.seed(2) 25 | 26 | #bm = load_gaussian(6, depth) 27 | bm = load_barstripe((3,3), depth) 28 | theta_list = np.random.random(bm.circuit.num_param)*2*np.pi 29 | 30 | assert_(bm.depth == depth) 31 | print('loss = %s'%bm.mmd_loss(theta_list)) 32 | g1 = bm.gradient(theta_list) 33 | g2 = bm.gradient_numerical(theta_list) 34 | assert_allclose(g1, g2, atol=1e-5) 35 | 36 | def test_wf(): 37 | depth = 0 38 | geometry = (6,) 39 | 40 | num_bit = np.prod(geometry) 41 | pairs = nearest_neighbor(geometry) 42 | circuit = get_demo_circuit(num_bit, depth, pairs) 43 | 44 | # cross check 45 | theta_list = np.random.random(circuit.num_param) 46 | with ScipyContext(np.prod(geometry)) as cc2: 47 | circuit(cc2.qureg, theta_list) 48 | with ProjectQContext(np.prod(geometry), 'simulate') as cc: 49 | circuit(cc.qureg, theta_list) 50 | assert_allclose(cc.wf, cc2.wf) 51 | 52 | def test_qclib(): 53 | cnot = CNOT(1,0,2) 54 | assert_(cnot.nnz==4) 55 | assert_allclose(cnot.toarray(), sps.coo_matrix(([1,1,1,1],([0,1,2,3],[0,1,3,2]))).toarray()) 56 | assert_allclose(rot(-np.pi/2.,np.pi/4.,np.pi/2.).toarray(),ry(np.pi/4.).toarray()) 57 | 58 | if __name__ == '__main__': 59 | test_dataset() 60 | test_wf() 61 | test_bm() 62 | test_qclib() 63 | -------------------------------------------------------------------------------- /qcbm/testsuit.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Several Models used for testing. 3 | ''' 4 | 5 | import numpy as np 6 | from scipy.sparse.csgraph import minimum_spanning_tree 7 | 8 | from .blocks import get_demo_circuit 9 | from .dataset import gaussian_pdf, barstripe_pdf 10 | from .qcbm import QCBM 11 | from .mmd import RBFMMD2 12 | from .structure import chowliu_tree, nearest_neighbor, random_tree 13 | from .contexts import ProjectQContext 14 | 15 | 16 | def load_gaussian(num_bit, depth, context='scipy'): 17 | '''gaussian distribution.''' 18 | geometry = (num_bit,) 19 | hndim = 2**num_bit 20 | 21 | # standard circuit 22 | pairs = nearest_neighbor(geometry) 23 | circuit = get_demo_circuit(num_bit, depth, pairs) 24 | 25 | # bar and stripe 26 | p_bs = gaussian_pdf(geometry, mu=hndim/2., sigma=hndim/4.) 27 | 28 | # mmd loss 29 | mmd = RBFMMD2([0.25,4], num_bit, False) 30 | 31 | # Born Machine 32 | bm = QCBM(circuit, mmd, p_bs) 33 | if context == 'projectq': 34 | bm.context = ProjectQContext 35 | return bm 36 | 37 | def load_barstripe(geometry, depth, context='scipy', structure='nn'): 38 | '''3 x 3 bar and stripes.''' 39 | num_bit = np.prod(geometry) 40 | 41 | # bar and stripe 42 | p_bs = barstripe_pdf(geometry) 43 | 44 | # standard circuit 45 | if structure == 'random-tree': 46 | pairs = random_tree() 47 | elif structure == 'chowliu-tree': 48 | pairs = chowliu_tree(p_bs) 49 | elif structure == 'nn': 50 | # nearest neighbor 51 | pairs = nearest_neighbor(geometry) 52 | else: 53 | raise ValueError('unknown entangle structure %s!'%structure) 54 | circuit = get_demo_circuit(num_bit, depth, pairs) 55 | 56 | # mmd loss 57 | mmd = RBFMMD2([0.5], num_bit, True) 58 | 59 | # Born Machine 60 | bm = QCBM(circuit, mmd, p_bs) 61 | if context == 'projectq': 62 | bm.context = ProjectQContext 63 | return bm 64 | -------------------------------------------------------------------------------- /qcbm/train.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | 4 | def train(bm, theta_list, method, max_iter=1000, step_rate=0.1): 5 | ''' 6 | train a Born Machine. 7 | 8 | Args: 9 | bm (QCBM): quantum circuit born machine training strategy. 10 | theta_list (1darray): initial parameters. 11 | method ('Adam'|'L-BFGS-B'): 12 | * L-BFGS-B: efficient, but not noise tolerant. 13 | * Adam: noise tolerant. 14 | max_iter (int): maximum allowed number of iterations. 15 | step_rate (float): learning rate for Adam optimizer. 16 | 17 | Returns: 18 | (float, 1darray): final loss and parameters. 19 | ''' 20 | theta_list = np.array(theta_list) 21 | if method == 'Adam': 22 | from climin import Adam 23 | optimizer = Adam(wrt=theta_list, fprime=bm.gradient,step_rate=step_rate) 24 | for info in optimizer: 25 | step = info['n_iter'] 26 | loss = bm.mmd_loss(theta_list) 27 | print('step = %d, loss = %s'%(step, loss)) 28 | if step == max_iter: 29 | break 30 | return bm.mmd_loss(theta_list), theta_list 31 | else: 32 | res = minimize(bm.mmd_loss, x0=theta_list, 33 | method=method, jac = bm.gradient, tol=1e-12, 34 | options={'maxiter': max_iter, 'disp': 2, 'gtol':1e-12, 'ftol':0}, 35 | ) 36 | return res.fun, res.x 37 | -------------------------------------------------------------------------------- /qcbm/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sys import platform 3 | import os 4 | 5 | def unpacknbits(arr, nbit, axis=-1): 6 | '''unpack numbers to bits.''' 7 | nd = np.ndim(arr) 8 | if axis < 0: 9 | axis = nd + axis 10 | return (((arr & (1 << np.arange(nbit - 1, -1, -1)).reshape([-1] + [1] * (nd - axis - 1)))) > 0).astype('int8') 11 | 12 | 13 | def packnbits(arr, axis=-1): 14 | '''pack bits to numbers.''' 15 | nd = np.ndim(arr) 16 | nbit = np.shape(arr)[axis] 17 | if axis < 0: 18 | axis = nd + axis 19 | return (arr * (1 << np.arange(nbit - 1, -1, -1)).reshape([-1] + [1] * (nd - axis - 1))).sum(axis=axis, keepdims=True).astype('int') 20 | 21 | 22 | def sample_from_prob(x, pl, num_sample): 23 | ''' 24 | sample x from probability. 25 | ''' 26 | pl = 1. / pl.sum() * pl 27 | indices = np.arange(len(x)) 28 | res = np.random.choice(indices, num_sample, p=pl) 29 | return np.array([x[r] for r in res]) 30 | 31 | 32 | def prob_from_sample(dataset, hndim, packbits): 33 | ''' 34 | emperical probability from data. 35 | ''' 36 | if packbits: 37 | dataset = packnbits(dataset).ravel() 38 | p_data = np.bincount(dataset, minlength=hndim) 39 | p_data = p_data / float(np.sum(p_data)) 40 | return p_data 41 | 42 | def openfile(filename): 43 | ''' 44 | Open a file. 45 | 46 | Args: 47 | filename (str): the target file. 48 | 49 | Return: 50 | bool: succeed if True. 51 | ''' 52 | if platform == "linux" or platform == "linux2": 53 | os.system('xdg-open %s' % filename) 54 | elif platform == "darwin": 55 | os.system('open %s' % filename) 56 | elif platform == "win32": 57 | os.startfile(filename) 58 | else: 59 | print('Can not open file, platform %s not handled!' % platform) 60 | return False 61 | return True 62 | 63 | 64 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scipy 2 | numpy 3 | matplotlib 4 | git+https://github.com/BRML/climin.git 5 | fire 6 | projectq 7 | --------------------------------------------------------------------------------