Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Kernel quantistici con gate frazionari

Stima dell'utilizzo: meno di 30 secondi su un processore Heron r2 (NOTA: Questa è solo una stima. Il vostro tempo di esecuzione può variare.)

Risultati di apprendimento​

  • Cosa sono i gate frazionari e come riducono la profondità e la durata del circuito sulle QPU IBM®

  • I vincoli associati all'uso dei gate frazionari (in particolare, l'intervallo dell'angolo RZZ)

  • Come costruire un flusso di lavoro di kernel quantistico che utilizzi gate frazionari con IBM Quantum Compute Service

  • Come confrontare le metriche di esecuzione hardware (profondità, durata, conteggio dei gate non locali, fedeltà) con e senza gate frazionari

  • Come usare solo gate RX frazionari mantenendo il flusso di lavoro standard dei pattern Qiskit

Prerequisiti​

Contesto​

gate frazionari sui QPU IBM​

I gate frazionari sono gate quantistici parametrizzati che consentono l'esecuzione diretta di rotazioni ad angoli arbitrari (entro limiti specifici), eliminando la necessità di decomporli in più gate di base. Sfruttando le interazioni native tra qubit fisici, puoi implementare determinati operatori unitari in modo più efficiente sull'hardware.

I QPU IBM Quantum® Heron supportano i seguenti gate frazionari:

  • RZZ(θ)R_{ZZ}(\theta) per 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) per qualsiasi valore reale θ\theta

Questi gate possono ridurre significativamente sia la profondità che la durata dei circuiti quantistici. Sono particolarmente vantaggiosi in applicazioni che si basano fortemente su RZZR_{ZZ} e RXR_X, come la simulazione hamiltoniana, il Quantum Approximate Optimization Algorithm (QAOA) e i metodi del kernel quantistico. In questo tutorial, ci concentriamo sul kernel quantistico come esempio pratico.

Limitazioni​

I gate frazionari sono attualmente una funzionalità sperimentale e presentano alcuni vincoli:

I gate frazionari richiedono un flusso di lavoro diverso rispetto all'approccio standard. Questo tutorial spiega come lavorare con i gate frazionari attraverso un'applicazione pratica.

Consultate quanto segue per maggiori dettagli sui gate frazionari.

Approcci di flusso di lavoro per il vincolo dell'angolo RZZ​

Il flusso di lavoro per l'utilizzo dei gate frazionari segue generalmente il flusso di lavoro dei pattern Qiskit. La differenza chiave è che tutti gli angoli RZZ devono soddisfare il vincolo 0<θ≤π/20 < \theta \leq \pi/2. Ci sono due approcci per garantire che questa condizione sia soddisfatta, come discutiamo di seguito. Raccomandiamo il secondo approccio, e in questo tutorial lo dimostriamo tramite un esempio ispirato al metodo del kernel quantistico. Per capire meglio dove i kernel quantistici potrebbero essere utili, raccomandiamo di leggere Liu, Arunachalam & Temme (2021).

Puoi anche seguire il tutorial Quantum kernel training e la lezione Quantum kernels nel corso di Quantum machine learning su 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. Generare valori di parametri che soddisfano il vincolo dell'angolo RZZ​

Se sei sicuro che tutti gli angoli RZZ rientrino nell'intervallo valido, puoi seguire il flusso di lavoro standard dei pattern Qiskit. In questo caso, invia semplicemente i valori dei parametri come parte di un PUB. Il flusso di lavoro procede come segue.

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 tentate di inviare un PUB che include un gate RZZ con un angolo al di fuori dell'intervallo valido, incontrerete un messaggio di errore come:

'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].'

Per evitare questo errore, usa il secondo approccio descritto di seguito.

2. Assegnare i valori dei parametri ai circuiti prima della transpilazione​

Il pacchetto qiskit-ibm-runtime fornisce un pass di transpilazione specializzato chiamato FoldRzzAngle. Questo pass trasforma i circuiti quantistici in modo che tutti gli angoli RZZ rispettino il vincolo dell'angolo RZZ. Se fornite il backend a generate_preset_pass_manager o transpile, Qiskit applica automaticamente FoldRzzAngle ai circuiti quantistici. Questo approccio richiede di assegnare i valori dei parametri ai circuiti quantistici prima della transpilazione. Il flusso di lavoro procede come segue.

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

