Sari la conținutul principal

Creează și transpilează față de Backend-uri personalizate

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit rustworkx
# Don't use SVGs for this file because the images are too large,
# and the SVGs are much larger than their PNGs equivalents.
%config InlineBackend.figure_format='png'
```json

{/* cspell:ignore multichip interchip Lasciate ogne speranza voi ch'intrate */}
{/*
DO NOT EDIT THIS CELL!!!
This cell's content is generated automatically by a script. Anything you add
here will be removed next time the notebook is run. To add new content, create
a new cell before or after this one.
*/}

<details>
<summary><b>Versiuni de pachete</b></summary>

Codul de pe această pagină a fost dezvoltat folosind următoarele cerințe.
Îți recomandăm să folosești aceste versiuni sau unele mai noi.

qiskit[all]~=2.3.0

</details>
{/* cspell:ignore LOCC */}

Una dintre cele mai puternice funcționalități ale Qiskit este capacitatea de a suporta configurații unice de dispozitive. Qiskit este construit să fie agnostic față de furnizorul hardware-ului cuantic pe care îl folosești, iar furnizorii pot configura obiectul `BackendV2` conform propriilor proprietăți unice ale dispozitivului. Acest subiect demonstrează cum să îți configurezi propriul Backend și să transpilezi Circuit-uri cuantice față de ele.

Poți crea obiecte `BackendV2` unice cu geometrii sau porți de bază diferite și să îți transpilezi Circuit-urile ținând cont de acele configurații. Exemplul de mai jos acoperă un Backend cu o rețea de Qubiți disjuncți, ale cărei porți de bază diferă de-a lungul muchiilor față de interior.
## Înțelege interfețele Provider, BackendV2 și Target \{#understand-the-provider-backendv2-and-target-interfaces}

Înainte de a începe, este util să înțelegi utilizarea și scopul obiectelor [`Provider`](../api/qiskit/providers), [`BackendV2`](../api/qiskit/qiskit.providers.BackendV2) și [`Target`](../api/qiskit/qiskit.transpiler.Target).

- Dacă ai un dispozitiv cuantic sau un simulator pe care vrei să îl integrezi în SDK-ul Qiskit, trebuie să îți scrii propria clasă `Provider`. Această clasă servește un singur scop: să obțină obiectele Backend pe care le furnizezi. Aici sunt gestionate orice sarcini necesare de credențiale și/sau autentificare. Odată instanțiat, obiectul provider va furniza o listă de Backend-uri, precum și capacitatea de a achiziționa/instanția Backend-uri.

- Apoi, clasele de Backend oferă interfața dintre SDK-ul Qiskit și hardware-ul sau simulatorul care va executa Circuit-urile. Acestea includ toate informațiile necesare pentru a descrie un Backend Transpiler-ului, astfel încât acesta să poată optimiza orice Circuit conform constrângerilor sale. Un `BackendV2` este alcătuit din patru componente principale:
- O proprietate [`Target`](../api/qiskit/qiskit.transpiler.Target), care conține o descriere a constrângerilor Backend-ului și furnizează un model al Backend-ului pentru Transpiler
- O proprietate `max_circuits` care definește o limită pentru numărul de Circuit-uri pe care un Backend le poate executa într-un singur job
- O metodă `run()` care acceptă trimiteri de job-uri
- Un set de `_default_options` pentru a defini opțiunile configurabile de utilizator și valorile lor implicite
## Creează un BackendV2 personalizat \{#create-a-custom-backendv2}

Obiectul `BackendV2` este o clasă abstractă folosită pentru toate obiectele Backend create de un furnizor (fie în `qiskit.providers`, fie în altă bibliotecă precum [`qiskit_ibm_runtime.IBMBackend`](../api/qiskit-ibm-runtime/ibm-backend)). Așa cum am menționat mai sus, aceste obiecte conțin mai multe atribute, inclusiv un [`Target`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.Target). `Target`-ul conține informații care specifică atributele Backend-ului — cum ar fi [`Coupling Map`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.CouplingMap), lista de [`Instructions`](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.Instruction) și altele — către Transpiler. Pe lângă `Target`, se pot defini și detalii la nivel de puls, cum ar fi [`DriveChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.DriveChannel) sau [`ControlChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.ControlChannel).

Următorul exemplu demonstrează această personalizare prin crearea unui Backend simulat multi-chip, unde fiecare chip posedă o conectivitate heavy-hex. Exemplul specifică setul de Gate-uri cu doi Qubiți al Backend-ului ca [`CZGates`](../api/qiskit/qiskit.circuit.library.CZGate) în interiorul fiecărui chip și [`CXGates`](../api/qiskit/qiskit.circuit.library.ECRGate) între chip-uri. Mai întâi, creează-ți propriul `BackendV2` și personalizează-i `Target`-ul cu Gate-uri de unu și doi Qubiți conform constrângerilor descrise anterior.

<Admonition type="tip" title="Biblioteca graphviz">
Afișarea unei hărți de cuplare necesită instalarea bibliotecii [`graphviz`](https://graphviz.org/).
</Admonition>

```python
import numpy as np
import rustworkx as rx

from qiskit.providers import BackendV2, Options
from qiskit.transpiler import Target, InstructionProperties
from qiskit.circuit.library import XGate, SXGate, RZGate, CZGate, ECRGate
from qiskit.circuit import Measure, Delay, Parameter, Reset
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_gate_map

class FakeLOCCBackend(BackendV2):
"""Fake multi chip backend."""

def __init__(self, distance=3, number_of_chips=3):
"""Instantiate a new fake multi chip backend.

Args:
distance (int): The heavy hex code distance to use for each chips'
coupling map. This number **must** be odd. The distance relates
to the number of qubits by:
:math:`n = \\frac{5d^2 - 2d - 1}{2}` where :math:`n` is the
number of qubits and :math:`d` is the ``distance``
number_of_chips (int): The number of chips to have in the multichip backend
each chip will be a heavy hex graph of ``distance`` code distance.
"""
super().__init__(name="Fake LOCC backend")
# Create a heavy-hex graph using the rustworkx library, then instantiate a new target
self._graph = rx.generators.directed_heavy_hex_graph(
distance, bidirectional=False
)
num_qubits = len(self._graph) * number_of_chips
self._target = Target(
"Fake multi-chip backend", num_qubits=num_qubits
)

# Generate instruction properties for single qubit gates and a measurement, delay,
# and reset operation to every qubit in the backend.
rng = np.random.default_rng(seed=12345678942)
rz_props = {}
x_props = {}
sx_props = {}
measure_props = {}
delay_props = {}

# Add 1q gates. Globally use virtual rz, x, sx, and measure
for i in range(num_qubits):
qarg = (i,)
rz_props[qarg] = InstructionProperties(error=0.0, duration=0.0)
x_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
sx_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
measure_props[qarg] = InstructionProperties(
error=rng.uniform(1e-3, 1e-1),
duration=rng.uniform(1e-8, 9e-7),
)
delay_props[qarg] = None
self._target.add_instruction(XGate(), x_props)
self._target.add_instruction(SXGate(), sx_props)
self._target.add_instruction(RZGate(Parameter("theta")), rz_props)
self._target.add_instruction(Measure(), measure_props)
self._target.add_instruction(Reset(), measure_props)

self._target.add_instruction(Delay(Parameter("t")), delay_props)
# Add chip local 2q gate which is CZ
cz_props = {}
for i in range(number_of_chips):
for root_edge in self._graph.edge_list():
offset = i * len(self._graph)
edge = (root_edge[0] + offset, root_edge[1] + offset)
cz_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)
self._target.add_instruction(CZGate(), cz_props)

