ข้ามไปยังเนื้อหาหลัก
โครงการนี้อยู่ในช่วงเบต้า พบข้อผิดพลาด? สร้าง GitHub Issue มีคำถามหรือไอเดีย? ถามใน Discussions — ยินดีรับฟังความคิดเห็น!
คุณภาพการแปลในหน้านี้เป็นอย่างไร?

ทำงานกับ fermionic circuit

สำคัญ

แนวคิดในคู่มือนี้ใช้ได้เฉพาะใน Python API ในตอนนี้ ฟังก์ชันการทำงานที่เทียบเท่ากันจะมีให้ใน C API ในรุ่นถัดไป

คู่มือนี้อธิบายวิธีใช้ FermionicCircuit เพื่อสร้างอัลกอริทึมควอนตัมในปริภูมิเฟอร์มิออนโดยตรง แทนที่จะแมปไปเป็น qubit ก่อน

ทำไมต้องใช้ fermionic circuit​

ลองดูเวิร์กโฟลว์แบบดั้งเดิมสำหรับการสร้างวิวัฒนาการตามเวลาของแฮมิลโทเนียนเฟอร์มิออนบนสถาปัตยกรรมที่ใช้ qubit:

  1. กำหนดแฮมิลโทเนียนเฟอร์มิออน (เช่น H=∑pqhpqcp†cqH = \sum_{pq} h_{pq} c^\dagger_p c_q)

  2. แมปไปเป็นตัวดำเนินการ qubit (เช่น ใช้การแมปแบบ Jordan-Wigner)

  3. สร้างวิวัฒนาการตามเวลาในปริภูมิ qubit (ใช้ Trotterization หรือวิธีอื่น ๆ)

แนวทางนี้มีข้อจำกัดพื้นฐาน การแมปไปเป็น qubit เร็วเกินไปทำให้ความรู้ที่รับรู้ปัญหาเกี่ยวกับโครงสร้างเฟอร์มิออนหายไป optimization pass มาตรฐานไม่สามารถใช้ประโยชน์จากสมมาตรของเฟอร์มิออนหรือการสลับลำดับเทอมที่ทำได้ ส่งผลให้ได้ circuit ที่ไม่เหมาะสมที่สุด มีความลึกและจำนวน gate เกินจำเป็น

FermionicCircuit ทำให้มีเวิร์กโฟลว์ที่ดีกว่า:

  1. กำหนดแฮมิลโทเนียนเฟอร์มิออน (เช่น H=∑pqhpqcp†cqH = \sum_{pq} h_{pq} c^\dagger_p c_q)

  2. สร้างวิวัฒนาการตามเวลาในปริภูมิเฟอร์มิออนโดยตรงด้วย fermionic gate

  3. แมปไปยังปริภูมิ qubit เป็นส่วนหนึ่งของกระบวนการ transpile

แนวทางนี้แก้ข้อจำกัดดังกล่าวด้วยการเก็บความรู้ที่รับรู้ปัญหาไว้ในปริภูมิเฟอร์มิออน ซึ่ง optimization pass สามารถใช้ประโยชน์จากโครงสร้างเฟอร์มิออนและความสัมพันธ์การสลับที่ (commutation) เพื่อลดความลึกของ circuit ได้ fermionic circuit อธิบายว่าต้องคำนวณอะไร ส่วนการ transpile จัดการว่าจะแมปไปเป็น qubit อย่างไร ทำให้แยกความรับผิดชอบได้ชัดเจนขึ้น

การทำดัชนีโหมดแบบทั่วไป​

ทั้งโมดูล operators และ FermionicCircuit ใช้การทำดัชนีตามโหมดแบบทั่วไปที่ไม่สมมติอะไรเกี่ยวกับธรรมชาติของโหมด โหมดคือดัชนีนามธรรมที่ใช้ระบุองศาอิสระของเฟอร์มิออน โดยไม่มีความหมายเฉพาะตัว การออกแบบนี้สอดคล้องกับโมดูล operators และทำให้ยืดหยุ่นสูงสุด ตัวอย่างเช่น คุณสร้างวิวัฒนาการตามเวลาสำหรับตัวดำเนินการใดก็ได้ที่ implement โปรโตคอล OperatorTrait โดยไม่ขึ้นกับรูปแบบการแทนทางคณิตศาสตร์ของมัน

