Saltar al contenido principal

Codificación de datos

Introducción y notación

Para usar un algoritmo cuántico, los datos clásicos deben introducirse de alguna forma en un circuito cuántico. Esto se conoce habitualmente como codificación de datos, aunque también se llama carga de datos. Recuerda de lecciones anteriores el concepto de mapeo de características: una transformación de las características de los datos de un espacio a otro. Transferir datos clásicos a un ordenador cuántico es en sí mismo un tipo de mapeo y podría denominarse mapeo de características. En la práctica, los mapeos de características integrados en Qiskit (como z_Feature Map y ZZ Feature Map) suelen incluir capas de rotación y capas de entrelazamiento que extienden el estado a muchas dimensiones en el espacio de Hilbert. Este proceso de codificación es una parte fundamental de los algoritmos de aprendizaje automático cuántico y afecta directamente a sus capacidades computacionales.

Algunas de las técnicas de codificación que se presentan a continuación pueden simularse eficientemente de forma clásica; esto es especialmente evidente en métodos de codificación que producen estados producto (es decir, que no entrelazan qubits). Y recuerda que la utilidad cuántica es más probable allá donde la complejidad cuántica del conjunto de datos encaje bien con el método de codificación. Por eso es muy probable que acabes escribiendo tus propios circuitos de codificación. Aquí mostramos una amplia variedad de estrategias de codificación posibles simplemente para que puedas compararlas y ver qué es factible. Hay algunas afirmaciones muy generales que pueden hacerse sobre la utilidad de las técnicas de codificación. Por ejemplo, efficient_su2 (ver más abajo) con un esquema de entrelazamiento completo es mucho más probable que capture características cuánticas de los datos que los métodos que producen estados producto (como z_feature_map). Pero esto no significa que efficient_su2 sea suficiente, ni que esté lo bastante bien ajustado a tu conjunto de datos, para producir una aceleración cuántica. Eso requiere una consideración cuidadosa de la estructura de los datos que se modelan o clasifican. También hay un equilibrio con la profundidad del circuito, ya que muchos mapeos de características que entrelazan completamente los qubits en un circuito producen circuitos muy profundos, demasiado profundos para obtener resultados útiles en los ordenadores cuánticos actuales.

Notación

Un conjunto de datos es un conjunto de MM vectores de datos: X={x(j)j[M]}\text{X} = \{\vec{x}^{(j)}\,|\,j\in [M]\}, donde cada vector tiene NN dimensiones, es decir, x(j)=(x1(j),,xN(j))RN\vec{x}^{(j)}=(\vec{x}^{(j)}_1,\ldots,\vec{x}^{(j)}_N)\in\mathbb{R}^N. Esto podría extenderse a características de datos complejas. En esta lección usaremos en ocasiones estas notaciones para el conjunto completo (X)(\text{X}) y para sus elementos específicos como x(j)\vec{x}^{(j)}. Sin embargo, la mayor parte del tiempo nos referiremos a la carga de un único vector de nuestro conjunto de datos a la vez, y con frecuencia haremos referencia simplemente a un único vector de NN características como x\vec{x}.

Además, es habitual usar el símbolo Φ(x)\Phi(\vec{x}) para referirse al mapeo de características Φ\Phi del vector de datos x\vec{x}. En computación cuántica en concreto, es común referirse a los mapeos mediante U(x)U(\vec{x}), una notación que refuerza la naturaleza unitaria de estas operaciones. Se podría usar el mismo símbolo para ambos; los dos son mapeos de características. A lo largo de este curso tendemos a usar:

  • Φ(x)\Phi(\vec{x}) cuando hablamos de mapeos de características en aprendizaje automático en general, y
  • U(x)U(\vec{x}) cuando hablamos de implementaciones en circuito de los mapeos de características.

Normalización y pérdida de información

En el aprendizaje automático clásico, las características de los datos de entrenamiento se "normalizan" o reescalan con frecuencia, lo que suele mejorar el rendimiento del modelo. Una forma habitual de hacerlo es la normalización mín-máx o la estandarización. En la normalización mín-máx, las columnas de características de la matriz de datos X\text{X} (digamos, la característica kk) se normalizan:

