Pular para o conteúdo principal

Utilitários do addon Qiskit

Versões dos pacotes

O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-addon-utils~=0.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-utils qiskit-ibm-runtime

O pacote de utilitários do addon Qiskit é uma coleção de funcionalidades para complementar fluxos de trabalho que envolvem um ou mais addons do Qiskit. Por exemplo, este pacote contém funções para criar Hamiltonianos, gerar circuitos de evolução temporal de Trotter e fatiar e combinar circuitos quânticos.

Instalação

Existem duas formas de instalar os utilitários do addon Qiskit: via PyPI e compilando a partir do código-fonte. É recomendável instalar esses pacotes em um ambiente virtual para garantir a separação entre as dependências dos pacotes.

Instalar via PyPI

A maneira mais simples de instalar o pacote de utilitários do addon Qiskit é via PyPI.

pip install 'qiskit-addon-utils'

Instalar a partir do código-fonte

Clique aqui para ler como instalar este pacote manualmente.

Se você deseja contribuir com este pacote ou instalá-lo manualmente, primeiro clone o repositório:

git clone git@github.com:Qiskit/qiskit-addon-utils.git

e instale o pacote via pip. Se você planeja executar os tutoriais encontrados no repositório do pacote, instale também as dependências de notebook. Se você planeja desenvolver no repositório, instale as dependências de dev.

pip install tox jupyterlab -e '.[notebook-dependencies,dev]'

Introdução aos utilitários

Existem vários módulos dentro do pacote qiskit-addon-utils, incluindo um para geração de problemas na simulação de sistemas quânticos, coloração de grafos para posicionar gates em um circuito quântico de forma mais eficiente, e fatiamento de circuitos, que pode ajudar com a retropropagação de operadores. As seções a seguir resumem cada módulo. A documentação da API do pacote também contém informações úteis.

Geração de problemas

O conteúdo do módulo qiskit_addon_utils.problem_generators inclui:

  • Uma função generate_xyz_hamiltonian(), que gera uma representação SparsePauliOp de um modelo XYZ do tipo Ising com consciência de conectividade:

H=(j,k)E(JxXjXk+JyYjYk+JzZjZk)+jV(hxXj+hyYj+hzZj)H = \sum_{(j,k)\in E} \left(J_x X_jX_k + J_yY_jY_k + J_zZ_jZ_k\right) + \sum_{j\in V} \left(h_x X_j + h_y Y_j + h_z Z_j\right)

  • Uma função generate_time_evolution_circuit(), que constrói um circuito modelando a evolução temporal de um operador fornecido.
  • Três objetos PauliOrderStrategy diferentes para enumerar entre diferentes ordenações de strings de Pauli. Isso é especialmente útil quando usado em conjunto com a coloração de grafos e pode ser utilizado como argumento nas funções generate_xyz_hamiltonian() e generate_time_evolution_circuit().

Coloração de grafos

O módulo qiskit_addon_utils.coloring é usado para colorir as arestas em um mapa de acoplamento e usar essa coloração para posicionar gates em um circuito quântico de forma mais eficiente. O objetivo desse mapa de acoplamento com arestas coloridas é encontrar um conjunto de cores de arestas tal que nenhuma duas arestas da mesma cor compartilhem um nó comum. Para um QPU, isso significa que gates ao longo de arestas da mesma cor (conexões de qubits) podem ser executados simultaneamente, fazendo o circuito rodar mais rápido.

Como exemplo rápido, você pode usar a função auto_color_edges() para gerar uma coloração de arestas para um circuito simples que executa um CZGate em cada conexão de qubit. O trecho de código abaixo usa o mapa de acoplamento do backend FakeSherbrooke, cria esse circuito simples e, em seguida, usa a função auto_color_edges() para criar um circuito equivalente mais eficiente.

from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
from qiskit import QuantumCircuit
from qiskit_addon_utils.coloring import auto_color_edges
from qiskit_addon_utils.slicing import combine_slices, slice_by_depth
from collections import defaultdict

backend = FakeSherbrooke()
coupling_map = backend.coupling_map

# Create naive circuit
circuit = QuantumCircuit(backend.num_qubits)
for edge in coupling_map.graph.edge_list():
circuit.cz(edge[0], edge[1])

# Color the edges of the coupling map
coloring = auto_color_edges(coupling_map)
circuit_with_coloring = QuantumCircuit(backend.num_qubits)

# Make a reverse coloring dict in order to make the circuit
color_to_edge = defaultdict(list)
for edge, color in coloring.items():
color_to_edge[color].append(edge)

