Saltar al contenido principal

Fórmulas multiproducto para reducir el error de Trotter

Uso estimado de QPU: Cuatro minutos en un procesador Heron r2 (NOTA: Esto es solo una estimación. Su tiempo de ejecución puede variar.)

Antecedentes

Este tutorial demuestra cómo utilizar una Fórmula Multiproducto (MPF, por sus siglas en inglés) para lograr un error de Trotter menor en nuestro observable en comparación con el que se incurriría con el circuito de Trotter más profundo que realmente ejecutaremos. Las MPF reducen el error de Trotter de la dinámica hamiltoniana mediante una combinación ponderada de varias ejecuciones de circuitos. Considera la tarea de encontrar los valores esperados de observables para el estado cuántico ρ(t)=eiHtρ(0)eiHt\rho(t)=e^{-i H t} \rho(0) e^{i H t} con el hamiltoniano HH. Se pueden utilizar Fórmulas de Producto (PF, por sus siglas en inglés) para aproximar la evolución temporal eiHte^{-i H t} realizando lo siguiente:

  • Escribir el hamiltoniano HH como H=a=1dFa,H=\sum_{a=1}^d F_a, donde FaF_a son operadores hermitianos tales que cada unitario correspondiente puede implementarse eficientemente en un dispositivo cuántico.
  • Aproximar los términos FaF_a que no conmutan entre sí.

Entonces, la PF de primer orden (fórmula de Lie-Trotter) es:

S1(t):=a=1deiFat,S_1(t):=\prod_{a=1}^d e^{-i F_a t},

la cual tiene un término de error cuadrático S1(t)=eiHt+O(t2)S_1(t)=e^{-i H t}+\mathcal{O}\left(t^{2}\right). También se pueden utilizar PF de orden superior (fórmulas de Lie-Trotter-Suzuki), que convergen más rápidamente y se definen recursivamente como:

S2(t):=a=1deiFat/2a=1deiFat/2S_2(t):=\prod_{a=1}^d e^{-i F_a t/2}\prod_{a=1}^d e^{-i F_a t/2}

S2χ(t):=S2χ2(sχt)2S2χ2((14sχ)t)S2χ2(sχt)2,S_{2 \chi}(t):= S_{2 \chi -2}(s_{\chi}t)^2 S_{2 \chi -2}((1-4s_{\chi})t)S_{2 \chi -2}(s_{\chi}t)^2,

donde χ\chi es el orden de la PF simétrica y sp=(441/(2p1))1s_p = \left( 4 - 4^{1/(2p-1)} \right)^{-1}. Para evoluciones temporales largas, se puede dividir el intervalo de tiempo tt en kk intervalos, llamados pasos de Trotter, de duración t/kt/k y aproximar la evolución temporal en cada intervalo con una fórmula de producto de orden χ\chi SχS_{\chi}. Así, la PF de orden χ\chi para el operador de evolución temporal sobre kk pasos de Trotter es:

Sχk(t)=[Sχ(tk)]k=eiHt+O(t(tk)χ) S_{\chi}^{k}(t) = \left[ S_{\chi} \left( \frac{t}{k} \right)\right]^k = e^{-i H t}+O\left(t \left( \frac{t}{k} \right)^{\chi} \right)

donde el término de error disminuye con el número de pasos de Trotter kk y el orden χ\chi de la PF.

Dado un entero k1k \geq 1 y una fórmula de producto Sχ(t)S_{\chi}(t), el estado evolucionado temporalmente aproximado ρk(t)\rho_k(t) se puede obtener a partir de ρ0\rho_0 aplicando kk iteraciones de la fórmula de producto Sχ(tk)S_{\chi}\left(\frac{t}{k}\right).

ρk(t)=Sχ(tk)kρ0Sχ(tk)k\rho_k(t)=S_{\chi}\left(\frac{t}{k}\right)^k \rho_0 S_{\chi}\left(\frac{t}{k}\right)^{-k}

ρk(t)\rho_k(t) es una aproximación de ρ(t)\rho(t) con el error de aproximación de Trotter ||ρk(t)ρ(t)\rho_k(t)-\rho(t) ||. Si consideramos una combinación lineal de aproximaciones de Trotter de ρ(t)\rho(t):

μ(t)=jlxjρjkj(tkj)+some remaining Trotter error,\mu(t) = \sum_{j}^{l} x_j \rho^{k_j}_{j}\left(\frac{t}{k_j}\right) + \text{some remaining Trotter error} \, ,

donde xjx_j son nuestros coeficientes de ponderación, ρjkj\rho^{k_j}_j es la matriz de densidad correspondiente al estado puro obtenido al evolucionar el estado inicial con la fórmula de producto SχkjS^{k_j}_{\chi}, que involucra kjk_j pasos de Trotter, y j1,...,lj \in {1, ..., l} indexa el número de PF que componen la MPF. Todos los términos en μ(t)\mu(t) utilizan la misma fórmula de producto Sχ(t)S_{\chi}(t) como base. El objetivo es mejorar ||ρk(t)ρ(t)\rho_k(t)-\rho(t) \| encontrando μ(t)\mu(t) con un μ(t)ρ(t)\|\mu(t)-\rho(t)\| aún menor.

  • μ(t)\mu(t) no necesita ser un estado físico ya que xix_i no necesita ser positivo. El objetivo aquí es minimizar el error en el valor esperado de los observables y no encontrar un reemplazo físico para ρ(t)\rho(t).
  • kjk_j determina tanto la profundidad del circuito como el nivel de aproximación de Trotter. Valores más pequeños de kjk_j conducen a circuitos más cortos, que incurren en menos errores de circuito pero serán una aproximación menos precisa del estado deseado.

La clave aquí es que el error de Trotter residual dado por μ(t)\mu(t) es menor que el error de Trotter que se obtendría simplemente utilizando el valor más grande de kjk_j.

puedes ver la utilidad de esto desde dos perspectivas:

  1. Para un presupuesto fijo de pasos de Trotter que puede ejecutar, puede obtener resultados con un error de Trotter que es menor en total.
  2. Dado algún número objetivo de pasos de Trotter que es demasiado grande para ejecutar, puede utilizar la MPF para encontrar una colección de circuitos de menor profundidad para ejecutar que resulte en un error de Trotter similar.

Requisitos

Antes de comenzar este tutorial, asegúrate de tener instalado lo siguiente:

  • Qiskit SDK v1.0 o posterior, con soporte de visualización
  • Qiskit Runtime v0.22 o posterior (pip install qiskit-ibm-runtime)
  • Complemento MPF de Qiskit (pip install qiskit_addon_mpf)
  • Utilidades de complementos de Qiskit (pip install qiskit_addon_utils)
  • Biblioteca Quimb (pip install quimb)
  • Biblioteca Qiskit Quimb (pip install qiskit-quimb)
  • Numpy v0.21 para compatibilidad entre paquetes (pip install numpy==0.21)

Parte I. Ejemplo a pequeña escala

Explorar la estabilidad de la MPF

No existe una restricción obvia en la elección del número de pasos de Trotter kjk_j que componen el estado MPF μ(t)\mu(t). Sin embargo, estos deben elegirse cuidadosamente para evitar inestabilidades en los valores esperados resultantes calculados a partir de μ(t)\mu(t). Una buena regla general es establecer el paso de Trotter más pequeño kmink_{\text{min}} de modo que t/kmin<1t/k_{\text{min}} \lt 1. Si desea obtener más información sobre esto y cómo elegir sus otros valores de kjk_j, consulta la guía Cómo elegir los pasos de Trotter para una MPF.

En el ejemplo a continuación, exploramos la estabilidad de la solución MPF calculando el valor esperado de la magnetización para un rango de tiempos utilizando diferentes estados evolucionados temporalmente. Específicamente, comparamos los valores esperados calculados a partir de cada una de las evoluciones temporales aproximadas implementadas con los pasos de Trotter correspondientes y los diversos modelos MPF (coeficientes estáticos y dinámicos) con los valores exactos del observable evolucionado temporalmente. Primero, definamos los parámetros para las fórmulas de Trotter y los tiempos de evolución.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-mpf qiskit-addon-utils qiskit-aer qiskit-ibm-runtime rustworkx scipy
import numpy as np

mpf_trotter_steps = [1, 2, 4]
order = 2
symmetric = False

