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

مبادئ تصميم تمثيلات المؤثرات

يشرح هذا الدليل كيف يُنظَّم BosonOperator وكيف تعمل به: كيف تبني واحدًا، وكيف تفحص المؤثرات وتجمعها، وكيف تُحضر مؤثرين إلى صيغة تمكّن من مقارنتهما. ابدأ من هنا عندما تكتب هاميلتونيًا.

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

[bi,bj†]=δij,[bi,bj]=[bi†,bj†]=0 .[b_i, b_j^\dagger] = \delta_{ij}, \qquad [b_i, b_j] = [b_i^\dagger, b_j^\dagger] = 0 \, .

نتيجتان تشكّلان الواجهة. يمكن أن يتكرر فهرس النمط داخل حد واحد، لأن bi†bi†b_i^\dagger b_i^\dagger مؤثر صالح ينشئ بوزونين. كذلك، إعادة ترتيب عاملين لا تُدخل إشارة أبدًا، لكن تحريك مؤثر إفناء عبر مؤثر إنشاء على النمط نفسه يضيف حدًا، bibi†=bi†bi+1b_i b_i^\dagger = b_i^\dagger b_i + 1.

التخزين​

يحتفظ المؤثر بحدوده في أربع مصفوفات مسطّحة، تشبه صيغة الصفوف المضغوطة المتفرقة (compressed sparse row) للمصفوفات المتفرقة:

coeffsمعامل مركّب واحد لكل حد.
actionsقيمة منطقية واحدة لكل عامل: True للإنشاء وFalse لمؤثر الإفناء.
modesفهرس نمط واحد لكل عامل.
boundariesالمواضع في actions وmodes حيث يبدأ كل حد وينتهي.

يأخذ المُنشئ هذه المصفوفات الأربع مباشرة. المؤثر أدناه له ثلاثة حدود، هي 2b0†b12 b_0^\dagger b_1 و−ib1†b1†-i b_1^\dagger b_1^\dagger والثابت 0.50.5، ويمكنك قراءة المصفوفات مجددًا بالدوال get_*:

>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>> op = BosonOperator(
... coeffs=[2.0, -1.0j, 0.5],
... actions=[True, False, True, True],
... modes=[0, 1, 1, 1],
... boundaries=[0, 2, 4, 4],
... )
>>> op.get_coeffs()
[(2+0j), (-0-1j), (0.5+0j)]
>>> op.get_actions()
[True, False, True, True]
>>> op.get_modes()
[0, 1, 1, 1]
>>> op.get_boundaries()
[0, 2, 4, 4]
>>> len(op)
3

الحد الأول يمتد من الموضع 0 إلى 2 في actions وmodes، والثاني من 2 إلى 4، والثالث من 4 إلى 4، وهكذا يُخزَّن الحد الثابت، وهو حد بلا عوامل. النمط المتكرر 1 في الحد الثاني يُحفظ كما كُتب؛ لا يُحذف أي حد ولا يُدمج لأن فهرس النمط يظهر مرتين.

ملاحظة

المؤثر المبني بـ from_dict() لا يخزّن حدوده بترتيب القاموس، لذلك قد تسرد مصفوفاته الحدود بترتيب مختلف من تشغيل لآخر. المؤثر نفسه لا يتغير، ولا شيء مما يلي ويقارن المؤثرات أو يطبعها يعتمد على الترتيب المخزّن.

هذا التخطيط يجعل البناء والحساب رخيصين، مع نتيجة واحدة: يمكن للمؤثر أن يحمل الحد نفسه عدة مرات في آن واحد. المجاميع وحواصل الضرب تُلحق حدودًا ولا تدمجها، لذلك استدعِ simplify() عندما تحتاج إلى حدود فريدة (انظر إجراء العمليات الحسابية).

بناء المؤثرات​

أكثر الطرق مباشرة لبناء مؤثر هي from_dict()، التي تأخذ قاموسًا من صفّ (tuple) من العوامل إلى معامل. الدالتان cre() وann() تنشئان العوامل، والصف الفارغ هو الحد الثابت:

