انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

القياس المرجعي في الوقت الفعلي لاختيار الكيوبتات

تقدير الاستخدام: 4 دقائق على معالج Eagle r2 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي.)

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_experiments": "qiskit-experiments", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E722

الخلفية النظرية​

يوضح هذا البرنامج التعليمي كيفية تشغيل تجارب التوصيف في الوقت الفعلي وتحديث خصائص الواجهة الخلفية لتحسين اختيار الكيوبتات عند تعيين الدائرة على الكيوبتات الفيزيائية في وحدة المعالجة الكمية (QPU). ستتعلم تجارب التوصيف الأساسية المستخدمة لتحديد خصائص وحدة المعالجة الكمية، وكيفية إجرائها في Qiskit، وكيفية تحديث الخصائص المحفوظة في كائن الواجهة الخلفية الذي يمثل وحدة المعالجة الكمية بناءً على هذه التجارب.

تُحدَّث الخصائص المُبلَّغ عنها من وحدة المعالجة الكمية مرة واحدة يوميًا، غير أن النظام قد يتغير بسرعة أكبر من الفترة الزمنية الفاصلة بين التحديثات. قد يؤثر ذلك على موثوقية روتينات اختيار الكيوبتات في مرحلة Layout من مدير التمريرات، إذ ستعتمد على خصائص مُبلَّغ عنها لا تعكس الحالة الراهنة لوحدة المعالجة الكمية. لهذا السبب، قد يكون من المفيد تخصيص جزء من وقت وحدة المعالجة الكمية لتجارب التوصيف، والتي يمكن استخدامها بعد ذلك لتحديث خصائص وحدة المعالجة الكمية التي تستخدمها روتينات Layout.

المتطلبات​

قبل البدء في هذا البرنامج التعليمي، تأكد من تثبيت ما يلي:

  • Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصور المرئي

  • Qiskit Runtime الإصدار 0.40 أو أحدث ( pip install qiskit-ibm-runtime )

  • Qiskit Experiments الإصدار 0.12 أو أحدث ( pip install qiskit-experiments )

  • مكتبة الرسوم البيانية Rustworkx (pip install rustworkx)

الإعداد​

from qiskit_ibm_runtime import SamplerV2
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import hellinger_fidelity
from qiskit.transpiler import InstructionProperties

from qiskit_experiments.library import (
T1,
T2Hahn,
LocalReadoutError,
StandardRB,
)
from qiskit_experiments.framework import BatchExperiment, ParallelExperiment

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session

from datetime import datetime
from collections import defaultdict
import numpy as np
import rustworkx
import matplotlib.pyplot as plt
import copy

الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمية​

لقياس الفرق في الأداء، نأخذ بعين الاعتبار دائرة تُعدّ حالة Bell عبر سلسلة خطية بأطوال متفاوتة. يُقاس إخلاص حالة Bell عند طرفي السلسلة.

from qiskit import QuantumCircuit

ideal_dist = {"00": 0.5, "11": 0.5}

num_qubits_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 127]
circuits = []
for num_qubits in num_qubits_list:
circuit = QuantumCircuit(num_qubits, 2)
circuit.h(0)
for i in range(num_qubits - 1):
circuit.cx(i, i + 1)
circuit.barrier()
circuit.measure(0, 0)
circuit.measure(num_qubits - 1, 1)
circuits.append(circuit)

circuits[-1].draw(output="mpl", style="clifford", fold=-1)

Output of the previous code cell

Output of the previous code cell

إعداد الواجهة الخلفية وخريطة الاقتران​

أولًا، اختر الواجهة الخلفية

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

qubits = list(range(backend.num_qubits))

ثم احصل على خريطة الاقتران الخاصة بها

coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)

# Get unidirectional coupling map
one_dir_coupling_map = coupling_graph.edge_list()