trotter_times = np.arange(0.5, 1.55, 0.1)
exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)

Para este ejemplo utilizaremos el estado de Néel como estado inicial Neel=0101...01\vert \text{Neel} \rangle = \vert 0101...01 \rangle y el modelo de Heisenberg en una línea de 10 sitios para el hamiltoniano que gobierna la evolución temporal.

H^Heis=Ji=1L1(XiX(i+1)+YiY(i+1)+ZiZ(i+1)),\hat{\mathcal{H}}_{Heis} = J \sum_{i=1}^{L-1} \left(X_i X_{(i+1)}+Y_i Y_{(i+1)}+ Z_i Z_{(i+1)} \right) \, ,

donde JJ es la constante de acoplamiento para aristas de vecinos más cercanos.

from qiskit.transpiler import CouplingMap
from rustworkx.visualization import graphviz_draw
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
import numpy as np

L = 10

# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(L, bidirectional=False)
graphviz_draw(coupling_map.graph, method="circo")

# Get a qubit operator describing the Heisenberg field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(1.0, 1.0, 1.0),
ext_magnetic_field=(0.0, 0.0, 0.0),
)

print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

El observable que mediremos es la magnetización en un par de qubits en el centro de la cadena.

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])

Definimos un pase de transpilación para recopilar las rotaciones XX e YY en el circuito como una sola compuerta XX+YY. Esto nos permitirá aprovechar las propiedades de conservación de espín de TeNPy durante el cálculo del MPO, acelerando significativamente el cálculo.

from qiskit.circuit.library import XXPlusYYGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
CollectAndCollapse,
collect_using_filter_function,
collapse_to_operation,
)
from functools import partial

def filter_function(node):
return node.op.name in {"rxx", "ryy"}

collect_function = partial(
collect_using_filter_function,
filter_function=filter_function,
split_blocks=True,
min_block_size=1,
)

def collapse_to_xx_plus_yy(block):
param = 0.0
for node in block.data:
param += node.operation.params[0]
return XXPlusYYGate(param)

collapse_function = partial(
collapse_to_operation,
collapse_function=collapse_to_xx_plus_yy,
)

pm = PassManager()
pm.append(CollectAndCollapse(collect_function, collapse_function))

Luego creamos los circuitos que implementan las evoluciones temporales aproximadas de Trotter.

from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit

# Initial Neel state preparation
initial_state_circ = QuantumCircuit(L)
initial_state_circ.x([i for i in range(L) if i % 2 != 0])

all_circs = []
for total_time in trotter_times:
mpf_trotter_circs = [
generate_time_evolution_circuit(
hamiltonian,
time=total_time,
synthesis=SuzukiTrotter(reps=num_steps, order=order),
)
for num_steps in mpf_trotter_steps
]

mpf_trotter_circs = pm.run(
mpf_trotter_circs
) # Collect XX and YY into XX + YY

mpf_circuits = [
initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs
]
all_circs.append(mpf_circuits)
mpf_circuits[-1].draw("mpl", fold=-1)

Output of the previous code cell

A continuación, calculamos los valores esperados evolucionados temporalmente a partir de los circuitos de Trotter.

from copy import deepcopy
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

aer_sim = AerSimulator()
estimator = Estimator(mode=aer_sim)

mpf_expvals_all_times, mpf_stds_all_times = [], []
for t, mpf_circuits in zip(trotter_times, all_circs):
mpf_expvals = []
circuits = [deepcopy(circuit) for circuit in mpf_circuits]
pm_sim = generate_preset_pass_manager(
backend=aer_sim, optimization_level=3
)
isa_circuits = pm_sim.run(circuits)
result = estimator.run(
[(circuit, observable) for circuit in isa_circuits], precision=0.005
).result()
mpf_expvals = [res.data.evs for res in result]
mpf_stds = [res.data.stds for res in result]
mpf_expvals_all_times.append(mpf_expvals)
mpf_stds_all_times.append(mpf_stds)

También calculamos los valores esperados exactos para comparación.

from scipy.linalg import expm
from qiskit.quantum_info import Statevector

exact_expvals = []
for t in exact_evolution_times:
# Exact expectation values
exp_H = expm(-1j * t * hamiltonian.to_matrix())
initial_state = Statevector(initial_state_circ).data
time_evolved_state = exp_H @ initial_state

exact_obs = (
time_evolved_state.conj()
@ observable.to_matrix()
@ time_evolved_state
).real
exact_expvals.append(exact_obs)

Coeficientes MPF estáticos

Las MPF estáticas son aquellas en las que los valores de xjx_j no dependen del tiempo de evolución, tt. Consideremos la PF de orden χ=1\chi = 1 con kjk_j pasos de Trotter; esto se puede escribir como:

S1kj(tkj)=eiHt+n=1Antn+1kjnS_1^{k_j}\left( \frac{t}{k_j} \right)=e^{-i H t}+ \sum_{n=1}^{\infty} A_n \frac{t^{n+1}}{k_j^n}

donde AnA_n son matrices que dependen de los conmutadores de los términos FaF_a en la descomposición del hamiltoniano. Es importante notar que AnA_n en sí mismas son independientes del tiempo y del número de pasos de Trotter kjk_j. Por lo tanto, es posible cancelar los términos de error de orden inferior que contribuyen a μ(t)\mu(t) con una elección cuidadosa de los pesos xjx_j de la combinación lineal. Para cancelar el error de Trotter para los primeros l1l-1 términos (estos darán las contribuciones más grandes ya que corresponden al número más bajo de pasos de Trotter) en la expresión de μ(t)\mu(t), los coeficientes xjx_j deben satisfacer las siguientes ecuaciones:

j=1lxj=1\sum_{j=1}^l x_j = 1 j=1l1xjkjn=0\sum_{j=1}^{l-1} \frac{x_j}{k_j^{n}} = 0

con n=0,...l2n=0, ... l-2. La primera ecuación garantiza que no haya sesgo en el estado construido μ(t)\mu(t), mientras que la segunda ecuación asegura la cancelación de los errores de Trotter. Para PF de orden superior, la segunda ecuación se convierte en j=1l1xjkjη=0\sum_{j=1}^{l-1} \frac{x_j}{k_j^{\eta}} = 0 donde η=χ+2n\eta = \chi + 2n para PF simétricas y η=χ+n\eta = \chi + n en caso contrario, con n=0,...,l2n=0, ..., l-2. El error resultante (Refs. [1],[2]) es entonces

ϵ=O(tl+1k1l). \epsilon = \mathcal{O} \left( \frac{t^{l+1}}{k_1^l} \right).

Determinar los coeficientes MPF estáticos para un conjunto dado de valores kjk_j equivale a resolver el sistema lineal de ecuaciones definido por las dos ecuaciones anteriores para las variables xjx_j: Ax=bAx=b. Donde xx son nuestros coeficientes de interés, AA es una matriz que depende de kjk_j y del tipo de PF que utilizamos (SS), y bb es un vector de restricciones. Específicamente:

A0,j=1A_{0,j} = 1 Ai>0,j=kj(χ+s(i1))A_{i>0,j} = k_{j}^{-(\chi + s(i-1))} b0=1b_0 = 1 bi>0=0b_{i>0} = 0

donde χ\chi es el order, ss es 22 si symmetric es True y 11 en caso contrario, kjk_{j} son los trotter_steps, y xx son las variables a resolver. Los índices ii y jj comienzan en 00. También podemos visualizar esto en forma matricial:

A=[A0,0A0,1A0,2...A1,0A1,1A1,2...A2,0A2,1A2,2...............]=[111...k0(χ+s(11))k1(χ+s(11))k2(χ+s(11))...k0(χ+s(21))k1(χ+s(21))k2(χ+s(21))...............]A = \begin{bmatrix} A_{0,0} & A_{0,1} & A_{0,2} & ... \\ A_{1,0} & A_{1,1} & A_{1,2} & ... \\ A_{2,0} & A_{2,1} & A_{2,2} & ... \\ ... & ... & ... & ... \end{bmatrix} = \begin{bmatrix} 1 & 1 & 1 & ... \\ k_{0}^{-(\chi + s(1-1))} & k_{1}^{-(\chi + s(1-1))} & k_{2}^{-(\chi + s(1-1))} & ... \\ k_{0}^{-(\chi + s(2-1))} & k_{1}^{-(\chi + s(2-1))} & k_{2}^{-(\chi + s(2-1))} & ... \\ ... & ... & ... & ... \end{bmatrix}

y

b=[b0b1b2...]=[100...]b = \begin{bmatrix} b_{0} \\ b_{1} \\ b_{2} \\ ... \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 0 \\ ... \end{bmatrix}

Para más detalles, consulta la documentación del Sistema Lineal de Ecuaciones (LSE).

Podemos encontrar una solución para xx analíticamente como x=A1bx = A^{-1}b; véase por ejemplo las Refs. [1] o [2]. Sin embargo, esta solución exacta puede estar "mal condicionada", resultando en normas L1 muy grandes de nuestros coeficientes, xx, lo que puede llevar a un mal rendimiento de la MPF. En su lugar, también se puede obtener una solución aproximada que minimiza la norma L1 de xx para intentar optimizar el comportamiento de la MPF.

Configurar el LSE

Ahora que hemos elegido nuestros valores de kjk_j, primero debemos construir el LSE, Ax=bAx=b como se explicó anteriormente. La matriz AA depende no solo de kjk_j sino también de nuestra elección de PF, en particular su orden. Adicionalmente, podrías tener en cuenta si la PF es simétrica o no (véase [1]) estableciendo symmetric=True/False. Sin embargo, esto no es obligatorio, como muestra la Ref. [2].

from qiskit_addon_mpf.static import setup_static_lse

lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)

Trabajemos con los valores elegidos anteriormente para construir la matriz AA y el vector bb. Con j=0,1,2j=0,1, 2 pasos de Trotter kj=[1,2,4]k_j = [1, 2, 4], orden χ=2\chi = 2 y elección de pasos de Trotter no simétricos (s=1s=1), tenemos que los elementos de la matriz AA debajo de la primera fila están determinados por la expresión Ai>0,j=kj(2+1(i1))A_{i>0,j} = k_{j}^{-(2 + 1(i-1))}, específicamente:

A0,0=A0,1=A0,2=1A_{0,0} = A_{0,1} = A_{0,2} = 1 A1,j=kj1A1,0=112,  ,A1,1=122,  ,A1,2=142 A_{1,j} = k_{j}^{-1} \rightarrow A_{1,0} = \frac{1}{1^2}, \;, A_{1,1} = \frac{1}{2^2}, \;, A_{1,2} = \frac{1}{4^2} A2,j=kj2A2,0=113,  ,A2,1=123,  ,A2,2=143 A_{2,j} = k_{j}^{-2} \rightarrow A_{2,0} = \frac{1}{1^3}, \;, A_{2,1} = \frac{1}{2^3}, \;, A_{2,2} = \frac{1}{4^3}

o en forma matricial:

A=[11111221421123143]A = \begin{bmatrix} 1 & 1 & 1\\ 1 & \frac{1}{2^2} & \frac{1}{4^2} \\ 1 & \frac{1}{2^3} & \frac{1}{4^3} \\ \end{bmatrix}

Esto es posible verlo inspeccionando el objeto lse:

lse.A
array([[1.      , 1.      , 1.      ],
[1. , 0.25 , 0.0625 ],
[1. , 0.125 , 0.015625]])

Mientras que el vector de restricciones bb tiene los siguientes elementos: b0=1b_{0} = 1 b1=b2=0b_1 = b_2 = 0

Por lo tanto,

b=[100]b = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}

Y de manera similar en lse:

lse.b
array([1., 0., 0.])

El objeto lse tiene métodos para encontrar los coeficientes estáticos xjx_j que satisfacen el sistema de ecuaciones.

mpf_coeffs = lse.solve()
print(
f"The static coefficients associated with the ansatze are: {mpf_coeffs}"
)
The static coefficients associated with the ansatze are: [ 0.04761905 -0.57142857  1.52380952]
Optimizar para xx utilizando un modelo exacto

Como alternativa a calcular x=A1bx=A^{-1}b, también puede utilizar setup_exact_model para construir una instancia de cvxpy.Problem que utiliza el LSE como restricciones y cuya solución óptima produzca xx.

En la siguiente sección, quedará claro por qué existe esta interfaz.

from qiskit_addon_mpf.costs import setup_exact_problem

model_exact, coeffs_exact = setup_exact_problem(lse)
model_exact.solve()
print(coeffs_exact.value)
[ 0.04761905 -0.57142857  1.52380952]

Como indicador de si una MPF construida con estos coeficientes producirá buenos resultados, podemos utilizar la norma L1 (véase también la Ref. [1]).

print(
"L1 norm of the exact coefficients:",
np.linalg.norm(coeffs_exact.value, ord=1),
) # ord specifies the norm. ord=1 is for L1
L1 norm of the exact coefficients: 2.1428571428556378
Optimizar para xx utilizando un modelo aproximado

Puede ocurrir que la norma L1 para el conjunto elegido de valores kjk_j se considera demasiado alta. Si ese es el caso y no puede elegir un conjunto diferente de valores kjk_j, puede utilizar una solución aproximada al LSE en lugar de una exacta.

Para hacerlo, simplemente utiliza setup_approximate_model para construir una instancia diferente de cvxpy.Problem, que restrinja la norma L1 a un umbral elegido mientras minimiza la diferencia de AxAx y bb.

from qiskit_addon_mpf.costs import setup_sum_of_squares_problem

model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=1.5
)
model_approx.solve()
print(coeffs_approx.value)
print(
"L1 norm of the approximate coefficients:",
np.linalg.norm(coeffs_approx.value, ord=1),
)
[-1.10294118e-03 -2.48897059e-01  1.25000000e+00]
L1 norm of the approximate coefficients: 1.5

Ten en cuenta que tienes completa libertad sobre cómo resolver este problema de optimización, lo que significa que puede cambiar el solucionador de optimización, sus umbrales de convergencia, y demás. Consulta la guía respectiva sobre Cómo utilizar el modelo aproximado.

Coeficientes MPF dinámicos

En la sección anterior, presentamos una MPF estática que mejora la aproximación estándar de Trotter. Sin embargo, esta versión estática no necesariamente minimiza el error de aproximación. Concretamente, la MPF estática, denotada μS(t)\mu^S(t), no es la proyección óptima de ρ(t)\rho(t) sobre el subespacio generado por los estados de fórmula de producto {ρki(t)}i=1r\{\rho_{k_i}(t)\}_{i=1}^r.

Para abordar esto, consideramos una MPF dinámica (introducida en la Ref. [2] y demostrada experimentalmente en la Ref. [3]) que sí minimiza el error de aproximación en la norma de Frobenius. Formalmente, nos enfocamos en minimizar

ρ(t)μD(t)F2  =  Tr[(ρ(t)μD(t))2],\|\rho(t) - \mu^D(t)\|_F^2 \;=\; \mathrm{Tr}\bigl[ \left( \rho(t) - \mu^D(t)\right)^2 \bigr],

con respecto a algunos coeficientes xi(t)x_i(t) en cada tiempo tt. El proyector óptimo en la norma de Frobenius es entonces μD(t)  =  i=1rxi(t)ρki(t)\mu^D(t) \;=\; \sum_{i=1}^r x_i(t)\,\rho_{k_i}(t), y llamamos a μD(t)\mu^D(t) la MPF dinámica. Sustituyendo las definiciones anteriores:

ρ(t)μD(t)F2  =  =Tr[(ρ(t)μD(t))2]  =  =Tr[(ρ(t)i=1rxi(t)ρki(t))(ρ(t)j=1rxj(t)ρkj(t))]  =  =1  +  i,j=1rMi,j(t)xi(t)xj(t)    2i=1rLiexact(t)xi(t),\|\rho(t) - \mu^D(t)\|_F^2 \;=\; \\ = \mathrm{Tr}\bigl[ \left( \rho(t) - \mu^D(t)\right)^2 \bigr] \;=\; \\ = \mathrm{Tr}\bigl[ \left( \rho(t) - \sum_{i=1}^r x_i(t)\,\rho_{k_i}(t) \right) \left( \rho(t) - \sum_{j=1}^r x_j(t)\,\rho_{k_j}(t) \right) \bigr] \;=\; \\ = 1 \;+\; \sum_{i,j=1}^r M_{i,j}(t)\,x_i(t)\,x_j(t) \;-\; 2 \sum_{i=1}^r L_i^{\mathrm{exact}}(t)\,x_i(t),

donde Mi,j(t)M_{i,j}(t) es la matriz de Gram, definida por

Mi,j(t)  =  Tr[ρki(t)ρkj(t)]  =  ψin ⁣S(t/ki)kiS(t/kj)kj ⁣ψin2.M_{i,j}(t) \;=\; \mathrm{Tr}\bigl[\rho_{k_i}(t)\,\rho_{k_j}(t)\bigr] \;=\; \bigl|\langle \psi_{\mathrm{in}} \!\mid S\bigl(t/k_i\bigr)^{-k_i}\,S\bigl(t/k_j\bigr)^{k_j} \!\mid \psi_{\mathrm{in}} \rangle \bigr|^2.

y

Liexact(t)=Tr[ρ(t)ρki(t)]L_i^{\mathrm{exact}}(t) = \mathrm{Tr}[\rho(t)\,\rho_{k_i}(t)]

representa el solapamiento entre el estado exacto ρ(t)\rho(t) y cada aproximación de fórmula de producto ρki(t)\rho_{k_i}(t). En escenarios prácticos, estos solapamientos solo pueden medirse de forma aproximada debido al ruido o al acceso parcial a ρ(t)\rho(t).

Aquí, ψin\lvert\psi_{\mathrm{in}}\rangle es el estado inicial, y S()S(\cdot) es la operación aplicada en la fórmula de producto. Al elegir los coeficientes xi(t)x_i(t) que minimizan esta expresión (y manejar datos de solapamiento aproximados cuando ρ(t)\rho(t) no se conoce completamente), obtenemos la "mejor" (en el sentido de la norma de Frobenius) aproximación dinámica de ρ(t)\rho(t) dentro del subespacio MPF. Las cantidades Li(t)L_i(t) y Mi,j(t)M_{i,j}(t) pueden calcularse eficientemente utilizando métodos de redes tensoriales [3]. El complemento MPF de Qiskit proporciona varios "backends" para llevar a cabo el cálculo. El ejemplo a continuación muestra la forma más flexible de hacerlo, y la documentación del backend basado en capas de TeNPy también lo explica en gran detalle. Para utilizar este método, comience a partir del circuito que implementa la evolución temporal deseada y crea modelos que representen estas operaciones a partir de las capas del circuito correspondiente. Finalmente, se crea un objeto Evolver que puede utilizarse para generar las cantidades evolucionadas temporalmente Mi,j(t)M_{i,j}(t) y Li(t)L_i(t). Comenzamos creando el objeto Evolver correspondiente a la evolución temporal aproximada (ApproxEvolverFactory) implementada por los circuitos. En particular, preste especial atención a la variable order para que coincidan. Ten en cuenta que al generar los circuitos correspondientes a la evolución temporal aproximada, utilizamos valores de marcador de posición para time = 1.0 y el número de pasos de Trotter (reps=1). Los circuitos de aproximación correctos son entonces producidos por el solucionador de problemas dinámicos en setup_dynamic_lse.

from qiskit_addon_utils.slicing import slice_by_depth
from qiskit_addon_mpf.backends.tenpy_layers import LayerModel
from qiskit_addon_mpf.backends.tenpy_layers import LayerwiseEvolver
from functools import partial

# Create approximate time-evolution circuits
single_2nd_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)
)
single_2nd_order_circ = pm.run(single_2nd_order_circ) # collect XX and YY

# Find layers in the circuit
layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)

# Create tensor network models
models = [
LayerModel.from_quantum_circuit(layer, conserve="Sz") for layer in layers
]

# Create the time-evolution object
approx_factory = partial(
LayerwiseEvolver,
layers=models,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 2,
},
)
aviso

Las opciones de LayerwiseEvolver que determinan los detalles de la simulación de redes tensoriales deben elegirse cuidadosamente para evitar configurar un problema de optimización mal definido.

Luego configuramos el evolucionador exacto (por ejemplo, ExactEvolverFactory), que devuelve un objeto Evolver que calcula la evolución temporal verdadera o "de referencia". De forma realista, aproximaríamos la evolución exacta utilizando una fórmula de Suzuki-Trotter de orden superior u otro método confiable con un paso temporal pequeño. A continuación, aproximamos el estado evolucionado temporalmente exacto con una fórmula de Suzuki-Trotter de cuarto orden utilizando un paso temporal pequeño dt=0.1, lo que significa que el número de pasos de Trotter utilizados en el tiempo tt es k=t/dtk=t/dt. También especificamos algunas opciones de truncamiento específicas de TeNPy para limitar la dimensión máxima de enlaza de la red tensorial subyacente, así como los valores singulares mínimos de los enlaces de la red tensorial dividida. Estos parámetros pueden afectar la precisión del valor esperado calculado con los coeficientes MPF dinámicos, por lo que es importante explorar un rango de valores para encontrar el equilibrio óptimo entre tiempo de cálculo y precisión. Ten en cuenta que el cálculo de los coeficientes MPF no depende del valor esperado de la PF obtenido de la ejecución en hardware, y por lo tanto puede ajustarse en post-procesamiento.

single_4th_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)
)
single_4th_order_circ = pm.run(single_4th_order_circ)
exact_model_layers = [
LayerModel.from_quantum_circuit(layer, conserve="Sz")
for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)
]

exact_factory = partial(
LayerwiseEvolver,
layers=exact_model_layers,
dt=0.1,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 2,
},
)

A continuación, crea el estado inicial de tu sistema en un formato compatible con TeNPy (por ejemplo, un MPS_neel_state=0101...01\vert 0101...01 \rangle). Esto configura la función de onda de muchos cuerpos que evolucionará en el tiempo ψin\lvert\psi_{\mathrm{in}}\rangle como un tensor.

from qiskit_addon_mpf.backends.tenpy_tebd import MPOState
from qiskit_addon_mpf.backends.tenpy_tebd import MPS_neel_state

def identity_factory():
return MPOState.initialize_from_lattice(models[0].lat, conserve=True)

mps_initial_state = MPS_neel_state(models[0].lat)

Para cada paso temporal tt configuramos el sistema lineal de ecuaciones dinámico con el método setup_dynamic_lse. El objeto correspondiente contiene la información sobre el problema MPF dinámico: lse.A proporciona la matriz de Gram MM mientras que lse.b proporciona el solapamiento LL. Luego podemos resolver el LSE (cuando no está mal definido) para encontrar los coeficientes dinámicos utilizando setup_frobenius_problem. Es importante notar la diferencia con los coeficientes estáticos, que solo dependen de los detalles de la fórmula de producto utilizada y son independientes de los detalles de la evolución temporal (hamiltoniano y estado inicial).

from qiskit_addon_mpf.dynamic import setup_dynamic_lse
from qiskit_addon_mpf.costs import setup_frobenius_problem

mpf_dynamic_coeffs_list = []
for t in trotter_times:
print(f"Computing dynamic coefficients for time={t}")
lse = setup_dynamic_lse(
mpf_trotter_steps,
t,
identity_factory,
exact_factory,
approx_factory,
mps_initial_state,
)
problem, coeffs = setup_frobenius_problem(lse)
try:
problem.solve()
mpf_dynamic_coeffs_list.append(coeffs.value)
except Exception as error:
mpf_dynamic_coeffs_list.append(np.zeros(len(mpf_trotter_steps)))
print(error, "Calculation Failed for time", t)
print("")
Computing dynamic coefficients for time=0.5

Computing dynamic coefficients for time=0.6

Computing dynamic coefficients for time=0.7

Computing dynamic coefficients for time=0.7999999999999999

Computing dynamic coefficients for time=0.8999999999999999

Computing dynamic coefficients for time=0.9999999999999999

Computing dynamic coefficients for time=1.0999999999999999

Computing dynamic coefficients for time=1.1999999999999997

Computing dynamic coefficients for time=1.2999999999999998

Computing dynamic coefficients for time=1.4

Computing dynamic coefficients for time=1.4999999999999998

Finalmente, graficamos estos valores esperados a lo largo del tiempo de evolución.

import matplotlib.pyplot as plt

sym = {1: "^", 2: "s", 4: "p"}
# Get expectation values at all times for each Trotter step
for k, step in enumerate(mpf_trotter_steps):
trotter_curve, trotter_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
trotter_curve.append(trotter_expvals[k])
trotter_curve_error.append(trotter_stds[k])

