Saltar al contenido principal

Utilidades del addon de Qiskit

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Se recomienda usar estas versiones o versiones más recientes.

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

El paquete de utilidades del addon de Qiskit es una colección de funcionalidades para complementar los flujos de trabajo que involucran uno o más addons de Qiskit. Por ejemplo, este paquete contiene funciones para crear Hamiltonianos, generar circuitos de evolución temporal de Trotter, y dividir y combinar circuitos cuánticos.

Instalación

Hay dos formas de instalar las utilidades del addon de Qiskit: desde PyPI o compilando desde el código fuente. Se recomienda instalar estos paquetes en un entorno virtual para garantizar la separación entre las dependencias de los paquetes.

Instalar desde PyPI

La forma más directa de instalar el paquete de utilidades del addon de Qiskit es a través de PyPI.

pip install 'qiskit-addon-utils'

Instalar desde el código fuente

Haz clic aquí para leer cómo instalar este paquete manualmente.

Si deseas contribuir a este paquete o instalarlo manualmente, primero clona el repositorio:

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

e instala el paquete con pip. Si planeas ejecutar los tutoriales que se encuentran en el repositorio del paquete, instala también las dependencias del notebook. Si planeas desarrollar en el repositorio, instala las dependencias dev.

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

Comenzar con las utilidades

Hay varios módulos dentro del paquete qiskit-addon-utils, incluyendo uno para la generación de problemas para la simulación de sistemas cuánticos, coloreo de grafos para colocar puertas de forma más eficiente en un circuito cuántico, y división de circuitos, que puede ayudar con la retropropagación de operadores. Las siguientes secciones resumen cada módulo. La documentación de la API del paquete también contiene información útil.

Generación de problemas

El contenido del módulo qiskit_addon_utils.problem_generators incluye:

  • Una función generate_xyz_hamiltonian(), que genera una representación SparsePauliOp del modelo XYZ de tipo Ising con conciencia de conectividad:

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)

  • Una función generate_time_evolution_circuit(), que construye un circuito que modela la evolución temporal de un operador dado.
  • Tres objetos PauliOrderStrategy diferentes para enumerar entre distintos ordenamientos de cadenas de Pauli. Esto es especialmente útil cuando se usa junto con el coloreo de grafos y puede utilizarse como argumento tanto en las funciones generate_xyz_hamiltonian() como en generate_time_evolution_circuit().

Coloreo de grafos

El módulo qiskit_addon_utils.coloring se utiliza para colorear las aristas de un mapa de acoplamiento y usar este coloreo para colocar puertas de forma más eficiente en un circuito cuántico. El propósito de este mapa de acoplamiento con aristas coloreadas es encontrar un conjunto de colores de aristas tal que no haya dos aristas del mismo color que compartan un nodo común. Para un QPU, esto significa que las puertas a lo largo de aristas del mismo color (conexiones de qubits) pueden ejecutarse simultáneamente y el circuito se ejecutará más rápido.

Como ejemplo rápido, puedes usar la función auto_color_edges() para generar un coloreo de aristas para un circuito simple que ejecuta una CZGate a lo largo de cada conexión de qubits. El fragmento de código a continuación utiliza el mapa de acoplamiento del backend FakeSherbrooke, crea este circuito simple y luego usa la función auto_color_edges() para crear un circuito equivalente más 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

División de circuitos

Por último, el módulo qiskit-addon-utils.slicing contiene funciones y pasadas de transpilación para trabajar con la creación de "segmentos" de circuitos, particiones temporales de un QuantumCircuit que se extienden a través de todos los qubits. Estos segmentos se utilizan principalmente para la retropropagación de operadores. Las cuatro formas principales de dividir un circuito son por tipo de puerta, profundidad, coloreo o instrucciones Barrier. La salida de estas funciones de división devuelve una lista de objetos QuantumCircuit. Los circuitos divididos también pueden recombinarse usando la función combine_slices(). Lee la referencia de la API del módulo para más información.

A continuación se muestran algunos ejemplos de cómo crear estos segmentos usando el siguiente 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)

Output of the previous code cell

En el caso en que no haya una forma clara de aprovechar la estructura de un circuito para la retropropagación de operadores, puedes particionar el circuito en segmentos de una profundidad dada.

# 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)

Output of the previous code cell

En casos como la ejecución de circuitos de Trotter para modelar la dinámica de un sistema cuántico, puede ser ventajoso dividir por tipo de puerta.

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)

Output of the previous code cell

Si tu flujo de trabajo está diseñado para aprovechar la conectividad física de qubits del QPU en el que se ejecutará, puedes crear segmentos basados en el coloreo de aristas. El fragmento de código a continuación asignará un coloreo de tres colores a las aristas del circuito y dividirá el circuito con respecto al coloreo de aristas. (Nota: esto solo afecta a las puertas no locales. Las puertas de un solo qubit se dividirán por tipo de puerta).

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)

Output of the previous code cell

Si tienes una estrategia de división personalizada, puedes en cambio colocar barreras en el circuito para delimitar dónde debe dividirse y usar la función 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)

Output of the previous code cell

Una vez que las barreras están en su lugar, puedes examinar cada uno de los segmentos individualmente.

from qiskit_addon_utils.slicing import slice_by_barriers

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

Output of the previous code cell

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

Output of the previous code cell

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

Output of the previous code cell

Próximos pasos

Recomendaciones