إنشاء backends مخصصة والتحويل البرمجي ضدها
# 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>إصدارات الحزم</b></summary>
الكود الموجود في هذه الصفحة طُوِّر باستخدام المتطلبات التالية.
نوصي باستخدام هذه الإصدارات أو ما هو أحدث منها.
qiskit[all]~=2.3.0
</details>
{/* cspell:ignore LOCC */}
من أبرز ميزات Qiskit القوية هي دعمه لتكوينات أجهزة فريدة. صُمِّم Qiskit ليكون مستقلاً عن مزوّد الأجهزة الكمومية التي تستخدمها، ويمكن للمزوّدين تهيئة كائن `BackendV2` وفق خصائص أجهزتهم الفريدة. يوضّح هذا الموضوع كيفية تهيئة backend خاص بك وتحويل الدوائر الكمومية برمجياً ضده.
يمكنك إنشاء كائنات `BackendV2` فريدة بأشكال هندسية أو بوابات أساسية مختلفة، وتحويل دوائرك برمجياً مع مراعاة تلك التكوينات. يغطّي المثال التالي backend يمتلك شبكة Qubit منفصلة، تختلف فيها بوابات الحافة عن تلك الموجودة داخل الكتلة الرئيسية.
## فهم واجهات Provider وBackendV2 وTarget \{#understand-the-provider-backendv2-and-target-interfaces}
قبل البدء، من المفيد أن تفهم استخدام وغرض كائنات [`Provider`](../api/qiskit/providers) و[`BackendV2`](../api/qiskit/qiskit.providers.BackendV2) و[`Target`](../api/qiskit/qiskit.transpiler.Target).
- إذا كان لديك جهاز كمومي أو محاكي تريد دمجه في Qiskit SDK، فستحتاج إلى كتابة كلاس `Provider` خاص بك. يخدم هذا الكلاس غرضاً وحيداً: الحصول على كائنات backend التي توفّرها. هذا هو المكان الذي تُعالَج فيه أي مهام تتعلق ببيانات الاعتماد و/أو المصادقة. بعد إنشاء مثيل منه، سيوفّر كائن provider قائمة بالـ backends وإمكانية الحصول عليها وإنشاء مثيل منها.
- بعد ذلك، توفّر كلاسات backend الواجهة بين Qiskit SDK والأجهزة أو المحاكيات التي ستنفّذ الدوائر. تتضمّن جميع المعلومات الضرورية لوصف backend للـ transpiler، حتى يتمكّن من تحسين أي دائرة وفق قيوده. يتكوّن `BackendV2` من أربعة أجزاء رئيسية:
- خاصية [`Target`](../api/qiskit/qiskit.transpiler.Target)، التي تحتوي على وصف لقيود الـ backend وتوفّر نموذجاً للـ backend يستخدمه الـ transpiler
- خاصية `max_circuits` التي تحدّد حدّاً أقصى لعدد الدوائر التي يمكن للـ backend تنفيذها في مهمة واحدة
- طريقة `run()` تقبل عمليات تسليم المهام
- مجموعة من `_default_options` لتحديد الخيارات القابلة للتهيئة من قِبَل المستخدم وقيمها الافتراضية
## إنشاء BackendV2 مخصص \{#create-a-custom-backendv2}
كائن `BackendV2` هو كلاس مجرّد يُستخدم لجميع كائنات backend التي ينشئها مزوّد ما (سواء داخل `qiskit.providers` أو مكتبة أخرى مثل [`qiskit_ibm_runtime.IBMBackend`](../api/qiskit-ibm-runtime/ibm-backend)). كما ذُكر آنفاً، تحتوي هذه الكائنات على عدة خصائص، من بينها [`Target`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.Target). يحتوي `Target` على معلومات تحدّد خصائص الـ backend — مثل [`Coupling Map`](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.CouplingMap)، وقائمة [`Instructions`](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.Instruction)، وغيرها — وتنقلها إلى الـ transpiler. إضافة إلى `Target`، يمكنك أيضاً تحديد تفاصيل على مستوى النبضات مثل [`DriveChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.DriveChannel) أو [`ControlChannel`](https://docs.quantum.ibm.com/api/qiskit/1.4/qiskit.pulse.channels.ControlChannel).
يوضّح المثال التالي هذا التخصيص من خلال إنشاء backend متعدد الرقائق محاكى، حيث تمتلك كل رقاقة اتصالية heavy-hex. يحدّد المثال مجموعة البوابات ثنائية الـ Qubit للـ backend لتكون [`CZGates`](../api/qiskit/qiskit.circuit.library.CZGate) داخل كل رقاقة و[`CXGates`](../api/qiskit/qiskit.circuit.library.ECRGate) بين الرقائق. أولاً، أنشئ `BackendV2` الخاص بك وخصّص `Target` الخاص به ببوابات أحادية وثنائية الـ Qubit وفق القيود الموصوفة سابقاً.
<Admonition type="tip" title="مكتبة graphviz">
رسم خريطة الاقتران يتطلب تثبيت مكتبة [`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"
)
تصوير الـ backends
يمكنك عرض مخطط الاتصال لهذا الكلاس الجديد باستخدام طريقة plot_gate_map() من وحدة qiskit.visualization. هذه الطريقة، إلى جانب plot_coupling_map() وplot_circuit_layout()، أدوات مفيدة لتصوير ترتيب الـ Qubit في backend، وكيف تتوزّع الدائرة عبر Qubits الـ backend. ينشئ هذا المثال backend يحتوي على ثلاث رقائق heavy-hex صغيرة، ويحدّد مجموعة من الإحداثيات لترتيب الـ Qubits، وكذلك مجموعة من الألوان المخصصة للبوابات ثنائية الـ Qubit المختلفة.
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