plt.errorbar(
trotter_times,
trotter_curve,
yerr=trotter_curve_error,
alpha=0.5,
markersize=4,
marker=sym[step],
color="grey",
label=f"{mpf_trotter_steps[k]} Trotter steps",
) # , , )

# Get expectation values at all times for the static MPF with exact coeffs
exact_mpf_curve, exact_mpf_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_exact.value, trotter_stds)
]
)
)
exact_mpf_curve_error.append(mpf_std)
exact_mpf_curve.append(trotter_expvals @ coeffs_exact.value)

plt.errorbar(
trotter_times,
exact_mpf_curve,
yerr=exact_mpf_curve_error,
markersize=4,
marker="o",
label="Static MPF - Exact",
color="purple",
)

# Get expectation values at all times for the static MPF with approximate
approx_mpf_curve, approx_mpf_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_approx.value, trotter_stds)
]
)
)
approx_mpf_curve_error.append(mpf_std)
approx_mpf_curve.append(trotter_expvals @ coeffs_approx.value)

plt.errorbar(
trotter_times,
approx_mpf_curve,
yerr=approx_mpf_curve_error,
markersize=4,
marker="o",
color="orange",
label="Static MPF - Approximate",
)

# # Get expectation values at all times for the dynamic MPF
dynamic_mpf_curve, dynamic_mpf_curve_error = [], []
for trotter_expvals, trotter_stds, dynamic_coeffs in zip(
mpf_expvals_all_times, mpf_stds_all_times, mpf_dynamic_coeffs_list
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(dynamic_coeffs, trotter_stds)
]
)
)
dynamic_mpf_curve_error.append(mpf_std)
dynamic_mpf_curve.append(trotter_expvals @ dynamic_coeffs)

plt.errorbar(
trotter_times,
dynamic_mpf_curve,
yerr=dynamic_mpf_curve_error,
markersize=4,
marker="o",
color="pink",
label="Dynamic MPF",
)

plt.plot(
exact_evolution_times,
exact_expvals,
lw=3,
color="red",
label="Exact time-evolution",
)

plt.title(
f"Expectation values for (ZZ,{(L//2-1, L//2)}) as a function of time"
)
plt.xlabel("Time")
plt.ylabel("Expectation Value")
plt.legend()
plt.grid()

Output of the previous code cell

En casos como el ejemplo anterior, donde la PF con k=1k=1 se comporta mal en todos los tiempos, la calidad de los resultados de la MPF dinámica también se ve fuertemente afectada. En tales situaciones, es útil investigar la posibilidad de utilizar PF individuales con un mayor número de pasos de Trotter para mejorar la calidad general de los resultados. En estas simulaciones, vemos la interacción de diferentes tipos de errores: error por muestreo finito y error de Trotter de las fórmulas de producto. La MPF ayuda a reducir el error de Trotter debido a las fórmulas de producto pero incurre en un mayor error de muestreo en comparación con las fórmulas de producto. Esto puede ser ventajoso, ya que las fórmulas de producto pueden reducir el error de muestreo con un muestreo incrementado, pero el error sistemático debido a la aproximación de Trotter permanece intacto.

Otro comportamiento interesante que podemos observar en el gráfico es que el valor esperado para la PF con k=1k=1 comienza a comportarse de manera errática (además de no ser una buena aproximación del valor exacto) en tiempos para los cuales t/k>1t/k > 1, como se explica en la guía sobre cómo elegir el número de pasos de Trotter.

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

Consideremos ahora un solo tiempo t=1.0t=1.0 y calculemos el valor esperado de la magnetización con los diversos métodos utilizando una QPU. La elección particular de tt se realizó para maximizar la diferencia entre los diversos métodos y observar su eficacia relativa. Para determinar la ventana de tiempo en la que se garantiza que la MPF dinámica produzca observables con menor error que cualquiera de las fórmulas de Trotter individuales dentro del multiproducto, podemos implementar la "prueba MPF" - véase la ecuación (17) y el texto circundante en [3].

Configurar los circuitos de Trotter

En este punto, hemos encontrado nuestros coeficientes de expansión, xx, y todo lo que queda por hacer es generar los circuitos cuánticos trotterizados. Una vez más, el módulo qiskit_addon_utils.problem_generators viene al rescate con una función útil para hacer esto:

from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit

total_time = 1.0
mpf_circuits = []
for k in mpf_trotter_steps:
# Initial Neel state preparation
circuit = QuantumCircuit(L)
circuit.x([i for i in range(L) if i % 2 != 0])

trotter_circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(order=order, reps=k),
time=total_time,
)

circuit.compose(trotter_circ, inplace=True)

mpf_circuits.append(pm.run(circuit))
mpf_circuits[-1].draw("mpl", fold=-1, scale=0.4)

Output of the previous code cell

Paso 2: Optimizar el problema para la ejecución en hardware cuántico

Volvamos al cálculo del valor esperado para un solo punto temporal. Seleccionaremos un backend para ejecutar el experimento en hardware.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)

qubits = list(range(backend.num_qubits))

Luego eliminamos los qubits atípicos del mapa de acoplamiento para asegurar que la etapa de distribución del transpilador no los incluye. A continuación, utilizamos las propiedades reportadas del backend almacenadas en el objeto target y eliminamos los qubits que tienen un error de medición o una compuerta de dos qubits por encima de cierto umbral (max_meas_err, max_twoq_err) o un tiempo T2T_2 (que determina la pérdida de coherencia) por debajo de cierto umbral (min_t2).

import copy
from qiskit.transpiler import Target, CouplingMap

target = backend.target
instruction_2q = "cz"

cmap = target.build_coupling_map(filter_idle_qubits=True)
cmap_list = list(cmap.get_edges())

max_meas_err = 0.012
min_t2 = 40
max_twoq_err = 0.005

# Remove qubits with bad measurement or t2
cust_cmap_list = copy.deepcopy(cmap_list)
for q in range(target.num_qubits):
meas_err = target["measure"][(q,)].error
if target.qubit_properties[q].t2 is not None:
t2 = target.qubit_properties[q].t2 * 1e6
else:
t2 = 0
if meas_err > max_meas_err or t2 < min_t2:
# print(q)
for q_pair in cmap_list:
if q in q_pair:
try:
cust_cmap_list.remove(q_pair)
except ValueError:
continue

# Remove qubits with bad 2q gate or t2
for q in cmap_list:
twoq_gate_err = target[instruction_2q][q].error
if twoq_gate_err > max_twoq_err:
# print(q)
for q_pair in cmap_list:
if q == q_pair:
try:
cust_cmap_list.remove(q_pair)
except ValueError:
continue

cust_cmap = CouplingMap(cust_cmap_list)

cust_target = Target.from_configuration(
basis_gates=backend.configuration().basis_gates
+ ["measure"], # or whatever new set of gates
coupling_map=cust_cmap,
)

sorted_components = sorted(
[list(comp.physical_qubits) for comp in cust_cmap.connected_components()],
reverse=True,
)
print("size of largest component", len(sorted_components[0]))
size of largest component 10

Queremos establecer max_meas_err, min_t2 y max_twoq_err de tal manera que encontremos un subconjunto de qubits lo suficientemente grande que soporte la ejecución del circuito. En nuestro caso, es suficiente encontrar una cadena 1D de 10 qubits.

cust_cmap.draw()

Output of the previous code cell

Luego podemos mapear el circuito y el observable a los qubits físicos del dispositivo.

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

transpiler = generate_preset_pass_manager(
optimization_level=3, target=cust_target
)

transpiled_circuits = [transpiler.run(circ) for circ in mpf_circuits]

qubits_layouts = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in transpiled_circuits
]

transpiled_circuits = []
for circuit, layout in zip(mpf_circuits, qubits_layouts):
transpiler = generate_preset_pass_manager(
optimization_level=3, backend=backend, initial_layout=layout
)
transpiled_circuit = transpiler.run(circuit)
transpiled_circuits.append(transpiled_circuit)

# transform the observable defined on virtual qubits to
# an observable defined on all physical qubits
isa_observables = [
observable.apply_layout(circ.layout) for circ in transpiled_circuits
]
print(transpiled_circuits[-1].depth(lambda x: x.operation.num_qubits == 2))
print(transpiled_circuits[-1].count_ops())
transpiled_circuits[-1].draw("mpl", idle_wires=False, fold=False)
51
OrderedDict([('sx', 310), ('rz', 232), ('cz', 132), ('x', 19)])

