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

17. ساختن یک شبیه‌ساز کوانتومی

17.1چرا خودتان یکی بسازید

برای همهٔ کارهای واقعی از Aer استفاده خواهید کرد، پس چرا ۳۰۰ خط شبیه‌ساز بنویسید؟ چون شبیه‌ساز تنها سیستم کوانتومی‌ای است که می‌توانید کاملاً درونش ببینید — هر دامنه، هر فاز — و ساختنش ریاضیات بخش پنجم را از نشانه‌گذاری به حافظهٔ عضلانی تبدیل می‌کند. تمرین، معادل نوشتن مفسر در این حوزه است: پس از آن، «شبیه‌ساز دروازه را اعمال می‌کند» دیگر جعبهٔ سیاه نیست و پرچم‌های متد Aer (16.6) انتخاب‌های مهندسی‌ای می‌شوند که می‌فهمید. بودجه: یک آخر هفتهٔ متمرکز. الزامات: numpy، هیچ چیز دیگر. مشخصات پروژهٔ انتهای این فصل آزمون‌های پذیرش را تعریف می‌کند — اول آن‌ها را بنویسید.

17.2شبیه‌سازی حالت-برداری

حلقهٔ هسته: حالت را به‌صورت آرایهٔ مختلط numpy از شکل (2ⁿ,) نگه دارید، هر دروازه را با به‌روزرسانی دامنه‌ها اعمال کنید و در پایان از |دامنه|² نمونه بگیرید. برای n کیوبیت، نمایهٔ i = Σ bₖ·2ᵏ حالت پایهٔ |bₙ₋₁…b₀⟩ را کد می‌کند. دروازه روی کیوبیت k فقط جفت‌هایی از دامنه‌ها را مخلوط می‌کند که در بیت k اختلاف دارند — مشاهده‌ای که شبیه‌سازی را O(2ⁿ) به‌ازای دروازه می‌کند نه O(4ⁿ). حافظه قید بسته‌کننده است: ۱۲۸ بایت به‌ازای دامنه (complex128) یعنی ۳۰ کیوبیت = ۱۳۷ گیگابایت. هر چیز دیگر این فصل همین حلقه را بسط می‌دهد.

17.3شبیه‌سازی تک‌کیوبیتی

import numpy as np

I2 = np.eye(2, dtype=complex)
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
X = np.array([[0, 1], [1, 0]], dtype=complex)

def apply_1q(state, gate, k, n):
    """اعمال `gate` روی کیوبیت k از بردار حالت n-کیوبیتی."""
    gk = np.kron(np.kron(np.eye(2**k), gate), np.eye(2**(n-k-1)))
    return gk @ state

این درست و آموزشی-صادقانه است — و به‌امیدوارانه کند (O(4ⁿ) به‌ازای دروازه). برای n=2..20 اجرایش کنید، زمانش بگیرید و اعداد را نگه دارید؛ 17.7 آن‌ها را مرتبه‌ها می‌زند و باید دقیقاً بفهمید چرا نسخهٔ اول می‌میرد: با ماتریس 2ⁿ×2ⁿ اکثراً-یکنواخت ضرب می‌کند.

17.4ضرب ماتریسی

رویکرد ساده‌لوحانه کل مدار را یک ماتریس می‌گیرد: U_total = U_m···U_2·U_1، سپس U_total @ state. ماتریس‌های کامل 2ⁿ×2ⁿ از n≈13 به بعد ناممکن‌اند (ماتریس متراکم ۲۷ گیگابایتی)، پس شبیه‌سازهای جدی هرگز نمی‌سازندشان. اما نگاه ترکیب را برای n≤10 به‌عنوان حقیقت مرجع نگه دارید: U_total را با np.kron و ضرب‌های تکی بسازید، یک‌بار اعمال کنید و نتیجه را برای آزمون واحدی پیاده‌سازی سریع (17.7) به‌کار ببرید. آزمون تفاضلی علیه نسخهٔ ساده‌لوحانه، مؤثرترین شکار باگ این پروژه است — هر دو پیاده‌سازی را برای همیشه در مجموعهٔ آزمون نگه دارید.

