Generează circuite SqDRIFT
Cu diagonalizarea cuantică bazată pe eșantionare (SQD), trebuie să alegi un ansatz din care să eșantionezi șiruri de biți. Varianta SqDRIFT folosește în schimb un ansamblu de circuite de evoluție în timp construite direct din Hamiltonianul țintă. Acest lucru se realizează prin subeșantionarea unor operatori de evoluție în timp mai mici din Hamiltonian pe baza coeficienților săi, metodă de trotterizare cunoscută drept qDRIFT.
Acest ghid de introducere arată cum se generează un ansamblu de astfel de circuite randomizate.
1. Configurarea Hamiltonianului
Pentru scopul acestui ghid, încarcă Hamiltonianul structurii electronice a N2 dintr-un fișier FCIDUMP. Există și alte mijloace de a construi FermionOperator. Asigură-te că îi consulți documentația, precum și qiskit_fermions.operators.library.
- PYTHON
- C
>>> from qiskit_fermions.operators.library import FCIDump
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> fcidump = FCIDump.from_file("docs/guides/n2.fcidump")
>>> num_modes = 2 * fcidump.norb
>>> hamil = FermionOperator.from_fcidump(fcidump)
#include <qiskit_fermions.h>
QfFCIDump* fcidump = NULL;
qf_fcidump_from_file("docs/guides/n2.fcidump", &fcidump);
QfFermionOperator* hamil = qf_ferm_op_from_fcidump(fcidump);
uint32_t num_modes = 2 * qf_fcidump_norb(fcidump);
2. Grupează termenii Hamiltonianului
Folosește numeroasele simetrii prezente în Hamiltonianul structurii electronice grupând termenii înrudiți care au coeficienți identici. Această acțiune modifică distribuția coeficienților operatorului din care eșantionează protocolul qDRIFT, dar nu afectează garanțiile sale de convergență. În mod esențial, gruparea termenilor legați prin simetrie duce la o anulare favorabilă a termenilor Pauli, rezultând o adâncime totală mai mică a circuitului la evoluția în timp a unei stări sub acțiunea lor.
Modulul qiskit_fermions.operators.terms.grouping oferă funcții utile pentru gruparea termenilor unui operator. Acest lucru este explicat mai detaliat în acest ghid.
Implementarea group_terms_by_electronic_structure() presupune că termenii Hamiltonianului sunt în ordine normală!
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_terms_by_electronic_structure
>>> from qiskit_fermions.operators.terms.ordering import canonical_order
>>>
>>> canon = canonical_order(hamil.normal_ordered().simplify(atol=1e-16))
>>> exit_code = group_terms_by_electronic_structure(canon, num_modes, two_body_physicist_order=False)
>>> assert exit_code is None
>>> print(canon.groups) # the groups attribute now contains some list of group indices
[0, ...]
QfFermionOperator* normal = qf_ferm_op_normal_ordered(hamil, NULL);
QfFermionOperator* simplified = qf_ferm_op_simplify(normal, 1e-16);
QfFermionOperator* canon = qf_ferm_op_canonical_order(simplified);
QfExitCode exit = qf_ferm_op_group_terms_by_electronic_structure(canon, num_modes, false);
assert(exit == QfExitCode_Success);
qf_ferm_op_free(normal);
qf_ferm_op_free(simplified);
Hamiltonianul complet al structurii electronice conține anumiți termeni a căror includere într-un circuit de evoluție în timp nu are niciun impact asupra șirurilor de biți percepute și, prin urmare, duce doar la un cost de eșantionare crescut. De aceea, se recomandă ca astfel de termeni să fie filtrați din Hamiltonian în acest punct, înainte de a construi poarta Evolution la pasul următor.
Termenii care se potrivesc acestei descrieri sunt cei diagonali în baza numerelor de ocupare, adică produsele de operatori de număr (). Aceștia includ decalajul constant de energie, a cărui evoluție în timp introduce doar o fază globală în circuit, operatorii de număr individuali a căror evoluție în timp echivalează cu rotații Z pe un singur qubit, precum și produse de ordin superior precum . Niciunul dintre ei nu poate schimba ocuparea unui mod, deci eliminarea lor nu costă conținut de excitație. Spre deosebire de respingerea per extragere de la pasul 5, acest filtrare se face o singură dată, pe Hamiltonianul însuși, astfel că distribuția coeficienților din care eșantionează protocolul este reconstruită consecvent din operatorul filtrat, în loc să fie renormalizată în mijlocul extragerii.
Funcția filter_diagonal_terms() elimină astfel de termeni dintr-un operator pe loc:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)
qf_ferm_op_filter_diagonal_terms(canon);
Filtrarea aici, o singură dată, este considerabil mai ieftină decât filtrarea repetată. FermionicQDrift rulează o dată pentru fiecare circuit transpilat, deci filtrarea inițială a Hamiltonianului, în loc de la fiecare apel, evită refacerea muncii pentru fiecare circuit generat din el.
Din același motiv, merită să ordonezi aici termenii Hamiltonianului după indexul de grup. Indecșii de grup sunt o etichetă per termen care nu spune nimic despre locul în care se află acei termeni, așa că termenii unui grup sunt în general împrăștiați în operator: gruparea aplicată mai sus atribuie indecși în ordinea primei întâlniri, ceea ce lasă al doilea termen al unui grup departe de primul.
FermionicQDrift eșantionează grupuri întregi și caută fiecare grup extras cu split_out_groups(). Găsirea unui grup printre termeni împrăștiați înseamnă parcurgerea tuturor, deci acea căutare costă la fel indiferent dacă se extrage un grup sau patruzeci. group_order() adună fiecare grup într-o secvență contiguă, după care o căutare este o căutare binară peste limitele grupurilor și costă cât costă grupurile extrase, nu termenii păstrați. Căutarea în sine devine dramatic mai ieftină; cât din o transpilare anume economisește aceasta depinde de ce altceva face fluxul, deoarece maparea și sinteza domină odată ce parcurgerea dispare:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.ordering import group_order
>>>
>>> canon = group_order(canon)
>>> print(canon.groups[:4])
[0, 0, ...]
QfFermionOperator* grouped = qf_ferm_op_group_order(canon);
Aceasta reordonează termenii fără a schimba valoarea operatorului și trebuie făcută o singură dată, indiferent câte randomizări de circuit sunt extrase din el.
3. Pregătește circuitul de evoluție în timp
Pregătește circuitul de evoluție în timp al Hamiltonianului și circuitul de bază din care se extrag eșantioanele. qiskit_fermions.circuit.library conține toate componentele necesare pentru aceasta, în conformitate cu convențiile Qiskit.
Protocolul qDRIFT este metoda de sinteză FermionicQDrift, iar poarta Evolution o poartă ca synthesis al ei. Metoda introduce câțiva parametri pe care îi poți folosi pentru a personaliza circuitele de generat:
-
Lungimea fiecărui circuit în termeni de grupuri de excitații:
num_groups -
Opțional, o sămânță pentru generatorul de numere aleatoare:
rng -
Opțional, ponderi de eșantionare precalculate, dacă transpilarea în sine devine un blocaj:
weights(vezi (Opțional) Precalculează ponderile de eșantionare mai jos)
Deoarece metoda este deținută de poartă, orice descrie operatorul porții, cum ar fi acele ponderi, se află chiar lângă acel operator.
- PYTHON
- C
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import FermionicQDrift
>>>
>>> time = 1.0 # you can choose a desired scaling factor here
>>> num_groups = 10
>>> qdrift = FermionicQDrift(num_groups, rng=19)
>>> evo_gate = Evolution(num_modes, canon, time, synthesis=qdrift)
>>>
>>> circ = FermionicCircuit(num_modes)
>>> circ.append(evo_gate, circ.modes)
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions.
Acest exemplu nici nu inițializează modurile fermionice cu particule, nici nu măsoară starea lor finală.
4. Transpilează circuitul
Modulul qiskit_fermions.transpiler se integrează direct cu fluxul de transpilare al Qiskit, permițând ca FermionicCircuit construit mai sus să fie transpilat direct într-un QuantumCircuit.
Folosește maparea fermion-qubit jordan_wigner() pentru a converti Hamiltonianul exprimat în termeni de fermioni în șiruri Pauli. Aceasta se poate face direct ca parte a procesului de transpilare folosind pasul de transpilare F2QSynthesis. Folosește generate_preset_jw_pass_manager() pentru a construi FermionicStagedPassManager, care asigură că încodarea Jordan-Wigner este folosită consecvent pentru toate instrucțiunile circuitului.
În mod esențial, adaugă pasul Decompose din Qiskit la etapa optimization a fluxului de transpilare. El descompune poarta Evolution cu metoda de sinteză pe care o poartă poarta, adică metoda qDRIFT pregătită mai sus. Acest lucru asigură că circuitul nu folosește evoluția în timp a întregului Hamiltonian, a cărui adâncime ar depăși capacitățile hardware-ului de calcul cuantic disponibil în prezent.
În schimb, subeșantionează un număr fix de groups de termeni ai Hamiltonianului pentru fiecare circuit, de fiecare dată când circuitul este transpilat. Astfel, poți genera mai multe randomizări de circuit, așa cum cere algoritmul SqDRIFT, rulând repetat fluxul de transpilare. Numărul de circuite de generat este num_sqdrift_randomizations.
- PYTHON
- C
>>> from qiskit.transpiler.passes import Decompose
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> pm.optimization = FermionicPassManager([Decompose(["Evolution"])])
>>>
>>> num_sqdrift_randomizations = 10
>>> sqdrift_circuits = [
... pm.run(circ) for _ in range(num_sqdrift_randomizations)
... ]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
Exemplele anterioare fixează sămânța rng a metodei de sinteză FermionicQDrift, astfel încât secvența de randomizări să poată fi reprodusă.
Același flux acceptă metoda qDRIFT în alte două locuri. Pasul F2FSynthesis primește o metodă pentru fiecare poartă Evolution după nume, F2FSynthesis({"Evolution": FermionicQDrift(num_groups)}), ceea ce este tot ce trebuie atunci când nicio poartă nu deține nimic specific operatorului ei. Pasul următor folosește al treilea loc, pasul QDriftTrotterization.
5. Filtrează excitațiile extrase după ocupare
Pe lângă termenii diagonali filtrați la pasul anterior, o excitație extrasă poate acționa în întregime într-un set de moduri a căror ocupare este deja fixată (toate ocupate sau toate neocupate), unde nu poate muta o particulă dintr-o parte în cealaltă. O astfel de excitație nu îți spune nimic despre șirurile de biți eșantionate, deci un loc consumat pe ea este un loc irosit. Pasul QDriftTrotterization, care înfășoară aceeași metodă FermionicQDrift, respinge astfel de excitații pe măsură ce sunt extrase și extrage un înlocuitor, astfel încât fiecare dintre cele num_groups locuri ale circuitului rezultat să poarte o excitație care cuplează cele două părți.
Această filtrare nu este gratuită și din acest motiv este un pas separat. Ceea ce conservă este bugetul de num_groups locuri eșantionate, nu adâncimea circuitului: deoarece acest buget este fix, o excitație respinsă este înlocuită, nu eliminată, iar una ieftină face loc unei excitații de cuplare care costă mai mult la sinteză. Așteaptă-te ca un circuit filtrat să fie mai adânc decât unul nefiltrat extras din aceeași sămânță.
Renormalizarea extragerii peste excitațiile acceptate schimbă distribuția din care eșantionează protocolul qDRIFT și, spre deosebire de gruparea de la pasul 2, rupe și garanția de convergență a protocolului: produsul eșantionat nu mai are ca medie evoluția sub Hamiltonianul pe care l-ai transmis. Fiecare excitație păstrată ajunge ponderată cu inversul probabilității de acceptare, cele respinse nu contribuie cu nimic, iar nici magnitudinea totală a coeficienților, nici timpul de evoluție per poartă nu sunt ajustate pentru compensare.
Regula de acceptare este, de asemenea, o supraaproximare. Ea compară doar suporturile modurilor, deci nu poartă nicio informație de amplitudine, iar seturile urmărite doar cresc: o excitație limitată la setul ocupat este cu adevărat inertă pe o stare produs, dar același raționament slăbește pentru extragerile ulterioare, odată ce excitațiile acceptate anterior au lăsat starea încâlcită și au marcat majoritatea modurilor drept „incerte”.
Folosește filtrarea pentru fluxul de eșantionare a șirurilor de biți pe care îl descrie acest ghid, unde circuitele alimentează post-procesarea SQD și contează doar șirurile de biți eșantionate. Nu o folosi dacă intenționezi să estimezi o valoare medie din aceste circuite sau să te bazezi în alt mod pe marginea de eroare qDRIFT: acele rezultate poartă o părtinire pe care pasul nu o corectează.
Această filtrare trebuie să știe care moduri sunt ocupate la început. De aceea necesită o poartă InitializeModes care precede porțile Evolution din circuit; InitializeModes.from_hartree_fock() este o modalitate convenabilă de a construi una. Adaug-o aici pentru referința Hartree-Fock a N2 (șapte electroni alfa și șapte beta în 14 orbitali spațiali) și compară excitațiile eșantionate cu și fără filtrare:
- PYTHON
- C
>>> from qiskit_fermions.circuit.library import InitializeModes
>>> from qiskit_fermions.transpiler.passes import F2FSynthesis, QDriftTrotterization
>>>
>>> init = InitializeModes.from_hartree_fock(fcidump.norb, (7, 7))
>>>
>>> hf_circ = FermionicCircuit(num_modes)
>>> hf_circ.append(init, hf_circ.modes)
>>> hf_circ.append(Evolution(num_modes, canon, time), hf_circ.modes)
>>>
>>> num_groups = 5
>>> qdrift_unfiltered = F2FSynthesis(
... {"Evolution": FermionicQDrift(num_groups, rng=3480)}
... )
>>> qdrift_trivial = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>>
>>> for instruction in FermionicPassManager(qdrift_unfiltered).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[2, 4]
[41, 45, 52]
[15, 45, 55]
[41, 52, 53]
[10, 16, 37, 38]
>>>
>>> for instruction in FermionicPassManager(qdrift_trivial).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[0, 1, 6, 7]
[0, 1, 28, 29]
[4, 13, 55]
[13, 20, 40, 41]
[0, 1, 28, 29]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
Niciuna dintre excitațiile extrase fără filtrare nu atinge deloc setul ocupat (0-6 și 28-34), deci niciuna nu poate muta o particulă între un mod ocupat și unul neocupat. Cu filtrarea, toate cele cinci sunt respinse și înlocuite cu excitații care cuplează într-adevăr un mod ocupat cu unul neocupat. De exemplu, prima excitație acceptată [0, 1, 6, 7] mută o particulă între modurile ocupate 0, 1 și 6 și modul neocupat 7.
Odată ce o excitație este acceptată, fiecare mod din suportul ei devine „incert” și, prin urmare, eligibil să joace oricare rol pentru eșantioanele ulterioare, astfel că seturile de moduri ocupate și neocupate continuă să crească pe măsură ce mai multe excitații sunt acceptate. Aceasta face acceptabilă a doua excitație, [0, 1, 28, 29]. Toate cele patru moduri ale ei se numără printre cele ocupate inițial, deci nu se cuplează cu niciun mod inițial neocupat. Este acceptată doar pentru că modurile 0 și 1 au devenit incerte (și deci eligibile ca parte „neocupată” a cuplării) odată ce prima excitație le-a atins.
Această creștere face din regulă o supraaproximare, nu un test: pe măsură ce tot mai multe moduri devin incerte, tot mai puține extrageri sunt respinse, iar filtrarea influențează distribuția de eșantionare cel mai mult la primele extrageri.
Pentru a judeca cât de mult a mutat filtrarea distribuția, citește numărătorile de respingeri pe care pasul le înregistrează în metadatele circuitului. Este stocată o intrare pentru fiecare poartă Evolution filtrată, iar raportul dintre acceptate și total estimează probabilitatea de acceptare, adică factorul cu care excitațiile păstrate au fost supraponderate:
- PYTHON
- C
>>> # a freshly seeded pass, so the counts do not depend on earlier draws
>>> qdrift_counted = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>> filtered = FermionicPassManager(qdrift_counted).run(hf_circ)
>>> discarded = filtered.metadata.get("filter_trivial.discarded")
>>> emitted = filtered.metadata.get("filter_trivial.emitted")
>>> print(f"{emitted[0]} kept, {discarded[0]} discarded")
5 kept, 7 discarded
>>> print(f"acceptance probability: {emitted[0] / (emitted[0] + discarded[0]):.2f}")
acceptance probability: 0.42
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
Filtrarea are nevoie de informații de ocupare față de care să filtreze. O poartă PrepareSlaterDeterminant le furnizează la fel ca InitializeModes, iar orice OrbitalRotation marchează fiecare mod pe care acționează drept „incert”. Fără niciuna dintre ele, sau când modurile inițializate ajung toate pe aceeași parte (toate ocupate sau toate neocupate), pasul emite un UserWarning și lasă eșantionarea nefiltrată pentru acea poartă Evolution. Pe un Hamiltonian ai cărui termeni rămași nu mai pot cupla cele două seturi urmărite, pasul ridică în schimb RuntimeError după QDriftTrotterization.MAX_SAMPLE_RETRIES respingeri consecutive.
(Opțional) Precalculează ponderile de eșantionare
Recurge la aceasta doar dacă transpilarea în sine este un blocaj. Nu schimbă nimic la circuitele pe care le obții.
FermionicQDrift extrage fiecare grup cu o probabilitate proporțională cu magnitudinea coeficientului său și derivă acele magnitudini din Hamiltonian la fiecare apel: o valoare pentru fiecare termen al Hamiltonianului, redusă la una pentru fiecare grup. Deoarece metoda nu are stare, repetă această reducere pentru fiecare randomizare, chiar dacă rezultatul este identic de fiecare dată. Argumentul weights îți permite să o calculezi o singură dată cu group_coeff_means() și să transmiți rezultatul, mutând costul în afara buclei și lăsând metoda liberă de orice stare în cache dependentă de Hamiltonian.
Aceasta este corespondentul ordonării termenilor de la pasul 2. Aceea scoate din buclă căutarea grupului, aceasta reducerea ponderilor, iar fiecare se observă doar atât cât lipsește cealaltă, deci merită să le aplici pe amândouă împreună. Deoarece canon a fost deja reordonat mai sus, cele două se compun direct.
Ponderile descriu grupurile unui anumit operator, deci aparțin porții Evolution a acelui operator: construiește metoda pentru acea poartă și transmite-o ca synthesis al porții, exact ca la pasul 3.
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_coeff_means
>>>
>>> weights = group_coeff_means(canon) # `canon` is group-ordered, from step 2
>>> qdrift_weighted = FermionicQDrift(10, rng=19, weights=weights)
>>> weighted_gate = Evolution(num_modes, canon, time, synthesis=qdrift_weighted)
>>>
>>> weighted_circ = FermionicCircuit(num_modes)
>>> weighted_circ.append(weighted_gate, weighted_circ.modes)
>>>
>>> weighted_circuits = [
... pm.run(weighted_circ) for _ in range(num_sqdrift_randomizations)
... ]
>>> len(weighted_circuits)
10
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
Deoarece acestea sunt exact ponderile pe care le-ar fi calculat pasul, circuitele sunt aceleași ca înainte; doar reducerea repetată a dispărut.
Este așteptată o intrare pentru fiecare grup, nu pentru fiecare termen, ori de câte ori operatorul poartă grupuri, deoarece un operator grupat este eșantionat pe grupuri. Intrările trebuie să fie nenegative: o pondere este o magnitudine, deoarece protocolul qDRIFT descompune cu pozitive, iar semnul unui coeficient aparține lui , de unde pasul îl citește direct din operator.
Ponderile sunt magnitudini absolute, nu preferințe relative. Suma lor fixează și timpul de evoluție, deci înmulțirea fiecărei intrări cu eșantionează identic, dar evoluează pentru în loc de . Transmiterea a orice altceva decât magnitudinile coeficienților proprii ai Hamiltonianului schimbă, așadar, evoluția pe care o aproximează ansamblul, motiv pentru care aceasta este o unealtă de performanță, nu un mod de a regla eșantionarea.
Deoarece ponderile aparțin porții al cărei operator îl descriu, un circuit poate conține mai multe porți Evolution, fiecare cu ponderile propriului operator. Pasul QDriftTrotterization de la pasul 5 le respectă în același mod: extrage o poartă cu FermionicQDrift pe care o poartă poarta și revine la cea cu care a fost construit pentru porțile care nu poartă niciuna. Un alt tip de metodă de sinteză pe o poartă nu este o declarație despre qDRIFT, deci pasul o ignoră. Transmiterea metodei unui pas după numele porții, ca în F2FSynthesis({"Evolution": ...}), aplică o singură metodă fiecărei porți Evolution, deci ponderile transmise acolo au sens doar pentru un circuit cu una singură. Lungimea ponderilor este verificată față de fiecare poartă la care se aplică.
(Opțional) Optimizează indexarea modurilor fermionice
Poți adăuga un pas suplimentar de optimizare la fluxul de transpilare, care minimizează distanța dintre suporturile excitațiilor fermionice prin reetichetarea indecșilor modurilor fermionice. Această optimizare a fost introdusă în lucrarea SqDRIFT și este implementată de build_excitation_span_minimization_model(). Poate fi inserată ușor în fluxul transpilerului folosind pasul RelabelModes:
- PYTHON
- C
>>> from pyomo.environ import SolverFactory
>>> from qiskit_fermions.transpiler.passes import RelabelModes
>>>
>>> solver = SolverFactory("appsi_highs")
>>> solver.options["time_limit"] = 10
>>>
>>> qdrift = F2FSynthesis({"Evolution": FermionicQDrift(5, rng=19)})
>>> relabel = RelabelModes(solver=solver)
>>>
>>> pm.optimization = FermionicPassManager([qdrift, relabel])
>>>
>>> relabeled_circ = pm.run(circ)
>>> # if the automatic mode relabeling was successful, the circuit's
>>> # metadata will contain the mode `permutation` information
// WARNING: This feature is not available via the C API.
Folosirea optimizării automate din RelabelModes (care se bazează pe build_excitation_span_minimization_model()) necesită dependența opțională gestionată de HAS_PYOMO.
Reetichetarea schimbă ce mod poartă un anumit qubit, deci diagonalizarea în subspațiu obține șirurile de biți corecte doar dacă se ține cont de aceasta. Locul în care se ține cont depinde de locul în care măsori.
Adaugă măsurătorile la FermionicCircuit cu FermionicCircuit.measure_all() înainte de transpilare și nu mai ai nimic de făcut: fiecare măsurătoare numește modul pe care îl citește, deci reetichetarea o poartă cu ea, iar șirurile de biți eșantionate sunt indexate după modul original.
Măsoară în schimb QuantumCircuit-ul transpilat, iar șirurile de biți trebuie post-procesate folosind permutation înregistrată în metadatele circuitului. Consultă documentația RelabelModes pentru rețetă.
Pașii următori
Acum că ai generat cu succes un ansamblu de circuite, poți eșantiona șiruri de biți din ele. Pentru aceasta, circuitele trebuie executate pe hardware. Consultă documentația Qiskit pentru instrucțiuni detaliate.
După ce ai obținut eșantioanele de șiruri de biți, acestea pot fi folosite împreună cu coeficienții Hamiltonianului pentru a efectua post-procesarea SQD, așa cum este explicat în tutorialele addon SQD.