diff --git a/challenges/PIQUE challenge/PIQUE_challenge.ipynb b/challenges/PIQUE challenge/PIQUE_challenge.ipynb new file mode 100644 index 0000000..0269203 --- /dev/null +++ b/challenges/PIQUE challenge/PIQUE_challenge.ipynb @@ -0,0 +1,185 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Challenge: PIQUE\n", + "### Team # 32\n", + "### Team Members\n", + "- Ayelen Perez | Argentina\n", + "- Jefferson Granizo | Ecuador\n", + "- Jesus Montemayor | Perú\n", + "- Rubén Guzman | México\n", + "- Andres Diaz | Puerto Rico\n", + "\n", + "\n", + "\n", + "# Quantum Investor: Exploring the Fusion of Blockchain and Quantum Computing\n", + "\n", + "**Introduction**:\n", + "\n", + "At the exciting intersection of blockchain investment and quantum computing emerges \"Quantum Investor,\" an educational game designed for high school students. This innovative game provides a unique opportunity to explore advanced concepts playfully and educationally, allowing young adventurers to delve into the intriguing world of quantum physics and blockchain investment strategies.\n", + "\n", + "**Game Description:**\n", + "\n", + "\"Quantum Investor\" invites you to take on the role of a savvy investor in the fascinating realm of quantum blockchains. Armed with financial knowledge and quantum cryptography strategies, you will face exciting challenges while optimizing your investment and safeguarding the integrity of a blockchain against quantum threats.\n", + "\n", + "**How It Works:**\n", + "\n", + "The game presents players with the ability to apply a quantum gate, in this case, the famous Hadamard gate, to strengthen the security and reliability of the blockchain. The application of this gate generates quantum properties such as superposition that enhance the blockchain's resistance to attacks. Participants also have the opportunity to simulate quantum attacks, providing a practical understanding of how threats can impact blockchain security and, ultimately, the investment.\n", + "\n", + "**Code Development:**\n", + "\n", + "The game was developed using IBM's Qiskit quantum programming language, a leading tool in quantum computing research and development.\n", + "The code is structured modularly to facilitate understanding and allow for future educational expansions. In addition, didactic explanations were incorporated into the game to help students understand why applying a quantum gate improves blockchain security and, consequently, investment performance.\n", + "\n", + "**Educational Objectives:**\n", + "\n", + "\"Quantum Investor\" aims to achieve the following educational objectives:\n", + "\n", + "* ***Basics of Quantum Physics:*** Participants will learn fundamental concepts of quantum physics, such as superposition and quantum entanglement.\n", + "\n", + "* ***Quantum Investment Strategies:*** They will experience the importance of investment strategies in the context of quantum blockchains, understanding how quantum techniques can impact investment profitability.\n", + "\n", + "* ***Quantum Programming:*** Players will become familiar with quantum programming using Qiskit, gaining practical insights into designing and executing quantum algorithms for game development.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "**Conclusion:**\n", + "\n", + "\"Quantum Investor\" offers an educational experience to ignite students' interest in the exciting intersection of quantum physics and blockchain investment strategies. This game demonstrates that learning about emerging technologies and finance can be fun and accessible, preparing the next generation for the challenges and opportunities of the digital future.\n", + "Embark on this thrilling educational adventure and discover the fascinating intersection of finance and quantum computing.\n", + "\n", + "# Welcome to \"Quantum Investor\"!\n", + "\n", + "Supporting Tools: ChatGPT" + ], + "metadata": { + "id": "crT2z7MxQhov" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a0s5Spg6cdCs" + }, + "outputs": [], + "source": [ + "!pip install qiskit[all]\n", + "!pip install qiskit-aer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7SvM-F6Dcnoh" + }, + "outputs": [], + "source": [ + "import random\n", + "from qiskit import QuantumCircuit, Aer, execute\n", + "\n", + "# Configuración del juego\n", + "quantum_bits = 8\n", + "blockchain_security = 60\n", + "\n", + "# Crear qubits cuánticos y registro clásico\n", + "qc = QuantumCircuit(quantum_bits, quantum_bits)\n", + "\n", + "# Variables globales para seguimiento\n", + "investment = 1000\n", + "current_security_level = blockchain_security\n", + "\n", + "# Función para aplicar una puerta cuántica de seguridad\n", + "def apply_security_gate(qc, level):\n", + " for i in range(quantum_bits):\n", + " for _ in range(level):\n", + " qc.h(i)\n", + "\n", + "# Función para simular un ataque cuántico\n", + "def simulate_quantum_attack(qc):\n", + " for i in range(quantum_bits):\n", + " if random.random() < 0.2:\n", + " qc.measure(i, i)\n", + "\n", + "# Función para calcular el nivel de seguridad\n", + "def calculate_security_level(success_rate):\n", + " return int(blockchain_security * (1 - success_rate))\n", + "\n", + "# Función para mostrar el estado actual\n", + "def show_game_state():\n", + " print(f\"Inversión actual: {investment} créditos\")\n", + " print(f\"Nivel de seguridad de la blockchain: {current_security_level}\")\n", + " print(f\"Qubits cuánticos: {quantum_bits}\")\n", + "\n", + "# Función para ejecutar el juego\n", + "def run_game():\n", + " global current_security_level, investment\n", + "\n", + " while True:\n", + " show_game_state()\n", + "\n", + " # Opciones del jugador\n", + " print(\"Opciones:\")\n", + " print(\"1. Aplicar seguridad a la blockchain\")\n", + " print(\"2. Simular ataque cuántico\")\n", + " print(\"3. Salir del juego\")\n", + "\n", + " choice = input(\"Elige una opción: \")\n", + "\n", + " if choice == \"1\":\n", + " # Explicación didáctica\n", + " print(\"Al aplicar una puerta cuántica, como la compuerta Hadamard, a los qubits de la blockchain,\")\n", + " print(\"se generan propiedades cuánticas que fortalecen su seguridad.\")\n", + " print(\"La compuerta Hadamard se utiliza para crear superposiciones, lo que hace que sea más difícil para\")\n", + " print(\"los atacantes predecir la información en los qubits, mejorando así la seguridad.\")\n", + " apply_security_gate(qc, 1)\n", + " current_security_level += 10\n", + " investment -= 100\n", + " elif choice == \"2\":\n", + " if choice == \"2\":\n", + " qc.measure(range(quantum_bits), range(quantum_bits))\n", + " backend = Aer.get_backend('qasm_simulator')\n", + " job = execute(qc, backend, shots=1024)\n", + " result = job.result()\n", + "\n", + " simulate_quantum_attack(qc)\n", + " counts = result.get_counts()\n", + " success_rate = counts.get('0', 0) / 1024\n", + " new_security_level = calculate_security_level(success_rate)\n", + "\n", + " # Determinar el resultado del ataque\n", + " if current_security_level > 80:\n", + " print(\"¡La blockchain es segura!\")\n", + " else:\n", + " print(\"¡La blockchain ha sido comprometida!\")\n", + "\n", + " # Actualizar la inversión en función de los resultados del ataque\n", + " investment += 100 if new_security_level > 80 else -100\n", + " elif choice == \"3\":\n", + " break\n", + "\n", + "if __name__ == \"__main__\":\n", + " run_game()\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/challenges/QOSF challenge/TicTacToe (1).ipynb b/challenges/QOSF challenge/TicTacToe (1).ipynb new file mode 100644 index 0000000..7bd3499 --- /dev/null +++ b/challenges/QOSF challenge/TicTacToe (1).ipynb @@ -0,0 +1,310 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Challenge: QOSF\n", + "### Team # 32\n", + "### Team Members\n", + "- Ayelen Perez | Argentina\n", + "- Jefferson Granizo | Ecuador\n", + "- Jesus Montemayor | Perú\n", + "- Rubén Guzman | México\n", + "- Andres Diaz | Puerto Rico" + ], + "metadata": { + "id": "5SltFSOKWsqm" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bRuwWMVmXXv_" + }, + "outputs": [], + "source": [ + "!pip install qiskit[all]\n", + "!pip install qiskit-aer\n", + "!pip install qiskit-ignis" + ] + }, + { + "cell_type": "code", + "source": [ + "from os import read\n", + "from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer, transpile, assemble\n", + "from qiskit.primitives import Sampler\n", + "from qiskit.visualization import plot_histogram\n", + "import random\n", + "import math\n", + "\n", + "posicion_f_X = None\n", + "posicion_c_X = None\n", + "posicion_diagonal_X = None\n", + "posicion_diagonal_secundaria_X = None\n", + "\n", + "celdas = [0, 1, 2, 3, 4, 5, 6, 7, 8]\n", + "numeros_de_celda_X = []\n", + "numeros_de_celda_O = []\n", + "\n", + "\n", + "pi = math.pi\n", + "n=0\n", + "\n", + "qr = QuantumRegister(9,'q')\n", + "cr = ClassicalRegister(9,'c')\n", + "\n", + "qc = QuantumCircuit(qr,cr)\n", + "\n", + "matriz_1 = [\n", + " ['X', 'O', 'O'],\n", + " ['X', '-', '-'],\n", + " ['O', '-', '-']\n", + "]\n", + "\n", + "# Matriz 2\n", + "matriz_2 = [\n", + " ['X', 'O', 'O'],\n", + " ['X', '-', '-'],\n", + " ['-', '-', '-']\n", + "]\n", + "\n", + "while True:\n", + " numeros_de_celda_X = []\n", + " numeros_de_celda_O = []\n", + " print(\"Elige una opción:\")\n", + " print(\"1. Mostrar Matriz 1\")\n", + " print(\"2. Mostrar Matriz 2\")\n", + " print(\"3. Salir\")\n", + "\n", + " opcion = input(\"Ingresa el número de la opción: \")\n", + "\n", + " if opcion == '1':\n", + " for fila in matriz_1:\n", + " print(' '.join(fila))\n", + " tablero = matriz_1\n", + " elif opcion == '2':\n", + " for fila in matriz_2:\n", + " print(' '.join(fila))\n", + " tablero = matriz_2\n", + "\n", + " ps = input(\"Ingresa la posición que desea utilizar 'O' (4,5,6,7,8): \")\n", + " ps = int(ps)\n", + "\n", + " elif opcion == '3':\n", + " break\n", + " else:\n", + " print(\"Opción no válida. Por favor, elige 1, 2 o 3.\")\n", + "\n", + " fila = ps // len(tablero)\n", + " columna = ps % len(tablero[0])\n", + "\n", + " # Reemplazar el valor en la posición con 'X'\n", + " tablero[fila][columna] = 'O'\n", + "\n", + " for fila in tablero:\n", + " print(fila)\n", + "\n", + " for fila_idx, fila in enumerate(tablero):\n", + " for columna_idx, valor in enumerate(fila):\n", + " if valor == 'X':\n", + " numero_de_celda = fila_idx * 3 + columna_idx\n", + " numeros_de_celda_X.append(numero_de_celda)\n", + "\n", + " for fila_ido, fila in enumerate(tablero):\n", + " for columna_ido, valor in enumerate(fila):\n", + " if valor == 'O':\n", + " numero_de_celda = fila_ido * 3 + columna_ido\n", + " numeros_de_celda_O.append(numero_de_celda)\n", + "\n", + " for i in numeros_de_celda_X:\n", + " qc.x(i)\n", + "\n", + " qc.barrier()\n", + "\n", + " index = list(set(numeros_de_celda_X + numeros_de_celda_O))\n", + " index_set = set(index)\n", + " celdas_set = set(celdas)\n", + " resultado = list(index_set.symmetric_difference(celdas_set))\n", + "\n", + " for i in resultado:\n", + " qc.h(i)\n", + " n = n+1\n", + "\n", + " qc.barrier()\n", + "\n", + " angle=pi/(1.5*n)\n", + "\n", + " for ind in numeros_de_celda_X:\n", + " fila = ind // 3\n", + " columna = ind % 3\n", + " elementos_fila = [fila * 3 + i for i in range(3)]\n", + " elementos_columna = [i * 3 + columna for i in range(3)]\n", + " if fila == columna:\n", + " elementos_diagonal_principal = [i * 3 + i for i in range(3)]\n", + " else:\n", + " elementos_diagonal_principal = []\n", + " if fila + columna == 2:\n", + " elementos_diagonal_secundaria = [i * 3 + (2 - i) for i in range(3)]\n", + " else:\n", + " elementos_diagonal_secundaria = []\n", + " todas_las_listas = elementos_fila + elementos_columna + elementos_diagonal_principal + elementos_diagonal_secundaria\n", + "\n", + " for j in todas_las_listas:\n", + " if ind != j: # Asegurarse de que ind y j no sean iguales\n", + " qc.crz(angle, ind, j)\n", + "\n", + "\n", + "\n", + "\n", + " for columna in range(len(tablero[0])):\n", + " elementos_columna = [tablero[fila][columna] for fila in range(len(tablero))]\n", + " count_X = elementos_columna.count('X')\n", + "\n", + " if count_X == 2:\n", + " for fila, elemento in enumerate(elementos_columna):\n", + " if elemento == '-':\n", + " posicion_c_X = fila * len(tablero[0]) + columna\n", + " break\n", + " if posicion_c_X is not None:\n", + " for i in numeros_de_celda_X:\n", + " qc.crz(angle, i, posicion_c_X)\n", + "\n", + "\n", + " for fila in range(len(tablero)):\n", + "\n", + " count_X = tablero[fila].count('X')\n", + "\n", + " if count_X == 2:\n", + " for columna, elemento in enumerate(tablero[fila]):\n", + " if elemento == '-':\n", + " posicion_f_X = fila * len(tablero[0]) + columna\n", + " break\n", + "\n", + " if posicion_f_X is not None:\n", + " for i in numeros_de_celda_X:\n", + " qc.crz(angle, i, posicion_f_X)\n", + "\n", + "\n", + "\n", + " qc.barrier()\n", + " for i in resultado:\n", + " qc.h(i)\n", + "\n", + " qc.barrier()\n", + "\n", + " for i in numeros_de_celda_X:\n", + " qc.x(i)\n", + "\n", + " qc.barrier()\n", + "\n", + " qc.measure(qr,cr)\n", + " break\n", + "display(qc.draw(output=\"mpl\"))\n", + "simulator = Aer.get_backend('qasm_simulator')\n", + "job = execute(qc, simulator, shots=8200)\n", + "result = job.result()\n", + "counts = result.get_counts()\n", + "\n", + "sorted_results = sorted(counts, key=counts.get, reverse=True)\n", + "\n", + "if sorted_results[0] == '0' * len(sorted_results[0]):\n", + " if len(sorted_results) > 1:\n", + " max_result = sorted_results[1]\n", + "else:\n", + " max_result = sorted_results[0]\n", + "position = max_result.rfind('1')\n", + "position = position - 8\n", + "if position < 0:\n", + " position = position * -1\n", + "print(\"El movimiento optimo será\",position)\n", + "\n", + "fila = position // len(tablero)\n", + "columna = position % len(tablero[0])\n", + "\n", + "# Reemplazar el valor en la posición con 'X'\n", + "tablero[fila][columna] = 'X'\n", + "\n", + "for fila in tablero:\n", + " print(fila)\n", + "\n", + "plot_histogram(counts)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "9gd8NXn8XZAK", + "outputId": "8ee8dfa9-874e-4d8f-8b91-81fb463efb01" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Elige una opción:\n", + "1. Mostrar Matriz 1\n", + "2. Mostrar Matriz 2\n", + "3. Salir\n", + "Ingresa el número de la opción: 2\n", + "X O O\n", + "X - -\n", + "- - -\n", + "Ingresa la posición que desea utilizar 'O' (4,5,6,7,8): 5\n", + "['X', 'O', 'O']\n", + "['X', '-', 'O']\n", + "['-', '-', '-']\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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BpMiWJKSkrRo0SIlJSWZjgJDqAE7pSYN0vDe47Ry4xJt3v3fapcpKTumPzx/tbolDdEvLv5NkBMCAAAAwcGcyG6MP6gBOE379u314YcfVja51q5dqx49eujxxx8/a5PrZLt27dJFF12kV155pcrjn332GU2ues6xja6tW7dqyZIlio+P14MPPljtMn369JFU0fE97tVXX9U111yjdu3aKSoqSikpKbrvvvuq3HwOoau8vFwFBQUqL+eSZLaiBuw1dvhv5XaH6bllv6v2+UeX3qGS0mJNv35xcIMBAAAAQcScyG6MP6gBOInL5dLixYt1zjnnSJJWr16tiy66SLt27ar1Nu+8806NHj26ymMDBgyocnYX6h/HNrpefvlleb1ejR07VjExMdUuc/wmcyc2uubNm6ewsDA98MADev/993XnnXfqySef1KWXXup3Bxj1z7Zt29SvXz9t27bNdBQYQg3YKyG+k4b1HKP1aR9r065VVZ57/ZP5+nzrO/rD+DfkiYwylBAAAAAIPOZEdmP8QQ3ASX71q1/pggsukFRxNtZll12m/Pz8Wm9vwoQJeuKJJyq//uCDDyr/vWjRIq4KV485ttG1fPlySdKwYcNOu0xGRoakqo2ut99+W//85z81duxYXXjhhZoyZYoef/xxrV69Wp988klgQwMAAurnF98nt8ut5z784ayuDWkrtOjdmfrtuFfUMi7JXDgAAAAAAADUSNOmTfXAAw9Ufv2///u/ysvLq/X2Tm5yzZ49WyNGjNBHH30kSWrbtq1+/etf1z4wAircdIBA2bNnjySpXbt21T5fVlam1atXS6ra6GrevPkpy/bt21eSlJmZWassffv2VXZ2dq3WxZmNHz/er+WzsrIkSe+9957Wr19fo3UWL17sZyoEEzXgTJHhDfX0pB1+r9ez41B99JDvtM+3a9FFy+b+cHmG7Nzdmv3Cdbr18ofUs+PQ2kStlNw5WSVlR3/UNoLhz7esUVyjVsrKylJiYj/TcYLO9v2XeA9s33+A7wFQA87DnAj+1EBtxl+iBuo7auDsrrr5LkXHNFZWdpYSExNP+5iTnby/9X3/z3Z1tfHjxys6OlqS9PTTT+vf//53rV+ruibX8Xty3Xrrrdq+fbsiIyP1y1/+Ur///e917NixareTnJwst9ux5xYFxInjPGTIEL8+l07k2EZXYWGhJOno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dAEAAAAAAFBKgi4qZdSoUZk/f35GjRpVdCkURA8AAABVZk5UbcYfPQDUor5FFwC9aejQoTnppJOKLoMC6QEAAKDKzImqzfijB4Ba5IouKuWpp57KNddck6eeeqroUiiIHgAAAKrMnKjajD96AKhFgi4qpbm5ORdddFGam5uLLoWC6AEAAKDKzImqzfijB4BaJOgCAAAAAACglCoRdLW2tmbu3LmZMGFCGhoaMmbMmMyZMydtbW0566yzUldXlyuuuKLoMqHbNd17a778vrr8etFlO9zmy++ry39cdkIvVgUAAAC9w7wYAGpf36IL6GlLly7Ncccdl5aWljQ2NmbKlCl54okncvnll+ehhx7acj/aGTNmFFsoAAAAAAAAnVLTV3S1trbmxBNPTEtLS84///w0NzfnzjvvTEtLS+bPn59Fixbl9ttvT11dXaZNm1Z0ufSCxsbGHHnkkWlsbCy6FAqiBwAAgCozJ6o2448eAGpRTV/Rde6556apqSnnnHNOLrts60vU586dm29/+9u5++67M378+AwZMqSgKulN48aNy1VXXVV0GRRIDwAAAFVmTlRtxh89ANSimr2ia/ny5Vm4cGGGDx+eiy++eLvbzJw5M0kyffr0Lcve8pa3pK6ubruvs88+u1dqp+ds3rw5a9asyebNm4supVdt2rA2z61u3e6raqraAwAAAEl150Tmxc+r6vjzR3oAqEU1e0XXggUL0t7enjPOOCODBg3a7jYDBgxIsnXQ9ZWvfCXPPvvsVtstWrQoF110UU44wYNJy+6+++7LKaeckmuvvTZTp04tupxe84vr5uUX180ruoxdQlV7AAAAIKnunMi8+HlVHX/+SA8Atahmg67FixcnSWbNmrXDbZqampJsHXRNmTJlm+2++MUvZq+99sqxxx7bpVoOPvjgtLS0dGlfXt4HPvCBTm3f3NycJLnpppty11137dQ+V199dSerevX69BuQd170QLcd78BZH87EN5y63XXf+5tjuuU9Jk2cmM0bn+uWY3VGrfYAAD3jb8+6PcMGj0xzc3NGjz6k6HIA4FWr1TmRefHO60wPdGX8E/PiXZ0eeGXv/POPpXHQkDS3NGf06NE7XFbLXnq+zr9a55/smp9Be3v7lt8fddRRnfq59GI1G3Q9+uijSZKxY8dud/2mTZty2223Jdk66Hqp3/3ud/nhD3+Y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xq7vbt17y6ShIyZmnwNnd+sxX+qJ5ieyaX3nxqM71GoPAFRdQ7+ev33gug1rM+/qd+TwKSflHUee86qP1/zEE1m3sfe/CwGotlqdE5kX77zO9EBXxj8xL97V6YFX1v6HWzW2b9685Vy2t6yWvfR8nX+1zj/Z9T+DJ598ssv71mTQNWLEiDz99NO58847c/jhh2+1rrm5ORdccEGSZNq0aamrq9uybvr06fnP//zPvOtd78rf/d3fJUkaGxvzne98J29605teVT30jBduMbmzGhoatvy6s/uOGjWq03W9Wn36DXjljXYxrx352kL+5Vqt9gBA1fXv2/PfhUvuuS4PN9+dla3359a7F26z/msfvzev2WOfnT7eyNe+1hVdAPS6Wp0TmRfvvM70QFfGPzEv3tXpgVdW36fPll9fOJftLatlLz1f51+t8092zc+gvb19y5Wme++9d5ePU5NB1+zZs7N8+fLMnz8/xxxzTCZNmpQkuf3223PmmWemtfX5B43OmDFjq/0eeOCBnHbaaTnkkEPykY98JH369Mk111yT9773vbnxxhtz9NFHd6meO+6441WdDzt23333dWr7ZcuW5etf/3re/va37/R9iC+66KKulPaqrN+UXLjt/2/bpd3/wAPZrYCfKLXaAwBVt3lDcsvlPfsex8w8M8fMPLPbjvfA/Q+kT/9uOxwA7JRanROZF++8zvRAV8Y/MS/e1emBV/alf7wmz65py8gRI9PU1LTDZbXspefr/Kt1/smu+Rm0tbVl0KBBSZKf/vSnXT5OTQZdc+fOzbe//e08/vjjmTp1ag444ICsW7cuDz74YI477riMGzcuN99881bP50qST37ykxk4cGC+973vpW/f5z+at73tbXnsscdy/vnnd/n+kOw6Jk2alNtuuy2DBw8uuhQKogcAAIAqMyeqNuOPHgBqUX3RBfSE0aNHZ8mSJTn++OPT0NCQFStWZNiwYbnyyiuzaNGi3H///UmyTdB1zz33ZPr06VtCrhccfPDBWb58ea/VT8/p169fhg0bln79+hVdCgXRAwAAQJWZE1Wb8UcPALWoJoOuJJk8eXJuvPHGrF69OqtXr84vf/nLfPjDH05bW1tWrFiR+vr6HHjggVvtM2LEiCxdujSbNm3aavntt99emft01rrHHnssH/nIR/LYY48VXQoF0QMAAECVmRNVm/FHDwC1qGaDrh1ZtmxZOjo6MnHixG0euPjRj340DzzwQN75znfmxhtvzA9+8IOceeaZ+a//+q/MmTOnoIrpTqtXr84tt9yS1atXF11Krxg95S2Z862OzDz+4zvcZs63OnLyx2/sxaqKVbUeAAAAeLGqzYnMi7dWtfFnW3oAqEU1+Yyul3PPPfck2fa2hUly6qmn5oYbbsj8+fPz/ve/P5s3b86kSZNyzTXX5PTTT+/tUgEAAAAAAHgZgq6XOOGEE3LCCSf0ZkkAAAAAAAB0gaALAIBK+e/fXJu7HvjPzHn3P2XDpvW58obzc8f9N6d/34bsN3J6PnH6t4ouEQAAANhJlQu6Fi9eXHQJFGjvvffOhRdemL333rvoUiiIHgDgtv/5XmbP/LMkyddu+kTq6upy9dz7U1dXl6eebSm4OgDoWeZE1Wb80QNALapc0EW1DR8+PB/4wAeKLoMC6QGA2rfmuVX50P87MOs3Ppe9dh+TjZvXp+X3D+etM8/MnHf9U5atuC0XnHZ1ntvQlh/+6mv59qebUldXlyQZNmREwdUDQM8yJ6o2448eAGpRfdEFQG965pln8sMf/jDPPPNM0aVQED0AUPsGDRiao2ecnncd9bFc+X+X5v+c9Pc5YOxhOf/Uq7L0wVsyZewR6dunX5pbH8rggcOyYPGX8pEvH5zzvvLG3PnAfxZdPgD0KHOiajP+6AGgFgm6qJSmpqacd955aWpqKroUCqIHAKrhwSeWZsKo1yVJHmj6dSa89vnf/2zZ93Pkge9Mkmxu35Qnn340Y18zJV+Zc0c+evLl+eK3TsvTq58srG4A6GnmRNVm/NEDQC0SdAEAUHMefmnQNep16ejoyB2/vTmHHnBckuQ1e+yT+rr6HP36M5IkE0a9LiOGjc8jzfcUVjcAAADQOYIuAABqSuszK5O6ugzffVSS5OGW32T8iINy3+O/yj57T86A3QYlSXZvHJ4ZE96aO357c5Kk+alH0vLUI9ln78mF1Q4AAAB0Tt+iCwAAgO704Mq7ttyqMEkGNQzN9T//SnZvHJ4jpr5jq20/9u5/zv/7zlm56qYLU19Xn4+9+8otARkAAACw6xN0USkNDQ2ZPHlyGhoaii6FgugBgNp32JQTctiUE7b8+R/n3J4k+eBlU3Pp2bdste3IPffNZS9ZBgC1zJyo2ow/egCoRYIuKmW//fbLd7/73aLLoEB6AKC6rvr4sqJLAIDCmRNVm/FHDwC1yDO6AAAAAAAAKCVBF5Vy7733Ztq0abn33nuLLoWC6AEAAKDKzImqzfijB4BaJOiiUjo6OrJx48Z0dHQUXQoF0QMAAECVmRNVm/FHDwC1yDO6YBfUv08y/7Siq+ic/n2KrgCAWlLfL5l1btFVdE59v6IrAIDaYV4MAOwsQRfsgurqkt381wlAhdXVJX36F10FAFAU82IAYGe5dSEAAAAAAACl5N/GUCn77bdfrr/++owZM6boUiiIHgAAAKrMnKjajD96AKhFgi4qpaGhIRMnTiy6DAqkBwAAgCozJ6o2448eAGqRWxdSKStXrsynP/3prFy5suhSKIgeAAAAqsycqNqMP3oAqEWCLipl1apVue6667Jq1aqiS6EgegAAAKgyc6JqM/7oAaAWCboAAAAAAAAoJUEXAAAAAAAApSToAgAAAAAAoJT6Fl0AvBoHHHBAp7bffffdM2/evBxxxBEZOXJkD1VFb9IDAABAlZkT0ZkeMP61SQ8AVSfoolJGjhyZz33uc0WXQYH0AAAAUGXmRNVm/NEDQC1y60IAAAAAAABKSdAFAAAAAABAKQm6AAAAAAAAKCVBFwAAAAAAAKUk6AIAAAAAAKCUBF0AAAAAAACUkqALAAAAAACAUhJ0AQAAAAAAUEqCLgAAAAAAAEpJ0AUAAAAAAEApCboAAAAAAAAoJUHXLqKtrS2f+MQnsu+++6ahoSEHHXRQrr322qLLAgAAAABgO2666abMmDEju+22W8aNG5e//du/LbqkXvXf//3fOfnkkzN27NjU1dXloosuKrqkXnPppZfm8MMPzx577JGhQ4fmqKOOyg9/+MOiy+pV3/zmNzNz5szsscceGTBgQCZPnpy//du/TUdHR6/X0rfX35Ht+vCHP5xf/OIXufLKK7Pvvvvmpptuyp/+6Z9myJAhedvb3lZ0eQAAAAAA/MEdd9yRk08+OR//+MezYMGC/PKXv8zZZ5+dgQMH5uyzzy66vF6xZs2aTJkyJaeffno+9rGPFV1Or1q8eHH+4i/+IoccckgGDhyYq666KieccEL+67/+K0ceeWTR5fWK17zmNfnMZz6T/fffP7vttluWLFmSj3zkI+nTp0/mzJnTq7UIunYB69aty7//+7/nm9/8Zo455pgkyV/91V/lJz/5Sb74xS8KugAAAAAAdiF/+7d/m0MOOSQXX3xxkmTy5MlZtmxZ/uZv/qYyQdfb3/72vP3tb0+SXHjhhQVX07t+8IMfbPXnSy65JD/84Q/z3e9+tzJB15/8yZ9s9ed999033//+93Prrbf2etDl1oW7gI0bN2bz5s1paGjYavmAAQPyi1/8Ihs3biyoMgAAAAAAXuq2227Lscceu9WyY489No8++miampoKqoqitLe359lnn01jY2PRpRSio6Mjv/rVr3Lbbbdl1qxZvf7+rujaBQwePDhHHnlkvvjFL2bGjBnZZ599cvPNN+c//uM/smHDhrS2tmbkyJFFlwkAAAAAsEv7zfKH8vSzq7datn7Dhi2//tcvl+5wWZIMGzokB+2/7yu+T3Nzc0aMGLHVshf+3NzcnNGjR3f1FF6V//39qix/cMU2y196vjs6/6Quh79uSvr379fzxfaA9vb2/OzOZdm8efNWy3f+/JPxY16bfV77mk6975e+9KWsWrUqH/7wh7tefDd5YEVTnniydZvlO/sZDGhoyCHT9k9dXd0rvtczzzyTUaNGZcOGDWlvb8+8efNy7rnnds+JdIKgaxfxrW99Kx/84Aez7777pr6+Pvvvv38++MEP5oorrkh9vQvvAAAAAABeyZDBjVlww+J0dHRss279ho35wa2/2uGy+rq6nP2+k3ulzp4ybOjgLL33oTT/7++3u/6ln8FL/3z466eWNuRKkvr6+rRvbt9mnF/wSuc/ZFBjDpk+uVPv+ZWvfCVf+tKXcv311xcWcL7Ynnvsnm9978dZv2H7d4p7pc/g9JPeulMhV/L8RTxLly7N2rVr87Of/Sx//dd/nde+9rU566yzXt1JdJIEZRcxduzY/PjHP86aNWvy2GOPZdmyZRkwYECGDBmSvfbaq+jyAAAAAAB2eeNGj8ib3zC9S/vOOvx1O30lz8iRI9PS0rLVsieffHLLuqL07dMnp50wK336dP5//e81bPcc95Y39EBVveuoQw7K+DFdG4NTj39zBjbsttPbX3bZZbngggty/fXXZ/bs2V16z+42bPfBOfGtR3Rp3xlTJmTa5P12evv6+vpMmDAh06ZNy9lnn525c+fmU5/6VJfe+9UQdO1iBg4cmNe+9rXZsGFDrr322rzjHe9wRRcAAAAAwE6afdTMjHzNnp3aZ/SIvXL0Ea/f6e2PPPLI3HzzzVst++EPf5ixY8cWflXPiL2G5dg3Hdqpferr6vKeE2alf7/y3wSuvr4+px7/luzWySvTjph5YCaO2/mx++xnP5vPf/7zuemmm3aZkOsFMw+alCkTx3Vqn90HN+akY458Ve/b3t6edevWvapjdIUEZRfx4x//OIsWLcrDDz+c//qv/8oxxxyT5557Ll/60peKLg0AAAAAoDT69umT954wK3379Nmp7fv17fxVUOedd15+9atf5VOf+lTuu+++/Ou//mv+4R/+IZ/4xCe6Wna3OvKQg7LvPjt/VdPRR74+Y0Z27rlUa9asydKlS7N06dJs2LAhLS0tWbp0aR588MHOltvthu0+OCfN3vnQZq9hQ3Pcm3c+HPzYxz6WSy+9NN/85jez//77p6WlJS0tLXnmmWe6Um63q6ury7uOfWMGDRyw0/uc+va3dOpqtnnz5uUnP/lJHn744fz2t7/Nv/zLv2T+/Pl5//vf35WSX5W6ju3drJRed9111+UTn/hEHnvssQwaNCh/8id/kosvvjhjx44tujQAAAAAgNJZcvtvsmjxL15xu5NmH5EjZh7Y6eMvWrQon/zkJ3PfffdlxIgRmTNnTv7v//2/XSm1Rzz9zOr8/dev3eGzml4wZuReOft9J6dPJ+8sduutt2bWrFnbLH/zm9+cW2+9tVPH6gkdHR351vd/nGX3r3jZ7err6/KR970jo0fu/COEdvQMq/e///25+uqrO1Flz7r3wUfzjetufsXtjpx5YE6c3bnbHZ533nm54YYbsnLlyjQ0NGTffffNX/zFX+Tss89On50Mmdva2jJo0KAkzwenjY2NnarhBYIuAAAAAABqTntHR676t0V5+LEndrjNxHGj8+fvOS71Owguyu7X/3N/vrPo1h2u79e3T87983dnr2FDe62m3rRm7XP5+69fmzVtz+1wm2OOOjhvPXLnb1tZNtf94L9z+2/u2+H61+w5NH/1/nelXwG3reyuoMutC0vmN8sfysqW1qLLAAAAAADYpdXX1eU9L/OspgENu+WUt7+5ZkOuJHn91Ik5cNL4Ha5/+6zDajbkSpJBAwfklOPevMP1Y0a+Jm85fEbvFVSAE44+LMOGDt7uuvr6upx2wtGFhFzdSdBVIs+tW5/v3rwk//Cv383DjzcXXQ4AAAAAwC5t6JBBecfbjtruune87ajsPrhrV5CURV1dXd75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9R0Ltn0dbWuro3XGpdVOpnXpuCCV0Y6jVquldayjrRutpVRxwQqGThmtmAAKJO8f1LxGAiYhEJ7w+VwXV57n3Pc59+9k+yNfzjk/+Ul69eqVJPn5z3+ePfbYI7Nnz271mNttt10qKioatY0YMWI9qgQAAAAAADaUDrt14fTp01NXV5cTTjhhjeDiA926dUvSOOj6wA033JBddtml4X15efmGKZRWu/LKKzN48OAkyVNPPZVDDz00S5cubfV4U6dOzVVXXdXwvra2NuXl5TniiCNyzDHHZMaMGetbMgAAAAAA0IY6bNA1a9asJMmECRPWes+CBQuSNB107bjjjvn0pz+9YYpjve255545++yzk6wOpI4//vg2Dbm+9rWv5aWXXsrMmTOTrA4+H3744fz1r39dr7oBAAAAAIC202GDrtdffz1Jsu222zZ5feXKlXniiSeSNB10tbWhQ4emuLh9doo8/NQLUl7RI9WLqjNgwIC1tm1K6urq1nn9wgsvbHj9la98Ja+99lqrn9VUyPXBdpc//vGPc/TRR6dPnz75/Oc/n2uvvXat47Tn1xgAAAAAAApVZWVlnnnmmVb17bBBV21tbZJk2bJlTV6fMWNGFi9enO7du2e77bZb4/oxxxyTxYsXp0+fPjn00EPzj//4j+nbt2+r66murm513/VVt2pVw8eFCxeuta1QDBgwIIceemiSZOHChfmXf/mXVo+1rpArSS655JIcffTRSZIvfelLue6661JfX9/kWO35NQYAAAAAgM1Rhw26Kisr8/bbb+e5557LuHHjGl2rrq7O1KlTkySjRo1KUVFRw7WePXtm6tSp2WeffVJRUZEnn3wyV1xxRZ566qk888wzKS0tbVU9/fr1a7fVPsWdOjV87N+//1rbNiV1dXVrDY5OPvnkdPpb/f/6r/+alStXtuoZHxdyJcmLL76Yn//85/nMZz6TYcOGZc8998yvfvWrJsdrz68xAAAAAAAUqsrKylb37bBB18SJEzN37txceeWVmTRpUoYNG5Ykefrpp3PSSSdl8eLFSZIxY8Y06rfzzjtn5513bng/fvz4jBw5MoceemimT5+eU089tVX1zJs3L+Xl5a2bzHq6/IY7sqSmNv0q+zWcS9ZU26aktrY2FRUVTV77cHB5xx13tGr85oRcH37GZz7zmYZnry3oas+vMQAAAAAAbI467PKTadOmpU+fPnnjjTcyYsSI7LTTThk6dGh23333DBkyJPvtt1+S5p3PNWXKlJSXl7d6f0ja1tixY5Mkb731Vl555ZUW929JyJWsDkc/+mwAAAAAAKD9ddiga8CAAXn88cdz8MEHp7S0NPPnz0/v3r1TVVWVBx54IC+99FKS5gVdH/jwFoe0j759+6Zfv35Jkueff77F/VsaciXJ3LlzG856a8n3CwAAAAAAsGF12K0Lk2T48OG5//7712ivqanJ/PnzU1xcnJEjR37sOPfdd19qa2uz++67b4gyaYEePXo0vF60aFGL+rYm5EqSVatWZfHixRk4cGC6d+/eomcCAAAAAAAbTocOutZmzpw5qa+vz7Bhw1JWVtbo2oknnpghQ4Zkl112SUVFRZ588slcddVVGTNmTI499th2qpgPvP7669lmm21SWlqa5cuXN7tfUVFR9thjj4b3zQ25PjB+/PisWrWqYWUXAAAAAADQ/jbLoOuFF15I0vQ2dCNGjMidd96Za6+9NsuWLcuAAQNy+umn59JLL02XLl02dql8xKpVq1JdXd3ifvX19Tn66KPz4x//OL/5zW9aFHIlyauvvtriZwIAAAAAABuWoOsjvvrVr+arX/3qxi6JjWDFihX53Oc+l7q6uvYuBQAAAAAAaAPF7V1Ae1hX0EXHJuQCAAAAAICOY7Nc0TVr1qz2LgEAAAAAAID1tFmu6AIAAAAAAKDwCboAAAAAAAAoSIIuAAAAAAAACpKgCwAAAAAAgIIk6AIAAAAAAKAgCboAAAAAAAAoSIIuAAAAAAAACpKgCwAAAAAAgIIk6AIAAAAAAKAgCboAAAAAAAAoSIIuAAAAAAAAClJJexcATSkrK0tNTU2bjfedqruypHZpepSXZeqZx67xvi2UlZW1yTgAAAAAAEDzCLrYJBUVFaW8vLzNxuvStTRdVqxKl66lKS8vX+M9AAAAAABQeGxdCAAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEEqae8CgDXV19dn6dKl7V1Gi5SVlaWoqKi9ywAAAAAAYDMi6IJN0NKlS1NRUdHeZbRITU1NysvL27sMAAAAAAA2I7YuBAAAAAAAoCAJugAAAAAAAChIgi4AAAAAAAAKkqALAAAAAACAgiToAgAAAAAAoCAJugAAAAAAAChIgi4AAAAAAAAKkqALAAAAAACAgiToAgAAAAAAoCAJugAAAAAAAChIgi6gWYqL/boAAAAAAGDTUtLeBQAbVvfu3bPLLrtkzJgx2WKLLVJSUpL33nsvr7zySp555pnMmzcv9fX16xyjc+fOueuuuzJnzpxccsklG6lyAAAAAABYN0EXdEDdu3fPiSeemDPOOCNjxoxZ571LlizJvffemxtvvDG/+c1v1rj+Qch1xBFH5IgjjsiKFSty2WWXbaDKAQAAAACg+Tr8XmSLFy/OtGnTsv3226e0tDQDBw7M+eefn9ra2nzxi19MUVFRvve977V3mdAmunbtmssvvzwLFy7MjTfe+LEhV5L06NEjp5xySn7961/n6aefzrhx4xqufTjkSpJly5blySef3FDlAwAAAABAi3ToFV2zZ8/O5MmTs2jRopSXl2fHHXfMm2++meuvvz6vvPJK3nrrrSRpVhgAm7rdd989t956a4YPH96o/fnnn8+TTz6ZZ599Nm+88UZWrVqVioqK7LTTThk7dmzGjx+fLbbYIkmy66675le/+lWuueaafOMb38itt97aKOQ69NBD8+ijj270uQEAAAAAQFM6bNC1ePHiHHLIIVm0aFEuvPDCXHrppenevXuS5KqrrspFF12UkpKSFBUVZdSoUe1cLayfz3/+8/nhD3+YTp06JUnee++93Hrrrbnxxhvzu9/9rsk+9913X5KkW7duOeaYY3L++ednzJgxKS4uzoUXXpgvfvGL6dWrVxIhFwAAAAAAm6YOu3XheeedlwULFuTcc8/N1Vdf3RByJcm0adMyevTorFy5MoMHD06PHj3asVJYP6eddlpuueWWhpDr6aefzi677JKzzjprrSHXhy1btiy33nprxo4dm6lTp2b58uVJIuQCAAAAAGCT1yGDrrlz52bGjBnp27dvrrjiiibvGTt2bJJk9OjRa1y79957s8cee6S8vDw9e/bMnnvumTlz5mzQmqE1DjjggFRVVTW8v+666zJu3Lj84Q9/aPFYdXV1ue666/KrX/2qUftrr72WX/ziF+tdKwAAAAAAtLUOGXRNnz49dXV1OeGEE1JRUdHkPd26dUuyZtB1/fXX5+ijj85ee+2V++67L9OnT8/EiROzbNmyDV43tETPnj1z0003pbh49Y/x1VdfnQsuuCCrVq1q1XidO3fOXXfdlYkTJyZJ6uvrkyQ77rhjpk6d2jZFAwAAAABAG+qQZ3TNmjUrSTJhwoS13rNgwYIkjYOuV155JVOnTs0111yTc889t6H9oIMO2kCVQuv98z//c/r3758keeihh9YrjPog5DriiCOSrN6ucNq0abn22mvTqVOnfP3rX899993XqpViAAAAAACwoXTIoOv1119Pkmy77bZNXl+5cmWeeOKJJI2DrptvvjmdO3fO6aef3uY1DR06tGHlzcZ2+KkXpLyiR6oXVWfAgAFrbevIPjrfTX3+dXV167w+bNiwfOELX0iSLFmyZL2+Z5sKuT44k2vAgAG56KKL0rVr11x66aU55phj1jpOe36PAwAAAABQuCorK/PMM8+0qm+HDLpqa2uTZK3bDc6YMSOLFy9O9+7ds9122zW0//d//3c++clP5vbbb8+3vvWtvPHGGxk6dGguueSSHHfccetVU3V19Xr1Xx91f9vKrm7VqixcuHCtbR3ZR+db6PM/66yzGl5/61vfalih2FLrCrmS5NJLL83nP//5bL311jn88MPTr1+/tX4vt+f3OAAAAAAAm6cOGXRVVlbm7bffznPPPZdx48Y1ulZdXd2wxduoUaNSVFTU6NrChQvz1a9+NVdeeWUGDhyYm266Kccff3y23HLLhrOLWqNfv37tttqluFOnho8fbHXXVFtH9tH5burzr6urW2tw1K1bt5x66qlJVgdTN910U6ue8XEhV5K89957+cEPfpCLL744nTt3zmmnnZbLLrusyfHa83scAAAAAIDCVVlZ2eq+HTLomjhxYubOnZsrr7wykyZNyrBhw5IkTz/9dE466aQsXrw4STJmzJhG/erq6lJTU5Pbbrsthx12WJLkM5/5TP7whz/ksssuW6+ga968eSkvL291//Vx+Q13ZElNbfpV9mtY+dNUW0f20flu6vOvra1NRUVFk9d222239OrVK0kyc+bMvPXWWy0evzkh1wf+9V//NRdffHGSZP/9919r0NWe3+MAAAAAAGyeOuTyi2nTpqVPnz554403MmLEiOy0004ZOnRodt999wwZMiT77bdfksbncyVJ7969k6RRoFVUVJSJEyfm97///cabAKzD2LFjG14/9thjLe7fkpArSd5444289tprSZKdd97Zqi0AAAAAADYZHfIv1gMGDMjjjz+egw8+OKWlpZk/f3569+6dqqqqPPDAA3nppZeSrBl0jRgxYq1jLl++fIPWDM314aDr2WefbVHfloZcH31OeXl5dthhhxZWDAAAAAAAG0aHDLqSZPjw4bn//vvz7rvv5t13382vf/3rnHHGGamtrc38+fNTXFyckSNHNurz2c9+Nknys5/9rKGtrq4ujzzySHbbbbeNWj+szTbbbNPw+oPQtjlaG3IlyYsvvtjwul+/fi2oFgAAAAAANpwOeUbXusyZMyf19fUZNmxYysrKGl075JBDsvfee+eMM87I//7v/2bQoEH54Q9/mDlz5uSRRx5pp4qhsZtvvjn/9V//ldLS0ixbtqzZ/c4///xWhVxJ8stf/jLJ6pWNH2xjCAAAAAAA7W2zC7peeOGFJGtuW5isPo/rvvvuy0UXXZSvfe1rWbJkSUaPHp0HH3yw4VwvaG+33357q/pdd9112WuvvbL//vu3KORKkkcffbRF9wMAAAAAwMYg6PqIXr16paqqKlVVVRuzLNjgVqxYkaOOOiqjRo1q8dleAAAAAACwKeqwZ3StzccFXdCRrVixQsgFAAAAAECHsdmt6Jo1a1Z7lwAAAAAAAEAb2OxWdAEAAAAAANAxCLoAAAAAAAAoSIIuAAAAAAAACpKgCwAAAAAAgIIk6AIAAAAAAKAgCboAAAAAAAAoSIIuAAAAAAAACpKgCwAAAAAAgIIk6AIAAAAAAKAgCboAAAAAAAAoSIIuAAAAAAAAClJJexcArKmsrCw1NTVtNt53qu7Kktql6VFelqlnHrvG+7ZQVlbWJuMAAAAAAEBzCbpgE1RUVJTy8vI2G69L19J0WbEqXbqWpry8fI33AAAAAABQiGxdCAAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQBF0AAAAAAAAUJEEXAAAAAAAABUnQBQAAAAAAQEESdAEAAAAAAFCQStq7AICm1NfXZ+nSpe1dRouUlZWlqKiovcsAAAAAANhsCLqATdLSpUtTUVHR3mW0SE1NTcrLy9u7DAAAAACAzYatCwEAAAAAAChIgi4AAAAAAAAKkqALAAAAAACAgiToAgAAAAAAoCAJugAAAAAAAChIgi4AAAAAAAAKkqALAAAAAACAgiToAgAAAAAAoCAJugAAAAAAAChIgi4AAAAAAAAKkqAL2Cx07tw5AwYMyCc+8YkMGjQoFRUVLepfWVmZv/u7v9tA1QEAAAAA0Bol7V0AwIZQXFycAw88MIccckjGjh2bUaNGpWvXro3uefHFF/Pss8/msccey5133pmampomx6qsrMysWbMyfPjwbLnllrnkkks2xhQAAAAAAPgYVnQBHUpZWVmmTp2aefPm5YEHHshZZ52V3XbbbY2QK0k++clP5vjjj09VVVUWLlyY7373uxk8eHCjez4cciXJiSeemF69em2EmQAAAAAA8HE2i6Br8eLFmTZtWrbffvuUlpZm4MCBOf/881NbW5svfvGLKSoqyve+9732LhNYT/vss09+97vf5aqrrsqQIUMa2uvq6vLHP/4x99xzT26//fbMmDEjv/71r7N8+fKGe3r06JFzzz03v//973PuueemqKhojZDrtddey4QJE/LXv/51Y08NAAAAAIAmdPitC2fPnp3Jkydn0aJFKS8vz4477pg333wz119/fV555ZW89dZbSZIxY8a0b6FAqxUVFeUf//EfM23atEbtP/3pT/Mv//IvmTVrVpPbEpaUlGSXXXbJF77whZx44okpLy9PeXl5vvvd7+bYY4/NlltumWHDhiX5/yHX66+/vlHmBAAAAADAx+vQK7oWL16cQw45JIsWLcqFF16Y6urqPPfcc1m0aFGuvPLKPPDAA3n66adTVFSUUaNGtXe5QCsUFRXl5ptvbhRyPf7449lhhx1y0EEH5b777lvr2VsrV67Mb37zm5x11lnp379/brjhhoZre+65p5ALAAAAAGAT16GDrvPOOy8LFizIueeem6uvvjrdu3dvuDZt2rSMHj06K1euzODBg9OjR492rBRore9+97v5/Oc/n2R1cPX3f//3GT9+fF588cUWjfPOO+/k3HPPzZFHHpmVK1c2tL/33nuZMmWKkAsAAAAAYBPUYYOuuXPnZsaMGenbt2+uuOKKJu8ZO3ZskmT06NENbePHj09RUVGT/84666yNUjvQPIcffnjOOeecJKtDrqOOOirXXHNN6urqWjVeZWVlLrvsspSU/P9dXbt27ZrzzjuvTeoFAAAAAKBtddgzuqZPn566urqccMIJqaioaPKebt26JWkcdN14441ZsmRJo/seeOCBfOtb38qUKVM2XMFAi/Tp0yff//73G96feeaZ+clPftLq8SorKzNr1qwMHz48SbJgwYJsscUWKS8vz5lnnpmZM2fm0UcfXd+yAQAAAABoQx026Jo1a1aSZMKECWu9Z8GCBUkaB1077rjjGvd9+9vfzpZbbpkDDzywjasEWutb3/pWtt566yTJf/zHf+Tmm29u9VgfDbk+OJNr8uTJDWFaVVVVhg4d2urVYgAAAAAAtL0OG3R9cJ7Otttu2+T1lStX5oknnkjSOOj6qL/85S956KGHcvbZZzfazqylhg4dmuLi9tkp8vBTL0h5RY9UL6rOgAED1trWkX10vua/6c9/XYFSz549c/LJJydJlixZsl7biq4t5Hr99ddTVVWVY489Nvvuu2+GDBmSgw46KPfff/9ax2rPn3MAAAAAgEJVWVmZZ555plV9O2zQVVtbmyRZtmxZk9dnzJiRxYsXp3v37tluu+3WOs706dOzcuXKnHTSSetVT3V19Xr1Xx91q1Y1fFy4cOFa2zqyj87X/At7/qecckrKysqSJD/60Y+yaNGiVo2zrpArSerr63PVVVdl3333TZKcffbZ6wy62vPnHAAAAABgc9Rhg67Kysq8/fbbee655zJu3LhG16qrqzN16tQkyahRo1JUVLTWcW677bYMHz48u+6663rV069fv3Zb6VHcqVPDx/79+6+1rSP76HzNf9Off11d3VqDo+OOO67h9YfP6WqJjwu5PvDQQw/ltddey3bbbZfJkyend+/eeeutt5ocsz1/zgEAAAAAClVlZWWr+3bYoGvixImZO3durrzyykyaNCnDhg1Lkjz99NM56aSTsnjx4iTJmDFj1jrGH//4xzzzzDO5/PLL17ueefPmpby8fL3HaY3Lb7gjS2pq06+yX8O5ZE21dWQfna/5b/rzr62tTUVFxRrtJSUlDT+3L774YubOndvisZsbciWrA7f77rsv559/fpJkl112yaOPPtrkuO35cw4AAAAAsDnqsEsPpk2b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/e1vM2nSpHTq1Cm33nprZsyYkYEDB+bII4/M/fffv1FqB2iu4447LocffniS5H//93+z3377Zc6cOa0eb4899shDDz3U8HvzvffeS5JMnDgxp5122voXDAAAAADQxjpk0DVr1qwkyYQJE9Z6z4IFC5I0DrpmzJiRoqKi/OQnP8mUKVNywAEH5K677srAgQNzxx13bNiiAVqgb9+++e53v9vw/owzzsgf//jHVo/3Qcj1wTavDz/8cI4++uiG6//0T/+UbbbZpvUFAwAAAABsACXtXcCG8PrrrydJtt122yavr1y5Mk888USSxkHX+++/ny5dujSs9kpWn1HTvXv31NXVrVdNQ4cOTXFx++SKh596QcoreqR6UXUGDBiw1raO7KPzNf/Na/5J4X0OPu53zhlnnJE+ffokSe6666712mK1qZDrgzO5br755nzhC19Ijx49cs455+T//t//u9Zx2vP3HAAAAABQuCorK/PMM8+0qm+HDLpqa2uTJMuWLWvy+owZM7J48eJ079492223XUP7SSedlBtuuCEXXnhhLrroopSUlKSqqirz5s3LjTfeuF41VVdXr1f/9VG3alXDx4ULF661rSP76HzNf/Oaf9KxPgedOnXKmWeemWR1IPaVr3yl1WOtK+RKkq997Ws56aST0rlz55x22mn5xje+kffff7/Jsdrz9xwAAAAAsHnqkEFXZWVl3n777Tz33HMZN25co2vV1dWZOnVqkmTUqFEpKipquDZ69Oj8/Oc/zxFHHJFrrrkmSVJeXp677747++yzz3rV1K9fv3Zb6VDcqVPDx/79+6+1rSP76HzNf/Oaf1J4n4O6urq1BkcHHHBABg0alCR54IEHGlaxttTHhVxJ8uc//zn//u//nmOPPTZbbbVVPvvZz+buu+9ucrz2/D0HAAAAABSuysrKVvftkEHXxIkTM3fu3Fx55ZWZNGlShg0bliR5+umnc9JJJ2Xx4sVJkjFjxjTqN2/evBxzzDHZbbfdcvbZZ6dTp0654447cuyxx+b+++/Pfvvt1+qa5s2bl/Ly8lb3Xx+X33BHltTUpl9lv4azyZpq68g+Ol/z37zmnxTe56C2tjYVFRVNXtt7770bXt96662tGr85IdeHn3Hsscc2PHttQVd7/p4DAAAAADZPHTLomjZtWu6888688cYbGTFiRHbYYYcsX748L7/8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f/zxef7551NZWZmvf/3ruf3229v9jKqly9/LtTfNzJKa1p0T9MkhA/P5Iw/82JDjwQcfzDvvvJMdd9wxS5YsSVVVVaZPn56HHnqoYVVee/mPR57Ik8/NaVXfzp1Lcv6pn0vfLXqu875u3bqlvr4+Rx55ZC644IK8+eabOffcc7P33nvnjjvuaNWz28obb/5Pvn/7f6Sulb/aDthnt0wYt3OL+jzzzDPZbbfd8tBDD+WAAw5o1XPbyqpVdbnx9p9kYRNhV3Ns3XeLnHvK4elcsu7/B1FfX59vf/vbDefVrVy5MqeffnqqqqpaHBICAAAAALRWcXsXwGq33357evXqlSFDhqRLly758pe/3LAFWnFx879Mo4Z/ImN23L5VNYzZcftmhVyLFy/OiSeemFtuuWWT27qvrLRrjjp431b3/dzkfZv1R/qDDjooxx13XEaPHp299947t99+e/bee+985zvfadWz29Lk8Z/Klr3XHVStzZT9Pv2xIVeyeuVY7969c8stt2TXXXfNoYcemn/+53/OnXfembfeeqtVz24rA7fZKhP2aFlQ9YFt+2+dfT81usX9qqqqst12220S24x26lScY6bsl5KSTi3vW1ycY6ZM+NiQK0lmzpyZG2+8Mbfcckuee+653H333fnpT3+aiy++uDVlAwAAAAC0iqBrE7HtttvmkUceSU1NTf70pz9lzpw56datW3r06LHGWUAf57OT9kzP7mtfPdaUnt3L89lJezbr3t///vd58803M2XKlJSUlKSkpCTf/OY388orr6SkpCR33nlni57d1oYOHpA9xo5scb/DD9g7PSpav+3euHHjMn/+/Fb3bytdOpfk6CkTUtzCVTWfHDIwu48e3qx7+/Xrl2HDhjXaUnPEiBFJktdff71Fz90Q9hu3SwZUtuznpkvnkhx98IQWBctJsmTJkkyfPj1nnHHGJrOSaas+vTJ5/Kda3G/S3rtmm637NuveCy+8MOeff35OOumk7LTTTjnyyCNz+eWX56qrrsry5ctb/GwAAAAAgNYQdG1iysrKss022+T999/PzJkzc9hhh7X4D+/dSrvmqIPGt6jPUQeNT7fSrs26d7fddssLL7yQ2bNnN/w766yzMnDgwMyePTsHH3xwi569IUzed/ePPWfqw3YeMTQ7NWM127o899xzGThw4HqN0VYG9tsq++2xS7Pv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\n" + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "El movimiento optimo será 6\n", + "['X', 'O', 'O']\n", + "['X', '-', 'O']\n", + "['X', '-', '-']\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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VaoUWrVqhT179uTZprzOFFasWBEJCQnqmcbi+quFbSq8Nr3/jXnPFH47lGcK2Sa2iW1im8zVpsTERLi5uT129vFzdaYwJSVFHZ/38MzghzVv3hwAsH79enUCSn5j+h4dD+jg4AB3d3dcvnwZOp0u16XpvMYPent748iRI4iOjs6VFD5uTCMA2NjYwMbGJle5lZWVUdIL5LzA8rpcbnghPWn5o4/7LOUajQYaTe4hp/mV5xc72/R8tclc+Hdim9gmtim/GJ+2nG0quDblqvdEtQqJjY0NBg0alOexPXv2IDo6Gq+88gpKly4NLy8veHt7w8PDAxEREUhNTTWagZyamoqIiAhUrlwZFStWVMsDAgKwevVqREREoHXr1kbPYVif8OHygIAA/PLLLwgPDzdaH/Hh+gEBAf+t4URERERm9lxdPv43eV0+Bgpn8erKlSvDysqKi1fTC4uLVxMRFV9Pmns8V2cKn8XYsWOxceNGzJ07F8ePH4efnx+OHTuG8PBwNG7cGKNGjTKqHxgYiHfeeQdLly6Fn58funTpgps3byI0NBQlS5bMNTbR1dUVCxcuRL9+/eDn54fevXsDAEJDQ3H37l2EhoY+cUJIRERE9Lx6fgYdPSMHBwfs3r0bo0aNwtmzZ/HFF1/g3Llz+OCDD7B9+3bY2dnlus/333+P+fPnAwDmz5+PLVu2oFu3bjh06BCqV6+eq37fvn2xdetW1KhRA8uXL8eKFStQq1YthIeHo2fPniZvIxEREZGpvTCXj4sKXj4mc+DlYyKi4qtI7H1MRERERIWDSSERERERMSkkIiIiIiaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERAQmhUREREQEJoVEREREBCaFRERERITnMCnMyMjAmDFj0Lp1a3h4eMDW1hblypWDv78/li9fDq1Wm+s+ycnJGDNmDDw9PWFjYwMvLy989NFHSElJyfM59Ho9FixYgLp168LOzg6lS5fGG2+8gZiYmHzjCgsLQ0BAAJycnODs7IzAwEBs3769wNpNREREZE6KiIi5g3hYQkICKlasiCZNmqB69eooXbo07t27h61bt+Lq1asIDg7G1q1bodHk5LOpqalo2bIloqKiEBwcjAYNGuD48eMIDw9H48aNsWfPHtja2ho9x+DBg7F06VLUrl0bXbp0QVxcHH799Vc4OjriwIED8Pb2NqofEhKCfv36oXTp0ujduzcAIDQ0FAkJCfj111/Ro0ePJ25fcnIyXFxckJSUBGdn5//YW0RPZvDX5n3+JaPM+/xERMXZk+Yez11SqNfrkZ2dDWtra6Py7OxstG/fHrt27cLmzZvRpUsXAMCUKVMwffp0jBs3DnPmzFHrjx8/HnPnzsWsWbMwYcIEtXznzp0ICgpC69atsW3bNvV5tm7dis6dOyM4OBhhYWFq/Xv37qFKlSqwtLTE8ePHUaFCBQDA9evX0aBBAwBATEwMnJycnqh9TArJHJgUEhEVX0+aezx3l481Gk2uhBAALC0t0a1bNwDAxYsXAQAigqVLl8LR0RGTJk0yqj9p0iQ4Ojpi6dKlRuVLliwBAMyYMcPoeTp16oQ2bdogPDwc165dU8vXrFmD+/fvY/jw4WpCCAAVKlTAsGHDkJCQgPXr1//HVhMRERGZ13OXFOZHr9fjzz//BADUqVMHABAdHY24uDj4+/vDwcHBqL6DgwP8/f0RExOD2NhYtXzXrl3qsUd16NABALB7926j+gAQHBz8RPWJiIiIXkSW5g4gP1lZWZg1axZEBHfv3sX27dtx7tw5DBw4EG3btgWQkxQCyDUG0MDb2xthYWGIjo5GxYoVkZqaips3b6JOnTqwsLDIs/7Dj/u458ir/qMy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heDgYHh6eqrlvXr1gouLCxYsWIDr16+r5devX8fChQtRqlQpdOvW7T+3m4iIiMicnruJJlOnTsW0adPg6OiIkSNH5nkd/NVXX4Wvry+AnDOC/v7+OHHiBIKDg+Hn54djx44hPDwcjRs3xu7du2FnZ2d0/8GDB2Pp0qWoXbs2unTpgps3byI0NBSOjo6IjIxE9erVjeqHhISgX79+KF26NHr37g0ACA0NRUJCAkJDQ9GzZ88nbh93NCFz4I4mRETF15PmHs/dRJMrV64AAFJSUnKtgWjg5eWlJoUODg7YvXs3pk6dit9++w07d+6Eu7s7PvjgA0yZMiVXQggA33//PerWrYvFixdj/vz5cHR0RLdu3TBz5sw8l9Lp27cvSpUqhVmzZmH58uVQFAUNGzbEJ598gnbt2hVY24mIiIjM5bk7U1jU8UwhmQPPFBIRFV8m2fuYiIiIiIomJoVERERExKSQiIiIiJgUEhERERGYFBIRERERmBQSEREREZgUEhERERGYFBIRERERmBQSEREREZgUEhERERGYFBIRERERmBQSEREREZgUEhERERGYFBIRERERmBQSEREREZgUFll79uzByy+/DA8PDyiKgg0bNuRb93//+x8URcHXX3+tlu3atQuKouT57/Dhw2q9sLAwNGvWDE5OTihdujS6d++OK1eumK5hREREZBJMCouo1NRU1K9fH998882/1lu/fj0OHDgADw8Po/IWLVrg5s2bRv/eeecdVK5cGY0aNQIAXL58GV27dkVQUBCioqIQFhaGhIQEvPbaayZrFxEREZmGpbkDINPo1KkTOnXq9K91bty4geHDhyMsLAxdunQxOmZtbY1y5cqpt7VaLTZu3Ijhw4dDURQAwNGjR6HT6fDpp59Co8n5ffHhhx+ia9eu0Gq1sLKyKuBWERERkanwTGExpdfr0a9fP3z00UeoXbv2Y+tv2rQJd+/excCBA9Wyhg0bQqPRYPny5dDpdEhKSsJPP/2Edu3aMSEkIiJ6wTApLKbmzp0LS0tLjBgx4onqL1u2DB06dECFChXUssqVKyM8PBwff/wxbGxsUKJECVy/fh2//vqrqcImIiIiE2FSWAwdPXoU8+fPx4oVK9RLwf/m+vXrCAsLw6BBg4zK4+PjMXjwYPTv3x+HDx/G7t27YW1tjR49ekBETBU+ERERmQDHFBZDe/fu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\n" + }, + "metadata": {}, + "execution_count": 34 + } + ] + } + ] +} \ No newline at end of file diff --git a/challenges/QOSF challenge/challenge_openqaoa.ipynb b/challenges/QOSF challenge/challenge_openqaoa.ipynb new file mode 100644 index 0000000..80e2562 --- /dev/null +++ b/challenges/QOSF challenge/challenge_openqaoa.ipynb @@ -0,0 +1,3623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "auLfxa4-RRQe" + }, + "source": [ + "# Challenge: OpenQAOA\n", + "### Team # 32\n", + "### Team Members\n", + "- Ayelen Perez | Argentina\n", + "- Jefferson Granizo | Ecuador\n", + "- Jesus Montemayor | Perú\n", + "- Rubén Guzman | México\n", + "- Andres Diaz | Puerto Rico" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "It1BO2eERRQl" + }, + "source": [ + "# Problem Definition - Tic-Tac-Toe\n", + "Introduction\n", + "Tic-tac-toe, also known as noughts and crosses or Xs and Os, is a classic paper-and-pencil game for two players. The game is typically played on a 3x3 grid, although variations with larger grids exist. The objective of the game is simple: one player tries to get three of their marks (either X or O) in a row, either horizontally, vertically, or diagonally, before their opponent does. Consider you want to play the classic version in a 3x3 grid, that you are the \"X\"s, and that you want to design a winning strategy. Consider also the following convention:\n", + "\n", + "the state of the X's is |1>\n", + "the state of the O's is |0>\n", + "the state of the empty cells in an unknown state.\n", + "Consider each bit of the tic-tac-toe board is represented by a qubit, in the form:\n", + "```\n", + "| q0 | q1 | q2 |\n", + "|----|----|----|\n", + "| q3 | q4 | q5 |\n", + "|----|----|----|\n", + "| q6 | q7 | q8 |\n", + "```\n", + "\n", + "We can convert this problem into a binary optimization problem, known as QUBO (Quadratic unconstrained binary optimization) in order to use known Quantum Approximate Optimization (QAOA) Algorithms. We first create a cost function, that can be of the form:\n", + "\n", + "$$𝐶(𝑞𝑖)=𝑀(𝑞_0+𝑞_1+𝑞_2+𝑞_3+𝑞_4+𝑞_5+𝑞_6+𝑞_7+𝑞_8−2)^2−(𝑞_0+𝑞_1+𝑞_2)^2−(𝑞_3+𝑞_4+𝑞_5)^2−(𝑞_6+𝑞_7+𝑞_8)^2−(𝑞_0+𝑞_3+𝑞_6)^2−(𝑞_1+𝑞_4+𝑞_7)^2−(𝑞_2+𝑞_5+𝑞_8)^2−(𝑞_0+𝑞_4+𝑞_8)^2−(𝑞_2+𝑞_4+𝑞_6)^2$$\n", + "\n", + "The idea is to minimize this function when you get three X's in a row (vertical, horizontal, or diagonal), represented by the terms that are being subtracted. We do have the challenge of considering each of the possibilities of our opponent, and the order of play. We chose this well-know problem in order to learn how to apply QAOA and its different options (different simulation methods, inclusion of noise, etc) to a case where the minimum-energy state can be readily verified.\n", + "\n", + "## Analysis of a toy problem - case # 1\n", + "\n", + "To better understand how we can design a winning strategy and determine the best possibilities to win, we are going to consider the situation in the figure below. Your turn is next and we need to develop a quantum algorithm to be able to find the best decisions with the higher probability.\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | | |\n", + "|----|----|----|\n", + "| O | | |\n", + "```\n", + "\n", + "We can then define a quantum state $|q_0q_1q_2q_3>$ made up of four qubits, each one representing the state of the four missing spaces:\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | q0 | q1 |\n", + "|----|----|----|\n", + "| O | q2 | q3 |\n", + "```\n", + "\n", + "Consider now all the possible ways to win (eight in total: 3 vertical, 3 horizontal, and 2 diagonal). Of this eight possibilities, only two of them (second row horizontal and diagonal from the top right), are possible with the current stay of play, simplifying the definiton of our cost function. Our cost function $C$ now becomes:\n", + "\n", + "$$C(q_i) = M(1+0+0+1+q_0+q_1+0+q_2+q_3-2)^2-(1+q_0+q_1)^2-(1+q_0+q_3)^2$$\n", + "$$C(q_i) = M(q_0+q_1+q_2+q_3)^2-(1+q_0+q_1)^2-(1+q_0+q_3)^2 $$\n", + "\n", + "We now proceed to consider constraints, determined by the dynamics of the game and that we don't want our opponent to win.\n", + "\n", + "**Equality Constraints**\n", + "\n", + "From the order of play, we are missing at most two X's and two O's. Since we are playing the X's and we have two more plays, we have the condition:\n", + "\n", + "$$q_0+q_1+q_2+q_3 = 2$$\n", + "\n", + "Note that by incorporating this condition into our Cost function, we can further simplify it to:\n", + "\n", + "$$C(q_i) = 4M -(1+q_0+q_1)^2-(1+q_0+q_3)^2 $$\n", + "\n", + "where we choose $M$ to our convenience. Note the equality constraint still needs to be enforced, to guarantee there are exactly two X's in the missing squares.\n", + "\n", + "**Inequality Constraints**\n", + "\n", + "We can also prevent our opponent from winning by not allowing him to place an 'O' in the center square. We can represent this by requiring:\n", + "\n", + "$$q_0 > 0$$\n", + "\n", + "We can try to run the program with or without this inequality constraint to see what happens.\n", + "\n", + "## Analysis of a toy problem - case # 2\n", + "A variation of case 1 is to consider starting one step earlier with your opponent not having chosen yet, as shown below. We can choose this option by calling the function `Problem` with input parameter '2'.\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | | |\n", + "|----|----|----|\n", + "| | | |\n", + "```\n", + "\n", + "We can then define a quantum state $|x_0x_1x_2x_3x_4>$ made up of four qubits, each one representing the state of the four missing spaces:\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | q0 | q1 |\n", + "|----|----|----|\n", + "| q4 | q2 | q3 |\n", + "```\n", + "\n", + "## Process\n", + "The problem has been solved using the following workflow:\n", + "- Part 1: problem definition in function `Problem` using the QUBO implementation described above. The problem is then converted to an Ising Model and solved using QAOA.\n", + "- Part 2: improvement of the QAOA algorithm, considering other simulation methods: statevector_simulator, shot-based simulator, and annealing parameterization\n", + "- Part 3: implementation of the model using a Noise Model.\n", + "- Part 4: the different model are compared by plotting the cost function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BYIwp4ttRRQp" + }, + "source": [ + "# Part 1 Problem definition and solution using QAOA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "K2ZzTO7mRRQr" + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "\n", + "# Import external libraries to present an manipulate the data\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "# Import docplex model to generate the problem to optimize\n", + "from docplex.mp.model import Model\n", + "\n", + "# Import the libraries needed to employ the QAOA quantum algorithm using OpenQAOA\n", + "from openqaoa import QAOA\n", + "\n", + "# method to covnert a docplex model to a qubo problem\n", + "from openqaoa.problems.converters import FromDocplex2IsingModel #check this method and properties\n", + "from openqaoa.backends import create_device\n", + "\n", + "# method to find the correct states for the QAOA object\n", + "from openqaoa.utilities import ground_state_hamiltonian" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "W8Y_TZrDRRQt" + }, + "source": [ + "Code your problem" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "B9vRaRMtRRQu" + }, + "outputs": [], + "source": [ + "# inputs\n", + "\n", + "def Problem(value): #(values,weights, max_weight):\n", + "\n", + " # initialize a model\n", + " mdl = Model(\"Basic Problem using minimize\")\n", + "\n", + " if value == 1:\n", + " #tic-tac-toe with only 4 cells left empty\n", + " #| X | O | O |\n", + " #| X | q0 | q1 |\n", + " #| O | q2 | q3 |\n", + " # indicate the binary variables\n", + " n_vars = 4 # Number of variables\n", + " q = mdl.binary_var_list(n_vars, name=\"q\")\n", + " # define the objective function\n", + " obj_func = 40 - (1+q[0]+q[1])*(1+q[0]+q[1]) - (1+q[0]+q[3])*(1+q[0]+q[3])\n", + " mdl.minimize(obj_func)\n", + " # add the constraints\n", + " mdl.add_constraint(q[0] + q[1] + q[2] + q[3] == 2)\n", + " #mdl.add_constraint(q[0] >= 0)\n", + " elif value == 2:\n", + " #tic-tac-toe with 5 cells left empty\n", + " #| X | O | O |\n", + " #| X | q0 | q1 |\n", + " #| q4 | q2 | q3 |\n", + " # indicate the binary variables\n", + " n_vars = 5 # Number of variables\n", + " q = mdl.binary_var_list(n_vars, name=\"q\")\n", + " # define the objective function\n", + " obj_func = 40 - (2+q[4])*(2+q[4]) -(q[2]+q[3]+q[4])*(q[2]+q[3]+q[4]) - (1+q[0]+q[1])*(1+q[0]+q[1]) - (1+q[0]+q[3])*(1+q[0]+q[3])\n", + " mdl.minimize(obj_func)\n", + " # add the constraints\n", + " mdl.add_constraint(q[0] + q[1] + q[2] + q[3] + q[4] == 2)\n", + " #mdl.add_constraint(q[0] >= 0)\n", + " # print a summary of the docplex model\n", + " # print(mdl.prettyprint())\n", + "\n", + " return mdl #return model, check FromDocplex2IsingModel\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "KTE07LS3RRQv", + "outputId": "f745ed9b-36f3-42be-b7e0-12a9157ec594" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: Model name contains whitespaces: |Basic Problem using minimize|\n", + "// This file has been generated by DOcplex\n", + "// model name is: Basic Problem using minimize\n", + "// var contrainer section\n", + "dvar bool q[4];\n", + "\n", + "// single vars section\n", + "dvar bool q_0;\n", + "dvar bool q_1;\n", + "dvar bool q_2;\n", + "dvar bool q_3;\n", + "\n", + "minimize\n", + " - 72 q_0 - 70 q_1 - 68 q_2 - 70 q_3 [ 15 q_0^2 + 32 q_0*q_1 + 34 q_0*q_2\n", + " + 32 q_0*q_3 + 16 q_1^2 + 34 q_1*q_2 + 34 q_1*q_3 + 17 q_2^2 + 34 q_2*q_3\n", + " + 16 q_3^2 ] + 106;\n", + " \n", + "subject to {\n", + "\n", + "}\n" + ] + } + ], + "source": [ + "# Ising encoding of the QUBO problem\n", + "mdl = Problem(1)\n", + "qubo = FromDocplex2IsingModel(mdl)\n", + "ising_encoding = qubo.ising_model\n", + "# Docplex encoding of the QUBO problem\n", + "mdl_qubo_docplex = qubo.qubo_docplex\n", + "\n", + "mdl_qubo_docplex.prettyprint()\n", + "# Print in a df the ising encoding (we need to remove keys: 'problem_instance' and 'metadata'))\n", + "#ising_encoding_dict = ising_encoding.asdict(exclude_keys=['problem_instance', 'metadata'])\n", + "#pd.DataFrame(ising_encoding_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JEcISoRiRRQy", + "outputId": "399b08a1-7831-43ff-eedf-7629468a2d6b" + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
solutions_bitstringsbitstrings_energiesprobabilities
0110027.00.119885
1100127.00.119885
2101032.00.113245
3010132.00.112614
4011035.00.111264
\n", + "
" + ], + "text/plain": [ + " solutions_bitstrings bitstrings_energies probabilities\n", + "0 1100 27.0 0.119885\n", + "1 1001 27.0 0.119885\n", + "2 1010 32.0 0.113245\n", + "3 0101 32.0 0.112614\n", + "4 0110 35.0 0.111264" + ] + }, + "execution_count": 193, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialize the QAOA object\n", + "qaoa = QAOA()\n", + "\n", + "# Set the parameters to use the QAOA algorithm\n", + "qaoa.set_backend_properties(n_shots=1024, seed_simulator=1)\n", + "# p=1, a custom type and range from 0 to pi\n", + "qaoa.set_circuit_properties(p=2, init_type=\"custom\", variational_params_dict={\"betas\":2*[0.01*np.pi],\"gammas\":2*[0.01*np.pi]})\n", + "#Indicate the ising e ncoding model from docplex\n", + "qaoa.compile(ising_encoding)\n", + "# Run the QAOA algorithm\n", + "qaoa.optimize()\n", + "\n", + "results_qaoa = qaoa.result\n", + "results_qaoa.plot_cost(label='Original')\n", + "pd.DataFrame(qaoa.result.lowest_cost_bitstrings(5))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wURSjn_fRRQ0", + "outputId": "fcba395d-17f0-4791-8a70-2fc6c6187372" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(27.0, ['1100', '1001'])" + ] + }, + "execution_count": 194, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# To find the correct answer using ground_state_hamiltonian\n", + "# and the parameter is a cost_hamiltonian\n", + "correct_solution = ground_state_hamiltonian(qaoa.cost_hamil)\n", + "correct_solution" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OcTebnHiRRQ0" + }, + "source": [ + "Validate your answer using docplex, you can see how to check the classical solution using the following tutorial [here](https://github.com/entropicalabs/openqaoa/blob/main/examples/community_tutorials/02_docplex_example.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a9rEXiPfRRQ1", + "outputId": "60896c68-54bf-4646-c08f-bf110e63b0f6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 27.000\n", + "status: OPTIMAL_SOLUTION(2)\n", + " q_0=1\n", + " q_1=1\n", + " q_2=0\n", + " q_3=0\n" + ] + } + ], + "source": [ + "## docplex solution\n", + "sol = mdl_qubo_docplex.solve()\n", + "mdl_qubo_docplex.print_solution(print_zeros=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OZwGgvpARRQ1" + }, + "source": [ + "# Part 2: Improve the QAOA circuit\n", + "\n", + "Perform the same process as above now with the variant of using different backends, p values, and different optimizers until you find the one that can provide the correct answers with the least number of iterations, quantum circuit depth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eNlmpecRRRQ2", + "outputId": "f229cf8e-e563-460e-be25-26be76bec60f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimized cost with AnnealingParams: 36.69610068004\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "## Implementation\n", + "\n", + "# Modeling with statevector_simulator\n", + "q_sv = QAOA()\n", + "qiskit_sv = create_device(location='local', name='qiskit.statevector_simulator')\n", + "q_sv.set_device(qiskit_sv)\n", + "q_sv.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_sv.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_sv.compile(ising_encoding)\n", + "q_sv.optimize()\n", + "results_sv = q_sv.result\n", + "#pd.DataFrame(q_sv.result.lowest_cost_bitstrings(5))\n", + "\n", + "# Modeling with shot-based simulator\n", + "q_shot = QAOA()\n", + "qiskit_shot = create_device(location='local', name='qiskit.qasm_simulator')\n", + "q_shot.set_device(qiskit_shot)\n", + "q_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_shot.set_backend_properties(n_shots = 400)\n", + "q_shot.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_shot.compile(ising_encoding)\n", + "q_shot.optimize()\n", + "results_shot = q_shot.result\n", + "pd.DataFrame(q_shot.result.lowest_cost_bitstrings(5))\n", + "\n", + "# Modeling with annealing parameterization\n", + "q_annealing = QAOA()\n", + "p=6\n", + "q_annealing.set_circuit_properties(p=p, param_type='annealing', init_type='ramp')\n", + "q_annealing.set_classical_optimizer(maxiter=100)\n", + "q_annealing.compile(ising_encoding)\n", + "q_annealing.optimize()\n", + "results_annealing = q_annealing.result\n", + "print(\"Optimized cost with AnnealingParams:\", q_annealing.result.optimized['cost'])\n", + "# annealing optimized schedule\n", + "opt_schedule = q_annealing.result.optimized['angles']\n", + "p_i = range(1,p+1)\n", + "fig = plt.figure(figsize=(8,5))\n", + "plt.plot(p_i,opt_schedule,lw=0.3,ls='--',marker='o')\n", + "plt.xlabel('p')\n", + "plt.title(f'Optimized annealing (discrete) schedule for p={p}');\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PWpQZ1F4RRQ2" + }, + "source": [ + "# Part 3: Noise Model\n", + "\n", + "The optimal combination that you found with the best optimizer, the lowest number of $p$'s and the correct answer, can give the same answer with noise, use the circuit with a noise model and identify if it gives the same answer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ErC1FJCzRRQ3", + "outputId": "9f9960e8-2979-40b6-9811-2c8e4c1045b7" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
solutions_bitstringsbitstrings_energiesprobabilities
0110027.00.005
1100127.00.010
2010132.00.140
3101032.00.095
4011035.00.140
\n", + "
" + ], + "text/plain": [ + " solutions_bitstrings bitstrings_energies probabilities\n", + "0 1100 27.0 0.005\n", + "1 1001 27.0 0.010\n", + "2 0101 32.0 0.140\n", + "3 1010 32.0 0.095\n", + "4 0110 35.0 0.140" + ] + }, + "execution_count": 204, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# implementation using a noise model using qiskit\n", + "from qiskit.providers.fake_provider import FakeManila\n", + "from qiskit.providers.aer.noise import NoiseModel\n", + "from qiskit.providers.aer import QasmSimulator\n", + "\n", + "# Modeling with noisy shot-based simulator\n", + "device_backend = FakeManila()\n", + "device = QasmSimulator.from_backend(device_backend)\n", + "noise_model = NoiseModel.from_backend(device)\n", + "\n", + "q_noisy_shot = QAOA()\n", + "qiskit_noisy_shot = create_device(location='local', name='qiskit.qasm_simulator')\n", + "q_noisy_shot.set_device(qiskit_noisy_shot)\n", + "q_noisy_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_noisy_shot.set_backend_properties(n_shots = 200, noise_model = noise_model)\n", + "q_noisy_shot.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_noisy_shot.compile(ising_encoding)\n", + "q_noisy_shot.optimize()\n", + "results_noisy_shot = q_noisy_shot.result\n", + "pd.DataFrame(q_noisy_shot.result.lowest_cost_bitstrings(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Azlj93FaRRQ3" + }, + "source": [ + "# Part 4: Model comparison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nqoBITCgRRQ3", + "outputId": "cd649b98-d563-425d-8c79-bfeca8bd89ce" + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Print figure comparing results\n", + "fig, ax = plt.subplots(1,1,figsize=(12,8))\n", + "results_qaoa.plot_cost(ax=ax,label='Original')\n", + "results_sv.plot_cost(ax=ax,color='green',label='Statevector Simulator')\n", + "results_shot.plot_cost(ax=ax,color='red',label='Shot Simulator')\n", + "results_noisy_shot.plot_cost(ax=ax,color='orange',label='Noisy Shot Simulator')\n", + "results_annealing.plot_cost(ax=ax,color='black',label='Annealing Simulator')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xcOyfF4URRQ4" + }, + "source": [ + "# Acknowledgments\n", + "\n", + "🎉🎉🎉\n", + "\n", + "Special thanks to Entropica Labs for helping us create this challenge and being able to use their SDK, OpenQAOA. If you want to know more about OpenQAOA or ask them questions directly, check out their [discord channel](discord.gg/ana76wkKBd).\n", + "\n", + "🎉🎉🎉" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/challenges/openqaoa challenge/challenge_openqaoa.ipynb b/challenges/openqaoa challenge/challenge_openqaoa.ipynb new file mode 100644 index 0000000..80e2562 --- /dev/null +++ b/challenges/openqaoa challenge/challenge_openqaoa.ipynb @@ -0,0 +1,3623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "auLfxa4-RRQe" + }, + "source": [ + "# Challenge: OpenQAOA\n", + "### Team # 32\n", + "### Team Members\n", + "- Ayelen Perez | Argentina\n", + "- Jefferson Granizo | Ecuador\n", + "- Jesus Montemayor | Perú\n", + "- Rubén Guzman | México\n", + "- Andres Diaz | Puerto Rico" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "It1BO2eERRQl" + }, + "source": [ + "# Problem Definition - Tic-Tac-Toe\n", + "Introduction\n", + "Tic-tac-toe, also known as noughts and crosses or Xs and Os, is a classic paper-and-pencil game for two players. The game is typically played on a 3x3 grid, although variations with larger grids exist. The objective of the game is simple: one player tries to get three of their marks (either X or O) in a row, either horizontally, vertically, or diagonally, before their opponent does. Consider you want to play the classic version in a 3x3 grid, that you are the \"X\"s, and that you want to design a winning strategy. Consider also the following convention:\n", + "\n", + "the state of the X's is |1>\n", + "the state of the O's is |0>\n", + "the state of the empty cells in an unknown state.\n", + "Consider each bit of the tic-tac-toe board is represented by a qubit, in the form:\n", + "```\n", + "| q0 | q1 | q2 |\n", + "|----|----|----|\n", + "| q3 | q4 | q5 |\n", + "|----|----|----|\n", + "| q6 | q7 | q8 |\n", + "```\n", + "\n", + "We can convert this problem into a binary optimization problem, known as QUBO (Quadratic unconstrained binary optimization) in order to use known Quantum Approximate Optimization (QAOA) Algorithms. We first create a cost function, that can be of the form:\n", + "\n", + "$$𝐶(𝑞𝑖)=𝑀(𝑞_0+𝑞_1+𝑞_2+𝑞_3+𝑞_4+𝑞_5+𝑞_6+𝑞_7+𝑞_8−2)^2−(𝑞_0+𝑞_1+𝑞_2)^2−(𝑞_3+𝑞_4+𝑞_5)^2−(𝑞_6+𝑞_7+𝑞_8)^2−(𝑞_0+𝑞_3+𝑞_6)^2−(𝑞_1+𝑞_4+𝑞_7)^2−(𝑞_2+𝑞_5+𝑞_8)^2−(𝑞_0+𝑞_4+𝑞_8)^2−(𝑞_2+𝑞_4+𝑞_6)^2$$\n", + "\n", + "The idea is to minimize this function when you get three X's in a row (vertical, horizontal, or diagonal), represented by the terms that are being subtracted. We do have the challenge of considering each of the possibilities of our opponent, and the order of play. We chose this well-know problem in order to learn how to apply QAOA and its different options (different simulation methods, inclusion of noise, etc) to a case where the minimum-energy state can be readily verified.\n", + "\n", + "## Analysis of a toy problem - case # 1\n", + "\n", + "To better understand how we can design a winning strategy and determine the best possibilities to win, we are going to consider the situation in the figure below. Your turn is next and we need to develop a quantum algorithm to be able to find the best decisions with the higher probability.\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | | |\n", + "|----|----|----|\n", + "| O | | |\n", + "```\n", + "\n", + "We can then define a quantum state $|q_0q_1q_2q_3>$ made up of four qubits, each one representing the state of the four missing spaces:\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | q0 | q1 |\n", + "|----|----|----|\n", + "| O | q2 | q3 |\n", + "```\n", + "\n", + "Consider now all the possible ways to win (eight in total: 3 vertical, 3 horizontal, and 2 diagonal). Of this eight possibilities, only two of them (second row horizontal and diagonal from the top right), are possible with the current stay of play, simplifying the definiton of our cost function. Our cost function $C$ now becomes:\n", + "\n", + "$$C(q_i) = M(1+0+0+1+q_0+q_1+0+q_2+q_3-2)^2-(1+q_0+q_1)^2-(1+q_0+q_3)^2$$\n", + "$$C(q_i) = M(q_0+q_1+q_2+q_3)^2-(1+q_0+q_1)^2-(1+q_0+q_3)^2 $$\n", + "\n", + "We now proceed to consider constraints, determined by the dynamics of the game and that we don't want our opponent to win.\n", + "\n", + "**Equality Constraints**\n", + "\n", + "From the order of play, we are missing at most two X's and two O's. Since we are playing the X's and we have two more plays, we have the condition:\n", + "\n", + "$$q_0+q_1+q_2+q_3 = 2$$\n", + "\n", + "Note that by incorporating this condition into our Cost function, we can further simplify it to:\n", + "\n", + "$$C(q_i) = 4M -(1+q_0+q_1)^2-(1+q_0+q_3)^2 $$\n", + "\n", + "where we choose $M$ to our convenience. Note the equality constraint still needs to be enforced, to guarantee there are exactly two X's in the missing squares.\n", + "\n", + "**Inequality Constraints**\n", + "\n", + "We can also prevent our opponent from winning by not allowing him to place an 'O' in the center square. We can represent this by requiring:\n", + "\n", + "$$q_0 > 0$$\n", + "\n", + "We can try to run the program with or without this inequality constraint to see what happens.\n", + "\n", + "## Analysis of a toy problem - case # 2\n", + "A variation of case 1 is to consider starting one step earlier with your opponent not having chosen yet, as shown below. We can choose this option by calling the function `Problem` with input parameter '2'.\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | | |\n", + "|----|----|----|\n", + "| | | |\n", + "```\n", + "\n", + "We can then define a quantum state $|x_0x_1x_2x_3x_4>$ made up of four qubits, each one representing the state of the four missing spaces:\n", + "\n", + "```\n", + "| X | O | O |\n", + "|----|----|----|\n", + "| X | q0 | q1 |\n", + "|----|----|----|\n", + "| q4 | q2 | q3 |\n", + "```\n", + "\n", + "## Process\n", + "The problem has been solved using the following workflow:\n", + "- Part 1: problem definition in function `Problem` using the QUBO implementation described above. The problem is then converted to an Ising Model and solved using QAOA.\n", + "- Part 2: improvement of the QAOA algorithm, considering other simulation methods: statevector_simulator, shot-based simulator, and annealing parameterization\n", + "- Part 3: implementation of the model using a Noise Model.\n", + "- Part 4: the different model are compared by plotting the cost function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BYIwp4ttRRQp" + }, + "source": [ + "# Part 1 Problem definition and solution using QAOA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "K2ZzTO7mRRQr" + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "\n", + "# Import external libraries to present an manipulate the data\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "# Import docplex model to generate the problem to optimize\n", + "from docplex.mp.model import Model\n", + "\n", + "# Import the libraries needed to employ the QAOA quantum algorithm using OpenQAOA\n", + "from openqaoa import QAOA\n", + "\n", + "# method to covnert a docplex model to a qubo problem\n", + "from openqaoa.problems.converters import FromDocplex2IsingModel #check this method and properties\n", + "from openqaoa.backends import create_device\n", + "\n", + "# method to find the correct states for the QAOA object\n", + "from openqaoa.utilities import ground_state_hamiltonian" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "W8Y_TZrDRRQt" + }, + "source": [ + "Code your problem" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "B9vRaRMtRRQu" + }, + "outputs": [], + "source": [ + "# inputs\n", + "\n", + "def Problem(value): #(values,weights, max_weight):\n", + "\n", + " # initialize a model\n", + " mdl = Model(\"Basic Problem using minimize\")\n", + "\n", + " if value == 1:\n", + " #tic-tac-toe with only 4 cells left empty\n", + " #| X | O | O |\n", + " #| X | q0 | q1 |\n", + " #| O | q2 | q3 |\n", + " # indicate the binary variables\n", + " n_vars = 4 # Number of variables\n", + " q = mdl.binary_var_list(n_vars, name=\"q\")\n", + " # define the objective function\n", + " obj_func = 40 - (1+q[0]+q[1])*(1+q[0]+q[1]) - (1+q[0]+q[3])*(1+q[0]+q[3])\n", + " mdl.minimize(obj_func)\n", + " # add the constraints\n", + " mdl.add_constraint(q[0] + q[1] + q[2] + q[3] == 2)\n", + " #mdl.add_constraint(q[0] >= 0)\n", + " elif value == 2:\n", + " #tic-tac-toe with 5 cells left empty\n", + " #| X | O | O |\n", + " #| X | q0 | q1 |\n", + " #| q4 | q2 | q3 |\n", + " # indicate the binary variables\n", + " n_vars = 5 # Number of variables\n", + " q = mdl.binary_var_list(n_vars, name=\"q\")\n", + " # define the objective function\n", + " obj_func = 40 - (2+q[4])*(2+q[4]) -(q[2]+q[3]+q[4])*(q[2]+q[3]+q[4]) - (1+q[0]+q[1])*(1+q[0]+q[1]) - (1+q[0]+q[3])*(1+q[0]+q[3])\n", + " mdl.minimize(obj_func)\n", + " # add the constraints\n", + " mdl.add_constraint(q[0] + q[1] + q[2] + q[3] + q[4] == 2)\n", + " #mdl.add_constraint(q[0] >= 0)\n", + " # print a summary of the docplex model\n", + " # print(mdl.prettyprint())\n", + "\n", + " return mdl #return model, check FromDocplex2IsingModel\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "KTE07LS3RRQv", + "outputId": "f745ed9b-36f3-42be-b7e0-12a9157ec594" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: Model name contains whitespaces: |Basic Problem using minimize|\n", + "// This file has been generated by DOcplex\n", + "// model name is: Basic Problem using minimize\n", + "// var contrainer section\n", + "dvar bool q[4];\n", + "\n", + "// single vars section\n", + "dvar bool q_0;\n", + "dvar bool q_1;\n", + "dvar bool q_2;\n", + "dvar bool q_3;\n", + "\n", + "minimize\n", + " - 72 q_0 - 70 q_1 - 68 q_2 - 70 q_3 [ 15 q_0^2 + 32 q_0*q_1 + 34 q_0*q_2\n", + " + 32 q_0*q_3 + 16 q_1^2 + 34 q_1*q_2 + 34 q_1*q_3 + 17 q_2^2 + 34 q_2*q_3\n", + " + 16 q_3^2 ] + 106;\n", + " \n", + "subject to {\n", + "\n", + "}\n" + ] + } + ], + "source": [ + "# Ising encoding of the QUBO problem\n", + "mdl = Problem(1)\n", + "qubo = FromDocplex2IsingModel(mdl)\n", + "ising_encoding = qubo.ising_model\n", + "# Docplex encoding of the QUBO problem\n", + "mdl_qubo_docplex = qubo.qubo_docplex\n", + "\n", + "mdl_qubo_docplex.prettyprint()\n", + "# Print in a df the ising encoding (we need to remove keys: 'problem_instance' and 'metadata'))\n", + "#ising_encoding_dict = ising_encoding.asdict(exclude_keys=['problem_instance', 'metadata'])\n", + "#pd.DataFrame(ising_encoding_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JEcISoRiRRQy", + "outputId": "399b08a1-7831-43ff-eedf-7629468a2d6b" + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
solutions_bitstringsbitstrings_energiesprobabilities
0110027.00.119885
1100127.00.119885
2101032.00.113245
3010132.00.112614
4011035.00.111264
\n", + "
" + ], + "text/plain": [ + " solutions_bitstrings bitstrings_energies probabilities\n", + "0 1100 27.0 0.119885\n", + "1 1001 27.0 0.119885\n", + "2 1010 32.0 0.113245\n", + "3 0101 32.0 0.112614\n", + "4 0110 35.0 0.111264" + ] + }, + "execution_count": 193, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialize the QAOA object\n", + "qaoa = QAOA()\n", + "\n", + "# Set the parameters to use the QAOA algorithm\n", + "qaoa.set_backend_properties(n_shots=1024, seed_simulator=1)\n", + "# p=1, a custom type and range from 0 to pi\n", + "qaoa.set_circuit_properties(p=2, init_type=\"custom\", variational_params_dict={\"betas\":2*[0.01*np.pi],\"gammas\":2*[0.01*np.pi]})\n", + "#Indicate the ising e ncoding model from docplex\n", + "qaoa.compile(ising_encoding)\n", + "# Run the QAOA algorithm\n", + "qaoa.optimize()\n", + "\n", + "results_qaoa = qaoa.result\n", + "results_qaoa.plot_cost(label='Original')\n", + "pd.DataFrame(qaoa.result.lowest_cost_bitstrings(5))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "wURSjn_fRRQ0", + "outputId": "fcba395d-17f0-4791-8a70-2fc6c6187372" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(27.0, ['1100', '1001'])" + ] + }, + "execution_count": 194, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# To find the correct answer using ground_state_hamiltonian\n", + "# and the parameter is a cost_hamiltonian\n", + "correct_solution = ground_state_hamiltonian(qaoa.cost_hamil)\n", + "correct_solution" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OcTebnHiRRQ0" + }, + "source": [ + "Validate your answer using docplex, you can see how to check the classical solution using the following tutorial [here](https://github.com/entropicalabs/openqaoa/blob/main/examples/community_tutorials/02_docplex_example.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a9rEXiPfRRQ1", + "outputId": "60896c68-54bf-4646-c08f-bf110e63b0f6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 27.000\n", + "status: OPTIMAL_SOLUTION(2)\n", + " q_0=1\n", + " q_1=1\n", + " q_2=0\n", + " q_3=0\n" + ] + } + ], + "source": [ + "## docplex solution\n", + "sol = mdl_qubo_docplex.solve()\n", + "mdl_qubo_docplex.print_solution(print_zeros=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OZwGgvpARRQ1" + }, + "source": [ + "# Part 2: Improve the QAOA circuit\n", + "\n", + "Perform the same process as above now with the variant of using different backends, p values, and different optimizers until you find the one that can provide the correct answers with the least number of iterations, quantum circuit depth." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eNlmpecRRRQ2", + "outputId": "f229cf8e-e563-460e-be25-26be76bec60f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimized cost with AnnealingParams: 36.69610068004\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "## Implementation\n", + "\n", + "# Modeling with statevector_simulator\n", + "q_sv = QAOA()\n", + "qiskit_sv = create_device(location='local', name='qiskit.statevector_simulator')\n", + "q_sv.set_device(qiskit_sv)\n", + "q_sv.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_sv.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_sv.compile(ising_encoding)\n", + "q_sv.optimize()\n", + "results_sv = q_sv.result\n", + "#pd.DataFrame(q_sv.result.lowest_cost_bitstrings(5))\n", + "\n", + "# Modeling with shot-based simulator\n", + "q_shot = QAOA()\n", + "qiskit_shot = create_device(location='local', name='qiskit.qasm_simulator')\n", + "q_shot.set_device(qiskit_shot)\n", + "q_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_shot.set_backend_properties(n_shots = 400)\n", + "q_shot.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_shot.compile(ising_encoding)\n", + "q_shot.optimize()\n", + "results_shot = q_shot.result\n", + "pd.DataFrame(q_shot.result.lowest_cost_bitstrings(5))\n", + "\n", + "# Modeling with annealing parameterization\n", + "q_annealing = QAOA()\n", + "p=6\n", + "q_annealing.set_circuit_properties(p=p, param_type='annealing', init_type='ramp')\n", + "q_annealing.set_classical_optimizer(maxiter=100)\n", + "q_annealing.compile(ising_encoding)\n", + "q_annealing.optimize()\n", + "results_annealing = q_annealing.result\n", + "print(\"Optimized cost with AnnealingParams:\", q_annealing.result.optimized['cost'])\n", + "# annealing optimized schedule\n", + "opt_schedule = q_annealing.result.optimized['angles']\n", + "p_i = range(1,p+1)\n", + "fig = plt.figure(figsize=(8,5))\n", + "plt.plot(p_i,opt_schedule,lw=0.3,ls='--',marker='o')\n", + "plt.xlabel('p')\n", + "plt.title(f'Optimized annealing (discrete) schedule for p={p}');\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PWpQZ1F4RRQ2" + }, + "source": [ + "# Part 3: Noise Model\n", + "\n", + "The optimal combination that you found with the best optimizer, the lowest number of $p$'s and the correct answer, can give the same answer with noise, use the circuit with a noise model and identify if it gives the same answer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ErC1FJCzRRQ3", + "outputId": "9f9960e8-2979-40b6-9811-2c8e4c1045b7" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
solutions_bitstringsbitstrings_energiesprobabilities
0110027.00.005
1100127.00.010
2010132.00.140
3101032.00.095
4011035.00.140
\n", + "
" + ], + "text/plain": [ + " solutions_bitstrings bitstrings_energies probabilities\n", + "0 1100 27.0 0.005\n", + "1 1001 27.0 0.010\n", + "2 0101 32.0 0.140\n", + "3 1010 32.0 0.095\n", + "4 0110 35.0 0.140" + ] + }, + "execution_count": 204, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# implementation using a noise model using qiskit\n", + "from qiskit.providers.fake_provider import FakeManila\n", + "from qiskit.providers.aer.noise import NoiseModel\n", + "from qiskit.providers.aer import QasmSimulator\n", + "\n", + "# Modeling with noisy shot-based simulator\n", + "device_backend = FakeManila()\n", + "device = QasmSimulator.from_backend(device_backend)\n", + "noise_model = NoiseModel.from_backend(device)\n", + "\n", + "q_noisy_shot = QAOA()\n", + "qiskit_noisy_shot = create_device(location='local', name='qiskit.qasm_simulator')\n", + "q_noisy_shot.set_device(qiskit_noisy_shot)\n", + "q_noisy_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')\n", + "q_noisy_shot.set_backend_properties(n_shots = 200, noise_model = noise_model)\n", + "q_noisy_shot.set_classical_optimizer(method='COBYLA', maxiter=400, tol=0.001, cost_progress=True, parameter_log=True)\n", + "\n", + "q_noisy_shot.compile(ising_encoding)\n", + "q_noisy_shot.optimize()\n", + "results_noisy_shot = q_noisy_shot.result\n", + "pd.DataFrame(q_noisy_shot.result.lowest_cost_bitstrings(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Azlj93FaRRQ3" + }, + "source": [ + "# Part 4: Model comparison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "nqoBITCgRRQ3", + "outputId": "cd649b98-d563-425d-8c79-bfeca8bd89ce" + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Print figure comparing results\n", + "fig, ax = plt.subplots(1,1,figsize=(12,8))\n", + "results_qaoa.plot_cost(ax=ax,label='Original')\n", + "results_sv.plot_cost(ax=ax,color='green',label='Statevector Simulator')\n", + "results_shot.plot_cost(ax=ax,color='red',label='Shot Simulator')\n", + "results_noisy_shot.plot_cost(ax=ax,color='orange',label='Noisy Shot Simulator')\n", + "results_annealing.plot_cost(ax=ax,color='black',label='Annealing Simulator')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xcOyfF4URRQ4" + }, + "source": [ + "# Acknowledgments\n", + "\n", + "🎉🎉🎉\n", + "\n", + "Special thanks to Entropica Labs for helping us create this challenge and being able to use their SDK, OpenQAOA. If you want to know more about OpenQAOA or ask them questions directly, check out their [discord channel](discord.gg/ana76wkKBd).\n", + "\n", + "🎉🎉🎉" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/hackathon/IBM_challenge.ipynb b/hackathon/IBM_challenge.ipynb new file mode 100644 index 0000000..dee7d9f --- /dev/null +++ b/hackathon/IBM_challenge.ipynb @@ -0,0 +1,185 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Challenge: IBM\n", + "### Team # 32\n", + "### Team Members\n", + "- Ayelen Perez | Argentina\n", + "- Jefferson Granizo | Ecuador\n", + "- Jesus Montemayor | Perú\n", + "- Rubén Guzman | México\n", + "- Andres Diaz | Puerto Rico\n", + "\n", + "\n", + "\n", + "# Quantum Investor: Exploring the Fusion of Blockchain and Quantum Computing\n", + "\n", + "**Introduction**:\n", + "\n", + "At the exciting intersection of blockchain investment and quantum computing emerges \"Quantum Investor,\" an educational game designed for high school students. This innovative game provides a unique opportunity to explore advanced concepts playfully and educationally, allowing young adventurers to delve into the intriguing world of quantum physics and blockchain investment strategies.\n", + "\n", + "**Game Description:**\n", + "\n", + "\"Quantum Investor\" invites you to take on the role of a savvy investor in the fascinating realm of quantum blockchains. Armed with financial knowledge and quantum cryptography strategies, you will face exciting challenges while optimizing your investment and safeguarding the integrity of a blockchain against quantum threats.\n", + "\n", + "**How It Works:**\n", + "\n", + "The game presents players with the ability to apply a quantum gate, in this case, the famous Hadamard gate, to strengthen the security and reliability of the blockchain. The application of this gate generates quantum properties such as superposition that enhance the blockchain's resistance to attacks. Participants also have the opportunity to simulate quantum attacks, providing a practical understanding of how threats can impact blockchain security and, ultimately, the investment.\n", + "\n", + "**Code Development:**\n", + "\n", + "The game was developed using IBM's Qiskit quantum programming language, a leading tool in quantum computing research and development.\n", + "The code is structured modularly to facilitate understanding and allow for future educational expansions. In addition, didactic explanations were incorporated into the game to help students understand why applying a quantum gate improves blockchain security and, consequently, investment performance.\n", + "\n", + "**Educational Objectives:**\n", + "\n", + "\"Quantum Investor\" aims to achieve the following educational objectives:\n", + "\n", + "* ***Basics of Quantum Physics:*** Participants will learn fundamental concepts of quantum physics, such as superposition and quantum entanglement.\n", + "\n", + "* ***Quantum Investment Strategies:*** They will experience the importance of investment strategies in the context of quantum blockchains, understanding how quantum techniques can impact investment profitability.\n", + "\n", + "* ***Quantum Programming:*** Players will become familiar with quantum programming using Qiskit, gaining practical insights into designing and executing quantum algorithms for game development.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "**Conclusion:**\n", + "\n", + "\"Quantum Investor\" offers an educational experience to ignite students' interest in the exciting intersection of quantum physics and blockchain investment strategies. This game demonstrates that learning about emerging technologies and finance can be fun and accessible, preparing the next generation for the challenges and opportunities of the digital future.\n", + "Embark on this thrilling educational adventure and discover the fascinating intersection of finance and quantum computing.\n", + "\n", + "# Welcome to \"Quantum Investor\"!\n", + "\n", + "Supporting Tools: ChatGPT" + ], + "metadata": { + "id": "crT2z7MxQhov" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "a0s5Spg6cdCs" + }, + "outputs": [], + "source": [ + "!pip install qiskit[all]\n", + "!pip install qiskit-aer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7SvM-F6Dcnoh" + }, + "outputs": [], + "source": [ + "import random\n", + "from qiskit import QuantumCircuit, Aer, execute\n", + "\n", + "# Configuración del juego\n", + "quantum_bits = 8\n", + "blockchain_security = 60\n", + "\n", + "# Crear qubits cuánticos y registro clásico\n", + "qc = QuantumCircuit(quantum_bits, quantum_bits)\n", + "\n", + "# Variables globales para seguimiento\n", + "investment = 1000\n", + "current_security_level = blockchain_security\n", + "\n", + "# Función para aplicar una puerta cuántica de seguridad\n", + "def apply_security_gate(qc, level):\n", + " for i in range(quantum_bits):\n", + " for _ in range(level):\n", + " qc.h(i)\n", + "\n", + "# Función para simular un ataque cuántico\n", + "def simulate_quantum_attack(qc):\n", + " for i in range(quantum_bits):\n", + " if random.random() < 0.2:\n", + " qc.measure(i, i)\n", + "\n", + "# Función para calcular el nivel de seguridad\n", + "def calculate_security_level(success_rate):\n", + " return int(blockchain_security * (1 - success_rate))\n", + "\n", + "# Función para mostrar el estado actual\n", + "def show_game_state():\n", + " print(f\"Inversión actual: {investment} créditos\")\n", + " print(f\"Nivel de seguridad de la blockchain: {current_security_level}\")\n", + " print(f\"Qubits cuánticos: {quantum_bits}\")\n", + "\n", + "# Función para ejecutar el juego\n", + "def run_game():\n", + " global current_security_level, investment\n", + "\n", + " while True:\n", + " show_game_state()\n", + "\n", + " # Opciones del jugador\n", + " print(\"Opciones:\")\n", + " print(\"1. Aplicar seguridad a la blockchain\")\n", + " print(\"2. Simular ataque cuántico\")\n", + " print(\"3. Salir del juego\")\n", + "\n", + " choice = input(\"Elige una opción: \")\n", + "\n", + " if choice == \"1\":\n", + " # Explicación didáctica\n", + " print(\"Al aplicar una puerta cuántica, como la compuerta Hadamard, a los qubits de la blockchain,\")\n", + " print(\"se generan propiedades cuánticas que fortalecen su seguridad.\")\n", + " print(\"La compuerta Hadamard se utiliza para crear superposiciones, lo que hace que sea más difícil para\")\n", + " print(\"los atacantes predecir la información en los qubits, mejorando así la seguridad.\")\n", + " apply_security_gate(qc, 1)\n", + " current_security_level += 10\n", + " investment -= 100\n", + " elif choice == \"2\":\n", + " if choice == \"2\":\n", + " qc.measure(range(quantum_bits), range(quantum_bits))\n", + " backend = Aer.get_backend('qasm_simulator')\n", + " job = execute(qc, backend, shots=1024)\n", + " result = job.result()\n", + "\n", + " simulate_quantum_attack(qc)\n", + " counts = result.get_counts()\n", + " success_rate = counts.get('0', 0) / 1024\n", + " new_security_level = calculate_security_level(success_rate)\n", + "\n", + " # Determinar el resultado del ataque\n", + " if current_security_level > 80:\n", + " print(\"¡La blockchain es segura!\")\n", + " else:\n", + " print(\"¡La blockchain ha sido comprometida!\")\n", + "\n", + " # Actualizar la inversión en función de los resultados del ataque\n", + " investment += 100 if new_security_level > 80 else -100\n", + " elif choice == \"3\":\n", + " break\n", + "\n", + "if __name__ == \"__main__\":\n", + " run_game()\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file