مبادئ تصميم تمثيلات المُشغّلات
يشرح هذا الدليل مبادئ التصميم المشتركة والمفاهيم الأساسية عبر كل تمثيلات المُشغّلات في الوحدة operators.
نظرة عامة
تتشارك تمثيلات المُشغّلات التي توفّرها هذه الوحدة عدة مبادئ تصميم أساسية:
-
بنية بيانات متفرقة: تُرمّز المُشغّلات العمليات غير المحايدة فقط. وتستلهم تخطيط البيانات الداخلي عمومًا من صيغ المصفوفات المتفرقة، مما يتيح تخزينًا وحسابًا فعّالين للأنظمة ذات الأنماط الكثيرة والإسهامات المهمة القليلة نسبيًا.
-
المرور على الحدود وإعادة البناء: بغضّ النظر عن التخزين المتفرق الداخلي، توفّر المُشغّلات واجهة مرور متسقة، فيمكنك فحص الحدود وتصفيتها وتحويلها دون فهم بنية البيانات الأساسية، ثم إعادة بناء مُشغّلات جديدة من الحدود المعدّلة.
-
الفهرسة المعتمدة على الأنماط: تستخدم المُشغّلات فهارس أنماط مجرّدة لوسم درجات الحرية الفرميونية، مما يتيح تخطيطًا مرنًا من الأنظمة الفيزيائية إلى تمثيل المُشغّل.
-
تجميع الحدود وعلاقات التبديل: تدعم المُشغّلات أصلًا معلومات التجميع التي تربط الحدود بفهارس المجموعات. يتيح هذا التحسينات والحفاظ على البنية الفيزيائية دون بنى بيانات منفصلة. راجع دليل التجميع للاستخدام العملي.
-
العمليات الحسابية والرياضية: تطبّق جميع المُشغّلات مجموعة متسقة من العمليات الحسابية (مثل الجمع والضرب والتركيب) والدوال الرياضية باستخدام بروتوكول
OperatorTrait، مما يتيح شيفرة موحّدة عبر أنواع المُشغّلات المختلفة. -
ترتيب حدود المُشغّل والصيغ النظامية: يمكن أن يكون لمُشغّلات متكافئة رياضيًا تمثيلات وسلوك مختلفان جدًا في الخوارزميات الكمومية. تدعم جميع تمثيلات المُشغّلات صيغًا نظامية متنوعة (مبنية على علاقات التبديل الخاصة بكل جبر) للحصول على تمثيلات مُشغّلات قانونية ويمكن التنبؤ بها وقابلة للتحسين.
بنية البيانات المتفرقة
تستخدم جميع المُشغّلات تمثيلًا متفرقًا لا تُتتبَّع فيه سوى العمليات غير المحايدة. تتكوّن كل عملية غير محايدة من معامل (عدد مركّب) وتسلسل أفعال على أنماط محددة.
يقلّل هذا النهج استهلاك الذاكرة ووقت الحساب بشكل كبير، خاصة للأنظمة ذات الأنماط الكثيرة والإسهامات المهمة القليلة نسبيًا. وبترميز العمليات غير المحايدة فقط، تركّز العمليات على ما يهم فحسب، مما يتيح العمل مع أنظمة كبيرة يستحيل التعامل معها بالتمثيلات الكثيفة. وإضافةً إلى ذلك، تتوسّع المُشغّلات طبيعيًا لأي عدد من الأنماط؛ فالمُشغّل الذي يعمل على الأنماط {0, 1} يعمل دون تغيير في أنظمة فيها أنماط أكثر بكثير لأن الأنماط غير المتأثرة محايدة ضمنيًا.
تُحفَظ الحدود المتطابقة منفصلة أثناء العمليات الحسابية ويجب دمجها صراحةً عند الحاجة.
صيغة التخزين الداخلي
داخليًا، تُخزَّن المُشغّلات في مصفوفات مستلهمة من صيغ المصفوفات المتفرقة:
-
مصفوفة المعاملات: المعامل المركّب لكل حد
-
مصفوفة فهارس الأنماط: الأنماط الفرميونية التي يعمل عليها كل فعل
-
مصفوفة الحدود الفاصلة: فهارس تحدّد أين تبدأ أنماط كل حد وأين تنتهي في مصفوفة الأنماط
قد تُوجد مصفوفات إضافية بحسب نوع المُشغّل. فمثلًا تتضمن نسخ FermionOperator مصفوفة أفعال من القيم المنطقية تحدّد نوع الفعل الفرميوني الذي يعمل على فهرس النمط المقابل. أما الفئة MajoranaOperator فلا تتطلب هذا التمييز لأنها ترمّز تلك المعلومة في تكافؤ فهرس النمط. راجع توثيق واجهة البرمجة لنوع مُشغّلك لفهم صيغة التخزين الكاملة.
تُظهر الأمثلة التالية كيف تُنظَّم هذه المصفوفات. الأول بناء مباشر باستخدام المصفوفات المتفرقة:
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> # Construct operators directly using sparse arrays
>>> # First operator: 1.0 * +0 -1
>>> op1 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[0, 1],
... boundaries=[0, 2],
... )
>>>
>>> # Second operator: 1.0 * +2 -3
>>> op2 = FermionOperator(
... coeffs=[1.0],
... actions=[True, False],
... modes=[2, 3],
... boundaries=[0, 2],
... )
>>>
>>> # Combine the sparse operators
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
#include <qiskit_fermions.h>
// Construct first operator: 1.0 * +0 -1
QkComplex64 coeff1[1] = {{1.0, 0.0}};
bool actions1[2] = {true, false};
uint32_t modes1[2] = {0, 1};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, actions1, modes1, boundaries1);
// Construct second operator: 1.0 * +2 -3
QkComplex64 coeff2[1] = {{1.0, 0.0}};
bool actions2[2] = {true, false};
uint32_t modes2[2] = {2, 3};
uint32_t boundaries2[2] = {0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(1, 2, coeff2, actions2, modes2, boundaries2);
// Combine the sparse operators. There is no in-place add in the C API, so
// the sum is a new operator that the caller owns.
QfFermionOperator *sum = qf_ferm_op_add(op1, op2);
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(sum);
طرق البناء المريحة
لمطوّري Python، تتوفر عدة طرق بناء مريحة تُخفي تفاصيل التخزين المتناثر. بهالطرق يصير أسهل تبني المؤثرات من غير ما تشغل بالك بإدارة مصفوفات المعاملات والأنماط (modes) والحدود:
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct operators using operator algebra notation
>>> op1 = FermionOperator.from_dict({(cre(0), ann(1)): 1.0})
>>> op2 = FermionOperator.from_dict({(cre(2), ann(3)): 1.0})
>>>
>>> # The result is sparse even when combining them
>>> op1 += op2
>>> print(format(op1))
1.000000e0 +0.000000e0j * (+0 -1)
1.000000e0 +0.000000e0j * (+2 -3)
// The C API uses direct array construction; convenience methods are not available.
ممكن تدعم تطبيقات المؤثرات الفردية طرق بناء إضافية تناسب حالة استخدامها. راجع توثيق API الخاص بنوع المؤثر عندك لتشوف كل خيارات البناء المتاحة.
التكرار على الحدود وإعادة البناء
توفّر المؤثرات واجهة تكرار موحّدة عبر OperatorTrait.iter_terms() بغض النظر عن تمثيلها المتناثر الداخلي. وبالتالي تقدر تفحص الحدود أو تفلترها أو تحوّلها من غير ما تحتاج تفهم بنية البيانات الأساسية. بعدين تقدر تعيد بناء مؤثر جديد من الحدود المحوّلة عبر OperatorTrait.from_terms().
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct an operator with terms of different orders
>>> op = FermionOperator.from_dict({
... (): 0.5, # constant term (order 0)
... (cre(0), ann(1)): 1.0, # two-body term (order 2)
... (cre(0), cre(1), ann(1), ann(0)): 0.25 # four-body term (order 4)
... })
>>>
>>> # Filter to keep only terms of order 2
>>> order_two_terms = [
... (term, coeff) for term, coeff in op.iter_terms()
... if len(term) == 2
... ]
>>>
>>> # Reconstruct operator from filtered terms
>>> filtered_op = FermionOperator.from_terms(order_two_terms)
>>> print(f"Original operator has {len(op)} terms")
Original operator has 3 terms
>>> print(f"Filtered operator has {len(filtered_op)} term")
Filtered operator has 1 term
// WARNING: Term iteration and filtering are not yet available in the C API.
الفهرسة المعتمدة على الأنماط (modes)
كل تمثيلات المؤثرات تسمّي الفهارس اللي تؤثر عليها حدودها أنماط (modes). النمط هو ببساطة فهرس يحدد درجة حرية فرميونية في نظامك. ربط درجات الحرية الفيزيائية (مثل المدارات الفراغية أو حالات السبين أو أرقام كمّية أخرى) بفهارس الأنماط متروك لك، وهذا يعطي أقصى مرونة.
هالتجريد موجود كمان في وحدة qiskit_fermions.circuit، حيث يعمل FermionicCircuit على سجل من الأنماط الفرميونية. في تمثيلات المؤثرات والدوائر (circuits) على حد سواء، توفّر الأنماط طريقة موحّدة ومستقلة عن الجبر لتحديد درجات الحرية المشاركة في أي عملية.
التطبيقات الحالية تستخدم أنماطًا بلا سبين: كل تمثيلات المؤثرات اللي توفرها هالوحدة حاليًا تعامل الأنماط كدرجات حرية فرميونية بلا سبين. يعني إذا نظامك فيه إلكترونات أو فرميونات بسبين لأعلى وسبين لأسفل، لازم تربطها صراحةً بأنماط مختلفة (مثلًا الأنماط 0-3 للسبين لأعلى لأربعة مدارات فراغية، والأنماط 4-7 للسبين لأسفل، أو أي اصطلاح آخر تختاره).
هالتصميم يخلي التمثيلات الأساسية بسيطة وعامة، ويتجنب فرض اصطلاح معين لترتيب السبين. وحدات مساعدة مثل qiskit_fermions.operators.library توفر دوال مريحة (مثل FCIDump.from_file()) تتولى هالربط عنك عند تحميل بيانات البنية الإلكترونية.
مع تطور الحزمة، ممكن تنضاف تمثيلات مؤثرات بسبين تدعم درجات حرية السبين بشكل أصلي في نموذج بياناتها. راح تتميّز بوضوح عن التطبيقات الحالية بلا سبين، وتتعايش معها في نفس الوحدة.
تجميع الحدود وعلاقات التبديل
مثل المعاملات وفهارس الأنماط، تقدر المؤثرات اختياريًا تخزّن مصفوفة مجموعات تربط كل حد برقم مجموعة. بدمج التجميع مباشرةً في تمثيل المؤثر كجزء من بنية البيانات المتناثرة، تنتقل معلومات التجميع بشكل طبيعي مع المؤثر عبر التحويلات. هذا يتيح استخدام البنية بشكل منهجي، سواء كانت ناتجة عن خصائص فيزيائية أو علاقات جبرية أو تناظرات خاصة بالمسألة. وبعدين تُستخدم المعلومات المنظّمة في عمليات لاحقة مثل تركيب الدوائر (circuits) وتحليلها عبر طرق مثل OperatorTrait.split_out_groups().
للحصول على إرشادات مفصلة عن كيفية تجميع حدود المؤثر في مسارات عملك، شوف دليل التجميع.
- PYTHON
- C
>>> from qiskit_fermions.operators import MajoranaOperator, gamma
>>> op = MajoranaOperator.from_dict({
... (gamma(0, False),): 1.0,
... (gamma(1, False),): 1.0,
... (gamma(2, False), gamma(3, False)): 1.0
... })
>>> # Assign group indices to terms
>>> op.groups = [0, 0, 1]
>>> # Partition operator by groups
>>> grouped_ops = op.split_out_groups()
#include <qiskit_fermions.h>
// Create operator with 3 terms
QkComplex64 coeffs[3] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t modes[4] = {0, 1, 2, 3};
uint32_t boundaries[4] = {0, 1, 2, 4};
QfMajoranaOperator *op = qf_maj_op_new(3, 4, coeffs, modes, boundaries);
// Assign grouping information
uint32_t groups[3] = {0, 0, 1};
qf_maj_op_set_groups(op, groups, 3);
// Partition operator by groups
QfMajoranaOperator *grouped_ops[2];
qf_maj_op_split_out_groups(op, NULL, 0, grouped_ops);
العمليات الحسابية والرياضية
كل المؤثرات تطبّق بروتوكول OperatorTrait، اللي يوفر مجموعة موحدة من العمليات عبر أنواع المؤثرات المختلفة. هذا يضمن أن الكود المكتوب لتمثيل مؤثر واحد يشتغل بنفس الطريقة مع غيره. شوف qiskit_fermions.protocols لتعرف علاقة هالبروتوكول بباقي البروتوكولات اللي تعرّفها هالحزمة.
يشمل البروتوكول عمليات حسابية (مثل الجمع والضرب والتركيب)، وعمليات بنيوية (التكرار على الحدود، تحليل دعم الأنماط، إعادة التسمية)، ودوال رياضية (الترتيب الطبيعي، التبسيط، فحص التكافؤ)، وغيرها. راجع توثيق OperatorTrait للحصول على مرجع كامل لكل العمليات المتاحة.
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Construct a Hermitian operator: H = +0 -1 + +1 -0
>>> op = FermionOperator.from_dict({
... (cre(0), ann(1)): 1.0,
... (cre(1), ann(0)): 1.0
... })
>>>
>>> # Check if the operator is Hermitian
>>> print(f"Operator is Hermitian: {op.is_hermitian(atol=1e-10)}")
Operator is Hermitian: True
#include <qiskit_fermions.h>
// Construct a Hermitian operator: H = +0 -1 + +1 -0
QkComplex64 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
bool actions[4] = {true, false, true, false};
uint32_t modes[4] = {0, 1, 1, 0};
uint32_t boundaries[3] = {0, 2, 4};
QfFermionOperator *op = qf_ferm_op_new(2, 4, coeffs, actions, modes, boundaries);
// Check if the operator is Hermitian
bool is_hermitian = qf_ferm_op_is_hermitian(op, 1e-10);
printf("Operator is Hermitian: %s\n", is_hermitian ? "true" : "false");
// Clean up
qf_ferm_op_free(op);
تتحقق is_hermitian() من أن عن طريق اختزال هالفرق إلى الصيغة الطبيعية ومقارنته بـ zero(). وبالتفصيل بدلالة عمليات البروتوكول الأخرى، تنفّذ الآتي:
>>> difference = (op - op.adjoint()).normal_ordered().simplify(atol=1e-10)
>>> difference.equiv(FermionOperator.zero(), atol=1e-10)
True
قوة هالفحص تعتمد على نوع المؤثر، لأنها تعتمد على مدى اكتمال تقليص الصيغة الطبيعية لذلك النوع للمولّدات المتجاورة. النتيجة True موثوقة دائمًا، لكن في بعض الأنواع تكون النتيجة False متحفظة: شوف is_hermitian() للتفاصيل.
المثال يستخدم atol=1e-10. المعامل atol (التسامح المطلق) يحدد عتبة. المعاملات اللي مقدارها أصغر من atol تُعامل كأنها صفر وتُهمل. هذا ضروري للاستقرار العددي عند مقارنة المؤثرات، لأن حسابات الفاصلة العائمة ممكن تسبب أخطاء تقريب صغيرة كانت راح تمنع التعرف على المؤثرات المتكافئة.
مع أن بروتوكول OperatorTrait يوفر واجهة مشتركة، ممكن تقدم تطبيقات المؤثرات الفردية طرقًا مريحة إضافية ما هي جزء من البروتوكول. مثلًا، يوفر FermionOperator وMajoranaOperator الطريقة max_rank() اللي تعطي طول أطول حد فيهما. راجع دائمًا توثيق API الخاص بنوع المؤثر عندك لتكتشف كل الوظائف المتاحة.
ترتيب حدود المؤثر والصيغ الطبيعية
من التحديات الأساسية في جبر المؤثرات الكمّية أن المؤثرات المتكافئة رياضيًا ممكن تُمثَّل بطرق كثيرة مختلفة، ولكل طريقة تبعات مختلفة على الخوارزميات الكمّية. نفس المؤثر ممكن يُكتب بصيغ متكافئة جبريًا (مثلًا يمكن التعبير عن كـ )، ومع ذلك تؤدي هالتمثيلات إلى سلوك مختلف في تركيب الدوائر (circuits) والتبسيط والخوارزميات العددية.
تدعم تمثيلات المؤثرات عمليات الترتيب الطبيعي (normal ordering) اللي تحوّل المؤثرات إلى صيغ قانونية خاصة بالجبر باستخدام علاقات التبديل. هذا يتيح مقارنات موثوقة (المؤثران المتكافئان لهما نفس الصيغة المرتّبة طبيعيًا)، ويكشف التبسيطات (علاقات التبديل تجعل الحدود تتلاشى أو تندمج)، ويدعم الخوارزميات اللي تحتاج صيغًا محددة للمؤثرات لضمان الصحة والكفاءة.
ممكن تطبّق تمثيلات المؤثرات المختلفة الترتيب الطبيعي بناءً على علاقات تبديل مختلفة تناسب جبرها. مثلًا، الترتيب الطبيعي الفرميوني يستخدم علاقات التبديل المضاد ()، بينما الترتيب الطبيعي لماجورانا يستخدم اصطلاحات جبرية مختلفة (). راجع دائمًا توثيق نوع المؤثر عندك لتفهم كيف يُطبَّق الترتيب الطبيعي.
- PYTHON
- C
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Two different representations of the same operator
>>> op1 = FermionOperator.from_dict({(ann(0), cre(0)): 1.0})
>>> op2 = FermionOperator.from_dict({(): 1.0, (cre(0), ann(0)): -1.0})
>>>
>>> # Direct comparison fails due to different forms
>>> op1.equiv(op2, atol=1e-10)
False
>>>
>>> # Normal-order both and compare again
>>> op1_normal = op1.normal_ordered()
>>> op2_normal = op2.normal_ordered()
>>> op1_normal.equiv(op2_normal, atol=1e-10)
True
#include <qiskit_fermions.h>
#include <stdbool.h>
// Two different representations of the same operator
QkComplex64 coeff1[1] = {{1.0, 0.0}};
bool actions1[2] = {false, true};
uint32_t modes1[2] = {0, 0};
uint32_t boundaries1[2] = {0, 2};
QfFermionOperator *op1 = qf_ferm_op_new(1, 2, coeff1, actions1, modes1, boundaries1);
QkComplex64 coeff2[2] = {{1.0, 0.0}, {-1.0, 0.0}};
bool actions2[2] = {true, false};
uint32_t modes2[2] = {0, 0};
uint32_t boundaries2[3] = {0, 0, 2};
QfFermionOperator *op2 = qf_ferm_op_new(2, 2, coeff2, actions2, modes2, boundaries2);
// Direct comparison fails due to different forms
bool equiv_before = qf_ferm_op_equiv(op1, op2, 1e-10);
printf("Equivalent before normal ordering: %s\n", equiv_before ? "true" : "false");
// Normal-order both and compare again
QfFermionOperator *op1_normal = qf_ferm_op_normal_ordered(op1, NULL);
QfFermionOperator *op2_normal = qf_ferm_op_normal_ordered(op2, NULL);
bool equiv_after = qf_ferm_op_equiv(op1_normal, op2_normal, 1e-10);
printf("Equivalent after normal ordering: %s\n", equiv_after ? "true" : "false");
// Clean up
qf_ferm_op_free(op1);
qf_ferm_op_free(op2);
qf_ferm_op_free(op1_normal);
qf_ferm_op_free(op2_normal);
طريقة البروتوكول OperatorTrait.normal_ordered() تترك وسائطها الموضعية وذات الكلمات المفتاحية غير محددة عمدًا، وهذا يسمح لتطبيقات المؤثرات الفعلية بتعريف معاملات قابلة للضبط تتحكم في الصيغة القانونية الدقيقة الناتجة. هذا يتيح تحسينات خاصة بالمؤثر وأشكالًا مختلفة للصيغة الطبيعية تناسب جبره أو حالة استخدامه. راجع توثيق API الخاص بنوع المؤثر عندك لتشوف المعاملات المتاحة.