# Place edges in order of color
for edges in color_to_edge.values():
for edge in edges:
circuit_with_coloring.cz(edge[0], edge[1])

print(f"The circuit without using edge coloring has depth: {circuit.depth()}")
print(
f"The circuit using edge coloring has depth: {circuit_with_coloring.depth()}"
)
The circuit without using edge coloring has depth: 37
The circuit using edge coloring has depth: 3

Fatiamento

Por fim, o módulo qiskit-addon-utils.slicing contém funções e passes do Transpiler para trabalhar com a criação de "fatias" de circuito — partições temporais de um QuantumCircuit que abrangem todos os qubits. Essas fatias são usadas principalmente para a retropropagação de operadores. As quatro principais formas de fatiar um circuito são: por tipo de gate, por profundidade, por coloração ou por instruções Barrier. A saída dessas funções de fatiamento retorna uma lista de objetos QuantumCircuit. Circuitos fatiados também podem ser recombinados usando a função combine_slices(). Consulte a referência da API do módulo para mais informações.

A seguir estão alguns exemplos de como criar essas fatias usando o seguinte circuito:

import numpy as np
from qiskit import QuantumCircuit

num_qubits = 9
qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
qubits_1 = [i for i in range(num_qubits) if i % 2 == 0]
qubits_2 = [i for i in range(num_qubits) if i % 2 == 1]
qc.cx(qubits_1[:-1], qubits_2)
qc.cx(qubits_2, qubits_1[1:])
qc.cx(qubits_1[-1], qubits_1[0])
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))
qc.draw("mpl", scale=0.6)

Saída da célula de código anterior

No caso em que não há uma forma clara de explorar a estrutura de um circuito para a retropropagação de operadores, você pode particionar o circuito em fatias de uma profundidade específica.

# Slice circuit into partitions of depth 1
slices = slice_by_depth(qc, 1)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Saída da célula de código anterior

Em casos como a execução de circuitos de Trotter para modelar a dinâmica de um sistema quântico, pode ser vantajoso fatiar por tipo de gate.

from qiskit_addon_utils.slicing import slice_by_gate_types

slices = slice_by_gate_types(qc)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Saída da célula de código anterior

Se sua carga de trabalho foi projetada para explorar a conectividade física dos qubits no QPU em que será executada, você pode criar fatias baseadas na coloração de arestas. O trecho de código abaixo atribuirá uma três-coloração às arestas do circuito e fatiará o circuito em relação à coloração de arestas. (Nota: isso afeta apenas gates não locais. Gates de qubit único serão fatiados por tipo de gate.)

from qiskit_addon_utils.slicing import slice_by_coloring

# Assign a color to each set of connected qubits
coloring = {}
for i in range(num_qubits - 1):
coloring[(i, i + 1)] = i % 3
coloring[(num_qubits - 1, 0)] = 2

# Create a circuit with operations added in order of color
qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
edges = [
edge for color in range(3) for edge in coloring if coloring[edge] == color
]
for edge in edges:
qc.cx(edge[0], edge[1])
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))

# Create slices by edge color
slices = slice_by_coloring(qc, coloring=coloring)

# Recombine slices in order to visualize the partitions together
combined_slices = combine_slices(slices, include_barriers=True)
combined_slices.draw("mpl", scale=0.6)

Saída da célula de código anterior

Se você tiver uma estratégia de fatiamento personalizada, pode inserir barriers no circuito para delimitar onde ele deve ser fatiado e usar a função slice_by_barriers.

qc = QuantumCircuit(num_qubits)
qc.ry(np.pi / 4, range(num_qubits))
qc.barrier()
qubits_1 = [i for i in range(num_qubits) if i % 2 == 0]
qubits_2 = [i for i in range(num_qubits) if i % 2 == 1]
qc.cx(qubits_1[:-1], qubits_2)
qc.cx(qubits_2, qubits_1[1:])
qc.cx(qubits_1[-1], qubits_1[0])
qc.barrier()
qc.rx(np.pi / 4, range(num_qubits))
qc.rz(np.pi / 4, range(num_qubits))
qc.draw("mpl", scale=0.6)

Saída da célula de código anterior

Com as barriers posicionadas, você pode examinar cada uma das fatias individualmente.

from qiskit_addon_utils.slicing import slice_by_barriers

slices = slice_by_barriers(qc)
slices[0].draw("mpl", scale=0.6)

Saída da célula de código anterior

slices[1].draw("mpl", scale=0.6)

Saída da célula de código anterior

slices[2].draw("mpl", scale=0.6)

Saída da célula de código anterior

Próximos passos

Recomendações