Noyaux quantiques avec des portes fractionnaires
Estimation de l'utilisation : moins de 30 secondes sur un processeur Heron r2 (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)
Acquis d'apprentissage
À l'issue de ce tutoriel, vous devriez avoir compris :
- Qu'est-ce qu'une porte fractionnaire et comment permet-elle de réduire la profondeur et la durée des circuits sur les QPU d' IBM®?
- Les contraintes liées à l'utilisation de portes fractionnaires (en particulier, la plage d'angles RZZ)
- Comment mettre en place un workflow de noyau quantique utilisant des portes fractionnaires avec le service de calcul « IBM Quantum »
- Comment comparer les indicateurs 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 de Qiskit Patterns
Prérequis
Nous vous recommandons de vous familiariser avec les sujets suivants avant de commencer ce tutoriel :
- Le flux de travail « Patterns » de Qiskit
- Guide des portes fractionnaires
- Le tutoriel sur l'entraînement des noyaux quantiques et la leçon consacrée aux noyaux quantiques du cours sur l'apprentissage automatique quantique
Arrière-plan
Portes fractionnaires sur les QPU d' IBM
Les portes fractionnaires sont des portes quantiques paramétrées qui permettent l'exécution directe de rotations d'angle arbitraire (dans certaines limites), ce qui évite de devoir les décomposer en plusieurs portes de base. En tirant parti des interactions natives entre les qubits physiques, il est possible de mettre en œuvre certaines opérations unitaires de manière plus efficace sur le matériel.
IBM Les QPU Quantum® Heron prennent en charge les portes fractionnaires suivantes :
- pour
- pour toute valeur réelle
Ces portes peuvent réduire considérablement la profondeur et la durée des circuits quantiques. Elles sont particulièrement avantageuses dans les applications qui s'appuient fortement sur et , comme la simulation hamiltonienne, l'algorithme d'optimisation approximative quantique (QAOA) et les méthodes à noyau quantique. Dans ce tutoriel, nous nous concentrons sur le noyau quantique en tant qu'exemple pratique.
Limites
Les portes fractionnaires sont actuellement une fonctionnalité expérimentale et s'accompagnent de quelques contraintes :
- est limitée aux angles dans l'intervalle .
- L'utilisation de portes fractionnaires n'est pas prise en charge pour les circuits dynamiques, le tournoiement de Pauli, l' annulation probabiliste des erreurs (PEC) et l' extrapolation à bruit nul (ZNE) (à l'aide de l' amplification probabiliste des erreurs (PEA)).
Les portes fractionnées nécessitent un flux de travail différent par rapport à l'approche standard. Ce tutoriel explique comment travailler avec des portes fractionnaires à travers une application pratique.
Pour plus de détails sur les portes fractionnaires, voir ce qui suit.
Approches de workflow pour la contrainte d'angle RZZ
Le processus d'utilisation des portes fractionnaires suit généralement celui défini par les modèles Qiskit. La principale différence réside dans le fait que tous les angles RZZ doivent satisfaire la contrainte suivante : . Il existe deux approches pour s'assurer que cette condition est respectée, comme nous le verrons ci-dessous. Nous recommandons la deuxième approche, et dans ce tutoriel, nous l'illustrons à l'aide d'un exemple inspiré de la méthode du noyau quantique. Pour mieux comprendre dans quels contextes les noyaux quantiques sont susceptibles d'être utiles, nous vous recommandons de consulter l'article de Liu, Arunachalam et Temme (2021).
Vous pouvez également suivre le tutoriel de formation sur les noyaux quantiques ainsi que la leçon consacrée aux noyaux quantiques dans le cadre du cours sur l'apprentissage automatique quantique disponible sur IBM Quantum® Learning.
1. Générer des valeurs de paramètres qui satisfont la contrainte d'angle RZZ
Si vous êtes sûr que tous les angles RZZ se situent dans la plage valide, vous pouvez suivre le processus standard des modèles Qiskit. Dans ce cas, il suffit de soumettre les valeurs des paramètres dans le cadre d'une PUB. Le processus 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 vous tentez de soumettre un site PUB qui comprend une porte RZZ dont l'angle n'est pas valide, vous obtiendrez un message d'erreur tel que le suivant :
'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, utilisez la deuxième méthode décrite ci-dessous.
2. Attribuer des valeurs de paramètres aux circuits avant la transpilation
Ce qiskit-ibm-runtime package fournit une étape de transcompilation spécialisée appelée FoldRzzAngle.
Cette étape transforme les circuits quantiques de manière à ce que tous les angles RZZ respectent la contrainte relative à ces angles.
Si vous fournissez le backend à generate_preset_pass_manager ou transpile, Qiskit applique FoldRzzAngle automatiquement aux circuits quantiques.
Cette approche nécessite d'attribuer des valeurs de paramètres aux circuits quantiques avant la transpilation.
Le déroulement des opérations est le suivant.
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)])Il convient de noter que ce workflow entraîne un coût de calcul plus élevé que la première approche, car il implique d'attribuer des valeurs de paramètres aux circuits quantiques et de stocker localement les circuits associés à ces paramètres.
Veuillez noter un problème connu, décrit à l'adresse qiskit-ibm-runtimev0.47.0, selon lequel, dans certains cas, des portes RZZ présentant des angles non valides peuvent subsister dans les circuits même après la transpilation.
Consultez la page qiskit-ibm-runtime#2441 pour suivre l'évolution de ce problème. Nous vous recommandons d'appliquer la solution de contournement suivante jusqu'à ce que le problème 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(...)Exigences
Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :
- Qiskit SDK v2.0 ou plus tard, avec 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 FoldRzzAngleActiver les portes fractionnaires et vérifier les portes de base
Pour utiliser des portes fractionnaires, vous pouvez 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, vous verrez rzz et rx listés 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")Output:
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 allons passer en revue les quatre étapes du workflow « Qiskit Patterns » sur un simulateur, en prenant comme exemple concret le circuit « quantum kernel ».
Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique
Circuit à noyau quantique
Dans cette section, nous explorons le circuit du noyau quantique à l'aide de portes RZZ afin d'introduire le flux de travail pour les portes fractionnaires.
Nous commençons par construire un circuit quantique pour calculer les entrées individuelles de la matrice du noyau. Pour ce faire, on combine des circuits de cartes de caractéristiques ZZ avec un chevauchement unitaire. La fonction noyau prend des vecteurs dans l'espace cartographié des caractéristiques et renvoie leur produit intérieur en tant qu'entrée de la matrice noyau : où représente l'état quantique représenté par les caractéristiques.
Nous construisons manuellement un circuit de carte de caractéristiques ZZ à l'aide de portes RZZ.
zz_feature_mapBien que Qiskit propose une fonctionnalité intégrée, il ne prend actuellement pas en charge les portes RZZ, selon la version de Qiskit disponible à l'adresse v2.4.1 ( voir le ticket ).
Ensuite, nous calculons la fonction noyau pour des entrées identiques - par exemple, . Sur les ordinateurs quantiques bruyants, cette valeur peut être inférieure à 1 en raison du bruit. Un résultat plus proche de 1 indique un bruit plus faible dans l'exécution. Dans ce tutoriel, nous appelons cette valeur la fidélité, définie comme suit
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_shotsDes circuits à 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 représenté ci-dessous.
circuits[0].draw("mpl", fold=-1)Output:
Dans le flux de travail standard des modèles Qiskit, les valeurs des paramètres sont généralement transmises à l'échantillonneur ou à l'estimateur primitif dans le cadre d'une PUB. Toutefois, lorsqu'on utilise un backend qui prend en charge les portes fractionnaires, ces valeurs de paramètres doivent être explicitement attribué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:
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Nous transpilons ensuite le circuit à l'aide du gestionnaire de passes en suivant le schéma standard de Qiskit.
En fournissant un backend qui supporte les portes fractionnaires à generate_preset_pass_manager, une passe spécialisée appelée FoldRzzAngle est automatiquement incluse.
Ce passage modifie le circuit pour qu'il soit conforme aux contraintes de l'angle RZZ.
Par conséquent, les portes RZZ de la figure précédente qui avaient des valeurs négatives 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)Output:
OrderedDict({'rz': 35, 'rzz': 18, 'x': 13, 'rx': 9, 'measure': 4, 'barrier': 2})
Pour évaluer l'impact des portes fractionnaires, nous évaluons le nombre de portes non locales (CZ et RZZ pour ce backend), ainsi que la profondeur et la durée des circuits, et nous comparons ces mesures à celles d'un flux de travail standard par la suite.
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écutez à l'aide d' Qiskit primitives
Nous exécutons le circuit transpilé avec le backend qui prend 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())Output:
085ce928-767e-4200-93bf-3905e5411cfe
Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité
Vous pouvez obtenir la valeur de la fonction noyau en mesurant la probabilité de la chaîne de bits entièrement nulle 00...00 dans la sortie.
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)Output:
[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 de Qiskit Patterns à l'aide d'un backend qui ne prend pas en charge les portes fractionnaires. En comparant les circuits transpilés, vous remarquerez que la version utilisant des portes fractionnaires (présentée dans la section précédente) est plus compacte que celle qui n'en utilise pas.
# 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]
Comparaison des niveaux de profondeur, des durées et des degrés de fidélité
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 de portes fractionnées en termes d'efficacité et de qualité d'exécution.
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>
Exemple de matériel à grande échelle
Dans cette section, nous comparons les performances du workflow du 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, qu'ils comportent ou non des portes fractionnaires, nous recueillons des métriques, puis nous soumettons ces circuits à du 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 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
Nous comparons ensuite les indicateurs.
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>
Nous comparons le temps d'utilisation du QPU avec et sans portes fractionnaires. Les résultats de 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")Output:
no fractional gates: 8 seconds
fractional gates: 8 seconds
Sujet avancé : Utilisation exclusive de portes RX fractionnaires
La nécessité de modifier le flux de travail lors de l'utilisation de portes fractionnées découle principalement de la restriction des angles de porte RZZ. Toutefois, si vous n'utilisez que les portes RX fractionnaires et excluez les portes RZZ fractionnaires, vous pouvez continuer à suivre le flux de travail standard des motifs Qiskit. Cette approche peut encore offrir des avantages significatifs, en particulier dans les circuits qui comportent 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 montrons comment optimiser vos circuits en utilisant uniquement des portes RX fractionnaires, tout en omettant les portes RZZ.
Pour ce faire, nous fournissons une fonction utilitaire qui vous 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_targetNous prenons comme exemple un circuit composé de portes U, CZ et RZZ.
qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")Output:
Nous transposons 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")Output:
OrderedDict({'rz': 23, 'sx': 16, 'cz': 4})
Ensuite, nous transposons le même circuit en utilisant des portes RX fractionnaires, tout en excluant les portes RZZ. Il en résulte une légère réduction du nombre total de portes, grâce à la mise en œuvre 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")Output:
OrderedDict({'rz': 22, 'sx': 14, 'cz': 4, 'rx': 1})
Optimiser les portes U avec des portes RX fractionnaires
Dans cette section, nous démontrons comment optimiser les portes U en utilisant des portes RX fractionnaires, en nous basant sur le même circuit que celui présenté dans la section précédente.
Nous transposons le circuit en utilisant uniquement des portes RX fractionnaires, à l'exclusion des portes RZZ. En introduisant une règle de décomposition personnalisée, comme indiqué ci-dessous, nous pouvons réduire le nombre de portes à un qubit nécessaires pour mettre en œuvre une porte U.
Cette fonctionnalité fait actuellement l'objet de discussions dans ce ticket sur 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 utilisons le transpileur en nous appuyant sur constructor-beta la traduction fournie par le qiskit-basis-constructor paquet.
Par conséquent, 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")Output:
OrderedDict({'rz': 16, 'rx': 9, 'cz': 4})
Etapes suivantes
Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :
- Guide sur les portes fractionnaires
- Quand ne pas utiliser les portes fractionnaires
FoldRzzAngleRéférence de l'API du passage de transpilation- Tutoriel sur l'entraînement du noyau Quantum
- La leçon sur les noyaux quantiques dans le cours sur l'apprentissage automatique quantique