Nota che questo flusso di lavoro comporta un costo computazionale più elevato rispetto al primo approccio, poiché implica l'assegnazione di valori dei parametri ai circuiti quantistici e la memorizzazione locale dei circuiti con parametri assegnati.

attenzione

Prendi nota di un problema noto in qiskit-ibm-runtime v0.47.0 in cui i gate RZZ con angoli non validi potrebbero rimanere nei circuiti anche dopo la trasposizione in certi scenari.

Vedi qiskit-ibm-runtime#2441 per seguire i progressi su questo problema. Raccomandiamo la seguente soluzione temporanea fino a quando non sarà risolto.

pm = generate_preset_pass_manager(backend=backend, ...)
pm.post_optimization = PassManager(
[
FoldRzzAngle(),
Optimize1qGatesDecomposition(target=backend.target),
RemoveIdentityEquivalent(target=backend.target),
]
)
... = pm.run(...)

Requisiti​

Prima di iniziare questo tutorial, assicuratevi di avere installato quanto segue:

  • Qiskit SDK v2.0 o successivo, con supporto per la visualizzazione
  • Qiskit Runtime v0.41 o successivo (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 o successivo (pip install qiskit-aer)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Setup​

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

Abilitare i gate frazionari e verificare i gate di base​

Per utilizzare i gate frazionari, puoi ottenere un backend che li supporti impostando l'opzione use_fractional_gates=True. Se il backend supporta i gate frazionari, vedrai rzz e rx elencati tra i suoi gate di 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']

Esempio di simulatore su piccola scala​

In questa sezione, esaminiamo i quattro passi del flusso di lavoro dei pattern Qiskit su un simulatore, utilizzando il circuito kernel quantistico come esempio pratico.

Step 1: Mappare gli input classici al problema quantistico​

Circuito del kernel quantistico​

In questa sezione, esploriamo il circuito del kernel quantistico utilizzando i gate RZZ per introdurre il flusso di lavoro per i gate frazionari.

Iniziamo costruendo un circuito quantistico per calcolare le singole voci della matrice del kernel. Questo viene fatto combinando circuiti di feature map ZZ con un overlap unitario. La funzione kernel prende vettori nello spazio mappato delle feature e restituisce il loro prodotto interno come voce della matrice del kernel: K(x,y)=⟨Φ(x)∣Φ(y)⟩,K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, dove ∣Φ(x)⟩|\Phi(x)\rangle rappresenta lo stato quantistico mappato dalle feature.

Costruiamo manualmente un circuito di feature map ZZ utilizzando gate RZZ. Sebbene Qiskit fornisca una zz_feature_map integrata, attualmente (Qiskit v2.4.1) non supporta i gate RZZ (vedi issue).

Successivamente, calcoliamo la funzione kernel per input identici - per esempio, K(x,x)=1K(x, x) = 1. Sui computer quantistici rumorosi, questo valore potrebbe essere inferiore a 1 a causa del rumore. Un risultato più vicino a 1 indica un rumore più basso nell'esecuzione. In questo tutorial, ci riferiamo a questo valore come fedeltà, definita come 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

I circuiti del kernel quantistico e i corrispondenti valori dei parametri vengono generati per sistemi da 4 a 40 qubit, e le loro fedeltà vengono successivamente valutate.

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

Il circuito a quattro qubit è visualizzato di seguito.

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

Output of the previous code cell

Nel flusso di lavoro standard dei pattern Qiskit, i valori dei parametri vengono tipicamente passati alla primitiva Sampler o Estimator come parte di un PUB. Tuttavia, quando si utilizza un backend che supporta i gate frazionari, questi valori dei parametri devono essere esplicitamente assegnati al circuito quantistico prima della transpilazione.

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

Step 2: Ottimizzare il problema per l'esecuzione sull'hardware quantistico​

Traspiliamo quindi il circuito utilizzando il pass manager seguendo il pattern Qiskit standard. Fornendo un backend che supporta i gate frazionari a generate_preset_pass_manager, viene incluso automaticamente un pass specializzato chiamato FoldRzzAngle. Questo pass modifica il circuito per rispettare i vincoli dell'angolo RZZ. Di conseguenza, i gate RZZ con valori negativi nella figura precedente vengono trasformati in valori positivi, e vengono aggiunti alcuni gate X aggiuntivi.

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