cx_props = {}
# Add interchip 2q gates which are ecr (effectively CX)
# First determine which nodes to connect
node_indices = self._graph.node_indices()
edge_list = self._graph.edge_list()
inter_chip_nodes = {}
for node in node_indices:
count = 0
for edge in edge_list:
if node == edge[0]:
count += 1
if count == 1:
inter_chip_nodes[node] = count
# Create inter-chip ecr props
cx_props = {}
inter_chip_edges = list(inter_chip_nodes.keys())
for i in range(1, number_of_chips):
offset = i * len(self._graph)
edge = (
inter_chip_edges[1] + (len(self._graph) * (i - 1)),
inter_chip_edges[0] + offset,
)
cx_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)

self._target.add_instruction(ECRGate(), cx_props)

@property
def target(self):
return self._target

@property
def max_circuits(self):
return None

@property
def graph(self):
return self._graph

@classmethod
def _default_options(cls):
return Options(shots=1024)

def run(self, circuit, **kwargs):
raise NotImplementedError(
"This backend does not contain a run method"
)

Vizualizează backends

Poți vizualiza graful de conectivitate al acestei noi clase cu metoda plot_gate_map() din modulul qiskit.visualization. Această metodă, împreună cu plot_coupling_map() și plot_circuit_layout(), sunt instrumente utile pentru vizualizarea aranjamentului qubiților unui Backend, precum și a modului în care un Circuit este distribuit pe qubiții unui Backend. Acest exemplu creează un Backend cu trei cipuri mici de tip heavy-hex. Se specifică un set de coordonate pentru aranjarea qubiților, precum și un set de culori personalizate pentru porțile cu doi qubiți diferite.

