Pular para o conteúdo principal

Primeiros passos com compilação quântica aproximada com redes tensoriais (AQC-Tensor)

Versões dos pacotes

O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.3.0
qiskit-aer~=0.17
qiskit-addon-utils~=0.3.0
qiskit-addon-aqc-tensor[aer,quimb-jax]~=0.2.0; sys.platform != 'darwin'
scipy~=1.16.3

Este guia demonstra um exemplo simples e funcional para começar a usar o AQC-Tensor. Neste exemplo, você utilizará um circuit de Trotter que simula a evolução de um modelo de Ising em campo transverso e usará o método AQC-Tensor para reduzir a profundidade do circuit resultante. Além disso, este exemplo requer o pacote qiskit-addon-utils para o gerador de problemas, qiskit-aer para a simulação de rede tensorial e scipy para a otimização de parâmetros.

Para começar, lembre-se de que o Hamiltoniano do modelo de Ising em campo transverso tem a forma

HIsing=i=1NJi,(i+1)ZiZi+1+hiXi\mathcal{H}_{Ising} = \sum_{i=1}^N J_{i,(i+1)}Z_iZ_{i+1} + h_i X_i

onde assumiremos condições de contorno periódicas, que implicam que para i=10i=10 obtemos i+1=111i+1=11\rightarrow 1, e JJ é a força de acoplamento entre dois sítios e hh é a força do campo magnético externo.

O trecho de código a seguir irá gerar o Hamiltoniano de uma cadeia de Ising com 10 sítios e condições de contorno periódicas.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-aer scipy
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
from qiskit_addon_aqc_tensor.simulation import compute_overlap
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
from qiskit_aer import AerSimulator
from scipy.optimize import OptimizeResult, minimize

# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)

# Choose a 10-qubit circle on this coupling map
reduced_coupling_map = coupling_map.reduce(
[0, 13, 1, 14, 10, 16, 4, 15, 3, 9]
)

# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
reduced_coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)

Dividir a evolução temporal em duas partes para execução clássica e quântica

O objetivo geral deste exemplo é simular a evolução temporal do Hamiltoniano modelo. Fazemos isso aqui por meio da evolução de Trotter, que será dividida em duas partes.

  1. Uma parte inicial que pode ser simulada usando estados de produto matricial (MPS). Isso é o que será "compilado" usando o AQC-Tensor.
  2. Uma parte subsequente que será executada em hardware quântico.

Optaremos por evoluir o sistema até o tempo tf=5t_f=5 e usar o AQC-Tensor para comprimir a evolução temporal até o tempo t=4t=4, para então evoluir usando passos de Trotter ordinários até tft_f.

A partir daqui, geraremos dois circuits: um que será comprimido usando o AQC-Tensor e outro que será executado em um QPU. Para o primeiro circuit, como este será simulado classicamente usando estados de produto matricial, usaremos um número generoso de camadas para minimizar o erro de Trotter. Enquanto isso, o segundo circuit que simula a evolução temporal de ti=4t_i=4 até tf=5t_f=5 usará muito menos camadas para minimizar a profundidade.

# Generate circuit to be compressed
aqc_evolution_time = 4.0
aqc_target_num_trotter_steps = 45

aqc_target_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
)

# Generate circuit to execute on hardware
subsequent_evolution_time = 1.0
subsequent_num_trotter_steps = 5

subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)

Para fins de comparação, também geraremos um terceiro circuit: aquele que evolui até t=4t=4, mas com o mesmo número de camadas que o segundo circuit evoluindo de ti=4t_i=4 até tf=5t_f=5. Este é o circuit que teríamos executado caso não tivéssemos usado a técnica AQC-Tensor. Chamaremos este de circuit de comparação por ora.

aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
aqc_comparison_num_trotter_steps

comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)

Gerar o ansatz e construir a simulação MPS

Em seguida, geraremos o ansatz que otimizaremos. Ele evoluirá até o mesmo tempo de evolução que nosso primeiro circuit (de ti=0t_i=0 até tf=4t_f=4), mas com menos passos de Trotter.