xk(i)=xk(i)min{xk(j)x(j)[X]}max{xk(j)x(j)[X]}min{xk(j)x(j)[X]}x^{'(i)}_k = \frac{x^{(i)}_k - \text{min}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}}{\text{max}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}-\text{min}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}}

donde min y max se refieren al mínimo y máximo de la característica kk sobre los MM vectores de datos del conjunto X\text{X}. Todos los valores de las características caen entonces en el intervalo unitario: xk(i)[0,1]x^{'(i)}_k \in [0,1] para todo i[M]i\in [M], k[N]k\in[N].

La normalización es también un concepto fundamental en mecánica cuántica y computación cuántica, aunque es ligeramente diferente a la normalización mín-máx. La normalización en mecánica cuántica exige que la longitud (en el contexto de la computación cuántica, la norma-2) de un vector de estado ψ|\psi\rangle sea igual a la unidad: ψ=ψψ=1\|\psi\|=\sqrt{\langle\psi|\psi\rangle} = 1, lo que garantiza que las probabilidades de medición sumen 1. El estado se normaliza dividiéndolo por la norma-2; es decir, reescalando

ψψ1ψ|\psi\rangle\rightarrow\|\psi\|^{-1}|\psi\rangle

En computación cuántica y mecánica cuántica, esta no es una normalización que la gente imponga sobre los datos, sino una propiedad fundamental de los estados cuánticos. Dependiendo de tu esquema de codificación, esta restricción puede afectar a cómo se reescalan los datos. Por ejemplo, en la codificación por amplitud (ver más abajo), el vector de datos se normaliza x(j)=1\vert\vec{x}^{(j)}\vert = 1 como exige la mecánica cuántica, y esto afecta al escalado de los datos que se codifican. En la codificación por fase, se recomienda reescalar los valores de las características como xi(j)(0,2π]\vec{x}^{(j)}_i \in (0,2\pi] para evitar pérdida de información debida al efecto módulo-2π2\pi de codificar en un ángulo de fase de qubit[1,2].

Métodos de codificación

En las siguientes secciones haremos referencia a un pequeño conjunto de datos clásico de ejemplo Xex\text{X}_\text{ex} compuesto por M=5M=5 vectores de datos, cada uno con N=3N=3 características:

Xex={(4,8,5),(9,8,6),(2,9,2),(5,7,0),(3,7,5)}\text{X}_{\text{ex}}=\{(4,8,5),(9,8,6),(2,9,2),(5,7,0),(3,7,5)\}

Con la notación introducida anteriormente, podríamos decir que la 1ra1^\text{ra} característica del 4to4^\text{to} vector de datos de nuestro conjunto Xex\text{X}_{\text{ex}} es x1(4)=5\vec{x}^{(4)}_1 = 5, por ejemplo.

Codificación en base

La codificación en base codifica una cadena clásica de PP bits en un estado base computacional de un sistema de PP qubits. Tomemos por ejemplo x3(1)=5=0(23)+1(22)+0(21)+1(20).\vec{x}^{(1)}_3 = 5 = 0(2^3)+1(2^2)+0(2^1)+1(2^0). Esto puede representarse como una cadena de 44 bits como (0101)(0101), y mediante un sistema de 44 qubits como el estado cuántico 0101|0101\rangle. De forma más general, para una cadena de PP bits: xk(j)=(b1,b2,...,bP)\vec{x}^{(j)}_k = (b_1, b_2, ... , b_P), el estado de PP qubits correspondiente es xk(j)=b1,b2,...,bP|x^{(j)}_k\rangle = | b_1, b_2, ... , b_P \rangle con bn{0,1}b_n \in \{0,1\} para n=1,,Pn = 1 , \dots , P. Hay que tener en cuenta que esto es solo para una única característica.

La codificación en base en computación cuántica representa cada bit clásico como un qubit separado, mapeando la representación binaria de los datos directamente sobre los estados cuánticos en la base computacional. Cuando es necesario codificar varias características, cada una se convierte primero a su forma binaria y luego se asigna a un grupo distinto de qubits —un grupo por característica— donde cada qubit refleja un bit de la representación binaria de esa característica.

Como ejemplo, vamos a codificar el vector (5, 7, 0).

Supongamos que todas las características se almacenan en cuatro bits (más de lo necesario, pero suficiente para representar cualquier entero de un solo dígito en base 10):

5 → binario 0101

7 → binario 0111

0 → binario 0000

Estas cadenas de bits se asignan a tres conjuntos de cuatro qubits, por lo que el estado base global de 12 qubits es:

010101110000∣0101 0111 0000⟩

Aquí, los primeros cuatro qubits representan la primera característica, los cuatro siguientes la segunda característica, y los cuatro últimos la tercera característica. El código siguiente convierte el vector de datos (5,7,0) en un estado cuántico, y está generalizado para hacer lo mismo con otras características de un solo dígito.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit
from qiskit import QuantumCircuit

# Data point to encode
x = 5 # binary: 0101
y = 7 # binary: 0111
z = 0 # binary: 0000

# Convert each to 4-bit binary list
x_bits = [int(b) for b in format(x, "04b")] # [0,1,0,1]
y_bits = [int(b) for b in format(y, "04b")] # [0,1,1,1]
z_bits = [int(b) for b in format(z, "04b")] # [0,0,0,0]

# Combine all bits
all_bits = x_bits + y_bits + z_bits # [0,1,0,1,0,1,1,1,0,0,0,0]

# Initialize a 12-qubit quantum circuit
qc = QuantumCircuit(12)

# Apply x-gates where the bit is 1
for idx, bit in enumerate(all_bits):
if bit == 1:
qc.x(idx)

qc.draw("mpl")

Output of the previous code cell

Comprueba tu comprensión

Lee la pregunta a continuación, piensa tu respuesta y luego haz clic en el triángulo para ver la solución.

Escribe código para codificar el primer vector de nuestro conjunto de datos de ejemplo Xex\text{X}_{\text{ex}}:

x(1)=(4,8,5)\vec{x}^{(1)}=(4,8,5)

usando codificación en base.

Respuesta:

import math
from qiskit import QuantumCircuit

# Data point to encode
x = 4 # binary: 0100
y = 8 # binary: 1000
z = 5 # binary: 0101

# Convert each to 4-bit binary list
x_bits = [int(b) for b in format(x, '04b')] # [0,1,0,0]
y_bits = [int(b) for b in format(y, '04b')] # [1,0,0,0]
z_bits = [int(b) for b in format(z, '04b')] # [0,1,0,1]

# Combine all bits
all_bits = x_bits + y_bits + z_bits # [0,1,0,0,1,0,0,0,0,1,0,1]

# Initialize a 12-qubit quantum circuit
qc = QuantumCircuit(12)

# Apply x-gates where the bit is 1
for idx, bit in enumerate(all_bits):
if bit == 1:
qc.x(idx)

qc.draw('mpl')

Codificación por amplitud

La codificación por amplitud codifica los datos en las amplitudes de un estado cuántico. Representa un vector de datos clásico normalizado de NN dimensiones, x(j)\vec{x}^{(j)}, como las amplitudes de un estado cuántico de nn qubits, ψx|\psi_x\rangle:

ψx(j)=1αi=1Nxi(j)i|\psi^{(j)}_x\rangle = \frac{1}{\alpha}\sum_{i=1}^N x^{(j)}_i |i\rangle

donde NN es la misma dimensión de los vectores de datos que antes, xi(j)\vec{x}^{(j)}_i es el imoi^\text{mo} elemento de x(j)\vec{x}^{(j)} y i|i\rangle es el imoi^\text{mo} estado base computacional. Aquí, α\alpha es una constante de normalización que se determina a partir de los datos que se codifican. Esta es la condición de normalización impuesta por la mecánica cuántica:

i=1Nxi(j)2=α2.\sum_{i=1}^N \left|x^{(j)}_i\right|^2 = \left|\alpha\right|^2.

En general, esta es una condición distinta a la normalización mín/máx usada para cada característica sobre todos los vectores de datos. La forma exacta de gestionarlo dependerá de tu problema. Pero no hay manera de evitar la condición de normalización de la mecánica cuántica anterior.

En la codificación por amplitud, cada característica de un vector de datos se almacena como la amplitud de un estado cuántico diferente. Como un sistema de nn qubits proporciona 2n2^n amplitudes, la codificación por amplitud de NN características requiere nlog2(N)n \ge \mathrm{log}_2(N) qubits.

Como ejemplo, vamos a codificar el primer vector de nuestro conjunto de datos de ejemplo Xex\text{X}_\text{ex}, x(1)=(4,8,5)\vec{x}^{(1)} = (4,8,5), usando codificación por amplitud. Al normalizar el vector resultante obtenemos:

i=1Nxi(1)2=42+82+52=105=α2α=105\sum_{i=1}^N \left|x^{(1)}_i\right|^2 = 4^2+8^2+5^2 = 105 = \left|\alpha\right|^2 \rightarrow \alpha = \sqrt{105}

y el estado cuántico de 2 qubits resultante sería:

ψ(x(1))=1105(400+801+510+011)|\psi(\vec{x}^{(1)})\rangle = \frac{1}{\sqrt{105}}(4|00\rangle+8|01\rangle+5|10\rangle+0|11\rangle)

En el ejemplo anterior, el número de características del vector N=3N=3 no es una potencia de 2. Cuando NN no es una potencia de 2, simplemente elegimos un valor para el número de qubits nn tal que 2nN2^n\geq N y rellenamos el vector de amplitudes con constantes no informativas (aquí, un cero).

Al igual que en la codificación en base, una vez que calculamos qué estado codificará nuestro conjunto de datos, en Qiskit podemos usar la función initialize para prepararlo:

import math

desired_state = [
1 / math.sqrt(105) * 4,
1 / math.sqrt(105) * 8,
1 / math.sqrt(105) * 5,
1 / math.sqrt(105) * 0,
]

qc = QuantumCircuit(2)
qc.initialize(desired_state, [0, 1])

qc.decompose(reps=5).draw(output="mpl")

Output of the previous code cell

Una ventaja de la codificación por amplitud es el requisito antes mencionado de solo log2(N)\mathrm{log}_2(N) qubits para la codificación. Sin embargo, los algoritmos posteriores deben operar sobre las amplitudes de un estado cuántico, y los métodos para preparar y medir los estados cuánticos tienden a no ser eficientes.

Comprueba tu comprensión

Lee las preguntas a continuación, piensa tus respuestas y luego haz clic en los triángulos para ver las soluciones.

Escribe el estado normalizado para codificar el siguiente vector (formado por dos vectores de nuestro conjunto de datos de ejemplo):

x=(9,8,6,2,9,2)\vec{x}=(9,8,6,2,9,2)

usando codificación por amplitud.

Respuesta:

Para codificar 6 números necesitaremos tener al menos 6 estados disponibles en cuyas amplitudes podamos codificar. Esto requerirá 3 qubits. Usando un factor de normalización desconocido α\alpha, podemos escribirlo como:

ψ=α(9000+8001+6010+2011+9100+2101+0110+0111)|\psi\rangle = \alpha(9|000\rangle+8|001\rangle+6|010\rangle+2|011\rangle+9|100\rangle+2|101\rangle+0|110\rangle+0|111\rangle)

Nótese que

ψψ=α2×(92+82+62+22+92+22+02+02)=α2×(270)=1α=1270\langle \psi|\psi\rangle = |\alpha|^2\times(9^2+8^2+6^2+2^2+9^2+2^2+0^2+0^2) = |\alpha|^2\times(270)=1 \rightarrow \alpha = \frac{1}{\sqrt{270}}

Entonces, finalmente,

ψ=1270(9000+8001+6010+2011+9100+2101+0110+0111)|\psi\rangle = \frac{1}{\sqrt{270}}(9|000\rangle+8|001\rangle+6|010\rangle+2|011\rangle+9|100\rangle+2|101\rangle+0|110\rangle+0|111\rangle)

Para el mismo vector de datos x=(9,8,6,2,9,2),\vec{x}=(9,8,6,2,9,2), escribe código para crear un circuito que cargue estas características de datos usando codificación por amplitud.

Respuesta:

desired_state = [
9 / math.sqrt(270),
8 / math.sqrt(270),
6 / math.sqrt(270),
2 / math.sqrt(270),
9 / math.sqrt(270),
2 / math.sqrt(270),
0,
0,
]

print(desired_state)

qc = QuantumCircuit(3)
qc.initialize(desired_state, [0, 1, 2])
qc.decompose(reps=8).draw(output="mpl")

[0.5477225575051662, 0.48686449556014766, 0.36514837167011077, 0.12171612389003691, 0.5477225575051662, 0.12171612389003691, 0, 0]

"Output of the previous code cell"

Es posible que tengas que trabajar con vectores de datos muy grandes. Considera el vector

x=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5).\vec{x}=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5).

Escribe código para automatizar la normalización y genera un circuito cuántico para la codificación por amplitud.

Respuesta:

Hay muchas respuestas posibles. Aquí hay un código que imprime algunos pasos intermedios:

import numpy as np
from math import sqrt

