Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Kernels quânticos com portas fracionárias

Estimativa de uso: menos de 30 segundos em um processador Heron r2 (NOTA: Esta é apenas uma estimativa. Seu tempo de execução pode variar.)

Resultados de aprendizado​

  • O que são portas fracionárias e como elas reduzem a profundidade e a duração de circuitos em QPUs IBM®

  • As restrições associadas ao uso de portas fracionárias (em particular, o intervalo de ângulo RZZ)

  • Como construir um fluxo de trabalho de kernel quântico que usa portas fracionárias com o IBM Quantum Compute Service

  • 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 de Qiskit patterns

Pré-requisitos​

Contexto​

Portas fracionárias em QPUs da 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 decompô-las em múltiplas portas de base. Ao aproveitar as interações nativas entre qubits físicos, você pode implementar certos unitários de forma mais eficiente no hardware.

QPUs Heron da IBM Quantum® 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 tanto a profundidade quanto a duração dos circuitos quânticos. Elas são particularmente vantajosas em aplicações que dependem fortemente de RZZR_{ZZ} e RXR_X, como simulação Hamiltoniana, o Algoritmo de Otimização Aproximada Quântica (QAOA) e métodos de kernel quântico. Neste tutorial, focamos no kernel quântico como um exemplo prático.

Limitações​

Portas fracionárias são atualmente uma funcionalidade experimental e vêm com algumas restrições:

Portas fracionárias requerem um fluxo de trabalho diferente em comparação com a abordagem padrão. Este tutorial explica como trabalhar com portas fracionárias através de uma aplicação prática.

Veja o seguinte para mais detalhes sobre portas fracionárias.

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

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

Você também pode trabalhar no tutorial Treinamento de kernel quântico e na lição Kernels quânticos no curso de aprendizado de máquina quântica no 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. Gerar valores de parâmetros que satisfazem a restrição de ângulo RZZ​

Se você tiver certeza de que todos os ângulos RZZ estão dentro do intervalo válido, você pode seguir o fluxo de trabalho padrão dos padrões Qiskit. Neste caso, você simplesmente submete os valores de parâmetros como parte de um PUB. O fluxo de trabalho prossegue 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 submeter um PUB que inclui uma porta RZZ com um ângulo fora do intervalo válido, você encontrará uma mensagem de erro como:

'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, use a segunda abordagem descrita abaixo.

2. Atribuir valores de parâmetros aos circuitos antes da transpilação​

O pacote qiskit-ibm-runtime fornece um passe de transpilador especializado chamado FoldRzzAngle. Este passe transforma circuitos quânticos para 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 automaticamente FoldRzzAngle aos circuitos quânticos. Esta abordagem requer que você atribua valores de parâmetros aos circuitos quânticos antes da transpilação. O fluxo de trabalho prossegue 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 incorre em um custo computacional maior do que a primeira abordagem, pois envolve atribuir valores de parâmetro a circuitos quânticos e armazenar localmente os circuitos com parâmetros vinculados.

cuidado

Observe um problema conhecido no qiskit-ibm-runtime v0.47.0 em que portas RZZ com ângulos inválidos podem permanecer nos circuitos mesmo após a transpilação em certos cenários.

Veja qiskit-ibm-runtime#2441 para acompanhar o progresso deste problema. Recomendamos a seguinte solução alternativa até que 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 começar este tutorial, certifique-se de ter o seguinte instalado:

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

Configuraçã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

Habilitar portas fracionárias e verificar portas de base​

Para usar portas fracionárias, você pode obter um backend que as suporta definindo a opção use_fractional_gates=True. Se o backend suporta 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")
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, percorremos as quatro etapas do fluxo de trabalho de padrões Qiskit em um simulador, usando o circuito de kernel quântico como um exemplo prático.

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

Circuito de kernel quântico​

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

Começamos construindo um circuito quântico para calcular entradas individuais da matriz de kernel. Isso é feito combinando circuitos de mapa de características ZZ com uma sobreposição unitária. A função kernel recebe vetores no espaço mapeado de características 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 de características.

