Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Generowanie obwodów SqDRIFT

W diagonalizacji kwantowej opartej na próbkowaniu (SQD) trzeba wybrać ansatz, z którego próbkuje się ciągi bitów. Wariant SqDRIFT zamiast tego używa zespołu obwodów ewolucji w czasie zbudowanych bezpośrednio z docelowego hamiltonianu. Osiąga się to przez podpróbkowanie mniejszych operatorów ewolucji w czasie z hamiltonianu na podstawie jego współczynników, co jest znane jako metoda trotteryzacji qDRIFT.

Ten przewodnik wprowadzający pokazuje, jak wygenerować zespół takich losowych obwodów.

1. Przygotowanie hamiltonianu​

Na potrzeby tego przewodnika wczytaj hamiltonian struktury elektronowej N2 z pliku FCIDUMP. Istnieją też inne sposoby konstruowania FermionOperator. Koniecznie zajrzyj do jego dokumentacji oraz do qiskit_fermions.operators.library.

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

2. Grupowanie wyrazów hamiltonianu​

Wykorzystaj liczne symetrie obecne w hamiltonianie struktury elektronowej, grupując powiązane wyrazy o identycznych współczynnikach. Ta czynność zmienia rozkład współczynników operatora, z którego próbkuje protokół qDRIFT, ale nie wpływa na jego gwarancje zbieżności. Co istotne, grupowanie wyrazów powiązanych symetrią prowadzi do korzystnego znoszenia się wyrazów Pauliego, co skutkuje ogólnie mniejszą głębokością obwodu podczas ewolucji stanu w czasie pod ich działaniem.

Moduł qiskit_fermions.operators.terms.grouping udostępnia wygodne funkcje do grupowania wyrazów operatora. Jest to dokładniej wyjaśnione w tym przewodniku.

Uwaga

Implementacja group_terms_by_electronic_structure() zakłada, że wyrazy hamiltonianu są uporządkowane normalnie!

>>> 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, ...]
Wskazówka

Pełny hamiltonian struktury elektronowej zawiera pewne wyrazy, których włączenie do obwodu ewolucji w czasie nie wpływa na obserwowane ciągi bitów, a zatem jedynie zwiększa narzut próbkowania. Dlatego zaleca się odfiltrowanie takich wyrazów z hamiltonianu w tym miejscu, przed zbudowaniem bramki Evolution w następnym kroku.

Wyrazy spełniające ten opis to te, które są diagonalne w bazie liczb obsadzeń, czyli iloczyny operatorów liczby cząstek (ai†aia^\dagger_i a_i). Obejmuje to stałe przesunięcie energii, którego ewolucja w czasie wprowadza do obwodu jedynie globalną fazę, pojedyncze operatory liczby cząstek, których ewolucja w czasie sprowadza się do jednokubitowych obrotów Z, a także iloczyny wyższego rzędu, takie jak ninjn_i n_j. Żaden z nich nie może zmienić obsadzenia modu, więc ich pominięcie nie kosztuje żadnej zawartości wzbudzeń. W przeciwieństwie do odrzucania przy każdym losowaniu z kroku 5, to filtrowanie odbywa się raz, na samym hamiltonianie, więc rozkład współczynników, z którego próbkuje protokół, jest odbudowywany spójnie z przefiltrowanego operatora, a nie renormalizowany w trakcie losowania.

Funkcja filter_diagonal_terms() usuwa takie wyrazy z operatora w miejscu:

>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)

Jednorazowe filtrowanie tutaj jest znacznie tańsze niż filtrowanie wielokrotne. FermionicQDrift działa raz na każdy transpilowany obwód, więc filtrowanie hamiltonianu na początku, a nie przy każdym wywołaniu, pozwala uniknąć powtarzania pracy dla każdego generowanego z niego obwodu.

Wskazówka

Z tego samego powodu opłaca się tutaj uporządkować wyrazy hamiltonianu według indeksu grupy. Indeksy grup to znacznik przypisany każdemu wyrazowi, który nic nie mówi o tym, gdzie te wyrazy się znajdują, więc wyrazy grupy są zazwyczaj rozrzucone po całym operatorze: zastosowane wyżej grupowanie przypisuje indeksy w kolejności pierwszego napotkania, przez co drugi wyraz grupy leży daleko od pierwszego.