17.5شبیه‌سازی چندکیوبیتی

دروازه‌های کنترلی اولین چالش واقعی‌اند. CX روی (کنترل c، هدف t): برای هر جفت دامنهٔ (i, i XOR 2ᵗ) که بیت c از i برابر ۱ است، آن‌ها را جابه‌جا-مخلوط کنید: (a_i, a_j) → (a_j, a_i) برای X؛ به‌طور کلی، دروازهٔ 2×2 را روی جفت‌های مشروط به بیت کنترل اعمال کنید. اصطلاح ماسک: mask_c = 1 << c; for i in range(2**n): if i & mask_c: pair with j = i ^ (1 << t). مراقب شمارش-دوباره باشید — روی جفت‌ها تکرار کنید نه همهٔ نمایه‌ها. همین حلقه، اگر درست برداری شود، ۹۰٪ پروژه است.

17.6حاصل‌ضرب تانسوری

np.kron راه عملگرهای زیرسیستم به عملگر کل است: H روی کیوبیت ۰ از سیستم ۳-کیوبیتی kron(H, I, I) است — با پرسش قرارداد: کیوبیت ۰ کدام عامل را می‌گیرد، چپ‌ترین را؟ یک‌بار تصمیم بگیرید (رشته-اصلی numpy چپ‌ترین = پرارزش‌ترین بیت را ترجیح می‌دهد؛ اینندی کوچک Qiskit عکس را) و آزمونی بنویسید که انتخاب‌تان را مستند کند، چون باگ اینندی پیچ‌خورده پاسخ‌های قابل‌باور اما غلط تولید می‌کند. حاصل‌ضرب تانسوری مقداردهی اولیهٔ حالت-جداشدنی و حاشیه‌سازی لازم برای شبیه‌سازی اندازه‌گیری را هم می‌دهد. ریاضیات بخش پنجم این‌جا مشخص می‌شود.

17.7اعمال کارآمد دروازه‌ها

رویکرد تولیدی: بازآرایی + einsum. حالت را تانسوری n-بعدی به شکل (2,)*n ببینید با محور k = کیوبیت k (قرارداد خود را برگزینید)، سپس دروازهٔ 2×2 را روی محور k با np.tensordot(gate, state, axes=([1],[k])) اعمال و محور را با np.moveaxis برگردانید. هزینه: O(2ⁿ) به‌ازای دروازه — بهینه — و numpy حلقه‌های درونی را در C اجرا می‌کند. دروازه‌های دوکیوبیتی: tensordot با ماتریس 4×4 روی دو محور، یا تجزیه به عمل‌های تک‌محور O(1). محک: شبیه‌سازی n=26 شما باید دروازه را در کسری از ثانیه اعمال کند؛ اگر نه، هنوز جایی ماتریس 2ⁿ×2ⁿ مادی می‌کنید.

17.8شبیه‌سازی اندازه‌گیری

با دامنه‌های نهایی، احتمال‌ها np.abs(state)2 هستند (پیاده‌سازی قاعدهٔ برن شما، که در فصل ۱ ساختید — بازاستفاده کنید). نمونه‌گیری یک شات: np.random.choice(2n, p=probs)؛ N شات: برداری یا حلقه. حالت پس از اندازه‌گیری (برای اندازه‌گیری میان‌مداری): دامنه‌های ناسازگار با نتیجه را صفر و بازنرمال کنید. قرارداد نگاشت عدد نمونه‌گیری‌شده به رشتهٔ بیتی را تصمیم و آزمونش کنید! اندازه‌گیری همان‌جاست که فشار حافظه شارپ می‌شود: احتمال‌ها را یک‌بار پیش‌محاسبه کنید، بارها نمونه بگیرید و آرایهٔ احتمال 2ⁿ را بیش از یک‌بار به‌ازای هر نقطهٔ اندازه‌گیری مادی نکنید.

