Reducerea adâncimii circuitului cu addon-ul AQC-Tensor pentru Qiskit
În acest notebook, vom parcurge pașii unui pattern Qiskit folosind compilarea cuantică aproximativă cu rețele tensoriale (AQC-Tensor) pentru a obține o adâncime a circuitului mai mică decât ar fi necesară în mod obișnuit pentru a efectua evoluția Trotter.
Aceștia sunt pașii pe care îi vom urma:
- Pasul 1: Maparea la problema cuantică
- Inițializarea Hamiltonianului problemei și a observabilului (observabilelor)
- Generarea unei stări țintă tip rețea tensorială pentru porțiunea inițială a circuitului
- Generarea unui circuit cu adâncime redusă care aproximează porțiunea ce urmează a fi comprimată
- Generarea unui ansatz general din acel circuit
- Optimizarea parametrilor pentru a aduce ansatz-ul cât mai aproape de țintă
- Adăugarea pașilor Trotter următori la ansatz-ul optimizat
- Pasul 2: Optimizarea pentru hardware-ul țintă
- Transpilarea circuitului pentru hardware
- Pasul 3: Executarea experimentelor
- Folosirea unui backend fals pentru simplitate
- Pasul 4: Reconstruirea rezultatelor
- N/A; în schimb, afișăm pur și simplu observabilul măsurat
Pasul 1: Maparea la circuit-ul cuantic și operator
Configurarea unui Hamiltonian model și a unui observabil
În acest notebook, folosim modelul Ising pe un cerc de 10 site-uri:
unde condițiile periodice la frontieră implică faptul că pentru obținem , este intensitatea cuplajului dintre două site-uri, iar este câmpul magnetic extern.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-ibm-runtime quimb scipy
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
# 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),
)
Observabilul pe care îl vom măsura este magnetizarea totală.
from qiskit.quantum_info import SparsePauliOp
L = reduced_coupling_map.size()
observable = SparsePauliOp.from_sparse_list([("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L)
Determinarea cât din evoluția temporală urmează să fie simulată clasic
Scopul nostru general este de a simula evoluția temporală a Hamiltonianului model de mai sus. Facem acest lucru prin evoluție Trotter, pe care o împărțim în două porțiuni:
- O porțiune inițială care poate fi simulată cu stări produs matriceal (MPS). Vom „compila" această porțiune folosind AQC, conform prezentării din https://arxiv.org/abs/2301.08609.
- O porțiune ulterioară a circuitului, care va fi executată pe hardware. Planificăm să folosim AQC-Tensor pentru a comprima circuitul nostru de evoluție temporală până la momentul , apoi să evoluăm folosind pași Trotter obișnuiți până la .
Generarea circuitelor înainte și după împărțire
Acum că am ales să facem împărțirea la , vom genera două circuite:
- Un circuit „țintă" pentru porțiunea AQC a evoluției, de la până la . Deoarece aceasta este simulată de un simulator bazat pe rețele tensoriale, numărul de straturi afectează timpul de execuție doar cu un factor constant, deci putem folosi un număr generos de straturi pentru a minimiza eroarea Trotter.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
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,
)
- Un circuit de evoluție ulterioară, care evoluează de la până la . Deoarece acesta va fi rulat pe hardware cuantic, este de dorit să folosim cât mai puține straturi Trotter.
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,
)
Pentru o comparație ulterioară, să generăm și un al treilea circuit: unul care evoluează pentru aqc_evolution_time, dar care are același timp de evoluție per pas Trotter ca circuitul ulterior. Acesta este circuitul cu care am fi lucrat dacă nu am fi folosit un număr generos de pași Trotter pentru circuitul țintă. Îl vom numi circuitul de comparație.
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps / subsequent_evolution_time * aqc_evolution_time
)
aqc_comparison_num_trotter_steps
20
comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
Generarea unui ansatz și a parametrilor inițiali dintr-un circuit Trotter cu mai puțini pași
Mai întâi, construim un circuit „bun" care are același timp de evoluție ca circuitul țintă, dar cu mai puțini pași Trotter (și, astfel, mai puține straturi).
Apoi, transmitem acest circuit „bun" funcției generate_ansatz_from_circuit din AQC-Tensor. Această funcție analizează conectivitatea cu doi qubiți a circuitului și returnează două lucruri:
- un circuit ansatz general, parametrizat, cu aceeași conectivitate cu doi qubiți ca circuitul de intrare; și
- parametri care, introduși în ansatz, reproduc circuitul de intrare (bun).
În curând, vom lua acești parametri și îi vom ajusta iterativ pentru a aduce circuitul ansatz cât mai aproape de MPS-ul țintă.
from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit
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)

