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?

Redukcja głębokości Circuit za pomocą wstecznej propagacji operatorów

Wsteczna propagacja operatorów to technika polegająca na pochłanianiu operacji z końca Circuit kwantowego przez operator Pauliego, co zazwyczaj redukuje głębokość Circuit kosztem dodatkowych składników w operatorze. Celem jest jak najgłębsze wsteczne propagowanie Circuit bez dopuszczania do nadmiernego rozrostu operatora.

Jednym ze sposobów na umożliwienie głębszej wstecznej propagacji przez Circuit przy jednoczesnym zapobieganiu nadmiernemu rozrostowi operatora jest obcinanie składników o małych współczynnikach zamiast dodawania ich do operatora. Obcinanie składników może skutkować mniejszą liczbą Circuit kwantowych do wykonania, jednak wiąże się to z pewnym błędem w końcowym obliczeniu wartości oczekiwanej, proporcjonalnym do wartości bezwzględnej współczynników obciętych składników. W tym samouczku zaimplementujemy wzorzec Qiskit do symulacji dynamiki kwantowej łańcucha spinów Heisenberga z użyciem wstecznej propagacji operatorów:

  • Krok 1: Odwzorowanie na problem kwantowy

    • Odwzoruj ewoluujący w czasie Hamiltonian na obwód kwantowy
  • Krok 2: Optymalizacja problemu

    • Podziel Circuit na wycinki

    • Wstecznie propaguj wycinki z Circuit na obserwowalną Pauliego
    • Połącz pozostałe wycinki w jeden obwód

    • Transpiluj Circuit dla Backend

  • Krok 3: Wykonanie eksperymentów

    • Oblicz wartość oczekiwaną przy użyciu zredukowanego Circuit i rozszerzonej obserwowalnej z StatevectorEstimator dla uproszczenia w tym notebooku
  • Krok 4: Rekonstrukcja wyników

    • N.A.

Uwaga: Qiskit luźno opisuje warstwy jako partycje Circuit o głębokości 1 w poprzek wszystkich qubit. Ten pakiet używa terminu wycinki do opisu warstw o dowolnej głębokości. Funkcja qiskit_addon_obp.backpropagate jest zaprojektowana do wstecznego propagowania całych wycinków naraz, więc sposób podziału Circuit na wycinki może mieć duży wpływ na skuteczność wstecznej propagacji dla danego problemu. Więcej o wycinkach dowiesz się poniżej.

Krok 1: Odwzorowanie na problem kwantowy​

Odwzoruj ewolucję czasową kwantowego modelu Heisenberga na eksperyment kwantowy.​

Pakiet qiskit_addon_utils dostarcza przydatnych, wielokrotnego użytku funkcjonalności do różnych celów.

Jego moduł qiskit_addon_utils.problem_generators zawiera funkcje do generowania Hamiltonianów podobnych do Heisenberga na zadanym grafie połączeń. Graf ten może być albo rustworkx.PyGraph, albo CouplingMap, co ułatwia korzystanie z niego w przepływach pracy skoncentrowanych na Qiskit.

Poniżej najpierw generujemy CouplingMap w układzie heavy-hex, z którego wycinamy liniowy łańcuch 10 qubit. Zauważ, że indeksy nowej reduced_coupling_map znowu zaczynają się od zera.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit.transpiler import CouplingMap

coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)

# Choose a 10-qubit linear chain on this coupling map
reduced_coupling_map = coupling_map.reduce([0, 13, 1, 14, 10, 16, 5, 12, 8, 18])
from rustworkx.visualization import graphviz_draw

graphviz_draw(reduced_coupling_map.graph, method="circo")

Code output

Następnie generujemy operator Pauliego modelujący Hamiltonian Heisenberga XYZ.