17.9نمونه‌گیری تصادفی

شبیه‌سازها دو نوع تصادفیِ درست به شما بدهکارند. نمونه‌گیری نتیجه: np.random.Generator (PCG64) بذردار برای تکرارپذیری — آزمون‌های ویژگی‌تان (16.15) به آن وابسته‌اند. و نویز صادقانه: شبیه‌ساز حالت-برداریِ ساده ایده‌آل است؛ افزودن نویز واقعی یعنی نمونه‌گیری کانال کراوس (بخش نهم روی موتور شما خواهد ساخت — API را همین حالا برایش طراحی کنید: apply_channel(state, kraus_ops) -> state). مقدار انتظاری دقیق ⟨ψ|O|ψ⟩ را هم با np.vdot(state, O @ state) برای مواردی که نمونه‌گیری اسراف است پیاده کنید. شبیه‌سازی‌ای که فقط نمونه می‌گیرد، نصف شبیه‌ساز است.

17.10تجزیهٔ مدار

به موتورتان جلویی بدهید تا آزمون‌ها مثل مدار خوانده شوند. قالب متنی کوچکی تعریف کنید:

H 0
CX 0 1
T 1
M 0 1

خطوط را تجزیه کنید → فهرست دستورهای (دروازه، عملوندها)؛ اعتبارسنجی (نمایه‌های کیوبیت در بازه، بدون اندازه‌گیری میان‌مداری مگر پشتیبانی کنید)؛ سپس تفسیر. آزمون رفت‌وبرگشت: مدار Qiskit را به این قالب تبدیل کنید (تجزیهٔ qc.draw('text') زیاده‌روی است — از qiskit.qasm2 استفاده کنید یا خودتان صادر کنید)، روی هر دو موتور اجرا و توزیع‌های هم‌بذر را ادعا کنید. حالا شبیه‌ساز شما هم‌ارزِ Qiskit است و هر فصل آینده می‌تواند یکی را با دیگری چک کند.

17.11بهینه‌سازی کارایی

به ترتیب بازده. یک: tensordot به‌جای اعمال ماتریس-کامل (۱۰۰ تا ۱۰۰۰ برابر). دو: dtype=complex128 پیوسته — upcasting بی‌صدای مختلط۲۵۶ یا آرایه‌های object کلاسیک کندی است. سه: توالی دروازه‌های تک‌کیوبیتی هر کیوبیت را پیش از اعمال ذوب کنید (حاصل‌ضرب 2×2ها، یک tensordot). چهار: شات‌ها را با نمونه‌گیری یک‌بار به‌ازای توزیع دسته‌ای کنید. پنج: np.einsum با optimize=True برای انقباض چندمحوره. پیش از بهینه‌سازی پروفایل کنید (cProfile، line_profiler) — در کد شبیه‌سازی، پروفایل تقریباً هرگز آن‌جا نیست که شهود می‌گوید. هدف واقع‌بینانه: ~10⁶ دروازه/ثانیه در n=25 روی چیپ M-سریز لپ‌تاپ؛ Aer هنوز ۱۰ برابر شما را می‌زند و اشکالی ندارد.

17.12پیچیدگی حافظه

بردار حالت 16·2ⁿ بایت است؛ دیوار در n=30 (۱۷۲ گیگابایت) روی سخت‌افزار متعارف و n≈34 روی ماشین ۵۱۲ گیگابایتی می‌رسد. دقیق باشید دربارهٔ هزینهٔ «شبیه‌سازی n کیوبیت»: زمان O(دروازه·2ⁿ)، حافظه O(2ⁿ) — و عدم‌تقارن با سخت‌افزار را ببینید که O(n) کیوبیت برای همان حالت خرج می‌کند. آزمایش حافظهٔ فصل ۱ را گسترش دهید: شبیه‌ساز خودتان، n = 24…30، زمان-به‌ازای-دروازه (تخت) و حافظهٔ بیشینه (نمایی) را رسم کنید. نمایی‌ای که روی لپ‌تاپ می‌سنجید همان ثابتی است که حالت ۵۰-کیوبیتی را از شبیه‌سازی توسط بزرگ‌ترین ابررایانه محافظت می‌کند — مرز صادقانهٔ حوزه، تجربه‌شده‌ی دست‌اول.