>>> b = BosonOperator.from_dict({(ann(0),): 1.0})
>>> b_dag = BosonOperator.from_dict({(cre(0),): 1.0})
>>> number = BosonOperator.from_dict({(cre(0), ann(0)): 1.0})
>>> print(format(number))
1.000000e0 +0.000000e0j * (+0 -0)

في الصيغة المطبوعة، يرمز +0 إلى b0†b_0^\dagger ويرمز -0 إلى b0b_0. المُنشئان zero() وone() يعيدان العنصر المحايد الجمعي والضربي، وfrom_terms() يعيد بناء مؤثر من مُكرِّر iter_terms().

توفر مكتبة المؤثرات المربّعات (quadratures) عديمة الأبعاد للموضع والزخم،

q^=b†+b2,p^=i(b†−b)2 ,\hat{q} = \frac{b^\dagger + b}{\sqrt{2}}, \qquad \hat{p} = \frac{i \left(b^\dagger - b\right)}{\sqrt{2}} \, ,

على شكل position() وmomentum(). مع دوال المبدّل (commutator) في الوحدة نفسها، يمكنك التحقق من الجبر مباشرة:

>>> from qiskit_bosons.operators.library import commutator, momentum, position
>>> q, p = position(0), momentum(0)
>>> q.is_hermitian() and p.is_hermitian()
True
>>> commutator(q, p).equiv(1j * BosonOperator.one())
True
>>> commutator(b, b_dag).equiv(BosonOperator.one())
True

توفر الوحدة أيضًا anti_commutator() وdouble_commutator(). الثلاث كلها تأخذ مؤثرات من النوع نفسه وتعيد مؤثرًا جديدًا.

انتبه إلى ترتيب حاصل الضرب​

يمكن ضرب مؤثرين بطريقتين، ومن السهل الخلط بينهما. المؤثر @ هو حاصل الضرب الرياضي، فـ a @ b يساوي ABAB. أما المؤثر & فهو التركيب، الذي يتبع اصطلاح Qiskit في القراءة من اليسار إلى اليمين: a & b يطبّق a أولًا ثم b، فهو BABA. دوال المبدّل تستخدم @، لذا commutator(a, b) هو [A,B][A, B]. وبناء القوس بنفسك باستخدام & يعكس إشارته بصمت:

>>> (b @ b_dag).equiv(b_dag @ b + BosonOperator.one())
True
>>> (b & b_dag).equiv(b_dag @ b)
True
>>> ((b @ b_dag) - (b_dag @ b)).equiv(BosonOperator.one())
True
>>> ((b & b_dag) - (b_dag & b)).equiv(-BosonOperator.one())
True

فحص المؤثر​

المؤثر غير قابل للتكرار مباشرة، لأن حدوده موجودة في المصفوفات المسطّحة أعلاه. استخدم بدلًا من ذلك iter_terms()، التي تعطي قائمة من أزواج (is_creation, mode) ومعاملًا لكل حد. المُكرِّر يعيد نسخًا، لذلك تغييرها لا يغيّر المؤثر.

>>> hopping = BosonOperator.from_dict(
... {
... (cre(0), ann(1)): -1.0,
... (cre(1), ann(0)): -1.0,
... (cre(2), cre(2), ann(2), ann(2)): 0.5,
... }
... )
>>> len(hopping)
3
>>> sorted(hopping.iter_terms())
[([(True, 0), (False, 1)], (-1+0j)), ([(True, 1), (False, 0)], (-1+0j)), ([(True, 2), (True, 2), (False, 2), (False, 2)], (0.5+0j))]

عدة دوال تلخّص بنية المؤثر:

>>> hopping.get_support()
{0, 1, 2}
>>> hopping.min_num_modes()
3
>>> hopping.max_rank()
4

get_support() هي مجموعة الأنماط التي يعمل عليها المؤثر، وmin_num_modes() هو أصغر سجل يمكنه استيعابه، وmax_rank() هو طول أطول حد فيه.

