สร้างวงจร SqDRIFT
ใน sample-based quantum diagonalization (SQD) คุณต้องเลือก ansatz เพื่อใช้สุ่มบิตสตริง ส่วนตัวแปร SqDRIFT ใช้กลุ่มของวงจรวิวัฒนาการตามเวลาที่สร้างจาก Hamiltonian เป้าหมายโดยตรงแทน ทำได้โดยสุ่มย่อยตัวดำเนินการวิวัฒนาการตามเวลาที่เล็กกว่าจาก Hamiltonian ตามสัมประสิทธิ์ของมัน ซึ่งเรียกว่าวิธีทรอตเตอร์ไรเซชัน qDRIFT
คู่มือเริ่มต้นนี้แสดงวิธีสร้างกลุ่มของวงจรสุ่มดังกล่าว
1. การตั้งค่า Hamiltonian
สำหรับคู่มือนี้ ให้โหลด Hamiltonian โครงสร้างอิเล็กทรอนิกส์ของ N2 จากไฟล์ FCIDUMP ยังมีวิธีอื่นในการสร้าง FermionOperator อย่าลืมดูเอกสารประกอบของมัน รวมถึง qiskit_fermions.operators.library
- PYTHON
- C
>>> from qiskit_fermions.operators.library import FCIDump
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> fcidump = FCIDump.from_file("docs/guides/n2.fcidump")
>>> num_modes = 2 * fcidump.norb
>>> hamil = FermionOperator.from_fcidump(fcidump)
#include <qiskit_fermions.h>
QfFCIDump* fcidump = NULL;
qf_fcidump_from_file("docs/guides/n2.fcidump", &fcidump);
QfFermionOperator* hamil = qf_ferm_op_from_fcidump(fcidump);
uint32_t num_modes = 2 * qf_fcidump_norb(fcidump);
2. จัดกลุ่มเทอมของ Hamiltonian
ใช้ประโยชน์จากสมมาตรจำนวนมากใน Hamiltonian โครงสร้างอิเล็กทรอนิกส์ โดยจัดกลุ่มเทอมที่เกี่ยวข้องกันซึ่งมีสัมประสิทธิ์เหมือนกัน การกระทำนี้เปลี่ยนการแจกแจงสัมประสิทธิ์ของตัวดำเนินการที่โปรโตคอล qDRIFT สุ่มจากมัน แต่ไม่กระทบต่อการรับประกันการลู่เข้า ที่สำคัญคือ การจัดกลุ่มเทอมที่เกี่ยวข้องกันด้วยสมมาตรทำให้เทอม Pauli หักล้างกันอย่างเป็นประโยชน์ ส่งผลให้ความลึกของวงจรโดยรวมสั้นลงเมื่อวิวัฒนาการสถานะตามเวลาภายใต้การกระทำของมัน
โมดูล qiskit_fermions.operators.terms.grouping มีฟังก์ชันอำนวยความสะดวกสำหรับจัดกลุ่มเทอมของตัวดำเนินการ ซึ่งอธิบายละเอียดขึ้นในคู่มือนี้
การทำงานของ group_terms_by_electronic_structure() สมมติว่าเทอมของ Hamiltonian เป็นแบบ normal-ordered!
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_terms_by_electronic_structure
>>> from qiskit_fermions.operators.terms.ordering import canonical_order
>>>
>>> canon = canonical_order(hamil.normal_ordered().simplify(atol=1e-16))
>>> exit_code = group_terms_by_electronic_structure(canon, num_modes, two_body_physicist_order=False)
>>> assert exit_code is None
>>> print(canon.groups) # the groups attribute now contains some list of group indices
[0, ...]
QfFermionOperator* normal = qf_ferm_op_normal_ordered(hamil, NULL);
QfFermionOperator* simplified = qf_ferm_op_simplify(normal, 1e-16);
QfFermionOperator* canon = qf_ferm_op_canonical_order(simplified);
QfExitCode exit = qf_ferm_op_group_terms_by_electronic_structure(canon, num_modes, false);
assert(exit == QfExitCode_Success);
qf_ferm_op_free(normal);
qf_ferm_op_free(simplified);
Hamiltonian โครงสร้างอิเล็กทรอนิกส์ฉบับเต็มมีบางเทอมที่การใส่ไว้ในวงจรวิวัฒนาการตามเวลาไม่มีผลต่อบิตสตริงที่สังเกตได้ จึงมีแต่เพิ่มภาระการสุ่ม ดังนั้นแนะนำให้กรองเทอมเช่นนั้นออกจาก Hamiltonian ตั้งแต่ตอนนี้ ก่อนสร้าง Gate Evolution ในขั้นตอนถัดไป
เทอมที่เข้าข่ายนี้คือเทอมที่เป็นแนวทแยงในฐานจำนวนครอบครอง คือผลคูณของตัวดำเนินการจำนวน () ซึ่งรวมถึงค่าออฟเซ็ตพลังงานคงที่ ที่วิวัฒนาการตามเวลาของมันเพียงเพิ่มเฟสรวมให้วงจร ตัวดำเนินการจำนวนเดี่ยวที่วิวัฒนาการตามเวลาเท่ากับการหมุน Z ของคิวบิตเดี่ยว รวมถึงผลคูณอันดับสูงกว่า เช่น ไม่มีเทอมใดเปลี่ยนการครอบครองของโหมดได้ การตัดทิ้งจึงไม่เสียเนื้อหาการกระตุ้นใด ต่างจากการปฏิเสธต่อการสุ่มแต่ละครั้งในขั้นตอนที่ 5 การกรองนี้เกิดขึ้นครั้งเดียวที่ตัว Hamiltonian เอง ดังนั้นการแจกแจงสัมประสิทธิ์ที่โปรโตคอลสุ่มจึงถูกสร้างใหม่อย่างสอดคล้องจากตัวดำเนินการที่กรองแล้ว แทนที่จะปรับค่ากลางการสุ่ม
ฟังก์ชัน filter_diagonal_terms() ลบเทอมเช่นนั้นออกจากตัวดำเนินการในที่เดิม:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
>>>
>>> filter_diagonal_terms(canon)
qf_ferm_op_filter_diagonal_terms(canon);
การกรองที่นี่ครั้งเดียวถูกกว่าการกรองซ้ำ ๆ มาก FermionicQDrift ทำงานหนึ่งครั้งต่อวงจรที่ทรานสไพล์ การกรอง Hamiltonian ตั้งแต่แรก แทนที่จะกรองทุกครั้งที่เรียก จึงหลีกเลี่ยงการทำงานซ้ำสำหรับทุกวงจรที่สร้างจากมัน
ด้วยเหตุผลเดียวกัน การเรียงลำดับเทอมของ Hamiltonian ตามดัชนีกลุ่มที่นี่จึงคุ้มค่า ดัชนีกลุ่มเป็นแท็กต่อเทอมที่ไม่ได้บอกว่าเทอมเหล่านั้นอยู่ตรงไหน เทอมของกลุ่มหนึ่งจึงมักกระจัดกระจายทั่วตัวดำเนินการ การจัดกลุ่มที่ใช้ข้างต้นกำหนดดัชนีตามลำดับที่พบครั้งแรก ทำให้เทอมที่สองของกลุ่มอยู่ไกลจากเทอมแรก
FermionicQDrift สุ่มทั้งกลุ่มและค้นหาแต่ละกลุ่มที่สุ่มได้ด้วย split_out_groups() การหากลุ่มท่ามกลางเทอมที่กระจัดกระจายหมายถึงต้องสแกนทั้งหมด การค้นหานั้นจึงมีต้นทุนเท่ากันไม่ว่าจะสุ่มหนึ่งกลุ่มหรือสี่สิบกลุ่ม group_order() รวบรวมแต่ละกลุ่มให้อยู่ในช่วงต่อเนื่องเดียว หลังจากนั้นการค้นหาคือการค้นหาแบบไบนารีบนขอบเขตกลุ่ม และมีต้นทุนเท่ากับจำนวนกลุ่มที่สุ่มได้ ไม่ใช่จำนวนเทอมที่ถือไว้ การค้นหาถูกลงอย่างมาก แต่จะประหยัดการทรานสไพล์ได้เท่าใดขึ้นกับสิ่งอื่นที่ไปป์ไลน์ทำ เพราะเมื่อไม่ต้องสแกนแล้ว การแมปและการสังเคราะห์จะกินเวลาเป็นส่วนใหญ่:
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.ordering import group_order
>>>
>>> canon = group_order(canon)
>>> print(canon.groups[:4])
[0, 0, ...]
QfFermionOperator* grouped = qf_ferm_op_group_order(canon);
การทำเช่นนี้จัดเรียงเทอมใหม่โดยไม่เปลี่ยนค่าของตัวดำเนินการ และต้องทำเพียงครั้งเดียวไม่ว่าจะสุ่มวงจรกี่แบบจากมัน
3. เตรียมวงจรวิวัฒนาการตามเวลา
เตรียมวงจรวิวัฒนาการตามเวลาของ Hamiltonian และวงจรฐานที่ใช้สุ่มตัวอย่าง qiskit_fermions.circuit.library มีส่วนประกอบที่จำเป็นทั้งหมดสำหรับสิ่งนี้ โดยเป็นไปตามข้อกำหนดของ Qiskit
โปรโตคอล qDRIFT คือวิธีสังเคราะห์ FermionicQDrift และ Gate Evolution ถือมันไว้เป็น synthesis ของมัน วิธีนี้มีพารามิเตอร์ไม่กี่ตัวที่คุณใช้ปรับแต่งวงจรที่จะสร้างได้:
-
ความยาวของแต่ละวงจรในหน่วยกลุ่มการกระตุ้น:
num_groups -
ไม่บังคับ: seed สำหรับตัวสร้างเลขสุ่ม:
rng -
ไม่บังคับ: น้ำหนักการสุ่มที่คำนวณไว้ล่วงหน้า หากการทรานสไพล์เองกลายเป็นคอขวด:
weights(ดู (ไม่บังคับ) คำนวณน้ำหนักการสุ่มล่วงหน้า ด้านล่าง)
เนื่องจากวิธีนี้ถูกถือไว้โดย Gate สิ่งใดที่อธิบายตัวดำเนินการของ Gate เช่นน้ำหนักเหล่านั้น จึงอยู่ติดกับตัวดำเนินการนั้น
- PYTHON
- C
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import FermionicQDrift
>>>
>>> time = 1.0 # you can choose a desired scaling factor here
>>> num_groups = 10
>>> qdrift = FermionicQDrift(num_groups, rng=19)
>>> evo_gate = Evolution(num_modes, canon, time, synthesis=qdrift)
>>>
>>> circ = FermionicCircuit(num_modes)
>>> circ.append(evo_gate, circ.modes)
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions.
ตัวอย่างนี้ไม่ได้กำหนดค่าเริ่มต้นให้โหมดเฟอร์มิออนด้วยอนุภาค และไม่ได้วัดสถานะสุดท้ายของมัน
4. ทรานสไพล์วงจร
โมดูล qiskit_fermions.transpiler ผสานรวมโดยตรงกับไปป์ไลน์การทรานสไพล์ของ Qiskit ทำให้ FermionicCircuit ที่สร้างข้างต้นถูกทรานสไพล์เป็น QuantumCircuit ได้โดยตรง
ใช้การแมปเฟอร์มิออนเป็นคิวบิต jordan_wigner() เพื่อแปลง Hamiltonian ที่แสดงในรูปเฟอร์มิออนให้เป็นสตริง Pauli แทน ทำได้โดยตรงเป็นส่วนหนึ่งของกระบวนการทรานสไพล์ด้วยการใช้พาสทรานสไพล์ F2QSynthesis ใช้ generate_preset_jw_pass_manager() เพื่อสร้าง FermionicStagedPassManager ซึ่งรับประกันว่าการเข้ารหัส Jordan-Wigner ถูกใช้อย่างสอดคล้องกันกับทุกคำสั่งของวงจร
ที่สำคัญ ให้เพิ่มพาส Decompose ของ Qiskit เข้าไปในสเตจ optimization ของไปป์ไลน์การทรานสไพล์ พาสนี้แยก Gate Evolution ออกด้วยวิธีสังเคราะห์ที่ Gate ถือไว้ ซึ่งคือวิธี qDRIFT ที่เตรียมไว้ข้างต้น เพื่อให้แน่ใจว่าวงจรไม่ได้ใช้วิวัฒนาการตามเวลาของ Hamiltonian ทั้งตัว ซึ่งความลึกจะเกินความสามารถของฮาร์ดแวร์ควอนตัมที่มีอยู่ในปัจจุบัน
แต่จะสุ่มย่อยจำนวนคงที่ของ groups ของเทอม Hamiltonian สำหรับแต่ละวงจร ทุกครั้งที่ทรานสไพล์วงจร ด้วยวิธีนี้ คุณสามารถสร้างวงจรสุ่มหลายแบบตามที่อัลกอริทึม SqDRIFT ต้องการ โดยรันไปป์ไลน์การทรานสไพล์ซ้ำ ๆ จำนวนวงจรที่จะสร้างคือ num_sqdrift_randomizations
- PYTHON
- C
>>> from qiskit.transpiler.passes import Decompose
>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> pm.optimization = FermionicPassManager([Decompose(["Evolution"])])
>>>
>>> num_sqdrift_randomizations = 10
>>> sqdrift_circuits = [
... pm.run(circ) for _ in range(num_sqdrift_randomizations)
... ]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
ตัวอย่างก่อนหน้ากำหนด seed rng ของวิธีสังเคราะห์ FermionicQDrift เพื่อให้ทำซ้ำลำดับของการสุ่มได้
ไปป์ไลน์เดียวกันนี้รับวิธี qDRIFT ได้อีกสองที่ พาส F2FSynthesis รับหนึ่งวิธีสำหรับ Gate Evolution ทุกตัว ตามชื่อ F2FSynthesis({"Evolution": FermionicQDrift(num_groups)}) ซึ่งเพียงพอเมื่อไม่มี Gate ใดถือสิ่งเฉพาะเจาะจงกับตัวดำเนินการของมัน ขั้นตอนถัดไปใช้ที่สาม คือพาส QDriftTrotterization
5. กรองการกระตุ้นที่สุ่มได้ตามการครอบครอง
นอกจากเทอมแนวทแยงที่กรองออกในขั้นตอนก่อนหน้าแล้ว การกระตุ้นที่สุ่มได้อาจกระทำภายในชุดโหมดที่การครอบครองถูกกำหนดแล้ว (ถูกครอบครองทั้งหมดหรือว่างทั้งหมด) ซึ่งไม่สามารถย้ายอนุภาคจากฝั่งหนึ่งไปอีกฝั่งได้ การกระตุ้นเช่นนั้นไม่บอกอะไรเกี่ยวกับบิตสตริงที่สุ่มได้ ช่องที่ใช้ไปกับมันจึงสูญเปล่า พาส QDriftTrotterization ซึ่งห่อหุ้มวิธี FermionicQDrift เดียวกัน จะปฏิเสธการกระตุ้นเช่นนั้นเมื่อสุ่มได้และสุ่มตัวแทนใหม่ เพื่อให้แต่ละช่องจากทั้งหมด num_groups ช่องของวงจรที่ได้มีการกระตุ้นที่เชื่อมสองฝั่งเข้าด้วยกัน
การกรองนี้ไม่ฟรี และด้วยเหตุนี้จึงเป็นพาสแยกต่างหาก สิ่งที่มันรักษาไว้คืองบประมาณช่องสุ่ม num_groups ช่อง ไม่ใช่ความลึกของวงจร เพราะงบประมาณนั้นคงที่ การกระตุ้นที่ถูกปฏิเสธจึงถูกแทนที่ ไม่ได้ถูกทิ้ง และการกระตุ้นที่ถูกจะหลีกทางให้การกระตุ้นที่เชื่อมสองฝั่งซึ่งสังเคราะห์แพงกว่า คาดว่าวงจรที่กรองแล้วจะลึกกว่าวงจรที่ไม่กรองซึ่งสุ่มจาก seed เดียวกัน
การทำ renormalize การสุ่มเหนือการกระตุ้นที่ยอมรับเปลี่ยนการแจกแจงที่โปรโตคอล qDRIFT สุ่มจากมัน และต่างจากการจัดกลุ่มในขั้นตอนที่ 2 มันยังทำลายการรับประกันการลู่เข้าของโปรโตคอลด้วย คือผลคูณที่สุ่มได้ไม่เฉลี่ยเป็นวิวัฒนาการภายใต้ Hamiltonian ที่คุณส่งเข้ามาอีกต่อไป การกระตุ้นที่คงไว้ทุกตัวถูกถ่วงน้ำหนักด้วยส่วนกลับของความน่าจะเป็นในการยอมรับ ตัวที่ถูกปฏิเสธไม่มีส่วนร่วม และทั้งขนาดสัมประสิทธิ์รวมและเวลาวิวัฒนาการต่อ Gate ไม่ได้ถูกปรับชดเชย
กฎการยอมรับยังเป็นการประมาณแบบเกินจริงด้วย มันเปรียบเทียบเฉพาะส่วนค้ำจุนของโหมด จึงไม่มีข้อมูลแอมพลิจูด และชุดที่ติดตามมีแต่จะโตขึ้น การกระตุ้นที่จำกัดอยู่ในชุดที่ถูกครอบครองนั้นไม่มีผลจริงบนสถานะผลคูณ แต่เหตุผลเดียวกันนี้อ่อนลงสำหรับการสุ่มครั้งหลัง ๆ เมื่อการกระตุ้นที่ยอมรับก่อนหน้าทำให้สถานะเอนแทงเกิลและทำเครื่องหมายโหมดส่วนใหญ่ว่า “ไม่แน่นอน”
ใช้การกรองสำหรับเวิร์กโฟลว์การสุ่มบิตสตริงที่คู่มือนี้อธิบาย ซึ่งวงจรป้อนให้การประมวลผลหลัง SQD และมีเพียงบิตสตริงที่สุ่มได้ที่สำคัญ อย่าใช้หากคุณตั้งใจประมาณค่าคาดหมายจากวงจรเหล่านี้ หรือพึ่งพาขอบเขตความคลาดเคลื่อนของ qDRIFT ในทางอื่นใด เพราะผลลัพธ์เหล่านั้นมีความเอนเอียงที่พาสไม่ได้แก้ไข
การกรองนี้ต้องรู้ว่าโหมดใดถูกครอบครองตั้งแต่เริ่ม จึงต้องมี Gate InitializeModes ก่อน Gate Evolution ในวงจร InitializeModes.from_hartree_fock() เป็นวิธีสร้างที่สะดวก เพิ่มหนึ่งตัวที่นี่สำหรับสถานะอ้างอิง Hartree-Fock ของ N2 (อิเล็กตรอนแอลฟาเจ็ดตัวและบีตาเจ็ดตัวใน 14 ออร์บิทัลเชิงพื้นที่) และเปรียบเทียบการกระตุ้นที่สุ่มได้ทั้งแบบมีและไม่มีการกรอง:
- PYTHON
- C
>>> from qiskit_fermions.circuit.library import InitializeModes
>>> from qiskit_fermions.transpiler.passes import F2FSynthesis, QDriftTrotterization
>>>
>>> init = InitializeModes.from_hartree_fock(fcidump.norb, (7, 7))
>>>
>>> hf_circ = FermionicCircuit(num_modes)
>>> hf_circ.append(init, hf_circ.modes)
>>> hf_circ.append(Evolution(num_modes, canon, time), hf_circ.modes)
>>>
>>> num_groups = 5
>>> qdrift_unfiltered = F2FSynthesis(
... {"Evolution": FermionicQDrift(num_groups, rng=3480)}
... )
>>> qdrift_trivial = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>>
>>> for instruction in FermionicPassManager(qdrift_unfiltered).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[2, 4]
[41, 45, 52]
[15, 45, 55]
[41, 52, 53]
[10, 16, 37, 38]
>>>
>>> for instruction in FermionicPassManager(qdrift_trivial).run(hf_circ)._inner.data:
... if instruction.operation.name == "Evolution":
... print(sorted(instruction.operation.operator.get_support()))
[0, 1, 6, 7]
[0, 1, 28, 29]
[4, 13, 55]
[13, 20, 40, 41]
[0, 1, 28, 29]
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
การกระตุ้นที่สุ่มได้โดยไม่กรองไม่มีตัวใดแตะชุดที่ถูกครอบครอง (0-6 และ 28-34) เลย จึงไม่มีตัวใดย้ายอนุภาคระหว่างโหมดที่ถูกครอบครองกับโหมดว่างได้ เมื่อกรอง ทั้งห้าตัวถูกปฏิเสธและแทนที่ด้วยการกระตุ้นที่เชื่อมโหมดที่ถูกครอบครองกับโหมดว่างจริง ตัวอย่างเช่น การกระตุ้นแรกที่ยอมรับ [0, 1, 6, 7] ย้ายอนุภาคระหว่างโหมดที่ถูกครอบครอง 0, 1 และ 6 กับโหมดว่าง 7
เมื่อการกระตุ้นถูกยอมรับ ทุกโหมดในส่วนค้ำจุนของมันจะกลายเป็น “ไม่แน่นอน” และมีสิทธิ์เล่นบทบาทใดก็ได้สำหรับตัวอย่างถัดไป ชุดโหมดที่ถูกครอบครองและว่างจึงโตขึ้นเรื่อย ๆ เมื่อยอมรับการกระตุ้นมากขึ้น นี่คือสิ่งที่ทำให้การกระตุ้นที่สอง [0, 1, 28, 29] ยอมรับได้ ทั้งสี่โหมดของมันอยู่ในกลุ่มที่ถูกครอบครองตั้งแต่แรก มันจึงไม่เชื่อมกับโหมดที่ว่างตั้งแต่แรกเลย มันถูกยอมรับเพียงเพราะโหมด 0 และ 1 กลายเป็นไม่แน่นอน (จึงมีสิทธิ์เป็นฝั่ง “ว่าง” ของการเชื่อม) หลังการกระตุ้นแรกแตะมัน
การเติบโตนี้ทำให้กฎเป็นการประมาณแบบเกินจริงมากกว่าจะเป็นการทดสอบ เมื่อโหมดไม่แน่นอนมากขึ้น การสุ่มจะถูกปฏิเสธน้อยลง และการกรองส่งผลต่อการแจกแจงการสุ่มมากที่สุดในการสุ่มช่วงแรก ๆ
เพื่อประเมินว่าการกรองเปลี่ยนการแจกแจงไปมากเพียงใด ให้อ่านจำนวนการปฏิเสธที่พาสบันทึกไว้ในเมทาดาทาของวงจร มีหนึ่งรายการต่อ Gate Evolution ที่ถูกกรอง และอัตราส่วนยอมรับต่อทั้งหมดประมาณความน่าจะเป็นในการยอมรับ ซึ่งคือตัวประกอบที่การกระตุ้นที่คงไว้ถูกถ่วงน้ำหนักเกิน:
- PYTHON
- C
>>> # a freshly seeded pass, so the counts do not depend on earlier draws
>>> qdrift_counted = QDriftTrotterization(
... FermionicQDrift(num_groups, rng=3480)
... )
>>> filtered = FermionicPassManager(qdrift_counted).run(hf_circ)
>>> discarded = filtered.metadata.get("filter_trivial.discarded")
>>> emitted = filtered.metadata.get("filter_trivial.emitted")
>>> print(f"{emitted[0]} kept, {discarded[0]} discarded")
5 kept, 7 discarded
>>> print(f"acceptance probability: {emitted[0] / (emitted[0] + discarded[0]):.2f}")
acceptance probability: 0.42
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
การกรองต้องมีข้อมูลการครอบครองไว้ใช้กรอง Gate PrepareSlaterDeterminant ให้ข้อมูลนี้ได้เช่นเดียวกับ InitializeModes และ OrbitalRotation ใด ๆ จะทำเครื่องหมายทุกโหมดที่มันกระทำว่า “ไม่แน่นอน” หากไม่มีสิ่งเหล่านี้เลย หรือเมื่อโหมดที่กำหนดค่าเริ่มต้นไปอยู่ฝั่งเดียวทั้งหมด (ถูกครอบครองทั้งหมดหรือว่างทั้งหมด) พาสจะส่ง UserWarning และปล่อยการสุ่มไม่กรองสำหรับ Gate Evolution นั้น กับ Hamiltonian ที่เทอมที่เหลือไม่สามารถเชื่อมสองชุดที่ติดตามได้อีก พาสจะยกข้อผิดพลาด RuntimeError แทน หลังถูกปฏิเสธติดต่อกัน QDriftTrotterization.MAX_SAMPLE_RETRIES ครั้ง
(ไม่บังคับ) คำนวณน้ำหนักการสุ่มล่วงหน้า
ใช้เฉพาะเมื่อการทรานสไพล์เองเป็นคอขวด มันไม่เปลี่ยนอะไรเกี่ยวกับวงจรที่คุณได้
FermionicQDrift สุ่มแต่ละกลุ่มด้วยความน่าจะเป็นที่เป็นสัดส่วนกับขนาดของสัมประสิทธิ์ และหาขนาดเหล่านั้นจาก Hamiltonian ทุกครั้งที่เรียก คือหนึ่งค่าต่อเทอมของ Hamiltonian แล้วลดเหลือหนึ่งค่าต่อกลุ่ม เนื่องจากวิธีนี้ไม่มีสถานะ มันจึงทำการลดซ้ำสำหรับทุกการสุ่มแบบ แม้ผลลัพธ์จะเหมือนเดิมทุกครั้ง อาร์กิวเมนต์ weights ให้คุณคำนวณครั้งเดียวด้วย group_coeff_means() แล้วส่งผลลัพธ์เข้าไป ย้ายต้นทุนออกจากลูป โดยที่วิธีไม่ต้องเก็บสถานะที่ขึ้นกับ Hamiltonian ไว้ในแคช
นี่คู่กับการเรียงลำดับเทอมในขั้นตอนที่ 2 อันนั้นยกการค้นหากลุ่มออกจากลูป อันนี้ยกการลดน้ำหนัก และแต่ละอย่างจะเห็นผลเฉพาะเมื่ออีกอย่างไม่มี จึงควรใช้ทั้งสองอย่างด้วยกัน เนื่องจาก canon ถูกเรียงใหม่ไปแล้วข้างต้น ทั้งสองจึงใช้ร่วมกันได้โดยตรง
น้ำหนักอธิบายกลุ่มของตัวดำเนินการหนึ่ง ๆ จึงอยู่กับ Gate Evolution ของตัวดำเนินการนั้น: สร้างวิธีสำหรับ Gate นั้นและส่งให้เป็น synthesis ของ Gate เหมือนในขั้นตอนที่ 3 ทุกประการ
- PYTHON
- C
>>> from qiskit_fermions.operators.terms.grouping import group_coeff_means
>>>
>>> weights = group_coeff_means(canon) # `canon` is group-ordered, from step 2
>>> qdrift_weighted = FermionicQDrift(10, rng=19, weights=weights)
>>> weighted_gate = Evolution(num_modes, canon, time, synthesis=qdrift_weighted)
>>>
>>> weighted_circ = FermionicCircuit(num_modes)
>>> weighted_circ.append(weighted_gate, weighted_circ.modes)
>>>
>>> weighted_circuits = [
... pm.run(weighted_circ) for _ in range(num_sqdrift_randomizations)
... ]
>>> len(weighted_circuits)
10
// WARNING: Qiskit's C API does not yet allow us to implement circuits
// with custom gate definitions, which we therefore also cannot transpile
// via this API.
เนื่องจากนี่คือน้ำหนักที่พาสจะคำนวณเองพอดี วงจรจึงเหมือนเดิม เพียงแต่การลดซ้ำหายไป
คาดว่าจะมีหนึ่งรายการต่อกลุ่ม ไม่ใช่ต่อเทอม เมื่อใดก็ตามที่ตัวดำเนินการมีกลุ่ม เพราะตัวดำเนินการที่จัดกลุ่มถูกสุ่มตามกลุ่ม รายการต้องไม่เป็นค่าลบ: น้ำหนักคือขนาด เพราะโปรโตคอล qDRIFT แยก โดยที่ เป็นบวก และเครื่องหมายของสัมประสิทธิ์อยู่ใน ซึ่งพาสอ่านจากตัวดำเนินการโดยตรง
น้ำหนักคือขนาดสัมบูรณ์ ไม่ใช่ความชอบสัมพัทธ์ ผลรวมของมันยังกำหนดเวลาวิวัฒนาการด้วย การคูณทุกรายการด้วย จึงสุ่มเหมือนเดิมแต่วิวัฒนาการเป็นเวลา แทน การส่งสิ่งอื่นนอกจากขนาดสัมประสิทธิ์ของ Hamiltonian เองจึงเปลี่ยนวิวัฒนาการที่กลุ่มวงจรประมาณ นี่คือเหตุผลที่มันเป็นเครื่องมือด้านประสิทธิภาพ ไม่ใช่วิธีปรับแต่งการสุ่ม
เนื่องจากน้ำหนักอยู่กับ Gate ที่มันอธิบายตัวดำเนินการ วงจรจึงมี Gate Evolution หลายตัวได้ แต่ละตัวมีน้ำหนักของตัวดำเนินการของตัวเอง พาส QDriftTrotterization ของขั้นตอนที่ 5 ให้ความสำคัญกับมันแบบเดียวกัน: มันสุ่ม Gate ด้วย FermionicQDrift ที่ Gate ถือไว้ และถอยไปใช้ตัวที่มันถูกสร้างมาสำหรับ Gate ที่ไม่ได้ถือตัวใดเลย วิธีสังเคราะห์ชนิดอื่นบน Gate ไม่ใช่การระบุเกี่ยวกับ qDRIFT พาสจึงเพิกเฉย การส่งวิธีให้พาสตามชื่อ Gate แทน เช่นใน F2FSynthesis({"Evolution": ...}) ใช้หนึ่งวิธีกับ Gate Evolution ทุกตัว น้ำหนักที่ส่งไปที่นั่นจึงมีความหมายเฉพาะกับวงจรที่มี Gate เดียว ความยาวของน้ำหนักถูกตรวจสอบกับแต่ละ Gate ที่นำไปใช้
(ไม่บังคับ) ปรับดัชนีโหมดเฟอร์มิออนให้เหมาะสม
คุณเพิ่มขั้นตอนการปรับให้เหมาะสมเพิ่มเติมในไปป์ไลน์การทรานสไพล์ ซึ่งลดระยะของช่วงการกระตุ้นเฟอร์มิออนให้น้อยที่สุดด้วยการติดป้ายดัชนีโหมดเฟอร์มิออนใหม่ได้ การปรับให้เหมาะสมนี้เสนอไว้ในบทความ SqDRIFT และถูกimplement โดย build_excitation_span_minimization_model() แทรกเข้าไปป์ไลน์ทรานสไพล์ได้ง่ายด้วยพาส RelabelModes:
- PYTHON
- C
>>> from pyomo.environ import SolverFactory
>>> from qiskit_fermions.transpiler.passes import RelabelModes
>>>
>>> solver = SolverFactory("appsi_highs")
>>> solver.options["time_limit"] = 10
>>>
>>> qdrift = F2FSynthesis({"Evolution": FermionicQDrift(5, rng=19)})
>>> relabel = RelabelModes(solver=solver)
>>>
>>> pm.optimization = FermionicPassManager([qdrift, relabel])
>>>
>>> relabeled_circ = pm.run(circ)
>>> # if the automatic mode relabeling was successful, the circuit's
>>> # metadata will contain the mode `permutation` information
// WARNING: This feature is not available via the C API.
การใช้การปรับให้เหมาะสมอัตโนมัติภายใน RelabelModes (ซึ่งใช้ build_excitation_span_minimization_model()) ต้องมี dependency เสริมที่จัดการโดย HAS_PYOMO
การติดป้ายใหม่เปลี่ยนว่าคิวบิตหนึ่งแทนโหมดใด การทำ diagonalize ในสเปซย่อยจึงได้บิตสตริงที่ถูกต้องก็ต่อเมื่อคำนึงถึงสิ่งนี้ ตำแหน่งที่ต้องคำนึงขึ้นกับว่าคุณวัดที่ใด
เพิ่มการวัดให้ FermionicCircuit ด้วย FermionicCircuit.measure_all() ก่อนทรานสไพล์ แล้วไม่ต้องทำอะไรเพิ่ม: แต่ละการวัดระบุโหมดที่มันอ่าน การติดป้ายใหม่จึงพามันไปด้วย และบิตสตริงที่สุ่มได้ถูกจัดดัชนีตามโหมดเดิม
หากวัด QuantumCircuit ที่ทรานสไพล์แล้วแทน บิตสตริงต้องถูกประมวลผลหลังด้วย permutation ที่บันทึกไว้ในเมทาดาทาของวงจร ดูสูตรได้ในเอกสารประกอบของ RelabelModes
ขั้นตอนถัดไป
เมื่อสร้างกลุ่มวงจรสำเร็จแล้ว คุณสามารถสุ่มบิตสตริงจากวงจรเหล่านั้นได้ โดยต้องรันวงจรบนฮาร์ดแวร์ ดูเอกสารประกอบ Qiskit สำหรับคำแนะนำโดยละเอียด
เมื่อได้ตัวอย่างบิตสตริงแล้ว สามารถนำไปใช้ร่วมกับสัมประสิทธิ์ของ Hamiltonian เพื่อประมวลผลหลัง SQD ตามที่อธิบายในบทช่วยสอน SQD addon