print(f"Comparison circuit: depth {comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters")
Comparison circuit: depth 120
Target circuit: depth 270
Ansatz circuit: depth 23, with 515 parameters
Alegerea setărilor pentru simularea rețelei tensoriale
Aici, folosim simulatorul de rețele tensoriale bazat pe quimb. În acest exemplu, folosim simulatorul de stări produs matriceal (MPS) din quimb și folosim JAX pentru diferențierea automată. Consultați documentația API pentru mai multe informații despre cum să folosiți simulatorul quimb.
from functools import partial
import quimb.tensor
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
simulator_settings = QuimbSimulator(
partial(quimb.tensor.CircuitMPS, max_bond=100, cutoff=1e-8),
autodiff_backend="jax",
)
Construirea reprezentării de stare produs matriceal a stării țintă AQC
În continuare, construim o reprezentare tip produs matriceal a stării ce urmează a fi aproximată prin AQC.
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)
Rețineți că, deoarece am ales un număr generos de pași Trotter pentru starea țintă, aceasta are de fapt mai puțină eroare Trotter decât circuitul de comparație. Putem calcula fidelitatea () a stării pregătite de circuitul de comparație față de starea țintă:
from qiskit_addon_aqc_tensor.simulation import compute_overlap
comparison_mps = tensornetwork_from_circuit(comparison_circuit, simulator_settings)
comparison_fidelity = abs(compute_overlap(comparison_mps, aqc_target_mps)) ** 2
comparison_fidelity
0.9996761790297157
Optimizarea parametrilor ansatz-ului folosind calcule MPS
Aici, minimizăm cea mai simplă funcție de cost posibilă, MaximizeStateFidelity, folosind optimizatorul L-BFGS din scipy.
Alegem un punct de oprire pentru fidelitate astfel încât aceasta să fie peste valoarea pe care ar fi avut-o circuitul de comparație fără AQC. Odată atins acest punct, circuitul comprimat are atât mai puțină eroare Trotter, cât și o adâncime mai mică decât circuitul original. Cu mai mult timp de procesare, se pot efectua pași suplimentari de optimizare pentru a crește și mai mult fidelitatea.
from scipy.optimize import OptimizeResult, minimize
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
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.loss_function,
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.95080335
Intermediate result: Fidelity 0.98408927
Intermediate result: Fidelity 0.99140876
Intermediate result: Fidelity 0.9951876
Intermediate result: Fidelity 0.99563147
Intermediate result: Fidelity 0.99646297
Intermediate result: Fidelity 0.99679298
Intermediate result: Fidelity 0.99715793
Intermediate result: Fidelity 0.99756604
Intermediate result: Fidelity 0.99804283
Intermediate result: Fidelity 0.99832283
Intermediate result: Fidelity 0.99856583
Intermediate result: Fidelity 0.99868698
Intermediate result: Fidelity 0.998867
Intermediate result: Fidelity 0.99902237
Intermediate result: Fidelity 0.99912174
Intermediate result: Fidelity 0.99919705
Intermediate result: Fidelity 0.99926724
Intermediate result: Fidelity 0.99938605
Intermediate result: Fidelity 0.99951297
Intermediate result: Fidelity 0.99956172
Intermediate result: Fidelity 0.99962274
Intermediate result: Fidelity 0.99963919
Intermediate result: Fidelity 0.99967423
Intermediate result: Fidelity 0.9997101
Done after 25 iterations.
Construirea circuitului final de transmis transpilerului
final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

Pasul 2: Transpilarea pentru execuția pe hardware-ul țintă
În Pasul 2 al unui pattern Qiskit, transpilăm acest circuit și orice observabil(e) dorit(e) pentru execuția pe un dispozitiv țintă. Aici folosim un backend fals furnizat de qiskit-ibm-runtime.
from qiskit import transpile
from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
backend = FakeMelbourneV2()
isa_circuit = transpile(final_circuit, backend)
isa_observable = observable.apply_layout(isa_circuit.layout)
Circuitul ISA rezultat poate fi trimis ulterior pentru execuție pe backend (pasul 3 al unui pattern Qiskit).
Pasul 3: Executarea pe hardware cuantic
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
pub_result = job.result()[0]
Pasul 4: Reconstruirea
Reconstruirea nu este necesară în cazul nostru. Putem să ne uităm direct la rezultat.
pub_result.data.evs[()]
np.float64(0.047998046875000006)