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?

Napisz własny przebieg transpilatora

Wersje pakietów

Kod na tej stronie został opracowany przy użyciu następujących wymagań. Zalecamy używanie tych wersji lub nowszych.

qiskit[all]~=2.6.1

Qiskit SDK umożliwia tworzenie niestandardowych przebiegów transpilacji i uruchamianie ich w obiekcie PassManager lub dodawanie ich do StagedPassManager. W tym miejscu pokażemy, jak napisać przebieg transpilatora, skupiając się na zbudowaniu przebiegu, który wykonuje Pauli twirling na zaszumionych bramkach kwantowych w obwodzie kwantowym. Ten przykład korzysta z DAG, czyli obiektu, którym operuje przebieg typu TransformationPass.

Podstawy: reprezentacja DAG

Zanim zbudujesz przebieg, ważne jest, aby wprowadzić wewnętrzną reprezentację obwodów kwantowych w Qiskit, czyli skierowany graf acykliczny (DAG) (zobacz ten samouczek, aby uzyskać przegląd). Aby przejść przez te kroki, zainstaluj bibliotekę graphviz dla funkcji rysowania DAG.

W Qiskit, w ramach etapów transpilacji, obwody są reprezentowane za pomocą DAG. Ogólnie DAG składa się z wierzchołków (znanych też jako „węzły”) oraz skierowanych krawędzi, które łączą pary wierzchołków w określonej orientacji. Ta reprezentacja jest przechowywana za pomocą obiektów qiskit.dagcircuit.DAGCircuit, które składają się z pojedynczych obiektów DagNode. Zaletą tej reprezentacji w porównaniu z czystą listą bramek (czyli netlistą) jest to, że przepływ informacji między operacjami jest jawny, co ułatwia podejmowanie decyzji o transformacjach.

Ten przykład ilustruje DAG poprzez utworzenie prostego obwodu, który przygotowuje stan Bella i stosuje obrót RZR_Z w zależności od wyniku pomiaru.

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np

qr = QuantumRegister(3, 'qr')
cr = ClassicalRegister(3, 'cr')
qc = QuantumCircuit(qr, cr)

qc.h(qr[0])
qc.cx(qr[0], qr[1])
qc.measure(qr[0], cr[0])
with qc.if_test((cr, 2)):
qc.rz(np.pi/2, qr[1])
qc.draw(output='mpl')

Circuit preparing a Bell state and applying an R_Z rotation depending on the measurement outcome.

Użyj funkcji qiskit.tools.visualization.dag_drawer(), aby zobaczyć DAG tego obwodu. Istnieją trzy rodzaje węzłów grafu: węzły kubitów/bitów klasycznych (zielone), węzły operacji (niebieskie) i węzły wyjściowe (czerwone). Każda krawędź wskazuje przepływ danych (lub zależność) między dwoma węzłami.

from qiskit.converters import circuit_to_dag
from qiskit.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

The circuit's DAG consists of nodes that are connected by directional edges. It is a visual way to represent qubits or classical bits, the operations, and the way that data flows.

Przebiegi transpilatora​

Przebiegi transpilatora są klasyfikowane jako AnalysisPass lub TransformationPass. Przebiegi ogólnie pracują z DAG-iem oraz property_set – obiektem słownikopodobnym służącym do przechowywania właściwości wyznaczonych przez przebiegi analizy. Przebiegi analizy pracują zarówno z DAG-iem, jak i z jego property_set. Nie mogą modyfikować DAG-u, ale mogą modyfikować property_set. Inaczej jest w przypadku przebiegów transformacji, które modyfikują DAG i mogą odczytywać (lecz nie zapisywać) property_set. Na przykład przebiegi transformacji tłumaczą układ na jego ISA lub wykonują przebiegi routingu wstawiające bramki SWAP w odpowiednich miejscach.

Utwórz przebieg transpilatora PauliTwirl​

