Saltar al contenido principal

Ansätze y formas variacionales

En el núcleo de todos los algoritmos variacionales se encuentra la idea central de analizar diferencias entre estados conectados mediante una aplicación bien comportada (por ejemplo, continua, diferenciable) a partir de un conjunto de parámetros o variables, de ahí el nombre.

Primero exploraremos cómo construir circuitos parametrizados manualmente. Usaremos estos circuitos para definir una forma variacional que representa una colección de estados parametrizados para que nuestro algoritmo variacional los explore. Luego construiremos nuestro ansatz aplicando esta forma variacional a nuestro estado de referencia.

También exploraremos cómo equilibrar velocidad y precisión al explorar este espacio de búsqueda.

Un diagrama que muestra los componentes principales de la discusión sobre ansätze, incluyendo ansätze heurísticos y ansätze específicos del problema.

Circuitos cuánticos parametrizados

Los algoritmos variacionales funcionan explorando y comparando una serie de estados cuánticos ψ(θ)|\psi(\vec{\theta})\rangle que dependen de un conjunto finito de kk parámetros θ=(θ0,,θk1)\vec{\theta} = (\theta^0, \ldots, \theta^{k-1}). Estos estados pueden prepararse usando un circuito cuántico parametrizado, donde las puertas se definen con parámetros ajustables. Es posible crear este circuito parametrizado sin vincular ángulos específicos:

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit rustworkx
from qiskit.circuit import QuantumCircuit, Parameter

theta = Parameter("θ")

qc = QuantumCircuit(3)
qc.rx(theta, 0)
qc.cx(0, 1)
qc.x(2)

qc.draw("mpl")

Output of the previous code cell

from math import pi

angle_list = [pi / 3, pi / 2]
circuits = [qc.assign_parameters({theta: angle}) for angle in angle_list]

for circuit in circuits:
display(circuit.draw("mpl"))

Output of the previous code cell

Output of the previous code cell

Forma variacional y ansatz

Para optimizar iterativamente desde un estado de referencia ρ|\rho\rangle hasta un estado objetivo ψ(θ)|\psi(\vec\theta)\rangle, debemos definir una forma variacional UV(θ)U_V(\vec{\theta}) que representa una colección de estados parametrizados para que nuestro algoritmo variacional los explore:

0URUR0=ρUV(θ)UA(θ)0=UV(θ)UR0=UV(θ)ρ=ψ(θ)\begin{aligned} |0\rangle \xrightarrow{U_R} U_R|0\rangle & = |\rho\rangle \xrightarrow{U_V(\vec{\theta})} U_A(\vec{\theta})|0\rangle \\[1mm] & = U_V(\vec{\theta})U_R|0\rangle \\[1mm] & = U_V(\vec{\theta})|\rho\rangle \\[1mm] & = |\psi(\vec{\theta})\rangle \\[1mm] \end{aligned}

Nótese que el estado parametrizado depende tanto del estado de referencia ρ|\rho\rangle, que no depende de ningún parámetro, como de la forma variacional UV(θ)U_V(\vec{\theta}), que siempre depende de parámetros. Denominamos la combinación de estas dos mitades como ansatz: UA(θ):=UV(θ)URU_A(\vec\theta) := U_V(\vec\theta)U_R.

Mientras construimos nuestro ansatz para representar una colección de estados parametrizados que nuestro algoritmo variacional explore, nos encontramos con un problema importante: la dimensionalidad. Un sistema de nn qubits (es decir, espacio de Hilbert) tiene un número enorme de diferentes estados cuánticos en el espacio de configuración. Necesitaríamos una cantidad inmanejable de parámetros para explorarlo completamente. Cuantitativamente, su dimensionalidad es D=22nD = 2^{2n}. Para empeorar las cosas, la complejidad en tiempo de ejecución de los algoritmos de búsqueda y similares crece exponencialmente con esta dimensionalidad, un fenómeno que a menudo se denomina en la literatura como la maldición de la dimensionalidad.

Para contrarrestar este problema, es práctica común imponer restricciones significativas a la forma variacional, de modo que solo se exploren los estados más relevantes. Encontrar un ansatz eficiente y abreviado es un área de investigación activa, pero presentaremos dos diseños comunes.

Ansätze heurísticos y compromisos

Si no tienes información sobre tu problema específico que pueda ayudar a restringir la dimensionalidad, puedes probar una familia arbitraria de circuitos parametrizados con menos de 22n2^{2n} parámetros. Sin embargo, hay algunos compromisos a considerar:

  • Velocidad: Al reducir el espacio de búsqueda, el algoritmo puede ejecutarse más rápido.
  • Precisión: Reducir el espacio conlleva el riesgo de excluir la solución real del problema, lo que lleva a soluciones subóptimas.
  • Ruido: Los circuitos más profundos se ven más afectados por el ruido, por lo que debemos experimentar con la conectividad, las puertas y la fidelidad de las puertas de nuestro ansatz.

Existe un compromiso fundamental entre calidad (o incluso resolubilidad) y velocidad: cuantos más parámetros, más probable es encontrar un resultado preciso, pero más tiempo tarda en ejecutarse el algoritmo.

Circuitos N-locales

Uno de los ejemplos más utilizados de ansätze heurísticos son los circuitos N-locales, por varias razones:

  • Implementación eficiente: El ansatz N-local consiste típicamente en puertas simples y locales que pueden implementarse eficientemente en un computador cuántico, usando un pequeño número de qubits físicos. Esto facilita la construcción y optimización de circuitos cuánticos.
  • Captura correlaciones importantes: El ansatz N-local puede capturar correlaciones importantes entre los qubits de un sistema cuántico, incluso con un pequeño número de puertas. Esto se debe a que las puertas locales actúan sobre qubits adyacentes y pueden crear entrelazamiento entre ellos, lo cual puede ser importante para la simulación de sistemas cuánticos complejos.

Estos circuitos consisten en capas de rotación y entrelazamiento que se alternan y repiten una o más veces de la siguiente manera:

  • Cada capa se construye con puertas de tamaño como máximo NN, donde NN debe ser menor que el número de qubits.
  • En una capa de rotación, las puertas se apilan unas sobre otras. Podemos usar operaciones de rotación estándar, como RX o CRZ.
  • En una capa de entrelazamiento, podemos usar puertas como puertas Toffoli o CX con una estrategia de entrelazamiento.
  • Ambos tipos de capas pueden estar parametrizados o no, pero al menos una de ellas debe contener parámetros. De lo contrario, ¡sin al menos un parámetro no habría variaciones!
  • Opcionalmente, se añade una capa de rotación adicional al final del circuito.

Por ejemplo, creemos un circuito NLocal de cinco qubits con bloques de rotación de puertas RX y CRZ, bloques de entrelazamiento de puertas Toffoli que actúan sobre los qubits [0,1,2][0,1,2], [0,2,3][0,2,3], [4,2,1][4,2,1] y [3,1,0][3,1,0], y 22 repeticiones de cada capa.

from qiskit.circuit.library import NLocal, CCXGate, CRZGate, RXGate
from qiskit.circuit import Parameter