backend = FakeLOCCBackend(3, 3)

target = backend.target
coupling_map_backend = target.build_coupling_map()

coordinates = [
(3, 1),
(3, -1),
(2, -2),
(1, 1),
(0, 0),
(-1, -1),
(-2, 2),
(-3, 1),
(-3, -1),
(2, 1),
(1, -1),
(-1, 1),
(-2, -1),
(3, 0),
(2, -1),
(0, 1),
(0, -1),
(-2, 1),
(-3, 0),
]

single_qubit_coordinates = []
total_qubit_coordinates = []

for coordinate in coordinates:
total_qubit_coordinates.append(coordinate)

for coordinate in coordinates:
total_qubit_coordinates.append(
(-1 * coordinate[0] + 1, coordinate[1] + 4)
)

for coordinate in coordinates:
total_qubit_coordinates.append((coordinate[0], coordinate[1] + 8))

line_colors = ["#adaaab" for edge in coupling_map_backend.get_edges()]
ecr_edges = []

# Get tuples for the edges which have an ecr instruction attached
for instruction in target.instructions:
if instruction[0].name == "ecr":
ecr_edges.append(instruction[1])

for i, edge in enumerate(coupling_map_backend.get_edges()):
if edge in ecr_edges:
line_colors[i] = "#000000"
print(backend.name)
plot_gate_map(
backend,
plot_directed=True,
qubit_coordinates=total_qubit_coordinates,
line_color=line_colors,
)
Fake LOCC backend

Rezultatul celulei de cod anterioare

Fiecare Qubit este etichetat, iar săgețile colorate reprezintă porțile cu doi qubiți. Săgețile gri sunt porțile CZ, iar săgețile negre sunt porțile CX inter-cip (acestea conectează qubiții 6216 \rightarrow 21 și 254025 \rightarrow 40). Direcția săgeții indică direcția implicită în care sunt executate aceste porți; ele specifică ce qubiți sunt control/țintă în mod implicit pentru fiecare canal cu doi qubiți.

Transpilare față de backends personalizate

Acum că a fost definit un Backend personalizat cu propriul său Target unic, este simplu să transpilezi circuite cuantice față de acest Backend, deoarece toate constrângerile relevante (porți de bază, conectivitate qubiți și altele) necesare pentru pasele Transpilerului sunt conținute în acest atribut. Următorul exemplu construiește un Circuit care creează o stare GHZ mare și îl transpilează față de Backend-ul construit mai sus.

from qiskit.transpiler import generate_preset_pass_manager

num_qubits = 50
ghz = QuantumCircuit(num_qubits)
ghz.h(range(num_qubits))
ghz.cx(0, range(1, num_qubits))
op_counts = ghz.count_ops()

print("Pre-Transpilation: ")
print(f"CX gates: {op_counts['cx']}")
print(f"H gates: {op_counts['h']}")
print("\n", 30 * "#", "\n")

pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
transpiled_ghz = pm.run(ghz)
op_counts = transpiled_ghz.count_ops()

print("Post-Transpilation: ")
print(f"CZ gates: {op_counts['cz']}")
print(f"ECR gates: {op_counts['ecr']}")
print(f"SX gates: {op_counts['sx']}")
print(f"RZ gates: {op_counts['rz']}")
Pre-Transpilation: 
CX gates: 49
H gates: 50

##############################
Post-Transpilation: 
CZ gates: 151
ECR gates: 6
SX gates: 295
RZ gates: 216