FermionicQDrift próbkuje całe grupy i wyszukuje każdą wylosowaną grupę za pomocą split_out_groups(). Znalezienie grupy wśród rozrzuconych wyrazów oznacza przeskanowanie ich wszystkich, więc to wyszukiwanie kosztuje tyle samo, niezależnie od tego, czy losuje się jedną grupę, czy czterdzieści. group_order() zbiera każdą grupę w jeden ciągły fragment, po czym wyszukiwanie jest przeszukiwaniem binarnym po granicach grup i kosztuje tyle, ile kosztują wylosowane grupy, a nie przechowywane wyrazy. Samo wyszukiwanie staje się dramatycznie tańsze; to, ile to oszczędza w konkretnej transpilacji, zależy od tego, co jeszcze robi potok, ponieważ po usunięciu skanowania dominują mapowanie i synteza:

>>> from qiskit_fermions.operators.terms.ordering import group_order
>>>
>>> canon = group_order(canon)
>>> print(canon.groups[:4])
[0, 0, ...]

To zmienia kolejność wyrazów bez zmiany wartości operatora i wystarczy to zrobić tylko raz, bez względu na to, ile losowych obwodów się z niego wylosuje.

3. Przygotowanie obwodu ewolucji w czasie​

Przygotuj obwód ewolucji hamiltonianu w czasie oraz obwód bazowy, z którego będą losowane próbki. qiskit_fermions.circuit.library zawiera wszystkie wymagane do tego komponenty, zgodnie z konwencjami Qiskit.

Protokół qDRIFT to metoda syntezy FermionicQDrift, a bramka Evolution niesie ją jako swoją synthesis. Metoda wprowadza kilka parametrów, za pomocą których możesz dostosować generowane obwody:

  • Długość każdego obwodu wyrażona w grupach wzbudzeń: num_groups

  • Opcjonalnie ziarno generatora liczb losowych: rng

  • Opcjonalnie wstępnie obliczone wagi próbkowania, jeśli sama transpilacja staje się wąskim gardłem: weights (zobacz (Opcjonalnie) Wstępne obliczenie wag próbkowania poniżej)

Ponieważ metoda jest przechowywana w bramce, wszystko, co opisuje operator bramki, na przykład wspomniane wagi, znajduje się tuż obok tego operatora.

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

Ten przykład ani nie inicjalizuje mody fermionowe cząstkami, ani nie mierzy ich stanu końcowego.

4. Transpilacja obwodu​

Moduł qiskit_fermions.transpiler integruje się bezpośrednio z potokiem transpilacji Qiskit, co pozwala bezpośrednio transpilować zbudowany wyżej FermionicCircuit do QuantumCircuit.

Użyj odwzorowania fermion–kubit jordan_wigner(), aby zamienić hamiltonian wyrażony przez fermiony na wyrażony w ciągach Pauliego. Można to zrobić bezpośrednio w ramach procesu transpilacji, używając przebiegu transpilacji F2QSynthesis. Użyj generate_preset_jw_pass_manager(), aby zbudować FermionicStagedPassManager, który zapewnia spójne użycie kodowania Jordana-Wignera dla wszystkich instrukcji obwodu.

Co istotne, dodaj przebieg Decompose z Qiskit do etapu optimization potoku transpilacji. Rozkłada on bramkę Evolution za pomocą metody syntezy, którą bramka niesie, czyli przygotowanej wyżej metody qDRIFT. Dzięki temu obwód nie używa ewolucji w czasie całego hamiltonianu, której głębokość przekraczałaby możliwości obecnie dostępnego sprzętu kwantowego.

Zamiast tego podpróbkowuje stałą liczbę groups wyrazów hamiltonianu dla każdego obwodu, za każdym razem, gdy obwód jest transpilowany. Dzięki temu możesz wygenerować wiele losowych obwodów, jak wymaga algorytm SqDRIFT, wielokrotnie uruchamiając potok transpilacji. Liczba obwodów do wygenerowania to num_sqdrift_randomizations.

>>> 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)
... ]
Uwaga