لقياس أكبر عدد ممكن من بوابات الكيوبت الثنائية في آنٍ واحد، نقسّم خريطة الاقتران إلى layered_coupling_map. يحتوي هذا الكائن على قائمة من الطبقات، حيث تتضمن كل طبقة قائمة من الحواف التي يمكن تنفيذ بوابات الكيوبت الثنائية عليها في نفس الوقت. يُعرف هذا أيضًا بتلوين حواف خريطة الاقتران.

# Get layered coupling map
edge_coloring = rustworkx.graph_bipartite_edge_color(coupling_graph)
layered_coupling_map = defaultdict(list)
for edge_idx, color in edge_coloring.items():
layered_coupling_map[color].append(
coupling_graph.get_edge_endpoints_by_index(edge_idx)
)
layered_coupling_map = [
sorted(layered_coupling_map[i])
for i in sorted(layered_coupling_map.keys())
]

تجارب التوصيف​

تُستخدم سلسلة من التجارب لتوصيف الخصائص الرئيسية للكيوبتات في وحدة المعالجة الكمية. وتشمل هذه الخصائص: T1T_1، وT2T_2، وخطأ القراءة، وخطأ البوابة أحادية الكيوبت والبوابة ثنائية الكيوبت. سنستعرض باختصار ماهية هذه الخصائص، مع الإشارة إلى التجارب الموجودة في حزمة qiskit-experiments المستخدمة لتوصيفها.

T1​

T1T_1 هو الزمن المميز الذي يستغرقه الكيوبت المُثار للعودة إلى الحالة الأساسية بسبب عمليات تبدد الترابط (decoherence) الناجمة عن تخميد السعة. في تجربة T1T_1، نقيس الكيوبت المُثار بعد تأخير زمني. كلما زاد زمن التأخير، زادت احتمالية عودة الكيوبت إلى الحالة الأساسية. تهدف هذه التجربة إلى توصيف معدل اضمحلال الكيوبت نحو الحالة الأساسية.

T2​

يمثل T2T_2 مقدار الزمن اللازم لانخفاض إسقاط متجه Bloch للكيوبت الواحد على المستوى XY إلى ما يقارب 37% (1e\frac{1}{e}) من سعته الابتدائية بسبب عمليات تبدد الترابط الناتجة عن فقدان الطور (dephasing). يمكننا تقدير معدل هذا الاضمحلال باستخدام تجربة T2T_2 Hahn Echo.

توصيف خطأ تحضير الحالة والقياس (SPAM)​

في تجربة توصيف خطأ SPAM، تُحضَّر الكيوبتات في حالة معينة (∣0⟩\vert 0 \rangle أو ∣1⟩\vert 1 \rangle) ثم تُقاس. تُعطي احتمالية قياس حالة مختلفة عن الحالة المُحضَّرة احتمالية حدوث الخطأ.

القياس المرجعي العشوائي أحادي وثنائي الكيوبت​

القياس المرجعي العشوائي (RB) هو بروتوكول شائع لتوصيف معدل الخطأ في المعالجات الكمية. تتضمن تجربة RB توليد دوائر Clifford عشوائية على الكيوبتات المحددة بحيث يكون المؤثر الوحدوي المحسوب بواسطة الدوائر هو مؤثر الهوية. بعد تشغيل الدوائر، يُحسب عدد القياسات التي أسفرت عن خطأ (أي مخرج يختلف عن الحالة الأساسية)، ومن هذه البيانات يمكن استنتاج تقديرات الخطأ للجهاز الكمي من خلال حساب الخطأ لكل Clifford.

# Create T1 experiments on all qubit in parallel
t1_exp = ParallelExperiment(
[
T1(
physical_qubits=[qubit],
delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],
)
for qubit in qubits
],
backend,
analysis=None,
)

# Create T2-Hahn experiments on all qubit in parallel
t2_exp = ParallelExperiment(
[
T2Hahn(
physical_qubits=[qubit],
delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],
)
for qubit in qubits
],
backend,
analysis=None,
)

