Skip to main content
IBM Quantum Platform

Núcleos quânticos com portas fracionárias

Estimativa de uso: menos de 30 segundos em um processador Heron r2 (OBSERVAÇÃO: esta é apenas uma estimativa. Seu tempo de execução pode variar)


Resultados do aprendizado

Ao concluir este tutorial, você deverá compreender:

  • O que são portas fracionárias e como elas reduzem a profundidade e a duração dos circuitos em QPUs d IBM®
  • As restrições associadas ao uso de portas fracionárias (em particular, a faixa de ângulos RZZ)
  • Como construir um fluxo de trabalho de kernel quântico que utilize portas fracionárias com o Qiskit Runtime
  • Como comparar métricas de execução em hardware (profundidade, duração, contagem de portas não locais, fidelidade) com e sem portas fracionárias
  • Como usar apenas portas RX fracionárias, mantendo o fluxo de trabalho padrão do Qiskit

Pré-requisitos

Sugerimos que você esteja familiarizado com os seguintes tópicos antes de seguir com este tutorial:


Segundo plano

Portas fracionárias em QPUs d IBM

Portas fracionárias são portas quânticas parametrizadas que permitem a execução direta de rotações de ângulos arbitrários (dentro de limites específicos), eliminando a necessidade de decompor essas rotações em várias portas básicas. Ao aproveitar as interações nativas entre os qubits físicos, é possível implementar certas operações unitárias de forma mais eficiente no hardware.

IBM As QPUs Quantum® Heron suportam as seguintes portas fracionárias:

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

Essas portas podem reduzir significativamente a profundidade e a duração dos circuitos quânticos. Eles são particularmente vantajosos em aplicativos que dependem muito de RZZR_{ZZ} e RXR_X, como a simulação Hamiltoniana, o Algoritmo de Otimização Aproximada Quântica (QAOA) e os métodos de kernel quântico. Neste tutorial, vamos nos concentrar no kernel quântico como um exemplo prático.

Limitações

Atualmente, as portas fracionárias são um recurso experimental e apresentam algumas restrições:

As portas fracionárias exigem um fluxo de trabalho diferente em comparação com a abordagem padrão. Este tutorial explica como trabalhar com portas fracionárias por meio de um aplicativo prático.

Consulte o seguinte para obter mais detalhes sobre portas fracionárias.

Abordagens de fluxo de trabalho para a restrição do ângulo RZZ

O fluxo de trabalho para o uso de portas fracionárias geralmente segue o fluxo de trabalho dos padrões do Qiskit. A principal diferença é que todos os ângulos RZZ devem satisfazer a restrição 0<θπ/20 < \theta \leq \pi/2. Existem duas abordagens para garantir que essa condição seja atendida, conforme discutiremos a seguir. Recomendamos a segunda abordagem e, neste tutorial, a demonstramos por meio de um exemplo inspirado no método do kernel quântico. Para entender melhor em que contextos os kernels quânticos podem ser úteis, recomendamos a leitura do artigo de Liu, Arunachalam e Temme (2021).

Você também pode seguir o tutorial de treinamento sobre kernels quânticos e a aula sobre kernels quânticos no curso de aprendizado de máquina quântico em IBM Quantum® Learning.

1. Gerar valores de parâmetros que satisfaçam a restrição do ângulo RZZ

Se você tiver certeza de que todos os ângulos RZZ estão dentro do intervalo válido, poderá seguir o fluxo de trabalho padrão dos padrões do Qiskit. Nesse caso, basta enviar os valores dos parâmetros como parte de um PUB. O fluxo de trabalho é realizado da seguinte forma.

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

Se você tentar enviar um PUB que inclua uma porta RZZ com um ângulo fora do intervalo válido, receberá uma mensagem de erro como esta:

'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 esse erro, utilize a segunda abordagem descrita a seguir.

2. Atribua valores de parâmetros aos circuitos antes da transpilagem