كل Qubit مسمّى، والأسهم الملوّنة تمثّل البوابات ثنائية الـ Qubit. الأسهم الرمادية هي بوابات CZ والأسهم السوداء هي بوابات CX بين الرقائق (التي تربط Qubits و). واتجاه السهم يشير إلى الاتجاه الافتراضي الذي تُنفَّذ فيه هذه البوابات؛ أي أنها تحدّد أي Qubits هي control/targets بشكل افتراضي لكل قناة ثنائية الـ Qubit.
الـ Transpile مقابل الـ Backends المخصصة
الآن بعد أن عرّفنا backend مخصصًا بـ Target فريد خاص به، أصبح من السهل إجراء عملية transpile للدوائر الكمومية مقابل هذا الـ backend، إذ تحتوي هذه الخاصية على كل القيود الضرورية (بوابات الأساس، وتوصيلية الـ Qubit، وما إلى ذلك) التي تحتاجها ممرات الـ Transpiler. المثال التالي يبني دائرة تُنشئ حالة GHZ كبيرة ثم يُجري عليها transpile مقابل الـ backend الذي بنيناه سابقًا.
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
الدائرة بعد الـ transpile تحتوي الآن على مزيج من بوابات CZ وECR، وهي البوابات الأساسية التي حددناها في Target الخاص بالـ backend. كما ستلاحظ أن عدد البوابات ازداد كثيرًا مقارنةً بما بدأنا به، وذلك بسبب الحاجة إلى إدراج تعليمات SWAP بعد اختيار التخطيط. في المقطع أدناه، يُستخدم أداة التصور plot_circuit_layout() لتحديد الـ Qubits والقنوات ثنائية الـ Qubit التي استُخدمت في هذه الدائرة.
from qiskit.visualization import plot_circuit_layout
plot_circuit_layout(
transpiled_ghz, backend, qubit_coordinates=total_qubit_coordinates
)

إنشاء backends فريدة
تحتوي حزمة rustworkx على مكتبة واسعة من الرسوم البيانية المختلفة وتتيح إنشاء رسوم بيانية مخصصة. الكود المثير للاهتمام بصريًا أدناه ينشئ backend مستوحى من الـ toric code. يمكنك بعد ذلك تصور الـ backend باستخدام الدوال الواردة في قسم تصور الـ Backends.
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))

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