# Create readout experiments on all qubit in parallel
readout_exp = LocalReadoutError(qubits)

# Create single-qubit RB experiments on all qubit in parallel
singleq_rb_exp = ParallelExperiment(
[
StandardRB(
physical_qubits=[qubit], lengths=[10, 100, 500], num_samples=10
)
for qubit in qubits
],
backend,
analysis=None,
)

# Create two-qubit RB experiments on the three layers of disjoint edges of the heavy-hex
twoq_rb_exp_batched = BatchExperiment(
[
ParallelExperiment(
[
StandardRB(
physical_qubits=pair,
lengths=[10, 50, 100],
num_samples=10,
)
for pair in layer
],
backend,
analysis=None,
)
for layer in layered_coupling_map
],
backend,
flatten_results=True,
analysis=None,
)

خصائص وحدة المعالجة الكمية عبر الزمن​

بالنظر إلى الخصائص المُبلَّغ عنها من وحدة المعالجة الكمية عبر الزمن (سنأخذ في الاعتبار أسبوعًا واحدًا أدناه)، نرى كيف يمكن أن تتذبذب هذه الخصائص على مقياس يوم واحد. يمكن أن تحدث تذبذبات طفيفة حتى في غضون يوم واحد. في هذا السيناريو، لن تعكس الخصائص المُبلَّغ عنها (التي تُحدَّث مرة واحدة يوميًا) الحالة الراهنة لوحدة المعالجة الكمية بدقة. علاوة على ذلك، إذا جرى تحويل دائرة (transpile) محليًا (باستخدام الخصائص المُبلَّغ عنها الحالية) وإرسالها للتنفيذ في وقت لاحق (دقائق أو أيام)، فقد تكون قد استُخدمت خصائص قديمة لاختيار الكيوبتات في خطوة التحويل. يسلط هذا الضوء على أهمية توفر معلومات محدَّثة عن وحدة المعالجة الكمية في وقت التنفيذ. أولًا، لنسترجع الخصائص خلال نطاق زمني معين.

instruction_2q_name = "cz" # set the name of the default 2q of the device
errors_list = []
for day_idx in range(10, 17):
calibrations_time = datetime(
year=2025, month=8, day=day_idx, hour=0, minute=0, second=0
)
targer_hist = backend.target_history(datetime=calibrations_time)

t1_dict, t2_dict = {}, {}
for qubit in range(targer_hist.num_qubits):
t1_dict[qubit] = targer_hist.qubit_properties[qubit].t1
t2_dict[qubit] = targer_hist.qubit_properties[qubit].t2

errors_dict = {
"1q": targer_hist["sx"],
"2q": targer_hist[f"{instruction_2q_name}"],
"spam": targer_hist["measure"],
"t1": t1_dict,
"t2": t2_dict,
}

errors_list.append(errors_dict)

ثم لنرسم القيم بيانيًا

fig, axs = plt.subplots(5, 1, figsize=(10, 20), sharex=False)

# Plot for T1 values
for qubit in range(targer_hist.num_qubits):
t1s = []
for errors_dict in errors_list:
t1_dict = errors_dict["t1"]
try:
t1s.append(t1_dict[qubit] / 1e-6)
except:
print(f"missing t1 data for qubit {qubit}")

axs[0].plot(t1s)

axs[0].set_title("T1")
axs[0].set_ylabel(r"Time ($\mu s$)")
axs[0].set_xlabel("Days")

# Plot for T2 values
for qubit in range(targer_hist.num_qubits):
t2s = []
for errors_dict in errors_list:
t2_dict = errors_dict["t2"]
try:
t2s.append(t2_dict[qubit] / 1e-6)
except:
print(f"missing t2 data for qubit {qubit}")

axs[1].plot(t2s)

axs[1].set_title("T2")
axs[1].set_ylabel(r"Time ($\mu s$)")
axs[1].set_xlabel("Days")