O qiskit-ibm-runtime pacote oferece uma etapa de transpilador especializada chamada FoldRzzAngle. Esta etapa transforma os circuitos quânticos de forma que todos os ângulos RZZ estejam em conformidade com a restrição de ângulo RZZ. Se você fornecer o backend para generate_preset_pass_manager ou transpile, o Qiskit aplica FoldRzzAngle automaticamente aos circuitos quânticos. Essa abordagem exige que você atribua valores aos parâmetros dos circuitos quânticos antes da transpilagem. O fluxo de trabalho se desenrola da seguinte forma.

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

Observe que esse fluxo de trabalho acarreta um custo computacional maior do que a primeira abordagem, pois envolve atribuir valores de parâmetros aos circuitos quânticos e armazenar localmente os circuitos associados a esses parâmetros.

Caution

Esteja ciente de um problema conhecido descrito em qiskit-ibm-runtimev0.47.0, no qual portas RZZ com ângulos inválidos podem permanecer nos circuitos mesmo após a transpilacão, em certos cenários.

Acesse qiskit-ibm-runtime#2441 para acompanhar o andamento dessa questão. Recomendamos a seguinte solução alternativa até que o problema seja resolvido.

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 iniciar este tutorial, verifique se você tem os seguintes itens instalados:

  • Qiskit SDK v2.0 ou posterior, com suporte para visualização
  • Qiskit Runtime v0.41 ou posterior (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 ou versão posterior (pip install qiskit-aer)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Instalação

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

Habilite portas fracionárias e verifique portas básicas

Para usar portas fracionárias, você pode obter um backend compatível com elas, definindo a opção use_fractional_gates=True . Se o backend for compatível com portas fracionárias, você verá rzz e rx listados entre suas portas de 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']

Exemplo de simulador em pequena escala

Nesta seção, vamos explicar as quatro etapas do fluxo de trabalho do Qiskit Patterns em um simulador, usando o circuito do kernel quântico como exemplo prático.

Etapa 1: Mapeie entradas clássicas para um problema quântico

Circuito de kernel quântico

Nesta seção, exploramos o circuito do kernel quântico usando portas RZZ para introduzir o fluxo de trabalho para portas fracionárias.

Começamos construindo um circuito quântico para computar entradas individuais da matriz do kernel. Isso é feito por meio da combinação de circuitos de mapas de recursos ZZ com uma sobreposição unitária. A função de kernel usa vetores no espaço mapeado de recursos e retorna seu produto interno como uma entrada da matriz de kernel: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, onde Φ(x)|\Phi(x)\rangle representa o estado quântico mapeado por recursos.

Construímos manualmente um circuito de mapa de características ZZ utilizando portas RZZ. zz_feature_mapEmbora o Qiskit ofereça um recurso integrado, ele ainda não oferece suporte a portas RZZ, conforme indicado na página v2.4.1 do Qiskit ( consulte a issue ).

Em seguida, calculamos a função do kernel para entradas idênticas - por exemplo, K(x,x)=1K(x, x) = 1. Em computadores quânticos ruidosos, esse valor pode ser menor que 1 devido ao ruído. Um resultado mais próximo de 1 indica menor ruído na execução. Neste tutorial, nos referimos a esse valor como a fidelidade, 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

Os circuitos de kernel quântico e seus valores de parâmetros correspondentes são gerados para sistemas com 4 a 40 qubits, e suas fidelidades são avaliadas posteriormente.

qubits = list(range(4, 12, 2))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]

O circuito de quatro qubits é visualizado abaixo.

circuits[0].draw("mpl", fold=-1)

Output:

Output of the previous code cell

No fluxo de trabalho padrão dos padrões do Qiskit, os valores dos parâmetros são normalmente passados para o primitivo Sampler ou Estimator como parte de um PUB. No entanto, ao usar um backend que ofereça suporte a portas fracionárias, esses valores de parâmetros devem ser explicitamente atribuídos ao circuito quântico antes da transpilação.

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

Etapa 2: Otimizar o problema para execução em hardware quântico

