Primeiros passos com fórmulas multi-produto (MPF)
Primeiros passos com fórmulas multi-produto (MPFs)
Versões dos pacotes
O código desta página foi desenvolvido com os seguintes requisitos. Recomendamos usar essas versões ou mais recentes.
qiskit[all]~=2.3.0
qiskit-addon-utils~=0.3.0
qiskit-addon-mpf~=0.3.0
scipy~=1.16.3
Este guia demonstra como usar o pacote qiskit-addon-mpf, utilizando a evolução temporal de um modelo de Ising como exemplo. Com esse pacote, você pode construir uma Fórmula Multi-Produto (MPF) que alcança um erro de Trotter menor nas medições de observáveis. As ferramentas fornecidas permitem determinar os pesos de uma MPF escolhida, que podem ser usados para recombinar os valores esperados estimados a partir de vários circuitos de evolução temporal, cada um com um número diferente de passos de Trotter.
Comece considerando o Hamiltoniano de um modelo de Ising com 10 sítios:
onde é a intensidade do acoplamento e é a intensidade do campo magnético externo. Para configurar o problema, o observável a ser medido será a magnetização total do sistema
O trecho de código abaixo prepara o Hamiltoniano da cadeia de Ising usando o pacote qiskit-addon-utils e define o observável que será medido.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-mpf qiskit-addon-utils scipy
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import SuzukiTrotter
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import StatevectorEstimator
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_addon_utils.problem_generators import (
generate_xyz_hamiltonian,
generate_time_evolution_circuit,
)
from qiskit_addon_mpf.costs import (
setup_exact_problem,
setup_sum_of_squares_problem,
)
from qiskit_addon_mpf.static import setup_static_lse
from scipy.linalg import expm
import numpy as np
# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(10, bidirectional=False)
# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)
print(f"Hamiltonian:\n {hamiltonian}\n")
L = coupling_map.size()
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L
)
print(f"Observable:\n {observable}")
Hamiltonian:
SparsePauliOp(['IIIIIIIZZI', 'IIIIIZZIII', 'IIIZZIIIII', 'IZZIIIIIII', 'IIIIIIIIZZ', 'IIIIIIZZII', 'IIIIZZIIII', 'IIZZIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIXI', 'IIIIIIIXII', 'IIIIIIXIII', 'IIIIIXIIII', 'IIIIXIIIII', 'IIIXIIIIII', 'IIXIIIIIII', 'IXIIIIIIII', 'XIIIIIIIII'],
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, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j,
0.4+0.j, 0.4+0.j, 0.4+0.j])
Observable:
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j,
0.05+0.j, 0.05+0.j, 0.05+0.j])
Em seguida, você prepara a MPF. A primeira escolha a ser feita é se os coeficientes serão estáticos (independentes do tempo) ou dinâmicos; este tutorial usa uma MPF estática. A próxima escolha é o conjunto de valores . Isso determina quantos termos a MPF terá, bem como quantos passos de Trotter cada termo usa para simular a evolução temporal. A escolha dos valores é heurística, portanto você precisa obter seu próprio conjunto de "bons" valores . Leia as diretrizes para encontrar um bom conjunto de valores ao final da página de primeiros passos.
Em seguida, uma vez determinados os valores , você pode preparar o sistema de equações a resolver. A matriz também é determinada pela fórmula de produto a ser usada. As escolhas aqui são sua ordem, que é definida como neste exemplo, e se a fórmula de produto deve ser simétrica, que é definida como True neste exemplo. O trecho de código abaixo seleciona um tempo total para evoluir o sistema, os valores a serem usados e o conjunto de equações a resolver usando o método qiskit_addon_mpf.static.setup_static_lse.
time = 8.0
trotter_steps = (8, 12, 19)
lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
print(lse)
LSE(A=array([[1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
[1.56250000e-02, 6.94444444e-03, 2.77008310e-03],
[2.44140625e-04, 4.82253086e-05, 7.67336039e-06]]), b=array([1., 0., 0.]))
Uma vez instanciado o sistema linear de equações, ele pode ser resolvido de forma exata ou por meio de um modelo aproximado usando uma soma de quadrados (ou a norma de Frobenius para coeficientes dinâmicos; consulte a referência da API para mais informações). A escolha de usar um modelo aproximado geralmente surge quando a norma dos coeficientes para o conjunto escolhido de valores é considerada muito alta e um conjunto diferente de valores não pode ser escolhido. Este guia demonstra ambas as abordagens para comparar os resultados.
model_exact, coeffs_exact = setup_exact_problem(lse)
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=3.0
)
model_exact.solve()
model_approx.solve()
print(f"Exact solution: {coeffs_exact.value}")
print(f"Approximate solution: {coeffs_approx.value}")
Exact solution: [ 0.17239057 -1.19447005 2.02207947]
Approximate solution: [-0.40454257 0.57553173 0.8290123 ]
O objeto LSE também possui um método LSE.solve(), que resolve o sistema de equações de forma exata. O motivo pelo qual setup_exact_problem() é usado neste guia é para demonstrar a interface fornecida pelos demais métodos aproximados.
Configurar e executar circuitos de Trotter
Agora que os coeficientes foram obtidos, o último passo é gerar os circuitos de evolução temporal para a ordem e o conjunto escolhido de passos da MPF. O pacote qiskit-addon-utils pode acelerar esse processo.
circuits = []
for k in trotter_steps:
circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(order=2, reps=k),
time=time,
)
circuits.append(circ)
circuits[0].draw("mpl", fold=-1)
circuits[1].draw("mpl", fold=-1)
circuits[2].draw("mpl", fold=-1)
Uma vez construídos esses circuitos, você pode transpilar e executá-los usando um QPU. Para este exemplo, usaremos apenas um dos simuladores sem ruído para demonstrar como o erro de Trotter é reduzido.
backend = GenericBackendV2(num_qubits=10)
transpiler = generate_preset_pass_manager(
optimization_level=2, backend=backend
)
transpiled_circuits = [transpiler.run(circ) for circ in circuits]
estimator = StatevectorEstimator()
job = estimator.run([(circ, observable) for circ in transpiled_circuits])
result = job.result()
mpf_evs = [res.data.evs for res in result]
print(mpf_evs)
[array(0.23799162), array(0.35754312), array(0.38649906)]
Reconstruir os resultados
Agora que os circuitos foram executados, reconstruir os resultados é bastante simples. Conforme mencionado na página de visão geral das MPFs, nosso observável é reconstruído por meio da soma ponderada
onde são os coeficientes encontrados e é a estimativa do observável para cada circuito. Podemos então comparar os resultados obtidos com o valor exato usando o pacote scipy.linalg.
exp_H = expm(-1j * time * hamiltonian.to_matrix())
initial_state = np.zeros(exp_H.shape[0])
initial_state[0] = 1.0
time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj() @ observable.to_matrix() @ time_evolved_state
)
# Print out the different observable measurements
print(f"Exact value: {exact_obs.real}")
print(f"PF with 19 steps: {mpf_evs[-1]}")
print(f"MPF using exact solution: {mpf_evs @ coeffs_exact.value}")
print(f"MPF using approximate solution: {mpf_evs @ coeffs_approx.value}")
Exact value: 0.4006024248789992
PF with 19 steps: 0.3864990619977402
MPF using exact solution: 0.3954847855979902
MPF using approximate solution: 0.4299121425348959
Aqui você pode ver que a MPF reduziu o erro de Trotter em comparação com o obtido usando apenas uma única PF com . No entanto, o modelo aproximado resultou em um valor esperado pior do que o modelo exato. Isso demonstra a importância de usar critérios de convergência rígidos no modelo aproximado e de encontrar um "bom" conjunto de valores .