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ónSparsePauliOpdel modelo XYZ de tipo Ising con conciencia de conectividad:
- Una función
generate_time_evolution_circuit(), que construye un circuito que modela la evolución temporal de un operador dado. - Tres objetos
PauliOrderStrategydiferentes 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 funcionesgenerate_xyz_hamiltonian()como engenerate_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)
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)
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)
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)
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)
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)
slices[1].draw("mpl", scale=0.6)
slices[2].draw("mpl", scale=0.6)
Próximos pasos
- Lee el resumen del addon OBP.
- Entiende cómo funciona el addon SQD.
- Familiarízate con el addon AQC-Tensor.