17.13شبیه‌سازی اسپارس

بسیاری از حالت‌های مهم اسپارس‌اند: حالت‌های پایه و حالت‌های قابل‌دسترس با مدارهای کم‌دروازهٔ غیرکلیفورد. شبیه‌سازهای اسپارس جفت‌های (نمایه، دامنه) را در dict نگه می‌دارند و دروازه روی کیوبیت k حداکثر به 2ⁿ⁻¹ جفتِ اشغال‌شده دست می‌زند. کجا می‌بَرد؟ جمع‌های کنترلی (حساب به سبک شور) در هر گام میانی حالت‌هایی با O(poly) پایهٔ اشغال‌شده می‌سازند — شبیه‌سازی اسپارس آماده‌سازی حالت شور N=15 را در کیلوبایت‌ها اجرا می‌کند. SparseSimulator را با اشتراک تجزیه‌گر (17.10) پیاده و روی n کوچک با موتور متراکم آزمونش کنید. Aer یکی همراه ندارد؛ کدهای پژوهشی (و ادعاهای نقطهٔ تلاقی برتری ۲۰۱۹ گوگل) در این رژیم زندگی می‌کنند.

17.14شبیه‌سازی پایدارنده

قضیهٔ گاتسمن–نیل: مدارهای ساخته‌شده فقط از {H، S، CX، پائولی‌ها، اندازه‌گیری Z} در زمان و حافظهٔ O(n²) کلاسیکی شبیه‌سازی می‌شوند — بدون هیچ بردار حالتی. حالت با n مولد پایدارنده (رشته‌های پائولی با علامت) بازنمایی می‌شود؛ هر دروازهٔ کلیفورد جدول را در زمان چندجمله‌ای به‌روزرسانی می‌کند. به همین دلیل هزاران کیوبیت مداریِ تصحیح‌خطا (بخش دهم: کدهای سطحی مدار کلیفوردند!) خوب شبیه‌سازی می‌شوند درحالی‌که ۵۰ کیوبیت الگوریتمی نه. یک مینی-جدول پیاده کنید (stim گوگل مرجع است — نصبش کنید، مقاله‌اش را بخوانید) و راستی‌آزمایی کنید: موتور پایدارندهٔ شما با موتور متراکم روی مدارهای کلیفورد ۱۰ تا ۳۰ کیوبیتی موافقت می‌کند، در میکروثانیه‌ها در برابر ثانیه‌ها.

17.15شبیه‌سازی شبکه-تانسوری

مرز شبیه‌سازی کلاسیک: حالت را به‌صورت شبکه‌ای از تانسورهای کوچک بازنمایی کنید (MPS، PEPS) که اندازه‌اش با درهم‌تنیدگی مقیاس می‌شود نه شمار کیوبیت. مدارهای کم‌درهم‌تنیدگی (دینامیک یک‌بعدی، عمق کم) تا ۱۰۰+ کیوبیت؛ مدارهای به‌شدت درهم‌تنیده به دیوار بُعد-پیوند می‌خورند — که دقیقاً همان است که گوگل با آن استدلال کرد نتیجهٔ ۵۳-کیوبیتی ۲۰۱۹ برای کلاسیک سخت است (و تحلیل متقابل IBM که نقطهٔ تلاقی دست‌یافتنی است). MPS را اینجا پیاده نمی‌کنید (بخش هجدهم به‌عنوان پروژه عرضه می‌کند)، اما quimb و ITensor را بشناسید و قاعده را بدانید: شبکه‌های تانسوری حافظه را با فرض درهم‌تنیدگی معامله می‌کنند — قرینهٔ کامل دیوار نماییِ سختِ 17.12.