init_list = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0, 3, 7, 5]
qubits = round(np.log(len(init_list)) / np.log(2) + 0.4999999999)
need_length = 2**qubits
pad = need_length - len(init_list)
for i in range(0, pad):
init_list.append(0)

init_array = np.array(init_list) # Unnormalized data vector
length = sqrt(
sum(init_array[i] ** 2 for i in range(0, len(init_array)))
) # Vector length
norm_array = init_array / length # Normalized array
print("Normalized array:")
print(norm_array)
print()

qubit_numbers = []
for i in range(0, qubits):
qubit_numbers.append(i)
print(qubit_numbers)

qc = QuantumCircuit(qubits)
qc.initialize(norm_array, qubit_numbers)
qc.decompose(reps=7).draw(output="mpl")

Normalized array: [0.17342199 0.34684399 0.21677749 0.39019949 0.34684399 0.26013299 0.086711 0.39019949 0.086711 0.21677749 0.30348849 0. 0.1300665 0.30348849 0.21677749 0. ]

[0, 1, 2, 3]

"Output of the previous code cell"

¿Ves ventajas de la codificación por amplitud sobre la codificación en base? Si es así, explícalas.

Respuesta:

Puede haber varias respuestas. Una de ellas es que, dado el orden fijo de los estados base, esta codificación por amplitud preserva el orden de los números codificados. Además, con frecuencia se codificará de forma más densa.

Una ventaja de la codificación por amplitud es que solo se necesitan log2(N)\log_2(N) qubits para un vector de datos xx\vec{x}\rightarrow|\vec{x}\rangle de NN dimensiones (NN características). Sin embargo, la codificación por amplitud es en general un procedimiento ineficiente que requiere preparación de estados arbitrarios, lo cual es exponencial en el número de puertas CNOT. Dicho de otra forma, la preparación del estado tiene una complejidad temporal polinomial de O(N)\mathcal O(N) en el número de dimensiones, donde N=2nN = 2^n y nn es el número de qubits. La codificación por amplitud "ofrece un ahorro exponencial en espacio a costa de un incremento exponencial en tiempo"[3]; sin embargo, es posible reducir la complejidad temporal a O(logN)\mathcal O(\log N) en ciertos casos[4]. Para una aceleración cuántica de extremo a extremo, es necesario tener en cuenta la complejidad temporal de la carga de datos.

Codificación por ángulo

La codificación por ángulo es de interés en muchos modelos QML que usan mapeos de características de Pauli, como las máquinas de vectores de soporte cuánticas (QSVM) y los circuitos cuánticos variacionales (VQC), entre otros. La codificación por ángulo está estrechamente relacionada con la codificación por fase y la codificación densa por ángulo, que se presentan más abajo. Aquí usaremos "codificación por ángulo" para referirnos a una rotación en θ\theta, es decir, una rotación que se aleja del eje zz conseguida, por ejemplo, mediante una puerta RXR_X o una puerta RYR_Y[1,3]. En realidad, se pueden codificar datos en cualquier rotación o combinación de rotaciones. Pero RYR_Y es común en la literatura, por lo que la enfatizamos aquí.

Cuando se aplica a un único qubit, la codificación por ángulo imparte una rotación en el eje Y proporcional al valor del dato. Considera la codificación de una única característica (la kmak^\text{ma}) del jmoj^\text{mo} vector de datos de un conjunto de datos, xk(j)\vec{x}^{(j)}_k:

xk(j)=RY(θ=xk(j))0=cos(xk(j)2)0+sin(xk(j)2)1.|\vec{x}^{(j)}_k\rangle = R_Y(\theta=\vec{x}^{(j)}_k)|0\rangle = \textstyle\cos\left(\frac{\vec{x}^{(j)}_k}{2}\right)|0\rangle + \sin\left(\frac{\vec{x}^{(j)}_k}{2}\right)|1\rangle.

Alternativamente, la codificación por ángulo puede realizarse usando puertas RX(θ)R_X(\theta), aunque el estado codificado tendría una fase relativa compleja en comparación con RY(θ)R_Y(\theta).

La codificación por ángulo difiere de los dos métodos anteriores en varios aspectos. En la codificación por ángulo:

  • Cada valor de característica se mapea a un qubit correspondiente, xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k, dejando los qubits en un estado producto.
  • Se codifica un valor numérico a la vez, en lugar de un conjunto completo de características de un punto de datos.
  • Se requieren nn qubits para NN características de datos, donde nNn\leq N. Aquí la igualdad suele cumplirse. Veremos cómo es posible que n<Nn<N en las próximas secciones.
  • El circuito cuántico resultante tiene profundidad constante (típicamente la profundidad es 1 antes de la transpilación).

La profundidad constante del circuito cuántico lo hace especialmente adecuado para el hardware cuántico actual. Una característica adicional de codificar nuestros datos usando θ\theta (y específicamente, nuestra elección de usar codificación por ángulo en el eje Y) es que crea estados cuánticos de valor real que pueden ser útiles para ciertas aplicaciones. Para la rotación en el eje Y, los datos se mapean con una puerta de rotación en el eje Y RY(θ)R_Y(\theta) con un ángulo real θ(0,2π]\theta \in (0, 2\pi] (Qiskit RYGate). Al igual que con la codificación por fase (ver más abajo), recomendamos reescalar los datos de modo que xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi], evitando pérdida de información y otros efectos no deseados.

El siguiente código de Qiskit rota un único qubit desde un estado inicial 0|0\rangle para codificar un valor de dato xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi.

from qiskit.quantum_info import Statevector
from math import pi

qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(pi / 2, 0) # Phase gate rotates by an angle pi/2
state2 = Statevector.from_instruction(qc)
states = state1, state2

Definiremos una función para visualizar la acción sobre el vector de estado. Los detalles de la definición de la función no son importantes, pero sí lo es la capacidad de visualizar los vectores de estado y sus cambios.

import numpy as np
from qiskit.visualization.bloch import Bloch
from qiskit.visualization.state_visualization import _bloch_multivector_data

def plot_Nstates(states, axis, plot_trace_points=True):
"""This function plots N states to 1 Bloch sphere"""
bloch_vecs = [_bloch_multivector_data(s)[0] for s in states]

if axis is None:
bloch_plot = Bloch()
else:
bloch_plot = Bloch(axes=axis)

bloch_plot.add_vectors(bloch_vecs)

if len(states) > 1:

def rgba_map(x, num):
g = (0.95 - 0.05) / (num - 1)
i = 0.95 - g * num
y = g * x + i
return (0.0, y, 0.0, 0.7)

num = len(states)
bloch_plot.vector_color = [rgba_map(x, num) for x in range(1, num + 1)]

bloch_plot.vector_width = 3
bloch_plot.vector_style = "simple"

if plot_trace_points:

def trace_points(bloch_vec1, bloch_vec2):
# bloch_vec = (x,y,z)
n_points = 15
thetas = np.arccos([bloch_vec1[2], bloch_vec2[2]])
phis = np.arctan2(
[bloch_vec1[1], bloch_vec2[1]], [bloch_vec1[0], bloch_vec2[0]]
)
if phis[1] < 0:
phis[1] = phis[1] + 2 * pi
angles0 = np.linspace(phis[0], phis[1], n_points)
angles1 = np.linspace(thetas[0], thetas[1], n_points)

xp = np.cos(angles0) * np.sin(angles1)
yp = np.sin(angles0) * np.sin(angles1)
zp = np.cos(angles1)
pnts = [xp, yp, zp]
bloch_plot.add_points(pnts)
bloch_plot.point_color = "k"
bloch_plot.point_size = [4] * len(bloch_plot.points)
bloch_plot.point_marker = ["o"]

for i in range(len(bloch_vecs) - 1):
trace_points(bloch_vecs[i], bloch_vecs[i + 1])

bloch_plot.sphere_alpha = 0.05
bloch_plot.frame_alpha = 0.15
bloch_plot.figsize = [4, 4]

bloch_plot.render()

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

Eso fue solo una característica de un único vector de datos. Cuando se codifican NN características en los ángulos de rotación de nn qubits, digamos para el jmoj^\text{mo} vector de datos x(j)=(x1,...,xN),\vec{x}^{(j)} = (x_1,...,x_N), el estado producto codificado tendrá este aspecto:

x(j)=k=1Ncos(xk(j))0+sin(xk(j))1|\vec{x}^{(j)}\rangle = \bigotimes^N_{k=1} \cos(\vec{x}^{(j)}_k)|0\rangle + \sin(\vec{x}^{(j)}_k)|1\rangle

Notamos que esto es equivalente a

x(j)=k=1NRY(2xk(j))0.|\vec{x}^{(j)}\rangle = \bigotimes^N_{k=1} R_Y(2\vec{x}^{(j)}_k)|0\rangle.