# Plot SPAM values
for qubit in range(targer_hist.num_qubits):
spams = []
for errors_dict in errors_list:
spam_dict = errors_dict["spam"]
spams.append(spam_dict[tuple([qubit])].error)

axs[2].plot(spams)

axs[2].set_title("SPAM Errors")
axs[2].set_ylabel("Error Rate")
axs[2].set_xlabel("Days")

# Plot 1Q Gate Errors
for qubit in range(targer_hist.num_qubits):
oneq_gates = []
for errors_dict in errors_list:
oneq_gate_dict = errors_dict["1q"]
oneq_gates.append(oneq_gate_dict[tuple([qubit])].error)

axs[3].plot(oneq_gates)

axs[3].set_title("1Q Gate Errors")
axs[3].set_ylabel("Error Rate")
axs[3].set_xlabel("Days")

# Plot 2Q Gate Errors
for pair in one_dir_coupling_map:
twoq_gates = []
for errors_dict in errors_list:
twoq_gate_dict = errors_dict["2q"]
twoq_gates.append(twoq_gate_dict[pair].error)

axs[4].plot(twoq_gates)

axs[4].set_title("2Q Gate Errors")
axs[4].set_ylabel("Error Rate")
axs[4].set_xlabel("Days")

plt.subplots_adjust(hspace=0.5)
plt.show()

Output of the previous code cell

يمكنك أن ترى أن بعض خصائص الكيوبتات يمكن أن تتغير تغييرًا ملحوظًا على مدى عدة أيام. يسلط هذا الضوء على أهمية توفر معلومات حديثة عن حالة وحدة المعالجة الكمية، لاختيار الكيوبتات الأفضل أداءً لأي تجربة.

الخطوة 2: تحسين المسألة لتنفيذها على الأجهزة الكمية​

لا يُجرى أي تحسين على الدوائر أو المؤثرات في هذا البرنامج التعليمي.

الخطوة 3: التنفيذ باستخدام العناصر الأولية في Qiskit​

تنفيذ دائرة كمية باختيار الكيوبتات الافتراضي​

كنتيجة مرجعية للأداء، سننفذ دائرة كمية على وحدة المعالجة الكمية باستخدام الكيوبتات الافتراضية، وهي الكيوبتات المختارة وفقًا لخصائص الواجهة الخلفية المطلوبة. سنستخدم optimization_level = 3. يتضمن هذا الإعداد أكثر تحسينات التحويل (transpilation) تقدمًا، ويستخدم خصائص الهدف (مثل أخطاء العمليات) لاختيار الكيوبتات الأفضل أداءً للتنفيذ.

pm = generate_preset_pass_manager(target=backend.target, optimization_level=3)
isa_circuits = pm.run(circuits)
initial_qubits = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in isa_circuits
]

تنفيذ دائرة كمية باختيار الكيوبتات في الوقت الفعلي​

في هذا القسم، سندرس أهمية توفر معلومات محدَّثة عن خصائص الكيوبتات في وحدة المعالجة الكمية للحصول على نتائج مثلى. أولًا، سننفذ مجموعة كاملة من تجارب توصيف وحدة المعالجة الكمية (T1T_1 وT2T_2 و SPAM والقياس المرجعي العشوائي أحادي الكيوبت وثنائي الكيوبت)، والتي يمكننا استخدامها لاحقًا لتحديث خصائص الواجهة الخلفية. يتيح ذلك لمدير التمريرات اختيار الكيوبتات للتنفيذ بناءً على معلومات حديثة عن وحدة المعالجة الكمية، مما قد يحسّن أداء التنفيذ. ثانيًا، ننفذ دائرة زوج Bell ونقارن الإخلاص المُحصَّل بعد اختيار الكيوبتات بخصائص وحدة المعالجة الكمية المحدَّثة بالإخلاص الذي حصلنا عليه مسبقًا عند استخدام الخصائص المُبلَّغ عنها الافتراضية لاختيار الكيوبتات.

تنبيه

