Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Noyaux quantiques avec portes fractionnaires

Estimation d'utilisation : moins de 30 secondes sur un processeur Heron r2 (REMARQUE : Il ne s'agit que d'une estimation. Ton temps d'exécution peut varier.)

Objectifs d'apprentissage​

  • Ce que sont les portes fractionnaires et comment elles réduisent la profondeur et la durée des circuits sur les QPU IBM®

  • Les contraintes associées à l'utilisation des portes fractionnaires (en particulier, la plage d'angle RZZ)

  • Comment construire un flux de travail de noyau quantique qui utilise des portes fractionnaires avec IBM Quantum Compute Service

  • Comment comparer les métriques d'exécution matérielle (profondeur, durée, nombre de portes non locales, fidélité) avec et sans portes fractionnaires

  • Comment utiliser uniquement des portes RX fractionnaires tout en conservant le flux de travail standard Qiskit patterns

Prérequis​

Contexte​

Portes fractionnaires sur les QPU IBM​

Les portes fractionnaires sont des portes quantiques paramétrées qui permettent l'exécution directe de rotations d'angle arbitraire (dans des limites spécifiques), éliminant ainsi la nécessité de les décomposer en plusieurs portes de base. En tirant parti des interactions natives entre les qubits physiques, tu peux implémenter certaines opérations unitaires plus efficacement sur le matériel.

Les QPU IBM Quantum® Heron prennent en charge les portes fractionnaires suivantes :

  • RZZ(θ)R_{ZZ}(\theta) pour 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) pour toute valeur réelle θ\theta

Ces portes peuvent réduire considérablement à la fois la profondeur et la durée des circuits quantiques. Elles sont particulièrement avantageuses dans les applications qui reposent fortement sur RZZR_{ZZ} et RXR_X, telles que la simulation hamiltonienne, l'algorithme d'optimisation approximative quantique (QAOA) et les méthodes de noyaux quantiques. Dans ce tutoriel, nous nous concentrons sur le noyau quantique comme exemple pratique.

Limitations​

Les portes fractionnaires sont actuellement une fonctionnalité expérimentale et comportent quelques contraintes :

Les portes fractionnaires nécessitent un flux de travail différent par rapport à l'approche standard. Ce tutoriel explique comment travailler avec les portes fractionnaires à travers une application pratique.

Consulte les ressources suivantes pour plus de détails sur les portes fractionnaires.

Approches de flux de travail pour la contrainte d'angle RZZ​

Le flux de travail pour l'utilisation des portes fractionnaires suit généralement le flux de travail Qiskit patterns. La différence principale est que tous les angles RZZ doivent satisfaire la contrainte 0<θ≤π/20 < \theta \leq \pi/2. Il existe deux approches pour garantir que cette condition est remplie, comme nous le verrons ci-dessous. Nous recommandons la seconde approche, et dans ce tutoriel, nous la démontrons à travers un exemple inspiré de la méthode de noyau quantique. Pour mieux comprendre où les noyaux quantiques sont susceptibles d'être utiles, nous recommandons de lire Liu, Arunachalam & Temme (2021).

Tu peux également suivre le tutoriel Entraînement de noyaux quantiques et la leçon Noyaux quantiques dans le cours Apprentissage automatique quantique sur 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. Générer des valeurs de paramètres qui satisfont la contrainte d'angle RZZ​

Si tu es certain que tous les angles RZZ se situent dans la plage valide, tu peux suivre le flux de travail standard Qiskit patterns. Dans ce cas, tu soumets simplement les valeurs de paramètres dans le cadre d'un PUB. Le flux de travail se déroule comme suit.

pm = generate_preset_pass_manager(backend=backend, ...)
t_circuit = pm.run(circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit, parameter_values)])
estimator.run([(t_circuit, t_observable, parameter_values)])

Si tu tentes de soumettre un PUB qui inclut une porte RZZ avec un angle en dehors de la plage valide, tu rencontreras un message d'erreur tel que :

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

Pour éviter cette erreur, utilise la seconde approche décrite ci-dessous.

2. Assigner les valeurs de paramètres aux circuits avant la transpilation​

Le package qiskit-ibm-runtime fournit une passe de transpilation spécialisée appelée FoldRzzAngle. Cette passe transforme les circuits quantiques de sorte que tous les angles RZZ respectent la contrainte d'angle RZZ. Si tu fournis le backend à generate_preset_pass_manager ou transpile, Qiskit applique automatiquement FoldRzzAngle aux circuits quantiques. Cette approche te demande d'assigner les valeurs de paramètres aux circuits quantiques avant la transpilation. Le flux de travail se déroule comme suit.

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