ملاحظة

النمط (mode) هو فهرس مجرد يسمّي درجة حرية بوزونية واحدة. ليس له معنى خاص به: أنت من تحدد أي موقع في شبكة، أو أي إحداثي اهتزازي، أو أي منفذ بصري يمثله النمط. يستخدم BosonicCircuit الفهارس نفسها (انظر العمل مع الدارات البوزونية)، لذلك يتفق المؤثر والبوابات التي تحمله على ما هو النمط 2 دون أن يعرف أي منهما ما يمثله. لا شيء في المؤثر يحدد عدد مستويات النمط، وهذا أيضًا سبب كون حجم السجل حدًا أدنى فقط؛ min_num_modes() هو أصغر سجل يحتوي المؤثر، وليس خاصية له.

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

>>> hopping.mode_occupations()
[1, 1, 2]
>>> hopping.mode_occupations([2, 0])
[2, 1]
>>> hopping.max_occupation()
2

فحصان إضافيان ينظران في الكميات المحفوظة. يحافظ المؤثر على عدد الجسيمات عندما يكون في كل حد عدد متساوٍ من مؤثرات الإنشاء والإفناء. تمرير قائمة بأحجام الكتل يختبر الشرط نفسه داخل كل كتلة من الأنماط المتتالية:

>>> hopping.conserves_particle_number()
True
>>> move = BosonOperator.from_dict({(cre(0), ann(2)): 1.0})
>>> move.conserves_sector([3])
True
>>> move.conserves_sector([2, 1])
False

أخيرًا، relabel_modes() تعيد تسمية الأنماط: النمط i يصبح permutation[i]. يجب أن تكون القائمة طويلة بما يكفي لفهرسة كل نمط في المؤثر، ويجب ألا تكرر أي عنصر:

>>> print(format(move.relabel_modes([5, 6, 7])))
1.000000e0 +0.000000e0j * (+5 -7)

إجراء العمليات الحسابية​

تدعم المؤثرات الجمع والطرح والنفي والضرب والقسمة على عدد، بالصيغة المعتادة للمؤثرات أو في المكان نفسه:

>>> total = 2 * b_dag + b_dag - 3j * b
>>> len(total)
3
>>> len(total.simplify())
2
>>> total.simplify().equiv(3 * b_dag - 3j * b)
True

لم تُدمج الحدود الثلاثة في total أبدًا، لذلك احتفظ المؤثر بـ b_dag مرتين حتى دمجتهما simplify(). تجمع هذه الدالة معاملات الحدود المتطابقة وتحذف الحدود التي يقل مقدار مجموعها عن عتبة تسامح. وهي عملية بنيوية: تقارن العوامل المكتوبة ولا تطبّق علاقات التبديل. الدالة ذات الصلة ichop() تزيل المعاملات الصغيرة في المكان دون دمج أولًا، لذلك استدعِ simplify() قبل ichop() عندما قد يحمل المؤثر حدودًا مكررة.

حواصل الضرب تستخدم @ أو & كما هو موضح أعلاه، و** يرفع المؤثر إلى أُسّ صحيح:

>>> x = b_dag + b
>>> print(format((x ** 2).simplify()))
1.000000e0 +0.000000e0j * (-0 -0)
1.000000e0 +0.000000e0j * (-0 +0)
1.000000e0 +0.000000e0j * (+0 -0)
1.000000e0 +0.000000e0j * (+0 +0)

تسرد النتيجة الترتيبات الأربعة كما كُتبت. اختزالها إلى صيغة واحدة يحتاج إلى علاقات التبديل، وهو ما يطبّقه القسم التالي.

المرافق (adjoint) يعكس ترتيب العوامل في كل حد، ويبدّل مؤثرات الإنشاء والإفناء، ويأخذ مرافق المعاملات المركّب. الدالة is_hermitian() تختبر ما إذا كان المؤثر يساوي مرافقه:

>>> hop = BosonOperator.from_dict({(cre(0), ann(1)): 1j})
>>> print(format(hop.adjoint()))
0.000000e0 -1.000000e0j * (+1 -0)
>>> hop.is_hermitian()
False
>>> (hop + hop.adjoint()).is_hermitian()
True

حد قفز منفرد ليس هيرميتيًا، وهذا مهم عندما تجمّع الحدود لتطور زمني: يجب أن تكون كل مجموعة هيرميتية بمفردها (انظر تجميع حدود المؤثر: استخدام بنية المؤثر).

الترتيب الطبيعي ومقارنة المؤثرات​

يمكن أن يكون حاصلا ضرب لمؤثرات السلّم المؤثر نفسه ومع ذلك يبدوان مختلفين، لأن b b†b\,b^\dagger وb†b+1b^\dagger b + 1 متساويان في الجبر. المؤثرات المتساوية بهذا المعنى لا يلزم أن تُخزَّن بشكل متساوٍ، لذلك تحتاج مقارنتها إلى علاقات التبديل. الدالة normal_ordered() تطبّقها: تنقل كل مؤثر إنشاء إلى يسار كل مؤثر إفناء وتضيف حدًا كلما تبدّل زوج على النمط نفسه.

>>> product = BosonOperator.from_dict({(ann(0), cre(0), ann(0), cre(0)): 1.0})
>>> ordered = product.normal_ordered()
>>> len(ordered)
5
>>> print(format(ordered.simplify()))
1.000000e0 +0.000000e0j * ()
3.000000e0 +0.000000e0j * (+0 -0)
1.000000e0 +0.000000e0j * (+0 +0 -0 -0)

يُعاد المؤثر المرتّب كمجموع غير مدمج، بخمسة حدود مخزّنة تختزل إلى ثلاثة بعد أن تدمجها simplify(). الترتيب الطبيعي لا يجعل أي حد يختفي: b†b†b^\dagger b^\dagger وكل عامل مكرر آخر يبقى.

الوسيط sandwich يحدد كيفية ترتيب الأنماط داخل مجموعتي العوامل. افتراضيًا تُرتَّب المجموعتان تنازليًا. مع sandwich=True، تكون مؤثرات الإنشاء تصاعدية ومؤثرات الإفناء تنازلية، مما يضع الفهارس الأكبر نحو المنتصف:

>>> pair = BosonOperator.from_dict({(cre(0), cre(1), ann(0), ann(1)): 1.0})
>>> list(pair.normal_ordered().iter_terms())
[([(True, 1), (True, 0), (False, 1), (False, 0)], (1+0j))]
>>> list(pair.normal_ordered(sandwich=True).iter_terms())
[([(True, 0), (True, 1), (False, 1), (False, 0)], (1+0j))]

المؤثر == يختبر المساواة التامة للحدود المخزّنة، دون تسامح ودون تبسيط، لذلك قد يبلّغ عن فرق بين مؤثرين متساويين رياضيًا. الدالة equiv() تضع فرق المؤثرين في الترتيب الطبيعي أولًا ثم تختبر أن كل معامل أقل من عتبة تسامح. وهي المقارنة التي تريدها في الغالب دائمًا:

>>> reordered = BosonOperator.from_dict({(cre(0), ann(0)): 1.0, (): 1.0})
>>> contracted = BosonOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> contracted == reordered
False
>>> contracted.simplify() == reordered.simplify()
False
>>> contracted.equiv(reordered)
True

الدالة simplify وحدها تُبقي الصيغتين منفصلتين، لأنها تدمج الحدود المكتوبة المتطابقة فقط. وبالمثل، is_hermitian() تقارن المؤثر بمرافقه في الترتيب الطبيعي، لذلك تتعرف على b b†+b†bb\,b^\dagger + b^\dagger b كمؤثر هيرميتي دون عمل إضافي.

ملاحظة

