مهندس کوانتومی

16. برنامه‌نویسی کوانتومی

16.1چرا برنامه‌نویسی کوانتومی متفاوت است

سه تفاوت همه‌چیز را بازآرایی می‌کند. یک: متغیر نیست — حالت در ثباتی زندگی می‌کند که نمی‌توانید میان پرواز بخوانیدش، پس جریان کنترل کلاسیک (if، while روی داده) عمدتاً در دسترس نیست؛ کل مدار را ایستا از پیش محاسبه می‌کنید. دو: رونوشت نیست — همان کیوبیت را نمی‌توان در شاخه‌های موازی شبیه‌سازی کلاون کرد، پس الگوریتم‌ها به‌صورت یک برنامهٔ ثابت تداخلی نوشته می‌شوند. سه: نتایج توزیع‌اند — هر اجرا هیستوگرام برمی‌گرداند و کار برنامه این است که هیستوگرام را قابل تفسیر کند. پیامد: «کد» کوانتومی عمدتاً ساختِ اعلانیِ مدار به‌اضافهٔ پس‌پردازش کلاسیک است، و آزمون شبیه آزمون فرض آماری است نه unit test. این فصل برای هر سه عادت می‌سازد.

16.2Qiskit

Qiskit (نسخهٔ ≥1.0، IBM، پایتون) SDK این کتاب است: گسترده‌ترین استقرار، بهترین مستندات و یکی که بزرگ‌ترین ناوگان سخت‌افزاری عرضه می‌کند. نصب محلی: pip install qiskit qiskit-aer matplotlib — برای شبیه‌سازها حساب لازم نیست. مدل شیء کوچک است: QuantumCircuit (برنامهٔ شما)، لایه‌های Primitive (SamplerV2 برای توزیع‌ها، EstimatorV2 برای مقدارهای انتظاری)، BackendV2 (ماشین یا شبیه‌ساز هدف) و transpile (نقطهٔ ورود کامپایلر). Cirq (گوگل)، PennyLane (Xanadu، ML-محور) و Amazon Braket دیگر پشته‌های اصلی‌اند؛ مفاهیم این‌جا منتقل می‌شوند — فقط املایش فرق می‌کند. انضباط نسخه مهم است: Qiskit 0.x→1.0 APIها را شکست؛ نسخه‌ها را سنجاق کنید.

16.3ساخت مدار

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)      # 2 کیوبیت، 2 بیت کلاسیک
qc.h(0)                        # H روی q0
qc.cx(0, 1)                    # CNOT: کنترل q0، هدف q1
qc.measure([0, 1], [0, 1])
print(qc)                      # رسم متنی

ساخت، فراخوانی متد به ترتیب مدار است — شیء فهرستی از دستورات است و می‌توانید مدارها را ترکیبی بسازید: qc.compose(other, qubits=[2,3])، qc.to_gate()، مدارهای پارامتری QuantumCircuit(2); qc.rz(θ, 0) با اشیای Parameter برای کار واریاسیونال (فصل ۵۹). اصطلاح: مدارهای کوچک نام‌دار بسازید، آن‌ها را ترکیب کنید و تابع کارخانهٔ make_circuit(**params) -> QuantumCircuit نگه دارید نه سراسریِ تغییرپذیر — در بخش سیزدهم از خودتان تشکر خواهید کرد.

16.4ثبات‌ها

کیوبیت‌ها و کل‌بیت‌ها در ثبات‌های نام‌دار زندگی می‌کنند: QuantumRegister(4, 'data')، ClassicalRegister(4, 'result')، به QuantumCircuit(data, result) پاس داده می‌شوند. ثبات‌ها شیرین آدرس‌دهی‌اند — می‌گذارند بنویسید qc.measure(data, result) و مدارها در عرض تغییرها ترکیب‌پذیر بمانند. قراردادی که از روز اول ارزش adoption دارد: ثبات‌ها را بر حسب نقش جدا کنید — data، ancilla، syndrome — چون بخش دهم شما را وادار به مدیریت صدها تا از هر کدام می‌کند و «کیوبیت ۳۷» بی‌معناست درحالی‌که «syndrome[3]» خودمستند است. زیر هود، نمایه‌سازی تخت هنوز کار می‌کند و ترنسپایل جایگشتش می‌کند، و به همین دلیل مدیریت نتیجه در 16.11 مراقبت می‌خواهد.