Note que ce flux de travail entraîne un coût de calcul plus élevé que la première approche, car il implique d'assigner des valeurs de paramètres aux circuits quantiques et de stocker localement les circuits aux paramètres liés.

attention

Prends note d'un problème connu dans qiskit-ibm-runtime v0.47.0 où des portes RZZ avec des angles invalides pourraient subsister dans les circuits même après la transpilation dans certains scénarios.

Voir qiskit-ibm-runtime#2441 pour suivre l'avancement de ce problème. Nous recommandons la solution de contournement suivante jusqu'à ce qu'il soit résolu.

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

Configuration requise​

Avant de commencer ce tutoriel, assure-toi d'avoir installé les éléments suivants :

  • Qiskit SDK v2.0 ou version ultérieure, avec la prise en charge de la visualisation
  • Qiskit Runtime v0.41 ou version ultérieure (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 ou version ultérieure (pip install qiskit-aer)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

Configuration​

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

Activer les portes fractionnaires et vérifier les portes de base​

Pour utiliser les portes fractionnaires, tu peux obtenir un backend qui les prend en charge en définissant l'option use_fractional_gates=True. Si le backend prend en charge les portes fractionnaires, tu verras rzz et rx parmi ses portes 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']

Exemple de simulateur à petite échelle​

Dans cette section, nous parcourons les quatre étapes du flux de travail Qiskit patterns sur un simulateur, en utilisant le circuit de noyau quantique comme exemple pratique.

Étape 1 : Mapper les entrées classiques sur un problème quantique​

Circuit de noyau quantique​

Dans cette section, nous explorons le circuit de noyau quantique utilisant des portes RZZ pour présenter le flux de travail des portes fractionnaires.

Nous commençons par construire un circuit quantique pour calculer les entrées individuelles de la matrice de noyau. Cela se fait en combinant des circuits de carte de caractéristiques ZZ avec un recouvrement unitaire. La fonction de noyau prend des vecteurs dans l'espace mappé par les caractéristiques et retourne leur produit scalaire comme entrée de la matrice de noyau : K(x,y)=⟨Φ(x)∣Φ(y)⟩,K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, où ∣Φ(x)⟩|\Phi(x)\rangle représente l'état quantique mappé par les caractéristiques.

Nous construisons manuellement un circuit de carte de caractéristiques ZZ utilisant des portes RZZ. Bien que Qiskit fournisse un zz_feature_map intégré, celui-ci ne prend pas actuellement en charge les portes RZZ dans Qiskit v2.4.1 (voir le problème).

Ensuite, nous calculons la fonction de noyau pour des entrées identiques - par exemple, K(x,x)=1K(x, x) = 1. Sur des ordinateurs quantiques bruités, cette valeur peut être inférieure à 1 en raison du bruit. Un résultat plus proche de 1 indique un niveau de bruit plus faible lors de l'exécution. Dans ce tutoriel, nous appelons cette valeur la fidélité, définie par 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

Les circuits de noyau quantique et leurs valeurs de paramètres correspondantes sont générés pour des systèmes de 4 à 40 qubits, et leurs fidélités sont ensuite évaluées.

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

Le circuit à quatre qubits est visualisé ci-dessous.

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

Output of the previous code cell

Dans le flux de travail standard Qiskit patterns, les valeurs de paramètres sont généralement transmises à la primitive Sampler ou Estimator dans le cadre d'un PUB. Cependant, lors de l'utilisation d'un backend prenant en charge les portes fractionnaires, ces valeurs de paramètres doivent être explicitement assignées au circuit quantique avant la transpilation.

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

Étape 2 : Optimiser le problème pour l'exécution sur le matériel quantique​

Nous transpilons ensuite le circuit en utilisant le gestionnaire de passes suivant le flux de travail standard Qiskit patterns. En fournissant un backend prenant en charge les portes fractionnaires à generate_preset_pass_manager, une passe spécialisée appelée FoldRzzAngle est automatiquement incluse. Cette passe modifie le circuit pour respecter les contraintes d'angle RZZ. En conséquence, les portes RZZ avec des valeurs négatives dans la figure précédente sont transformées en valeurs positives, et quelques portes X supplémentaires sont ajoutées.

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

