Kernel quantistici con porte frazionarie
Stima di utilizzo: meno di 30 secondi su un processore Heron r2 (NOTA: questa è solo una stima. Il tempo di esecuzione può variare)
Risultati di apprendimento
Dopo aver seguito questo tutorial, dovresti aver compreso:
- Cosa sono i gate frazionari e in che modo riducono la profondità e la durata dei circuiti sulle QPU dell' IBM®
- I vincoli legati all'uso delle porte frazionarie (in particolare, l'intervallo angolare RZZ)
- Come creare un flusso di lavoro con kernel quantistico che utilizzi porte frazionarie con il servizio di calcolo " IBM Quantum "
- Come confrontare le metriche di esecuzione hardware (profondità, durata, numero di gate non locali, fedeltà) con e senza gate frazionari
- Come utilizzare solo gate RX frazionari mantenendo il flusso di lavoro standard di Qiskit
Prerequisiti
Ti consigliamo di acquisire familiarità con gli argomenti seguenti prima di seguire questo tutorial:
- Il flusso di lavoro dei modelli di Qiskit
- Guida ai cancelli frazionari
- Il tutorial sull'addestramento dei kernel quantistici e la lezione sui kernel quantistici del corso di machine learning quantistico
Sfondo
Gate frazionari su QPU IBM
I gate frazionari sono gate quantistici parametrizzati che consentono l'esecuzione diretta di rotazioni con angoli arbitrari (entro limiti specifici), eliminando la necessità di scomporli in più gate di base. Sfruttando le interazioni native tra i qubit fisici, è possibile implementare determinati operatori unitari in modo più efficiente sull'hardware.
IBM Le QPU Quantum® Heron supportano le seguenti porte frazionarie:
- per
- per qualsiasi valore reale
Queste porte possono ridurre significativamente sia la profondità che la durata dei circuiti quantistici. Sono particolarmente vantaggiosi nelle applicazioni che si basano molto su e , come la simulazione hamiltoniana, il Quantum Approximate Optimization Algorithm (QAOA) e i metodi kernel quantistici. In questa esercitazione ci concentriamo sul kernel quantistico come esempio pratico.
Limitazioni
Le porte frazionali sono attualmente una funzione sperimentale e presentano alcune limitazioni:
- è limitato agli angoli dell'intervallo .
- L'uso di porte frazionali non è supportato per i circuiti dinamici, il Pauli twirling, la cancellazione probabilistica degli errori (PEC) e l' estrapolazione a rumore zero (ZNE ) (che utilizza l' amplificazione probabilistica degli errori (PEA)).
I gate frazionali richiedono un flusso di lavoro diverso rispetto all'approccio standard. Questa esercitazione spiega come lavorare con le porte frazionarie attraverso un'applicazione pratica.
Per maggiori dettagli sui gate frazionari, vedere quanto segue.
Approcci basati sul 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 modelli di Qiskit. La differenza fondamentale sta nel fatto che tutti gli angoli RZZ devono soddisfare il vincolo . Esistono due approcci per garantire il rispetto di questa condizione, come illustreremo di seguito. Consigliamo il secondo approccio e, in questo tutorial, lo illustriamo attraverso un esempio ispirato al metodo del kernel quantistico. Per comprendere meglio in quali contesti i kernel quantistici potrebbero rivelarsi utili, consigliamo di leggere Liu, Arunachalam & Temme (2021).
Puoi anche seguire il tutorial di formazione sui kernel quantistici e la lezione sui kernel quantistici nel corso sul machine learning quantistico disponibile su IBM Quantum® Learning.
1. Generare valori dei parametri che soddisfino il vincolo dell'angolo RZZ
Se siete sicuri che tutti gli angoli RZZ rientrano nell'intervallo valido, potete seguire il flusso di lavoro standard dei modelli Qiskit. In questo caso, è sufficiente inviare 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 si tenta di inviare un PUB che include un gate RZZ con un angolo al di fuori dell'intervallo valido, verrà visualizzato un messaggio di errore del tipo:
'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, utilizzare il secondo metodo descritto di seguito.
2. Assegnare i valori dei parametri ai circuiti prima della transpilazione
Il qiskit-ibm-runtime pacchetto fornisce un passaggio di transpilazione specializzato denominato FoldRzzAngle.
Questo passaggio trasforma i circuiti quantistici in modo che tutti gli angoli RZZ rispettino il vincolo relativo agli angoli RZZ.
Se si fornisce il backend a generate_preset_pass_manager o transpile, Qiskit applica FoldRzzAngle automaticamente ai circuiti quantistici.
Questo approccio richiede di assegnare i valori dei parametri ai circuiti quantistici prima della transpilazione.
Il flusso di lavoro si svolge 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)])Si noti che questo flusso di lavoro comporta un costo computazionale maggiore rispetto al primo approccio, poiché prevede l’assegnazione di valori ai parametri dei circuiti quantistici e la memorizzazione locale dei circuiti associati a tali parametri.
Si prega di tenere presente un problema noto segnalato all'indirizzo qiskit-ibm-runtimev0.47.0, secondo cui, in determinati scenari, i gate RZZ con angoli non validi potrebbero rimanere nei circuiti anche dopo la transpilazione.
Visita il sito qiskit-ibm-runtime#2441 per seguire gli sviluppi su questo argomento. Fino a quando il problema non sarà risolto, consigliamo di adottare la seguente soluzione alternativa.
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 questa esercitazione, assicuratevi di aver installato quanto segue:
- Qiskit SDK v2.0 o versioni successive, con supporto alla visualizzazione
- Qiskit Runtime v0.41 o versioni successive (
pip install qiskit-ibm-runtime) - Qiskit Aer v0.17 o versioni successive (
pip install qiskit-aer) - Costruttore di basi Qiskit (
pip install qiskit_basis_constructor)
Configura
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 FoldRzzAngleAbilita porte frazionarie e controlla le porte di base
Per utilizzare i gate frazionari, è possibile ottenere un backend che li supporti impostando l'opzione use_fractional_gates=True .
Se il backend supporta le porte frazionarie, si vedranno rzz e rx elencate tra le porte 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")Output:
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 illustreremo le quattro fasi del flusso di lavoro dei modelli Qiskit su un simulatore, utilizzando il circuito del kernel quantistico come esempio pratico.
Fase 1: Mappare gli input classici su un problema quantistico
Circuito quantistico a kernel
In questa sezione, esploriamo il circuito del kernel quantistico utilizzando le porte RZZ per introdurre il flusso di lavoro per le porte frazionali.
Cominciamo con la costruzione di un circuito quantistico per calcolare le singole voci della matrice kernel. Ciò avviene combinando circuiti di mappe di caratteristiche ZZ con una sovrapposizione unitaria. La funzione kernel prende i vettori nello spazio delle caratteristiche e restituisce il loro prodotto interno come voce della matrice kernel: dove rappresenta lo stato quantico mappato con le caratteristiche.
Costruiamo manualmente un circuito di mappa delle caratteristiche ZZ utilizzando porte RZZ.
zz_feature_mapSebbene Qiskit fornisca un'interfaccia integrata, al momento non supporta i gate RZZ, come indicato nella versione Qiskit v2.4.1 ( vedi issue ).
Successivamente, calcoliamo la funzione kernel per ingressi identici - ad esempio, . Sui computer quantistici rumorosi, questo valore può essere inferiore a 1 a causa del rumore. Un risultato più vicino a 1 indica un minore rumore nell'esecuzione. In questa guida, ci si riferisce a questo valore come alla fedeltà, definita come
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_shotsI circuiti kernel quantistici e i corrispondenti valori dei parametri sono generati per sistemi da 4 a 40 qubit e la loro fedeltà viene successivamente valutata.
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:
Nel flusso di lavoro standard dei modelli Qiskit, i valori dei parametri sono tipicamente passati alla primitiva Sampler o Estimator come parte di un PUB. Tuttavia, quando si utilizza un backend che supporta porte frazionarie, questi valori dei parametri devono essere assegnati esplicitamente 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:
Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico
Quindi transpiliamo il circuito utilizzando il pass manager secondo lo schema standard di Qiskit.
Fornendo un backend che supporta porte frazionarie a generate_preset_pass_manager, viene automaticamente incluso un passaggio specializzato chiamato FoldRzzAngle .
Questo passaggio modifica il circuito per rispettare i vincoli dell'angolo RZZ.
Di conseguenza, le porte RZZ con valori negativi nella figura precedente vengono trasformate in valori positivi e vengono aggiunte alcune porte X supplementari.
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})
Per valutare l'impatto delle porte frazionali, valutiamo il numero di porte non locali (CZ e RZZ per questo backend), insieme alla profondità e alla durata dei circuiti e confrontiamo 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
]Passaggio 3: eseguire utilizzando Qiskit primitives
Eseguiamo il circuito transpilato con il backend che supporta le porte frazionali.
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
Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato
È possibile ottenere il valore della funzione kernel misurando la probabilità della stringa di bit tutti zero 00...00 nell'uscita.
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)Output:
[0.929, 0.882, 0.8645, 0.817]
Confronto tra flusso di lavoro e circuito senza porte frazionarie
In questa sezione presentiamo il flusso di lavoro standard di Qiskit Patterns utilizzando un backend che non supporta i gate frazionari. Confrontando i circuiti transpilati, noterete che la versione che utilizza porte frazionarie (della sezione precedente) è più compatta di quella che ne è priva.
# 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})
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]
Confronto tra profondità, durata e fedeltà
In questa sezione, confrontiamo il numero di porte non locali e le fedeltà tra circuiti con e senza porte frazionarie. Ciò evidenzia i potenziali vantaggi dell'uso di porte frazionali in termini di efficienza e qualità dell'esecuzione.
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>
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>
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>
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>
Esempio di hardware su larga scala
In questa sezione, effettuiamo un confronto prestazionale del flusso di lavoro del kernel quantistico con e senza gate frazionari su hardware quantistico con un massimo di 40 qubit.
Passaggi da 1 a 4 combinati
Il flusso di lavoro segue la stessa struttura dell'esempio su piccola scala. Effettuiamo la transpilazione di tutti i circuiti, sia con che senza porte frazionarie, raccogliamo le metriche e infine 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 4Output:
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 questo punto mettiamo a confronto gli indicatori.
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>
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>
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>
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>
Confrontiamo il tempo di utilizzo della QPU con e senza porte frazionali. I risultati nella cella seguente mostrano che i tempi di utilizzo della QPU sono quasi identici.
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
Argomento avanzato: utilizzo esclusivo di porte RX frazionarie
La necessità di modificare il flusso di lavoro quando si utilizzano porte frazionali deriva principalmente dalla limitazione degli angoli delle porte RZZ. Tuttavia, se si utilizzano solo le porte RX frazionali e si escludono le porte RZZ frazionali, è possibile continuare a seguire il flusso di lavoro standard dei modelli Qiskit. Questo approccio può ancora offrire vantaggi significativi, in particolare nei circuiti che coinvolgono un gran numero di porte RX e porte U, riducendo il numero complessivo di porte e migliorando potenzialmente le prestazioni. In questa sezione dimostriamo come ottimizzare i circuiti utilizzando solo porte RX frazionarie e omettendo le porte RZZ.
A tale scopo, è disponibile una funzione di utilità che consente di disabilitare uno specifico gate di base in un oggetto Target. In questo caso, lo 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_targetUtilizziamo come esempio un circuito composto da porte U, CZ e RZZ.
qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")Output:
Per prima cosa transpiliamo il circuito per un backend che non supporta porte frazionarie.
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})
Quindi, si traspone lo stesso circuito utilizzando porte RX frazionarie, escludendo le porte RZZ. Ciò comporta una leggera riduzione del numero totale di porte, grazie all'implementazione più efficiente delle porte 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})
Ottimizzare i gate U con gate RX frazionari
In questa sezione dimostriamo come ottimizzare le porte U utilizzando porte RX frazionali, partendo dallo stesso circuito introdotto nella sezione precedente.
Il circuito viene transpilato utilizzando solo porte RX frazionarie, escludendo le porte RZZ. Introducendo una regola di decomposizione personalizzata, come mostrato di seguito, è possibile ridurre il numero di porte a singolo qubit necessarie per implementare una porta U.
Questa funzionalità è attualmente oggetto di discussione in questa segnalazione su 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 constructor-beta la traduzione fornita dal qiskit-basis-constructor pacchetto.
Di conseguenza, il numero totale di porte logiche risulta ridotto rispetto alla precedente transpilazione.
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})
Passi successivi
Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:
- Guida ai gate frazionari
- Quando non utilizzare i gate frazionari
FoldRzzAngleRiferimento all'API del passaggio di transpiler- Tutorial sulla formazione del kernel Quantum
- La lezione sui kernel quantistici nel corso di apprendimento automatico quantistico