16.5اندازه‌گیری‌ها

qc.measure(qubit, clbit) نتایج پایه-Z را در بیت‌های کلاسیک ثبت می‌کند؛ qc.measure_all() سد اضافه کرده و همه را در یک ثبات اندازه می‌گیرد. تغییر پایه: H (یا qc.barrier() سپس H) پیش از اندازه‌گیری برای خوانش پایهٔ X — الگویی که دائم استفاده خواهید کرد (دویچ–جوزا، QFT، چک‌های پایدارنده). اندازه‌گیری میان‌مداری و بازخورد کلاسیک: with qc.if_test((creg, value)): روی بیت‌های اندازه‌گیری‌شده شاخه می‌زند — مدارهای پویا، پشتیبانی‌شده روی سخت‌افزار مدرن IBM و حیاتی برای رمزگشاهای QEC که درون حلقه اجرا می‌شوند. اندازه‌گیری هم مجانی نیست: زمان می‌برد (~۶۰۰ نانوثانیه روی ترانسمون‌های بسامد-ثابت) که بودجه‌های عمق باید واردش کنند.

16.6شبیه‌سازها

ابزار اصلی شما. Aer (qiskit-aer) می‌دهد: AerSimulator() — شبیه‌سازی دقیق حالت-برداری تا ~۳۰ کیوبیت، با مدل‌های نویز قابل‌تنظیم؛ method="statevector"، "density_matrix" (حالت‌های آمیخته)، "stabilizer" (مدارهای کلیفورد، صدها کیوبیت)، "matrix_product_state" (مدارهای کم‌درهم‌تنیدگی، ۱۰۰+ کیوبیت). جداگانه، Statevector.from_instruction(qc) دامنه‌های دقیق را بدون نمونه‌گیری می‌دهد — معدن طلا اشکال‌زدایی (16.14). مدل‌های نویز: NoiseModel.from_backend(backend) داده‌های کالیبراسیون ماشین واقعی را وارد می‌کند، پس آزمایش‌های لپ‌تاپی شما می‌تواند رفتار سخت‌افزار را پیش‌نمایش کند. قاعدهٔ سرانگشتی: شبیه‌سازی دقیق برای باگ‌های منطق، نویزی برای آمار، سخت‌افزار برای حقیقت.

16.7ترنسپایل

transpiled = transpile(qc, backend, optimization_level=3) جایی است که مدار انتزاعی زیبایتان به واقعیت سخت‌افزار چفت می‌شود: ترجمهٔ پایه (H شما می‌شود RZ–SX–RZ)، مسیریابی (درج SWAP برای اتصال)، بهینه‌سازی (حذف دروازه، تحلیل جابه‌جایی، ~۲۰–۴۰٪ کاهش عمق در سطح ۳) و زمان‌بندی. مدار ترنسپایل‌شده را بخوانید — transpiled.count_ops()، transpiled.depth() — پیش از اجرا؛ بیانیهٔ صادقانهٔ کاری است که ماشین انجام خواهد داد. مدیر گذر قابل‌گسترش توسط کاربر است (PassManager با گذرهای سفارشی)، که دروازهٔ ورود به مهندسی کامپایلر بخش دوازدهم است. سطح بهینه‌سازی ۳ کند اما برای هر چیزی که منتشر می‌کنید می‌ارزد.

16.8انتخاب بک‌اند