J_{y} \sigma_j^{y} \sigma_{k}^{y} + J_{z} \sigma_j^{z} \sigma_{k}^{z}) + \sum_{j\in V} (h_{x} \sigma_j^{x} + h_{y} \sigma_j^{y} + h_{z} \sigma_j^{z})$$ Gdzie $G(V,E)$ jest grafem dostarczonej mapy połączeń. ```python import numpy as np from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian # Get a qubit operator describing the Heisenberg XYZ model hamiltonian = generate_xyz_hamiltonian( reduced_coupling_map, coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2), ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9), ) print(hamiltonian) ``` ```text SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'], coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j]) ``` Na podstawie operatora qubitowego możemy wygenerować Circuit kwantowy modelujący jego ewolucję czasową. Po raz kolejny z pomocą przychodzi moduł [qiskit_addon_utils.problem_generators](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.problem_generators.html) z przydatną funkcją do tego celu: ```python from qiskit.synthesis import LieTrotter from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit circuit = generate_time_evolution_circuit( hamiltonian, time=0.2, synthesis=LieTrotter(reps=2), ) circuit.draw("mpl", style="iqp", scale=0.6) ``` ![Quantum circuit diagram](/img/qiskit-addons/obp/01-getting-started/output_2.png) ## Krok 2: Optymalizacja problemu \{#step-2-optimize-the-problem} ### Tworzenie wycinków Circuit do wstecznej propagacji \{#create-circuit-slices-to-backpropagate} Pamiętaj, że funkcja ``backpropagate`` będzie wstecznie propagować całe wycinki Circuit naraz, więc sposób podziału na wycinki może mieć wpływ na skuteczność wstecznej propagacji dla danego problemu. Tutaj pogrupujemy Gate tego samego typu w wycinki, używając funkcji [slice_by_gate_types](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.slicing.slice_by_gate_types.html). Bardziej szczegółowe omówienie podziału Circuit na wycinki znajdziesz w [poradniku praktycznym](https://qiskit.github.io/qiskit-addon-utils/how_tos/create_circuit_slices.html) pakietu [qiskit-addon-utils](https://qiskit.github.io/qiskit-addon-utils/index.html). ```python from qiskit_addon_utils.slicing import slice_by_gate_types slices = slice_by_gate_types(circuit) print(f"Separated the circuit into {len(slices)} slices.") ``` ```text Separated the circuit into 18 slices. ``` ### Ograniczanie rozrostu operatora podczas wstecznej propagacji \{#constrain-how-large-the-operator-may-grow-during-backpropagation} Podczas wstecznej propagacji liczba składników w operatorze będzie generalnie szybko zbliżać się do $4^N$, gdzie $N$ to liczba qubit. Rozmiar operatora można ograniczyć, podając argument kluczowy ``operator_budget`` funkcji ``backpropagate``, który przyjmuje instancję [OperatorBudget](https://qiskit.github.io/qiskit-addon-obp/stubs/qiskit_addon_obp.utils.simplify.OperatorBudget.html). Tutaj określamy, że wsteczna propagacja powinna się zatrzymać, gdy liczba grup Pauliego przemiennych qubitowo w operatorze przekroczy 8. ```python from qiskit_addon_obp.utils.simplify import OperatorBudget op_budget = OperatorBudget(max_qwc_groups=8) ``` ### Wsteczna propagacja wycinków z Circuit \{#backpropagate-slices-from-the-circuit} Najpierw określimy obserwowalną Pauli-Z na qubicie 0 i będziemy wstecznie propagować wycinki z Circuit ewolucji czasowej, aż składniki w obserwowalnej nie będą mogły być już połączone w 8 lub mniej grup Pauliego przemiennych qubitowo. Poniżej zobaczysz, że wstecznie propagowaliśmy 7 wycinków, ale użyliśmy tylko 6 z 8 przydzielonych grup Pauliego. Oznacza to, że wsteczna propagacja kolejnego wycinka spowodowałaby przekroczenie liczby 8 grup Pauliego. Możemy zweryfikować, czy tak jest, sprawdzając zwrócone metadane. ```python from qiskit.quantum_info import SparsePauliOp from qiskit_addon_obp import backpropagate from qiskit_addon_utils.slicing import combine_slices # Specify a single-qubit observable observable = SparsePauliOp("IIIIIIIIIZ") # Backpropagate slices onto the observable bp_obs, remaining_slices, metadata = backpropagate(observable, slices, operator_budget=op_budget) # Recombine the slices remaining after backpropagation bp_circuit = combine_slices(remaining_slices, include_barriers=True) print(f"Backpropagated {metadata.num_backpropagated_slices} slices.") print( f"New observable has {len(bp_obs.paulis)} terms, which can be combined into {len(bp_obs.group_commuting(qubit_wise=True))} groups." ) print( f"Note that backpropagating one more slice would result in {metadata.backpropagation_history[-1].num_paulis[0]} terms " f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups." ) print("The remaining circuit after backpropagation looks as follows:") bp_circuit.draw("mpl", scale=0.6) ``` ```text Backpropagated 7 slices. New observable has 18 terms, which can be combined into 8 groups. Note that backpropagating one more slice would result in 27 terms across 12 groups. The remaining circuit after backpropagation looks as follows: ``` ![Quantum circuit diagram](/img/qiskit-addons/obp/01-getting-started/output_3.png) Następnie określimy ten sam problem z tymi samymi ograniczeniami dotyczącymi rozmiaru wyjściowej obserwowalnej. Tym razem jednak przydzielimy budżet błędu do każdego wycinka, używając funkcji [setup_budget](https://qiskit.github.io/qiskit-addon-obp/stubs/qiskit_addon_obp.utils.truncating.setup_budget.html). Składniki Pauliego o małych współczynnikach będą obcinane z każdego wycinka, aż budżet błędu zostanie wyczerpany, a niewykorzystany budżet zostanie przeniesiony do budżetu kolejnego wycinka. Aby umożliwić to obcinanie, musimy skonfigurować nasz budżet błędu w następujący sposób: ```python from qiskit_addon_obp.utils.truncating import setup_budget truncation_error_budget = setup_budget(max_error_per_slice=0.005) ``` Zauważ, że przydzielając na potrzeby obcinania błąd `5e-3` na wycinek, jesteśmy w stanie usunąć 3 dodatkowe wycinki z Circuit, pozostając w ramach pierwotnego budżetu 8 grup Pauliego przemiennych qubitowo w obserwowalnej. Domyślnie `backpropagate` używa normy L1 obciętych współczynników do ograniczania całkowitego błędu wynikającego z obcinania. Informacje o innych opcjach znajdziesz w [poradniku praktycznym dotyczącym określania p_norm](https://qiskit.github.io/qiskit-addon-obp/how_tos/bound_error_using_p_norm.html). W tym konkretnym przykładzie, w którym wykonaliśmy propagację wsteczną 10 wycinków, całkowity błąd obcięcia nie powinien przekraczać ``(5e-3 error/slice) * (10 slices) = 5e-2``. Aby dowiedzieć się więcej o rozkładaniu budżetu błędu między wycinkami, zapoznaj się z [tym poradnikiem praktycznym](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html). ```python # Run the same experiment but truncate observable terms with small coefficients bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate( observable, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget ) # Recombine the slices remaining after backpropagation bp_circuit_trunc = combine_slices(remaining_slices_trunc, include_barriers=True) print(f"Backpropagated {metadata.num_backpropagated_slices} slices.") print( f"New observable has {len(bp_obs_trunc.paulis)} terms, which can be combined into {len(bp_obs_trunc.group_commuting(qubit_wise=True))} groups.\n" f"After truncation, the error in our observable is bounded by {metadata.accumulated_error(0):.3e}" ) print( f"Note that backpropagating one more slice would result in {metadata.backpropagation_history[-1].num_paulis[0]} terms " f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups." ) print("The remaining circuit after backpropagation looks as follows:") bp_circuit_trunc.draw("mpl", scale=0.6) ``` ```text Backpropagated 10 slices. New observable has 19 terms, which can be combined into 8 groups. After truncation, the error in our observable is bounded by 4.933e-02 Note that backpropagating one more slice would result in 27 terms across 13 groups. The remaining circuit after backpropagation looks as follows: ``` ![Quantum circuit diagram](/img/qiskit-addons/obp/01-getting-started/output_4.png) ### Teraz, gdy mamy nasze zredukowane ansatze i rozszerzone obserwowalne, możemy transpilować nasze eksperymenty do Backend. \{#now-that-we-have-our-reduced-ansatze-and-expanded-observables-we-can-transpile-our-experiments-to-the-backend} Tutaj użyjemy 14-qubitowego [FakeMelbourneV2](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime/fake-provider-fake-melbourne-v2) z [qiskit-ibm-runtime](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime), aby zademonstrować, jak transpilować do Backend QPU. ```python from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2 # Specify a backend and a pass manager for transpilation backend = FakeMelbourneV2() pm = generate_preset_pass_manager(backend=backend, optimization_level=1) # Transpile original experiment circuit_isa = pm.run(circuit) observable_isa = observable.apply_layout(circuit_isa.layout) # Transpile backpropagated experiment bp_circuit_isa = pm.run(bp_circuit) bp_obs_isa = bp_obs.apply_layout(bp_circuit_isa.layout) # Transpile the backpropagated experiment with truncated observable terms bp_circuit_trunc_isa = pm.run(bp_circuit_trunc) bp_obs_trunc_isa = bp_obs_trunc.apply_layout(bp_circuit_trunc_isa.layout) ``` ## Krok 3: Wykonanie eksperymentów kwantowych \{#step-3-execute-quantum-experiments} ### Obliczanie wartości oczekiwanej \{#calculate-expectation-value} Na koniec możemy uruchomić eksperymenty z wsteczną propagacją i porównać je z pełnym eksperymentem przy użyciu bezszumowego [StatevectorEstimator](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.primitives.StatevectorEstimator). Możemy zobaczyć, że wartość oczekiwana uzyskana przy wstecznej propagacji bez obcinania jest równoważna dokładnej wartości w granicach precyzji numerycznej. Wartość oczekiwana dla operatora z obciętymi składnikami ma pewien błąd rzędu ``1e-4``, co mieści się w oczekiwanej tolerancji. **Uwaga:** Używamy prymitywu ``Estimator`` opartego na wektorze stanu, aby zilustrować wpływ obcinania na wynik. Aby uruchomić eksperymenty na Backend, do którego transpilowano eksperymenty w Kroku 2, należy zaimportować [EstimatorV2](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime/estimator-v2) z ``qiskit-ibm-runtime`` i przekazać instancję Backend do konstruktora. ```python from qiskit.primitives import StatevectorEstimator as Estimator estimator = Estimator() # Run the experiments using Estimator primitive result_exact = estimator.run([(circuit_isa, observable_isa)]).result()[0].data.evs.item() result_bp = estimator.run([(bp_circuit_isa, bp_obs_isa)]).result()[0].data.evs.item() result_bp_trunc = ( estimator.run([(bp_circuit_trunc_isa, bp_obs_trunc_isa)]).result()[0].data.evs.item() ) print(f"Exact expectation value: {result_exact}") print(f"Backpropagated expectation value: {result_bp}") print(f"Backpropagated expectation value with truncation: {result_bp_trunc}") print(f" - Expected Error for truncated observable: {metadata.accumulated_error(0):.3e}") print(f" - Observed Error for truncated observable: {abs(result_exact - result_bp_trunc):.3e}") ``` ```text Exact expectation value: 0.8854160687717507 Backpropagated expectation value: 0.8854160687717532 Backpropagated expectation value with truncation: 0.8850236647156059 - Expected Error for truncated observable: 4.933e-02 - Observed Error for truncated observable: 3.924e-04 ``` <TutorialFeedback /> {/* doqumentation-source-hash: 6a2b8207 */}