Comprueba tu comprensión

Lee las preguntas a continuación, piensa tus respuestas y luego haz clic en los triángulos para ver las soluciones.

Codifica el vector de datos x=(0,π/4,π/2)\vec{x} = (0, \pi/4, \pi/2) usando codificación por ángulo, tal como se describió anteriormente.

Respuesta:

qc = QuantumCircuit(3)
qc.ry(0, 0)
qc.ry(2 * math.pi / 4, 1)
qc.ry(2 * math.pi / 2, 2)
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Con la codificación por ángulo descrita anteriormente, ¿cuántos qubits se necesitan para codificar 5 características?

Respuesta: 5

Codificación por fase

La codificación por fase es muy similar a la codificación por ángulo descrita anteriormente. El ángulo de fase de un qubit es un ángulo real ϕ\phi alrededor del eje zz desde el eje +x+x. Los datos se mapean con una rotación de fase, P(ϕ)=eiϕ/2RZ(ϕ)P(\phi) = e^{i\phi/2}R_Z(\phi), donde ϕ(0,2π]\phi \in (0,2\pi] (ver Qiskit PhaseGate para más información). Se recomienda reescalar los datos de modo que xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi]. Esto evita pérdida de información y otros efectos potencialmente no deseados[1,2].

Un qubit se inicializa habitualmente en el estado 0|0\rangle, que es un autoestado del operador de rotación de fase, lo que significa que el estado del qubit primero necesita rotarse para que la codificación por fase pueda implementarse. Por tanto, tiene sentido inicializar el estado con una puerta Hadamard: H0=+=12(0+1)H|0\rangle = |+\rangle = \textstyle\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle). La codificación por fase en un único qubit consiste en imprimir una fase relativa proporcional al valor del dato:

xk(j)=P(ϕ=xk(j))+=12(0+eixk(j)1).|\vec{x}^{(j)}_k\rangle = P(\phi=\vec{x}^{(j)}_k)|+\rangle = \textstyle\frac{1}{\sqrt{2}}\big(|0\rangle + e^{i\vec{x}^{(j)}_k}|1\rangle\big).

El procedimiento de codificación por fase mapea cada valor de característica a la fase del qubit correspondiente, xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k. En total, la codificación por fase tiene una profundidad de circuito de 2, incluyendo la capa Hadamard, lo que la convierte en un esquema de codificación eficiente. El estado de múltiples qubits codificado por fase (nn qubits para N=nN=n características) es un estado producto:

x(j)=k=1NPk(ϕ=xk(j))+N=12Nk=1N(0+eixk(j)1).|\vec{x}^{(j)}\rangle = \bigotimes_{k=1}^{N} P_k(\phi = \vec{x}^{(j)}_k)|+\rangle^{\otimes N} = {\textstyle\frac{1}{\sqrt{2^N}}} \bigotimes_{k=1}^{N}\big(|0\rangle + e^{i\vec{x}^{(j)}_k}|1\rangle\big).

El siguiente código de Qiskit primero prepara el estado inicial de un único qubit rotándolo con una puerta Hadamard, y luego lo rota de nuevo usando una puerta de fase para codificar una característica de dato xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi.

qc = QuantumCircuit(1)
qc.h(0) # Hadamard gate rotates state down to Bloch equator
state1 = Statevector.from_instruction(qc)

qc.p(pi / 2, 0) # Phase gate rotates by an angle pi/2
state2 = Statevector.from_instruction(qc)

states = state1, state2

qc.draw("mpl", scale=1)

Output of the previous code cell

Podemos visualizar la rotación en ϕ\phi usando la función plot_Nstates que definimos.

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

El gráfico de la esfera de Bloch muestra la rotación en el eje Z +P(12π)+|+\rangle \rightarrow P(\frac{1}{2}\pi)|+\rangle donde xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi. La flecha verde claro muestra el estado final.

La codificación por fase se usa en muchos mapeos de características cuánticos, en particular los mapeos de características ZZ y ZZZZ, y los mapeos de características de Pauli en general, entre otros.

Comprueba tu comprensión

Lee las preguntas a continuación, piensa tus respuestas y luego haz clic en los triángulos para ver las soluciones.

¿Cuántos qubits se necesitan para usar la codificación por fase descrita anteriormente y almacenar 8 características?

Respuesta: 8

Escribe código para el vector x(1)=(4,8,5,9,8,6,2,9,2,5,7,0)\vec{x}^{(1)}=(4,8,5,9,8,6,2,9,2,5,7,0) usando codificación por fase.

Respuesta:

Puede haber muchas respuestas. Aquí hay un ejemplo:

phase_data = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0]
qc = QuantumCircuit(len(phase_data))
for i in range(0, len(phase_data)):
qc.h(i)
qc.rz(phase_data[i] * 2 * math.pi / float(max(phase_data)), i)
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Codificación densa por ángulo

La codificación densa por ángulo (DAE, por sus siglas en inglés) es una combinación de codificación por ángulo y codificación por fase. La DAE permite codificar dos valores de características en un único qubit: uno con un ángulo de rotación en el eje Y, y el otro con un ángulo de rotación en el eje zz: xk(j),\vec{x}^{(j)}_k, x(j)θ,ϕ\vec{x}^{(j)}_\ell \rightarrow \theta, \phi. Codifica dos características de la siguiente manera:

xk(j),x(j)=RZ(ϕ=x(j))RY(θ=xk(j))0=cos(xk(j)2)0+eix(j)sin(xk(j)2)1.|\vec{x}^{(j)}_k,\vec{x}^{(j)}_\ell\rangle = R_Z(\phi=\vec{x}^{(j)}_\ell) R_Y(\theta=\vec{x}^{(j)}_k)|0\rangle = \cos\left(\frac{\vec{x}^{(j)}_k}{2}\right)|0\rangle + e^{i\vec{x}^{(j)}_\ell} \sin\left(\frac{\vec{x}^{(j)}_k}{2}\right)|1\rangle.

Codificar dos características de datos en un qubit da como resultado una reducción de 2×2\times en el número de qubits necesarios para la codificación. Extendiendo esto a más características, el vector de datos x=(x1,...,xN)\vec{x} = (x_1,...,x_N) puede codificarse como:

x=k=1N/2cos(x2k1)0+eix2ksin(x2k1)1|\vec{x}\rangle = \bigotimes_{k=1}^{N/2} \cos(x_{2k-1})|0\rangle + e^{i x_{2k}}\sin(x_{2k-1})|1\rangle

La DAE puede generalizarse a funciones arbitrarias de las dos características en lugar de las funciones sinusoidales usadas aquí. Esto se denomina codificación general de qubit[7].

Como ejemplo de DAE, el código siguiente codifica y visualiza la codificación de las características x1=θ=3π/8x_1=\theta = 3\pi/8 y x2=ϕ=7π/4x_2=\phi = 7\pi/4.

qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(3 * pi / 8, 0)
state2 = Statevector.from_instruction(qc)
qc.rz(7 * pi / 4, 0)
state3 = Statevector.from_instruction(qc)
states = state1, state2, state3

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

Comprueba tu comprensión

Lee las preguntas a continuación, piensa tus respuestas y luego haz clic en los triángulos para ver las soluciones.

Según el tratamiento anterior, ¿cuántos qubits se necesitan para codificar 6 características usando codificación densa?

Respuesta: 3

Escribe código para cargar el vector x(1)=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5)\vec{x}^{(1)}=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5) usando codificación densa por ángulo.

Respuesta:

Nota que hemos rellenado la lista con un "0" para evitar el problema de tener un único parámetro sin usar en nuestro esquema de codificación.

dense_data = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0, 3, 7, 5, 0]
qc = QuantumCircuit(int(len(dense_data) / 2))
entry = 0
for i in range(0, int(len(dense_data) / 2)):
qc.ry(dense_data[entry] * 2 * math.pi / float(max(dense_data)), i)
entry = entry + 1
qc.rz(dense_data[entry] * 2 * math.pi / float(max(dense_data)), i)
entry = entry + 1
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Codificación con mapas de características incorporados

Codificación en puntos arbitrarios

La codificación por ángulo, la codificación por fase y la codificación densa preparaban estados producto con una característica codificada en cada qubit (o dos características por qubit). Esto es diferente de la codificación en base y la codificación en amplitud, ya que esos métodos hacen uso de estados entrelazados. No existe una correspondencia 1:1 entre una característica del dato y un qubit. En la codificación en amplitud, por ejemplo, una característica puede ser la amplitud del estado 01|01\rangle y otra característica puede ser la amplitud del estado 10|10\rangle. En general, los métodos que codifican en estados producto producen circuitos más superficiales y pueden almacenar 1 o 2 características en cada qubit. Los métodos que usan entrelazamiento y asocian una característica con un estado en lugar de con un qubit producen circuitos más profundos y pueden almacenar más características por qubit en promedio.

Sin embargo, la codificación no tiene que estar completamente en estados producto ni completamente en estados entrelazados como en la codificación en amplitud. De hecho, muchos esquemas de codificación incorporados en Qiskit permiten codificar tanto antes como después de una capa de entrelazamiento, en lugar de solo al principio. Esto se conoce como "re-carga de datos" (data reuploading). Para trabajo relacionado, consulta las referencias [5] y [6].

En esta sección, utilizaremos y visualizaremos algunos de los esquemas de codificación incorporados. Todos los métodos de esta sección codifican NN características como rotaciones en NN compuertas parametrizadas sobre nn qubits, donde nNn \leq N. Ten en cuenta que maximizar la carga de datos para un número dado de qubits no es la única consideración. En muchos casos, la profundidad del circuito puede ser incluso más importante que la cantidad de qubits.

SU2 eficiente

Un ejemplo común y útil de codificación con entrelazamiento es el circuito efficient_su2 de Qiskit. De manera impresionante, este circuito puede, por ejemplo, codificar 8 características en solo 2 qubits. Veamos esto y luego tratemos de entender cómo es posible.

from qiskit.circuit.library import efficient_su2

circuit = efficient_su2(num_qubits=2, reps=1, insert_barriers=True)
circuit.decompose().draw(output="mpl")

Salida de la celda de código anterior

Al escribir nuestro estado, usaremos la convención de Qiskit en la que los qubits menos significativos se ordenan al extremo derecho, como en q2,q1,q0|q_2,q_1,q_0\rangle o q2q1q0.|q_2\rangle\otimes|q_1\rangle\otimes|q_0\rangle. Estos estados pueden volverse muy complicados rápidamente, y este raro ejemplo puede ayudar a explicar por qué dichos estados rara vez se escriben explícitamente.

Nuestro sistema comienza en el estado 00.|00\rangle. Hasta la primera barrera (un punto que etiquetamos b1b1), nuestros estados son:

ψb1=(cos(θ12)0+sin(θ12)eiθ31)(cos(θ02)0+sin(θ02)eiθ21)|\psi\rangle_{b1} = \left(\cos\left(\frac{\theta_1}{2}\right)|0\rangle+\sin\left(\frac{\theta_1}{2}\right)e^{i\theta_3}|1\rangle\right)\otimes\left(\cos\left(\frac{\theta_0}{2}\right)|0\rangle+\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}|1\rangle\right)

Eso es simplemente codificación densa, que ya hemos visto antes. Ahora, después de la compuerta CNOT, en la segunda barrera (b2b2), nuestro estado es

ψb2=cos(θ12)cos(θ02)00+cos(θ12)sin(θ02)eiθ211+sin(θ12)cos(θ02)eiθ310+sin(θ12)sin(θ02)eiθ2eiθ301\begin{aligned} |\psi\rangle_{b2} = & \cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_0}{2}\right)|00\rangle+\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}|11\rangle\\ + & \sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_0}{2}\right)e^{i\theta_3}|10\rangle+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}e^{i\theta_3}|01\rangle \end{aligned}

Ahora aplicamos el último conjunto de rotaciones de un solo qubit y agrupamos los estados semejantes para obtener:

ψfinal=[cos(θ02)(cos(θ12)cos(θ52)sin(θ12)sin(θ52)eiθ3)cos(θ42)+sin(θ02)(cos(θ12)sin(θ52)sin(θ12)cos(θ52)eiθ3)sin(θ42)eiθ2]00+[cos(θ02)(cos(θ12)cos(θ52)sin(θ12)sin(θ52)eiθ3)sin(θ42)+sin(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)cos(θ42)eiθ2]eiθ601+[cos(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)cos(θ42)sin(θ02)(cos(θ12)cos(θ52)+sin(θ12)sin(θ52)eiθ3)sin(θ42)eiθ2]eiθ710+[cos(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)sin(θ42)+sin(θ02)(cos(θ12)cos(θ52)+sin(θ12)sin(θ52)eiθ3)cos(θ42)eiθ2]eiθ6eiθ711\begin{align*} |\psi\rangle_{\text{final}} = & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] |00\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(-\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_6}|01\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)\right.\\ - & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_7}|10\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_6}e^{i\theta_7}|11\rangle \end{align*}

Esto probablemente resulte demasiado complejo de analizar. En cambio, simplemente retrocede y piensa en cuántos parámetros hemos cargado en el estado: ocho. Pero tenemos solo cuatro estados de base computacional. A primera vista, puede parecer que hemos cargado más parámetros de lo que tiene sentido, ya que el estado final puede escribirse como ψfinal=c000+c101+c210+c311\psi_\text{final} = c_0|00\rangle+c_1|01\rangle+c_2|10\rangle+c_3|11\rangle. Sin embargo, ten en cuenta que ¡cada prefactor es complejo! Escrito así:

ψfinal=(a0+ib0)00+(a1+ib1)01+(a2+ib2)10+(a3+ib3)11\psi_\text{final} = (a_0+ib_0)|00\rangle+(a_1+ib_1)|01\rangle+(a_2+ib_2)|10\rangle+(a_3+ib_3)|11\rangle

Se puede ver que sí tenemos ocho parámetros en el estado sobre los cuales codificar nuestras ocho características.

Al aumentar el número de qubits e incrementar el número de repeticiones de capas de entrelazamiento y rotación, se pueden codificar muchos más datos. Escribir las funciones de onda se vuelve rápidamente intratable. Pero aún podemos ver la codificación en acción. Aquí codificamos el vector de datos x\vec{x} con 12 características en un circuito efficient_su2 de 3 qubits, usando cada una de las compuertas parametrizadas para codificar una característica diferente.

x=(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.2)\vec{x} = (0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.2)

En este vector de datos, las características se presentan en un orden particular. De forma aislada, no importa si se codifican en este orden o en el inverso. Lo importante es llevar el registro y ser consistente. Observa en el diagrama del circuito que efficient_su2 asume un cierto orden de codificación; específicamente, llena la primera capa de compuertas parametrizadas del qubit 0 al qubit 2, y luego pasa a la siguiente capa. Esto no es consistente ni inconsistente con la notación little-endian, ya que aquí las características de los datos no pueden ordenarse por qubit a priori, antes de especificar un circuito de codificación.

x = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2]
circuit = efficient_su2(num_qubits=3, reps=1, insert_barriers=True)
encode = circuit.assign_parameters(x)
encode.decompose().draw(output="mpl")

Salida de la celda de código anterior

En lugar de aumentar el número de qubits, podrías optar por incrementar el número de repeticiones de capas de entrelazamiento y rotación. Sin embargo, hay límites en cuántas repeticiones son útiles. Como se menciónó antes, existe una compensación: los circuitos con más qubits o más repeticiones de capas de entrelazamiento y rotación pueden almacenar más parámetros, pero lo hacen con una mayor profundidad de circuito. Volveremos a las profundidades de algunos mapas de características incorporados más adelante. Los siguientes métodos de codificación incorporados en Qiskit tienen "mapa de características" como parte de sus nombres. Recalquemos que codificar datos en un circuito cuántico es un mapeo de características, en el sentido de que lleva los datos a un nuevo espacio: el espacio de Hilbert de los qubits involucrados. La relación entre la dimensionalidad del espacio de características original y la del espacio de Hilbert dependerá del circuito que uses para la codificación.

Mapa de características ZZ

El mapa de características ZZ (ZFM, por sus siglas en inglés) puede interpretarse como una extensión natural de la codificación por fase. El ZFM consiste en capas alternadas de compuertas de un solo qubit: capas de compuertas Hadamard y capas de compuertas de fase. Sea el vector de datos x\vec{x} con NN características. El circuito cuántico que realiza el mapeo de características se representa como un operador unitario que actúa sobre el estado inicial:

UZFM(x)0N=ϕ(x)\mathscr{U}_{\text{ZFM}}(\vec{x})|0\rangle^{\otimes N}=|\phi(\vec{x})\rangle

donde 0N|0\rangle^{\otimes N} es el estado base de NN qubits. Esta notación se usa por coherencia con la referencia [4] de Havlicek et al. Las características xix_i del dato se mapean uno a uno con los qubits correspondientes. Por ejemplo, si tienes 8 características en un vector de datos, usarías 8 qubits. El circuito ZFM se compone de rr repeticiones de un subcircuito formado por capas de compuertas Hadamard y capas de compuertas de fase. Una capa Hadamard está formada por una compuerta Hadamard actuando sobre cada qubit en un registro de nn qubits, HHH=HnH \otimes H \otimes \dots \otimes H = H^{\otimes n}, dentro de la misma etapa del algoritmo. Esta descripción también aplica a una capa de compuertas de fase en la que el qubit ithi^\text{th} es actuado por P(xi)P(\vec{x}_i). Cada compuerta PP tiene una característica como argumento, pero la capa de compuertas de fase (P(x1)P(xk)P(xN)P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)) es una función del vector de datos. El unitario completo del circuito ZFM con una sola repetición es:

UZFM=(P(x1)P(xk)P(xN)HN)=(k=1NP(xk))HN\mathscr{U}_{\text{ZFM}}=\big(P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)H^{\otimes N}\big)=\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}

Entonces rr repeticiones de este unitario serían

UZFM(r)(x)=s=1r[(k=1NP(xk))HN]\mathscr{U}^{(r)}_{\text{ZFM}}\left(\vec{x}\right)=\prod_{s=1}^{r}\left[\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}\right]

Las características xkx_k se mapean a las compuertas de fase de la misma manera en todas las rr repeticiones. El estado del mapa de características ZFM es un estado producto y es eficiente para la simulación clásica[4].

Para comenzar con un ejemplo pequeño, se codifica un circuito ZFM de dos qubits usando Qiskit y se dibuja para mostrar la estructura simple del circuito. En el ejemplo, se implementa una sola repetición, r=1r=1, con el vector de datos x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right). Ten en cuenta que esto está escrito en el orden estándar de un vector en Python, lo que significa que el elemento 0th0^\text{th} es 12π.\textstyle\frac{1}{2}\pi. Somos libres de codificar esta característica 0th0^\text{th} en nuestro qubit 0th0^\text{th} o en el Nth.N^\text{th}. Nuevamente, no siempre puede haber un único mapeo 1:1 del orden de características al orden de qubits, ya que diferentes mapas de características codifican distintas cantidades de características por qubit. Lo importante es que seamos conscientes de dónde se codifica cada característica. Al proporcionar una lista de parámetros al mapa de características ZZ, este codificará la característica 0 de la lista en el qubit menos significativo con una compuerta parametrizada, es decir, el qubit 0. Por lo tanto, seguiremos esa convención al hacerlo manualmente. Codificaremos 12π\textstyle\frac{1}{2}\pi en el qubit 0th0^\text{th} y 13π\textstyle\frac{1}{3}\pi en el qubit 1st1^\text{st}.

El operador unitario del circuito ZFM actúa sobre el estado inicial de la siguiente manera:

UZFM(xˉ)00=P(xˉ)2H200=(P(13π)H0)(P(12π)H0).\mathscr{U}_{\text{ZFM}}(\bar{x})|00\rangle = P(\bar{x})^{\otimes 2} H^{\otimes 2}|00\rangle = \left( P\left(\textstyle\frac{1}{3}\pi\right)H|0\rangle \right) \otimes \left(P\left(\textstyle\frac{1}{2}\pi\right)H|0\rangle\right).

La fórmula se ha reorganizado alrededor del producto tensorial para enfatizar las operaciones en cada qubit. El siguiente código de Qiskit usa compuertas Hadamard y de fase explícitamente para mostrar la estructura del ZFM:

qc0 = QuantumCircuit(1)
qc1 = QuantumCircuit(1)

qc0.h(0)
qc0.p(pi / 2, 0)

qc1.h(0)
qc1.p(pi / 3, 0)

# Combine circuits qc0 and qc1 into 1 circuit
qc = QuantumCircuit(2)
qc.compose(qc0, [0], inplace=True)
qc.compose(qc1, [1], inplace=True)

qc.draw("mpl", scale=1)

Salida de la celda de código anterior

Ahora codificamos el mismo vector de datos x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right) en un circuito ZFM con tres repeticiones, r=3r=3, usando la clase z_feature_map de Qiskit, lo que en conjunto nos da el mapa de características cuántico UZFM(x)\mathscr{U}_{\text{ZFM}}(\vec{x}). De forma predeterminada en la clase z_feature_map, los parámetros β\beta se multiplican por 2 antes de mapearse a la compuerta de fase βP(θ=2β)\beta \rightarrow P(\theta = 2\beta). Para reproducir las mismas codificaciones que arriba, dividimos por 2.

from qiskit.circuit.library import z_feature_map

zfeature_map = z_feature_map(feature_dimension=2, reps=3)
zfeature_map = zfeature_map.assign_parameters([(1 / 2) * pi / 2, (1 / 2) * pi / 3])
zfeature_map.decompose().draw("mpl")

Salida de la celda de código anterior

Claramente este es un mapeo diferente al realizado manualmente arriba, pero observa la consistencia en el orden de parámetros: 12π\textstyle\frac{1}{2}\pi se codificó nuevamente en el qubit 0th0^\text{th}.

Puedes usar el ZFM mediante la clase ZFM de Qiskit; también puedes usar esta estructura como inspiración para construir tu propio mapeo de características.

Mapa de características ZZZZ

El mapa de características ZZZZ (ZZFM) extiende el ZFM con la inclusión de compuertas de entrelazamiento de dos qubits, específicamente la compuerta de rotación ZZZZ, RZZ(θ)R_{ZZ}(\theta). Se conjetura que el ZZFM es generalmente costoso de calcular en una computadora clásica, a diferencia del ZFM.

RZZ(θ)R_{ZZ}(\theta) implementa una interacción ZZZZ y tiene entrelazamiento máximo para θ=12π\theta = \textstyle{\frac{1}{2}}\pi. RZZ(θ)R_{ZZ}(\theta) puede descomponerse en una serie de compuertas sobre dos qubits, como se muestra en el siguiente código de Qiskit usando la compuerta RZZ y el método decompose de la clase QuantumCircuit. Codificamos una sola característica del vector de datos x\vec{x}: xk=π.\vec{x}_k=\pi.

qc = QuantumCircuit(2)
qc.rzz(pi, 0, 1)
qc.draw("mpl", scale=1)

Salida de la celda de código anterior

Como suele ocurrir, lo vemos representado como una sola unidad similar a una compuerta, hasta que usamos .decompose() para ver todas las compuertas constituyentes.

qc.decompose().draw("mpl", scale=1)

Salida de la celda de código anterior

Los datos se mapean con una rotación de fase P(θ)=eiθ/2RZ(θ)P(\theta) = e^{i\theta/2}R_Z(\theta) en el segundo qubit. La compuerta RZZ(θ)R_{ZZ}(\theta) entrelaza los dos qubits sobre los que opera con un grado de entrelazamiento determinado por el valor de la característica codificada.

El circuito ZZFM completo consiste en una compuerta Hadamard y una compuerta de fase, como en el ZFM, seguidas del entrelazamiento descrito anteriormente. Una sola repetición del circuito ZZFM es:

UZZFM(x)=UZZ(x)(P(x1)P(xk)P(xN)HN)=UZZ(x)(k=1NP(xk))HN,\mathscr{U}_{\text{ZZFM}}(\vec{x}) = U_{ZZ}(\vec{x})\big(P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)H^{\otimes N}\big)=U_{ZZ}(\vec{x})\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N},

donde UZZ(x)U_{ZZ}(\vec{x}) contiene una capa de compuertas ZZ estructurada por un esquema de entrelazamiento. Varios esquemas de entrelazamiento se muestran en los bloques de código a continuación. La estructura de UZZ(x)U_{ZZ}(\vec{x}) también incluye una función que combina las características de los datos de los qubits que se entrelazan de la siguiente manera. Supongamos que la compuerta RZZR_{ZZ} se aplica a los qubits pp y qq. En la capa de fase, estos qubits tienen compuertas de fase que codifican xp\vec{x}_p y xq\vec{x}_q en ellos, respectivamente. El argumento θq,p\theta_{q,p} de RZZ,q,p(θq,p)R_{ZZ,q,p}(\theta_{q,p}) no será simplemente una de estas características u otra, sino una función que suele denotarse por ϕ\phi (que no debe confundirse con el ángulo azimutal):

θq,pϕ(xq,xp)=2(πxq)(πxp).\theta_{q,p} \rightarrow \phi(\vec{x}_q, \vec{x}_p) = 2(\pi-\vec{x}_q)(\pi-\vec{x}_p).

Veremos esto en varios ejemplos a continuación. La extensión a múltiples repeticiones es la misma que en el caso del z_feature_map:

UZZFM(r)(x)=s=1r[UZZ(x)(k=1NP(xk))HN].\mathscr{U}^{(r)}_{\text{ZZFM}}\left(\vec{x}\right)=\prod_{s=1}^{r}\left[U_{ZZ}(\vec{x})\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}\right].