Poprzednie przykłady ustalają ziarno rng metody syntezy FermionicQDrift, dzięki czemu sekwencję losowań można odtworzyć.

Wskazówka

Ten sam potok przyjmuje metodę qDRIFT w dwóch innych miejscach. Przebieg F2FSynthesis przyjmuje jedną metodę dla każdej bramki Evolution według nazwy, F2FSynthesis({"Evolution": FermionicQDrift(num_groups)}), co wystarcza, gdy żadna bramka nie przechowuje niczego specyficznego dla swojego operatora. Następny krok używa trzeciego miejsca, przebiegu QDriftTrotterization.

5. Filtrowanie wylosowanych wzbudzeń według obsadzenia​

Poza wyrazami diagonalnymi odfiltrowanymi w poprzednim kroku wylosowane wzbudzenie może działać wyłącznie w zbiorze modów, których obsadzenie jest już ustalone (wszystkie obsadzone lub wszystkie nieobsadzone), gdzie nie może przenieść cząstki z jednej strony na drugą. Takie wzbudzenie nie mówi nic o spróbkowanych ciągach bitów, więc miejsce poświęcone na nie jest miejscem zmarnowanym. Przebieg QDriftTrotterization, który opakowuje tę samą metodę FermionicQDrift, odrzuca takie wzbudzenia w trakcie losowania i losuje zamiennik, tak aby każde z num_groups miejsc powstałego obwodu zawierało wzbudzenie sprzęgające obie strony.

Ostrzeżenie

To filtrowanie nie jest darmowe i właśnie dlatego jest osobnym przebiegiem. Oszczędza budżet num_groups spróbkowanych miejsc, a nie głębokość obwodu: ponieważ ten budżet jest stały, odrzucone wzbudzenie jest zastępowane, a nie pomijane, a tanie ustępuje miejsca wzbudzeniu sprzęgającemu, którego synteza kosztuje więcej. Spodziewaj się, że przefiltrowany obwód będzie głębszy niż nieprzefiltrowany, wylosowany z tego samego ziarna.

Renormalizacja losowania po zaakceptowanych wzbudzeniach zmienia rozkład, z którego próbkuje protokół qDRIFT, i w przeciwieństwie do grupowania z kroku 2 łamie też gwarancję zbieżności protokołu: spróbkowany iloczyn nie uśrednia się już do ewolucji pod działaniem przekazanego hamiltonianu. Każde zachowane wzbudzenie zostaje ważone odwrotnością prawdopodobieństwa akceptacji, odrzucone nie wnoszą nic, a ani całkowita wielkość współczynników, ani czas ewolucji na bramkę nie są korygowane w ramach kompensacji.

Reguła akceptacji jest też nadszacowaniem. Porównuje tylko nośniki modów, więc nie niesie informacji o amplitudach, a śledzone zbiory tylko rosną: wzbudzenie ograniczone do zbioru obsadzonego jest rzeczywiście bezczynne na stanie produktowym, ale to samo rozumowanie słabnie dla późniejszych losowań, gdy wcześniej zaakceptowane wzbudzenia splątały stan i oznaczyły większość modów jako „niepewne”.

Używaj filtrowania w przepływie pracy próbkowania ciągów bitów opisanym w tym przewodniku, gdzie obwody zasilają przetwarzanie końcowe SQD i liczą się tylko spróbkowane ciągi bitów. Nie używaj go, jeśli zamierzasz estymować wartość oczekiwaną z tych obwodów lub w jakikolwiek inny sposób polegać na ograniczeniu błędu qDRIFT: wyniki te obarczone są obciążeniem, którego przebieg nie koryguje.

To filtrowanie musi wiedzieć, które mody są na początku obsadzone. Dlatego wymaga bramki InitializeModes poprzedzającej bramki Evolution w obwodzie; InitializeModes.from_hartree_fock() to wygodny sposób jej zbudowania. Dodaj ją tutaj dla referencji Hartree-Focka dla N2 (siedem elektronów alfa i siedem beta w 14 orbitalach przestrzennych) i porównaj spróbkowane wzbudzenia z filtrowaniem i bez niego:

>>> 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]

