Kernels cuánticos con puertas fraccionarias
Estimación de uso: menos de 30 segundos en un procesador Heron r2 (NOTA: Esto es solo una estimación. Tu tiempo de ejecución puede variar.)
Resultados de aprendizaje
-
Qué son las puertas fraccionarias y cómo reducen la profundidad y duración del circuito en las QPU de IBM®
-
Las restricciones asociadas con el uso de puertas fraccionarias (en particular, el rango de ángulo RZZ)
-
Cómo construir un flujo de trabajo de kernel cuántico que utilice puertas fraccionarias con IBM Quantum Compute Service
-
Cómo comparar las métricas de ejecución de hardware (profundidad, duración, recuento de puertas no locales, fidelidad) con y sin puertas fraccionarias
-
Cómo usar únicamente puertas RX fraccionarias mientras se mantiene el flujo de trabajo estándar de patrones de Qiskit
Prerrequisitos
-
El flujo de trabajo de patrones de Qiskit
-
La guía de Puertas fraccionarias
-
El tutorial de Entrenamiento de kernel cuántico y la lección Kernels cuánticos del curso de Aprendizaje automático cuántico
Antecedentes
Puertas fraccionarias en las QPU de IBM
Las puertas fraccionarias son puertas cuánticas parametrizadas que permiten la ejecución directa de rotaciones de ángulo arbitrario (dentro de límites específicos), eliminando la necesidad de descomponerlas en múltiples puertas base. Al aprovechar las interacciones nativas entre qubits físicos, puedes implementar ciertas unitarias de manera más eficiente en el hardware.
Las QPU IBM Quantum® Heron admiten las siguientes puertas fraccionarias:
- para
- para cualquier valor real
Estas puertas pueden reducir significativamente tanto la profundidad como la duración de los circuitos cuánticos. Son particularmente ventajosas en aplicaciones que dependen en gran medida de y , como la simulación hamiltoniana, el Algoritmo de Optimización Aproximada Cuántica (QAOA) y los métodos de kernel cuántico. En este tutorial, nos centramos en el kernel cuántico como ejemplo práctico.
Limitaciones
Las puertas fraccionarias son actualmente una característica experimental y presentan algunas restricciones:
- está limitado a ángulos en el rango .
- El uso de puertas fraccionarias no es compatible con circuitos dinámicos, Pauli twirling, cancelación probabilística de errores (PEC), ni con extrapolación a ruido cero (ZNE) (usando amplificación probabilística de errores (PEA)).
Las puertas fraccionarias requieren un flujo de trabajo diferente en comparación con el enfoque estándar. Este tutorial explica cómo trabajar con puertas fraccionarias a través de una aplicación práctica.
Consulta los siguientes recursos para obtener más detalles sobre las puertas fraccionarias.
Enfoques de flujo de trabajo para la restricción de ángulo RZZ
El flujo de trabajo para usar puertas fraccionarias generalmente sigue el flujo de trabajo de patrones de Qiskit. La diferencia clave es que todos los ángulos RZZ deben satisfacer la restricción . Existen dos enfoques para garantizar que se cumpla esta condición, como se discute a continuación. Recomendamos el segundo enfoque, y en este tutorial, lo demostramos a través de un ejemplo inspirado en el método de kernel cuántico. Para comprender mejor dónde es probable que los kernels cuánticos sean útiles, recomendamos leer Liu, Arunachalam & Temme (2021).
También puede seguir el tutorial Entrenamiento de kernel cuántico y la lección Kernels cuánticos en el curso de Aprendizaje automático cuántico en IBM Quantum® Learning.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_basis_constructor": "qiskit-basis-constructor", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
1. Generar valores de parámetros que satisfagan la restricción de ángulo RZZ
Si tienes la certeza de que todos los ángulos RZZ se encuentran dentro del rango válido, puedes seguir el flujo de trabajo estándar de patrones de Qiskit. En este caso, simplemente envíe los valores de los parámetros como parte de un PUB. El flujo de trabajo procede de la siguiente manera.
pm = generate_preset_pass_manager(backend=backend, ...)
t_circuit = pm.run(circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit, parameter_values)])
estimator.run([(t_circuit, t_observable, parameter_values)])
Si intenta enviar un PUB que incluye una puerta RZZ con un ángulo fuera del rango válido, encontrarás un mensaje de error como el siguiente:
'The instruction rzz is supported only for angles in the range [0, pi/2], but an angle (20.0) outside of this range has been requested; via parameter value(s) γ[0]=10.0, substituted in parameter expression 2.0*γ[0].'
Para evitar este error, usa el segundo enfoque descrito a continuación.
2. Asignar valores de parámetros a los circuitos antes de la transpilación
El paquete qiskit-ibm-runtime proporciona un paso de transpilación especializado llamado FoldRzzAngle.
Este paso transforma los circuitos cuánticos para que todos los ángulos RZZ cumplan con la restricción de ángulo RZZ.
Si proporcionas el backend a generate_preset_pass_manager o transpile, Qiskit aplica automáticamente FoldRzzAngle a los circuitos cuánticos.
Este enfoque requiere que asignes los valores de los parámetros a los circuitos cuánticos antes de la transpilación.
El flujo de trabajo procede de la siguiente manera.
pm = generate_preset_pass_manager(backend=backend, ...)
b_circuit = circuit.assign_parameters(parameter_values)
t_circuit = pm.run(b_circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit,)])
estimator.run([(t_circuit, t_observable)])
Ten en cuenta que este flujo de trabajo conlleva un costo computacional más alto que el primer enfoque, ya que implica asignar valores de parámetros a circuitos cuánticos y almacenar localmente los circuitos con parámetros vinculados.
Ten en cuenta un problema conocido en qiskit-ibm-runtime v0.47.0 donde las puertas RZZ con ángulos inválidos podrían permanecer en los circuitos incluso después de la transpilación en ciertos escenarios.
Consulta qiskit-ibm-runtime#2441 para hacer seguimiento del progreso de este problema. Recomendamos la siguiente solución alternativa hasta que se resuelva.
pm = generate_preset_pass_manager(backend=backend, ...)
pm.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend.target),
RemoveIdentityEquivalent(target=backend.target),
]
)
... = pm.run(...)
Requisitos
Antes de comenzar este tutorial, asegúrate de tener instalado lo siguiente:
- Qiskit SDK v2.0 o posterior, con soporte de visualización
- Qiskit Runtime v0.41 o posterior (
pip install qiskit-ibm-runtime) - Qiskit Aer v0.17 o posterior (
pip install qiskit-aer) - Qiskit Basis Constructor (
pip install qiskit_basis_constructor)
Configuración
import matplotlib.pyplot as plt
import numpy as np
from qiskit import QuantumCircuit, generate_preset_pass_manager
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import UGate, n_local, unitary_overlap
from qiskit.transpiler import Target, PassManager
from qiskit.transpiler.passes import (
Optimize1qGatesDecomposition,
RemoveIdentityEquivalent,
)
from qiskit_aer.primitives import SamplerV2 as AerSampler
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
from qiskit_ibm_runtime.transpiler.passes import FoldRzzAngle
Habilitar puertas fraccionarias y verificar las puertas base
Para usar puertas fraccionarias, puede obtener un backend que las admita estableciendo la opción use_fractional_gates=True.
Si el backend admite puertas fraccionarias, verás rzz y rx listadas entre sus puertas base.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
) # backend should be a heron device or later
backend_name = backend.name
backend_c = service.backend(backend_name) # w/o fractional gates
backend_f = service.backend(
backend_name, use_fractional_gates=True
) # w/ fractional gates
print(f"Backend: {backend_name}")
print(f"No fractional gates: {backend_c.basis_gates}")
print(f"With fractional gates: {backend_f.basis_gates}")
if "rzz" not in backend_f.basis_gates:
print(f"Backend {backend_name} does not support fractional gates")
Backend: ibm_marrakesh
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']
Ejemplo de simulador a pequeña escala
En esta sección, recorremos los cuatro pasos del flujo de trabajo de patrones de Qiskit en un simulador, utilizando el circuito de kernel cuántico como ejemplo práctico.
Paso 1: Mapear entradas clásicas a un problema cuántico
Circuito de kernel cuántico
En esta sección, exploramos el circuito de kernel cuántico utilizando puertas RZZ para presentar el flujo de trabajo de las puertas fraccionarias.
Comenzamos construyendo un circuito cuántico para calcular entradas individuales de la matriz de kernel. Esto se realiza combinando circuitos de mapa de características ZZ con un solapamiento unitario (unitary overlap). La función de kernel toma vectores en el espacio mapeado por características y devuelve su producto interno como una entrada de la matriz de kernel: donde representa el estado cuántico mapeado por características.
Construimos manualmente un circuito de mapa de características ZZ utilizando puertas RZZ.
Aunque Qiskit proporciona un zz_feature_map incorporado, actualmente no admite puertas RZZ a partir de Qiskit v2.4.1 (consulta el issue).
A continuación, calculamos la función de kernel para entradas idénticas, por ejemplo, . En computadoras cuánticas ruidosas, este valor puede ser menor que 1 debido al ruido. Un resultado más cercano a 1 indica menor ruido en la ejecución. En este tutorial, nos referimos a este valor como la fidelidad, definida como
optimization_level = 2
shots = 2000
reps = 3
rng = np.random.default_rng(seed=123)
def my_zz_feature_map(num_qubits: int, reps: int = 1) -> QuantumCircuit:
x = ParameterVector("x", num_qubits * reps)
qc = QuantumCircuit(num_qubits)
qc.h(range(num_qubits))
for k in range(reps):
K = k * num_qubits
for i in range(num_qubits):
qc.rz(x[i + K], i)
pairs = [(i, i + 1) for i in range(num_qubits - 1)]
for i, j in pairs[0::2] + pairs[1::2]:
qc.rzz((np.pi - x[i + K]) * (np.pi - x[j + K]), i, j)
return qc
def quantum_kernel(num_qubits: int, reps: int = 1) -> QuantumCircuit:
qc = my_zz_feature_map(num_qubits, reps=reps)
inner_product = unitary_overlap(qc, qc, "x", "y", insert_barrier=True)
inner_product.measure_all()
return inner_product
def random_parameters(inner_product: QuantumCircuit) -> np.ndarray:
return np.tile(rng.random(inner_product.num_parameters // 2), 2)
def fidelity(result) -> float:
ba = result.data.meas
return ba.get_int_counts().get(0, 0) / ba.num_shots
Se generan circuitos de kernel cuántico y sus correspondientes valores de parámetros para sistemas de 4 a 40 qubits, y posteriormente se evalúan sus fidelidades.
qubits = list(range(4, 12, 2))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]
El circuito de cuatro qubits se visualiza a continuación.
circuits[0].draw("mpl", fold=-1)
En el flujo de trabajo estándar de patrones de Qiskit, los valores de los parámetros se pasan típicamente al primitivo Sampler o Estimator como parte de un PUB. Sin embargo, al usar un backend que admite puertas fraccionarias, estos valores de parámetros deben asignarse explícitamente al circuito cuántico antes de la transpilación.
b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
b_qc[0].draw("mpl", fold=-1)
Paso 2: Optimizar el problema para la ejecución en hardware cuántico
Luego transpilamos el circuito utilizando el gestor de pases siguiendo el patrón estándar de Qiskit.
Al proporcionar un backend que admite puertas fraccionarias a generate_preset_pass_manager, se incluye automáticamente un paso especializado llamado FoldRzzAngle.
Este paso modifica el circuito para cumplir con las restricciones de ángulo RZZ.
Como resultado, las puertas RZZ con valores negativos en la figura anterior se transforman en valores positivos, y se agregan algunas puertas X adicionales.
backend_f = service.backend(name=backend_name, use_fractional_gates=True)
# pm_f includes `FoldRzzAngle` pass
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_f
)
pm_f.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend_f.target),
RemoveIdentityEquivalent(target=backend_f.target),
]
)
t_qc_f = pm_f.run(b_qc)
print(t_qc_f[0].count_ops())
t_qc_f[0].draw("mpl", fold=-1)
OrderedDict({'rz': 35, 'rzz': 18, 'x': 13, 'rx': 9, 'measure': 4, 'barrier': 2})
Para evaluar el impacto de las puertas fraccionarias, evaluamos el número de puertas no locales (CZ y RZZ para este backend), junto con las profundidades y duraciones de los circuitos, y comparamos estas métricas con las de un flujo de trabajo estándar más adelante.
nnl_f = [qc.num_nonlocal_gates() for qc in t_qc_f]
depth_f = [qc.depth() for qc in t_qc_f]
duration_f = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc_f
]
Paso 3: Ejecutar utilizando los primitivos de Qiskit
Ejecutamos el circuito transpilado con el backend que admite puertas fraccionarias.
sampler_f = AerSampler.from_backend(backend_f)
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())
085ce928-767e-4200-93bf-3905e5411cfe
Paso 4: Posprocesar y devolver el resultado en el formato clásico deseado
Puedes obtener el valor de la función de kernel midiendo la probabilidad de la cadena de bits de todos ceros 00...00 en la salida.
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
[0.929, 0.882, 0.8645, 0.817]
Comparación del flujo de trabajo y circuito sin puertas fraccionarias
En esta sección, presentamos el flujo de trabajo estándar de patrones de Qiskit utilizando un backend que no admite puertas fraccionarias. Al comparar los circuitos transpilados, notarás que la versión que usa puertas fraccionarias (de la sección anterior) es más compacta que la que no las usa.
# step 1: map classical inputs to quantum problem
# `circuits` and `params` from the previous section are reused here
# step 2: optimize circuits
backend_c = service.backend(backend_name) # w/o fractional gates
pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc_c = pm_c.run(circuits)
print(t_qc_c[0].count_ops())
t_qc_c[0].draw("mpl", fold=-1)
OrderedDict({'rz': 130, 'sx': 80, 'cz': 36, 'measure': 4, 'barrier': 2})
nnl_c = [qc.num_nonlocal_gates() for qc in t_qc_c]
depth_c = [qc.depth() for qc in t_qc_c]
duration_c = [
qc.estimate_duration(backend_c.target, unit="u") for qc in t_qc_c
]
# step 3: execute
sampler_c = AerSampler.from_backend(backend_c)
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())
f2cca29d-7263-4976-9e51-13a91b75c3ae
# step 4: post-processing
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)
[0.8625, 0.7605, 0.702, 0.671]
Comparación de profundidades, duraciones y fidelidades
En esta sección, comparamos el número de puertas no locales y las fidelidades entre circuitos con y sin puertas fraccionarias. Esto destaca los beneficios potenciales del uso de puertas fraccionarias en términos de eficiencia de ejecución y calidad.
plt.plot(qubits, depth_c, "-o", label="no fractional gates")
plt.plot(qubits, depth_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("depth")
plt.title("Comparison of depths")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x116af3cb0>
plt.plot(qubits, duration_c, "-o", label="no fractional gates")
plt.plot(qubits, duration_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("duration (µs)")
plt.title("Comparison of durations")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x11ea4f4d0>
plt.plot(qubits, nnl_c, "-o", label="no fractional gates")
plt.plot(qubits, nnl_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("number of non-local gates")
plt.title("Comparison of numbers of non-local gates")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x1247fc440>
plt.plot(qubits, fidelity_c, "-o", label="no fractional gates")
plt.plot(qubits, fidelity_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("fidelity")
plt.title("Comparison of fidelities")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x120b792b0>
Ejemplo de hardware a gran escala
En esta sección, comparamos el flujo de trabajo de kernel cuántico con y sin puertas fraccionarias en hardware cuántico con hasta 40 qubits.
Pasos 1-4 combinados
El flujo de trabajo sigue la misma estructura que el ejemplo a pequeña escala. Transpilamos todos los circuitos con y sin puertas fraccionarias, recopilamos métricas y luego enviamos los circuitos al hardware cuántico real.
# -------------------------Step 1-------------------------
qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]
b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
def benchmark(b_qc, backend):
# -------------------------Step 2-------------------------
pm = generate_preset_pass_manager(optimization_level, backend=backend)
if "rzz" in backend.target.operation_names:
# workaround until https://github.com/Qiskit/qiskit-ibm-runtime/issues/2441 is resolved
pm.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend.target),
RemoveIdentityEquivalent(target=backend.target),
]
)
t_qc = pm.run(b_qc)
nnl = [qc.num_nonlocal_gates() for qc in t_qc]
depth = [qc.depth() for qc in t_qc]
duration = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc
]
# -------------------------Step 3-------------------------
sampler = SamplerV2(mode=backend)
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.dynamical_decoupling.skip_reset_qubits = True
sampler.options.environment.job_tags = ["TUT_FG"]
job = sampler.run(t_qc, shots=shots)
job_id = job.job_id()
return nnl, depth, duration, job_id
def postprocessing(job_id: str):
# -------------------------Step 4-------------------------
job = service.job(job_id)
result = job.result()
fidelities = [fidelity(result=res) for res in result]
usage = job.usage()
return fidelities, usage
backend_f = service.backend(backend_name, use_fractional_gates=True)
nnl_f, depth_f, duration_f, job_id_f = benchmark(
b_qc, backend_f
) # step 2 & 3
print("job id (w/ fractional gates):", job_id_f)
fidelity_f, usage_f = postprocessing(job_id_f) # step 4
job id (w/ fractional gates): d8uasitbh0os73eqnpig
backend_c = service.backend(backend_name, use_fractional_gates=False)
nnl_c, depth_c, duration_c, job_id_c = benchmark(b_qc, backend_c)
print("job id (w/o fractional gates):", job_id_c)
fidelity_c, usage_c = postprocessing(job_id_c)
job id (w/o fractional gates): d8uav3lposuc738pruug
Luego comparamos las métricas.
plt.plot(qubits, depth_c, "-o", label="no fractional gates")
plt.plot(qubits, depth_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("depth")
plt.title("Comparison of depths")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12461e660>
plt.plot(qubits, duration_c, "-o", label="no fractional gates")
plt.plot(qubits, duration_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("duration (µs)")
plt.title("Comparison of durations")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x11f2ac980>
plt.plot(qubits, nnl_c, "-o", label="no fractional gates")
plt.plot(qubits, nnl_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("number of non-local gates")
plt.title("Comparison of numbers of non-local gates")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x125c91be0>
plt.plot(qubits, fidelity_c, "-o", label="no fractional gates")
plt.plot(qubits, fidelity_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("fidelity")
plt.title("Comparison of fidelities")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x11fcf6e40>
Comparamos el tiempo de uso de la QPU con y sin puertas fraccionarias. Los resultados en la siguiente celda muestran que los tiempos de uso de la QPU son casi idénticos.
print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 8 seconds
fractional gates: 8 seconds
Tema avanzado: Uso exclusivo de puertas RX fraccionarias
La necesidad del flujo de trabajo modificado al usar puertas fraccionarias proviene principalmente de la restricción en los ángulos de las puertas RZZ. Sin embargo, si utilizas únicamente las puertas RX fraccionarias y excluyes las puertas RZZ fraccionarias, puedes continuar siguiendo el flujo de trabajo estándar de patrones de Qiskit. Este enfoque puede ofrecer beneficios significativos, particularmente en circuitos que involucran una gran cantidad de puertas RX y puertas U, al reducir el conteo total de puertas y potencialmente mejorar el rendimiento. En esta sección, demostramos cómo optimizar tus circuitos utilizando únicamente puertas RX fraccionarias, omitiendo las puertas RZZ.
Para respaldar esto, proporcionamos una función utilitaria que te permite deshabilitar una puerta base específica en un objeto Target. Aquí la usamos para deshabilitar las puertas RZZ.
def remove_instruction_from_target(target: Target, gate_name: str) -> Target:
new_target = Target(
description=target.description,
num_qubits=target.num_qubits,
dt=target.dt,
granularity=target.granularity,
min_length=target.min_length,
pulse_alignment=target.pulse_alignment,
acquire_alignment=target.acquire_alignment,
qubit_properties=target.qubit_properties,
concurrent_measurements=target.concurrent_measurements,
)
for name, qarg_map in target.items():
if name == gate_name:
continue
instruction = target.operation_from_name(name)
if qarg_map == {None: None}:
qarg_map = None
new_target.add_instruction(instruction, qarg_map, name=name)
return new_target
Usamos un circuito compuesto por puertas U, CZ y RZZ como ejemplo.
qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")
Primero transpilamos el circuito para un backend que no admite puertas fraccionarias.
pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc = pm_c.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict({'rz': 23, 'sx': 16, 'cz': 4})
Luego, transpilamos el mismo circuito utilizando puertas RX fraccionarias, excluyendo las puertas RZZ. Esto resulta en una ligera reducción del conteo total de puertas, gracias a la implementación más eficiente de las puertas RX.
backend_f = service.backend(backend_name, use_fractional_gates=True)
target = remove_instruction_from_target(backend_f.target, "rzz")
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict({'rz': 22, 'sx': 14, 'cz': 4, 'rx': 1})
Optimizar puertas U con puertas RX fraccionarias
En esta sección, demostramos cómo optimizar puertas U usando puertas RX fraccionarias, basándonos en el mismo circuito presentado en la sección anterior. Transpilamos el circuito utilizando únicamente puertas RX fraccionarias, excluyendo las puertas RZZ. Al introducir una regla de descomposición personalizada, como se muestra a continuación, podemos reducir el número de puertas de un solo qubit necesarias para implementar una puerta U.
Esta característica está actualmente en discusión en este issue de GitHub.
# special decomposition rule for UGate
x = ParameterVector("x", 3)
zxz = QuantumCircuit(1)
zxz.rz(x[2] - np.pi / 2, 0)
zxz.rx(x[0], 0)
zxz.rz(x[1] + np.pi / 2, 0)
DEFAULT_EQUIVALENCE_LIBRARY.add_equivalence(UGate(x[0], x[1], x[2]), zxz)
A continuación, aplicamos el transpilador utilizando la traducción constructor-beta proporcionada por el paquete qiskit-basis-constructor.
Como resultado, el número total de puertas se reduce en comparación con la transpilación anterior.
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
translation_method="constructor-beta",
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict({'rz': 16, 'rx': 9, 'cz': 4})
Próximos pasos
Si encontraste este trabajo interesante, podría interesarte el siguiente material:
- Guía de Puertas fraccionarias
- Cuándo no usar puertas fraccionarias
- Referencia de API del paso de transpilación
FoldRzzAngle - El tutorial de Entrenamiento de kernel cuántico
- La lección Kernels cuánticos del curso de Aprendizaje automático cuántico





