class: center, middle, inverse, title-slide # Introducción a la Programación Cuántica ### JPAG ### Modelación Predictiva ### 2021/05/19 --- # Mecánica Cuántica 101 La **computación cuántica** es el uso de fenómenos mecánicos cuánticos (como la superposición y el entrelazamiento) para realizar cálculos. La **química cuántica** (mecánica cuántica molecular) es una rama de la química centrada en la aplicación de la mecánica cuántica en sistemas químicos. Comprender la estructura electrónica y la dinámica molecular mediante las ecuaciones de Schrödinger, así como los estudios del estado fundamental de las moléculas y los estados de transición que ocurren durante las reacciones químicas, son temas centrales en la química cuántica. (La química cuántica teórica tiende a caer en la categoría de química computacional). Realizar química cuántica con cálculos en computadoras cuánticas implica usar algoritmos que tienen supremacía en las computadoras cuánticas (lo que significa que son difíciles de calcular en las computadoras clásicas). Todos los cálculos químicos cuánticos actuales en computadoras cuánticas usan software estándar de mecánica cuántica que tienen complementos especiales para usar estos algoritmos cuánticos que funcionan mejor en procesadores cuánticos. --- El **principio de incertidumbre de Heisenberg** es el límite fundamental de la precisión con la que se pueden predecir los valores para ciertos pares de cantidades físicas. El principio de incertidumbre implica que, en general, no es posible predecir el valor de una cantidad con certeza arbitraria, incluso si se especifican todas las condiciones iniciales. Un sistema en mecánica cuántica o una partícula que está acotada sólo puede tomar ciertos valores discretos de energía, llamados **niveles de energía**. Esto contrasta con las partículas clásicas, que pueden tener cualquier cantidad de energía. Se dice que el espectro de energía de un sistema con tales niveles de energía discretos, está **cuantificado**. El tipo más simple de cálculo de estructura electrónica *ab initio* es el método **Hartree-Fock** (HF), una extensión de la teoría de los orbitales moleculares. El concepto de orbitales de tipo *Slater* son soluciones exactas para el átomo de hidrógeno y proporcionan una base precisa para moléculas de muchos electrones; sin embargo, los cálculos de las integrales son costosos ya que no existe una solución exacta simple. La **ecuación de Schrödinger** es una ecuación diferencial parcial lineal que describe la función de onda o función de estado de un sistema mecánico cuántico. --- La dinámica de un sistema cuántico unidimensional se rige por la ecuación de Schrodinger dependiente del tiempo: `$$iℏ\frac{∂ψ}{∂t}=\frac{−ℏ^2}{2m}\frac{∂^2ψ}{∂x^2}+Vψ$$` La **función de onda**, `\(\psi\)`, es una función tanto de la posición `\(x\)` como del tiempo `\(t\)`, y es la descripción fundamental del reino de lo muy pequeño. Considerando que se sigue el movimiento de una sola partícula en una dimensión, esta función de onda representa una probabilidad de medir la partícula en una posición `\(x\)` al tiempo `\(t\)`. La mecánica cuántica nos dice que (contrariamente al razonamiento clásico familiar) esta probabilidad no es una limitación de nuestro conocimiento del sistema, sino un reflejo de una incertidumbre inevitable sobre la posición y el tiempo de los eventos en el ámbito de lo muy pequeño. Para visualizar los resultados, en el siguiente código se coloca una partícula en una caja con una barrera potencial. La barrera es lo suficientemente alta como para que una partícula clásica no pueda penetrarla. Una partícula cuántica, sin embargo, puede *hacer un túnel*, lo que lleva a una probabilidad distinta de cero de encontrar la partícula en el otro lado de la partición. --- # Simulación Ec. de Schrödinger ```ptyhon from __future__ import print_function from __future__ import division %matplotlib notebook import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation # Concatenación de la función de onda def D_nowrap(A, dX): d = A[1:] - A[:-1] d = np.concatenate(([d[0]], d, [d[-1]])) d = 0.5 * (d[1:] + d[:-1]) return d D = D_nowrap #selecciona la función diff N = 400 #número ded puntos size = 10 #x en [-size, size] dX = 2.*size/N #paso en x dt = 0.0005 #paso en el tiempo X = np.linspace(-size, size, N) ``` --- ```ptyhon x0 = -5 A = np.exp(-4*(X-x0)*((X-x0)+1500j)) #función de onda inicial A = np.array(A, dtype=np.complex) #V = np.zeros(N) V = 0.5*(np.abs(X - 2) < 0.5) #potencial fig, ax = plt.subplots() line, = ax.plot(X, A) ax.set_ylim([0, 1]) ax.plot(X, V) def animate(i): global A for i in range(1000): #ciclo para no mostrar cada paso de tiempo A += dt*(1j*D(D(A, dX), dX) - 1j * V * A) #ecuación de schrödinger print(np.sum(np.abs(A**2))) #devuelve la norma line.set_ydata(np.abs(A**2)) return line, ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=True) plt.show() ``` --- background-image: url(https://upload.wikimedia.org/wikipedia/commons/d/d0/Simulation_Schr%C3%B6dinger_equation.svg) ??? Image credit: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Simulation_Schr%C3%B6dinger_equation.svg) --- # Definiciones Básicas De la probabilidad clásica, podemos deducir sus reglas básicas para fenómenos discretos, mediante la experimentación con problemas básicos. Por ejemplo, al lanzar una moneda justa, hay un `\(50\%\)` de probabilidad de observar Águila y un `\(50\%\)` de probabilidad de observar Sol. También, se puede deducir la regla aditiva para la unión de eventos, y la regla multiplicativa para la intersección de eventos. Para los modelos cuánticos, estas reglas básicas se rompen, no siguen las esquemáticas *clásicas*. En resumen, la teoría cuántica es la teoría de la probabilidad con números negativos. Para que estas probabilidades negativas tengan sentido, se hace uso de una nueva cantidad llamada **amplitud**. se elevan al cuadrado las amplitudes para calcular las probabilidades, y al igual que con la probabilidad clásica, la forma en que todas estas probabilidades suman `\(1\)`, es necesario multiplicar las amplitudes a lo largo de la intersección de los eventos para calcular la amplitud de cada resultado. Esto produce una cancelación de observar la resultante de los fenómenos en ciertos estados, un efecto conocido como **interferencia**. Resulta que estos efectos de interferencia se pueden utilizar ventajosamente; se pueden combinar operaciones para construir algoritmos más eficientes. Estos algoritmos pueden usar efectos de interferencia para hacer que las respuestas incorrectas se cancelen rápidamente y así, brindar una alta probabilidad de medir la respuesta correcta. Esta es la idea detrás de la computación y la programación cuántica. --- Los bits están diseñados para ser el alfabeto más simple del mundo. Con solo dos caracteres, `\(0\)` y `\(1\)`, se puede representar cualquier dato. Al igual que las computadoras digitales estándar, las computadoras cuánticas se basan en esta misma idea básica. La principal diferencia es que usan **qubits**, una extensión del bit a la mecánica cuántica. Ya sea en la programación clásica o la cuántica, es necesario manipular las entradas que se ingresan en salidas de utilidad. Es útil representar este proceso en un diagrama conocido como **diagrama de circuito**. En un circuito, primero se codifican las entradas a la izquierda, luego se llevan a cabo operaciones representadas por símbolos arcanos en el medio y finalmente se extrae una salida a la derecha. Estas operaciones se denominan **compuertas**. `Qiskit` es un *SDK* (Kit de desarrollo de software) de código abierto para trabajar con computadoras cuánticas a nivel de pulsos, circuitos y módulos de aplicación. `Qiskit` acelera el desarrollo de aplicaciones cuánticas al proporcionar el conjunto completo de herramientas necesarias para interactuar con simuladores y sistemas cuánticos. Para poder trabar con esta herramienta, se importa esta paquetería desde `Python` ```ptyhon import qiskit qiskit.__qiskit_version__ ``` `## '{'qiskit-terra': '0.17.4', 'qiskit-aer': '0.8.2', 'qiskit-ignis': '0.6.0', 'qiskit-ibmq-provider': '0.13.1', 'qiskit-aqua': '0.9.1', 'qiskit': '0.26.2', 'qiskit-nature': None, 'qiskit-finance': None, 'qiskit-optimization': None, 'qiskit-machine-learning': None}` --- Aterrizando las ideas principales, se pueden construir circuitos en `Qiskit`. Este ejemplo contiene ocho qubits y ocho salidas. ```ptyhon n = 8 n_q = n n_b = n qc_output = QuantumCircuit(n_q,n_b) ``` Este circuito, llamado `qc_output`, es creado por `Qiskit` usando `QuantumCircuit`. El número `n_q` define el número de qubits en el circuito. Con `n_b` se define el número de bits de salida que extrae el circuito al final. La extracción de salidas en un circuito cuántico se realiza mediante una operación llamada `measure`. Cada medición le dice a un qubit específico que dé una salida a un bit de salida específico. Con el comando `qc.measure (j, j)` se agrega una medida al circuito `qc_output` para decirle al qubit `j` que escriba una salida en el bit `j`. ```ptyhon for j in range(n): qc_output.measure(j,j) ``` Y ahora que el circuito tiene algo, se puede visualizar. ```ptyhon qc_output.draw() ``` --- background-image: url(https://upload.wikimedia.org/wikipedia/commons/a/ac/Qc_output.svg) [](https://commons.wikimedia.org/wiki/File:Qc_output.svg) ---