Żadne z wzbudzeń wylosowanych bez filtrowania w ogóle nie dotyka zbioru obsadzonego (0-6 i 28-34), więc żadne z nich nie może przenieść cząstki między modem obsadzonym a nieobsadzonym. Przy filtrowaniu wszystkie pięć jest odrzucanych i zastępowanych wzbudzeniami, które sprzęgają mod obsadzony z nieobsadzonym. Na przykład pierwsze zaakceptowane wzbudzenie [0, 1, 6, 7] przenosi cząstkę między obsadzonymi modami 0, 1 i 6 a nieobsadzonym modem 7.

Gdy wzbudzenie zostanie zaakceptowane, każdy mod w jego nośniku staje się „niepewny” i tym samym może pełnić którąkolwiek z ról w kolejnych próbkach, więc zbiory modów obsadzonych i nieobsadzonych rosną w miarę akceptowania kolejnych wzbudzeń. To sprawia, że drugie wzbudzenie, [0, 1, 28, 29], jest akceptowalne. Wszystkie jego cztery mody należą do modów pierwotnie obsadzonych, więc nie sprzęga się z żadnym pierwotnie nieobsadzonym modem. Zostaje zaakceptowane tylko dlatego, że mody 0 i 1 stały się niepewne (a więc mogą być stroną „nieobsadzoną” sprzężenia), gdy dotknęło ich pierwsze wzbudzenie.

Ten wzrost sprawia, że reguła jest nadszacowaniem, a nie testem: gdy więcej modów staje się niepewnych, odrzucanych jest mniej losowań, a filtrowanie najsilniej wpływa na rozkład próbkowania w najwcześniejszych losowaniach.

Aby ocenić, jak bardzo filtrowanie przesunęło rozkład, odczytaj liczniki odrzuceń zapisywane przez przebieg w metadanych obwodu. Dla każdej przefiltrowanej bramki Evolution zapisywany jest jeden wpis, a stosunek zaakceptowanych do wszystkich szacuje prawdopodobieństwo akceptacji, czyli czynnik, przez który zachowane wzbudzenia zostały przeważone:

>>> # 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
Uwaga

Filtrowanie potrzebuje informacji o obsadzeniu, względem której filtruje. Bramka PrepareSlaterDeterminant dostarcza ją tak samo jak InitializeModes, a każda OrbitalRotation oznacza każdy mod, na który działa, jako „niepewny”. Bez żadnej z nich lub gdy zainicjowane mody trafiają wszystkie po jednej stronie (wszystkie obsadzone albo wszystkie nieobsadzone), przebieg zgłasza UserWarning i pozostawia próbkowanie nieprzefiltrowane dla danej bramki Evolution. W hamiltonianie, którego pozostałe wyrazy nie mogą już sprzęgać obu śledzonych zbiorów, przebieg zamiast tego zgłasza RuntimeError po QDriftTrotterization.MAX_SAMPLE_RETRIES kolejnych odrzuceniach.

(Opcjonalnie) Wstępne obliczenie wag próbkowania​

Sięgnij po to tylko wtedy, gdy sama transpilacja jest wąskim gardłem. Nie zmienia to niczego w otrzymywanych obwodach.

FermionicQDrift losuje każdą grupę z prawdopodobieństwem proporcjonalnym do wielkości jej współczynnika i wyprowadza te wielkości z hamiltonianu przy każdym wywołaniu: jedna wartość na wyraz hamiltonianu, zredukowana do jednej na grupę. Ponieważ metoda jest bezstanowa, powtarza tę redukcję dla każdego losowego obwodu, mimo że wynik jest za każdym razem identyczny. Argument weights pozwala obliczyć to raz za pomocą group_coeff_means() i przekazać wynik, przenosząc koszt poza pętlę i nie obciążając metody żadnym buforowanym stanem zależnym od hamiltonianu.

To odpowiednik uporządkowania wyrazów z kroku 2. Tamto wynosi z pętli wyszukiwanie grup, to zaś redukcję wag, a każde z nich jest tak zauważalne, jak nieobecne jest drugie, więc warto stosować oba razem. Ponieważ canon został już wyżej uporządkowany, oba składają się bezpośrednio.

Wagi opisują grupy jednego konkretnego operatora, więc należą do bramki Evolution tego operatora: zbuduj metodę dla tej bramki i przekaż ją jako synthesis bramki, dokładnie jak w kroku 3.

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