A medida que los operadores han aumentado en complejidad, codifiquemos primero un vector de datos x=(x0,x1)\vec{x} = (x_0, x_1) con un ZZFM de dos qubits y una repetición usando el siguiente código:

from qiskit.circuit.library import zz_feature_map

feature_dim = 2
zzfeature_map = zz_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1
)
zzfeature_map.decompose(reps=1).draw("mpl", scale=1)

Salida de la celda de código anterior

De forma predeterminada en Qiskit, las características (x1,x2)(\vec{x}_1, \vec{x}_2) se mapean conjuntamente a RZZ(θ)R_{ZZ}(\theta) mediante esta función de mapeo θ1,2=ϕ(x1,x2)=2(πx1)(πx2)\theta_{1,2} = \phi(\vec{x}_1, \vec{x}_2) = 2(\pi-\vec{x}_1)(\pi-\vec{x}_2). Qiskit permite al usuario personalizar la función ϕ\phi (o ϕS\phi_S donde SS es el conjunto de pares de qubits acoplados mediante compuertas RZZR_{ZZ}) como un paso de preprocesamiento.

Pasando a un vector de datos de cuatro dimensiones x=(x1,x2,x3,x4)\vec{x} = (\vec{x}_1, \vec{x}_2, \vec{x}_3, \vec{x}_4) y mapeando a un ZZFM de cuatro qubits con una repetición, podemos comenzar a ver el mapeo ϕ\phi para varios pares de qubits. También podemos ver el significado del entrelazamiento "lineal":

feature_dim = 4
zzfeature_map = zz_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1
)
zzfeature_map.decompose().draw("mpl", scale=1)

Salida de la celda de código anterior

En el esquema de entrelazamiento lineal, los pares de qubits vecinos más cercanos (numerados) en este circuito están entrelazados. Existen otros esquemas de entrelazamiento incorporados en Qiskit, incluyendo circular y full.

Mapa de características de Pauli

El mapa de características de Pauli (PFM, por sus siglas en inglés) es la generalización del ZFM y el ZZFM para usar compuertas de Pauli arbitrarias. El PFM tiene una forma muy similar a los dos mapas de características anteriores. Para rr repeticiones de la codificación de las NN características del vector x,\vec{x},

UPFM(x)=s=1rU(x)Hn.\mathscr{U}_{\text{PFM}}(\vec{x}) = \prod_{s=1}^{r} U(\vec{x}) H^{\otimes n}.

Para el PFM, U(x)U(\vec{x}) se generaliza a un operador unitario de expansión de Pauli. Aquí presentamos una forma más generalizada de los mapas de características considerados hasta ahora:

U(x)=exp(iSIϕS(x)iSσi),U(\vec{x}) = \exp\left(i \sum_{S \in\mathcal{I}} \phi_S(\vec{x}) \prod_{i \in S} \sigma_i \right),

donde σi\sigma_i es un operador de Pauli, σiI,X,Y,Z\sigma_i \in {I,X,Y,Z}. Aquí I\mathcal{I} es el conjunto de todas las conectividades de qubits determinadas por el mapa de características, incluyendo el conjunto de qubits sobre los que actúan las compuertas de un solo qubit. Es decir, para un mapa de características en el que el qubit 0 fue actuado por una compuerta de fase, y los qubits 2 y 3 fueron actuados por una compuerta RZZR_{ZZ}, el conjunto I\mathcal{I} incluiría {{0},{2,3}}\{\{0\},\{2,3\}\}. SS recorre todos los elementos de ese conjunto. En los mapas de características anteriores, la función ϕS(x)\phi_S(\vec{x}) estaba involucrada ya sea exclusivamente con compuertas de un qubit o exclusivamente con compuertas de dos qubits. Aquí la definimos en general:

ϕS(x)={xiif S={i} (single-qubit)jS(πxj)if S2 (multi-qubit)\phi_S(\vec{x})= \begin{cases} x_i & \text{if } S= \{i\} \text{ (single-qubit)}\\ \prod_{j\in{S}}(\pi-x_j) & \text{if } |S|\ge2 \text{ (multi-qubit)}\\ \end{cases}

Para la documentación, consulta la documentación de la clase Pauli feature map de Qiskit). En el ZZFM, el operador σi\sigma_i está restringido a ZiZ_i.

Una forma de entender el unitario anterior es mediante la analogía con el propagador en un sistema físico. El unitario anterior es un operador de evolución unitaria, exp(itH)\exp(it\mathcal{H}), para un Hamiltoniano H\mathcal{H}, similar al modelo de Ising, donde el parámetro de tiempo tt se reemplaza con valores de datos para impulsar la evolución. La expansión de este operador unitario da el circuito PFM. Las conectividades de entrelazamiento en SS pueden interpretarse como acoplamientos de Ising en una red de espines.

Consideremos un ejemplo de los operadores de Pauli YY y XXXX que representan esas interacciones de tipo Ising. Qiskit proporciona una clase pauli_feature_map para instanciar un PFM con una elección de compuertas de un qubit y nn qubits, que en este ejemplo se pasarán como cadenas de Pauli 'Y' y 'XX'. Típicamente, nn es 1 o 2 para interacciones de un qubit y dos qubits, respectivamente. El esquema de entrelazamiento es "lineal", lo que significa que solo los qubits vecinos más cercanos en el circuito cuántico están acoplados. Ten en cuenta que esto no corresponde a qubits vecinos más cercanos en la computadora cuántica en sí, ya que este circuito cuántico es una capa de abstracción.

from qiskit.circuit.library import pauli_feature_map

feature_dim = 3
pfmap = pauli_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1, paulis=["Y", "XX"]
)

pfmap.decompose().draw("mpl", scale=1.5)

Salida de la celda de código anterior

Qiskit proporciona un parámetro α\alpha en los mapas de características de Pauli para controlar la escala de las rotaciones de Pauli.

U(xˉ)=exp(iαS[n]ϕS(xˉ)iSσi)U(\bar{x}) = \exp\left(i \alpha \sum_{S\subseteq[n]} \phi_S(\bar{x}) \prod_{i \in S} \sigma_i \right)

El valor predeterminado de α\alpha es 22. Optimizando su valor en el intervalo, por ejemplo, [0,4],[0,4], se puede alinear mejor un núcleo cuántico con los datos.

Aquí visualizamos varios mapas de características de Pauli para circuitos de dos qubits para tener una mejor imagen del rango de posibilidades.

from qiskit.visualization import circuit_drawer
import matplotlib.pyplot as plt

feature_dim = 2
fig, axs = plt.subplots(9, 2)
i_plot = 0
for paulis in [
["I"],
["X"],
["Y"],
["Z"],
["XX"],
["XY"],
["XZ"],
["YY"],
["YZ"],
["ZZ"],
["X", "ZZ"],
["Y", "ZZ"],
["Z", "ZZ"],
["X", "YZ"],
["Y", "YZ"],
["Z", "YZ"],
["YY", "ZZ"],
["XY", "ZZ"],
]:
pfmap = pauli_feature_map(feature_dimension=feature_dim, paulis=paulis, reps=1)
circuit_drawer(
pfmap.decompose(),
output="mpl",
style={"backgroundcolor": "#EEEEEE"},
ax=axs[int((i_plot - i_plot % 2) / 2), i_plot % 2],
)
axs[int((i_plot - i_plot % 2) / 2), i_plot % 2].title.set_text(paulis)
i_plot += 1

fig.set_figheight(16)
fig.set_figwidth(16)

Salida de la celda de código anterior

Lo anterior puede, por supuesto, extenderse para incluir otras permutaciones y repeticiones de matrices de Pauli. Se anima a quienes estudian este material a experimentar con esas opciones.

Revisión de los mapas de características incorporados

Has visto varios esquemas para codificar datos en un circuito cuántico:

  • Codificación en base
  • Codificación en amplitud
  • Codificación por ángulo
  • Codificación por fase
  • Codificación densa

Has visto cómo construir tus propios mapas de características usando estos esquemas de codificación, y has visto cuatro mapas de características incorporados que aprovechan la codificación por ángulo y por fase:

  • SU2 eficiente
  • Mapa de características Z
  • Mapa de características ZZ
  • Mapa de características de Pauli

Estos mapas de características incorporados se diferenciaron entre sí en varios aspectos:

  • La profundidad para un número dado de características codificadas
  • La cantidad de qubits requeridos para un número dado de características
  • El grado de entrelazamiento (obviamente relacionado con las otras diferencias)