Pour évaluer l'impact des portes fractionnaires, nous mesurons le nombre de portes non locales (CZ et RZZ pour ce backend), ainsi que les profondeurs et durées des circuits, et comparons ces métriques à celles d'un flux de travail standard plus loin.

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
]

Étape 3 : Exécuter en utilisant les primitives Qiskit​

Nous exécutons le circuit transpilé avec le backend prenant en charge les portes fractionnaires.

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

Étape 4 : Post-traiter et retourner le résultat dans le format classique souhaité​

Tu peux obtenir la valeur de la fonction de noyau K(x,x)K(x, x) en mesurant la probabilité de la chaîne de bits tout-zéro 00...00 dans la sortie.

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

Comparaison du flux de travail et du circuit sans portes fractionnaires​

Dans cette section, nous présentons le flux de travail standard Qiskit patterns utilisant un backend qui ne prend pas en charge les portes fractionnaires. En comparant les circuits transpilés, tu remarqueras que la version utilisant les portes fractionnaires (de la section précédente) est plus compacte que celle sans portes fractionnaires.

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

Comparaison des profondeurs, durées et fidélités​

Dans cette section, nous comparons le nombre de portes non locales et les fidélités entre les circuits avec et sans portes fractionnaires. Cela met en évidence les avantages potentiels de l'utilisation des portes fractionnaires en termes d'efficacité d'exécution et de 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

Exemple matériel à grande échelle​

Dans cette section, nous comparons le flux de travail de noyau quantique avec et sans portes fractionnaires sur du matériel quantique comptant jusqu'à 40 qubits.

Étapes 1 à 4 combinées​

Le flux de travail suit la même structure que l'exemple à petite échelle. Nous transpilons tous les circuits avec et sans portes fractionnaires, collectons des métriques, puis soumettons les circuits au matériel quantique réel.

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

Nous comparons ensuite les métriques.

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

Nous comparons le temps d'utilisation du QPU avec et sans portes fractionnaires. Les résultats dans la cellule suivante montrent que les temps d'utilisation du QPU sont presque identiques.

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

Sujet avancé : Utiliser uniquement les portes RX fractionnaires​

La nécessité d'un flux de travail modifié lors de l'utilisation des portes fractionnaires provient principalement de la restriction sur les angles des portes RZZ. Cependant, si tu utilises uniquement les portes RX fractionnaires et exclus les portes RZZ fractionnaires, tu peux continuer à suivre le flux de travail standard Qiskit patterns. Cette approche peut tout de même offrir des avantages notables, en particulier dans les circuits qui impliquent un grand nombre de portes RX et de portes U, en réduisant le nombre total de portes et en améliorant potentiellement les performances. Dans cette section, nous démontrons comment optimiser tes circuits en utilisant uniquement les portes RX fractionnaires, tout en omettant les portes RZZ.

Pour ce faire, nous fournissons une fonction utilitaire qui te permet de désactiver une porte de base spécifique dans un objet Target. Ici, nous l'utilisons pour désactiver les portes 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

Nous utilisons un circuit composé de portes U, CZ et RZZ comme exemple.

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

Output of the previous code cell

Nous transpilons d'abord le circuit pour un backend qui ne prend pas en charge les portes fractionnaires.

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

Ensuite, nous transpilons le même circuit en utilisant les portes RX fractionnaires, tout en excluant les portes RZZ. Cela entraîne une légère réduction du nombre total de portes, grâce à l'implémentation plus efficace des portes 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

Optimiser les portes U avec les portes RX fractionnaires​

Dans cette section, nous démontrons comment optimiser les portes U en utilisant des portes RX fractionnaires, en nous appuyant sur le même circuit introduit dans la section précédente. Nous transpilons le circuit en utilisant uniquement les portes RX fractionnaires, en excluant les portes RZZ. En introduisant une règle de décomposition personnalisée, comme illustré ci-dessous, nous pouvons réduire le nombre de portes à un seul qubit nécessaires pour implémenter une porte U.

Cette fonctionnalité est actuellement en discussion dans ce problème 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)

Ensuite, nous appliquons le transpileur en utilisant la traduction constructor-beta fournie par le package qiskit-basis-constructor. En conséquence, le nombre total de portes est réduit par rapport à la transpilation précédente.

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

Étapes suivantes​

Recommandations

Si tu as trouvé ce travail intéressant, tu pourrais être intéressé par les documents suivants :