بک‌اندها در شمار کیوبیت، فیدیلیتی دروازه، زمان‌های همدوسی، اتصال و زمان صف فرق دارند. برنامه‌ریزانه پرس‌وجو کنید: backend.target نرخ خطای به‌ازای-دروازه و مدت‌ها را ارائه می‌دهد؛ backend.properties() (برای ماشین‌های واقعی) اسنپ‌شات کالیبراسیون. ابتکارهای انتخابی که واقعاً مهم‌اند: دستگاهی را بردارید که خطاهای *دوکیوبیتی*‌اش روی کیوبیت‌هایی که مدارتان فرود می‌آید کمینه است (نه دستگاه با بیشترین کیوبیت)؛ نقشهٔ اتصال را با گراف تعامل مدارتان چک کنید؛ و برای کار جدی، چک عمر-کالیبراسیون — دادهٔ کهنه‌تر از یک روز کهنه است. بک‌اندهای ساختگی (GenericBackendV2(n)) ویژگی‌های دستگاه‌های واقعی را برای کار آفلاین اسنپ می‌کنند — پیش‌فرض این کتاب.

16.9اجرا

جریان primitive (Qiskit نسخهٔ ≥1.0):

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()          # یا SamplerV2 زمان-اجرا برای سخت‌افزار
job = sampler.run([transpiled], shots=4096)
result = job.result()[0]
counts = result.data.meas.get_counts()  # {'00': 2041, '11': 2055, ...}

اجرای ناهمگام است (ارسال جاب → صف → نتایج)، دسته‌پذیر (مدارهای زیاد به‌ازای جاب — همیشه دسته‌ای) و شات-محدود (زمان سخت‌افزار گران است؛ ۴۰۹۶ شات به‌ازای مدار بودجهٔ معمول است). تفکیک Sampler/Estimator نقطهٔ همگرایی حوزه است: توزیع‌ها در برابر مقدارهای انتظاری، با گزینه‌های رفع خطا (ZNE، PEC — فصل ۳۱) در همین لایه. اجرا را از روز اول در توابع خودتان با retry و گزارش‌دهی بپیچید.

16.10آزمایش‌های شات-محور

همهٔ چیزِ قابل‌اندازه‌گیری روی رایانهٔ کوانتومی، آمار نمونه‌گیری روی شات‌هاست. آزمایش‌ها را متناسب طراحی کنید: شات‌ها را از آمارِ موردنیاز انتخاب کنید — برآورد احتمال با خطای ±1٪ حدود 10⁴ شات می‌خواهد (خطای استاندارد √(p(1−p)/N))؛ تفکیک 0.500 از 0.505 حدود 10⁵. در هر آزمایش یک پارامتر تغییر دهید (زاویهٔ چرخش، شمار تکرار)، فراداده ثبت کنید (بک‌اند، تاریخ کالیبراسیون، بذر ترنسپایل) و همیشه نسخهٔ شبیه‌ساز-ایده‌آل را کنار نویزی اجرا کنید — اختلاف خودِ نویز است و مطالعه‌اش بخش نهم در مقیاس کوچک است. از نخستین آزمایش‌تان گزارش نتیجه نگه دارید؛ دفتر آزمایش می‌سازید نه اسکریپت اجرا می‌کنید.

16.11بازیابی نتایج

counts رشته‌های بیتی را به فراوانی نگاشت می‌کند — با یک ظرافت که همه را می‌گزد: ترتیب رشتهٔ بیتی. Qiskit q_{n-1}...q_0 چاپ می‌کند، اِندیِن کوچک: در '11' پس از مدار بل، بیت سمت چپ q1 است. چیدمان ثبات شما و ترتیب چاپ‌شده از انتظارتان پیچ خورده‌اند؛ یک‌بار با مدار نامتقارن (H فقط روی q0) راستی‌آزمایی کنید و دیگر هرگز حدس نزنید. پس‌پردازش این‌جاست: هیستوگرام → احتمال‌ها → هر چه کاربردتان می‌خواهد (مقدار انتظاری، چک توازن، رمزگشایی درست‌نمایی بیشینه). پس‌پردازش را در توابع جدا و آزمون‌شده از ساخت مدار نگه دارید — ترکیبشان باگ‌های کوانتومی را ابطال‌ناپذیر می‌کند.

16.12نمایش مدارها