لاحظ أن بعض تجارب التوصيف قد تفشل عندما لا تتمكن روتينات المطابقة من ملاءمة منحنى مع البيانات المقيسة. إذا رأيت تحذيرات صادرة عن هذه التجارب، افحصها لفهم التوصيف الذي فشل على أي كيوبتات، وحاول ضبط معاملات التجربة (مثل الأزمنة الخاصة بـ T1T_1 وT2T_2، أو أعداد أطوال تجارب RB).

# Prepare characterization experiments
batches = [t1_exp, t2_exp, readout_exp, singleq_rb_exp, twoq_rb_exp_batched]
batches_exp = BatchExperiment(batches, backend) # , analysis=None)
run_options = {"shots": int(1e3), "dynamic": False}

with Session(backend=backend) as session:
sampler = SamplerV2(mode=session)

# Run characterization experiments
batches_exp_data = batches_exp.run(
sampler=sampler, **run_options
).block_for_results()

EPG_sx_result_list = batches_exp_data.analysis_results("EPG_sx")
EPG_sx_result_q_indices = [
result.device_components.index for result in EPG_sx_result_list
]
EPG_x_result_list = batches_exp_data.analysis_results("EPG_x")
EPG_x_result_q_indices = [
result.device_components.index for result in EPG_x_result_list
]
T1_result_list = batches_exp_data.analysis_results("T1")
T1_result_q_indices = [
result.device_components.index for result in T1_result_list
]

T2_result_list = batches_exp_data.analysis_results("T2")
T2_result_q_indices = [
result.device_components.index for result in T2_result_list
]

Readout_result_list = batches_exp_data.analysis_results(
"Local Readout Mitigator"
)

EPG_2q_result_list = batches_exp_data.analysis_results(
f"EPG_{instruction_2q_name}"
)

# Update target properties
target = copy.deepcopy(backend.target)
for i in range(target.num_qubits - 1):
qarg = (i,)

if qarg in EPG_sx_result_q_indices:
target.update_instruction_properties(
instruction="sx",
qargs=qarg,
properties=InstructionProperties(
error=EPG_sx_result_list[i].value.nominal_value
),
)
if qarg in EPG_x_result_q_indices:
target.update_instruction_properties(
instruction="x",
qargs=qarg,
properties=InstructionProperties(
error=EPG_x_result_list[i].value.nominal_value
),
)

err_mat = Readout_result_list.value.assignment_matrix(i)
readout_assignment_error = (
err_mat[0, 1] + err_mat[1, 0]
) / 2 # average readout error
target.update_instruction_properties(
instruction="measure",
qargs=qarg,
properties=InstructionProperties(error=readout_assignment_error),
)

if qarg in T1_result_q_indices:
target.qubit_properties[i].t1 = T1_result_list[
i
].value.nominal_value
if qarg in T2_result_q_indices:
target.qubit_properties[i].t2 = T2_result_list[
i
].value.nominal_value

for pair_idx, pair in enumerate(one_dir_coupling_map):
qarg = tuple(pair)
try:
target.update_instruction_properties(
instruction=instruction_2q_name,
qargs=qarg,
properties=InstructionProperties(
error=EPG_2q_result_list[pair_idx].value.nominal_value
),
)
except:
target.update_instruction_properties(
instruction=instruction_2q_name,
qargs=qarg[::-1],
properties=InstructionProperties(
error=EPG_2q_result_list[pair_idx].value.nominal_value
),
)

# transpile circuits to updated target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
isa_circuit_updated = pm.run(circuits)
updated_qubits = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in isa_circuit_updated
]

n_trials = 3 # run multiple trials to see variations

# interleave circuits
interleaved_circuits = []
for original_circuit, updated_circuit in zip(
isa_circuits, isa_circuit_updated
):
interleaved_circuits.append(original_circuit)
interleaved_circuits.append(updated_circuit)

# Run circuits
# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"

job_interleaved = sampler.run(interleaved_circuits * n_trials)

