Sari la conținutul principal

Lucrează cu DAG-uri în pasele Transpiler

În Qiskit, în cadrul etapelor de transpilare, circuitele sunt reprezentate folosind un DAG. În general, un DAG este compus din vârfuri (cunoscute și ca „noduri") și muchii direcționate care conectează perechi de vârfuri într-o anumită orientare. Această reprezentare este stocată folosind obiecte qiskit.dagcircuit.DAGCircuit care sunt alcătuite din obiecte individuale DagNode. Avantajul acestei reprezentări față de o simplă listă de porți (adică un netlist) constă în faptul că fluxul de informații dintre operații este explicit, facilitând luarea deciziilor de transformare.

Acest ghid demonstrează cum să lucrezi cu DAG-uri și să le folosești pentru a scrie pase personalizate de Transpiler. Va începe cu construirea unui Circuit simplu și examinarea reprezentării sale DAG, apoi explorează operații de bază cu DAG și implementează o pasă personalizată BasicMapper.

Construiește un Circuit și examinează DAG-ul său

Fragmentul de cod de mai jos ilustrează DAG-ul prin crearea unui Circuit simplu care pregătește o stare Bell și aplică o rotație RZR_Z, în funcție de rezultatul măsurătorii.

Versiuni de pachete

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe. Recomandăm utilizarea acestor versiuni sau a unora mai noi.

qiskit[all]~=2.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.converters import circuit_to_dag
from qiskit.visualization import circuit_drawer
from qiskit.visualization.dag_visualization import dag_drawer

# Create circuit
q = QuantumRegister(3, "q")
c = ClassicalRegister(3, "c")
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])

# Qiskit 2.0 uses if_test instead of c_if
with circ.if_test((c, 2)):
circ.rz(0.5, q[1])

circuit_drawer(circ, output="mpl")

Output of the previous code cell

În DAG există trei tipuri de noduri de graf: noduri de intrare Qubit/clbit (verde), noduri de operație (albastru) și noduri de ieșire (roșu). Fiecare muchie indică fluxul de date (sau dependența) între două noduri. Folosește funcția qiskit.tools.visualization.dag_drawer() pentru a vizualiza DAG-ul acestui Circuit. (Instalează biblioteca Graphviz pentru a rula aceasta.)

# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)

Output of the previous code cell

Operații de bază cu DAG

Exemplele de cod de mai jos demonstrează operații comune cu DAG-uri, inclusiv accesarea nodurilor, adăugarea de operații și substituirea sub-circuitelor. Aceste operații formează baza pentru construirea paselor de Transpiler.

Obține toate nodurile de operație din DAG

Metoda op_nodes() returnează o listă iterabilă de obiecte DAGOpNode din Circuit:

dag.op_nodes()
[DAGOpNode(op=Instruction(name='h', num_qubits=1, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=()),
DAGOpNode(op=Instruction(name='cx', num_qubits=2, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>, <Qubit register=(3, "q"), index=1>), cargs=()),
DAGOpNode(op=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=(<Clbit register=(3, "c"), index=0>,)),
DAGOpNode(op=Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f912f47db10>, None]), qargs=(<Qubit register=(3, "q"), index=1>,), cargs=(<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>))]

Fiecare nod este o instanță a clasei DAGOpNode:

node = dag.op_nodes()[3]
print("node name:", node.name)
print("op:", node.op)
print("qargs:", node.qargs)
print("cargs:", node.cargs)
print("condition:", node.op.condition)
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f912f4ceed0>, None])
qargs: (<Qubit register=(3, "q"), index=1>,)
cargs: (<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>)
condition: (ClassicalRegister(3, 'c'), 2)

Adaugă o operație la sfârșit

O operație este adăugată la sfârșitul DAGCircuit folosind metoda apply_operation_back(). Aceasta atașează Gate-ul specificat pentru a acționa asupra Qubit-urilor date după toate operațiile existente din Circuit.

from qiskit.circuit.library import HGate

dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)

Output of the previous code cell

Adaugă o operație la început

O operație este adăugată la începutul DAGCircuit folosind metoda apply_operation_front(). Aceasta inserează Gate-ul specificat înaintea tuturor operațiilor existente din Circuit, făcând-o efectiv prima operație executată.

from qiskit.circuit.library import CCXGate

dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)

Output of the previous code cell

Substituie un nod cu un sub-circuit

Un nod care reprezintă o operație specifică din DAGCircuit este înlocuit cu un sub-circuit. Mai întâi, un sub-DAG nou este construit cu secvența dorită de Gate-uri, apoi nodul țintă este substituit cu acest sub-DAG folosind substitute_node_with_dag(), păstrând conexiunile cu restul Circuit-ului.

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library import CHGate, U2Gate, CXGate

