Lavora con i DAG nei passaggi del transpiler
In Qiskit, nelle fasi di trasposizione, i circuiti vengono rappresentati utilizzando un DAG. In generale, un DAG è composto da vertici (noti anche come "nodi") e da spigoli diretti che collegano coppie di vertici con un particolare orientamento. Questa rappresentazione viene memorizzata utilizzando gli oggetti qiskit.dagcircuit.DAGCircuit che sono composti da singoli oggetti DagNode . Il vantaggio di questa rappresentazione rispetto a un puro elenco di porte (cioè una netlist) è che il flusso di informazioni tra le operazioni è esplicito, rendendo più facile prendere decisioni di trasformazione.
Questa guida mostra come lavorare con i DAG e utilizzarli per scrivere passaggi di transpiler personalizzati. Inizierà con la costruzione di un semplice circuito e l'esame della sua rappresentazione DAG, quindi esplorerà le operazioni DAG di base e implementerà un passaggio BasicMapper personalizzato.
Costruisci un circuito ed esamina il suo DAG
Il frammento di codice seguente illustra il DAG creando un semplice circuito che prepara uno stato Bell e applica una rotazione , a seconda del risultato della misurazione.
Il codice di questa pagina è stato sviluppato in base ai seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.5.1
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:
Nel DAG ci sono tre tipi di nodi del grafo: nodi di ingresso del qubit/clbit (verdi), nodi di operazione (blu) e nodi di uscita (rossi). Ogni bordo indica il flusso di dati (o la dipendenza) tra due nodi. Utilizzare la funzione qiskit.tools.visualization.dag_drawer () per visualizzare il DAG di questo circuito. (Per eseguire questa operazione, installare la libreria Graphviz )
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)Output:
Operazioni DAG di base
Gli esempi di codice che seguono dimostrano le operazioni più comuni con i DAG, tra cui l'accesso ai nodi, le operazioni di aggiunta e la sostituzione dei sottocircuiti. Queste operazioni costituiscono la base per la costruzione dei passaggi del transpiler.
Ottieni tutti i nodi operativi nel DAG
Il metodo op_nodes() restituisce un elenco iterabile di oggetti DAGOpNode nel circuito:
dag.op_nodes()Output:
[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 0x7fcbf0f00510>, 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>))]
Ogni nodo è un'istanza della classe 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)Output:
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7fcbf0fefbd0>, 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)
Aggiungi un'operazione alla parte posteriore
Un'operazione viene aggiunta alla fine del DAGCircuit con il metodo apply_operation_back() . Aggiunge il gate specificato per agire sui qubit indicati dopo tutte le operazioni esistenti nel circuito.
from qiskit.circuit.library import HGate
dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)Output:
Aggiungi un'operazione alla parte anteriore
Un'operazione viene aggiunta all'inizio del DAGCircuit utilizzando il metodo apply_operation_front() . Questo inserisce il gate specificato prima di tutte le operazioni esistenti nel circuito, rendendolo di fatto la prima operazione eseguita.
from qiskit.circuit.library import CCXGate
dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)Output:
Sostituire un nodo con un sottocircuito
Un nodo che rappresenta un'operazione specifica nel circuito DAG viene sostituito da un sottocircuito. In primo luogo, si costruisce un nuovo sotto-DAG con la sequenza di porte desiderata, quindi si sostituisce il nodo target con questo sotto-DAG utilizzando substitute_node_with_dag(), preservando le connessioni con il resto del circuito.
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:
Una volta completate tutte le trasformazioni, il DAG può essere riconvertito in un normale oggetto QuantumCircuit . Ecco come funziona la pipeline del transpiler. Si prende un circuito, lo si elabora in forma di DAG e si produce in uscita un circuito trasformato.
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")Output:
Implementare un passaggio " BasicMapper "
La struttura del DAG può essere sfruttata per scrivere i passaggi del transpiler. Nell'esempio che segue, viene implementato un passaggio BasicMapper per mappare un circuito arbitrario su un dispositivo con connettività di qubit limitata. Per ulteriori indicazioni, consultare la guida sulla scrittura di passaggi di transpiler personalizzati.
Il passaggio è definito come TransformationPass, cioè modifica il circuito. Lo fa attraversando il DAG strato per strato, verificando se ogni istruzione soddisfa i vincoli imposti dalla mappa di accoppiamento del dispositivo. Se viene rilevata una violazione, viene determinato un percorso di swap e vengono inseriti di conseguenza i gate SWAP necessari.
Quando si crea un passo di transpiler, la prima decisione riguarda la scelta se il passo debba ereditare da TransformationPass o da AnalysisPass. I passaggi di trasformazione sono progettati per modificare il circuito, mentre i passaggi di analisi hanno il solo scopo di estrarre informazioni da utilizzare nei passaggi successivi. La funzionalità principale viene quindi implementata nel metodo run(dag) . Infine, il pass deve essere registrato nel modulo qiskit.transpiler.passes .
In questo passaggio specifico, il DAG viene attraversato strato per strato (dove ogni strato contiene operazioni che agiscono su insiemi disgiunti di qubit e possono quindi essere eseguite indipendentemente). Per ogni operazione, se i vincoli della mappa di accoppiamento non sono soddisfatti, viene identificato un percorso di scambio adeguato e vengono inseriti gli scambi necessari per portare i qubit coinvolti in adiacenza.
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_dagIl passaggio può ora essere testato su un piccolo circuito di esempio. Viene costruito un gestore di pass con il nuovo pass definito. Il circuito di esempio viene quindi fornito a questo pass manager, ottenendo in uscita un nuovo circuito trasformato.
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:
Passi successivi
- Consulta la guida sulla creazione di un passaggio personalizzato del transpiler
- Scopri come creare e compilare utilizzando backend personalizzati
- Prova la guida Confronta le impostazioni del transpiler.
- Consulta la documentazione dell'API DAG Circuit.