Em seguida, transpilamos o circuito usando o gerenciador de passagens seguindo o padrão Qiskit. Ao fornecer um backend compatível com portas fracionárias para generate_preset_pass_manager, uma passagem especializada chamada FoldRzzAngle é automaticamente incluída. Essa passagem modifica o circuito para atender às restrições de ângulo RZZ. Como resultado, as portas RZZ com valores negativos na figura anterior são transformadas em valores positivos, e algumas portas X adicionais são adicionadas.

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 avaliar o impacto das portas fracionárias, avaliamos o número de portas não locais (CZ e RZZ para esse backend), juntamente com as profundidades e durações dos circuitos, e comparamos essas métricas com as de um fluxo de trabalho padrão 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
]

Passo 3: Execute usando Qiskit primitives

Executamos o circuito transpilado com o backend que suporta portas fracionárias.

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

Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado

Você pode obter o valor da função kernel K(x,x)K(x, x) medindo a probabilidade do bitstring totalmente zero 00...00 na saída.

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

Output:

[0.929, 0.882, 0.8645, 0.817]

Comparação do fluxo de trabalho e do circuito sem portas fracionárias

Nesta seção, apresentamos o fluxo de trabalho padrão do Qiskit Patterns utilizando um backend que não suporta portas fracionárias. Ao comparar os circuitos transpilados, você perceberá que a versão que utiliza portas fracionárias (da seção anterior) é mais compacta do que aquela sem portas fracionárias.

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

Comparação entre profundidades, durações e fidelidades

Nesta seção, comparamos o número de portas não locais e as fidelidades entre os circuitos com e sem portas fracionárias. Isso destaca os possíveis benefícios do uso de portas fracionárias em termos de eficiência e qualidade de execução.

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

Exemplo de hardware em grande escala

Nesta seção, comparamos o desempenho do fluxo de trabalho do kernel quântico com e sem portas fracionárias em hardware quântico com até 40 qubits.

Etapas 1 a 4 combinadas

O fluxo de trabalho segue a mesma estrutura do exemplo em pequena escala. Compilamos todos os circuitos, com e sem portas fracionárias, coletamos métricas e, em seguida, submetemos os circuitos a hardware quâ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

Em seguida, comparamos as 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 o tempo de uso da QPU com e sem portas fracionárias. Os resultados na célula a seguir mostram que os tempos de uso da QPU são quase 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

Tópico avançado: Usando apenas portas RX fracionárias

A necessidade de modificar o fluxo de trabalho ao usar portas fracionárias decorre principalmente da restrição dos ângulos de porta RZZ. No entanto, se você usar apenas as portas RX fracionárias e excluir as portas RZZ fracionárias, poderá continuar a seguir o fluxo de trabalho padrão dos padrões Qiskit. Essa abordagem ainda pode oferecer benefícios significativos, especialmente em circuitos que envolvem um grande número de portas RX e portas U, reduzindo o número total de portas e melhorando potencialmente o desempenho. Nesta seção, demonstramos como otimizar seus circuitos usando apenas portas RX fracionárias, omitindo as portas RZZ.

Para dar suporte a isso, fornecemos uma função utilitária que permite desativar uma porta de base específica em um objeto Target. Aqui, nós o usamos para desativar as portas 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 um circuito que consiste em portas U, CZ e RZZ como exemplo.

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

Primeiro, transpilamos o circuito para um backend que não oferece suporte a portas fracionárias.

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

Em seguida, transpilamos o mesmo circuito usando portas RX fracionárias, excluindo as portas RZZ. Isso resulta em uma pequena redução na contagem total de portas, graças à implementação mais eficiente das portas 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

Otimize portas U com portas RX fracionárias

Nesta seção, demonstramos como otimizar as portas U usando portas RX fracionárias, com base no mesmo circuito apresentado na seção anterior.

Transpilamos o circuito usando apenas portas RX fracionárias, excluindo as portas RZZ. Ao introduzir uma regra de decomposição personalizada, conforme mostrado a seguir, podemos reduzir o número de portas de um único qubit necessárias para implementar uma porta U.

Esse recurso está atualmente em discussão nesta questão do 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)

Em seguida, aplicamos o transpiler utilizando constructor-beta a tradução fornecida pelo qiskit-basis-constructor pacote. Como resultado, o número total de portas é reduzido em comparação com a transpilacão 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óximas etapas

Recomendações

Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.