Aducem totul împreună cu Qiskit Runtime
Rezumat
Victoria Lipinska oferă un recapitulativ final al celor învățate până acum.
Referințe
Următoarele articole sunt menționate în videoclipul de mai sus.
- Quantum Chemistry in the Age of Quantum Computing, Cao, et al.
- Quantum computational chemistry, McArdle, et al.
VQE cu pattern-urile Qiskit
Avem toate componentele necesare pentru un calcul VQE:
- Hamiltonian
- Ansatz
- Optimizator clasic
Acum trebuie doar să le reunim în cadrul pattern-urilor Qiskit.
Pasul 1: Maparea intrărilor clasice către o problemă cuantică
Așa cum am menționat anterior, vom presupune că un Hamiltonian de interes, formatat corespunzător, a fost deja generat. Dacă ai întrebări despre asta, consultă lecția despre Hamiltonieni pentru îndrumare. Blocul de cod de mai jos configurează componentele explicate în lecțiile anterioare. Am ales să modelăm H2 deoarece Hamiltonianul său este suficient de compact pentru a fi scris integral.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime scipy
# General imports
import numpy as np
from qiskit.quantum_info import SparsePauliOp
# Hamiltonian obtained from a previous lesson
H = SparsePauliOp(
[
"IIII",
"IIIZ",
"IZII",
"IIZI",
"ZIII",
"IZIZ",
"IIZZ",
"ZIIZ",
"IZZI",
"ZZII",
"ZIZI",
"YYYY",
"XXYY",
"YYXX",
"XXXX",
],
coeffs=[
-0.09820182 + 0.0j,
-0.1740751 + 0.0j,
-0.1740751 + 0.0j,
0.2242933 + 0.0j,
0.2242933 + 0.0j,
0.16891402 + 0.0j,
0.1210099 + 0.0j,
0.16631441 + 0.0j,
0.16631441 + 0.0j,
0.1210099 + 0.0j,
0.17504456 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
],
)
nuclear_repulsion = 0.7199689944489797
Selectăm un circuit efficient_su2 și optimizatorul COBYLA pentru început.
# Pre-defined ansatz circuit
from qiskit.circuit.library import efficient_su2
# SciPy minimizer routine
from scipy.optimize import minimize
# Plotting functions
# Random initial state and efficient_su2 ansatz
ansatz = efficient_su2(H.num_qubits, su2_gates=["rx"], entanglement="linear", reps=1)
x0 = 2 * np.pi * np.random.random(ansatz.num_parameters)
print(ansatz.decompose().depth())
ansatz.decompose().draw("mpl")
5
Acum construim funcția de cost. Aceasta este evident legată de Hamiltonian, dar diferă prin faptul că Hamiltonianul este un operator, iar noi dorim o funcție care să returneze valoarea de așteptare a acelui operator, folosind Estimator. Desigur, aceasta realizează acest lucru utilizând ansatz-ul și parametrii variaționali, astfel că toate acestea apar ca argumente. Mai jos, definim versiuni ușor diferite pentru utilizarea pe hardware real sau simulatoare.
def cost_func(params, ansatz, H, estimator):
pub = (ansatz, [H], [params])
result = estimator.run(pubs=[pub]).result()
energy = result[0].data.evs[0]
return energy
# def cost_func_sim(params, ansatz, H, estimator):
# energy = estimator.run(ansatz, H, parameter_values=params).result().values[0]
# return energy
Pasul 2: Optimizează problema pentru execuție cuantică.
Vrem ca codul nostru să ruleze cât mai eficient posibil pe hardware-ul pe care îl folosim. Prin urmare, trebuie să selectăm un backend pentru a începe etapa de optimizare. Codul de mai jos selectează backend-ul cel mai puțin ocupat disponibil pentru tine.
# To run on hardware, select the backend with the fewest number of jobs in the queue
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.least_busy(operational=True, simulator=False)
backend.name
Optimizarea circuitului pentru rularea pe un backend real este un subiect bogat și critic. Dar nu este specific pentru VQE. Deocamdată, îți vom aminti pur și simplu doi termeni importanți:
- optimization_level: Acesta descrie cât de bine este adaptat circuitul la structura backend-ului selectat. Cel mai scăzut nivel de optimizare face doar minimul necesar pentru a rula circuitul pe dispozitiv; mapează qubiții circuitului la qubiții dispozitivului și adaugă porți swap pentru a permite toate operațiile cu doi qubiți. Cel mai înalt nivel de optimizare este mult mai inteligent și utilizează o mulțime de trucuri pentru a reduce numărul total de porți. Deoarece porțile cu mai mulți qubiți au rate de eroare ridicate și qubiții se decoerează în timp, circuitele mai scurte ar trebui să ofere rezultate mai bune.
- Dynamical Decoupling: Putem aplica o secvență de porți qubiților inactivi. Aceasta elimină unele interacțiuni nedorite cu mediul înconjurător.
Consultă documentația linkată pentru mai multe informații despre optimizarea circuitelor. Codul de mai jos generează un manager de treceri utilizând manageri de treceri presetați din
qiskit.transpiler.
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
ConstrainedReschedule,
)
from qiskit.circuit.library import XGate
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)
# Use the pass manager and draw the resulting circuit
ansatz_isa = pm.run(ansatz)
ansatz_isa.draw(output="mpl", idle_wires=False, style="iqp")
De asemenea, trebuie să aplicăm caracteristicile de layout ale dispozitivului Hamiltonianului.
hamiltonian_isa = H.apply_layout(ansatz_isa.layout)
hamiltonian_isa
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYXXII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXXXII'],
coeffs=[-0.09820182+0.j, -0.1740751 +0.j, -0.1740751 +0.j, 0.2242933 +0.j,
0.2242933 +0.j, 0.16891402+0.j, 0.1210099 +0.j, 0.16631441+0.j,
0.16631441+0.j, 0.1210099 +0.j, 0.17504456+0.j, 0.04530451+0.j,
0.04530451+0.j, 0.04530451+0.j, 0.04530451+0.j])
Pasul 3: Execută folosind Qiskit Primitives.
Înainte de a executa pe hardware-ul selectat, este o idee bună să folosești un simulator pentru depanare succintă și, uneori, pentru estimări ale erorii. Din aceste motive, arătăm pe scurt cum să rulezi VQE pe un simulator. Dar este esențial să reții că niciun calculator clasic, simulator sau GPU nu poate simula cu acuratețe funcționalitatea completă a unui calculator cuantic cu 127 de qubiți puternic entanglați. În era actuală a utilității cuantice, simulatoarele vor avea o utilizare limitată.
Reține că pentru fiecare alegere de parametri din circuitul variațional, trebuie calculată o valoare de așteptare (deoarece aceasta este valoarea care trebuie minimizată). Așa cum probabil ai ghicit deja, cel mai eficient mod de a face asta este folosind primitiva Qiskit, Estimator. Vom începe prin utilizarea unui simulator local, care va necesita să folosim versiunea locală a Estimator numită BackendEstimator.
Păstrând backend-ul real pe care l-am folosit pentru optimizare, putem importa un model al comportamentului de zgomot al acelui dispozitiv pentru a-l folosi cu simulatorul local ales. Aici vom folosi aer_simulator_statevector.
# We will start by using a local simulator
from qiskit_aer import AerSimulator
# Import an estimator, this time from qiskit (we will import from Runtime for real hardware)
from qiskit.primitives import BackendEstimatorV2
# generate a simulator that mimics the real quantum system
backend_sim = AerSimulator.from_backend(backend)
estimator = BackendEstimatorV2(backend=backend_sim)
A venit în sfârșit momentul să implementăm VQE, minimizând funcția de cost folosind Hamiltonianul selectat, ansatz-ul, optimizatorul clasic și BackendEstimator-ul nostru, bazat pe backend-ul real selectat pentru utilizare ulterioară. Reține că aici am ales un număr relativ mic pentru numărul maxim de iterații. Aceasta deoarece folosim simulatorul doar pentru depanare. Pașii de optimizare VQE necesită adesea sute de iterații pentru a converge.
res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)
print(getattr(res, "fun") - nuclear_repulsion)
print(res)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11556938907226563
The corresponding X is:
[4.11796514 4.52126324 0.69570423 4.12781503 6.55507846 1.80713073
0.9645473 6.23812214]
-0.8355383835212453
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11556938907226563
x: [ 4.118e+00 4.521e+00 6.957e-01 4.128e+00 6.555e+00
1.807e+00 9.645e-01 6.238e+00]
nfev: 10
maxcv: 0.0
Acest cod s-a executat corect, deși nu a converges, lucru la care ne-am așteptat. Vom continua rulând calculul pe hardware real și vom discuta apoi rezultatele. Pentru backend-uri reale, vom folosi Qiskit Runtime Estimator. Va trebui să executăm aceasta într-o sesiune Qiskit Runtime și, în general, vom dori să specificăm opțiuni pentru acea sesiune.
from qiskit_ibm_runtime import QiskitRuntimeService, Session
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.options import EstimatorOptions
Printre altele, utilizarea unei sesiuni înseamnă că jobul nostru va aștepta în coadă o singură dată, la început. Iterațiile ulterioare ale optimizatorului clasic nu vor fi puse în coadă. În cadrul sesiunii, putem seta niveluri de reziliență și optimizare. Aceste instrumente sunt suficient de importante încât includem o scurtă trecere în revistă a fiecăruia și a relevanței lor în VQE, cu linkuri pentru a afla mai multe:
- Sesiuni Runtime: VQE este prin natura sa iterativ, optimizatorul clasic selectând noi parametri variaționali și, astfel, noi porți, la fiecare iterație ulterioară. Fără sesiuni, acest lucru ar putea duce la timp suplimentar de așteptare în coadă între fiecare circuit de încercare. Încapsularea calculului VQE într-o sesiune duce la o singură coadă inițială înainte de începerea jobului, fără timp suplimentar de așteptare între pașii variaționali. Această strategie a fost deja folosită în exemplul din lecția anterioară, dar poate juca un rol și mai important atunci când se variază geometria. Pentru mai multe informații despre sesiuni, consultă documentația modurilor de execuție.
- Optimizarea încorporată a Estimator: În Estimator există opțiuni încorporate pentru optimizarea unui calcul. În multe contexte (inclusiv Estimator), setările sunt limitate la 0 și 1, unde 0 înseamnă nicio optimizare, iar 1 (implicit) înseamnă o anumită optimizare a circuitului tău în funcție de hardware-ul selectat. Unele alte contexte permit setările 0, 1, 2 sau 3. Pentru mai multe informații despre metodele specifice utilizate în diferite setări, consultă documentația. Aici, vom seta de fapt optimizarea la 0 și vom folosi
skip\_transpilation = true, deoarece am transpilat deja circuitul nostru folosind pass manager-ul de mai sus, în secțiunea de optimizare. - Reziliența încorporată a Estimator: La fel ca optimizarea, Estimator are setări încorporate pentru reziliența la erori, corespunzând diferitelor abordări de atenuare a erorilor. Pentru a afla despre setările nivelului de reziliență, consultă documentația.
Merită menționat că atenuarea erorilor joacă un rol nuanțat în convergența unui calcul VQE. Optimizatorul clasic caută în spațiul parametrilor acei parametri care minimizează energia. Când ești foarte departe de parametrii optimi, un gradient abrupt poate fi vizibil pentru optimizatorul clasic chiar și în prezența erorilor. Dar pe măsură ce calculul converge și te apropii de valorile optime, gradientul devine mai mic și mai ușor de anulat de erori. Câtă atenuare a erorilor vrei să folosești? La ce puncte din convergență? Acestea sunt alegeri pe care trebuie să le faci pentru cazul tău de utilizare particular.
Pentru această primă rulare pe hardware, am setat reziliența la 0 pentru a facilita o rulare relativ rapidă. Pentru orice aplicație serioasă, vei dori să folosești atenuarea erorilor. Reține că în celula de mai jos există două seturi de opțiuni: (1) opțiuni pentru sesiunea Runtime, pe care le-am numit „session_options", și (2) opțiuni pentru optimizatorul clasic, numite simplu „options" aici.
estimator_options = EstimatorOptions(resilience_level=0, default_shots=2000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11691688904
The corresponding X is:
[5.11796514 5.52126324 0.69570423 5.12781503 6.55507846 1.80713073
1.9645473 6.23812214]
Poți urmări progresul jobului tău pe IBM Quantum® Platform, sub Workloads.
print(getattr(res, "fun") - nuclear_repulsion)
print(res)
-0.8368858834889796
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11691688904
x: [ 5.118e+00 5.521e+00 6.957e-01 5.128e+00 6.555e+00
1.807e+00 1.965e+00 6.238e+00]
nfev: 10
maxcv: 0.0
Pasul 4: Post-procesare, returnarea rezultatului în format clasic.
Să ne acordăm un moment pentru a înțelege aceste rezultate. Rezultatul „fun" este valoarea minimă pe care am obținut-o pentru funcția de cost (nu neapărat ultima valoare calculată). Aceasta este energia totală, inclusiv repulsia nucleară pozitivă, de aceea am definit și electron_energy.
În cazul de mai sus, avem un mesaj că numărul maxim de evaluări ale funcției a fost depășit și că numărul de evaluări ale funcției (nfev) a fost 10. Aceasta înseamnă pur și simplu că alte criterii de convergență a optimizării nu au fost îndeplinite; cu alte cuvinte, nu există niciun motiv să credem că am găsit energia stării fundamentale. Acesta este și sensul lui success cu valoarea „False".
În final, avem x. Acesta este vectorul parametrilor variaționali. Aceștia sunt parametrii folosiți în calculul care a produs funcția de cost minimă (valoarea de așteptare a energiei). Aceste opt valori corespund celor opt unghiuri de rotație ale porților din ansatz care acceptă unghiuri de rotație variabile.
Felicitări! Ai rulat un calcul VQE pe un QPU IBM Quantum!
În lecția următoare, vom vedea cum să ajustăm acest flux de lucru pentru a include variabile în Hamiltonianul tău. În contextul problemelor de chimie cuantică, aceasta ar putea însemna varierea geometriei pentru a determina formele moleculelor sau ale situsurilor de legare.
import qiskit
import qiskit_ibm_runtime
print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())
2.1.0
0.40.1