Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Instancias y extensiones

En este capítulo se presentan varios algoritmos variacionales cuánticos, entre ellos:

A través de estos algoritmos aprenderás varias ideas de diseño que pueden incorporarse en algoritmos variacionales personalizados, como pesos, penalizaciones, sobremuestreo y submuestreo. Te animamos a experimentar con estos conceptos y a compartir tus hallazgos con la comunidad.

El marco de patrones de Qiskit aplica a todos estos algoritmos, pero solo señalaremos los pasos explícitamente en el primer ejemplo.

Eigensolver cuántico variacional (VQE)​

VQE es uno de los algoritmos variacionales cuánticos más utilizados, y establece una plantilla sobre la que se construyen otros algoritmos.

Un diagrama que muestra cómo VQE usa el estado de referencia y el ansatz para estimar una función de coste, y luego itera usando parámetros variacionales.

Paso 1: Mapear las entradas clásicas a un problema cuántico​

Esquema teórico​

El esquema de VQE es sencillo:

  • Preparar los operadores de referencia URU_R

    • Partimos del estado ∣0⟩|0\rangle y llegamos al estado de referencia ∣ρ⟩|\rho\rangle
  • Aplicar la forma variacional UV(θ⃗i,j)U_V(\vec\theta_{i,j}) para crear un ansatz UA(θ⃗i,j)U_A(\vec\theta_{i,j})

    • Pasamos del estado ∣ρ⟩|\rho\rangle a UV(θ⃗i,j)∣ρ⟩=∣ψ(θ⃗i,j)⟩U_V(\vec\theta_{i,j})|\rho\rangle = |\psi(\vec\theta_{i,j})\rangle
  • Arranque en i=0i=0 si tenemos un problema similar (normalmente obtenido mediante simulación clásica o muestreo)

    • Cada optimizador se inicializará de forma diferente, generando un conjunto inicial de vectores de parámetros Θ0:=θ⃗0,j∣j∈Jopt0\Theta_0 := \\{ {\vec\theta_{0,j} | j \in \mathcal{J}_\text{opt}^0} \\} (por ejemplo, a partir de un punto inicial θ⃗0\vec\theta_0).
  • Evaluar la función de coste C(θ⃗i,j):=⟨ψ(θ⃗)∣H^∣ψ(θ⃗)⟩C(\vec\theta_{i,j}) := \langle \psi(\vec{\theta}) | \hat{H} | \psi(\vec{\theta})\rangle para todos los estados preparados en un ordenador cuántico.

  • Usar un optimizador clásico para seleccionar el siguiente conjunto de parámetros Θi+1\Theta_{i+1}.

  • Repetir el proceso hasta alcanzar la convergencia.

Este es un ciclo de optimización clásico sencillo en el que evaluamos la función de coste. Algunos optimizadores pueden requerir varias evaluaciones para calcular un gradiente, determinar la siguiente iteración o verificar la convergencia.

A continuación se muestra el ejemplo para el siguiente observable:

O^1=2II−2XX+3YY−3ZZ,\hat{O}_1 = 2 II - 2 XX + 3 YY - 3 ZZ,

Implementación​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import TwoLocal
import numpy as np

theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
observable = SparsePauliOp.from_list([("II", 2), ("XX", -2), ("YY", 3), ("ZZ", -3)])

reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)

variational_form = TwoLocal(
2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)

ansatz = reference_circuit.compose(variational_form)

ansatz.decompose().draw("mpl")

Salida de la celda de código anterior