Poniższy przykład konstruuje przebieg transpilatora, który dodaje Pauli twirls. Pauli twirling to strategia tłumienia błędów, która losuje sposób, w jaki qubity doświadczają zaszumionych kanałów – przyjmujemy tutaj, że są to bramki dwu-qubitowe (ponieważ są o wiele bardziej podatne na błędy niż bramki jedno-qubitowe). Pauli twirls nie wpływają na działanie bramek dwu-qubitowych. Są dobierane tak, aby te zastosowane przed bramką dwu-qubitową (po lewej stronie) były neutralizowane przez te zastosowane po bramce (po prawej stronie). W tym sensie operacje dwu-qubitowe są identyczne, lecz sposób ich realizacji jest różny. Jedną z zalet Pauli twirling jest zamiana błędów koherentnych na błędy stochastyczne, które można redukować przez uśrednianie na większej liczbie pomiarów.

Przebiegi transpilatora działają na DAG-u, dlatego ważną metodą do nadpisania jest .run(), która przyjmuje DAG jako wejście. Inicjalizacja par Paulich w pokazany sposób zachowuje działanie każdej bramki dwu-qubitowej. Realizuje to metoda pomocnicza build_twirl_set, która przechodzi przez każdy dwu-qubitowy Pauli (uzyskany z pauli_basis(2)) i znajduje drugi Pauli zachowujący daną operację.

Z DAG-u korzystaj z metody op_nodes(), aby zwrócić wszystkie jego węzły. DAG może być też używany do zbierania przebiegów, czyli sekwencji węzłów działających nieprzerwanie na qubicie. Można je zbierać jako przebiegi jedno-qubitowe z collect_1q_runs, dwu-qubitowe z collect_2q_runs oraz przebiegi węzłów, których nazwy instrukcji znajdują się na liście nazw, z collect_runs. DAGCircuit oferuje wiele metod do przeszukiwania i przemierzania grafu. Często stosowaną metodą jest topological_op_nodes, która dostarcza węzły w kolejności zależności. Inne metody, takie jak bfs_successors, służą przede wszystkim do sprawdzania interakcji węzłów z kolejnymi operacjami w DAG-u.

W tym przykładzie chcemy zastąpić każdy węzeł reprezentujący instrukcję podmikroukładem zbudowanym jako mini DAG. Do mini DAG-u dodaje się dwu-qubitowy rejestr kwantowy. Operacje są dodawane do mini DAG-u za pomocą apply_operation_back, które umieszcza Instruction na wyjściu mini DAG-u (natomiast apply_operation_front umieściłoby je na wejściu). Węzeł jest następnie zastępowany mini DAG-iem przy użyciu substitute_node_with_dag, a proces kontynuowany jest dla każdego wystąpienia CXGate i ECRGate w DAG-u (odpowiadających dwu-qubitowym bramkom bazowym na Backend-ach IBM®).

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

_needed = {"numpy": "numpy", "qiskit": "qiskit"}
_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.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis

import numpy as np

from typing import Iterable, Optional
class PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""

def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()

def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}

# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []

# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))

self.twirl_set[twirl_gate.name] = twirl_list

def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue

# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]

# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)

# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)

# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)

return dag

Użyj przebiegu transpilatora PauliTwirl​

Poniższy kod używa stworzonego powyżej przebiegu do transpilacji układu. Rozważmy prosty układ z bramkami cx i ecr.

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")

Wynik poprzedniej komórki kodu

Aby zastosować własny przebieg, zbuduj menedżer przebiegów używając przebiegu PauliTwirl i uruchom go na 50 układach.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

Każda bramka dwu-qubitowa jest teraz otoczona przez dwa Paulie z obu stron.

twirled_qcs[-1].draw("mpl")

Wynik poprzedniej komórki kodu

Operatory są takie same, gdy użyjemy Operator z qiskit.quantum_info:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_

Następne kroki​

Zalecenia