Skip to main content
IBM Quantum Platform

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 :


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 :

  • 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 profondeur et la durée des circuits quantiques. Elles sont particulièrement avantageuses dans les applications qui s'appuient fortement sur RZZR_{ZZ} et RXR_X, 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 :

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 : 0<θπ/20 < \theta \leq \pi/2. 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.

Caution

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 FoldRzzAngle

Activer 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 : K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle,Φ(x)|\Phi(x)\rangle 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, K(x,x)=1K(x, x) = 1. 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 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

Des 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:

Output of the previous code cell

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:

Output of the previous code cell

É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})
Output of the previous code cell

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 K(x,x)K(x, x) 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})
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())

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

Output:

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

Output:

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

Output:

<matplotlib.legend.Legend at 0x120b792b0>
Output of the previous code cell

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 4

Output:

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

Output:

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

Output:

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

Output:

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

Nous 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:

Output of the previous code cell

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

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

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

Etapes suivantes

Recommandations

Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.