Skip to main content
IBM Quantum Platform

Núcleos cuánticos con puertas fraccionarias

Estimación de uso: menos de 30 segundos en un procesador Heron r2 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)


Resultados del aprendizaje

Una vez que hayas completado este tutorial, deberías haber comprendido lo siguiente:

  • Qué son las puertas fraccionarias y cómo reducen la profundidad y la duración de los circuitos en las QPU de IBM®
  • Las limitaciones asociadas al uso de puertas fraccionarias (en concreto, el rango de ángulos RZZ)
  • Cómo crear un flujo de trabajo de «quantum kernel» que utilice puertas fraccionarias con « Qiskit Runtime »
  • Cómo comparar métricas de ejecución en hardware (profundidad, duración, recuento de puertas no locales, fidelidad) con y sin puertas fraccionarias
  • Cómo utilizar únicamente puertas RX fraccionarias sin alterar el flujo de trabajo estándar de Qiskit Patterns

Requisitos previos

Te recomendamos que te familiarices con los siguientes temas antes de seguir este tutorial:


En segundo plano

Puertas fraccionarias en QPU de IBM

Las puertas fraccionarias son puertas cuánticas parametrizadas que permiten la ejecución directa de rotaciones de ángulo arbitrario (dentro de unos límites específicos), lo que elimina la necesidad de descomponerlas en varias puertas básicas. Al aprovechar las interacciones nativas entre los qubits físicos, es posible implementar ciertos operadores unitarios de forma más eficiente en el hardware.

IBM Las QPUs Quantum® Heron admiten las siguientes puertas fraccionarias:

  • RZZ(θ)R_{ZZ}(\theta) para 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) para cualquier valor real θ\theta

Estas puertas pueden reducir significativamente tanto la profundidad como la duración de los circuitos cuánticos. Son especialmente ventajosos en aplicaciones que dependen en gran medida de RZZR_{ZZ} y RXR_X, como la simulación hamiltoniana, el algoritmo de optimización aproximada cuántica (QAOA) y los métodos de núcleo cuántico. En este tutorial, nos centraremos en el núcleo cuántico como ejemplo práctico.

Limitaciones

Las puertas fraccionarias son actualmente una función experimental y tienen algunas limitaciones:

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.

Para más información sobre las puertas fraccionarias, consulte lo siguiente.

Enfoques de flujo de trabajo para la restricción del ángulo RZZ

El flujo de trabajo para utilizar puertas fraccionarias suele seguir el flujo de trabajo de patrones de Qiskit. La diferencia fundamental es que todos los ángulos RZZ deben cumplir la restricción « 0<θπ/20 < \theta \leq \pi/2 ». Existen dos enfoques para garantizar que se cumpla esta condición, tal y como se explica a continuación. Recomendamos el segundo enfoque y, en este tutorial, lo ilustramos mediante un ejemplo inspirado en el método del núcleo cuántico. Para comprender mejor en qué contextos pueden resultar útiles los núcleos cuánticos, recomendamos leer el artículo de Liu, Arunachalam y Temme (2021).

También puedes seguir el tutorial de formación sobre kernels cuánticos y la lección sobre kernels cuánticos del curso de aprendizaje automático cuántico en IBM Quantum® Learning.

1. Generar valores de parámetros que satisfagan la restricción del ángulo RZZ

Si está seguro de que todos los ángulos RZZ se encuentran dentro del intervalo válido, puede seguir el flujo de trabajo estándar de los patrones Qiskit. En este caso, basta con enviar los valores de los parámetros como parte de PUB. El flujo de trabajo es el siguiente.

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 incluya una puerta RZZ con un ángulo fuera del intervalo válido, aparecerá 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, utiliza el segundo método que se describe a continuación.

2. Asignar valores de parámetros a los circuitos antes de la transpilación

El qiskit-ibm-runtime paquete ofrece una pasada de transpilador especializada denominada FoldRzzAngle. Esta pasada transforma los circuitos cuánticos de tal forma que todos los ángulos RZZ cumplan la restricción de ángulo RZZ. Si proporcionas el backend a generate_preset_pass_manager o transpile, Qiskit aplica FoldRzzAngle automáticamente a los circuitos cuánticos. Este enfoque requiere que se asignen valores a los parámetros de los circuitos cuánticos antes de la transpilación. El proceso se desarrolla 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)])

