Vai al contenuto principale

Inizia con la compilazione quantistica approssimata con reti tensoriali (AQC-Tensor)

Versioni dei pacchetti

Il codice in questa pagina è stato sviluppato usando i seguenti requisiti. Si consiglia di usare queste versioni o versioni più recenti.

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

Questa guida illustra un semplice esempio funzionante per iniziare a usare AQC-Tensor. In questo esempio prenderai un circuito di Trotter che simula l'evoluzione di un modello di Ising a campo trasverso e utilizzerai il metodo AQC-Tensor per ridurre la profondità del circuito risultante. Inoltre, questo esempio richiede il pacchetto qiskit-addon-utils per il generatore del problema, qiskit-aer per la simulazione tramite reti tensoriali e scipy per l'ottimizzazione dei parametri.

Per cominciare, ricorda che l'hamiltoniano del modello di Ising a campo trasverso ha la 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

dove si assumono condizioni al contorno periodiche, il che implica che per i=10i=10 si ottiene i+1=11→1i+1=11\rightarrow 1; JJ è la costante di accoppiamento tra due siti e hh è l'intensità del campo magnetico esterno.

Il seguente frammento di codice genera l'hamiltoniano di una catena di Ising a 10 siti con condizioni al contorno periodiche.

# 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),
)

Dividi l'evoluzione temporale in due parti per l'esecuzione classica e quantistica​

L'obiettivo generale di questo esempio è simulare l'evoluzione temporale dell'hamiltoniano del modello. Lo facciamo qui tramite l'evoluzione di Trotter, che viene suddivisa in due parti.

  1. Una parte iniziale simulabile con gli stati prodotto a matrice (MPS). Questa è la parte che verrà "compilata" con AQC-Tensor.
  2. Una parte successiva che verrà eseguita su hardware quantistico.

Sceglieremo di evolvere il sistema fino al tempo tf=5t_f=5 e di usare AQC-Tensor per comprimere l'evoluzione temporale fino al tempo t=4t=4, per poi continuare con i normali passi di Trotter fino a tft_f.

Da qui genereremo due circuiti: uno che verrà compresso con AQC-Tensor e uno che verrà eseguito su un QPU. Per il primo circuito, poiché verrà simulato classicamente con gli stati prodotto a matrice, utilizzeremo un numero generoso di layer per minimizzare l'errore di Trotter. Il secondo circuito, che simula l'evoluzione temporale da ti=4t_i=4 a tf=5t_f=5, userà invece molti meno layer per minimizzare la profondità.

# 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,
)

A scopo di confronto, genereremo anche un terzo circuito: uno che evolve fino a t=4t=4, ma con lo stesso numero di layer del secondo circuito che evolve da ti=4t_i=4 a tf=5t_f=5. Questo è il circuito che avremmo eseguito se non avessimo usato la tecnica AQC-Tensor. Lo chiameremo circuito di confronto.

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,
)

Genera l'ansatz e costruisci la simulazione MPS​

Successivamente genereremo l'ansatz che ottimizzeremo. Evolverà fino allo stesso tempo di evoluzione del primo circuito (da ti=0t_i=0 a tf=4t_f=4), ma con meno passi di Trotter.

Una volta generato il circuito, lo passiamo alla funzione generate_ansatz_from_circuit() di AQC-Tensor, che analizza la connettività a due qubit e restituisce due elementi. Il primo è un circuito ansatz generato con la stessa connettività a due qubit; il secondo è un insieme di parametri che, inseriti nell'ansatz, riproducono il circuito di input.

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)

Output della cella di codice precedente

Successivamente costruiremo la rappresentazione MPS dello stato da approssimare con AQC. Calcoleremo anche la fedeltà ∣⟨ψ1∣ψ2⟩∣2|\langle\psi_1 | \psi_2 \rangle |^2 tra lo stato preparato dal circuito di confronto e il circuito che genera lo stato target (che ha usato più passi di 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

Ottimizza i parametri dell'ansatz con l'MPS​

Infine, ottimizzeremo il circuito ansatz in modo che produca lo stato target con una fedeltà superiore a quella del nostro comparison_fidelity. La funzione di costo da minimizzare sarà MaximizeStateFidelity e verrà ottimizzata con l'ottimizzatore L-BFGS di 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.

A questo punto abbiamo un insieme di parametri che generano lo stato target con una fedeltà superiore a quella che il circuito di confronto avrebbe prodotto senza usare AQC. Con questi parametri ottimali, il circuito compresso presenta ora meno errore di Trotter e meno profondità rispetto al circuito originale.

Come passo finale, il seguente frammento di codice costruisce il circuito completo di evoluzione temporale che può essere passato a una pipeline di transpilazione ed eseguito su hardware quantistico.

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

Output della cella di codice precedente

Passi successivi​