Ponieważ są to dokładnie te wagi, które przebieg sam by obliczył, obwody są takie same jak wcześniej; znika tylko powtarzana redukcja.

Oczekiwany jest jeden wpis na grupę, a nie na wyraz, ilekroć operator niesie grupy, ponieważ operator pogrupowany jest próbkowany grupami. Wpisy muszą być nieujemne: waga jest wielkością, ponieważ protokół qDRIFT rozkłada H=∑jhjHjH = \sum_j h_j H_j z dodatnimi hjh_j, a znak współczynnika należy do HjH_j, skąd przebieg odczytuje go bezpośrednio z operatora.

Uwaga

Wagi to bezwzględne wielkości, a nie względne preferencje. Ich suma ustala też czas ewolucji, więc pomnożenie każdego wpisu przez γ\gamma próbkuje identycznie, ale ewoluuje przez γt\gamma t zamiast tt. Przekazanie czegokolwiek innego niż własne wielkości współczynników hamiltonianu zmienia więc to, jaką ewolucję przybliża zespół, dlatego jest to narzędzie wydajnościowe, a nie sposób dostrajania próbkowania.

Ponieważ wagi należą do bramki, której operator opisują, obwód może zawierać kilka bramek Evolution, każdą z wagami własnego operatora. Przebieg QDriftTrotterization z kroku 5 respektuje je w ten sam sposób: losuje bramkę za pomocą FermionicQDrift, który bramka niesie, a dla bramek, które żadnego nie niosą, wraca do tego, z którym został skonstruowany. Inny rodzaj metody syntezy na bramce nie jest stwierdzeniem o qDRIFT, więc przebieg go ignoruje. Przekazanie metody przebiegowi według nazwy bramki, jak w F2FSynthesis({"Evolution": ...}), stosuje jedną metodę do każdej bramki Evolution, więc przekazane tam wagi mają sens tylko dla obwodu z jedną taką bramką. Długość wag jest sprawdzana względem każdej bramki, do której są stosowane.

(Opcjonalnie) Optymalizacja indeksowania modów fermionowych​

Możesz dodać do potoku transpilacji dodatkowy krok optymalizacji, który minimalizuje odległość nośników wzbudzeń fermionowych przez zmianę etykiet indeksów modów fermionowych. Ta optymalizacja została wprowadzona w artykule o SqDRIFT i jest zaimplementowana przez build_excitation_span_minimization_model(). Można ją łatwo wstawić do potoku transpilera za pomocą przebiegu RelabelModes:

>>> 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
Uwaga

Użycie automatycznej optymalizacji wewnątrz RelabelModes (która korzysta z build_excitation_span_minimization_model()) wymaga opcjonalnej zależności zarządzanej przez HAS_PYOMO.

Ważne

Zmiana etykiet zmienia to, który mod niesie dany kubit, więc diagonalizacja podprzestrzeni otrzyma właściwe ciągi bitów tylko wtedy, gdy się to uwzględni. To, gdzie następuje to uwzględnienie, zależy od tego, gdzie mierzysz.

Dodaj pomiary do FermionicCircuit za pomocą FermionicCircuit.measure_all() przed transpilacją, a nie ma nic do zrobienia: każdy pomiar nazywa mod, który odczytuje, więc zmiana etykiet niesie go ze sobą, a spróbkowane ciągi bitów są indeksowane pierwotnym modem.

Jeśli zamiast tego mierzysz transpilowany QuantumCircuit, ciągi bitów trzeba przetworzyć końcowo, używając permutation zapisanej w metadanych obwodu. Przepis znajdziesz w dokumentacji RelabelModes.

Kolejne kroki​

Skoro udało Ci się wygenerować zespół obwodów, możesz z nich próbkować ciągi bitów. W tym celu obwody muszą zostać wykonane na sprzęcie. Szczegółowe instrukcje znajdziesz w dokumentacji Qiskit.

Po uzyskaniu próbek ciągów bitów można ich użyć razem ze współczynnikami hamiltonianu do przetwarzania końcowego SQD, jak wyjaśniono w samouczkach dodatku SQD.