Cabe señalar que este flujo de trabajo conlleva un mayor coste computacional que el primer enfoque, ya que implica asignar valores a los parámetros de los circuitos cuánticos y almacenar localmente los circuitos con los parámetros definidos.

Caution

Tome nota de un problema conocido en qiskit-ibm-runtime v0.47.0 donde las puertas RZZ con ángulos no válidos podrían permanecer en los circuitos incluso después de la transpilación en ciertos escenarios.

Consulta qiskit-ibm-runtime#2441 para seguir la evolución de este asunto. Recomendamos la siguiente solución provisional hasta que se resuelva el problema.

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 empezar 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)
  • Constructor de bases Qiskit (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 comprobar puertas básicas

Para utilizar puertas fraccionarias, puede obtener un backend que las admita configurando la opción use_fractional_gates=True . Si el backend soporta puertas fraccionarias, verá 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")

Output:

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, repasamos los cuatro pasos del flujo de trabajo de Qiskit Patterns en un simulador, utilizando el circuito del núcleo cuántico como ejemplo práctico.

Paso 1: Asignar entradas clásicas a un problema cuántico

Circuito de núcleo cuántico

En esta sección, exploramos el circuito de núcleo cuántico utilizando puertas RZZ para introducir el flujo de trabajo para puertas fraccionarias.

Comenzamos construyendo un circuito cuántico para calcular las entradas individuales de la matriz del núcleo. Para ello, se combinan circuitos de mapas de características ZZ con una superposición unitaria. La función kernel toma vectores en el espacio mapeado de características y devuelve su producto interno como una entrada de la matriz kernel: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, donde Φ(x)|\Phi(x)\rangle representa el estado cuántico mapeado.

Construimos manualmente un circuito de mapa de características ZZ utilizando puertas RZZ. zz_feature_mapAunque Qiskit ofrece una función integrada, actualmente no es compatible con las puertas RZZ, según se indica en Qiskit v2.4.1 ( véase el problema ).

A continuación, calculamos la función kernel para entradas idénticas, por ejemplo, K(x,x)=1K(x, x) = 1. En ordenadores cuánticos ruidosos, este valor puede ser inferior a 1 debido al ruido. Un resultado cercano a 1 indica menor ruido en la ejecución. En este tutorial, nos referiremos a este valor como la fidelidad, definida como fidelity=K(x,x).\text{fidelity} = K(x, x).

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 núcleos cuánticos 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)

Output:

Output of the previous code cell

En el flujo de trabajo estándar de los patrones Qiskit, los valores de los parámetros suelen pasarse a la primitiva Sampler o Estimator como parte de un PUB. Sin embargo, cuando se utiliza 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)

Output:

Output of the previous code cell

Paso 2: Optimizar el problema para la ejecución en hardware cuántico

A continuación, transpilamos el circuito utilizando el gestor de pases siguiendo el patrón estándar de Qiskit. Al proporcionar un backend que admita puertas fraccionarias a generate_preset_pass_manager, se incluye automáticamente un pase especializado denominado 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 añaden 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)

Output:

OrderedDict({'rz': 35, 'rzz': 18, 'x': 13, 'rx': 9, 'measure': 4, 'barrier': 2})
Output of the previous code cell

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 posterior.

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 Qiskit primitives

Ejecutamos el circuito transpilado con el backend que soporta puertas fraccionarias.

sampler_f = AerSampler.from_backend(backend_f)
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())

Output:

085ce928-767e-4200-93bf-3905e5411cfe

Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado

Puede obtener el valor de la función kernel K(x,x)K(x, x) midiendo la probabilidad de la cadena de bits completamente nula 00...00 en la salida.

result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)

Output:

[0.929, 0.882, 0.8645, 0.817]

Comparación del flujo de trabajo y el circuito sin puertas fraccionarias

En esta sección, presentamos el flujo de trabajo estándar de Qiskit Patterns utilizando un backend que no admite puertas fraccionarias. Al comparar los circuitos transpilados, observarás que la versión que utiliza puertas fraccionarias (de la sección anterior) es más compacta que la que no las utiliza.

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

Output:

OrderedDict({'rz': 130, 'sx': 80, 'cz': 36, 'measure': 4, 'barrier': 2})
Output of the previous code cell
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())

Output:

f2cca29d-7263-4976-9e51-13a91b75c3ae
# step 4: post-processing
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)

Output:

[0.8625, 0.7605, 0.702, 0.671]

Comparación de la profundidad, la duración y la fidelidad

En esta sección, comparamos el número de puertas no locales y las fidelidades entre circuitos con y sin puertas fraccionarias. Esto pone de relieve las posibles ventajas de utilizar puertas fraccionarias en términos de eficacia y calidad de ejecución.

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

Output:

<matplotlib.legend.Legend at 0x116af3cb0>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x11ea4f4d0>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x1247fc440>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x120b792b0>
Output of the previous code cell

Ejemplo de hardware a gran escala

En esta sección, comparamos el rendimiento del flujo de trabajo del núcleo cuántico, con y sin puertas fraccionarias, en hardware cuántico de hasta 40 qubits.

Pasos 1 a 4 combinados

El flujo de trabajo sigue la misma estructura que el ejemplo a pequeña escala. Transpilamos todos los circuitos, tanto los que contienen puertas fraccionarias como los que no, recopilamos métricas y, a continuación, enviamos los circuitos a 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

Output:

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)

Output:

job id (w/o fractional gates): d8uav3lposuc738pruug

A continuación, 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()

Output:

<matplotlib.legend.Legend at 0x12461e660>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x11f2ac980>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x125c91be0>
Output of the previous code cell
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()

Output:

<matplotlib.legend.Legend at 0x11fcf6e40>
Output of the previous code cell

Comparamos el tiempo de uso de la QPU con y sin puertas fraccionarias. Los resultados de la celda siguiente 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")

Output:

no fractional gates: 8 seconds
fractional gates: 8 seconds

Tema avanzado: Uso exclusivo de puertas RX fraccionarias

La necesidad de modificar el flujo de trabajo cuando se utilizan compuertas fraccionarias se debe principalmente a la restricción de los ángulos de las compuertas RZZ. Sin embargo, si utilizas sólo las compuertas fraccionarias RX y excluyes las compuertas fraccionarias RZZ, puedes continuar siguiendo el flujo de trabajo estándar de los patrones Qiskit. Este enfoque todavía puede ofrecer beneficios significativos, en particular en circuitos que implican un gran número de puertas RX y puertas U, reduciendo el número total de puertas y mejorando potencialmente el rendimiento. En esta sección, demostramos cómo optimizar sus circuitos utilizando sólo puertas RX fraccionarias, omitiendo las puertas RZZ.

Para ello, proporcionamos una función de utilidad que permite desactivar una puerta base específica en un objeto Target. Aquí, lo utilizamos para desactivar 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

Utilizaremos como ejemplo un circuito formado por puertas U, CZ y RZZ.

qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")

Output:

Output of the previous code cell

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

Output:

OrderedDict({'rz': 23, 'sx': 16, 'cz': 4})
Output of the previous code cell

A continuación, transpilamos el mismo circuito utilizando puertas RX fraccionarias, excluyendo las puertas RZZ. Esto se traduce en una ligera reducción del número total de compuertas, gracias a la implementación más eficiente de las compuertas 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")

Output:

OrderedDict({'rz': 22, 'sx': 14, 'cz': 4, 'rx': 1})
Output of the previous code cell

Optimizar puertas U con puertas RX fraccionarias

En esta sección, demostramos cómo optimizar las compuertas U utilizando compuertas RX fraccionarias, basándonos en el mismo circuito introducido en la sección anterior.

Transpilamos el circuito utilizando sólo puertas RX fraccionarias, excluyendo las puertas RZZ. Introduciendo una regla de descomposición personalizada, como se muestra a continuación podemos reducir el número de puertas single-qubit necesarias para implementar una puerta U.

Esta función se está debatiendo actualmente en esta incidencia 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 constructor-beta la traducción que proporciona el qiskit-basis-constructor paquete. 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")

Output:

OrderedDict({'rz': 16, 'rx': 9, 'cz': 4})
Output of the previous code cell

Próximos pasos

Recomendaciones

Si este trabajo te ha parecido interesante, quizá te interese el siguiente material:

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.