Após a geração do circuit, passamos ele para a função generate_ansatz_from_circuit() do AQC-Tensor, que analisa a conectividade de dois Qubits e retorna duas coisas. A primeira é um circuit ansatz gerado com a mesma conectividade de dois Qubits, e a segunda é um conjunto de parâmetros que, quando inseridos no ansatz, reproduzem o circuit de entrada.

aqc_ansatz_num_trotter_steps = 5

aqc_good_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
time=aqc_evolution_time,
)

aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
aqc_good_circuit, qubits_initially_zero=True
)
aqc_ansatz.draw("mpl", fold=-1)

Saída da célula de código anterior

Em seguida, construiremos a representação MPS do estado a ser aproximado pelo AQC. Também calcularemos a fidelidade ψ1ψ22|\langle\psi_1 | \psi_2 \rangle |^2 entre o estado preparado pelo circuit de comparação e o circuit que gera o estado alvo (que utilizou mais passos de Trotter).

# Generate MPS simulator settings and obtain the MPS representation of the target state
simulator_settings = AerSimulator(
method="matrix_product_state",
matrix_product_state_max_bond_dimension=100,
)
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)

# Compute the fidelity between the MPS representation of the target state and the state produced by the comparison circuit
comparison_mps = tensornetwork_from_circuit(
comparison_circuit, simulator_settings
)
comparison_fidelity = (
abs(compute_overlap(comparison_mps, aqc_target_mps)) ** 2
)
print(f"Comparison fidelity: {comparison_fidelity}")
Comparison fidelity: 0.9997111919739693

Otimizar os parâmetros do ansatz usando o MPS

Por fim, otimizaremos o circuit ansatz para que ele produza o estado alvo com uma fidelidade maior do que a comparison_fidelity. A função de custo a minimizar será a MaximizeStateFidelity e será otimizada usando o otimizador L-BFGS do scipy.

objective = MaximizeStateFidelity(
aqc_target_mps, aqc_ansatz, simulator_settings
)

stopping_point = 1 - comparison_fidelity

def callback(intermediate_result: OptimizeResult):
print(f"Intermediate result: Fidelity {1 - intermediate_result.fun:.8}")
if intermediate_result.fun < stopping_point:
# Good enough for now
raise StopIteration

result = minimize(
objective,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": 100},
callback=callback,
)
if (
result.status
not in (
0,
1,
99,
)
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)

print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
Intermediate result: Fidelity 0.95084365
Intermediate result: Fidelity 0.98409893
Intermediate result: Fidelity 0.99142033
Intermediate result: Fidelity 0.99521405
Intermediate result: Fidelity 0.99566673
Intermediate result: Fidelity 0.99650054
Intermediate result: Fidelity 0.99683487
Intermediate result: Fidelity 0.99720426
Intermediate result: Fidelity 0.99761726
Intermediate result: Fidelity 0.99809073
Intermediate result: Fidelity 0.99838244
Intermediate result: Fidelity 0.99861841
Intermediate result: Fidelity 0.99874617
Intermediate result: Fidelity 0.99892696
Intermediate result: Fidelity 0.99908129
Intermediate result: Fidelity 0.99917737
Intermediate result: Fidelity 0.99925456
Intermediate result: Fidelity 0.99933134
Intermediate result: Fidelity 0.99947173
Intermediate result: Fidelity 0.99956469
Intermediate result: Fidelity 0.99964488
Intermediate result: Fidelity 0.99967419
Intermediate result: Fidelity 0.99968821
Intermediate result: Fidelity 0.9997448
Done after 24 iterations.

Neste ponto, temos um conjunto de parâmetros que geram o estado alvo com uma fidelidade maior do que o circuit de comparação teria produzido sem o uso do AQC. Com esses parâmetros otimizados, o circuit comprimido agora tem menos erro de Trotter e menos profundidade do que o circuit original.

Como etapa final, o trecho de código a seguir constrói o circuit completo de evolução temporal, que pode ser passado para um pipeline de Transpiler e executado em hardware quântico.

final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

Saída da célula de código anterior

Próximas etapas