El código a continuación aplica estos cuatro mapas de características incorporados a la codificación de un conjunto de características y grafica la profundidad de dos qubits del circuito resultante. Dado que las tasas de error de dos qubits son mucho más altas que las de compuertas de un solo qubit, podría ser razonable estar más interesado en la profundidad de las compuertas de dos qubits. En el código siguiente, obtenemos conteos de todas las compuertas en un circuito descomponiendo primero el circuito y luego usando count_ops(), como se muestra a continuación. Aquí las compuertas de dos qubits que nos interesan son las compuertas 'cx':

# Initializing parameters and empty lists for depths
x = [0.1, 0.2]
n_data = []
zz2gates = []
su22gates = []
z2gates = []
p2gates = []

# Generating feature maps
for n in range(3, 10):
x.append(n / 10)
zzcircuit = zz_feature_map(n, reps=1, insert_barriers=True)
zcircuit = z_feature_map(n, reps=1, insert_barriers=True)
su2circuit = efficient_su2(n, reps=1, insert_barriers=True)
pcircuit = pauli_feature_map(n, reps=1, paulis=["XX"], insert_barriers=True)
# Getting the cx depths
zzcx = zzcircuit.decompose().count_ops().get("cx")
zcx = zcircuit.decompose().count_ops().get("cx")
su2cx = su2circuit.decompose().count_ops().get("cx")
pcx = pcircuit.decompose().count_ops().get("cx")

# Appending the cx gate counts to the lists. We shift the zz and pauli data points, because they overlap.
n_data.append(n)
zz2gates.append(zzcx - 0.5)
z2gates.append(0)
su22gates.append(su2cx)
p2gates.append(pcx + 0.5)

# Plot the output
plt.plot(n_data, p2gates, "bo")
plt.plot(n_data, zz2gates, "ro")
plt.plot(n_data, su22gates, "yo")
plt.plot(n_data, z2gates, "go")
plt.ylabel("CX Gates")
plt.xlabel("Data elements")
plt.legend(["Pauli", "ZZ", "SU2", "Z"])
# plt.suptitle('zz_feature_map(n)')
plt.show()

En general, los mapas de características de Pauli y ZZ resultarán en mayor profundidad de circuito y mayor número de compuertas de 2 qubits que efficient_su2 y los mapas de características Z.

Dado que los mapas de características incorporados en Qiskit son ampliamente aplicables, a menudo no necesitaremos diseñar los nuestros propios, especialmente en la fase de aprendizaje. Sin embargo, los expertos en aprendizaje automático cuántico probablemente volverán al tema del diseño de su propio mapeo de características a medida que enfrenten dos desafíos complejos:

  1. Hardware moderno: la presencia de ruido y la gran sobrecarga de los códigos de corrección de errores significa que las aplicaciones actuales necesitarán considerar cosas como la eficiencia del hardware y la minimización de la profundidad de las compuertas de dos qubits.

  2. Mapeos que se adapten al problema en cuestión: Una cosa es decir que el zz_feature_map, por ejemplo, es difícil de simular clásicamente y, por lo tanto, interesante. Otra muy diferente es que el zz_feature_map sea idealmente adecuado para tu tarea de aprendizaje automático o conjunto de datos. El rendimiento de diferentes circuitos cuánticos parametrizados en diferentes tipos de datos es un área activa de investigación.

Cerramos con una nota sobre la eficiencia del hardware.

Mapeo de características eficiente en hardware

Un mapeo de características eficiente en hardware es aquel que tiene en cuenta las restricciones de las computadoras cuánticas reales, con el objetivo de reducir el ruido y los errores en el cómputo. Al ejecutar circuitos cuánticos en computadoras cuánticas de corto plazo (near-term), existen muchas estrategias para mitigar el ruido inherente al hardware. Una estrategia principal para la eficiencia en hardware es la minimización de la profundidad del circuito cuántico, de modo que el ruido y la decoherencia tengan menos tiempo para corromper el cómputo. La profundidad de un circuito cuántico es el número de pasos de compuertas alineados en el tiempo requeridos para completar todo el cómputo (después de la optimización del circuito)[5]. Recuerda que la profundidad del circuito lógico abstracto puede ser mucho menor que la profundidad una vez que el circuito es transpilado para una computadora cuántica real.

La transpilación es el proceso de convertir el circuito cuántico de una abstracción de alto nivel a uno listo para ejecutarse en una computadora cuántica real, teniendo en cuenta las restricciones del hardware. Una computadora cuántica tiene un conjunto nativo de compuertas de uno y dos qubits. Esto significa que todas las compuertas en el código de Qiskit deben transpilarse al conjunto de compuertas nativas del hardware. Por ejemplo, en ibm_torino, una QPU con un procesador Heron r1 completada en 2023, las compuertas nativas o de base son \{CZ, ID, RZ, SX, X\}. Estas son la compuerta de dos qubits Z controlada, y compuertas de un qubit llamadas identidad, rotación ZZ, raíz cuadrada de NOT y NOT, respectivamente, proporcionando un conjunto universal. Al implementar compuertas de múltiples qubits como un subcircuito equivalente, se requieren compuertas físicas CZCZ de dos qubits, junto con otras compuertas de un qubit disponibles en el hardware. Además, para realizar una compuerta de dos qubits en un par de qubits que no están físicamente acoplados, se agregan compuertas SWAP para mover los estados de los qubits entre qubits para habilitar el acoplamiento, lo que genera una extensión inevitable del circuito. Usando el argumento optimization que puede configurarse desde 0 hasta un nivel máximo de 3. Para mayor control y personalización, el flujo de transpilación puede gestionarse con el Qiskit Pass Manager. Consulta la documentación del Transpiler de Qiskit para más información sobre la transpilación.

En Havlicek et al. 2019 [2], una de las formas en que los autores logran eficiencia en hardware es usando el mapa de características ZZZZ porque es una expansión de segundo orden (consulta la sección "Mapa de características ZZZZ" arriba). Una expansión de orden NN tiene compuertas de NN qubits. Las computadoras cuánticas de IBM® no tienen compuertas nativas de NN qubits, donde N>2N>2, por lo que implementarlas requeriría descomposición en compuertas CNOT de dos qubits disponibles en el hardware. Una segunda forma en que los autores minimizan la profundidad es eligiendo una topología de acoplamiento ZZZZ que se mapee directamente a los acoplamientos de la arquitectura. Una optimización adicional que realizan es apuntar a un subcircuito de hardware de alto rendimiento y conectividad adecuada. Otras cosas a considerar son minimizar el número de repeticiones del mapa de características y elegir un esquema de entrelazamiento personalizado de baja profundidad o "lineal" en lugar del esquema "completo" que entrelaza todos los qubits.

Imagen de codificación de datos

El gráfico anterior muestra una red de nodos y aristas que representan qubits físicos y acoplamientos del hardware, respectivamente. Se muestra el mapa de acoplamiento y el rendimiento de ibm_torino con todas las posibles compuertas de acoplamiento CZ de dos qubits. Los qubits están codificados por colores en una escala basada en el tiempo de relajación T1 en microsegundos (μs), donde tiempos T1 más largos son mejores y se muestran en un tono más claro. Las aristas de acoplamiento están codificadas por colores según el error de CZ, donde los tonos más oscuros son mejores. La información sobre la especificación del hardware se puede acceder en el esquema de configuración del backend del hardware IBMQBackend.configuration().

Referencias

  1. Maria Schuld and Francesco Petruccione, Supervised Learning with Quantum Computers, Springer 2018, doi:10.1007/978-3-319-96424-9.
  2. Vojtech Havlicek et al., "Supervised Learning with Quantum Enhanced Feature Spaces." Nature, vol. 567 (2019): 209–212. https://arxiv.org/abs/1804.11326.
  3. Ryan LaRose and Brian Coyle, "Robust data encodings for quantum classifiers", Physical Review A 102, 032420 (2020), doi:10.1103/PhysRevA.102.032420, arXiv:2003.01695.
  4. Lou Grover and Terry Rudolph. "Creating Superpositions That Correspond to Efficiently Integrable Probability Distributions." arXiv:quant-ph/0208112, August 15, 2002, https://arxiv.org/abs/quant-ph/0208112.
  5. Adrián Pérez-Salinas, Alba Cervera-Lierta, Elies Gil-Fuster, José I. Latorre, "Data re-uploading for a universal quantum classifier", Quantum 4, 226 (2020), ArXiv.org/abs/1907.02085.
  6. Maria Schuld, Ryan Sweke, Johannes Jakob Meyer, "The effect of data encoding on the expressive power of variational quantum machine learning models", Phys. Rev. A 103, 032430 (2021), arxiv.org/abs/2008.08605
import qiskit

qiskit.version.get_version_info()