qc.draw('mpl') (matplotlib، کیفیت انتشاری)، qc.draw('text') (ترمینال)، qc.draw('latex') (مقاله). برای مدارهای ترنسپایل‌شده، qc.draw('mpl', idle_wires=False) چیدمان فیزیکی را نشان می‌دهد و plot_circuit_layout(transpiled, backend) نقشهٔ اتصال را هم‌پوشانی می‌کند — پس از آن تصمیم‌های مسیریابی مرئی می‌شوند. نمودارها ابزار اشکال‌زدایی‌اند: اگر H–CX–H شما آن‌جا که تقارن می‌خواستید متقارن به نظر نمی‌رسد، باگ کنترل/هدف دارید. مدارهای پارامتری با برچسب θ رسم می‌شوند. قاعدهٔ سفت «پیش از اجرا رسم کن»: ۳۰ ثانیه چشم، از ۳۰ دقیقه counts سرگردان بهتر است.

16.13نمایش توزیع‌ها

plot_histogram(counts) برای دادهٔ شات؛ برای بینش حالت، بردار بلوخ (plot_bloch_multivector(Statevector(qc))) و QSphere برای ساختار دامنه-و-فاز چندکیوبیتی. نمای شهر (نمودار میله‌ای روی رشته‌های بیتی) پیش‌فرض شماست، اما مرتب و آستانه‌گذاری‌اش کنید: dict(filter(lambda kv: kv[1] > 10, counts.items())). برای جاروهای پارامتری، احتمال اندازه‌گیری‌شده را در برابر پارامتر رسم کنید — فرنج‌های تداخلی که ظاهر می‌شوند (نوسان‌های رابی، فرنج‌های رمزی) امضای آن‌اند که مدار می‌کند آنچه فکر می‌کنید. هر شکل آزمایش‌های این کتاب با همین سه تابع بازتولیدپذیر است؛ شکل‌های خودتان را همان‌طور بسازید.

16.14اشکال‌زدایی برنامه‌های کوانتومی

جعبه‌ابزار، به ترتیب تشدید. یک: Statevector(qc_بدون_اندازه‌گیری) — دامنه‌های دقیق؛ با انتظارات دست‌حساب برای n کوچک مقایسه کنید. دو: Operator(qc) — ماتریس یونیتی کامل؛ با Operator(target) با np.allclose مقایسه کنید. سه: شبیه‌سازی جزئی — مدار را تکه‌تکه اجرا و حالت پس از هر مرحله را چک کنید. چهار: ادعاهای میان‌مداری (فقط شبیه‌ساز) و save_statevector در Aer برای اسنپ‌شات. پنج: برای باگ‌های احتمالاتی، بذر را ثابت کنید (seed_simulator=42) تا شکست‌ها پیش از اشکال‌زدایی تکرارپذیر شوند. قاعدهٔ اصلی: هرگز روی سخت‌افزار اشکال‌زدایی نکنید. اشکال‌زدایی سخت‌افزاری «رفع خطا» نام دارد و رشتهٔ پژوهشی است نه جریان کاری.

16.15آزمون برنامه‌های کوانتومی

کد کوانتومی آزمون واقعی می‌تواند داشته باشد. مبتنی بر ویژگی: اوراکل جمع‌کنندهٔ شما برای همهٔ ورودی‌ها f(a,b) = a+b mod 16 را ارضا می‌کند — روی شبیه‌ساز کامل آزمونش کنید (۴-بیتی: ۲۵۶ حالت، میلی‌ثانیه‌ها). ناوردایی‌ها: ancillaها به |0⟩ برمی‌گردند (تست با حاشیه‌سازی Statevector)؛ مدارتان یونیتی است (Operator(qc).is_unitary())؛ احتمال‌ها جمع به ۱. رگرسیون: اسنپ‌شات شمارش دروازه و عمق — تغییری که عمق را دو برابر کند باید بازبینی را رد کند. آماری: آستانه‌های پذیرش با نویز شات معلوم («P(1) درون 0.5±3σ»). pytest همه را می‌پوشاند. پروژه‌های بخش هجدهم با مجموعه‌آزمون به همین سبک می‌آیند؛ برای همهٔ چیزهایی که می‌سازید adoption کنید، از شبیه‌ساز فصل ۱۷ شروع کنید.