الخطوة 4: المعالجة اللاحقة وإرجاع النتيجة بالصيغة الكلاسيكية المطلوبة​

أخيرًا، لنقارن إخلاص حالة Bell المُحصَّل في الإعدادين المختلفين:

  • original: أي مع الكيوبتات الافتراضية التي اختارها المُحوِّل البرمجي بناءً على الخصائص المُبلَّغ عنها للواجهة الخلفية.

  • updated: أي مع الكيوبتات المختارة بناءً على الخصائص المحدَّثة للواجهة الخلفية بعد تشغيل تجارب التوصيف.

results = job_interleaved.result()
all_fidelity_list, all_fidelity_updated_list = [], []
for exp_idx in range(n_trials):
fidelity_list, fidelity_updated_list = [], []

for idx, num_qubits in enumerate(num_qubits_list):
pub_result_original = results[
2 * exp_idx * len(num_qubits_list) + 2 * idx
]
pub_result_updated = results[
2 * exp_idx * len(num_qubits_list) + 2 * idx + 1
]

fid = hellinger_fidelity(
ideal_dist, pub_result_original.data.c.get_counts()
)
fidelity_list.append(fid)

fid_up = hellinger_fidelity(
ideal_dist, pub_result_updated.data.c.get_counts()
)
fidelity_updated_list.append(fid_up)
all_fidelity_list.append(fidelity_list)
all_fidelity_updated_list.append(fidelity_updated_list)
plt.figure(figsize=(8, 6))
plt.errorbar(
num_qubits_list,
np.mean(all_fidelity_list, axis=0),
yerr=np.std(all_fidelity_list, axis=0),
fmt="o-.",
label="original",
color="b",
)
# plt.plot(num_qubits_list, fidelity_list, '-.')
plt.errorbar(
num_qubits_list,
np.mean(all_fidelity_updated_list, axis=0),
yerr=np.std(all_fidelity_updated_list, axis=0),
fmt="o-.",
label="updated",
color="r",
)
# plt.plot(num_qubits_list, fidelity_updated_list, '-.')
plt.xlabel("Chain length")
plt.xticks(num_qubits_list)
plt.ylabel("Fidelity")
plt.title("Bell pair fidelity at the edge of N-qubits chain")
plt.legend()
plt.grid(
alpha=0.2,
linestyle="-.",
)
plt.show()

Output of the previous code cell

لن تُظهر جميع التشغيلات تحسناً في الأداء بسبب التوصيف في الوقت الفعلي - ومع زيادة طول السلسلة، وبالتالي حرية أقل لاختيار الكيوبتات الفيزيائية، تصبح أهمية معلومات الجهاز المحدّثة أقل جوهرية. مع ذلك، من الممارسات الجيدة جمع بيانات جديدة عن خصائص الجهاز لفهم أدائه. أحياناً، قد تؤثر أنظمة ثنائية المستوى العابرة على أداء بعض الكيوبتات. يمكن أن تُعلمنا البيانات في الوقت الفعلي عند حدوث مثل هذه الأحداث وتساعدنا على تجنب فشل التجارب في مثل هذه الحالات.

دعوة للعمل

حاول تطبيق هذه الطريقة على تنفيذاتك وحدد مقدار الفائدة التي تحصل عليها! يمكنك أيضاً تجربة رؤية مقدار التحسينات التي تحصل عليها من الواجهات الخلفية المختلفة.

استطلاع البرنامج التعليمي​

يُرجى تعبئة هذا الاستطلاع القصير لتقديم ملاحظاتك حول هذا البرنامج التعليمي. ستساعدنا آراؤك في تحسين محتوانا وتجربة المستخدم.

ملاحظة: هذا الاستطلاع من IBM Quantum ويغطي محتوى الدرس (الذي كتبته IBM). توفّر doQumentation الموقع والترجمات وتنفيذ الكود — للتعليق على هذه الجوانب، يرجى فتح مشكلة على GitHub.

رابط الاستطلاع