Per valutare l'impatto dei gate frazionari, valutiamo il numero di gate non locali (CZ e RZZ per questo backend), insieme alle profondità e alle durate dei circuiti, e confrontiamo successivamente queste metriche con quelle di un flusso di lavoro standard.

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
]

Step 3: Eseguire utilizzando le primitive Qiskit​

Eseguiamo il circuito transpilato con il backend che supporta i gate frazionari.

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

Step 4: Post-elaborare e restituire il risultato nel formato classico desiderato​

Puoi ottenere il valore della funzione kernel K(x,x)K(x, x) misurando la probabilità della stringa di bit tutti zero 00...00 nell'output.

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

Confronto del flusso di lavoro e del circuito senza gate frazionari​

In questa sezione, presentiamo il flusso di lavoro standard dei pattern Qiskit utilizzando un backend che non supporta i gate frazionari. Confrontando i circuiti transpilati, noterete che la versione che utilizza i gate frazionari (della sezione precedente) è più compatta di quella senza gate frazionari.

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

Confronto tra profondità, durate e fedeltà​

In questa sezione confrontiamo il numero di gate non locali e le fedeltà tra circuiti con e senza gate frazionari. Questo evidenzia i potenziali vantaggi dell'utilizzo dei gate frazionari in termini di efficienza di esecuzione e qualità.

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

Esempio di hardware su larga scala​

In questa sezione, confrontiamo il flusso di lavoro del kernel quantistico con e senza gate frazionari su hardware quantistico fino a 40 qubit.

Passi 1-4 combinati​

Il flusso di lavoro segue la stessa struttura dell'esempio su piccola scala. Transpiliamo tutti i circuiti con e senza gate frazionari, raccogliamo le metriche, e poi inviamo i circuiti all'hardware quantistico reale.

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

Poi confrontiamo le metriche.

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

Confrontiamo il tempo di utilizzo della QPU con e senza gate frazionari. I risultati nella cella seguente mostrano che i tempi di utilizzo della QPU sono pressoché identici.

print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 8 seconds
fractional gates: 8 seconds

Argomento avanzato: utilizzo dei soli gate RX frazionari​

La necessità del flusso di lavoro modificato quando si utilizzano gate frazionari deriva principalmente dalla restrizione sugli angoli dei gate RZZ. Tuttavia, se utilizzi solo i gate RX frazionari ed escludi i gate RZZ frazionari, puoi continuare a seguire il flusso di lavoro standard dei pattern Qiskit. Questo approccio può comunque offrire vantaggi significativi, in particolare nei circuiti che coinvolgono un gran numero di gate RX e gate U, riducendo il conteggio complessivo dei gate e potenzialmente migliorando le prestazioni. In questa sezione dimostriamo come ottimizzare i tuoi circuiti utilizzando solo gate RX frazionari, omettendo i gate RZZ.

Per supportare questo approccio, forniamo una funzione di utilità che ti consente di disabilitare uno specifico gate di base in un oggetto Target. Qui la utilizziamo per disabilitare i gate 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

Utilizziamo come esempio un circuito composto da gate U, CZ e RZZ.

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

Output of the previous code cell

Per prima cosa transpiliamo il circuito per un backend che non supporta i gate frazionari.

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

Successivamente, transpiliamo lo stesso circuito utilizzando gate RX frazionari, escludendo i gate RZZ. Questo risulta in una leggera riduzione del conteggio totale dei gate, grazie all'implementazione più efficiente dei gate 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

Ottimizzazione dei gate U con gate RX frazionari​

In questa sezione, dimostriamo come ottimizzare i gate U utilizzando gate RX frazionari, basandoci sullo stesso circuito introdotto nella sezione precedente. Transpiliamo il circuito usando solo gate RX frazionari, escludendo i gate RZZ. Introducendo una regola di decomposizione personalizzata, come mostrato di seguito, possiamo ridurre il numero di gate a singolo qubit necessari per implementare un gate U.

Questa funzionalità è attualmente in discussione in questa issue 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)

Successivamente, applichiamo il transpiler utilizzando la traduzione constructor-beta fornita dal pacchetto qiskit-basis-constructor. Di conseguenza, il numero totale di gate viene ridotto rispetto alla transpilazione precedente.

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

Prossimi passi​

Raccomandazioni

Se hai trovato interessante questo lavoro, potresti essere interessato al seguente materiale: