diff --git a/08_Quantum_Approximate_Optimization_Algorithms.ipynb b/08_Quantum_Approximate_Optimization_Algorithms.ipynb index 822e98f2e97d99f256dc2b6a126a5e55cbb7937f..725c44a8c9ec927935821e02ed8c73663f49e091 100644 --- a/08_Quantum_Approximate_Optimization_Algorithms.ipynb +++ b/08_Quantum_Approximate_Optimization_Algorithms.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "nbgrader": { "grade": false, @@ -26,17 +26,7 @@ "solution": false } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Available frameworks:\n", - "Qiskit\n", - "D-Wave Ocean\n" - ] - } - ], + "outputs": [], "source": [ "%run -i \"assignment_helper_QML.py\"\n", "%matplotlib inline" @@ -63,7 +53,7 @@ "The aim of MaxCut is to maximize the number of edges (yellow lines) in a graph that are \"cut\" by\n", "a given partition of the vertices (blue circles) into two sets (see figure below).\n", "\n", - "" + "" ] }, { @@ -74,11 +64,7 @@ "\n", "\\begin{align}C(z) = \\sum_{\\alpha=1}^{m}C_\\alpha(z),\\end{align}\n", "\n", - "where $C$ counts the number of edges cut. $C_\\alpha(z)=1$ if $z$ places one vertex from the $\\alpha^\\text{th}$ edge in set $A$ and the other in set $B$, and $C_\\alpha(z)=0$ otherwise. Finding a cut which yields the maximum possible value of $C$ is an NP-complete problem, so our best hope for a polynomial-time algorithm lies in an approximate optimization. In the case of MaxCut, this means finding a partition $z$ which yields a value for $C(z)$ that is close to the maximum possible value.\n", - "\n", - "We can represent the assignment of vertices to set $A$ or $B$ using a bitstring, $z=z_1...z_n$ where $z_i=0$ if the $i^\\text{th}$ vertex is in $A$ and $z_i = 1$ if it is in $B$. For instance, in the situation depicted in the figure above the bitstring representation is $z=0101\\text{,}$ indicating that the $0^{\\text{th}}$ and $2^{\\text{nd}}$ vertices are in $A$ while the $1^{\\text{st}}$ and $3^{\\text{rd}}$ are in $B$. This assignment yields a value for the objective function (the number of yellow lines cut) $C=4$, which turns out to be the maximum cut. In the following sections, we will represent partitions using computational basis states and use PennyLane to rediscover this maximum cut.\n", - "\n", - "

#### Note

In the graph above, $z=1010$ could equally well serve as the maximum cut.

" + "where $C$ counts the number of edges cut. $C_\\alpha(z)=1$ if $z$ places one vertex from the $\\alpha^\\text{th}$ edge in set $A$ and the other in set $B$, and $C_\\alpha(z)=0$ otherwise. Finding a cut which yields the maximum possible value of $C$ is an NP-complete problem, so our best hope for a polynomial-time algorithm lies in an approximate optimization. In the case of MaxCut, this means finding a partition $z$ which yields a value for $C(z)$ that is close to the maximum possible value.\n" ] }, { @@ -124,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -155,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -196,7 +182,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -222,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -250,7 +236,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { @@ -286,44 +272,30 @@ "source": [ "## Optimization ##\n", "\n", - "Finally, we optimize the objective over the angle parameters $\\boldsymbol{\\gamma}$ (params[0]) and $\\boldsymbol{\\beta}$ (params[1]) and then sample the optimized circuit multiple times to yield a distribution of bitstrings. One of the optimal partitions ($z=0101$ or $z=1010$) should be the most frequently sampled bitstring. We perform a maximization of $C$ by minimizing $-C$, following the convention that optimizations are cast as minimizations in PennyLane.\n", + "Finally, we optimize the objective over the angle parameters $\\boldsymbol{\\gamma}$ (params[0]) and $\\boldsymbol{\\beta}$ (params[1]) and then sample the optimized circuit multiple times to yield a distribution of bitstrings. One of the optimal partitions should be the most frequently sampled bitstring. We perform a maximization of $C$ by minimizing $-C$, following the convention that optimizations are cast as minimizations in PennyLane.\n", "\n" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "p=1\n", - "Objective after step 5: 2.7870528\n", - "Objective after step 10: 2.9983800\n", - "Optimized (gamma, beta) vectors:\n", - "[[-0.78539768]\n", - " [-1.1923295 ]]\n", - "Most frequently sampled bit string is: 1010\n" - ] - } - ], + "outputs": [], + "source": [ + "n_layers = 1 # Enter the layes you want\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "n_layers = 1 # Enter the layes you want\n", - "\n", - "\n", - "print(\"\\np={:d}\".format(n_layers))\n", - "\n", - "# initialize the parameters near zero\n", - "init_params = 0.01 * np.random.rand(2, n_layers)\n", - "\n", "# minimize the negative of the objective function\n", "def objective(params):\n", " gammas = params[0]\n", @@ -332,18 +304,34 @@ " for edge in graph:\n", " # objective for the MaxCut problem\n", " neg_obj -= 0.5 * (1 - circuit(gammas, betas, edge=edge, n_layers=n_layers))\n", - " return neg_obj\n", + " return neg_obj\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", "# initialize optimizer: Adagrad works well empirically\n", - "opt = qml.AdagradOptimizer(stepsize=0.5)\n", + "opt = qml.AdagradOptimizer(stepsize=0.1)\n", "\n", "# optimize parameters in objective\n", - "params = init_params\n", + "params = 0.01 * np.random.rand(2, n_layers)\n", "steps = 10\n", "for i in range(steps):\n", " params = opt.step(objective, params)\n", " if (i + 1) % 5 == 0:\n", - " print(\"Objective after step {:5d}: {: .7f}\".format(i + 1, -objective(params)))\n", + " print(\"Objective after step {:5d}: {: .7f}\".format(i + 1, -objective(params)))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", "# sample measured bitstrings 100 times\n", "bit_strings = []\n", @@ -359,50 +347,25 @@ "\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the case where we set n_layers=2, we recover the optimal\n", - "objective function $C=4$\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plotting the results\n", "--------------------\n", - "We can plot the distribution of measurements obtained from the optimized circuits. As\n", - "expected for this graph, the partitions 0101 and 1010 are measured with the highest frequencies,\n", - "and in the case where we set n_layers=2 we obtain one of the optimal partitions with 100% certainty.\n", - "\n" + "We can plot the distribution of measurements obtained from the optimized circuits." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "