Spanish
Idiomas
English
Japanese
Spanish

Nota

Esta página fue generada a partir de docs/tutorials/sea_with_sampler.ipynb.

Algoritmo de Solucionador Propio Espectroscópico con Sampler

Este tutorial demuestra la capacidad de enviar circuitos flexibles a la primitiva Sampler mediante la realización de un ejemplo simple del Algoritmo de Solucionador Propio Espectroscópico (Spectroscopic Eigensolver Algorithm, SEA) (arXiv:2202.12910). El SEA se utiliza para la simulación cuántica de modelos Hamiltonianos y funciona mediante la interacción de un qubit auxiliar “probe” (“sonda”) con un registro de simulación. La energía del qubit sonda se barre y los valores propios de la simulación Hamiltoniana se observan como picos o caídas en la respuesta, similar a la herramienta experimental de la espectroscopía. Debido a que cada punto (energía) es un circuito cuántico diferente, esta técnica es costosa con respecto a la cantidad de circuitos necesarios. El Sampler proporciona la flexibilidad de enviar un solo circuito con los Parameters necesarios.

Configura tu entorno de desarrollo local

Este tutorial requiere una instancia del servicio Qiskit Runtime. Si aún no lo has hecho, sigue estos pasos para configurar uno.

[1]:
# load necessary Runtime libraries
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session

backend = "ibmq_qasm_simulator"  # use the simulator

Modelo Hamiltoniano simple

Consideremos una matriz de Pauli X actuando sobre un qubit,

\[H_{\rm Pauli}/\hbar = \mu X\]

donde podemos colocar \(\mu\) más tarde, o incluso barrer sus valores también. El SEA funciona tomando el Hamiltoniano del modelo Pauli (el qubit) y construyendo un Hamiltoniano de «resonancia» más grande que incluye tanto el registro de simulación como el qubit sonda q0 a través de

\[H_{\rm res} / \hbar = -\frac{1}{2} \omega IZ + c XX + H_{\rm Pauli}/\hbar \otimes I\]

donde la frecuencia angular \(\omega\) corresponde a la energía del qubit sonda, y \(c\) es el acoplamiento entre el qubit sonda y un qubit en el registro de simulación (q1 en este caso ). Las letras \(I\), \(X\), y \(Z\) corresponden a las matrices de espín de Pauli y su orden refleja en qué qubit operan (ten en cuenta que esta notación es little-endian). Estableceremos \(\hbar \equiv 1\) en el siguiente código.

Podemos construir los circuitos SEA con herramientas de Qiskit Opflow.

[2]:
from qiskit.circuit import Parameter
from qiskit.opflow import I, X, Z

mu = Parameter("$\\mu$")
ham_pauli = mu * X
[3]:
cc = Parameter("$c$")
ww = Parameter("$\\omega$")

ham_res = -(1 / 2) * ww * (I ^ Z) + cc * (X ^ X) + (ham_pauli ^ I)

El tiempo evoluciona el Hamiltoniano de resonancia.

[4]:
tt = Parameter("$t$")
U_ham = (tt * ham_res).exp_i()

Del operador de evolución temporal \(U_{\rm ham}\), usamos la expansión Suzuki-Trotter para convertir este operador en circuitos cuánticos que implementan pasos de tiempo discretos de la simulación. Cuanto más pequeños sean los pasos de tiempo (más pasos de Trotter), más preciso será el circuito cuántico, pero también más profundo, lo que podría introducir errores al ejecutar en hardware cuántico ruidoso. Luego, transpilamos los circuitos a las compuertas base del backend de IBM y medimos solo el qubit sonda q0.

[5]:
from qiskit import transpile
from qiskit.circuit import ClassicalRegister
from qiskit.opflow import PauliTrotterEvolution, Suzuki
import numpy as np

num_trot_steps = 5
total_time = 10
cr = ClassicalRegister(1, "c")

spec_op = PauliTrotterEvolution(
    trotter_mode=Suzuki(order=2, reps=num_trot_steps)
).convert(U_ham)
spec_circ = spec_op.to_circuit()
spec_circ_t = transpile(spec_circ, basis_gates=["sx", "rz", "cx"])
spec_circ_t.add_register(cr)
spec_circ_t.measure(0, cr[0])
[5]:
<qiskit.circuit.instructionset.InstructionSet at 0x1273c9a00>
[6]:
spec_circ_t.draw("mpl")
[6]:
../_images/tutorials_sea_with_sampler_10_0.png

Ahora fijemos nuestros parámetros y hagamos un barrido sobre la frecuencia con varios puntos num_pts. Los valores propios de nuestro modelo Hamiltoniano \(H_{\rm Pauli}\) son \(\pm \mu\), por lo que debemos elegir un rango que incluya esos números.

Ten en cuenta que las claves y valores de los Parameters deben separarse y convertirse en una List de Lists. Las claves nos dan los Parameters dentro de cada circuito. En este caso, tenemos solo un circuito, por lo que la List de claves contiene una sola List. Para los valores de Parameter, hay una List para cada valor de ww.

[7]:
# fixed Parameters
fixed_params = {cc: 0.3, mu: 0.7, tt: total_time}
# Parameter value for single circuit
param_keys = list(spec_circ_t.parameters)

# run through all the ww values to create a List of Lists of Parameter value
num_pts = 101
wvals = np.linspace(-2, 2, num_pts)
param_vals = []
for wval in wvals:
    all_params = {**fixed_params, **{ww: wval}}
    param_vals.append([all_params[key] for key in param_keys])

Cuando llamamos al sampler, especificamos una lista de circuits que apunta a los circuitos que deseamos ejecutar y los valores de los parámetros para cada circuito.

[8]:
with Session(backend=backend):
    sampler = Sampler()
    job = sampler.run(
        circuits=[spec_circ_t] * num_pts, parameter_values=param_vals, shots=1e5
    )
    result = job.result()

Construye los valores esperados de \(Z\) convirtiendo las cuasi-probabilidades en \(\langle Z \rangle\).

[9]:
Zexps = []
for dist in result.quasi_dists:
    if 1 in dist:
        Zexps.append(1 - 2 * dist[1])
    else:
        Zexps.append(1)

Como verificación rápida, calcularemos los valores esperados exactos con Qiskit Opflow.

[10]:
from qiskit.opflow import PauliExpectation, Zero

param_bind = {cc: 0.3, mu: 0.7, tt: total_time}

init_state = Zero ^ 2
obsv = I ^ Z
Zexp_exact = (U_ham @ init_state).adjoint() @ obsv @ (U_ham @ init_state)

diag_meas_op = PauliExpectation().convert(Zexp_exact)
Zexact_values = []
for w_set in wvals:
    param_bind[ww] = w_set
    Zexact_values.append(np.real(diag_meas_op.bind_parameters(param_bind).eval()))

Y graficar todo junto muestra que la energía en la que ocurren nuestros picos es \(\pm \mu\).

[11]:
import matplotlib.pyplot as plt

plt.style.use("dark_background")

fig, ax = plt.subplots(dpi=100)
ax.plot([-param_bind[mu], -param_bind[mu]], [0, 1], ls="--", color="purple")
ax.plot([param_bind[mu], param_bind[mu]], [0, 1], ls="--", color="purple")
ax.plot(wvals, Zexact_values, label="Exact")
ax.plot(wvals, Zexps, label=f"{backend}")
ax.set_xlabel(r"$\omega$ (arb)")
ax.set_ylabel(r"$\langle Z \rangle$ Expectation")
ax.legend()
[11]:
<matplotlib.legend.Legend at 0x12c286cd0>
../_images/tutorials_sea_with_sampler_20_1.png
[12]:
import qiskit_ibm_runtime

qiskit_ibm_runtime.version.get_version_info()
[12]:
'0.7.0'
[13]:
from qiskit.tools.jupyter import *

%qiskit_version_table

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.0
qiskit-aer0.10.3
qiskit-ignis0.7.0
qiskit-ibmq-provider0.18.3
qiskit0.35.0
System information
Python version3.9.10
Python compilerClang 11.1.0
Python buildmain, Feb 1 2022 21:27:48
OSDarwin
CPUs2
Memory (Gb)16.0
Mon Apr 11 16:17:45 2022 EDT