Output of the previous code cell

Paso 3: Ejecutar utilizando primitivas de Qiskit

Con la primitiva Estimator podemos obtener la estimación del valor esperado desde la QPU. Ejecutamos los circuitos AQC optimizados con técnicas adicionales de mitigación y supresión de errores.

from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(mode=backend)
estimator.options.default_shots = 30000

# Set simple error suppression/mitigation options
estimator.options.dynamical_decoupling.enable = True
estimator.options.twirling.enable_gates = True
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.strategy = "active-accum"
estimator.options.resilience.measure_mitigation = True
estimator.options.experimental.execution_path = "gen3-turbo"

estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")

estimator.options.environment.job_tags = ["mpf small"]

job = estimator.run(
[
(circ, observable)
for circ, observable in zip(transpiled_circuits, isa_observables)
]
)

Paso 4: Post-procesar y devolver el resultado en el formato clásico deseado

El único paso de post-procesamiento es combinar el valor esperado obtenido de las primitivas de Qiskit Runtime en diferentes pasos de Trotter utilizando los coeficientes MPF respectivos. Para un observable AA tenemos:

Ampf=Tr[Aμ(t)]=jxjTr[Aρkj]=jxjAj \langle A \rangle_{\text{mpf}} = \text{Tr} [A \mu(t)] = \sum_{j} x_j \text{Tr} [A \rho_{k_j}] = \sum_{j} x_j \langle A \rangle_j

Primero, extraemos los valores esperados individuales obtenidos para cada uno de los circuitos de Trotter:

result_exp = job.result()
evs_exp = [res.data.evs for res in result_exp]
evs_std = [res.data.stds for res in result_exp]

print(evs_exp)
[array(-0.06361607), array(-0.23820448), array(-0.50271805)]

A continuación, simplemente los recombinamos con nuestros coeficientes MPF para obtener los valores esperados totales de la MPF. A continuación, lo hacemos para cada una de las diferentes formas en que hemos calculado xx.

exact_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_exact.value, evs_std)
]
)
)
print(
"Exact static MPF expectation value: ",
evs_exp @ coeffs_exact.value,
"+-",
exact_mpf_std,
)
approx_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_approx.value, evs_std)
]
)
)
print(
"Approximate static MPF expectation value: ",
evs_exp @ coeffs_approx.value,
"+-",
approx_mpf_std,
)
dynamic_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(mpf_dynamic_coeffs_list[7], evs_std)
]
)
)
print(
"Dynamic MPF expectation value: ",
evs_exp @ mpf_dynamic_coeffs_list[7],
"+-",
dynamic_mpf_std,
)
Exact static MPF expectation value:  -0.6329590442738475 +- 0.012798249760406036
Approximate static MPF expectation value: -0.5690390035339492 +- 0.010459559917168473
Dynamic MPF expectation value: -0.4655579758795695 +- 0.007639139186720507

Finalmente, para este problema pequeño podemos calcular el valor de referencia exacto utilizando scipy.linalg.expm de la siguiente manera:

from scipy.linalg import expm
from qiskit.quantum_info import Statevector

exp_H = expm(-1j * total_time * hamiltonian.to_matrix())

initial_state_circuit = QuantumCircuit(L)
initial_state_circuit.x([i for i in range(L) if i % 2 != 0])
initial_state = Statevector(initial_state_circuit).data

time_evolved_state = exp_H @ initial_state

exact_obs = (
time_evolved_state.conj() @ observable.to_matrix() @ time_evolved_state
)
print("Exact expectation value ", exact_obs.real)
Exact expectation value  -0.39909900734489434
sym = {1: "^", 2: "s", 4: "p"}
# Get expectation values at all times for each Trotter step
for k, step in enumerate(mpf_trotter_steps):
plt.errorbar(
k,
evs_exp[k],
yerr=evs_std[k],
alpha=0.5,
markersize=4,
marker=sym[step],
color="grey",
label=f"{mpf_trotter_steps[k]} Trotter steps",
) # , , )

plt.errorbar(
3,
evs_exp @ coeffs_exact.value,
yerr=exact_mpf_std,
markersize=4,
marker="o",
color="purple",
label="Static MPF",
)

plt.errorbar(
4,
evs_exp @ coeffs_approx.value,
yerr=approx_mpf_std,
markersize=4,
marker="o",
color="orange",
label="Approximate static MPF",
)

plt.errorbar(
5,
evs_exp @ mpf_dynamic_coeffs_list[7],
yerr=dynamic_mpf_std,
markersize=4,
marker="o",
color="pink",
label="Dynamic MPF",
)

plt.axhline(
y=exact_obs.real,
linestyle="--",
color="red",
label="Exact time-evolution",
)

plt.title(
f"Expectation values for (ZZ,{(L//2-1, L//2)}) at time {total_time} for the different methods "
)
plt.xlabel("Method")
plt.ylabel("Expectation Value")
plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=2)
plt.grid(alpha=0.1)
plt.tight_layout()
plt.show()

Output of the previous code cell

En el ejemplo anterior, el método MPF dinámico se desempeña mejor en términos de valor esperado, mejorando lo que habríamos obtenido utilizando únicamente el mayor número de pasos de Trotter. Aunque las diversas técnicas MPF no siempre logran un valor esperado mejorado en comparación con el mayor número de pasos de Trotter (como el modelo exacto y el aproximado en el gráfico anterior), la desviación estándar de estos valores captura bien la varianza incrementada que se incurre al utilizar la técnica MPF. Esto resalta la incertidumbre alrededor del valor esperado obtenido, que siempre incluye el valor esperado que esperaríamos de una evolución temporal exacta del sistema. Por otro lado, los valores esperados calculados con el menor número de pasos de Trotter no logran capturar el valor esperado exacto dentro de su incertidumbre, devolviendo así con confianza el resultado incorrecto.

def relative_error(ev, exact_ev):
return abs(ev - exact_ev)

relative_error_k = [relative_error(ev, exact_obs.real) for ev in evs_exp]
relative_error_mpf = relative_error(evs_exp @ mpf_coeffs, exact_obs.real)
relative_error_approx_mpf = relative_error(
evs_exp @ coeffs_approx.value, exact_obs.real
)
relative_error_dynamic_mpf = relative_error(
evs_exp @ mpf_dynamic_coeffs_list[7], exact_obs.real
)

print("relative error for each trotter steps", relative_error_k)
print("relative error with MPF exact coeffs", relative_error_mpf)
print("relative error with MPF approx coeffs", relative_error_approx_mpf)
print("relative error with MPF dynamic coeffs", relative_error_dynamic_mpf)
relative error for each trotter steps [0.33548293650112293, 0.16089452939226306, 0.10361904247828346]
relative error with MPF exact coeffs 0.2338600369291003
relative error with MPF approx coeffs 0.16993999618905486
relative error with MPF dynamic coeffs 0.06645896853467514

Parte II: escalar el problema

Escalemos el problema más allá de lo que es posible simular de forma exacta. En esta sección nos enfocaremos en reproducir algunos de los resultados mostrados en la Ref. [3].

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

Hamiltoniano

Para el ejemplo a gran escala, utilizamos el modelo XXZ en una línea de 50 sitios:

H^XXZ=i=1L1Ji,(i+1)(XiX(i+1)+YiY(i+1)+2ZiZ(i+1)),\hat{\mathcal{H}}_{XXZ} = \sum_{i=1}^{L-1} J_{i,(i+1)}\left(X_i X_{(i+1)}+Y_i Y_{(i+1)}+ 2\cdot Z_i Z_{(i+1)} \right) \, ,

donde Ji,(i+1)J_{i,(i+1)} es un coeficiente aleatorio correspondiente a la arista (i,i+1)(i, i+1). Este es el hamiltoniano considerado en la demostración presentada en la Ref. [3].

L = 50
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(L, bidirectional=False)
graphviz_draw(coupling_map.graph, method="circo")

Output of the previous code cell

import numpy as np
from qiskit.quantum_info import SparsePauliOp, Pauli

# Generate random coefficients for our XXZ Hamiltonian
np.random.seed(0)
even_edges = list(coupling_map.get_edges())[::2]
odd_edges = list(coupling_map.get_edges())[1::2]