def cost_func_vqe(parameters, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator

Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance

Returns:
float: Energy estimate
"""

estimator_job = estimator.run([(ansatz, hamiltonian, [parameters])])
estimator_result = estimator_job.result()[0]

cost = estimator_result.data.evs[0]
return cost
from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Podemos usar esta función de coste para calcular los parámetros óptimos:

# SciPy minimizer routine
from scipy.optimize import minimize

x0 = np.ones(8)

result = minimize(
cost_func_vqe, x0, args=(ansatz, observable, estimator), method="COBYLA"
)

result
message: Optimization terminated successfully.
success: True
status: 1
fun: -5.999999982445723
x: [ 1.741e+00 9.606e-01 1.571e+00 2.115e-05 1.899e+00
1.243e+00 6.063e-01 6.063e-01]
nfev: 136
maxcv: 0.0

Paso 2: Optimizar el problema para la ejecución cuántica​

Seleccionaremos el backend menos ocupado e importaremos los componentes necesarios de qiskit_ibm_runtime.

from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Session, EstimatorOptions
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
print(backend)
<IBMBackend('ibm_brisbane')>

Transpilaremos el circuito usando el gestor de pasadas predefinido con nivel de optimización 3, y aplicaremos el diseño correspondiente al observable.

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_ansatz = pm.run(ansatz)
isa_observable = observable.apply_layout(layout=isa_ansatz.layout)

Paso 3: Ejecutar usando primitivas de IBM Quantum​

Ya estamos listos para ejecutar nuestro cálculo en hardware de IBM Quantum®. Dado que la minimización de la función de coste es muy iterativa, iniciaremos una sesión de Runtime. De este modo, solo tendremos que esperar en la cola una vez. Una vez que el trabajo comience a ejecutarse, cada iteración con actualizaciones de parámetros se ejecutará de inmediato.

x0 = np.ones(8)

estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)

with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

result = minimize(
cost_func_vqe,
x0,
args=(isa_ansatz, isa_observable, estimator),
method="COBYLA",
options={"maxiter": 200, "disp": True},
)
session.close()
print(result)

Paso 4: Posprocesar y devolver el resultado en formato clásico​

Podemos ver que la rutina de minimización terminó con éxito, lo que significa que se alcanzó la tolerancia predeterminada del optimizador clásico COBYLA. Si necesitamos un resultado más preciso, podemos especificar una tolerancia menor. Esto podría ser necesario, ya que el resultado se alejó varios puntos porcentuales del obtenido por el simulador anterior.

El valor de x obtenido es la mejor estimación actual de los parámetros que minimizan la función de coste. Si se itera para obtener mayor precisión, esos valores deben usarse en lugar del x0 inicial (un vector de unos).

Por último, cabe señalar que la función fue evaluada 96 veces durante el proceso de optimización. Este número puede diferir de la cantidad de pasos de optimización, ya que algunos optimizadores requieren múltiples evaluaciones de la función en un solo paso, como ocurre al estimar un gradiente.

VQE de búsqueda en subespacios (SSVQE)​

SSVQE es una variante de VQE que permite obtener los primeros kk valores propios de un observable H^\hat{H} con valores propios {λ0,λ1,...,λN−1}\{\lambda_0, \lambda_1,...,\lambda_{N-1}\}, donde N≥kN\geq k. Sin pérdida de generalidad, suponemos que λ0<λ1<...<λN−1\lambda_0<\lambda_1<...<\lambda_{N-1}. SSVQE introduce una nueva idea al añadir pesos para priorizar la optimización del término con mayor peso.

Un diagrama que muestra cómo el VQE de búsqueda en subespacios utiliza los componentes del algoritmo variacional.

Para implementar este algoritmo, necesitamos kk estados de referencia mutuamente ortogonales {∣ρj⟩}j=0k−1\{ |\rho_j\rangle \}_{j=0}^{k-1}, lo que significa que ⟨ρj∣ρl⟩=δjl\langle \rho_j | \rho_l \rangle = \delta_{jl} para j,l<kj,l<k. Estos estados pueden construirse usando operadores de Pauli. La función de coste de este algoritmo es:

C(θ⃗):=∑j=0k−1wj⟨ρj∣UV†(θ⃗)H^UV(θ⃗)∣ρj⟩:=∑j=0k−1wj⟨ψj(θ⃗)∣H^∣ψj(θ⃗)⟩\begin{aligned} C(\vec{\theta}) & := \sum_{j=0}^{k-1} w_j \langle \rho_j | U_{V}^{\dagger}(\vec{\theta})\hat{H} U_{V}(\vec{\theta})|\rho_j \rangle \\[1mm] & := \sum_{j=0}^{k-1} w_j \langle \psi_{j}(\vec{\theta}) | \hat{H} | \psi_{j}(\vec{\theta}) \rangle \\[1mm] \end{aligned}

donde wjw_j es un número positivo arbitrario tal que si j<l<kj<l<k entonces wj>wlw_j>w_l, y UV(θ⃗)U_V(\vec{\theta}) es la forma variacional definida por el usuario.

El algoritmo SSVQE se basa en el hecho de que los estados propios correspondientes a distintos valores propios son mutuamente ortogonales. En particular, el producto interno de UV(θ⃗)∣ρj⟩U_V(\vec{\theta})|\rho_j\rangle y UV(θ⃗)∣ρl⟩U_V(\vec{\theta})|\rho_l\rangle puede expresarse como:

⟨ρj∣UV†(θ⃗)UV(θ⃗)∣ρl⟩=⟨ρj∣I∣ρl⟩=⟨ρj∣ρl⟩=δjl\begin{aligned} \langle \rho_j | U_{V}^{\dagger}(\vec{\theta})U_{V}(\vec{\theta})|\rho_l \rangle & = \langle \rho_j | I |\rho_l \rangle \\[1mm] & = \langle \rho_j | \rho_l \rangle \\[1mm] & = \delta_{jl} \end{aligned}

La primera igualdad es válida porque UV(θ⃗)U_{V}(\vec{\theta}) es un operador cuántico y, por tanto, unitario. La última igualdad se cumple por la ortogonalidad de los estados de referencia ∣ρj⟩|\rho_j\rangle. El hecho de que la ortogonalidad se conserve a través de transformaciones unitarias está profundamente relacionado con el principio de conservación de la información, tal como se expresa en la ciencia de la información cuántica. Desde esta perspectiva, las transformaciones no unitarias representan procesos en los que se pierde o se inyecta información.

Los pesos wjw_j ayudan a garantizar que todos los estados sean estados propios. Si los pesos son suficientemente distintos, el término con mayor peso (es decir, w0w_0) recibirá prioridad durante la optimización sobre los demás. Como resultado, el estado resultante UV(θ⃗)∣ρ0⟩U_{V}(\vec{\theta})|\rho_0 \rangle se convertirá en el estado propio correspondiente a λ0\lambda_0. Dado que {UV(θ⃗)∣ρj⟩}j=0k−1\{ U_{V}(\vec{\theta})|\rho_j\rangle \}_{j=0}^{k-1} son mutuamente ortogonales, los estados restantes serán ortogonales a este y, por tanto, estarán contenidos en el subespacio correspondiente a los valores propios {λ1,...,λN−1}\{\lambda_1,...,\lambda_{N-1}\}.

Aplicando el mismo argumento al resto de los términos, la siguiente prioridad sería el término con peso w1w_1, de modo que UV(θ⃗)∣ρ1⟩U_{V}(\vec{\theta})|\rho_1 \rangle sería el estado propio correspondiente a λ1\lambda_1, y los demás términos estarían contenidos en el espacio propio de {λ2,...,λN−1}\{\lambda_2,...,\lambda_{N-1}\}.

Razonando inductivamente, deducimos que UV(θ⃗)∣ρj⟩U_{V}(\vec{\theta})|\rho_j \rangle será un estado propio aproximado de λj\lambda_j para 0≤j<k0\leq j < k.

Esquema teórico​

El algoritmo SSVQE puede resumirse de la siguiente manera:

  • Preparar varios estados de referencia aplicando un unitario U_R a kk estados distintos de la base computacional

    • Este algoritmo requiere el uso de kk estados de referencia mutuamente ortogonales {∣ρj⟩}j=0k−1\{ |\rho_j\rangle \}_{j=0}^{k-1}, de modo que ⟨ρj∣ρl⟩=δjl\langle \rho_j | \rho_l \rangle = \delta_{jl} para j,l<kj,l<k.
  • Aplicar la forma variacional UV(θ⃗i,j)U_V(\vec\theta_{i,j}) a cada estado de referencia, obteniendo el siguiente ansatz UA(θ⃗i,j)U_A(\vec\theta_{i,j}).

  • Arranque en i=0i=0 si se dispone de un problema similar (normalmente obtenido mediante simulación clásica o muestreo).

  • Evaluar la función de coste C(θ⃗i,j):=∑j=0k−1wj⟨ψj(θ⃗)∣H^∣ψj(θ⃗)⟩C(\vec\theta_{i,j}) := \sum_{j=0}^{k-1} w_j \langle \psi_{j}(\vec{\theta}) | \hat{H} | \psi_{j}(\vec{\theta}) \rangle para todos los estados preparados en un ordenador cuántico.

    • Esto puede separarse en el cálculo del valor esperado de un observable ⟨ψj(θ⃗)∣H^∣ψj(θ⃗)⟩\langle \psi_{j}(\vec{\theta}) | \hat{H} | \psi_{j}(\vec{\theta}) \rangle y la multiplicación del resultado por wjw_j.

    • A continuación, la función de coste devuelve la suma de todos los valores esperados ponderados.

  • Usar un optimizador clásico para determinar el siguiente conjunto de parámetros Θi+1\Theta_{i+1}.

  • Repetir los pasos anteriores hasta alcanzar la convergencia.

Reconstruirás la función de coste de SSVQE en la evaluación, pero aquí tienes el siguiente fragmento para orientar tu solución:

import numpy as np

def cost_func_ssvqe(
params, initialized_anastz_list, weights, ansatz, hamiltonian, estimator
):
# """Return estimate of energy from estimator

# Parameters:
# params (ndarray): Array of ansatz parameters
# initialized_anastz_list (list QuantumCircuit): Array of initialised ansatz with reference
# weights (list): List of weights
# ansatz (QuantumCircuit): Parameterized ansatz circuit
# hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
# estimator (Estimator): Estimator primitive instance

# Returns:
# float: Weighted energy estimate
# """

energies = []

# Define SSVQE

weighted_energy_sum = np.dot(energies, weights)
return weighted_energy_sum

Deflación cuántica variacional (VQD)​

VQD es un método iterativo que extiende VQE para obtener los primeros kk valores propios de un observable H^\hat{H} con valores propios {λ0,λ1,...,λN−1}\{\lambda_0, \lambda_1,...,\lambda_{N-1}\}, donde N≥kN\geq k, en lugar de solo el primero. Para el resto de esta sección, asumiremos, sin pérdida de generalidad, que λ0≤λ1≤...≤λN−1\lambda_0\leq\lambda_1\leq...\leq\lambda_{N-1}. VQD introduce la noción de un coste de penalización para guiar el proceso de optimización.

Un diagrama que muestra cómo VQD usa los componentes de un algoritmo variacional. VQD introduce un término de penalización, denotado como β\beta, para equilibrar la contribución de cada término de solapamiento al coste. Este término de penalización sirve para sancionar el proceso de optimización si no se logra la ortogonalidad. Imponemos esta restricción porque los estados propios de un observable, o de un operador hermítico, correspondientes a distintos valores propios son siempre mutuamente ortogonales, o pueden hacerse así en el caso de degeneración o valores propios repetidos. Así, al imponer la ortogonalidad con el estado propio correspondiente a λ0\lambda_0, estamos optimizando efectivamente sobre el subespacio que corresponde al resto de los valores propios {λ1,λ2,...,λN−1}\{\lambda_1, \lambda_2,..., \lambda_{N-1}\}. Aquí, λ1\lambda_1 es el valor propio más bajo de entre los restantes y, por tanto, la solución óptima del nuevo problema puede obtenerse usando el teorema variacional.

La idea general detrás de VQD es usar VQE de la forma habitual para obtener el valor propio más bajo λ0:=C0(θ⃗0)≡CVQE(θ⃗0)\lambda_0 := C_0(\vec\theta^0) \equiv C_\text{VQE}(\vec\theta^0) junto con el estado propio (aproximado) correspondiente ∣ψ(θ0⃗)⟩|\psi(\vec{\theta^0})\rangle para algún vector de parámetros óptimo θ0⃗\vec{\theta^0}. Luego, para obtener el siguiente valor propio λ1>λ0\lambda_1 > \lambda_0, en lugar de minimizar la función de coste C0(θ⃗):=⟨ψ(θ⃗)∣H^∣ψ(θ⃗)⟩C_0(\vec{\theta}) := \langle \psi(\vec{\theta}) | \hat{H} | \psi(\vec{\theta})\rangle, optimizamos:

C1(θ⃗):=C0(θ⃗)+β0∣⟨ψ(θ⃗)∣ψ(θ0⃗)⟩∣2C_1(\vec{\theta}) := C_0(\vec{\theta})+ \beta_0 |\langle \psi(\vec{\theta})| \psi(\vec{\theta^0})\rangle |^2

El valor positivo β0\beta_0 debería ser idealmente mayor que λ1−λ0\lambda_1-\lambda_0.

Esto introduce una nueva función de coste que puede verse como un problema con restricciones, donde minimizamos CVQE(θ⃗)=⟨ψ(θ⃗)∣H^∣ψ(θ⃗)⟩C_\text{VQE}(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H} | \psi(\vec{\theta})\rangle sujeto a la restricción de que el estado debe ser ortogonal al ∣ψ(θ0⃗)⟩|\psi(\vec{\theta^0})\rangle previamente obtenido, con β0\beta_0 actuando como término de penalización si la restricción no se satisface.

Alternativamente, este nuevo problema puede interpretarse como ejecutar VQE sobre el observable:

H1^:=H^+β0∣ψ(θ0⃗)⟩⟨ψ(θ0⃗)∣⇒C1(θ⃗)=⟨ψ(θ⃗)∣H1^∣ψ(θ⃗)⟩,\hat{H_1} := \hat{H} + \beta_0 |\psi(\vec{\theta^0})\rangle \langle \psi(\vec{\theta^0})| \quad \Rightarrow \quad C_1(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H_1} | \psi(\vec{\theta})\rangle,

Asumiendo que la solución al nuevo problema es ∣ψ(θ1⃗)⟩|\psi(\vec{\theta^1})\rangle, el valor esperado de H^\hat{H} (no de H1^\hat{H_1}) debería ser ⟨ψ(θ1⃗)∣H^∣ψ(θ1⃗)⟩=λ1 \langle \psi(\vec{\theta^1}) | \hat{H} | \psi(\vec{\theta^1})\rangle = \lambda_1. Para obtener el tercer valor propio λ2\lambda_2, la función de coste a optimizar es:

C2(θ⃗):=C1(θ⃗)+β1∣⟨ψ(θ⃗)∣ψ(θ1⃗)⟩∣2C_2(\vec{\theta}) := C_1(\vec{\theta}) + \beta_1 |\langle \psi(\vec{\theta})| \psi(\vec{\theta^1})\rangle |^2

donde β1\beta_1 es una constante positiva suficientemente grande para imponer la ortogonalidad del estado solución tanto con ∣ψ(θ0⃗)⟩|\psi(\vec{\theta^0})\rangle como con ∣ψ(θ1⃗)⟩|\psi(\vec{\theta^1})\rangle. Esto penaliza los estados del espacio de búsqueda que no cumplen este requisito, restringiendo efectivamente dicho espacio. Así, la solución óptima del nuevo problema debería ser el estado propio correspondiente a λ2\lambda_2.

Al igual que en el caso anterior, este nuevo problema también puede interpretarse como VQE con el observable:

H2^:=H1^+β1∣ψ(θ1⃗)⟩⟨ψ(θ1⃗)∣⇒C2(θ⃗)=⟨ψ(θ⃗)∣H2^∣ψ(θ⃗)⟩.\hat{H_2} := \hat{H_1} + \beta_1 |\psi(\vec{\theta^1})\rangle \langle \psi(\vec{\theta^1})| \quad \Rightarrow \quad C_2(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H_2} | \psi(\vec{\theta})\rangle.

Si la solución a este nuevo problema es ∣ψ(θ2⃗)⟩|\psi(\vec{\theta^2})\rangle, el valor esperado de H^\hat{H} (no de H2^\hat{H_2}) debería ser ⟨ψ(θ2⃗)∣H^∣ψ(θ2⃗)⟩=λ2 \langle \psi(\vec{\theta^2}) | \hat{H} | \psi(\vec{\theta^2})\rangle = \lambda_2. De forma análoga, para obtener el kk-ésimo valor propio λk−1\lambda_{k-1}, minimizarías la función de coste:

Ck−1(θ⃗):=Ck−2(θ⃗)+βk−2∣⟨ψ(θ⃗)∣ψ(θk−2⃗)⟩∣2,C_{k-1}(\vec{\theta}) := C_{k-2}(\vec{\theta}) + \beta_{k-2} |\langle \psi(\vec{\theta})| \psi(\vec{\theta^{k-2}})\rangle |^2,

Recuerda que definimos θj⃗\vec{\theta^j} de forma que ⟨ψ(θj⃗)∣H^∣ψ(θj⃗)⟩=λj,∀j<k\langle \psi(\vec{\theta^j}) | \hat{H} | \psi(\vec{\theta^j})\rangle = \lambda_j, \forall j<k. Este problema es equivalente a minimizar C(θ⃗)=⟨ψ(θ⃗)∣H^∣ψ(θ⃗)⟩C(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H} | \psi(\vec{\theta})\rangle pero con la restricción de que el estado debe ser ortogonal a ∣ψ(θj⃗)⟩;∀j∈0,⋯ ,k−1|\psi(\vec{\theta^j})\rangle ; \forall j \in {0, \cdots, k-1}, restringiendo así el espacio de búsqueda al subespacio correspondiente a los valores propios {λk−1,⋯ ,λN−1}\{\lambda_{k-1},\cdots,\lambda_{N-1}\}.

Este problema es equivalente a un VQE con el observable:

H^k−1:=H^k−2+βk−2∣ψ(θk−2⃗)⟩⟨ψ(θk−2⃗)∣⇒Ck−1(θ⃗)=⟨ψ(θ⃗)∣H^k−1∣ψ(θ⃗)⟩,\hat{H}_{k-1} := \hat{H}_{k-2} + \beta_{k-2} |\psi(\vec{\theta^{k-2}})\rangle \langle \psi(\vec{\theta^{k-2}})| \quad \Rightarrow \quad C_{k-1}(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H}_{k-1} | \psi(\vec{\theta})\rangle,

Como puedes ver en el proceso, para obtener el kk-ésimo valor propio necesitas los estados propios (aproximados) de los k−1k-1 valores propios anteriores, por lo que tendrías que ejecutar VQE un total de kk veces. Por tanto, la función de coste de VQD es la siguiente:

Ck(θ⃗)=⟨ψ(θ⃗)∣H^∣ψ(θ⃗)⟩+∑j=0k−1βj∣⟨ψ(θ⃗)∣ψ(θj⃗)⟩∣2C_k(\vec{\theta}) = \langle \psi(\vec{\theta}) | \hat{H} | \psi(\vec{\theta})\rangle + \sum_{j=0}^{k-1}\beta_j |\langle \psi(\vec{\theta})| \psi(\vec{\theta^j})\rangle |^2

Diseño teórico​

El esquema de VQD puede resumirse así:

  • Preparar un operador de referencia URU_R

  • Aplicar la forma variacional UV(θ⃗i,j)U_V(\vec\theta_{i,j}) al estado de referencia, creando los siguientes ansätze UA(θ⃗i,j)U_A(\vec\theta_{i,j})

  • Arrancar en i=0i=0 si se dispone de un problema similar (típicamente hallado mediante simulación clásica o muestreo).

  • Evaluar la función de coste Ck(θ⃗)C_k(\vec{\theta}), que implica calcular kk estados excitados y un array de β\beta's que define la penalización por solapamiento para cada término.

    • Calcular el valor esperado de un observable ⟨ψj(θ⃗)∣H^∣ψj(θ⃗)⟩\langle \psi_{j}(\vec{\theta}) | \hat{H} | \psi_{j}(\vec{\theta}) \rangle para cada kk

    • Calcular la penalización ∑j=0k−1βj∣⟨ψ(θ⃗)∣ψ(θj⃗)⟩∣2\sum_{j=0}^{k-1}\beta_j |\langle \psi(\vec{\theta})| \psi(\vec{\theta^j})\rangle |^2.

    • La función de coste debe devolver la suma de estos dos términos

  • Usar un optimizador clásico para elegir el siguiente conjunto de parámetros Θi+1\Theta_{i+1}.

  • Repetir este proceso hasta alcanzar la convergencia.

Implementación​

Para esta implementación, crearemos una función para una penalización por solapamiento. Esta penalización se usará en la función de coste en cada iteración. Este proceso se repetirá para cada estado excitado.

from qiskit.circuit.library import TwoLocal

ansatz = TwoLocal(2, rotation_blocks=["ry", "rz"], entanglement_blocks="cz", reps=1)

ansatz.decompose().draw("mpl")

Salida de la celda de código anterior

Primero, definiremos una función que calcula la fidelidad del estado — un porcentaje de solapamiento entre dos estados que usaremos como penalización para VQD:

import numpy as np

def calculate_overlaps(ansatz, prev_circuits, parameters, sampler):
def create_fidelity_circuit(circuit_1, circuit_2):
"""
Constructs the list of fidelity circuits to be evaluated.
These circuits represent the state overlap between pairs of input circuits,
and their construction depends on the fidelity method implementations.
"""

if len(circuit_1.clbits) > 0:
circuit_1.remove_final_measurements()
if len(circuit_2.clbits) > 0:
circuit_2.remove_final_measurements()

circuit = circuit_1.compose(circuit_2.inverse())
circuit.measure_all()
return circuit

overlaps = []

for prev_circuit in prev_circuits:
fidelity_circuit = create_fidelity_circuit(ansatz, prev_circuit)
sampler_job = sampler.run([(fidelity_circuit, parameters)])
meas_data = sampler_job.result()[0].data.meas

counts_0 = meas_data.get_int_counts().get(0, 0)
shots = meas_data.num_shots
overlap = counts_0 / shots
overlaps.append(overlap)

return np.array(overlaps)

Es el momento de escribir la función de coste de VQD. Como antes, cuando calculamos solo el estado fundamental, determinaremos el estado de menor energía usando la primitiva Estimator. Sin embargo, tal como se describió más arriba, ahora añadiremos un término de penalización para garantizar la ortogonalidad de los estados de mayor energía. Es decir, para cada nuevo estado excitado, se añade una penalización por cualquier solapamiento entre el estado variacional actual y los estados propios de menor energía ya encontrados.

def cost_func_vqd(
parameters, ansatz, prev_states, step, betas, estimator, sampler, hamiltonian
):
estimator_job = estimator.run([(ansatz, hamiltonian, [parameters])])

total_cost = 0

if step > 1:
overlaps = calculate_overlaps(ansatz, prev_states, parameters, sampler)
total_cost = np.sum(
[np.real(betas[state] * overlap) for state, overlap in enumerate(overlaps)]
)

estimator_result = estimator_job.result()[0]

value = estimator_result.data.evs[0] + total_cost

return value

Nota especialmente que la función de coste anterior hace referencia a la función calculate_overlaps, que en realidad crea un nuevo circuito cuántico. Si queremos ejecutar esto en hardware real, ese nuevo circuito también debe ser transpilado, idealmente de forma óptima, para ejecutarse en el backend que seleccionemos. Observa que la transpilación no está incorporada en las funciones calculate_overlaps ni cost_func_vqd. Puedes intentar modificar el código tú mismo para añadir esta transpilación adicional (y condicional) — pero esto también se hará por ti en la siguiente lección.

En esta lección, ejecutaremos el algoritmo VQD usando el Statevector Sampler y el Statevector Estimator:

from qiskit.primitives import StatevectorEstimator as Estimator

sampler = Sampler()
estimator = Estimator()

Introduciremos un observable a estimar. En la siguiente lección le daremos un contexto físico, como el estado excitado de una molécula. Puede ser útil pensar en este observable como el hamiltoniano de un sistema que puede tener estados excitados, aunque este observable no se ha elegido para representar ninguna molécula o átomo en particular.

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp.from_list([("II", 2), ("XX", -2), ("YY", 3), ("ZZ", -3)])

Aquí definimos el número total de estados que deseamos calcular (estado fundamental y estados excitados, k), y las penalizaciones (betas) por solapamiento entre vectores de estado que deberían ser ortogonales. Las consecuencias de elegir betas demasiado altas o demasiado bajas se explorarán un poco en la siguiente lección. Por ahora, usaremos simplemente las que se proporcionan a continuación. Comenzaremos usando ceros como parámetros iniciales. En tus propios cálculos, quizás quieras usar parámetros iniciales más inteligentes basados en tu conocimiento del sistema o en cálculos anteriores.

k = 3
betas = [33, 33, 33]
x0 = np.zeros(8)

Ya podemos ejecutar el cálculo:

from scipy.optimize import minimize

prev_states = []
prev_opt_parameters = []
eigenvalues = []

for step in range(1, k + 1):
if step > 1:
prev_states.append(ansatz.assign_parameters(prev_opt_parameters))

result = minimize(
cost_func_vqd,
x0,
args=(ansatz, prev_states, step, betas, estimator, sampler, observable),
method="COBYLA",
options={
"maxiter": 200,
},
)
print(result)

prev_opt_parameters = result.x
eigenvalues.append(result.fun)
message: Optimization terminated successfully.
success: True
status: 1
fun: -5.999999979545955
x: [-5.150e-01 -5.452e-02 -1.571e+00 -2.853e-05 2.671e-01
-2.672e-01 -8.509e-01 -8.510e-01]
nfev: 131
maxcv: 0.0
message: Optimization terminated successfully.
success: True
status: 1
fun: 4.024550284767612
x: [-3.745e-01 1.041e+00 8.637e-01 1.202e+00 -8.847e-02
1.181e-02 7.611e-01 -3.006e-01]
nfev: 110
maxcv: 0.0
message: Optimization terminated successfully.
success: True
status: 1
fun: 5.608925562838559
x: [-2.670e-01 1.280e+00 1.070e+00 -8.031e-01 -1.524e-01
-6.956e-02 7.018e-01 1.514e+00]
nfev: 90
maxcv: 0.0

Los valores que obtuvimos de la función de coste son aproximadamente -6,00, 4,02 y 5,61. Lo importante de estos resultados es que los valores de la función van aumentando. Si hubiéramos obtenido un primer estado excitado con una energía menor que el cálculo inicial sin restricciones del estado fundamental, eso habría indicado un error en algún lugar de nuestro código.

Los valores de x son los parámetros que produjeron un vector de estado correspondiente a cada uno de estos costes (energías).

Por último, observamos que las tres minimizaciones convergieron dentro de la tolerancia predeterminada del optimizador clásico (aquí COBYLA). Requirieron 131, 110 y 90 evaluaciones de función, respectivamente.

Regresión por muestreo cuántico (QSR)​

Uno de los principales problemas de VQE es la gran cantidad de llamadas a un ordenador cuántico necesarias para obtener los parámetros en cada paso, por ejemplo, kk, k−1k-1, etc. Esto es especialmente problemático cuando el acceso a los dispositivos cuánticos está en cola. Aunque se puede usar una Session para agrupar múltiples llamadas iterativas, un enfoque alternativo es el muestreo. Al aprovechar más recursos clásicos, podemos completar el proceso completo de optimización en una sola llamada. Aquí es donde entra en juego la Regresión por muestreo cuántico. Dado que el acceso a los ordenadores cuánticos sigue siendo un recurso escaso con alta demanda, este intercambio nos parece tanto factible como conveniente para muchos estudios actuales. Este enfoque aprovecha toda la capacidad clásica disponible y, al mismo tiempo, captura muchos de los mecanismos internos y propiedades intrínsecas de los cálculos cuánticos que no aparecen en la simulación.

Un diagrama que muestra cómo QSR usa los componentes de un algoritmo variacional.

La idea detrás de QSR es que la función de coste C(θ):=⟨ψ(θ)∣H^∣ψ(θ)⟩C(\theta) := \langle \psi(\theta) | \hat{H} | \psi(\theta)\rangle puede expresarse como una serie de Fourier de la siguiente manera:

C(θ⃗):=⟨ψ(θ)∣H^∣ψ(θ)⟩:=a0+∑k=1S[akcos⁡(kθ)+bksin⁡(kθ)]\begin{aligned} C(\vec{\theta}) & := \langle \psi(\theta) | \hat{H} | \psi(\theta)\rangle \\[1mm] & := a_0 + \sum_{k=1}^S[a_k\cos(k\theta)+ b_k\sin(k\theta)] \\[1mm] \end{aligned}

Dependiendo de la periodicidad y el ancho de banda de la función original, el conjunto SS puede ser finito o infinito. A efectos de esta discusión, asumiremos que es infinito. El siguiente paso es muestrear la función de coste C(θ)C(\theta) múltiples veces para obtener los coeficientes de Fourier {a0,ak,bk}k=1S\{a_0, a_k, b_k\}_{k=1}^S. En concreto, como tenemos 2S+12S+1 incógnitas, necesitaremos muestrear la función de coste 2S+12S+1 veces.

Si muestreamos la función de coste para 2S+12S+1 valores de parámetro {θ1,...,θ2S+1}\{\theta_1,...,\theta_{2S+1}\}, obtenemos el siguiente sistema:

(1cos⁡(θ1)sin⁡(θ1)cos⁡(2θ1)...sin⁡(Sθ1)1cos⁡(θ2)sin⁡(θ2)cos⁡(2θ2)⋯sin⁡(Sθ2)⋮⋮⋮⋮⋱⋮1cos⁡(θ2S+1)sin⁡(θ2S+1)cos⁡(2θ2S+1)⋯sin⁡(Sθ2S+1))(a0a1b1a2⋮bS)=(C(θ1)C(θ2)⋮C(θ2S+1)),\begin{pmatrix} 1 & \cos(\theta_1) & \sin(\theta_1) & \cos(2\theta_1) & ... & \sin(S\theta_1) \\ 1 & \cos(\theta_2) & \sin(\theta_2) & \cos(2\theta_2) & \cdots & \sin(S\theta_2)\\ \vdots & \vdots & \vdots & \vdots & \ddots & \vdots\\ 1 & \cos(\theta_{2S+1}) & \sin(\theta_{2S+1}) & \cos(2\theta_{2S+1}) & \cdots & \sin(S\theta_{2S+1}) \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ b_1 \\ a_2 \\ \vdots \\ b_S \end{pmatrix} = \begin{pmatrix} C(\theta_1) \\ C(\theta_2) \\ \vdots \\ C(\theta_{2S+1}) \end{pmatrix},

que reescribiremos como

Fa=c.Fa=c.

En la práctica, este sistema generalmente no es consistente porque los valores de la función de coste cc no son exactos. Por ello, suele ser buena idea normalizarlos multiplicando por F†F^\dagger por la izquierda, lo que da como resultado:

F†Fa=F†c.F^\dagger Fa = F^\dagger c.

Este nuevo sistema siempre es consistente, y su solución es la solución de mínimos cuadrados al problema original. Si tenemos kk parámetros en lugar de solo uno, y cada parámetro θi\theta^i tiene su propio SiS_i para i∈1,...,ki \in {1,...,k}, entonces el número total de muestras requeridas es:

T=∏i=1k(2Si+1)≤∏i=1k(2Smax+1)=(2Smax+1)n,T=\prod_{i=1}^k(2S_i+1)\leq \prod_{i=1}^k(2S_{max}+1) = (2S_{max}+1)^n,

donde Smax⁡=max⁡i(Si)S_{\max} = \max_i(S_i). Además, ajustar Smax⁡S_{\max} como parámetro ajustable (en lugar de inferirlo) abre nuevas posibilidades, como:

  • Sobremuestreo para mejorar la precisión.

  • Submuestreo para aumentar el rendimiento reduciendo la sobrecarga de tiempo de ejecución o eliminando mínimos locales.

Esquema teórico​

El esquema de QSR puede resumirse así:

  • Preparar operadores de referencia URU_R.

    • Iremos del estado ∣0⟩|0\rangle al estado de referencia ∣ρ⟩|\rho\rangle
  • Aplicar la forma variacional UV(θ⃗i,j)U_V(\vec\theta_{i,j}) para crear un ansatz UA(θ⃗i,j)U_A(\vec\theta_{i,j}).

    • Determinar el ancho de banda asociado a cada parámetro del ansatz. Una cota superior es suficiente.
  • Iniciar en i=0i=0 si tenemos un problema similar (normalmente encontrado mediante simulación clásica o muestreo).

  • Muestrea la función de costo C(θ⃗):=a0+∑k=1S[akcos⁡(kθ)+bksin⁡(kθ)]C(\vec\theta) := a_0 + \sum_{k=1}^S[a_k\cos(k\theta)+ b_k\sin(k\theta)] al menos TT veces.

    • T=∏i=1k(2Si+1)≤∏i=1k(2Smax+1)=(2Smax+1)nT=\prod_{i=1}^k(2S_i+1)\leq \prod_{i=1}^k(2S_{max}+1) = (2S_{max}+1)^n

    • Decidir si realizar sobremuestreo/submuestreo para equilibrar velocidad frente a precisión ajustando TT.

  • Calcular los coeficientes de Fourier a partir de las muestras (es decir, resolver el sistema de ecuaciones lineales normalizado).

  • Resolver el mínimo global de la función de regresión resultante en una máquina clásica.

Resumen​

Con esta lección, aprendiste sobre múltiples instancias variacionales disponibles:

  • Esquema general

  • Introducción de pesos y penalizaciones para ajustar una función de coste

  • Exploración del submuestreo frente al sobremuestreo para intercambiar velocidad por precisión

Estas ideas se pueden adaptar para formar un algoritmo variacional personalizado que se ajuste a tu problema. Te animamos a compartir tus resultados con la comunidad. La próxima lección explorará cómo usar un algoritmo variacional para resolver una aplicación.