Skip to main content
IBM Quantum Platform

Trabalhe com DAGs em passagens do transpiler

No Qiskit, nos estágios de transpilação, os circuitos são representados usando um DAG. Em geral, um DAG é composto de vértices (também conhecidos como "nós") e bordas direcionadas que conectam pares de vértices em uma orientação específica. Essa representação é armazenada usando objetos qiskit.dagcircuit.DAGCircuit que são compostos de objetos DagNode individuais. A vantagem dessa representação em relação a uma lista pura de portas (ou seja, uma lista de rede) é que o fluxo de informações entre as operações é explícito, o que facilita a tomada de decisões de transformação.

Este guia demonstra como trabalhar com DAGs e usá-los para escrever passagens personalizadas do transpilador. Ele começará com a construção de um circuito simples e examinará sua representação DAG, depois explorará as operações DAG básicas e implementará uma passagem BasicMapper personalizada.


Construa um circuito e examine seu DAG

O trecho de código abaixo ilustra o DAG criando um circuito simples que prepara um estado Bell e aplica uma rotação RZR_Z, dependendo do resultado da medição.

  • O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos o uso dessas versões ou de versões mais recentes.

    qiskit[all]~=2.5.0
    
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:

Output of the previous code cell

No DAG, há três tipos de nós gráficos: nós de entrada de qubit/clbit (verde), nós de operação (azul) e nós de saída (vermelho). Cada borda indica o fluxo de dados (ou dependência) entre dois nós. Use a função qiskit.tools.visualization.dag_drawer () para visualizar o DAG desse circuito. (Instale a biblioteca do Graphviz para executá-lo.)

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

Output:

Output of the previous code cell

Operações básicas do DAG

Os exemplos de código abaixo demonstram operações comuns com DAGs, incluindo acesso a nós, adição de operações e substituição de subcircuitos. Essas operações formam a base para a criação de passagens de transpiladores.


Obter todos os nós de operação no DAG

O método op_nodes() retorna uma lista iterável de objetos DAGOpNode no 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 0x7f703fe0bd50>, 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>))]

Cada nó é uma instância da 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 0x7f703facdb10>, 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)

Adicionar uma operação à parte traseira

Uma operação é adicionada ao final do DAGCircuit usando o método apply_operation_back() . Isso acrescenta a porta especificada para atuar nos qubits fornecidos após todas as operações existentes no circuito.

from qiskit.circuit.library import HGate

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

Output:

Output of the previous code cell

Adicionar uma operação à frente

Uma operação é adicionada ao início do DAGCircuit usando o método apply_operation_front() . Isso insere a porta especificada antes de todas as operações existentes no circuito, tornando-a efetivamente a primeira operação executada.

from qiskit.circuit.library import CCXGate

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

Output:

Output of the previous code cell

Substitua um nó por um subcircuito

Um nó que representa uma operação específica no DAGCircuit é substituído por um subcircuito. Primeiro, um novo sub-DAG é construído com a sequência desejada de portas e, em seguida, o nó de destino é substituído por esse sub-DAG usando substitute_node_with_dag(), preservando as conexões com o restante do 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:

Output of the previous code cell

Após a conclusão de todas as transformações, o DAG pode ser convertido novamente em um objeto QuantumCircuit normal. É assim que o pipeline do transpilador funciona. Um circuito é obtido, processado na forma de DAG, e um circuito transformado é produzido como saída.

from qiskit.converters import dag_to_circuit

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

Output:

Output of the previous code cell

Implementar uma passagem “ BasicMapper ”

A estrutura do DAG pode ser aproveitada para escrever passagens do transpilador. No exemplo abaixo, uma passagem BasicMapper é implementada para mapear um circuito arbitrário em um dispositivo com conectividade de qubit restrita. Para obter orientação adicional, consulte o guia sobre como escrever passes de transpiladores personalizados.

O passe é definido como um TransformationPass, o que significa que ele modifica o circuito. Ele faz isso percorrendo o DAG camada por camada, verificando se cada instrução satisfaz as restrições impostas pelo mapa de acoplamento do dispositivo. Se uma violação for detectada, um caminho de troca é determinado e as portas SWAP necessárias são inseridas de acordo.

Ao criar uma passagem de transpilador, a primeira decisão envolve selecionar se a passagem deve herdar de TransformationPass ou AnalysisPass. As passagens de transformação são projetadas para modificar o circuito, enquanto as passagens de análise destinam-se apenas a extrair informações para uso em passagens subsequentes. A funcionalidade principal é então implementada no método run(dag) . Por fim, o passe deve ser registrado no módulo qiskit.transpiler.passes .

Nessa passagem específica, o DAG é percorrido camada por camada (em que cada camada contém operações que agem em conjuntos de qubits separados e, portanto, podem ser executadas independentemente). Para cada operação, se as restrições do mapa de acoplamento não forem atendidas, um caminho de troca adequado é identificado e as trocas necessárias são inseridas para colocar os qubits envolvidos em adjacência.

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

A passagem pode agora ser testada em um pequeno circuito de exemplo. Um gerenciador de passes é construído com o passe recém-definido incluído. O circuito de exemplo é então fornecido a esse gerenciador de passagem, e um circuito novo e transformado é obtido como saída.

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:

Output of the previous code cell

Próximas etapas

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.