Js = np.random.uniform(0.5, 1.5, size=L)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
hamiltonian += SparsePauliOp.from_sparse_list(
[
("XX", (edge), 2 * Js[i]),
("YY", (edge), 2 * Js[i]),
("ZZ", (edge), 4 * Js[i]),
],
num_qubits=L,
)

print(hamiltonian)
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXX', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYY', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'XXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'YYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXI', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYI', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZI', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IXXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1. +0.j, 2.09762701+0.j, 2.09762701+0.j, 4.19525402+0.j,
2.43037873+0.j, 2.43037873+0.j, 4.86075747+0.j, 2.20552675+0.j,
2.20552675+0.j, 4.4110535 +0.j, 2.08976637+0.j, 2.08976637+0.j,
4.17953273+0.j, 1.8473096 +0.j, 1.8473096 +0.j, 3.6946192 +0.j,
2.29178823+0.j, 2.29178823+0.j, 4.58357645+0.j, 1.87517442+0.j,
1.87517442+0.j, 3.75034885+0.j, 2.783546 +0.j, 2.783546 +0.j,
5.567092 +0.j, 2.92732552+0.j, 2.92732552+0.j, 5.85465104+0.j,
1.76688304+0.j, 1.76688304+0.j, 3.53376608+0.j, 2.58345008+0.j,
2.58345008+0.j, 5.16690015+0.j, 2.05778984+0.j, 2.05778984+0.j,
4.11557968+0.j, 2.13608912+0.j, 2.13608912+0.j, 4.27217824+0.j,
2.85119328+0.j, 2.85119328+0.j, 5.70238655+0.j, 1.14207212+0.j,
1.14207212+0.j, 2.28414423+0.j, 1.1742586 +0.j, 1.1742586 +0.j,
2.3485172 +0.j, 1.04043679+0.j, 1.04043679+0.j, 2.08087359+0.j,
2.66523969+0.j, 2.66523969+0.j, 5.33047938+0.j, 2.5563135 +0.j,
2.5563135 +0.j, 5.112627 +0.j, 2.7400243 +0.j, 2.7400243 +0.j,
5.48004859+0.j, 2.95723668+0.j, 2.95723668+0.j, 5.91447337+0.j,
2.59831713+0.j, 2.59831713+0.j, 5.19663426+0.j, 1.92295872+0.j,
1.92295872+0.j, 3.84591745+0.j, 2.56105835+0.j, 2.56105835+0.j,
5.12211671+0.j, 1.23654885+0.j, 1.23654885+0.j, 2.4730977 +0.j,
2.27984204+0.j, 2.27984204+0.j, 4.55968409+0.j, 1.28670657+0.j,
1.28670657+0.j, 2.57341315+0.j, 2.88933783+0.j, 2.88933783+0.j,
5.77867567+0.j, 2.04369664+0.j, 2.04369664+0.j, 4.08739329+0.j,
1.82932388+0.j, 1.82932388+0.j, 3.65864776+0.j, 1.52911122+0.j,
1.52911122+0.j, 3.05822245+0.j, 2.54846738+0.j, 2.54846738+0.j,
5.09693476+0.j, 1.91230066+0.j, 1.91230066+0.j, 3.82460133+0.j,
2.1368679 +0.j, 2.1368679 +0.j, 4.2737358 +0.j, 1.0375796 +0.j,
1.0375796 +0.j, 2.0751592 +0.j, 2.23527099+0.j, 2.23527099+0.j,
4.47054199+0.j, 2.22419145+0.j, 2.22419145+0.j, 4.44838289+0.j,
2.23386799+0.j, 2.23386799+0.j, 4.46773599+0.j, 2.88749616+0.j,
2.88749616+0.j, 5.77499231+0.j, 2.3636406 +0.j, 2.3636406 +0.j,
4.7272812 +0.j, 1.7190158 +0.j, 1.7190158 +0.j, 3.4380316 +0.j,
1.87406391+0.j, 1.87406391+0.j, 3.74812782+0.j, 2.39526239+0.j,
2.39526239+0.j, 4.79052478+0.j, 1.12045094+0.j, 1.12045094+0.j,
2.24090189+0.j, 2.33353343+0.j, 2.33353343+0.j, 4.66706686+0.j,
2.34127574+0.j, 2.34127574+0.j, 4.68255148+0.j, 1.42076512+0.j,
1.42076512+0.j, 2.84153024+0.j, 1.2578526 +0.j, 1.2578526 +0.j,
2.51570519+0.j, 1.6308567 +0.j, 1.6308567 +0.j, 3.2617134 +0.j])

Para el observable elegimos Z24Z25Z_{24}Z_{25}, como se muestra en el panel inferior de la Fig. 5 de la Ref. [3].

observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])

Elegir los pasos de Trotter

El experimento mostrado en la Fig. 4 de la Ref. [3] utiliza kj=[2,3,4]k_j = [2, 3, 4] pasos de Trotter simétricos de orden 22. Nos enfocamos en los resultados para el tiempo t=3t=3, donde la MPF y una PF con un mayor número de pasos de Trotter (6 en este caso) tienen el mismo error de Trotter. Sin embargo, el valor esperado de la MPF se calcula a partir de circuitos correspondientes al menor número de pasos de Trotter y, por lo tanto, más superficiales. En la práctica, incluso si la MPF y el circuito de pasos de Trotter más profundo tienen el mismo error de Trotter, esperamos que el valor esperado experimental calculado a partir de los circuitos MPF esté más cerca del valor teórico, ya que implica ejecutar circuitos más superficiales menos expuestos al ruido del hardware en comparación con el circuito correspondiente a la PF de mayor número de pasos de Trotter.

total_time = 3
mpf_trotter_steps = [2, 3, 4]
order = 2
symmetric = True

Configurar el LSE

Aquí examinamos los coeficientes MPF estáticos para este problema.

lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)
mpf_coeffs = lse.solve()
print(
f"The static coefficients associated with the ansatze are: {mpf_coeffs}"
)
print("L1 norm:", np.linalg.norm(mpf_coeffs, ord=1))
The static coefficients associated with the ansatze are: [ 0.26666667 -2.31428571  3.04761905]
L1 norm: 5.628571428571431
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=2.0
)
model_approx.solve()
print(coeffs_approx.value)
print(
"L1 norm of the approximate coefficients:",
np.linalg.norm(coeffs_approx.value, ord=1),
)
[-0.24255546 -0.25744454  1.5       ]
L1 norm of the approximate coefficients: 2.0

Coeficientes dinámicos

# Create approximate time-evolution circuits
single_2nd_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)
)
single_2nd_order_circ = pm.run(single_2nd_order_circ) # collect XX and YY

# Find layers in the circuit
layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)

# Create tensor network models
models = [
LayerModel.from_quantum_circuit(layer, conserve="Sz") for layer in layers
]

# Create the time-evolution object
approx_factory = partial(
LayerwiseEvolver,
layers=models,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 4,
},
)

# Create exact time-evolution circuits
single_4th_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)
)
single_4th_order_circ = pm.run(single_4th_order_circ)
exact_model_layers = [
LayerModel.from_quantum_circuit(layer, conserve="Sz")
for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)
]

# Create the time-evolution object
exact_factory = partial(
LayerwiseEvolver,
layers=exact_model_layers,
dt=0.1,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 3,
},
)

def identity_factory():
return MPOState.initialize_from_lattice(models[0].lat, conserve=True)

mps_initial_state = MPS_neel_state(models[0].lat)

lse = setup_dynamic_lse(
mpf_trotter_steps,
total_time,
identity_factory,
exact_factory,
approx_factory,
mps_initial_state,
)
problem, coeffs = setup_frobenius_problem(lse)
try:
problem.solve()
mpf_dynamic_coeffs = coeffs.value
except Exception as error:
print(error, "Calculation Failed for time", total_time)
print("")

Construir cada uno de los circuitos de Trotter en nuestra descomposición MPF

from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit import QuantumCircuit

mpf_circuits = []
for k in mpf_trotter_steps:
# Initial state preparation |1010..>
circuit = QuantumCircuit(L)
circuit.x([i for i in range(L) if i % 2])

trotter_circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=k, order=order),
time=total_time,
)

circuit.compose(trotter_circ, qubits=range(L), inplace=True)

mpf_circuits.append(circuit)

