Saltar al contenido principal

Teleportación cuántica y codificación superdensa

nota

Kifumi Numata (26 abr 2024)

Descargar el PDF de la clase original. Ten en cuenta que algunos fragmentos de código pueden estar desactualizados, ya que son imágenes estáticas.

El tiempo aproximado de QPU para este experimento es de 10 segundos.

1. Introducción

Para resolver un problema cuántico a escala de utilidad, necesitamos ser capaces de mover información en un computador cuántico de un qubit a otro. Existen protocolos conocidos para ello, y algunos de los más fundamentales se formularon en el contexto de la transferencia de información entre partes distantes. En esta lección, ocasionalmente usamos un lenguaje que se ajusta a este contexto, como "amigos distantes que intercambian información". Sin embargo, ten en cuenta que estos protocolos tienen una importancia más amplia para la computación cuántica. En esta lección consideramos los siguientes protocolos de comunicación cuántica:

  • Teleportación cuántica Uso de un estado entrelazado compartido (a veces denominado E-bit) para enviar un estado cuántico desconocido a un amigo distante, complementado con comunicación clásica.
  • Codificación superdensa cuántica Cómo transmitir dos bits de información enviando un solo qubit a un amigo distante (también utilizando qubits entrelazados previamente compartidos).

Para información de fondo adicional sobre estos temas, recomendamos la lección 4 en "Fundamentos de la información cuántica" sobre Entrelazamiento en acción.

En la descripción anterior, un "estado cuántico desconocido" es simplemente un estado de la forma descrita en la lección anterior:

ψ=α0+β1|\psi\rangle =\alpha|0\rangle+\beta|1\rangle

donde α\alpha y β\beta son números complejos tales que α2+β2=1|\alpha|^2+|\beta|^2 = 1. Con esto podemos escribir el estado cuántico como

ψ=cosθ20+eiφsinθ21=(cosθ2eiφsinθ2)|\psi\rangle =\cos\frac{\theta}{2}|0\rangle+e^{i\varphi}\sin\frac{\theta}{2}|1\rangle= \left( \begin{matrix} \cos\frac{\theta}{2}\\ e^{i\varphi}\sin\frac{\theta}{2} \end{matrix} \right)

Dado que queremos ser capaces de transferir la información de un estado cuántico arbitrario, comenzamos esta lección creando dicho estado.

2. Matrices de densidad

El estado cuántico ψ|\psi \rangle también puede escribirse como una matriz de densidad. Esta representación es útil para expresar mezclas probabilísticas de estados cuánticos puros. En el caso de un solo qubit, se puede escribir:

ψψρ=((cosθ2eiφsinθ2))((cosθ2eiφsinθ2))=12((1+cosθeiφsinθeiφsinθ1cosθ))|\psi \rangle \langle \psi| \equiv \rho = \left( \begin{pmatrix} \cos\frac{\theta}{2}\\ e^{i\varphi}\sin\frac{\theta}{2} \end{pmatrix} \right) \left( \begin{pmatrix} \cos\frac{\theta}{2} & e^{-i\varphi}\sin\frac{\theta}{2} \end{pmatrix} \right) =\frac{1}{2}\left(\begin{pmatrix} 1+\cos\theta & e^{-i\varphi}\sin\theta\\ e^{-i\varphi}\sin\theta & 1-\cos\theta \end{pmatrix}\right)

La matriz de densidad ρ\rho es una combinación lineal de las matrices de Pauli:

ρ=12(I+(sinθcosφ)X+(sinθsinφ)Y+(cosθ)Z)\rho = \frac{1}{2}\bigl( \textbf{I} + (\sin{\theta}\cos{\varphi})\textbf{X}+ (\sin{\theta}\sin{\varphi})\textbf{Y} + (\cos{\theta})\textbf{Z} \bigr)

O en general:

ρ=12(I+rxX+ryY+rzZ)\rho = \frac{1}{2}(\textbf{I} + r_{x}\textbf{X}+ r_{y}\textbf{Y} + r_{z}\textbf{Z})

donde rx2+ry2+rz2=1r_{x}^2+r_{y}^2+r_{z}^2=1.

El vector de Bloch es r=(rx,ry,rz)\textbf{r} = (r_{x}, r_{y}, r_{z}).

Ahora creamos un estado cuántico arbitrario usando números aleatorios.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime
import numpy as np

# create a random 1-qubit state from a random (theta, varphi) to define r vector
np.random.seed(1) # fixing seed for repeatibility

theta = np.random.uniform(0.0, 1.0) * np.pi # from 0 to pi
varphi = np.random.uniform(0.0, 2.0) * np.pi # from 0 to 2*pi

def get_r_vec(theta, varphi):
rx = np.sin(theta) * np.cos(varphi)
ry = np.sin(theta) * np.sin(varphi)
rz = np.cos(theta)
return (rx, ry, rz)

# get r vector
rx, ry, rz = get_r_vec(theta, varphi)

print("theta=" + str(theta), ",varphi=" + str(varphi))
print("(rx, ry, rz) = (" + str(rx) + ", " + str(ry) + ", " + str(rz) + ")")
theta=1.3101132663588946 ,varphi=4.525932273597346
(rx, ry, rz) = (-0.1791150283307452, -0.9494670044331133, 0.2577405946274022)

Podemos representar este vector de Bloch en la esfera de Bloch.

from qiskit.visualization import plot_bloch_vector

r = [rx, ry, rz]
plot_bloch_vector(r)

Salida de la celda de código anterior

3. Tomografía de estado cuántico

Si medimos el estado cuántico solo en la base computacional (0|0 \rangle y 1|1 \rangle), se pierde la información de fase (la información sobre números complejos). Sin embargo, si generamos muchas copias de ψ|\psi \rangle repitiendo el proceso de preparación (los estados no pueden clonarse, pero los procesos de preparación pueden repetirse), podemos estimar los valores de rx,ry,rzr_{x}, r_{y}, r_{z} mediante tomografía de estado cuántico de la matriz de densidad ρ\rho. Partiendo de la forma:

ρ=12(I+rxX+ryY+rzZ)\rho = \frac{1}{2}(\textbf{I} + r_{x}\textbf{X}+ r_{y}\textbf{Y} + r_{z}\textbf{Z})

se cumple:

Tr(Xρ)=rx,Tr(Yρ)=ry,Tr(Zρ)=rzTr(\textbf{X} \rho) = r_{x}, \quad Tr(\textbf{Y} \rho) = r_{y}, \quad Tr(\textbf{Z} \rho) = r_{z}

En el caso de rzr_{z}:

Tr(Zρ)=0Zρ0+1Zρ1Tr(\textbf{Z} \rho) = \langle 0|\textbf{Z} \rho|0 \rangle + \langle 1|\textbf{Z} \rho|1 \rangle =0(0011)ρ0+1(0011)ρ1= \langle 0|(|0 \rangle\langle 0|-|1 \rangle\langle 1|) \rho|0 \rangle +\langle 1|(|0 \rangle\langle 0|-|1 \rangle\langle 1|) \rho|1 \rangle =0ρ01ρ1=\langle 0|\rho|0 \rangle- \langle 1| \rho|1 \rangle =0ψψ01ψψ1=\langle 0|\psi\rangle\langle \psi|0 \rangle - \langle 1| \psi\rangle\langle \psi|1 \rangle =α2β2=|\alpha|^2-|\beta|^2

La última transformación se cumple para ψ=α0+β1|\psi \rangle =\alpha|0\rangle+\beta|1\rangle. Obtenemos rzr_{z} como la probabilidad de 0|0 \rangle menos la probabilidad de 1|1 \rangle.

Estimación del valor rzr_z

Para estimar rzr_z, generamos un estado cuántico y lo medimos. Repetimos este proceso muchas veces. Luego usamos la estadística de medición para estimar las probabilidades anteriores y así determinar rzr_z.

Para generar el estado cuántico aleatorio, usamos la puerta unitaria general UU con los parámetros θ,φ\theta, \varphi. (Más información en U-Gate.)

from qiskit import QuantumCircuit

# create a 1-qubit quantum state psi from theta, varphi parameters
qc = QuantumCircuit(1, 1)
qc.u(theta, varphi, 0.0, 0)

# measure in computational basis
qc.measure(0, 0)

qc.draw(output="mpl")

Salida de la celda de código anterior

Usando el AerSimulator, medimos en la base computacional para estimar rzr_z.

# see if the expected value of measuring in the computational basis
# approaches the limit of rz
from qiskit_aer import AerSimulator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Sampler
from qiskit.visualization import plot_histogram

# Define backend
backend = AerSimulator()
nshots = 1000 # or 10000
# nshots = 10000

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=nshots)
result = job.result()

# Extract counts data
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram

plot_histogram(counts)
{'1': 375, '0': 625}

Salida de la celda de código anterior

rz_approx = (counts["0"] - counts["1"]) / nshots

print("rz = ", rz, " and approx of rz = ", rz_approx)
rz =  0.2577405946274022  and approx of rz =  0.25

Mediante la tomografía de estado cuántico, hemos estimado el valor rzr_z. Como en este caso nosotros mismos elegimos el parámetro para el estado "aleatorio", conocemos el valor verdadero de rzr_z y podemos verificar nuestro resultado. Sin embargo, en el trabajo real a escala de utilidad, dicha verificación no siempre es tan sencilla. Cómo verificar resultados cuánticos lo discutiremos más adelante en este curso. Por ahora, simplemente señalemos que nuestra estimación fue suficientemente precisa.

Ejercicio 1: Estimación del valor rxr_x

Los computadores cuánticos de IBM® miden a lo largo del eje zz (a veces también se dice "en la base zz" o "en la base computacional"). Sin embargo, mediante rotaciones antes de la medición, también podemos medir la proyección del estado cuántico en el eje x. Más concretamente: si rotamos nuestro sistema de modo que lo que antes apuntaba a lo largo de xx ahora apunte a lo largo de zz, podemos usar el mismo hardware de medición a lo largo de zz y aún así obtener información sobre el estado que antes estaba a lo largo de xx. Así es como la mayoría de los computadores cuánticos (y todos los computadores cuánticos de IBM) realizan mediciones a lo largo de múltiples ejes.

Con esta comprensión, intenta escribir código que estime el valor de rxr_x usando tomografía de estado cuántico.

Solución:

# create a 1-qubit quantum state psi from theta, varphi parameters
qc = QuantumCircuit(1, 1)
qc.u(theta, varphi, 0.0, 0)

qc.h(0)
qc.measure(0, 0)

qc.draw(output="mpl")

Salida de la celda de código anterior

# Define backend
backend = AerSimulator()
nshots = 10000

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=nshots)
result = job.result()

# Extract counts data
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'1': 5925, '0': 4075}

Salida de la celda de código anterior

rx_approx = (counts["0"] - counts["1"]) / nshots

print("rx = ", rx, " and approx of rx = ", rx_approx)
rx =  -0.1791150283307452  and approx of rx =  -0.185

Ejercicio 2: Estimación del valor ryr_y

Con la misma lógica que antes, podemos rotar el sistema antes de la medición para determinar ryr_y. Intenta escribir código que estime el valor de ryr_y usando tomografía de estado cuántico. Puedes partir del ejemplo anterior y realizar otras rotaciones. (Más información sobre las puertas utilizadas, incluyendo sdg, en la referencia de la API.)

Solución:

# create a 1-qubit quantum state psi from theta, varphi parameters
qc = QuantumCircuit(1, 1)
qc.u(theta, varphi, 0.0, 0)

qc.sdg(0)
qc.h(0)
qc.measure(0, 0)

qc.draw(output="mpl")

Salida de la celda de código anterior

# Define backend
backend = AerSimulator()
nshots = 10000

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=nshots)
result = job.result()

# Extract counts data
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'1': 9759, '0': 241}

Salida de la celda de código anterior

ry_approx = (counts["0"] - counts["1"]) / nshots

print("ry = ", ry, " and approx of ry = ", ry_approx)
ry =  -0.9494670044331133  and approx of ry =  -0.9518

Con esto hemos estimado todos los componentes de r\vec{r} y podemos indicar el vector completo.

print("Estimated vector is (", rx_approx, ",", ry_approx, ",", rz_approx, ").")
print("Original random vector was (" + str(rx) + ", " + str(ry) + ", " + str(rz) + ").")
Estimated vector is ( -0.185 , -0.9518 , 0.25 ).
Original random vector was (-0.1791150283307452, -0.9494670044331133, 0.2577405946274022).

Con este método de tomografía de estado cuántico, has estimado el vector aleatorio original con bastante precisión.

4. Teleportación cuántica

Imaginemos que una persona llamada Alice quiere enviar un estado cuántico desconocido ψ|\psi \rangle a su amigo distante Bob. Supongamos que solo pueden comunicarse clásicamente (por ejemplo, por correo electrónico o teléfono). Alice no puede copiar el estado cuántico (debido al teorema de no clonación). Si repitiera el mismo proceso de preparación muchas veces, podría construir estadísticas, como acabamos de hacer. Pero, ¿qué pasa si solo hay un único estado desconocido? Este podría provenir de un proceso físico que deseas investigar, o ser parte de un cálculo cuántico más grande. ¿Cómo podría Alice transferir el estado a Bob? Esto es posible si ella y Bob comparten un recurso cuántico valioso: un estado entrelazado compartido, como el estado de Bell de la lección anterior: 00+112.\frac {|00\rangle + |11\rangle}{\sqrt 2}. A veces también se denomina "par EPR" o "E-bit" (una unidad fundamental de entrelazamiento). Si Alice comparte un estado entrelazado de este tipo con Bob, puede teleportar el estado cuántico desconocido a Bob mediante una serie de operaciones cuánticas y transmitiéndole dos bits de información clásica.

4.1 El protocolo de teleportación cuántica

Requisito previo: Alice tiene un estado cuántico desconocido ψ|\psi \rangle que debe enviarse a Bob. Alice y Bob comparten un estado entrelazado de 2 qubits (E-bit), donde cada uno tiene uno de los qubits en su ubicación física.

Aquí describimos el procedimiento sin explicación detallada. Los detalles se implementan más abajo.

  1. Alice entrelaza ψ|\psi \rangle con su parte del E-bit mediante la puerta CNOT.
  2. Alice aplica una puerta Hadamard a ψ|\psi \rangle y mide ambos qubits en la base computacional.
  3. Alice envía a Bob sus resultados de medición ("00", "01", "10" o "11").
  4. Bob realiza una corrección en su parte del par E-bit basándose en los dos bits de información de Alice.
    • Para "00", Bob no hace nada.
    • Para "01", Bob aplica la puerta X.
    • Para "10", Bob aplica la puerta Z.
    • Para "11", Bob aplica iY = ZX.
  5. La parte de Bob del E-bit se convierte en ψ|\psi \rangle.

Esto también se cubre con más detalle en Fundamentos de la información cuántica. El procedimiento se hará más claro al implementarlo en Qiskit.

4.2 Circuito cuántico para simular la teleportación cuántica

Como es habitual, aplicamos el framework Qiskit Patterns. Esta sección se centra exclusivamente en el mapeo.

Paso 1: Mapear el problema a circuitos cuánticos y operadores

Para describir el escenario anterior, necesitamos un circuito con tres qubits: dos para el par entrelazado compartido por Alice y Bob y uno para el estado cuántico desconocido ψ|\psi\rangle.

from qiskit import QuantumCircuit
import numpy as np
# create 3-qubits circuit
qc = QuantumCircuit(3, 3)

qc.draw(output="mpl")

Salida de la celda de código anterior

Al principio, Alice tiene un estado cuántico desconocido ψ|\psi \rangle. Lo creamos usando la puerta UU.

# Create the unknown quantum state using the u-gate. Alice has this.
qc.u(theta, varphi, 0.0, 0)
qc.barrier() # for visual separation

qc.draw(output="mpl")

Salida de la celda de código anterior

Podemos visualizar el estado creado, pero solo porque sabemos qué parámetros se usaron en la puerta UU. Si este estado proviniera de un proceso cuántico complejo, no sería reconstruible sin muchas repeticiones del proceso y la recopilación de estadísticas (como en la tomografía).

# show the quantum state on bloch sphere
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

out_vector = Statevector(qc)

