Skip to main content
IBM Quantum Platform

Expérience à l'échelle industrielle III

Note

Toshinari Itoko, Tamiya Onodera, Kifumi Numata (19 juillet 2024)

Télécharger le pdf de la conférence originale. Notez que certains extraits de code peuvent devenir obsolètes car il s'agit d'images statiques.

Le temps approximatif d'exécution de cette première expérience par la QPU est de 12 m 30 s. Il y a une expérience supplémentaire ci-dessous qui nécessite environ 4 m.

(Note : ce carnet pourrait ne pas être évalué dans le temps imparti sur le plan ouvert. Veillez à utiliser les ressources informatiques quantiques à bon escient)

import qiskit

qiskit.__version__

Output:

'2.0.2'
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__

Output:

'0.40.1'
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram
from qiskit.visualization import plot_gate_map
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.providers import BackendV2
from qiskit.quantum_info import SparsePauliOp

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Sampler, Estimator, Batch, SamplerOptions

1. Introduction

Passons brièvement en revue les états GHZ et le type de distribution que l'on peut attendre de l'application de Sampler à l'un d'entre eux. Ensuite, nous énoncerons explicitement l'objectif de cette leçon.

1.1 État GHZ

L'état GHZ (état de Greenberger-Horne-Zeilinger) pour les qubits nn est défini comme suit

12(0n+1n)\frac{1}{\sqrt 2}(|0\rangle ^ {\otimes n}+ |1\rangle^ {\otimes n})

Naturellement, il peut être créé pour 6 qubits avec le circuit quantique suivant.

N = 6
qc = QuantumCircuit(N, N)

qc.h(0)
for i in range(N - 1):
    qc.cx(0, i + 1)

# qc.measure_all()
qc.barrier()
qc.measure(list(range(N)), list(range(N)))

qc.draw(output="mpl", idle_wires=False, scale=0.5)

Output:

Output of the previous code cell
print("Depth:", qc.depth())

Output:

Depth: 7

La profondeur n'est pas trop importante, même si vous savez, grâce aux leçons précédentes, que vous pouvez faire mieux. Choisissons un backend et transposons ce circuit.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
backend.name
# or
# backend = service.least_busy(operational=True)
# backend.name

Output:

'ibm_kingston'
pm = generate_preset_pass_manager(3, backend=backend)
qc_transpiled = pm.run(qc)
qc_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", qc_transpiled.depth())
print(
    "Two-qubit Depth:",
    qc_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 27
Two-qubit Depth: 11

Là encore, la profondeur de deux qubits transposée n'est pas trop importante. Mais pour travailler avec un état GHZ sur un plus grand nombre de qubits, il est clair qu'il faut penser à optimiser le circuit. Exécutons cette opération à l'aide de Sampler et voyons ce que donne un véritable ordinateur quantique.

sampler = Sampler(mode=backend)
shots = 40000
job = sampler.run([qc_transpiled], shots=shots)
job_id = job.job_id()
print(job_id)

Output:

d147y20n2txg008jvv70
job.status()

Output:

'DONE'
job = service.job(job_id)
result = job.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

Voici le résultat du circuit GHZ à 6 qubits. Comme vous pouvez le constater, les états de tous les 0|0\rangle 's et de tous les 1|1\rangle 's dominent, mais les erreurs sont substantielles. Essayons de voir quelle taille de circuit GHZ il est possible de réaliser avec un dispositif Eagle, tout en obtenant des résultats où les états corrects ont au moins plus de 50 % de chances de se produire.

1.2 Votre objectif

Construire un circuit GHZ pour 20 qubits ou plus de sorte que, lors de la mesure, la fidélité de votre état GHZ > 0.5 Remarque :

  • Vous devez utiliser un appareil Eagle (min_num_qubits=127) et régler le nombre de tirs sur 40 000.
  • Vous devez exécuter le circuit GHZ à l'aide de la fonction execute_ghz_fidelity et calculer la fidélité à l'aide de la fonction check_ghz_fidelity_from_jobs .

Il s'agit d'un exercice indépendant, dans lequel vous mettez à profit ce que vous avez appris jusqu'à présent dans ce cours.

def execute_ghz_fidelity(
    ghz_circuit: QuantumCircuit,  # Quantum circuit to create GHZ state (Circuit after Routing or without Routing), Classical register name is "c"
    physical_qubits: list[int],  # Physical qubits to represent GHZ state
    backend: BackendV2,
    sampler_options: dict | SamplerOptions | None = None,
):
    N_SHOTS = 40_000
    N = len(physical_qubits)
    base_circuit = ghz_circuit.remove_final_measurements(inplace=False)
    # M_k measurement circuits
    mk_circuits = []
    for k in range(1, N + 1):
        circuit = base_circuit.copy()
        # change measurement basis
        for q in physical_qubits:
            circuit.rz(-k * np.pi / N, q)
            circuit.h(q)
        mk_circuits.append(circuit)

    obs = SparsePauliOp.from_sparse_list(
        [("Z" * N, physical_qubits, 1)], num_qubits=backend.num_qubits
    )
    job_ids = []
    pm1 = generate_preset_pass_manager(1, backend=backend)
    org_transpiled = pm1.run(ghz_circuit)
    mk_transpiled = pm1.run(mk_circuits)
    with Batch(backend=backend):
        sampler = Sampler(options=sampler_options)
        sampler.options.twirling.enable_measure = True
        job = sampler.run([org_transpiled], shots=N_SHOTS)
        job_ids.append(job.job_id())
        # print(f"Sampler job id: {job.job_id()}, shots={N_SHOTS}")
        estimator = Estimator()  # TREX is applied as default
        estimator.options.dynamical_decoupling.enable = True
        estimator.options.execution.rep_delay = 0.0005
        estimator.options.twirling.enable_measure = True
        job2 = estimator.run([(circ, obs) for circ in mk_transpiled], precision=1 / 100)
        job_ids.append(job2.job_id())
        # print("Estimator job id:", job2.job_id())
        return [job.job_id(), job2.job_id()]
def check_ghz_fidelity_from_jobs(
    sampler_job,
    estimator_job,
    num_qubits,
    shots=40_000,
):
    N = num_qubits
    sampler_result = sampler_job.result()
    counts = sampler_result[0].data.c.get_counts()
    all_zero = counts.get("0" * N, 0) / shots
    all_one = counts.get("1" * N, 0) / shots
    top3 = sorted(counts, key=counts.get, reverse=True)[:3]
    print(
        f"N={N}: |00..0>: {counts.get('0'*N, 0)}, |11..1>: {counts.get('1'*N, 0)}, |3rd>: {counts.get(top3[2], 0)} ({top3[2]})"
    )
    print(f"P(|00..0>)={all_zero}, P(|11..1>)={all_one}")

    estimator_result = estimator_job.result()
    non_diagonal = (1 / N) * sum(
        (-1) ** k * estimator_result[k - 1].data.evs for k in range(1, N + 1)
    )
    print(f"REM: Coherence (non-diagonal): {non_diagonal:.6f}")
    fidelity = 0.5 * (all_zero + all_one + non_diagonal)
    sigma = 0.5 * np.sqrt(
        (1 - all_zero - all_one) * (all_zero + all_one) / shots
        + sum(estimator_result[k].data.stds ** 2 for k in range(N)) / (N * N)
    )
    print(f"GHZ fidelity = {fidelity:.6f} ± {sigma:.6f}")
    if fidelity - 2 * sigma > 0.5:
        print("GME (genuinely multipartite entangled) test: Passed")
    else:
        print("GME (genuinely multipartite entangled) test: Failed")
    return {
        "fidelity": fidelity,
        "sigma": sigma,
        "shots": shots,
        "job_ids": [sampler_job.job_id(), estimator_job.job_id()],
    }

Dans ce carnet, nous appliquerons trois stratégies pour créer de bons états GHZ en utilisant 16 qubits et 30 qubits. Ces approches s'appuient sur des stratégies que vous connaissez déjà grâce aux leçons précédentes.


2. Stratégie 1. Sélection de qubits sensible au bruit

Nous commençons par spécifier un backend. Comme nous allons beaucoup travailler avec les propriétés d'un backend spécifique, il est préférable de spécifier un backend, plutôt que d'utiliser l'option least_busy .

backend = service.backend("ibm_strasbourg")  # eagle
twoq_gate = "ecr"
print(f"Device {backend.name} Loaded with {backend.num_qubits} qubits")
print(f"Two Qubit Gate: {twoq_gate}")

Output:

Device ibm_strasbourg Loaded with 127 qubits
Two Qubit Gate: ecr

Nous allons construire un circuit impliquant de nombreuses portes à deux qubits. Il est logique que nous utilisions les qubits qui présentent les erreurs les plus faibles lors de la mise en œuvre de ces portes à deux qubits. Trouver la meilleure "chaîne de qubits" sur la base des erreurs signalées sur 2q-gate est un problème non trivial. Mais nous pouvons définir quelques fonctions pour nous aider à déterminer les meilleurs qubits à utiliser.

coupling_map = backend.target.build_coupling_map(twoq_gate)
G = coupling_map.graph
def to_edges(path):  # create edges list from node paths
    edges = []
    prev_node = None
    for node in path:
        if prev_node is not None:
            if G.has_edge(prev_node, node):
                edges.append((prev_node, node))
            else:
                edges.append((node, prev_node))
        prev_node = node
    return edges


def path_fidelity(path, correct_by_duration: bool = True, readout_scale: float = None):
    """Compute an estimate of the total fidelity of 2-qubit gates on a path.
    If `correct_by_duration` is true, each gate fidelity is worsen by
    scale = max_duration / duration, that is, gate_fidelity^scale.
    If `readout_scale` > 0 is supplied, readout_fidelity^readout_scale
    for each qubit on the path is multiplied to the total fielity.
    The path is given in node indices form, for example, [0, 1, 2].
    An external function `to_edges` is used to obtain edge list, for example, [(0, 1), (1, 2)]."""
    path_edges = to_edges(path)
    max_duration = max(backend.target[twoq_gate][qs].duration for qs in path_edges)

    def gate_fidelity(qpair):
        duration = backend.target[twoq_gate][qpair].duration
        scale = max_duration / duration if correct_by_duration else 1.0
        # 1.25 = (d+1)/d with d = 4
        return max(0.25, 1 - (1.25 * backend.target[twoq_gate][qpair].error)) ** scale

    def readout_fidelity(qubit):
        return max(0.25, 1 - backend.target["measure"][(qubit,)].error)

    total_fidelity = np.prod(
        [gate_fidelity(qs) for qs in path_edges]
    )  # two qubits gate fidelity for each path
    if readout_scale:
        total_fidelity *= (
            np.prod([readout_fidelity(q) for q in path]) ** readout_scale
        )  # multiply readout fidelity
    return total_fidelity


def flatten(paths, cutoff=None):  # cutoff is for not making run time too large
    return [
        path
        for s, s_paths in paths.items()
        for t, st_paths in s_paths.items()
        for path in st_paths[:cutoff]
        if s < t
    ]
N = 16  # Number of qubits to use in the GHZ circuit
num_qubits_in_chain = N

Nous utiliserons les fonctions ci-dessus pour trouver tous les chemins simples de N qubits entre toutes les paires de nœuds du graphe (Référence : all_pairs_all_simple_paths ).

Ensuite, en utilisant la fonction path_fidelity créée ci-dessus, nous trouverons la meilleure chaîne de qubits qui a la plus grande fidélité au chemin.

from functools import partial

%%time
paths = rx.all_pairs_all_simple_paths(
    G.to_undirected(multigraph=False),
    min_depth=num_qubits_in_chain,
    cutoff=num_qubits_in_chain,
)
paths = flatten(paths, cutoff=25)  # If you have time, you could set a larger cutoff.
if not paths:
    raise Exception(
        f"No qubit chain with length={num_qubits_in_chain} exists in {backend.name}. Try smaller num_qubits_in_chain."
    )

print(f"Selecting the best from {len(paths)} candidate paths")

best_qubit_chain = max(
    paths, key=partial(path_fidelity, correct_by_duration=True, readout_scale=1.0)
)
assert len(best_qubit_chain) == num_qubits_in_chain
print(f"Predicted (best possible) process fidelity: {path_fidelity(best_qubit_chain)}")

Output:

Selecting the best from 6046 candidate paths
Predicted (best possible) process fidelity: 0.8929026784775056
CPU times: user 284 ms, sys: 10.9 ms, total: 295 ms
Wall time: 295 ms
np.array(best_qubit_chain)

Output:

array([55, 49, 48, 47, 46, 45, 54, 64, 65, 66, 73, 85, 86, 87, 88, 89],
      dtype=uint64)

Représentons la meilleure chaîne de qubits, en rose, dans le diagramme de couplage.

qubit_color = []
for i in range(133):
    if i in best_qubit_chain:
        qubit_color.append("#ff00dd")  # pink
    else:
        qubit_color.append("#8c00ff")  # purple
plot_gate_map(
    backend, qubit_color=qubit_color, qubit_size=50, font_size=25, figsize=(6, 6)
)

Output:

Output of the previous code cell

2.1 Construisez un circuit GHZ sur la meilleure chaîne de qubits

Nous choisissons un qubit au milieu de la chaîne pour appliquer la porte H en premier. Cela devrait réduire la profondeur du circuit d'environ la moitié.

ghz1 = QuantumCircuit(max(best_qubit_chain) + 1, N)
ghz1.h(best_qubit_chain[N // 2])
for i in range(N // 2, 0, -1):
    ghz1.cx(best_qubit_chain[i], best_qubit_chain[i - 1])
for i in range(N // 2, N - 1, +1):
    ghz1.cx(best_qubit_chain[i], best_qubit_chain[i + 1])
ghz1.barrier()  # for visualization
ghz1.measure(best_qubit_chain, list(range(N)))
ghz1.draw(output="mpl", idle_wires=False, scale=0.5, fold=-1)

Output:

Output of the previous code cell
ghz1.depth()

Output:

10
pm = generate_preset_pass_manager(1, backend=backend)
ghz1_transpiled = pm.run(ghz1)
ghz1_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", ghz1_transpiled.depth())
print(
    "Two-qubit Depth:",
    ghz1_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 27
Two-qubit Depth: 8
opts = SamplerOptions()
res = execute_ghz_fidelity(
    ghz_circuit=ghz1,
    physical_qubits=best_qubit_chain,
    backend=backend,
    sampler_options=opts,
)
job_s = service.job(res[0])  # Use your job id showed above.
job_e = service.job(res[1])
print(job_s.status(), job_e.status())

Output:

DONE DONE

Veillez à exécuter la cellule suivante après que les statuts des travaux ci-dessus sont devenus "DONE", afin de montrer le résultat à l'aide de la fonction check_ghz_fidelity_from_jobs .

N = 16
# Check fidelity from job IDs
res = check_ghz_fidelity_from_jobs(
    sampler_job=job_s,
    estimator_job=job_e,
    num_qubits=N,
)

Output:

N=16: |00..0>: 153, |11..1>: 8681, |3rd>: 2262 (1111111111101111)
P(|00..0>)=0.003825, P(|11..1>)=0.217025
REM: Coherence (non-diagonal): 0.073809
GHZ fidelity = 0.147329 ± 0.002438
GME (genuinely multipartite entangled) test: Failed
result = job_s.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

Ce résultat ne répond pas aux critères. Passons à l'idée suivante.


3. Stratégie 2. Arbre équilibré de qubits

L'idée suivante est de trouver un arbre équilibré de qubits. En utilisant l'arbre plutôt que la chaîne, la profondeur du circuit devrait diminuer. Avant cela, nous supprimons du graphe de couplage les nœuds présentant de "mauvaises" erreurs de lecture et les arêtes présentant de "mauvaises" erreurs de porte.

BAD_READOUT_ERROR_THRESHOLD = 0.1
BAD_ECRGATE_ERROR_THRESHOLD = 0.1
bad_readout_qubits = [
    q
    for q in range(backend.num_qubits)
    if backend.target["measure"][(q,)].error > BAD_READOUT_ERROR_THRESHOLD
]
bad_ecrgate_edges = [
    qpair
    for qpair in backend.target["ecr"]
    if backend.target["ecr"][qpair].error > BAD_ECRGATE_ERROR_THRESHOLD
]
print("Bad readout qubits:", bad_readout_qubits)
print("Bad ECR gates:", bad_ecrgate_edges)

Output:

Bad readout qubits: [19, 28, 41, 72, 91, 114, 120]
Bad ECR gates: []
g = backend.coupling_map.graph.copy().to_undirected()
g.remove_edges_from(
    bad_ecrgate_edges
)  # remove edge first (otherwise might fail with a NoEdgeBetweenNodes error)
g.remove_nodes_from(bad_readout_qubits)

Dessinons le graphe de la carte de couplage sans les mauvaises arêtes et les mauvais qubits.

qubit_color = []
for i in range(133):
    if i in bad_readout_qubits:
        qubit_color.append("#000000")  # black
    else:
        qubit_color.append("#8c00ff")  # purple
line_color = []
for e in backend.target.build_coupling_map().get_edges():
    if e in bad_ecrgate_edges:
        line_color.append("#ffffff")  # white
    else:
        line_color.append("#888888")  # gray
plot_gate_map(
    backend,
    qubit_color=qubit_color,
    line_color=line_color,
    qubit_size=50,
    font_size=25,
    figsize=(6, 6),
)

Output:

Output of the previous code cell

Nous essayons de créer un état GHZ à 16 qubits comme précédemment.

N = 16

Nous appelons la fonction betweenness_centrality pour trouver un qubit pour le nœud racine. Le nœud ayant la valeur la plus élevée de la centralité d'interdépendance se trouve au centre du graphe. Référence : https://www.rustworkx.org/tutorial/betweenness_centrality.html

Vous pouvez également le sélectionner manuellement.

# central = 65 #Select the center node manually
c_degree = dict(rx.betweenness_centrality(g))
central = max(c_degree, key=c_degree.get)
central

Output:

66

En commençant par le nœud racine, générer un arbre par la méthode BFS (breadth first search). Référence : https://qiskit.org/ecosystem/rustworkx/apiref/rustworkx.bfs_search.html #rustworkx-bfs-search

class TreeEdgesRecorder(rx.visit.BFSVisitor):
    def __init__(self, N):
        self.edges = []
        self.N = N

    def tree_edge(self, edge):
        self.edges.append(edge)
        if len(self.edges) >= self.N - 1:
            raise rx.visit.StopSearch()


vis = TreeEdgesRecorder(N)
rx.bfs_search(g, [central], vis)
best_qubits = sorted(list(set(q for e in vis.edges for q in (e[0], e[1]))))
# print('Tree edges:', vis.edges)
print("Qubits selected:", best_qubits)

Output:

Qubits selected: [54, 55, 63, 64, 65, 66, 67, 68, 69, 70, 73, 83, 84, 85, 86, 87]

Représentons les qubits sélectionnés, en rose, dans le diagramme de couplage.

qubit_color = []
for i in range(133):
    if i in bad_readout_qubits:
        qubit_color.append("#000000")  # black
    elif i in best_qubits:
        qubit_color.append("#ff00dd")  # pink
    else:
        qubit_color.append("#8c00ff")  # purple
plot_gate_map(
    backend,
    qubit_color=qubit_color,
    line_color=line_color,
    qubit_size=50,
    font_size=25,
    figsize=(6, 6),
)

Output:

Output of the previous code cell

Montrons la structure arborescente des qubits.

from rustworkx.visualization import graphviz_draw

tree = rx.PyDiGraph()
tree.extend_from_weighted_edge_list(vis.edges)
tree.remove_nodes_from([n for n in range(max(best_qubits) + 1) if n not in best_qubits])

graphviz_draw(tree, method="dot")

Output:

Output of the previous code cell
ghz2 = QuantumCircuit(max(best_qubits) + 1, N)

ghz2.h(tree.edge_list()[0][0])  # apply H-gate to the root node
# Apply CNOT from the root node to the each edge.
for u, v in tree.edge_list():
    ghz2.cx(u, v)
ghz2.barrier()  # for visualization
ghz2.measure(best_qubits, list(range(N)))
ghz2.draw(output="mpl", idle_wires=False, scale=0.5)

Output:

Output of the previous code cell
ghz2.depth()

Output:

8
pm = generate_preset_pass_manager(1, backend=backend)
ghz2_transpiled = pm.run(ghz2)
ghz2_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", ghz2_transpiled.depth())
print(
    "Two-qubit Depth:",
    ghz2_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 22
Two-qubit Depth: 6

La profondeur du circuit est désormais bien inférieure à celle de la structure en chaîne.

res = execute_ghz_fidelity(
    ghz_circuit=ghz2,
    physical_qubits=best_qubits,
    backend=backend,
    sampler_options=opts,
)
job_s = service.job(res[0])  # Use your job id showed above.
job_e = service.job(res[1])
print(job_s.status(), job_e.status())

Output:

DONE DONE
N = 16
# Check fidelity from job IDs
res = check_ghz_fidelity_from_jobs(
    sampler_job=job_s,
    estimator_job=job_e,
    num_qubits=N,
)

Output:

N=16: |00..0>: 9509, |11..1>: 10978, |3rd>: 1795 (1111110111111111)
P(|00..0>)=0.237725, P(|11..1>)=0.27445
REM: Coherence (non-diagonal): 0.606515
GHZ fidelity = 0.559345 ± 0.003188
GME (genuinely multipartite entangled) test: Passed

Nous avons passé avec succès les critères avec la structure arborescente équilibrée!

result = job_s.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

Essayons maintenant de créer un état GHZ plus grand : un état GHZ de 30 qubits.

3.1 N = 30

Nous suivrons le cadre des modèles Qiskit.

  • Étape 1 : Tracer le problème à l'aide de circuits et d'opérateurs quantiques
  • Étape 2 : Optimisation pour le matériel cible
  • Étape 3 : Exécution sur le matériel cible
  • Étape 4 : Post-traitement des résultats

Étape 1 : Mettre en correspondance le problème avec les circuits et opérateurs quantiques Étape 2 : Optimiser pour le matériel cible

Ici, nous sélectionnons manuellement le nœud racine.

central = 62  # Select the center node manually
# c_degree = dict(rx.betweenness_centrality(g))
# central = max(c_degree, key=c_degree.get)
# central
N = 30

vis = TreeEdgesRecorder(N)
rx.bfs_search(g, [central], vis)
best_qubits = sorted(list(set(q for e in vis.edges for q in (e[0], e[1]))))
print("Qubits selected:", best_qubits)

Output:

Qubits selected: [34, 35, 42, 43, 44, 45, 46, 47, 48, 53, 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 71, 73, 77, 84, 85, 86]
qubit_color = []
for i in range(133):
    if i in bad_readout_qubits:
        qubit_color.append("#000000")
    elif i in best_qubits:
        qubit_color.append("#ff00dd")
    else:
        qubit_color.append("#8c00ff")
line_color = []
for e in backend.target.build_coupling_map().get_edges():
    if e in bad_ecrgate_edges:
        line_color.append("#ffffff")
    else:
        line_color.append("#888888")
plot_gate_map(
    backend,
    qubit_color=qubit_color,
    line_color=line_color,
    qubit_size=50,
    font_size=25,
    figsize=(6, 6),
)

Output:

Output of the previous code cell
from rustworkx.visualization import graphviz_draw

tree = rx.PyDiGraph()
tree.extend_from_weighted_edge_list(vis.edges)
tree.remove_nodes_from([n for n in range(max(best_qubits) + 1) if n not in best_qubits])

graphviz_draw(tree, method="dot")

Output:

Output of the previous code cell

La profondeur de cet arbre est de 5.

ghz3 = QuantumCircuit(max(best_qubits) + 1, N)

ghz3.h(tree.edge_list()[0][0])  # apply H-gate to the root node
# Apply CNOT from the root node to the each edge.
for u, v in tree.edge_list():
    ghz3.cx(u, v)
ghz3.barrier()  # for visualization
ghz3.measure(best_qubits, list(range(N)))
ghz3.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
ghz3.depth()

Output:

11
pm = generate_preset_pass_manager(1, backend=backend)
ghz3_transpiled = pm.run(ghz3)
ghz3_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", ghz3_transpiled.depth())
print(
    "Two-qubit Depth:",
    ghz3_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 31
Two-qubit Depth: 9

3.2 Sélectionnez manuellement un autre nœud racine

central = 54

vis = TreeEdgesRecorder(N)
rx.bfs_search(g, [central], vis)
best_qubits = sorted(list(set(q for e in vis.edges for q in (e[0], e[1]))))
print("Qubits selected:", best_qubits)

Output:

Qubits selected: [23, 24, 25, 34, 35, 42, 43, 44, 45, 46, 47, 48, 49, 50, 54, 55, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 73, 84, 85, 86]
from rustworkx.visualization import graphviz_draw

tree = rx.PyDiGraph()
tree.extend_from_weighted_edge_list(vis.edges)
tree.remove_nodes_from([n for n in range(max(best_qubits) + 1) if n not in best_qubits])

graphviz_draw(tree, method="dot")

Output:

Output of the previous code cell

La profondeur de cet arbre est de 6.

ghz3 = QuantumCircuit(max(best_qubits) + 1, N)

ghz3.h(tree.edge_list()[0][0])  # apply H-gate to the root node
# Apply CNOT from the root node to the each edge.
for u, v in tree.edge_list():
    ghz3.cx(u, v)
ghz3.barrier()  # for visualization
ghz3.measure(best_qubits, list(range(N)))
ghz3.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
ghz3.depth()

Output:

11
pm = generate_preset_pass_manager(1, backend=backend)
ghz3_transpiled = pm.run(ghz3)
ghz3_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", ghz3_transpiled.depth())
print(
    "Two-qubit Depth:",
    ghz3_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 30
Two-qubit Depth: 9

De manière surprenante, alors que la profondeur de l'arbre est passée de 5 à 6, la profondeur des deux qubits a diminué de 9 à 8! Utilisons donc ce dernier circuit.

Étape 3 : Exécuter sur le matériel cible

res = execute_ghz_fidelity(
    ghz_circuit=ghz3,
    physical_qubits=best_qubits,
    backend=backend,
    sampler_options=opts,
)
job_s = service.job(res[0])  # Use your job id showed above.
job_e = service.job(res[1])
print(job_s.status(), job_e.status())

Output:

DONE DONE

Étape 4 : Post-traitement des résultats

N = 30
# Check fidelity from job IDs
res = check_ghz_fidelity_from_jobs(
    sampler_job=job_s,
    estimator_job=job_e,
    num_qubits=N,
)

Output:

N=30: |00..0>: 4, |11..1>: 218, |3rd>: 265 (111111111111111011111111111111)
P(|00..0>)=0.0001, P(|11..1>)=0.00545
REM: Coherence (non-diagonal): 0.187073
GHZ fidelity = 0.096312 ± 0.003254
GME (genuinely multipartite entangled) test: Failed

Comme vous pouvez le constater, ce résultat ne répond pas aux critères.

# It will take some time
result = job_s.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

4. Stratégie 3. Exécutez avec les options de suppression des erreurs

Vous pouvez définir les options de suppression des erreurs dans l' V2 de Sampler. Pour plus d'informations, consultez le guide de gestion du bruit de l'échantillonneur et la ExecutionOptionsV2 documentation de l'API.

opts = SamplerOptions()
opts.dynamical_decoupling.enable = True
opts.execution.rep_delay = 0.0005
opts.twirling.enable_gates = True
res = execute_ghz_fidelity(
    ghz_circuit=ghz3,
    physical_qubits=best_qubits,
    backend=backend,
    sampler_options=opts,
)
job_s = service.job(res[0])  # Use your job id showed above.
job_e = service.job(res[1])
print(job_s.status(), job_e.status())

Output:

DONE DONE
N = 30
# Check fidelity from job IDs
res = check_ghz_fidelity_from_jobs(
    sampler_job=job_s,
    estimator_job=job_e,
    num_qubits=N,
)

Output:

N=30: |00..0>: 1459, |11..1>: 1543, |3rd>: 359 (111111111111111111111111111110)
P(|00..0>)=0.036475, P(|11..1>)=0.038575
REM: Coherence (non-diagonal): 0.165532
GHZ fidelity = 0.120291 ± 0.003369
GME (genuinely multipartite entangled) test: Failed
# It will take some time
result = job_s.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

Le résultat s'est amélioré mais ne répond toujours pas aux critères.

Nous avons vu trois idées jusqu'à présent. Vous pouvez combiner et développer ces idées ou trouver vos propres idées pour créer un meilleur circuit GHZ. Reprenons maintenant l'objectif.


5. Votre objectif (récapitulatif)

Construisez un circuit GHZ pour 20 qubits ou plus afin que le résultat de la mesure réponde aux critères : La fidélité de votre état GHZ > 0.5.

  • Vous devez utiliser un appareil Eagle (tel que ibm_brisbane) et définir le nombre de tirs à 40 000.
  • Vous devez exécuter le circuit GHZ à l'aide de la fonction execute_ghz_fidelity et calculer la fidélité à l'aide de la fonction check_ghz_fidelity_from_jobs . Vous devez trouver les plus gros qubits - circuit GHZ qui répondent aux critères. Ecrivez votre code ci-dessous, montrez le résultat avec la fonction check_ghz_fidelity_from_jobs .

Nous allons maintenant mettre en œuvre le même flux de travail GHZ que dans l'article précédent, mais sur un appareil Heron. Cela vous permet d'acquérir une certaine expérience de la présentation et des fonctionnalités des processeurs Heron. Aucune nouvelle stratégie n'est introduite.

Le temps approximatif d'exécution de cette expérience par la QPU est de 4 m 40 s.

service = QiskitRuntimeService()
backend = service.backend("ibm_kingston")
# backend = service.backend("ibm_fez")

twoq_gate = "cz"
print(f"Device {backend.name} Loaded with {backend.num_qubits} qubits")
print(f"Two Qubit Gate: {twoq_gate}")

Output:

Device ibm_kingston Loaded with 156 qubits
Two Qubit Gate: cz
BAD_READOUT_ERROR_THRESHOLD = 0.1
BAD_CZGATE_ERROR_THRESHOLD = 0.1
bad_readout_qubits = [
    q
    for q in range(backend.num_qubits)
    if backend.target["measure"][(q,)].error > BAD_READOUT_ERROR_THRESHOLD
]
bad_czgate_edges = [
    qpair
    for qpair in backend.target["cz"]
    if backend.target["cz"][qpair].error > BAD_CZGATE_ERROR_THRESHOLD
]
print("Bad readout qubits:", bad_readout_qubits)
print("Bad CZ gates:", bad_czgate_edges)

Output:

Bad readout qubits: [112, 113, 120, 131, 146]
Bad CZ gates: [(111, 112), (112, 111), (112, 113), (113, 112), (120, 121), (121, 120), (130, 131), (131, 130), (145, 146), (146, 145), (146, 147), (147, 146)]
g = backend.coupling_map.graph.copy().to_undirected()
g.remove_edges_from(
    bad_czgate_edges
)  # remove edge first (otherwise might fail with a NoEdgeBetweenNodes error)
g.remove_nodes_from(bad_readout_qubits)
qubit_color = []
for i in range(backend.num_qubits):
    if i in bad_readout_qubits:
        qubit_color.append("#000000")  # black
    else:
        qubit_color.append("#8c00ff")  # purple
line_color = []
for e in backend.target.build_coupling_map().get_edges():
    if e in bad_czgate_edges:
        line_color.append("#ffffff")  # white
    else:
        line_color.append("#888888")  # gray
plot_gate_map(
    backend,
    qubit_color=qubit_color,
    line_color=line_color,
    qubit_size=60,
    font_size=30,
    figsize=(10, 10),
)

Output:

Output of the previous code cell
N = 40
central = 100  # Select the center node manually
# c_degree = dict(rx.betweenness_centrality(g))
# central = max(c_degree, key=c_degree.get)
# central
class TreeEdgesRecorder(rx.visit.BFSVisitor):
    def __init__(self, N):
        self.edges = []
        self.N = N

    def tree_edge(self, edge):
        self.edges.append(edge)
        if len(self.edges) >= self.N - 1:
            raise rx.visit.StopSearch()


vis = TreeEdgesRecorder(N)
rx.bfs_search(g, [central], vis)
best_qubits = sorted(list(set(q for e in vis.edges for q in (e[0], e[1]))))
print("Qubits selected:", best_qubits)

Output:

Qubits selected: [61, 65, 76, 77, 80, 81, 82, 83, 84, 85, 86, 87, 96, 97, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 116, 117, 121, 122, 123, 124, 125, 126, 127, 136, 140, 141, 142, 143, 144, 145]
qubit_color = []
for i in range(backend.num_qubits):
    if i in bad_readout_qubits:
        qubit_color.append("#000000")
    elif i in best_qubits:
        qubit_color.append("#ff00dd")
    else:
        qubit_color.append("#8c00ff")
line_color = []
for e in backend.target.build_coupling_map().get_edges():
    if e in bad_czgate_edges:
        line_color.append("#ffffff")
    else:
        line_color.append("#888888")
plot_gate_map(
    backend,
    qubit_color=qubit_color,
    line_color=line_color,
    qubit_size=60,
    font_size=30,
    figsize=(10, 10),
)

Output:

Output of the previous code cell
from rustworkx.visualization import graphviz_draw

tree = rx.PyDiGraph()
tree.extend_from_weighted_edge_list(vis.edges)
tree.remove_nodes_from([n for n in range(max(best_qubits) + 1) if n not in best_qubits])

graphviz_draw(tree, method="dot")

Output:

Output of the previous code cell
ghz_h = QuantumCircuit(max(best_qubits) + 1, N)

ghz_h.h(tree.edge_list()[0][0])  # apply H-gate to the root node
# Apply CNOT from the root node to the each edge.
for u, v in tree.edge_list():
    ghz_h.cx(u, v)
ghz_h.barrier()  # for visualization
ghz_h.measure(best_qubits, list(range(N)))
ghz_h.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
ghz_h.depth()

Output:

15
pm = generate_preset_pass_manager(1, backend=backend)
ghz_h_transpiled = pm.run(ghz_h)
ghz_h_transpiled.draw(output="mpl", idle_wires=False, fold=-1)

Output:

Output of the previous code cell
print("Depth:", ghz_h_transpiled.depth())
print(
    "Two-qubit Depth:",
    ghz_h_transpiled.depth(filter_function=lambda x: x.operation.num_qubits == 2),
)

Output:

Depth: 45
Two-qubit Depth: 13
opts = SamplerOptions()
opts.dynamical_decoupling.enable = True
opts.execution.rep_delay = 0.0005
opts.twirling.enable_gates = True
res = execute_ghz_fidelity(
    ghz_circuit=ghz_h,
    physical_qubits=best_qubits,
    backend=backend,
    sampler_options=opts,
)
job_s = service.job(res[0])  # Use your job id showed above.
job_e = service.job(res[1])
print(job_s.status(), job_e.status())

Output:

RUNNING RUNNING
# Check fidelity from job IDs
N = 40
res = check_ghz_fidelity_from_jobs(
    sampler_job=job_s,
    estimator_job=job_e,
    num_qubits=N,
)

Output:

N=40: |00..0>: 3186, |11..1>: 3277, |3rd>: 620 (1111111011111111111111111111111111111111)
P(|00..0>)=0.07965, P(|11..1>)=0.081925
REM: Coherence (non-diagonal): 0.029987
GHZ fidelity = 0.095781 ± 0.002619
GME (genuinely multipartite entangled) test: Failed
# It will take some time
result = job_s.result()
plot_histogram(result[0].data.c.get_counts(), figsize=(30, 5))

Output:

Output of the previous code cell

Félicitations ! Vous avez terminé votre introduction à l'informatique quantique à grande échelle! Vous êtes maintenant prêt à apporter des contributions significatives dans la quête d'un avantage quantique! Merci d'avoir fait de IBM Quantum® une partie de votre voyage quantique personnel.


Enquête post-formation

Félicitations pour avoir suivi ce cours! Veuillez prendre un moment pour nous aider à améliorer notre cours en répondant au questionnaire rapide suivant. Vos commentaires seront utilisés pour améliorer notre offre de contenu et l'expérience des utilisateurs. Merci !

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