Construir el circuito de Trotter con error de Trotter comparable al de la MPF

k = 6

# Initial state preparation |1010..>
comp_circuit = QuantumCircuit(L)
comp_circuit.x([i for i in range(L) if i % 2])

trotter_circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=k, order=order),
time=total_time,
)

comp_circuit.compose(trotter_circ, qubits=range(L), inplace=True)

mpf_circuits.append(comp_circuit)

Paso 2: Optimizar el problema para la ejecución en hardware cuántico

import copy
from qiskit.transpiler import Target, CouplingMap

target = backend.target
instruction_2q = "cz"

cmap = target.build_coupling_map(filter_idle_qubits=True)
cmap_list = list(cmap.get_edges())

max_meas_err = 0.055
min_t2 = 30
max_twoq_err = 0.01

# Remove qubits with bad measurement or t2
cust_cmap_list = copy.deepcopy(cmap_list)
for q in range(target.num_qubits):
meas_err = target["measure"][(q,)].error
if target.qubit_properties[q].t2 is not None:
t2 = target.qubit_properties[q].t2 * 1e6
else:
t2 = 0
if meas_err > max_meas_err or t2 < min_t2:
# print(q)
for q_pair in cmap_list:
if q in q_pair:
try:
cust_cmap_list.remove(q_pair)
except ValueError:
continue

# Remove qubits with bad 2q gate or t2
for q in cmap_list:
twoq_gate_err = target[instruction_2q][q].error
if twoq_gate_err > max_twoq_err:
# print(q)
for q_pair in cmap_list:
if q == q_pair:
try:
cust_cmap_list.remove(q_pair)
except ValueError:
continue

cust_cmap = CouplingMap(cust_cmap_list)

cust_target = Target.from_configuration(
basis_gates=backend.configuration().basis_gates
+ ["measure"], # or whatever new set of gates
coupling_map=cust_cmap,
)

sorted_components = sorted(
[list(comp.physical_qubits) for comp in cust_cmap.connected_components()],
reverse=True,
)
print("size of largest component", len(sorted_components[0]))
size of largest component 73
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

transpiler = generate_preset_pass_manager(
optimization_level=3, target=cust_target
)

transpiled_circuits = [transpiler.run(circ) for circ in mpf_circuits]

qubits_layouts = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in transpiled_circuits
]

transpiled_circuits = []
for circuit, layout in zip(mpf_circuits, qubits_layouts):
transpiler = generate_preset_pass_manager(
optimization_level=3, backend=backend, initial_layout=layout
)
transpiled_circuit = transpiler.run(circuit)
transpiled_circuits.append(transpiled_circuit)

# transform the observable defined on virtual qubits to
# an observable defined on all physical qubits
isa_observables = [
observable.apply_layout(circ.layout) for circ in transpiled_circuits
]

Paso 3: Ejecutar utilizando primitivas de Qiskit

from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(mode=backend)
estimator.options.default_shots = 30000

# Set simple error suppression/mitigation options
estimator.options.dynamical_decoupling.enable = True
estimator.options.twirling.enable_gates = True
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.strategy = "active-accum"
estimator.options.resilience.measure_mitigation = True
estimator.options.experimental.execution_path = "gen3-turbo"

estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 1.2, 1.4)
estimator.options.resilience.zne.extrapolator = "linear"

estimator.options.environment.job_tags = ["mpf large"]

job_50 = estimator.run(
[
(circ, observable)
for circ, observable in zip(transpiled_circuits, isa_observables)
]
)

Paso 4: Post-procesar y devolver el resultado en el formato clásico deseado

result = job_50.result()
evs = [res.data.evs for res in result]
std = [res.data.stds for res in result]

print(evs)
print(std)
[array(-0.08034071), array(-0.00605026), array(-0.15345759), array(-0.18127293)]
[array(0.04482517), array(0.03438413), array(0.21540776), array(0.21520829)]
exact_mpf_std = np.sqrt(
sum([(coeff**2) * (std**2) for coeff, std in zip(mpf_coeffs, std[:3])])
)
print(
"Exact static MPF expectation value: ",
evs[:3] @ mpf_coeffs,
"+-",
exact_mpf_std,
)
approx_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_approx.value, std[:3])
]
)
)
print(
"Approximate static MPF expectation value: ",
evs[:3] @ coeffs_approx.value,
"+-",
approx_mpf_std,
)
dynamic_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(mpf_dynamic_coeffs, std[:3])
]
)
)
print(
"Dynamic MPF expectation value: ",
evs[:3] @ mpf_dynamic_coeffs,
"+-",
dynamic_mpf_std,
)
Exact static MPF expectation value:  -0.47510243192011536 +- 0.6613940032465087
Approximate static MPF expectation value: -0.20914170384216998 +- 0.32341567460419135
Dynamic MPF expectation value: -0.07994951978722761 +- 0.07423091963310202
sym = {2: "^", 3: "s", 4: "p"}
# Get expectation values at all times for each Trotter step
for k, step in enumerate(mpf_trotter_steps):
plt.errorbar(
k,
evs[k],
yerr=std[k],
alpha=0.5,
markersize=4,
marker=sym[step],
color="grey",
label=f"{mpf_trotter_steps[k]} Trotter steps",
)

plt.errorbar(
3,
evs[-1],
yerr=std[-1],
alpha=0.5,
markersize=8,
marker="x",
color="blue",
label="6 Trotter steps",
)

plt.errorbar(
4,
evs[:3] @ mpf_coeffs,
yerr=exact_mpf_std,
markersize=4,
marker="o",
color="purple",
label="Static MPF",
)

plt.errorbar(
5,
evs[:3] @ coeffs_approx.value,
yerr=approx_mpf_std,
markersize=4,
marker="o",
color="orange",
label="Approximate static MPF",
)

plt.errorbar(
6,
evs[:3] @ mpf_dynamic_coeffs,
yerr=dynamic_mpf_std,
markersize=4,
marker="o",
color="pink",
label="Dynamic MPF",
)

exact_obs = -0.24384471447172074 # Calculated via Tensor Network calculation
plt.axhline(
y=exact_obs, linestyle="--", color="red", label="Exact time-evolution"
)

plt.title(
f"Expectation values for (ZZ,{(L//2-1, L//2)}) at time {total_time} for the different methods "
)
plt.xlabel("Method")
plt.ylabel("Expectation Value")
plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=2)
plt.grid(alpha=0.1)
plt.tight_layout()
plt.show()

Output of the previous code cell

Al ejecutar circuitos en hardware, podemos encontrar desafíos adicionales para obtener valores esperados precisos debido a la presencia de ruido del hardware. Esto no se tiene en cuenta en el formalismo MPF y podría actuar en contra de la solución MPF. Por ejemplo, esta podría ser la razón del fracaso de los coeficientes dinámicos para proporcionar una mejor estimación del valor esperado en comparación con el coeficiente estático aproximado en el gráfico. Es decir, el evolucionador aproximado, que simula el circuito aproximado, no refleja con precisión los resultados obtenidos al ejecutar los circuitos aproximados en presencia de ruido del hardware. Por estas razones, se recomienda combinar diferentes técnicas de mitigación de errores para obtener resultados lo más cercanos posible a los valores ideales para cada una de las fórmulas de producto. Esto mostrará beneficios consistentes del enfoque MPF.

En general, los coeficientes estáticos aproximados aún proporcionan una solución más precisa que la fórmula de producto con mayor número de pasos de Trotter con la misma cantidad de error de Trotter en el escenario sin ruido.

También es importante notar que en el ejemplo que reproduce el experimento de la Ref. [3], el punto temporal t=3t=3 está más allá del límite en el que se espera que la PF con k=2k=2 se comporte bien, que es t/k>1t/k>1 como se discutió en esta guía.

Referencias

[1] Vazquez, A. C., Egger, D. J., Ochsner, D., & Woerner, S. (2023). Well-conditioned multi-product formulas for hardware-friendly Hamiltonian simulation. Quantum, 7, 1067.

[2] Zhuk, S., Robertson, N. F., & Bravyi, S. (2024). Trotter error bounds and dynamic multi-product formulas for Hamiltonian simulation. Physical Review Research, 6(3), 033309.

[3] Robertson, N. F., et al. (2024). Tensor network enhanced dynamic multiproduct formulas. arXiv preprint arXiv:2407.17405.