plot_bloch_multivector(out_vector)

Salida de la celda de código anterior

Antes de que este protocolo comience, presuponemos que Alice y Bob ya tienen un par entrelazado compartido. Si Alice y Bob realmente se encuentran en diferentes ubicaciones, pueden haber establecido el estado compartido antes de que el estado desconocido ψ|\psi\rangle fuera creado. Como estas operaciones ocurren en qubits diferentes, su orden no importa aquí, y este orden es práctico para la visualización.

# Alice and Bob are together in the same place and set up an entangled pair.
qc.h(1)
qc.cx(1, 2)
qc.barrier() # for visual separation.
# We can consider that Alice and Bob might move their qubits to different physical locations, now.

qc.draw(output="mpl")

Salida de la celda de código anterior

A continuación, Alice entrelaza ψ|\psi \rangle con su parte del E-bit compartido, usando la puerta CXCX y la puerta HH, y mide ambos en la base computacional.

# Alice entangles the unknown state with her part of the e-bit, using the CNOT gate and H gate.
qc.cx(0, 1)
qc.h(0)
qc.barrier()

# Alice measures the two qubits.
qc.measure(0, 0)
qc.measure(1, 1)

qc.draw(output="mpl")

Salida de la celda de código anterior

Alice envía a Bob sus resultados de medición ("00", "01", "10" o "11"), y Bob realiza una corrección en su parte del E-bit compartido basándose en los dos bits de información de Alice. Después, el qubit de Bob se convierte en ψ|\psi \rangle.

# Alice sent the results to Bob. Bob applies correction
with qc.if_test((0, 1)):
qc.z(2)
with qc.if_test((1, 1)):
qc.x(2)
qc.barrier()

qc.draw(output="mpl")

Salida de la celda de código anterior

Has completado un circuito de teleportación cuántica. Veamos el estado de salida de este circuito usando el simulador de vector de estado.

from qiskit_aer import StatevectorSimulator

backend = StatevectorSimulator()
out_vector = backend.run(qc, shots=1).result().get_statevector() # set shots = 1

plot_bloch_multivector(out_vector)

Salida de la celda de código anterior

Puedes ver que el estado cuántico creado por la puerta UU del qubit 0 (el qubit que originalmente contenía el estado secreto) fue transferido al qubit 2 (el qubit de Bob).

Puedes ejecutar la celda anterior varias veces para convencerte. Quizás notes que los qubits 0 y 1 cambian, pero el qubit 2 siempre permanece en el estado ψ|\psi\rangle.

4.3 Ejecutar y confirmar el resultado aplicando U inversa

Arriba verificamos visualmente que el estado teleportado se ve correcto. Otra forma de confirmar la teleportación correcta es aplicar la puerta UU inversa al qubit de Bob, de modo que podamos medir "0". Como U1UU^{-1}U da la identidad: si el qubit de Bob está en el estado U0U|0\rangle, aplicar la inversa debería dar U1U0=0U^{-1}U|0\rangle=|0\rangle.

# Apply the inverse of u-gate to measure |0>
qc.u(theta, varphi, 0.0, 2).inverse() # inverse of u(theta,varphi,0.0)
qc.measure(2, 2) # add measurement gate

qc.draw(output="mpl")

Salida de la celda de código anterior

Ejecutamos el circuito primero con el AerSimulator antes de pasar a un computador cuántico real.

from qiskit_aer import AerSimulator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Sampler
from qiskit.visualization import plot_histogram

# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=nshots)
result = job.result()

# Extract counts data
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'011': 2510, '010': 2417, '000': 2635, '001': 2438}

Salida de la celda de código anterior

En la notación Little-Endian, el qubit 2 es el más a la izquierda (o el inferior en las etiquetas de columnas). Observa que el qubit más a la izquierda en las etiquetas de columnas es un 0 en todos los resultados posibles. Esto nos indica que la probabilidad de medir q2q_2 en el estado 0|0\rangle es del 100%. Este es el resultado esperado y confirma que el protocolo de teleportación ha funcionado correctamente.

4.4 Teleportación en un computador cuántico real

