หลักการออกแบบการแทนตัวดำเนินการ
คู่มือนี้อธิบายว่า BosonOperator ถูกจัดโครงสร้างอย่างไรและใช้งานอย่างไร ได้แก่ วิธีสร้าง วิธีตรวจสอบและรวมตัวดำเนินการ และวิธีนำตัวดำเนินการสองตัวมาอยู่ในรูปที่เปรียบเทียบกันได้ เริ่มที่นี่เมื่อคุณเขียนแฮมิลโทเนียน
BosonOperator คือผลรวมที่แน่นอนของผลคูณของตัวดำเนินการสร้างและทำลายโบซอนที่มีสัมประสิทธิ์เชิงซ้อน มันไม่มีค่าตัดจำนวนการครอบครอง โหมดโบซอนรับโบซอนได้จำนวนเท่าใดก็ได้ ปริภูมิ Fock ของมันจึงมีมิติอนันต์ และการตัดทอนเกิดขึ้นเมื่อแมปตัวดำเนินการไปเป็นคิวบิตเท่านั้น (ดู ตัดทอนปริภูมิ Fock) ทุกอย่างในคู่มือนี้จึงเกิดขึ้นในพีชคณิตที่แน่นอนของความสัมพันธ์การสลับที่แบบคาโนนิคัล
มีสองผลที่ตามมาซึ่งกำหนดอินเทอร์เฟซ ดัชนีโหมดซ้ำได้ภายในพจน์เดียว เพราะ เป็นตัวดำเนินการที่ใช้ได้ซึ่งสร้างโบซอนสองตัว นอกจากนี้ การสลับลำดับสองปัจจัยไม่เคยทำให้เกิดเครื่องหมาย แม้ว่าการย้ายตัวดำเนินการทำลายผ่านตัวดำเนินการสร้างบนโหมดเดียวกันจะเพิ่มพจน์
การจัดเก็บ
ตัวดำเนินการเก็บพจน์ของมันในอาร์เรย์แบนสี่ชุด คล้ายรูปแบบ compressed sparse row ของเมทริกซ์เบาบาง:
coeffs | สัมประสิทธิ์เชิงซ้อนหนึ่งค่าต่อหนึ่งพจน์ |
actions | บูลีนหนึ่งค่าต่อหนึ่งปัจจัย: True สำหรับตัวดำเนินการสร้าง และ False สำหรับตัวดำเนินการทำลาย |
modes | ดัชนีโหมดหนึ่งค่าต่อหนึ่งปัจจัย |
boundaries | ตำแหน่งใน actions และ modes ที่แต่ละพจน์เริ่มและสิ้นสุด |
คอนสตรักเตอร์รับอาร์เรย์ทั้งสี่นี้โดยตรง ตัวดำเนินการด้านล่างมีสามพจน์ คือ , และค่าคงที่ และคุณอ่านอาร์เรย์กลับได้ด้วยเมธอด 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() ซึ่งรับดิกชันนารีจากทูเพิลของปัจจัยไปเป็นสัมประสิทธิ์ ฟังก์ชัน 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 แทน และ -0 แทน คอนสตรักเตอร์ zero() และ one() คืนเอกลักษณ์การบวกและเอกลักษณ์การคูณ และ from_terms() สร้างตัวดำเนินการขึ้นใหม่จากอิเทอเรเตอร์ของ iter_terms()
ไลบรารีตัวดำเนินการมีควอดราเจอร์ตำแหน่งและโมเมนตัมแบบไร้มิติ
เป็น position() และ momentum() เมื่อใช้ร่วมกับฟังก์ชันคอมมิวเตเตอร์ของโมดูลเดียวกัน คุณตรวจสอบพีชคณิตได้โดยตรง:
>>> 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 คือ ตัวดำเนินการ & คือ การประกอบ ซึ่งตามแบบแผนของ Qiskit ที่อ่านจากซ้ายไปขวา: a & b ใช้ a ก่อนแล้วจึงใช้ b ดังนั้นคือ ฟังก์ชันคอมมิวเตเตอร์ใช้ @ ดังนั้น commutator(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() คือความยาวของพจน์ที่ยาวที่สุด
โหมด คือดัชนีนามธรรมที่ระบุองศาอิสระของโบซอนหนึ่งตัว มันไม่มีความหมายในตัวเอง: คุณเป็นผู้กำหนดว่าโหมดหนึ่งหมายถึงไซต์ใดของแลตทิซ พิกัดการสั่นใด หรือพอร์ตแสงใด BosonicCircuit ใช้ดัชนีชุดเดียวกัน (ดู ทำงานกับ Circuit โบซอน) ตัวดำเนินการและเกตที่นำมันไปจึงตรงกันว่าโหมด 2 คืออะไร โดยไม่ต้องรู้ว่ามันแทนสิ่งใด ไม่มีสิ่งใดในตัวดำเนินการที่กำหนดว่าโหมดมีกี่ระดับ ซึ่งเป็นเหตุผลที่ขนาดรีจิสเตอร์เป็นเพียงขอบล่าง min_num_modes() คือรีจิสเตอร์ที่เล็กที่สุดที่รองรับตัวดำเนินการ ไม่ใช่คุณสมบัติของมัน
เมธอดการครอบครองรายงานว่าหนึ่งพจน์วางโบซอนในหนึ่งโหมดได้กี่ตัว นี่เป็นขอบล่างของค่าตัดที่การแมปต้องใช้ แต่ไม่เพียงพอ เพราะการใช้ตัวดำเนินการซ้ำ ๆ เช่นในการวิวัฒน์ตามเวลา อาจเติมระดับที่สูงขึ้น (ดู ตัดทอนปริภูมิ Fock):
>>> 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)
ผลลัพธ์แสดงสี่ลำดับตามที่เขียน การลดให้เหลือรูปเดียวต้องใช้ความสัมพันธ์การสลับที่ ซึ่งหัวข้อถัดไปจะนำมาใช้
ตัวร่วมเสริมจะกลับลำดับของปัจจัยในแต่ละพจน์ สลับตัวดำเนินการสร้างกับทำลาย และสังยุคสัมประสิทธิ์ เมธอด 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
พจน์การกระโดดเดี่ยวไม่เป็นเฮอร์มีเชียน ซึ่งสำคัญเมื่อคุณจัดกลุ่มพจน์สำหรับการวิวัฒน์ตามเวลา: แต่ละกลุ่มต้องเป็นเฮอร์มีเชียนด้วยตัวเอง (ดู จัดกลุ่มพจน์ตัวดำเนินการ: ใช้โครงสร้างของตัวดำเนินการ)
จัดลำดับปกติและเปรียบเทียบตัวดำเนินการ
ผลคูณของตัวดำเนินการแลดเดอร์สองชุดอาจเป็นตัวดำเนินการเดียวกันแต่ดูต่างกัน เพราะ และ เท่ากันในพีชคณิต ตัวดำเนินการที่เท่ากันในความหมายนี้ไม่จำเป็นต้องถูกเก็บเหมือนกัน การเปรียบเทียบจึงต้องใช้ความสัมพันธ์การสลับที่ เมธอด 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() รวมพวกมัน การจัดลำดับปกติไม่เคยทำให้พจน์หายไป: และปัจจัยซ้ำอื่นทุกตัวยังอยู่
อาร์กิวเมนต์ 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() เปรียบเทียบตัวดำเนินการกับตัวร่วมเสริมในรูปจัดลำดับปกติ จึงรู้ว่า เป็นเฮอร์มีเชียนโดยไม่ต้องทำอะไรเพิ่ม
การจัดลำดับปกติเป็นเอกลักษณ์ที่แน่นอนในพีชคณิตที่ยังไม่ตัดทอน และ equiv พึ่งพาสิ่งนั้น แต่ ไม่ใช่ เอกลักษณ์หลังการตัดทอน ตัวดำเนินการที่แมปแล้วขึ้นกับรูปที่คุณแมป ดังนั้นภาพที่แมปของ contracted และ reordered ข้างต้นจึงต่างกัน เฉพาะที่ระดับสูงสุดที่คงไว้ ดู ตัดทอนปริภูมิ Fock สำหรับขนาดของความต่างและวิธีเลือกรูป
จัดลำดับและกรองพจน์
การจัดลำดับ พจน์ ของตัวดำเนินการใหม่ ต่างจากปัจจัยภายในพจน์ ไม่เปลี่ยนตัวดำเนินการ แต่อาจเปลี่ยนพฤติกรรมของอัลกอริทึมที่อ่านพจน์ตามลำดับ เช่นสูตรผลคูณแบบสุ่ม ฟังก์ชันของ 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]
ตัวช่วยสุดท้ายลบพจน์แทนที่จะจัดลำดับใหม่ พจน์ที่เป็นผลคูณของตัวดำเนินการจำนวน เพียงคูณแต่ละสถานะ Fock ด้วยเฟส ดังนั้น 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
เหลือเพียงสองพจน์การกระโดด อันตรกิริยาบนไซต์และตัวดำเนินการจำนวนเป็นแนวทแยงในฐานการครอบครอง จึงถูกลบพร้อมกับค่าคงที่ โหมดที่ซ้ำในอันตรกิริยาถูกจัดการอย่างถูกต้อง: เป็นแนวทแยง ขณะที่ ไม่ใช่
ขั้นตอนถัดไป
-
จัดกลุ่มพจน์ตัวดำเนินการ: ใช้โครงสร้างของตัวดำเนินการ แสดงวิธีติดแท็กพจน์ด้วยดัชนีกลุ่ม
-
แมปการแทนตัวดำเนินการ เปลี่ยนตัวดำเนินการเป็นตัวดำเนินการคิวบิต รวมถึงการเลือกการเข้ารหัสและผลของการตัดทอน
-
ทำงานกับ Circuit โบซอน วางตัวดำเนินการลงใน Circuit เป็นเกต
Evolution -
จำลองพลวัต Bose-Hubbard โดยเลือกการเข้ารหัสได้ สร้างแฮมิลโทเนียนของสาย Bose-Hubbard และจำลองตั้งแต่ต้นจนจบ
-
โมดูล
qiskit_bosons.operatorsและqiskit_bosons.operators.termsบันทึกอินเทอร์เฟซทั้งหมดไว้