Construímos manualmente um circuito de mapa de características ZZ usando portas RZZ. Embora o Qiskit forneça um zz_feature_map integrado, ele não suporta atualmente portas RZZ a partir do Qiskit v2.4.1 (veja o problema).

Em seguida, calculamos a função kernel para entradas idênticas - por exemplo, K(x,x)=1K(x, x) = 1. Em computadores quânticos ruidosos, este 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 este valor como 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

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 subsequentemente avaliadas.

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 of the previous code cell

No fluxo de trabalho padrão dos padrões Qiskit, valores de parâmetros são tipicamente passados para a primitiva Sampler ou Estimator como parte de um PUB. No entanto, ao usar um backend que suporta 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 of the previous code cell

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

Em seguida, transpilamos o circuito usando o gerenciador de passes seguindo o padrão Qiskit padrão. Ao fornecer um backend que suporta portas fracionárias para generate_preset_pass_manager, um passe especializado chamado FoldRzzAngle é automaticamente incluído. Este passe modifica o circuito para estar em conformidade com as restrições de ângulo RZZ. Como resultado, 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)
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 este backend), juntamente com profundidades e durações de circuitos, e comparamos essas métricas com aquelas de um fluxo de trabalho padrão mais tarde.

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
]

Etapa 3: Executar usando primitivas Qiskit​

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())
085ce928-767e-4200-93bf-3905e5411cfe

Etapa 4: Pós-processar e retornar resultado no formato clássico desejado​

Você pode obter o valor da função kernel K(x,x)K(x, x) medindo a probabilidade da cadeia de bits de todos zeros 00...00 na saída.

result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
[0.929, 0.882, 0.8645, 0.817]

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

Nesta seção, apresentamos o fluxo de trabalho padrão dos padrões Qiskit usando um backend que não suporta portas fracionárias. Ao comparar os circuitos transpilados, você notará que a versão usando portas fracionárias (da seção anterior) é mais compacta do que a 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)
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())
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]

Comparação de profundidades, durações e fidelidades​

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

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>

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()
<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()
<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()
<matplotlib.legend.Legend at 0x120b792b0>

Output of the previous code cell

Exemplo de hardware em grande escala​

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

Etapas 1-4 combinadas​

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

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()
<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()
<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()
<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()
<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")
no fractional gates: 8 seconds
fractional gates: 8 seconds

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

A necessidade do fluxo de trabalho modificado ao usar portas fracionárias decorre principalmente da restrição nos ângulos das portas RZZ. No entanto, se você usar apenas as portas RX fracionárias e excluir as portas RZZ fracionárias, você pode continuar seguindo o fluxo de trabalho padrão dos padrões Qiskit. Esta abordagem ainda pode oferecer benefícios significativos, particularmente em circuitos que envolvem um grande número de portas RX e portas U, ao reduzir a contagem geral de portas e potencialmente melhorar o desempenho. Nesta seção, demonstramos como otimizar seus circuitos usando apenas portas RX fracionárias, omitindo portas RZZ.

Para suportar isto, fornecemos uma função utilitária que permite desabilitar uma porta de base específica em um objeto Target. Aqui, a usamos para desabilitar 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 consistindo de 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 of the previous code cell

Primeiro transpilamos o circuito para um backend que não suporta 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")
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 portas RZZ. Isto resulta em uma ligeira 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")
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 portas U usando portas RX fracionárias, com base no mesmo circuito introduzido na seção anterior. Transpilamos o circuito usando apenas portas RX fracionárias, excluindo portas RZZ. Ao introduzir uma regra de decomposição customizada, como mostrado a seguir, podemos reduzir o número de portas de qubit único necessárias para implementar uma porta U.

Este recurso está atualmente em discussão nesta issue 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 transpilador usando a tradução constructor-beta fornecida pelo pacote qiskit-basis-constructor. Como resultado, o número total de portas é reduzido em comparação à transpilaçã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")
OrderedDict({'rz': 16, 'rx': 9, 'cz': 4})

Output of the previous code cell

Próximos passos​

Recomendações

Se você achou este trabalho interessante, pode se interessar pelo seguinte material: