หลักการออกแบบของการแทนค่าตัวดำเนินการ
คู่มือนี้อธิบายหลักการออกแบบทั่วไปและแนวคิดหลักที่ใช้ร่วมกันในการแทนค่าตัวดำเนินการทั้งหมดในโมดูล operators
ภาพรวม
การแทนค่าตัวดำเนินการที่โมดูลนี้จัดให้ใช้หลักการออกแบบพื้นฐานร่วมกันหลายข้อ:
-
โครงสร้างข้อมูลแบบเบาบาง: ตัวดำเนินการเข้ารหัสเฉพาะการดำเนินการที่ไม่ใช่เอกลักษณ์ รูปแบบข้อมูลภายในโดยทั่วไปได้แรงบันดาลใจจากรูปแบบข้อมูลเมทริกซ์เบาบาง ทำให้จัดเก็บและคำนวณได้อย่างมีประสิทธิภาพสำหรับระบบที่มี mode จำนวนมากแต่มีส่วนร่วมที่สำคัญค่อนข้างน้อย
-
การวนผ่านพจน์และการสร้างใหม่: ไม่ว่าการจัดเก็บแบบเบาบางภายในจะเป็นอย่างไร ตัวดำเนินการมีอินเทอร์เฟซการวนซ้ำที่สอดคล้องกัน คุณจึงตรวจสอบ กรอง และแปลงพจน์ได้โดยไม่ต้องเข้าใจโครงสร้างข้อมูลเบื้องหลัง แล้วสร้างตัวดำเนินการใหม่จากพจน์ที่ปรับแล้ว
-
การจัดดัชนีตาม mode: ตัวดำเนินการใช้ดัชนี mode เชิงนามธรรมเพื่อระบุองศาอิสระของเฟอร์มิออน ทำให้แมปจากระบบกายภาพไปยังการแทนค่าตัวดำเนินการได้อย่างยืดหยุ่น
-
การจัดกลุ่มพจน์และความสัมพันธ์การ commute: ตัวดำเนินการรองรับข้อมูลการจัดกลุ่มโดยกำเนิด ซึ่งเชื่อมโยงพจน์กับดัชนีกลุ่ม ทำให้ปรับให้เหมาะสมและคงโครงสร้างทางกายภาพได้โดยไม่ต้องมีโครงสร้างข้อมูลแยก ดู คู่มือการจัดกลุ่ม สำหรับการใช้งานจริง
-
การดำเนินการทางเลขคณิตและคณิตศาสตร์: ตัวดำเนินการทั้งหมดนำชุดการดำเนินการทางเลขคณิตที่สอดคล้องกัน (เช่น การบวก การคูณ และการประกอบ) และฟังก์ชันทางคณิตศาสตร์มาใช้ โดยใช้โปรโตคอล
OperatorTraitทำให้เขียนโค้ดเดียวกันได้กับตัวดำเนินการหลายชนิด -
ลำดับพจน์ของตัวดำเนินการและรูปแบบปกติ: ตัวดำเนินการที่เท่าเทียมกันทางคณิตศาสตร์อาจมีการแทนค่าและพฤติกรรมต่างกันมากในอัลกอริทึมควอนตัม การแทนค่าตัวดำเนินการทั้งหมดรองรับรูปแบบปกติหลายแบบ (อิงตามความสัมพันธ์การ commute เฉพาะพีชคณิต) เพื่อให้ได้การแทนค่าตัวดำเนินการที่เป็นแบบแผน คาดการณ์ได้ และปรับให้เหมาะสมได้
โครงสร้างข้อมูลแบบเบาบาง
ตัวดำเนินการทั้งหมดใช้ การแทนค่าแบบเบาบาง ที่ติดตามเฉพาะการดำเนินการที่ไม่ใช่เอกลักษณ์ การดำเนินการที่ไม่ใช่เอกลักษณ์แต่ละอย่างประกอบด้วย สัมประสิทธิ์ (จำนวนเชิงซ้อน) และ ลำดับของ action บน mode เฉพาะ
แนวทางนี้ลดการใช้หน่วยความจำและเวลาคำนวณลงอย่างมาก โดยเฉพาะสำหรับระบบที่มี mode จำนวนมากแต่มีส่วนร่วมที่สำคัญค่อนข้างน้อย การเข้ารหัสเฉพาะการดำเนินการที่ไม่ใช่เอกลักษณ์ทำให้การดำเนินการโฟกัสแต่สิ่งที่สำคัญ ทำงานกับระบบขนาดใหญ่ที่ไม่อาจทำได้ด้วยการแทนค่าแบบหนาแน่น นอกจากนี้ ตัวดำเนินการขยายไปยังจำนวน mode เท่าใดก็ได้โดยธรรมชาติ ตัวดำเนินการที่กระทำบน mode {0, 1} ใช้งานได้โดยไม่ต้องแก้ไขในระบบที่มี mode มากกว่านั้นมาก เพราะ mode ที่ไม่ได้รับผลกระทบเป็นเอกลักษณ์โดยปริยาย
พจน์ที่เหมือนกันจะถูกเก็บแยกกันระหว่างการดำเนินการทางเลขคณิต และต้องรวมกันอย่างชัดเจนหากจำเป็น
รูปแบบการจัดเก็บภายใน
ภายใน ตัวดำเนินการถูกเก็บในอาร์เรย์ที่ได้แรงบันดาลใจจากรูปแบบข้อมูลเมทริกซ์เบาบาง:
-
อาร์เรย์สัมประสิทธิ์: สัมประสิทธิ์เชิงซ้อนของแต่ละพจน์
-
อาร์เรย์ดัชนี mode: mode เฟอร์มิออนที่แต่ละ action กระทำ
-
อาร์เรย์ขอบเขต: ดัชนีที่ระบุว่า mode ของแต่ละพจน์เริ่มและสิ้นสุดที่ใดในอาร์เรย์ mode
อาจมีอาร์เรย์เพิ่มเติมขึ้นอยู่กับชนิดของตัวดำเนินการ ตัวอย่างเช่น อินสแตนซ์ของ FermionOperator มี อาร์เรย์ action ของบูลีนที่ระบุชนิดของ action แบบเฟอร์มิออนที่กระทำต่อดัชนี mode นั้น ในทางตรงกันข้าม คลาส MajoranaOperator ไม่ต้องการการแยกแยะนี้ เพราะเข้ารหัสข้อมูลนั้นไว้ในพาริตีของดัชนี mode ดูเอกสาร API ของชนิดตัวดำเนินการของคุณเพื่อทำความเข้าใจรูปแบบการจัดเก็บทั้งหมด
ตัวอย่างต่อไปนี้แสดงว่าอาร์เรย์เหล่านี้ถูกจัดระเบียบอย่างไร อย่างแรกคือการสร้างโดยตรงโดยใช้อาร์เรย์แบบเบาบาง:
- 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 มีวิธีสร้างที่สะดวกหลายวิธีที่ซ่อนรายละเอียดการเก็บข้อมูลแบบ sparse ไว้ ทำให้สร้างตัวดำเนินการได้ง่ายขึ้นโดยไม่ต้องกังวลกับการจัดการอาร์เรย์ของสัมประสิทธิ์ โหมด และขอบเขต:
- 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.
การimplement ตัวดำเนินการแต่ละแบบอาจรองรับวิธีสร้างเพิ่มเติมที่เหมาะกับกรณีใช้งานเฉพาะของมัน ดูเอกสาร API ของตัวดำเนินการประเภทที่คุณใช้เพื่อดูตัวเลือกการสร้างทั้งหมด
การวนซ้ำเทอมและการสร้างใหม่
ตัวดำเนินการมีอินเทอร์เฟซสำหรับวนซ้ำที่สอดคล้องกันผ่าน OperatorTrait.iter_terms() โดยไม่ขึ้นกับการแทนแบบ sparse ภายในของมัน ดังนั้นคุณจึงตรวจสอบ กรอง หรือแปลงเทอมได้โดยไม่ต้องเข้าใจโครงสร้างข้อมูลเบื้องหลัง จากนั้นคุณสร้างตัวดำเนินการใหม่จากเทอมที่แปลงแล้วได้โดยใช้ 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.
การจัดดัชนีแบบอิงโหมด
การแทนตัวดำเนินการทุกแบบเรียกดัชนีที่เทอมของมันกระทำต่อว่า โหมด โหมดคือดัชนีที่ระบุดีกรีอิสระแบบเฟอร์มิออนหนึ่งตัวในระบบของคุณ การแมปจากดีกรีอิสระทางกายภาพ (เช่น ออร์บิทัลเชิงพื้นที่ สถานะสปิน หรือเลขควอนตัมอื่น ๆ) ไปเป็นดัชนีโหมดปล่อยให้ผู้ใช้เป็นผู้กำหนด ทำให้ยืดหยุ่นได้สูงสุด
แนวคิดนามธรรมนี้ปรากฏในโมดูล qiskit_fermions.circuit ด้วย ซึ่ง FermionicCircuit ทำงานบนรีจิสเตอร์ของโหมดเฟอร์มิออน ทั้งในการแทนแบบตัวดำเนินการและแบบ Circuit โหมดเป็นวิธีที่สอดคล้องกันและไม่ขึ้นกับพีชคณิตในการระบุว่าดีกรีอิสระใดมีส่วนร่วมในการดำเนินการหนึ่ง ๆ
การimplement ปัจจุบันใช้โหมดที่ไม่มีสปิน: การแทนตัวดำเนินการทั้งหมดที่โมดูลนี้มีให้ในปัจจุบันถือว่าโหมดเป็นดีกรีอิสระแบบเฟอร์มิออนที่ไม่มีสปิน นั่นหมายความว่าถ้าระบบของคุณมีทั้งอิเล็กตรอนหรือเฟอร์มิออนสปินขึ้นและสปินลง คุณต้องแมปมันไปยังโหมดที่แตกต่างกันอย่างชัดเจน (เช่น โหมด 0-3 สำหรับสปินขึ้นของออร์บิทัลเชิงพื้นที่สี่ตัว โหมด 4-7 สำหรับสปินลง หรือแบบอื่นใดที่คุณเลือก)
การออกแบบนี้ทำให้การแทนหลักเรียบง่ายและเป็นแบบทั่วไป โดยไม่บังคับลำดับสปินเฉพาะ โมดูลยูทิลิตี้อย่าง qiskit_fermions.operators.library มีฟังก์ชันอำนวยความสะดวก (เช่น FCIDump.from_file()) ที่จัดการการแมปแบบนี้ให้คุณเมื่อโหลดข้อมูลโครงสร้างอิเล็กทรอนิกส์
เมื่อแพ็กเกจพัฒนาไป อาจมีการเพิ่มการแทนตัวดำเนินการแบบมีสปินที่รองรับดีกรีอิสระของสปินในโมเดลข้อมูลโดยตรง ซึ่งจะแยกแยะจากการ implement แบบไม่มีสปินในปัจจุบันอย่างชัดเจน และจะอยู่ร่วมกันในโมดูล
การจัดกลุ่มเทอมและความสัมพันธ์การสลับที่
เช่นเดียวกับสัมประสิทธิ์และดัชนีโหมด ตัวดำเนินการสามารถเก็บ อาร์เรย์กลุ่ม ได้ตามต้องการ ซึ่งเชื่อมแต่ละเทอมกับดัชนีกลุ่ม การรวมการจัดกลุ่มเข้าไปในการแทนตัวดำเนินการโดยตรงเป็นส่วนหนึ่งของโครงสร้างข้อมูลแบบ sparse ทำให้ข้อมูลการจัดกลุ่มติดไปกับตัวดำเนินการตลอดการแปลงโดยธรรมชาติ ซึ่งช่วยให้ใช้โครงสร้างได้อย่างเป็นระบบ ไม่ว่าจะมาจากสมบัติทางกายภาพ ความสัมพันธ์เชิงพีชคณิต หรือสมมาตรเฉพาะของปัญหา จากนั้นข้อมูลที่มีโครงสร้างนี้สามารถนำไปใช้ในการดำเนินการขั้นถัดไป เช่น การสังเคราะห์และการแยกส่วน Circuit โดยใช้เมธอดอย่าง OperatorTrait.split_out_groups()
สำหรับคำแนะนำโดยละเอียดเกี่ยวกับวิธีจัดกลุ่มเทอมของตัวดำเนินการใน workflow ของคุณ ดูคู่มือ การจัดกลุ่ม
- 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);
การดำเนินการทางเลขคณิตและคณิตศาสตร์
ตัวดำเนินการทั้งหมด implement โปรโตคอล 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 จะให้อินเทอร์เฟซร่วม แต่การ implement ตัวดำเนินการแต่ละแบบอาจมีเมธอดอำนวยความสะดวกเพิ่มเติมที่ไม่อยู่ในโปรโตคอล ตัวอย่างเช่น FermionOperator และ MajoranaOperator มีเมธอด max_rank() ที่รายงานความยาวของเทอมที่ยาวที่สุด ควรดูเอกสาร API ของตัวดำเนินการประเภทที่คุณใช้เสมอเพื่อค้นหาฟังก์ชันทั้งหมดที่มี
การจัดลำดับเทอมของตัวดำเนินการและรูปปกติ
ความท้าทายพื้นฐานในพีชคณิตตัวดำเนินการควอนตัมคือ ตัวดำเนินการที่เทียบเท่ากันทางคณิตศาสตร์สามารถแทนได้หลายรูปแบบ แต่ละแบบมีผลต่ออัลกอริทึมควอนตัมต่างกัน ตัวดำเนินการเดียวกันเขียนได้ในรูปที่เทียบเท่ากันเชิงพีชคณิต (เช่น เขียนเป็น ได้) แต่การแทนเหล่านี้ให้พฤติกรรมต่างกันในการสังเคราะห์ Circuit การทำให้เรียบง่าย และอัลกอริทึมเชิงตัวเลข
การแทนตัวดำเนินการรองรับการดำเนินการ การจัดลำดับแบบปกติ ที่แปลงตัวดำเนินการให้เป็นรูปแบบมาตรฐานเฉพาะพีชคณิตโดยใช้ความสัมพันธ์การสลับที่ ซึ่งช่วยให้เปรียบเทียบได้อย่างเชื่อถือได้ (ตัวดำเนินการที่เทียบเท่ากันสองตัวมีรูปแบบที่จัดลำดับแบบปกติเหมือนกัน) เผยให้เห็นการทำให้เรียบง่าย (ความสัมพันธ์การสลับที่ทำให้เทอมหักล้างหรือรวมกัน) และรองรับอัลกอริทึมที่ต้องการรูปแบบตัวดำเนินการเฉพาะเพื่อความถูกต้องและประสิทธิภาพ
การแทนตัวดำเนินการแต่ละแบบอาจ implement การจัดลำดับแบบปกติโดยอิงความสัมพันธ์การสลับที่ที่ต่างกันตามพีชคณิตของมัน ตัวอย่างเช่น การจัดลำดับแบบปกติของเฟอร์มิออนใช้ความสัมพันธ์การสลับที่แบบปฏิสับเปลี่ยน () ส่วนการจัดลำดับแบบปกติของ Majorana ใช้ข้อกำหนดพีชคณิตที่ต่างออกไป () ควรดูเอกสารของตัวดำเนินการประเภทที่คุณใช้เสมอเพื่อทำความเข้าใจว่าการจัดลำดับแบบปกติถูก implement อย่างไร
- 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() จงใจไม่กำหนดอาร์กิวเมนต์แบบตำแหน่งและแบบคีย์เวิร์ด เพื่อให้การ implement ตัวดำเนินการจริงกำหนดพารามิเตอร์ที่ปรับได้ซึ่งควบคุมรูปแบบมาตรฐานที่ได้ ซึ่งช่วยให้ทำการปรับแต่งเฉพาะตัวดำเนินการและรูปปกติแบบต่าง ๆ ที่เหมาะกับพีชคณิตหรือกรณีใช้งานของมันได้ ดูเอกสาร API ของตัวดำเนินการประเภทที่คุณใช้เพื่อดูว่ามีพารามิเตอร์ใดบ้าง