A continuación, ejecutamos la teleportación en un computador cuántico real. Usando la función de circuitos dinámicos, podemos reaccionar a los resultados de medición durante el circuito y ejecutar las operaciones condicionales del circuito de teleportación en tiempo real. Para resolver problemas con computadores cuánticos reales, seguimos los cuatro pasos de Qiskit Patterns.

  1. Mapear el problema a circuitos cuánticos y operadores
  2. Optimizar para el hardware objetivo
  3. Ejecutar en el hardware objetivo
  4. Postprocesar los resultados

Ejercicio 3: Construir el circuito de teleportación

Intenta construir todo el circuito de teleportación desde cero para poner a prueba tu comprensión. Desplázate hacia arriba si necesitas una referencia.

Solución:

# Step 1: Map problem to quantum circuits and operators
# Create the circuit with 3-qubits and 1-bit
qc = QuantumCircuit(3, 3)

# Alice creates an unknown quantum state using the u-gate.
qc.u(theta, varphi, 0.0, 0)
qc.barrier() # for visual separation

# Eve creates EPR pair and sends q1 to Alice and q2 to Bob
##your code goes here##
qc.h(1)
qc.cx(1, 2)
qc.barrier()

# Alice entangles the unknown state with her EPR part, using the CNOT gate and H gate.
##your code goes here##
qc.cx(0, 1)
qc.h(0)
qc.barrier()

# Alice measures the two qubits.
##your code goes here##
qc.measure(0, 0)
qc.measure(1, 1)

# Alice sent the results to Bob. Now, Bob applies correction
##your code goes here##
with qc.if_test((0, 1)):
qc.z(2)
with qc.if_test((1, 1)):
qc.x(2)
qc.barrier()

# Apply the inverse of u-gate to measure |0>
qc.u(theta, varphi, 0.0, 2).inverse()
qc.measure(2, 2)

qc.draw(output="mpl")

Salida de la celda de código anterior

Como recordatorio: aplicar la puerta UU inversa solo sirve para verificar el comportamiento esperado. No es parte de la transferencia del estado a Bob, y no usaríamos esta puerta UU inversa si el único objetivo fuera la transferencia de información cuántica.

Paso 2: Optimizar para el hardware objetivo