# Build sub-DAG
mini_dag = DAGCircuit()
p = QuantumRegister(2, "p")
mini_dag.add_qreg(p)
mini_dag.apply_operation_back(CHGate(), qargs=[p[1], p[0]])
mini_dag.apply_operation_back(U2Gate(0.1, 0.2), qargs=[p[1]])

# Replace CX with mini_dag
cx_node = dag.op_nodes(op=CXGate).pop()
dag.substitute_node_with_dag(cx_node, mini_dag, wires=[p[0], p[1]])
dag_drawer(dag)

Output of the previous code cell

După finalizarea tuturor transformărilor, DAG-ul poate fi convertit înapoi într-un obiect QuantumCircuit obișnuit. Astfel funcționează pipeline-ul Transpiler. Un Circuit este preluat, procesat în formă DAG și un Circuit transformat este produs ca ieșire.

from qiskit.converters import dag_to_circuit

new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")

Output of the previous code cell

Implementează o pasă BasicMapper

Structura DAG poate fi valorificată pentru scrierea paselor de Transpiler. În exemplul de mai jos, o pasă BasicMapper este implementată pentru a mapa un Circuit arbitrar pe un dispozitiv cu conectivitate restricționată a Qubit-urilor. Pentru îndrumare suplimentară, consultă ghidul despre scrierea paselor personalizate de Transpiler.

Pasa este definită ca o TransformationPass, ceea ce înseamnă că modifică Circuit-ul. Aceasta parcurge DAG-ul strat cu strat, verificând dacă fiecare instrucțiune respectă constrângerile impuse de harta de cuplare a dispozitivului. Dacă este detectată o încălcare, se determină o cale de swap și se inserează Gate-urile SWAP necesare în consecință.

La crearea unei pase de Transpiler, prima decizie implică selectarea dacă pasa trebuie să moștenească din TransformationPass sau AnalysisPass. Pasele de transformare sunt proiectate să modifice Circuit-ul, în timp ce pasele de analiză sunt destinate doar să extragă informații pentru utilizare de către pasele ulterioare. Funcționalitatea principală este apoi implementată în metoda run(dag). În final, pasa ar trebui înregistrată în modulul qiskit.transpiler.passes.

În această pasă specifică, DAG-ul este parcurs strat cu strat (unde fiecare strat conține operații care acționează pe seturi disjuncte de Qubit-uri și pot fi astfel executate independent). Pentru fiecare operație, dacă constrângerile hărții de cuplare nu sunt respectate, este identificată o cale de swap potrivită, iar swap-urile necesare sunt inserate pentru a aduce Qubit-urile implicate în adiacență.

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler import Layout
from qiskit.circuit.library import SwapGate

class BasicSwap(TransformationPass):
def __init__(self, coupling_map, initial_layout=None):
super().__init__()
self.coupling_map = coupling_map
self.initial_layout = initial_layout

def run(self, dag):
new_dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)

if self.initial_layout is None:
self.initial_layout = Layout.generate_trivial_layout(
*dag.qregs.values()
)

current_layout = self.initial_layout.copy()

for layer in dag.serial_layers():
subdag = layer["graph"]
for gate in subdag.two_qubit_ops():
q0, q1 = gate.qargs
p0 = current_layout[q0]
p1 = current_layout[q1]

if self.coupling_map.distance(p0, p1) != 1:
path = self.coupling_map.shortest_undirected_path(p0, p1)
for i in range(len(path) - 2):
wire1, wire2 = path[i], path[i + 1]
qubit1 = current_layout[wire1]
qubit2 = current_layout[wire2]
new_dag.apply_operation_back(
SwapGate(), qargs=[qubit1, qubit2]
)
current_layout.swap(wire1, wire2)

new_dag.compose(
subdag, qubits=current_layout.reorder_bits(new_dag.qubits)
)

return new_dag

Pasa poate fi acum testată pe un Circuit exemplu mic. Un manager de pase este construit cu pasa nou definită inclusă. Circuit-ul exemplu este apoi furnizat acestui manager de pase și se obține un Circuit nou, transformat, ca ieșire.

from qiskit.transpiler import CouplingMap, PassManager
from qiskit import QuantumRegister, QuantumCircuit

q = QuantumRegister(7, "q")
in_circ = QuantumCircuit(q)
in_circ.h(q[0])
in_circ.cx(q[0], q[4])
in_circ.cx(q[2], q[3])
in_circ.cx(q[6], q[1])
in_circ.cx(q[5], q[0])
in_circ.rz(0.1, q[2])
in_circ.cx(q[5], q[0])

coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
coupling_map = CouplingMap(couplinglist=coupling)

pm = PassManager()
pm.append(BasicSwap(coupling_map))

out_circ = pm.run(in_circ)

in_circ.draw(output="mpl")
out_circ.draw(output="mpl")

Output of the previous code cell

Pași următori