الترتيب الطبيعي متطابقة دقيقة في الجبر غير المقتطع، وequiv تعتمد على ذلك. وهو ليس متطابقة بعد الاقتطاع. المؤثر المحوَّل يعتمد على الصيغة التي تحوّلها، لذلك تختلف صورتا contracted وreordered المحوّلتان أعلاه، في أعلى مستوى محتفَظ به فقط. انظر اقتطاع فضاء فوك لمعرفة حجم الفرق وكيفية اختيار الصيغة.

ترتيب الحدود وتصفيتها​

إعادة ترتيب حدود المؤثر، بخلاف العوامل داخل الحد، لا تغيّر المؤثر لكنها قد تغيّر سلوك الخوارزميات التي تقرأ الحدود بالترتيب، مثل صيغة الضرب العشوائية. دوال qiskit_bosons.operators.terms تعيد مؤثرًا جديدًا وتُبقي أي فهارس مجموعات مرتبطة بحدودها.

canonical_order() ترتّب الحدود في ترتيب ثابت يعتمد فقط على العوامل المكتوبة، لذلك يُقرأ المؤثر بالشكل نفسه مهما كانت طريقة تجميعه. order_terms() ترتّب بمفتاح تقدّمه أنت، يستقبل كل حد على شكل الصف الذي تعطيه iter_terms():

>>> from qiskit_bosons.operators.terms import canonical_order, order_terms
>>> mixed = BosonOperator.from_dict({(cre(1), ann(0)): 1.0, (cre(0), ann(1)): 3.0, (): 2.0})
>>> list(canonical_order(mixed).iter_terms())
[([], (2+0j)), ([(True, 0), (False, 1)], (3+0j)), ([(True, 1), (False, 0)], (1+0j))]
>>> by_weight = order_terms(mixed, key=lambda term: abs(term[1]), reverse=True)
>>> [coeff for _, coeff in by_weight.iter_terms()]
[(3+0j), (2+0j), (1+0j)]

إذا كان المؤثر يحمل فهارس مجموعات (انظر تجميع حدود المؤثر: استخدام بنية المؤثر)، فإن الدالة group_order() تجعل كل مجموعة سلسلة متصلة من الحدود:

>>> from qiskit_bosons.operators.terms import group_order
>>> grouped = BosonOperator.from_dict({(cre(1), ann(0)): 1.0, (cre(0), ann(1)): 3.0, (): 2.0})
>>> grouped.groups = [1, 0, 1]
>>> group_order(grouped).groups
[0, 1, 1]

الدالة المساعدة الأخيرة تزيل الحدود بدلًا من إعادة ترتيبها. الحد الذي هو حاصل ضرب مؤثرات العدد، bi†bib_i^\dagger b_i، يضرب كل حالة فوك بطور فقط، لذلك يمكن لـ filter_diagonal_terms() أن تحذف الثابت ومؤثرات العدد وحواصل ضربها عندما لا يهم هذا الطور فيما تحسبه. وهي تغيّر المؤثر في المكان وتتطلب مؤثرًا مرتبًا ترتيبًا طبيعيًا، ولا تتحقق من ذلك:

>>> from qiskit_bosons.operators.terms import filter_diagonal_terms
>>> model = BosonOperator.from_dict(
... {
... (cre(0), ann(1)): -1.0,
... (cre(1), ann(0)): -1.0,
... (cre(0), cre(0), ann(0), ann(0)): 0.5,
... (cre(0), ann(0)): 0.2,
... (): 0.1,
... }
... )
>>> filter_diagonal_terms(model)
>>> len(model)
2
>>> model.is_hermitian()
True

لا يبقى سوى حدّي القفز. التفاعل في الموقع ومؤثر العدد قطريان في أساس الإشغال، لذلك يُزالان مع الثابت. يُعالَج النمط المتكرر في التفاعل بشكل صحيح: b0†b0†b0b0b_0^\dagger b_0^\dagger b_0 b_0 قطري، بينما b0†b0†b0b_0^\dagger b_0^\dagger b_0 ليس كذلك.

الخطوات التالية​