Para ejecutar en hardware, importa QiskitRuntimeService y carga tus credenciales guardadas. Selecciona el backend con la cola más corta.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
service.backends()
[<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_torino')>]
# You can also identify the least busy device
backend = service.least_busy(operational=True)
print("The least busy device is ", backend)
The least busy device is  <IBMBackend('ibm_brisbane')>
# You can specify the device
# backend = service.backend('ibm_brisbane')

Veamos el mapa de acoplamiento del dispositivo seleccionado.

from qiskit.visualization import plot_gate_map

plot_gate_map(backend)

Salida de la celda de código anterior

Diferentes dispositivos pueden tener diferentes mapas de acoplamiento, y cada dispositivo tiene qubits y acopladores que son más eficientes que otros. Además, diferentes computadores cuánticos pueden tener diferentes puertas nativas (puertas que el hardware puede ejecutar directamente). Durante la transpilación, el circuito cuántico abstracto se reescribe en puertas que el computador cuántico objetivo puede ejecutar, y se elige el mapeo óptimo a qubits físicos (entre otras cosas). La transpilación es un tema amplio y multifacético. Puedes encontrar más información en la referencia de la API.

# Step 2: Optimize for target hardware
# Transpile the circuit into basis gates executable on the hardware
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
qc_compiled = pm.run(qc)

qc_compiled.draw("mpl", idle_wires=False, fold=-1)

Salida de la celda de código anterior

Paso 3: Ejecutar el circuito

Usando el primitivo Runtime Sampler, ejecutamos el circuito objetivo.

# Step 3: Execute the target circuit
sampler = Sampler(backend)
job = sampler.run([qc_compiled])
job_id = job.job_id()
print("job id:", job_id)
job id: d13nkhpn2txg008jt0d0
# Check the job status
job.status()
'DONE'

También puedes verificar el estado del trabajo a través de tu panel de IBM Quantum®.

# If the Notebook session got disconnected you can also check your job status by running the following code
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
job_real = service.job(job.job_id()) # Input your job-id between the quotations
job_real.status()
'DONE'

Cuando se muestre 'DONE', puedes obtener el resultado con la siguiente celda.

# Execute after 'DONE' is displayed
result_real = job_real.result()
print(result_real[0].data.c.get_counts())
{'001': 992, '110': 430, '011': 579, '010': 605, '111': 402, '000': 925, '100': 57, '101': 106}

Paso 4: Postprocesar los resultados

# Step 4: Post-process the results
from qiskit.visualization import plot_histogram

plot_histogram(result_real[0].data.c.get_counts())

Salida de la celda de código anterior

Puedes interpretar los resultados anteriores directamente. Alternativamente, puedes usar marginal_count para filtrar los resultados de Bob en el qubit 2.

# trace out Bob's results on qubit 2
from qiskit.result import marginal_counts

bobs_qubit = 2
real_counts = result_real[0].data.c.get_counts()
bobs_counts = marginal_counts(real_counts, [bobs_qubit])
plot_histogram(bobs_counts)

Salida de la celda de código anterior

Como vemos aquí, hay algunos resultados donde se midió 1|1 \rangle. Estos se deben al ruido y los errores. En particular, los circuitos dinámicos tienen una tasa de error más alta porque la medición en medio del circuito consume tiempo.

4.5 Conclusiones clave sobre la teleportación cuántica

Usando un par de qubits entrelazados compartidos (E-bit), podemos transferir un estado cuántico a un amigo distante.

  1. ¿Puede la teleportación cuántica enviar el estado cuántico más rápido que la luz? No, porque Alice debe comunicar los resultados de medición a Bob por vía clásica.

  2. ¿Violaría la teleportación cuántica el "teorema de no clonación", que prohíbe copiar un estado cuántico? No, porque el estado cuántico original que Alice tenía en uno de sus qubits se perdió durante la medición: colapsó a 0|0\rangle o 1|1\rangle.

5. Codificación superdensa

Prácticamente la misma configuración se puede utilizar para otro propósito. Supongamos que Alice quiere enviar a Bob dos bits de información clásica, pero no tiene medios de comunicación clásicos. Sin embargo, comparte un par entrelazado con Bob y puede enviar su qubit a la ubicación de Bob. Observa la diferencia con el protocolo de teleportación cuántica: en la teleportación, la comunicación clásica estaba disponible y el objetivo era enviar un estado cuántico. Aquí, la comunicación clásica no es posible, y utilizan la transferencia de un qubit para intercambiar dos bits de información clásica.

5.1 El protocolo de codificación superdensa

Requisito previo: Alice tiene dos bits de información, por ejemplo a1a2{00,01,10,11}a_1a_2 \in \{00, 01, 10, 11\}. Alice y Bob comparten un par entrelazado (E-bit), pero no pueden comunicarse clásicamente.

  1. Alice realiza una de las siguientes operaciones en su parte del E-bit.
    • Si a1a2=00a_1a_2 = 00, no hace nada.
    • Si a1a2=01a_1a_2 = 01, aplica la puerta Z.
    • Si a1a2=10a_1a_2 = 10, aplica la puerta X.
    • Si a1a2=11a_1a_2 = 11, aplica la puerta Z y la puerta X.
  2. Alice envía su parte del E-bit a la ubicación de Bob.
  3. Bob aplica una puerta CNOT (el qubit de Alice como control, su qubit como objetivo), luego una puerta H al qubit de Alice, y mide ambos qubits. Los posibles estados de salida y resultados de las operaciones de Bob son:
00+112CX01H000\frac {|00\rangle + |11\rangle}{\sqrt 2} \rightarrow CX_{01}\otimes H_0 \rightarrow |00\rangle 00112CX01H001\frac {|00\rangle - |11\rangle}{\sqrt 2} \rightarrow CX_{01}\otimes H_0 \rightarrow |01\rangle 10+012CX01H010\frac {|10\rangle + |01\rangle}{\sqrt 2} \rightarrow CX_{01}\otimes H_0 \rightarrow |10\rangle 10012CX01H011\frac {|10\rangle - |01\rangle}{\sqrt 2} \rightarrow CX_{01}\otimes H_0 \rightarrow -|11\rangle

El signo negativo en 11-|11\rangle es una fase global y por lo tanto no es medible.

5.2 Circuito cuántico para simular la codificación superdensa

Basándote en el protocolo de codificación superdensa, puedes construir el circuito de codificación superdensa de la siguiente manera. Intenta cambiar el mensaje msg que Alice quiere transmitir a Bob.

from qiskit import QuantumCircuit

Los pasos de Qiskit Patterns se indican en los comentarios del código.

# Step 1: Map problem to quantum circuits and operators
# Create 2-qubits circuit
qc = QuantumCircuit(2, 2)

# Eve creates EPR pair and send q0 to Alice and q1 to Bob
qc.h(0)
qc.cx(0, 1)
qc.barrier()

# set message which Alice wants to transform to Bob
msg = "11" # You can change the message

if msg == "00":
pass
elif msg == "10":
qc.x(0)
elif msg == "01":
qc.z(0)
elif msg == "11":
qc.z(0)
qc.x(0)

qc.barrier()
# Bob receives EPR qubit from Alice and performs unitary operations
qc.cx(0, 1)
qc.h(0)
qc.barrier()

# Bob measures q0 and q1
qc.measure(0, 0)
qc.measure(1, 1)

qc.draw(output="mpl")

Salida de la celda de código anterior

# We will execute on a simulator first
from qiskit_aer import AerSimulator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Sampler

# Define backend
backend = AerSimulator()
shots = 1000

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job_sim = sampler.run([isa_qc], shots=shots)
result_sim = job_sim.result()

# Extract counts data
counts = result_sim[0].data.c.get_counts()
print(counts)
{'11': 1000}
# Visualize the results
from qiskit.visualization import plot_histogram

plot_histogram(counts)

Salida de la celda de código anterior

Puedes ver que Bob ha recibido el mensaje que Alice quería enviarle.

A continuación, probamos con un computador cuántico real.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(operational=True)
print("The least busy device is ", backend)
The least busy device is  <IBMBackend('ibm_brisbane')>
# Step 1 was already completed before the simulator job above.
# Step 2: Optimize for target hardware
# Transpile the circuit into basis gates executable on the hardware
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
qc_compiled = pm.run(qc)

qc_compiled.draw("mpl", idle_wires=False)

Salida de la celda de código anterior

# Step 3:Execute the target circuit
sampler = Sampler(backend)
job = sampler.run([qc_compiled])
job_id = job.job_id()
print("job id:", job_id)
job id: d13nnyq3grvg008j0zag
# Check the job status
job.status()
'DONE'
# If the Notebook session got disconnected you can also check your job status by running the following code
# from qiskit_ibm_runtime import QiskitRuntimeService
# service = QiskitRuntimeService()
job = service.job(job_id) # Input your job-id between the quotations
job.status()
'DONE'
# Execute after job has successfully run
real_result = job.result()
print(real_result[0].data.c.get_counts())
{'11': 3942, '01': 107, '10': 41, '00': 6}
# Step 4: post-process the results
from qiskit.visualization import plot_histogram

plot_histogram(real_result[0].data.c.get_counts())

Salida de la celda de código anterior

El resultado coincide con nuestras expectativas. Es notable que la codificación superdensa en un computador cuántico real presenta menos errores que la teleportación cuántica. Una razón podría ser que la teleportación cuántica utiliza circuitos dinámicos, mientras que la codificación superdensa no. Aprenderemos más sobre errores en circuitos cuánticos en lecciones posteriores.

6. Resumen

En esta lección implementamos dos protocolos cuánticos. Aunque los escenarios con amigos distantes están algo alejados de la computación cuántica en una sola QPU, tienen aplicaciones en la computación cuántica y nos ayudan a comprender mejor la transferencia de información cuántica.

  • Teleportación cuántica: Aunque no podemos copiar estados cuánticos, podemos teleportar estados cuánticos desconocidos usando entrelazamiento compartido.
  • Codificación superdensa cuántica: Un par entrelazado compartido y la transferencia de un qubit permiten la comunicación de dos bits de información clásica.
# See the version of Qiskit
import qiskit

qiskit.__version__
'2.0.2'