theta = Parameter("θ")
ansatz = NLocal(
num_qubits=5,
rotation_blocks=[RXGate(theta), CRZGate(theta)],
entanglement_blocks=CCXGate(),
entanglement=[[0, 1, 2], [0, 2, 3], [4, 2, 1], [3, 1, 0]],
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")

Output of the previous code cell

En el ejemplo anterior, la puerta más grande es la puerta Toffoli, que actúa sobre tres qubits, lo que hace que el circuito sea 33-local. El tipo más comúnmente utilizado de circuitos NN-locales son los circuitos 22-locales con puertas de rotación de un solo qubit y puertas de entrelazamiento de 22 qubits.

Creemos un circuito 22-local con la clase TwoLocal de Qiskit. La sintaxis es la misma que para NLocal, pero hay algunas diferencias. Por ejemplo, la mayoría de las puertas, como RX, RZ y CNOT, pueden pasarse como cadenas de texto sin importar las puertas ni crear una instancia de Parameter.

from qiskit.circuit.library import TwoLocal

ansatz = TwoLocal(
num_qubits=5,
rotation_blocks=["rx", "rz"],
entanglement_blocks="cx",
entanglement="linear",
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")

Output of the previous code cell

En este caso hemos utilizado la distribución de entrelazamiento lineal, donde cada qubit está entrelazado con el siguiente. Para información sobre otras estrategias, consulta la documentación de TwoLocal.

Efficient SU2

efficient_su2 es un circuito eficiente en hardware que consiste en capas de operaciones de un solo qubit sobre SU(2) y entrelazamientos CX. Este es un patrón heurístico que puede usarse para preparar funciones de onda de prueba para algoritmos cuánticos variacionales o como circuito de clasificación para aprendizaje automático.

from qiskit.circuit.library import efficient_su2

ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
ansatz.decompose().draw("mpl")

Output of the previous code cell

Ansätze específicos del problema

Mientras que los ansätze heurísticos y eficientes en hardware nos ayudan a resolver un problema de manera ingenua, podemos utilizar conocimiento específico del problema para restringir el espacio de búsqueda de nuestro circuito a un tipo determinado. Esto nos ayuda a ganar velocidad sin perder precisión en nuestro proceso de búsqueda.

Optimización

En un problema Max-Cut, queremos particionar los nodos de un grafo de manera que se maximice el número de aristas entre nodos en diferentes grupos. La partición Max-Cut deseada para el siguiente grafo es clara: el nodo 0 del lado izquierdo debe separarse mediante un corte de los demás nodos del lado derecho.

import rustworkx as rx
from rustworkx.visualization import mpl_draw

n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)

mpl_draw(
G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)

Output of the previous code cell

Para utilizar el algoritmo QAOA en un problema Max-Cut, necesitamos un hamiltoniano de Pauli que codifique los costos de modo que el valor esperado mínimo del operador corresponda al número máximo de aristas entre los nodos en dos grupos diferentes.

Para este ejemplo simple, el operador es una combinación lineal de términos con operadores Z en nodos conectados por una arista (nótese que el qubit 0 está a la derecha): ZZII+IZZI+ZIIZ+IZIZ+IIZZZZII + IZZI + ZIIZ + IZIZ + IIZZ. Una vez construido el operador, el ansatz para el algoritmo QAOA puede construirse fácilmente usando el circuito QAOAAnsatz de la biblioteca de circuitos de Qiskit.

# Pre-defined ansatz circuit, operator class and visualization tools
from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp

# Problem to Hamiltonian operator
hamiltonian = SparsePauliOp.from_list(
[("ZZII", 1), ("IZZI", 1), ("ZIIZ", 1), ("IZIZ", 1), ("IIZZ", 1)]
)
# QAOA ansatz circuit
ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")

Output of the previous code cell

La imagen anterior muestra el ansatz en puertas básicas para mayor claridad. Sin embargo, puede representarse en varios niveles de descomposición modificando el argumento reps o dibujando el circuito sin el método decompose. La siguiente representación muestra, por ejemplo, directamente la estructura QAOA con el valor predeterminado de reps, es decir, reps=1.

ansatz.decompose(reps=2).draw("mpl")

Output of the previous code cell

Aprendizaje automático cuántico

En el aprendizaje automático, una aplicación común es la clasificación de datos en dos o más categorías. Para ello, un punto de datos se codifica en un feature map que mapea vectores de características clásicos al espacio cuántico de Hilbert. La construcción de feature maps cuánticos basados en circuitos cuánticos parametrizados que son clásicamente difíciles de simular es un paso importante hacia una potencial ventaja sobre los enfoques clásicos de aprendizaje automático y es un área de investigación activa.

La zz_feature_map puede usarse para crear un circuito parametrizado. Podemos pasar nuestros puntos de datos al feature map (xx) y una forma variacional separada para introducir pesos como parámetros (θ\theta).

from qiskit.circuit.library import zz_feature_map, TwoLocal

data = [0.1, 0.2]

zz_feature_map_reference = zz_feature_map(feature_dimension=2, reps=2)
zz_feature_map_reference = zz_feature_map_reference.assign_parameters(data)

variation_form = TwoLocal(2, ["ry", "rz"], "cz", reps=2)
vqc_ansatz = zz_feature_map_reference.compose(variation_form)
vqc_ansatz.decompose().draw("mpl")

Output of the previous code cell

Resumen

En esta lección aprendiste cómo definir tu espacio de búsqueda con una forma variacional:

  • Preparar estados con un circuito cuántico parametrizado, donde las puertas se definen con parámetros ajustables
  • Cómo construir ansätze que equilibren velocidad y precisión
  • Ansätze heurísticos
  • Ansätze específicos del problema

Nuestro flujo de trabajo variacional de alto nivel se ve así:

Un diagrama de circuito con dos operadores unitarios: uno para preparar el estado de referencia y otro para preparar el ansatz.

Para cada parámetro variacional θ\vec\theta se produce un estado cuántico diferente. Para encontrar los parámetros óptimos, debemos definir una función de costo específica del problema para actualizar iterativamente los parámetros de nuestro ansatz.