Circuitul transpilat conține acum un amestec de porți CZ și ECR, pe care le-am specificat ca porți de bază în Target-ul Backend-ului. Există, de asemenea, mult mai multe porți decât ai început, din cauza necesității de a insera instrucțiuni SWAP după alegerea unui layout. Mai jos, instrumentul de vizualizare plot_circuit_layout() este folosit pentru a specifica ce qubiți și canale cu doi qubiți au fost utilizate în acest Circuit.

from qiskit.visualization import plot_circuit_layout

plot_circuit_layout(
transpiled_ghz, backend, qubit_coordinates=total_qubit_coordinates
)

Rezultatul celulei de cod anterioare

Creează Backend-uri unice

Pachetul rustworkx conține o bibliotecă mare de grafuri diferite și permite crearea de grafuri personalizate. Codul vizual interesant de mai jos creează un Backend inspirat din codul toric. Poți vizualiza ulterior Backend-ul folosind funcțiile din secțiunea Vizualizează Backend-uri.

class FakeTorusBackend(BackendV2):
"""Fake multi chip backend."""

def __init__(self):
"""Instantiate a new backend that is inspired by a toric code"""
super().__init__(name="Fake LOCC backend")
graph = rx.generators.directed_grid_graph(20, 20)
for column in range(20):
graph.add_edge(column, 19 * 20 + column, None)
for row in range(20):
graph.add_edge(row * 20, row * 20 + 19, None)
num_qubits = len(graph)
rng = np.random.default_rng(seed=12345678942)
rz_props = {}
x_props = {}
sx_props = {}
measure_props = {}
delay_props = {}
self._target = Target("Fake Kookaburra", num_qubits=num_qubits)
# Add 1q gates. Globally use virtual rz, x, sx, and measure
for i in range(num_qubits):
qarg = (i,)
rz_props[qarg] = InstructionProperties(error=0.0, duration=0.0)
x_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
sx_props[qarg] = InstructionProperties(
error=rng.uniform(1e-6, 1e-4),
duration=rng.uniform(1e-8, 9e-7),
)
measure_props[qarg] = InstructionProperties(
error=rng.uniform(1e-3, 1e-1),
duration=rng.uniform(1e-8, 9e-7),
)
delay_props[qarg] = None
self._target.add_instruction(XGate(), x_props)
self._target.add_instruction(SXGate(), sx_props)
self._target.add_instruction(RZGate(Parameter("theta")), rz_props)
self._target.add_instruction(Measure(), measure_props)
self._target.add_instruction(Reset(), measure_props)
self._target.add_instruction(Delay(Parameter("t")), delay_props)
cz_props = {}
for edge in graph.edge_list():
cz_props[edge] = InstructionProperties(
error=rng.uniform(7e-4, 5e-3),
duration=rng.uniform(1e-8, 9e-7),
)
self._target.add_instruction(CZGate(), cz_props)

@property
def target(self):
return self._target

@property
def max_circuits(self):
return None

@classmethod
def _default_options(cls):
return Options(shots=1024)

def run(self, circuit, **kwargs):
raise NotImplementedError("Lasciate ogne speranza, voi ch'intrate")
backend = FakeTorusBackend()
# We set `figsize` to a smaller size to make the documentation website faster
# to load. Normally, you do not need to set the argument.
plot_gate_map(backend, figsize=(4, 4))

Rezultatul celulei de cod anterioare

num_qubits = int(backend.num_qubits / 2)
full_device_bv = QuantumCircuit(num_qubits, num_qubits - 1)
full_device_bv.x(num_qubits - 1)
full_device_bv.h(range(num_qubits))
full_device_bv.cx(range(num_qubits - 1), num_qubits - 1)
full_device_bv.h(range(num_qubits))
full_device_bv.measure(range(num_qubits - 1), range(num_qubits - 1))
tqc = transpile(full_device_bv, backend, optimization_level=3)
op_counts = tqc.count_ops()
print(f"CZ gates: {op_counts['cz']}")
print(f"X gates: {op_counts['x']}")
print(f"SX gates: {op_counts['sx']}")
print(f"RZ gates: {op_counts['rz']}")
CZ gates: 867
X gates: 18
SX gates: 1630
RZ gates: 1174