สร้าง fermionic circuit​

ตัวอย่างด้านล่างสร้างวิวัฒนาการตามเวลาที่อธิบายในหัวข้อก่อนหน้า โดยสร้าง FermionicCircuit ด้วยการระบุจำนวนโหมดเฟอร์มิออน จากนั้นเพิ่ม fermionic gate จาก qiskit_fermions.circuit.library เพื่อสร้างวิวัฒนาการตามเวลา

ตัวอย่างนี้ยังแสดงวิธีใส่ความรู้เฉพาะด้านลงในตัวดำเนินการด้วย การจัดกลุ่มเทอมของตัวดำเนินการ การกำหนดดัชนีกลุ่มให้เทอมของแฮมิลโทเนียนช่วยรักษาข้อมูลโครงสร้างที่ optimization pass ใช้ประโยชน์ได้ตลอดทั้งสแตกของ transpile

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> # Create a circuit with 4 fermionic modes
>>> circuit = FermionicCircuit(4)
>>>
>>> # Define a simple fermionic Hamiltonian
>>> hamiltonian = FermionOperator.from_terms([
... ([cre(0), ann(2)], 0.5),
... ([cre(2), ann(0)], 0.5),
... ([cre(1), ann(3)], 0.5),
... ([cre(3), ann(1)], 0.5),
... ])
>>> # Add some problem structure by grouping our Hamiltonian terms
>>> hamiltonian.groups = [0, 0, 1, 1]
>>>
>>> # Add an evolution gate to implement exp(-i * t * H)
>>> evolution = Evolution(4, hamiltonian, time=1.0)
>>> circuit.append(evolution, circuit.register)

A simple `FermionicCircuit` with a single time evolution gate.

A decomposed `FermionicCircuit` with several time evolution gates.

สังเกตว่าการจัดกลุ่มเทอมของตัวดำเนินการยังคงอยู่แม้ในการดำเนินการง่าย ๆ อย่างการ decompose ซึ่งแสดงให้เห็นว่าข้อมูลโครงสร้างไหลผ่านสแตกของ circuit อย่างไร หากต้องการเข้าใจการ transpile ไปเป็น qubit ทั้งหมด ดูคู่มือ Transpiling fermionic circuits

แยก gate ออกจากกันด้วย barrier​

optimization pass จะรวม gate ที่อยู่ติดกันเมื่อทำได้ ตัวอย่างเช่น MergeOrbitalRotations รวม OrbitalRotation gate ที่เรียงต่อกันให้เหลือ gate เดียว และ MergeSlaterDeterminantPreparation พับ InitializeModes เข้าไปในการหมุนที่ตามมา

ใช้ FermionicCircuit.barrier() เมื่อต้องการป้องกันการรวมนั้น barrier ไม่มีผลเชิงยูนิทารี จึงไม่เปลี่ยนสถานะที่ circuit ของคุณเตรียมไว้ มันเพียงทำเครื่องหมายจุดที่ pass จะไม่รวมข้ามไป เรียกโดยไม่ใส่อาร์กิวเมนต์เพื่อครอบคลุมทุกโหมด หรือส่งโหมดเข้าไปเพื่อจำกัดเฉพาะโหมดเหล่านั้น:

>>> from qiskit_fermions.circuit.library import OrbitalRotation
>>> import numpy as np
>>>
>>> rotations = FermionicCircuit(4)
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)
>>> rotations.barrier()
>>> rotations.append(OrbitalRotation(np.eye(4, dtype=complex)), rotations.register)

A `FermionicCircuit` with a barrier separating two orbital rotations.

barrier ยังคงอยู่หลังการ transpile จากเฟอร์มิออนไปเป็น qubit ดังนั้นจึงจำกัดการปรับให้เหมาะสมในระดับ qubit ที่ตามมาด้วย คู่มือ Transpile fermionic circuits ครอบคลุมขั้นตอนนั้น

ทำซ้ำ circuit​

อัลกอริทึมจำนวนมากใช้บล็อกของ gate ชุดเดิมซ้ำหลายครั้ง วิวัฒนาการตามเวลาเป็นกรณีทั่วไป: สร้างขั้น Trotter หนึ่งครั้ง แล้วทำซ้ำเพื่อให้ถึงเวลาเป้าหมาย ใช้ repeat() เพื่อสร้าง circuit ที่ทำซ้ำ และ compose() เพื่อต่อเข้ากับส่วนนำหน้าที่เตรียมสถานะ

ตัวอย่างด้านล่างสร้างขั้น Trotter อันดับสองสำหรับแบบจำลอง Fermi-Hubbard ขั้นนี้สมมาตร: แยกเทอม hopping เป็น OrbitalRotation gate สองตัวที่มีระยะเวลาครึ่งหนึ่ง ล้อมรอบอันตรกิริยาบนไซต์

>>> import scipy.linalg
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>>
>>> norb, reps, time = 2, 3, 1.0
>>> num_modes = 2 * norb
>>> dt = time / reps
>>>
>>> hopping = np.zeros((norb, norb))
>>> hopping[0, 1] = hopping[1, 0] = -1.0
>>> interaction = FermionOperator.from_dict(
... {(cre(p), ann(p), cre(p + norb), ann(p + norb)): 2.0 for p in range(norb)}
... )
>>>
>>> def rotation(duration):
... propagator = scipy.linalg.expm(-1j * duration * hopping)
... # the same rotation acts on the alpha and beta halves of the register
... return scipy.linalg.block_diag(propagator, propagator)
>>>
>>> trotter_step = FermionicCircuit(num_modes)
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)
>>> trotter_step.append(
... Evolution(num_modes, interaction, dt, atomic=True), trotter_step.modes
... )
>>> trotter_step.append(OrbitalRotation(rotation(dt / 2)), trotter_step.modes)

การทำซ้ำขั้นนี้แล้วต่อเข้ากับสถานะอ้างอิง Hartree-Fock จะได้วิวัฒนาการทั้งหมด:

>>> evolution = FermionicCircuit(num_modes)
>>> evolution.append(InitializeModes([True, False, True, False]), evolution.modes)
>>> evolution.compose(trotter_step.repeat(reps), inplace=True)
>>> evolution.count_ops()["OrbitalRotation"]
6

A `FermionicCircuit` with repeated steps.

การทำซ้ำถูกแผ่ออกมาเป็น circuit ที่ส่งคืน แทนที่จะห่อเป็น gate เดียว ดังนั้น count_ops() จึงนับการหมุนสองครั้งต่อขั้น การแผ่ออกนี้ทำให้ transpiler มองเห็นความซ้ำซ้อนที่รอยต่อระหว่างขั้น: การหมุนครึ่งระยะเวลาท้ายของขั้นหนึ่งกับตัวต้นของขั้นถัดไปอยู่ติดกัน MergeOrbitalRotations จึงรวมเป็นการหมุนเดียว

>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import MergeOrbitalRotations
>>>
>>> merged = FermionicPassManager([MergeOrbitalRotations()]).run(evolution)
>>> merged.count_ops()["OrbitalRotation"]
4

A `FermionicCircuit` with repeated steps and merged orbital rotations.

ผลลัพธ์มีต้นทุนหนึ่งการหมุนต่อขั้นบวกอีกหนึ่ง ซึ่งเท่ากับที่ได้จากการรวมครึ่งขั้นเหล่านั้นด้วยมือ การเขียนขั้นในรูปสมมาตรตามธรรมชาติแล้วทำซ้ำให้ circuit เดียวกัน การรวมจึงปล่อยให้เป็นหน้าที่ของขั้นตอนการปรับให้เหมาะสม

เมื่อต้องการให้การทำซ้ำแต่ละรอบแยกจากกัน ให้ส่ง insert_barriers=True เพื่อวาง barrier() ไว้ระหว่างรอบ optimization pass จะไม่รวม gate ข้าม barrier ดังนั้นทุกการหมุนจึงยังอยู่:

>>> separated = trotter_step.repeat(reps, insert_barriers=True)
>>> FermionicPassManager([MergeOrbitalRotations()]).run(separated).count_ops()[
... "OrbitalRotation"
... ]
6

A `FermionicCircuit` with repeated steps separated by barriers.

หมายเหตุ

อาร์กิวเมนต์ reps ของ FermionicSuzukiTrotter เป็นคนละแนวคิดกัน มันแบ่ง Evolution gate หนึ่งตัวออกเป็นตัวประกอบที่สั้นลงและมากขึ้น ส่วน repeat() ทำซ้ำทั้ง circuit การทำซ้ำ circuit ที่มี Evolution ตัวเดียวเป็นเพียงการคูณเวลาวิวัฒนาการรวมของมันเท่านั้น

วัดโหมดเฟอร์มิออน​

measure_all() อ่านทุกโหมดออกมาเป็นบิตคลาสสิกของตัวเอง โดยจับคู่โหมด i กับบิต i ใช้ measure() ร่วมกับ ClassicalRegister ของคุณเองเพื่ออ่านเฉพาะบางโหมด

>>> measured = trotter_step.measure_all(inplace=False)
>>> measured.count_ops()["measure"]
4

การส่ง inplace=False จะคืนสำเนาที่วัดแล้วและไม่แตะต้องของเดิม ซึ่งสำคัญเพราะการวัดไม่ใช่ยูนิทารี: circuit ที่มีการวัดจะนำไปใช้กับเวกเตอร์สถานะไม่ได้อีก ดังนั้นให้เก็บ circuit ที่ยังไม่วัดไว้หากต้องการจำลองด้วย

การวัดที่นี่ แทนที่จะวัดบน QuantumCircuit ที่ transpile แล้ว ช่วยให้บิตสตริงที่สุ่มได้อ่านง่าย การวัดแบบเฟอร์มิออนระบุชื่อโหมดที่อ่าน ดังนั้นทุกขั้นตอนที่ย้ายโหมดไปมา (RelabelModes ในระดับเฟอร์มิออน, layout และ routing ในระดับ qubit) จะพาการวัดไปด้วย และบิตสตริงยังคงถูกทำดัชนีตามโหมดที่คุณระบุ

หมายเหตุ

ต่างจาก measure_all() ตรงที่ไม่มีการแทรก barrier() ก่อนการวัด ให้เรียก barrier() เองก่อนหากต้องการให้มี

บิตที่วัดได้จะมีความหมายตามที่คุณคาดหรือไม่ขึ้นอยู่กับการเข้ารหัสจากเฟอร์มิออนเป็น qubit ซึ่งเลือกระหว่างการ transpile ไม่ใช่ที่นี่ ภายใต้การเข้ารหัสแบบ occupation-basis เช่น Jordan-Wigner แต่ละโหมดถูกแทนด้วย qubit หนึ่งตัว และบิตคือการครอบครอง (occupation) ของโหมดนั้น ดู FermionicMeasure

Transpile fermionic circuit​

หากต้องการนำอัลกอริทึมควอนตัมที่ FermionicCircuit ของคุณแทนไปใช้งาน ต้อง transpile เป็น QuantumCircuit คุณเรียนรู้วิธีทำได้ในคู่มือ Transpile fermionic circuits

ขั้นตอนถัดไป​

  • เรียนรู้เกี่ยวกับ fermionic gate ที่มีให้ในเอกสาร qiskit_fermions.circuit.library

  • สำรวจ คู่มืออธิบาย operators เพื่อทำความเข้าใจวิธีสร้างแฮมิลโทเนียนเฟอร์มิออนที่ใช้กับ fermionic circuit ได้

  • ดูคู่มือเริ่มต้นใช้งาน SQDRIFT สำหรับตัวอย่างแบบครบวงจรที่ใช้งานได้จริง

  • อ่าน คู่มือความสัมพันธ์กับ ffsim เพื่อทำความเข้าใจว่า FermionicCircuit จำลองได้โดยตรงอย่างไร และดัชนีโหมดทั่วไปเหล่านี้มีความหมายเชิงสปินเฉพาะตอนจำลองอย่างไร