แมปการแทนค่าตัวดำเนินการ
Mapper คือรูทีนที่แปลงตัวดำเนินการจากการแทนค่าหนึ่งเป็นอีกแบบหนึ่งโดยรักษาความเท่าเทียมทางคณิตศาสตร์ ใช้เพื่อทำงานข้ามพีชคณิตตัวดำเนินการต่างๆ ได้อย่างยืดหยุ่น และเตรียมตัวดำเนินการสำหรับการรัน Circuit ควอนตัม
พัฒนา mapper แบบกำหนดเอง
โมดูล mappers ทำให้การพัฒนาการแมปแบบกำหนดเองตรงไปตรงมา Mapper ทำงานโดยแปลงการกระทำพื้นฐาน (การสร้าง การทำลาย มาจอรานา และอื่นๆ) ที่ประกอบเป็นตัวดำเนินการ แล้วรวมการกระทำที่แปลงแล้วเหล่านี้ตามกฎของการแทนค่าเป้าหมาย
ตัวอย่างที่เป็นรูปธรรม ลองดูการแปลง Jordan-Wigner ที่แมป MajoranaOperator ไปเป็นตัวดำเนินการ qubit โดยใช้ map_majorana_action_generators():
- PYTHON
- C
>>> from qiskit.quantum_info import SparseObservable
>>> from qiskit_fermions.mappers import map_majorana_action_generators
>>> from qiskit_fermions.operators import MajoranaOperator
>>>
>>> # Create a Majorana operator
>>> maj_op = MajoranaOperator.from_dict({(0, 1): 1.0, (2,): 0.5})
>>>
>>> # Define how each Majorana action maps to Pauli strings
>>> num_qubits = 2
>>> def map_action(mode: int) -> SparseObservable:
... idx = mode // 2
... qubits = list(range(idx + 1))
... pauli = "Y" if mode % 2 else "X"
... return SparseObservable.from_sparse_list(
... [("Z" * idx + pauli, qubits, 1.0)],
... num_qubits=num_qubits,
... )
>>>
>>> # Apply the mapping to transform the operator
>>> custom_observable = map_majorana_action_generators(
... maj_op,
... map_action,
... identity=lambda: SparseObservable.identity(num_qubits),
... compose=SparseObservable.compose,
... )
>>> print(custom_observable.simplify())
<SparseObservable with 2 terms on 2 qubits: (0+1j)(Z_0) + (0.5+0j)(X_1 Z_0)>
// The C API provides direct mapping functions for common transformations.
// Custom mapper prototyping as shown in Python is primarily a Python feature.
// For custom mappings in C, you will need to perform the iteration and
// conversion logic entirely by yourself.
ขั้นตอนหลักในการสร้าง mapper แบบกำหนดเองมีดังนี้:
-
กำหนดการแปลง action: ระบุว่าแต่ละ action (ที่ถูกจัดดัชนีด้วย mode) แมปไปเป็นการแทนค่าเป้าหมายอย่างไร ในตัวอย่างข้างต้น action แบบ Majorana จะแมปไปเป็น Pauli string ตามกฎของ Jordan-Wigner
-
ใช้ฟังก์ชันแมปที่มีให้ตัวใดตัวหนึ่ง: โมดูล
mappersมีฟังก์ชันอรรถประโยชน์สำหรับวนผ่านพจน์ของตัวดำเนินการและนำ action map แบบกำหนดเองไปใช้ต่อ สำหรับการแทนค่าตัวดำเนินการที่โมดูลoperatorsจัดให้ เช่นmap_majorana_action_generators()ใช้งานได้กับตัวดำเนินการ Majorana -
รับผลลัพธ์: ฟังก์ชันแมปจะคืนตัวดำเนินการในการแทนค่าเป้าหมายที่ใช้การแปลงอย่างสอดคล้องกันกับทุกพจน์
การออกแบบนี้ทำให้สร้างต้นแบบของการแมปแบบกำหนดเองได้ง่าย โดยไม่ต้องเข้าใจโครงสร้างภายในของตัวดำเนินการอย่างลึกซึ้ง การแปลงจะถูกใช้กับทุกพจน์ในตัวดำเนินการโดยอัตโนมัติ พร้อมคงความเท่าเทียมทางคณิตศาสตร์ไว้
คุณยังวนผ่านพจน์ของตัวดำเนินการด้วยตัวเองได้ แทนที่จะใช้ฟังก์ชัน iterator ที่มีให้ ดูรายละเอียดได้ในหัวข้อ การวนผ่านพจน์ ในคู่มือตัวดำเนินการ
การนำไปใช้ในไลบรารี
โมดูล qiskit_fermions.mappers.library มีการนำการแมปที่ใช้กันทั่วไปไปใช้อย่างมีประสิทธิภาพและผ่านการทดสอบอย่างละเอียด การนำไปใช้เหล่านี้ใช้รูปแบบพื้นฐานเดียวกับ mapper แบบกำหนดเอง แต่ได้รับการปรับให้เหมาะกับการใช้งานจริง และมีให้ทั้งใน Python API และ C API
ตัวอย่างด้านล่างใช้ library mapper กับ MajoranaOperator ตัวเดียวกันจากหัวข้อ mapper แบบกำหนดเอง เพื่อแสดงผลลัพธ์ที่เทียบเท่ากัน:
- PYTHON
- C
>>> from qiskit_fermions.mappers.library import majorana_to_fermion, jordan_wigner
>>>
>>> # Convert MajoranaOperator to FermionOperator using library mapper
>>> ferm_op = majorana_to_fermion(maj_op)
>>>
>>> # Apply Jordan-Wigner mapper to get qubit operator
>>> sparse_observable = jordan_wigner(ferm_op, num_qubits=2)
>>> difference = sparse_observable - custom_observable
>>> print(difference.simplify() == SparseObservable.zero(num_qubits))
True
#include <qiskit_fermions.h>
// Create a MajoranaOperator with the same terms as maj_op above
uint64_t num_terms = 2;
uint64_t num_modes = 3;
uint32_t modes[3] = {0, 1, 2};
QkComplex64 coeffs[2] = {{1.0, 0.0}, {0.5, 0.0}};
uint32_t boundaries[3] = {0, 2, 3};
QfMajoranaOperator *maj_op = qf_maj_op_new(num_terms, num_modes, coeffs, modes, boundaries);
// Convert MajoranaOperator to FermionOperator using library mapper
QfFermionOperator *ferm_op = qf_majorana_to_fermion(maj_op);
// Apply Jordan-Wigner mapper to get qubit operator
QkObs *qubit_op;
QfExitCode exit = qf_ferm_op_jordan_wigner(ferm_op, 2, &qubit_op);
assert(exit == QfExitCode_Success);
// Clean up
qf_ferm_op_free(ferm_op);
qf_maj_op_free(maj_op);
qk_obs_free(qubit_op);
สองขั้นตอนข้างต้นเขียนแยกออกมาเพื่อแสดงการประกอบ library mapper สองตัวเข้าด้วยกัน ในทางปฏิบัติคุณจะแมป MajoranaOperator ในขั้นตอนเดียวแทน ด้วย majorana_jordan_wigner() (qf_maj_op_jordan_wigner() ใน C API) และทำนองเดียวกันกับตัวดำเนินการประเภทอื่น – edge_vertex_jordan_wigner() และ transfer_vertex_jordan_wigner() (qf_edge_op_jordan_wigner(), qf_transfer_op_jordan_wigner()) การผ่าน FermionOperator ยังทำให้ผลลัพธ์ระหว่างทางบวมขึ้นด้วย: ตัวสร้างทุกตัวของพีชคณิตเหล่านี้แมปไปเป็น Pauli string เพียงตัวเดียว ขณะที่แต่ละ action แบบเฟอร์มิออนแมปไปเป็นผลรวมสองพจน์ ดังนั้นพจน์ที่สร้างจากตัวสร้างหลายตัวจะขยายออกไปแล้วต้องรวมกลับลงมาอีกครั้ง การประหยัดนี้เพิ่มขึ้นตามความยาวของพจน์ สำหรับพจน์ที่มีตัวสร้างตัวเดียวของ Hamiltonian แบบ hopping ทั้งสองเส้นทางมีต้นทุนใกล้เคียงกัน
ใน Python API library mapper บางตัวเหล่านี้ยังเรียกใช้ผ่าน mapper อำนวยความสะดวกที่ไม่ขึ้นกับชนิดได้ด้วย fermion_operator() และ majorana_operator() ทำหน้าที่เป็นด่านหน้าของ majorana_to_fermion() และ fermion_to_majorana() (รวมถึงตัวอื่น ๆ) โดยส่งต่อไปยังการนำไปใช้ในไลบรารีที่เหมาะสมภายในตามชนิดของอ็อบเจกต์ที่ได้รับ แทนที่จะให้คุณเลือกฟังก์ชันที่ถูกต้องเอง เช่นเดียวกันกับ jordan_wigner() และ ternary_tree() (แนะนำด้านล่าง) ซึ่งแต่ละตัวจะส่งต่อไปยังการนำไปใช้โดยตรงที่ตรงกับตัวดำเนินการที่ได้รับ ดู qiskit_fermions.protocols เพื่อดูว่าการส่งต่อนี้ถูกนำไปใช้ภายในอย่างไร
เลือกการเข้ารหัสด้วย ternary tree
Jordan-Wigner ไม่ใช่การเข้ารหัสจากเฟอร์มิออนไปเป็น qubit เพียงแบบเดียว และไม่ใช่แบบที่ถูกที่สุด สตริง ของมันยาวขึ้นตามดัชนี mode ดังนั้นตัวดำเนินการ Majorana บน mode สุดท้ายจากทั้งหมด mode จะแมปไปเป็น Pauli string ที่มีน้ำหนัก การเข้ารหัสที่กระจายข้อมูลพาริตีผ่านต้นไม้แทนที่จะเป็นโซ่จะทำได้ดีกว่าในเชิงอสมมาตร
ternary tree บน node ครอบคลุมตระกูลนี้ทั้งหมด แต่ละ node คือ qubit ที่มีลิงก์ลงสามเส้นติดป้าย X, Y และ Z ลิงก์หนึ่งอาจลงไปยัง node ลูก (edge) หรือสิ้นสุด (leg) เส้นทางของแต่ละ leg ย้อนกลับไปถึงรากสะกดเป็น Pauli string: การข้ามลิงก์ที่ติดป้าย ซึ่งลงจาก node ให้ บน qubit ต้นไม้ทุกต้นให้การเข้ารหัสที่ถูกต้อง และแบบที่คุ้นเคยก็เป็นเพียงรูปร่างเฉพาะ – โซ่ตาม Z คือ Jordan-Wigner ส่วนต้นไม้ที่ทุก node มีลูกสามตัวเท่าที่จำนวน node เอื้ออำนวยคือแบบสมดุล
- PYTHON
- C
>>> from qiskit_fermions.mappers import TernaryTree, TernaryTreeEncoding
>>>
>>> # Jordan-Wigner is a chain descending along Z, with X and Y as legs at every node.
>>> TernaryTree.chain(13, "Z").max_weight
13
>>> # The balanced tree spreads the same information over depth log_3(2n+1).
>>> TernaryTree.breadth_first(13, 3).max_weight
3
#include <qiskit_fermions.h>
QfTernaryTree *chain;
qf_ternary_tree_chain(13, QfPauliLabel_Z, &chain);
QfPauliLabel order[3] = {QfPauliLabel_X, QfPauliLabel_Y, QfPauliLabel_Z};
QfTernaryTree *balanced;
qf_ternary_tree_breadth_first(13, 3, order, &balanced);
assert(qf_ternary_tree_max_weight(chain) == 13);
assert(qf_ternary_tree_max_weight(balanced) == 3);
qf_ternary_tree_free(chain);
qf_ternary_tree_free(balanced);
น้ำหนักสูงสุดนั้นเหมาะสมที่สุดแล้ว Jiang et al. พิสูจน์ว่าตัวดำเนินการ Majorana ของการแมปจากเฟอร์มิออนไปเป็น qubit ใด ๆ ของ mode มีน้ำหนัก Pauli เฉลี่ยอย่างน้อย จึงไม่มีการแมปใดลดตัวที่ใหญ่ที่สุดให้ต่ำกว่า ได้ ต้นไม้ทั้งสองใช้ qubit เท่ากับ พอดี ดังนั้นสิ่งที่ประหยัดได้คือน้ำหนัก Pauli ไม่ใช่จำนวน qubit
หากต้องการแมปตัวดำเนินการ ให้คอมไพล์ต้นไม้เป็น encoding แล้วส่งให้ mapper พร้อมจำนวน qubit ของผลลัพธ์ การคอมไพล์จะจับคู่ leg ของต้นไม้เป็นตัวดำเนินการ Majorana และสร้าง Pauli string ของมันเพียงครั้งเดียว ดังนั้นควรใช้ encoding เดียวกันซ้ำกับตัวดำเนินการหลายตัว จำนวน qubit อยู่กับการเรียกแมปแทนที่จะอยู่กับ encoding ดังนั้น encoding เดียวจึงรองรับรีจิสเตอร์ที่มีความกว้างต่างกันได้ จำนวนที่มากกว่าจำนวน node ของต้นไม้จะถูกเติมด้วยเอกลักษณ์
- PYTHON
- C
>>> from qiskit_fermions.mappers.library import fermion_ternary_tree
>>> from qiskit_fermions.operators import FermionOperator
>>>
>>> hamiltonian = FermionOperator.from_dict(
... {((True, 0), (False, 3)): 1.0, ((True, 3), (False, 0)): 1.0}
... )
>>> encoding = TernaryTreeEncoding(TernaryTree.breadth_first(4, 3))
>>> fermion_ternary_tree(hamiltonian, encoding, 4).simplify()
<SparseObservable with 2 terms on 4 qubits: (0.5+0j)(Y_3 Z_1 Y_0) + (0.5+0j)(X_3 Z_2 X_0)>
// Create the hopping Hamiltonian a^dagger_0 a_3 + a^dagger_3 a_0
QfFermionOperator *hamiltonian = qf_ferm_op_zero();
QkComplex64 coeffs[2] = {{1.0, 0.0}, {1.0, 0.0}};
bool actions[4] = {true, false, true, false};
uint32_t indices[4] = {0, 3, 3, 0};
for (int i = 0; i < 2; i++) {
qf_ferm_op_add_term(hamiltonian, 2, actions + 2 * i, indices + 2 * i, &coeffs[i]);
}
QfPauliLabel order[3] = {QfPauliLabel_X, QfPauliLabel_Y, QfPauliLabel_Z};
QfTernaryTree *tree;
qf_ternary_tree_breadth_first(4, 3, order, &tree);
// Compile the tree once, then reuse the encoding for every operator mapped through it.
QfTernaryTreeEncoding *encoding;
qf_ternary_tree_encoding_new(tree, NULL, &encoding);
QkObs *qubit_op;
QfExitCode exit = qf_ferm_op_ternary_tree(hamiltonian, encoding, 4, &qubit_op);
assert(exit == QfExitCode_Success);
// Clean up
qf_ferm_op_free(hamiltonian);
qf_ternary_tree_encoding_free(encoding);
qf_ternary_tree_free(tree);
qk_obs_free(qubit_op);
การแมปผ่านโซ่ตาม Z ให้ผลเหมือน fermion_jordan_wigner() (qf_ferm_op_jordan_wigner() ใน C API) ทุกประการ ซึ่งเป็นความหมายที่ว่าแบบนี้เป็นการสรุปความทั่วไปของมัน:
>>> from qiskit_fermions.mappers.library import fermion_jordan_wigner
>>> chain = TernaryTreeEncoding(TernaryTree.chain(4, "Z"))
>>> direct = fermion_jordan_wigner(hamiltonian, 4)
>>> (fermion_ternary_tree(hamiltonian, chain, 4) - direct).simplify().num_terms
0
ตัวดำเนินการทุกการแทนค่ามี ternary-tree mapper โดยสะท้อนชุดของ Jordan-Wigner ดูรายการทั้งหมดได้ที่ qiskit_fermions.mappers.library และ Mapper Library ใน C API ใน Python API ยังเรียกใช้ผ่าน ternary_tree() ซึ่งเป็น wrapper ที่ไม่ขึ้นกับชนิดและเลือกตัวที่ถูกต้องให้ตัวดำเนินการที่ได้รับ เช่นเดียวกับที่ jordan_wigner() ทำ:
>>> from qiskit_fermions.mappers.library import ternary_tree
>>> ternary_tree(hamiltonian, encoding, 4).simplify()
<SparseObservable with 2 terms on 4 qubits: (0.5+0j)(Y_3 Z_1 Y_0) + (0.5+0j)(X_3 Z_2 X_0)>
เอกลักษณ์บางอย่างที่เป็นจริงภายใต้ Jordan-Wigner เป็นสมบัติของโซ่ ไม่ใช่ของ ternary tree ตัวดำเนินการจำนวนคือ เฉพาะบนโซ่เท่านั้น บนต้นไม้แบบอื่น จะกลายเป็นสตริง บนเซตของ qubit ในทำนองเดียวกัน ตัวดำเนินการ vertex เป็น Pauli น้ำหนัก 1 เฉพาะบนโซ่ ที่ซึ่งโซ่ ของสตริง Majorana ทั้งสองหักล้างกัน
เนื่องจากตัวดำเนินการจำนวนทุกตัวยังคงแมปไปเป็นสตริง เพียงตัวเดียว สถานะ Fock จึงยังคงเป็นสถานะฐานการคำนวณภายใต้ ternary tree ใด ๆ แต่บิต ไม่ได้อ่านค่า occupation อีกต่อไป occupation คือพาริตีของบิตที่วัดได้บน support ของภาพของ ให้อ่าน support เหล่านั้นจาก mapper แทนที่จะอนุมานจากต้นไม้ เพื่อให้การถอดรหัสถูกต้องสำหรับรูปร่างหรือการสลับ mode ใด ๆ:
>>> for mode in range(4):
... number = FermionOperator.from_dict({((True, mode), (False, mode)): 1.0})
... print(mode, ternary_tree(number, encoding, 4).simplify())
0 <SparseObservable with 2 terms on 4 qubits: (0.5+0j)() + (-0.5+0j)(Z_2 Z_1 Z_0)>
1 <SparseObservable with 2 terms on 4 qubits: (0.5+0j)() + (-0.5+0j)(Z_1)>
2 <SparseObservable with 2 terms on 4 qubits: (0.5+0j)() + (-0.5+0j)(Z_2)>
3 <SparseObservable with 2 terms on 4 qubits: (0.5+0j)() + (-0.5+0j)(Z_3)>
บนต้นไม้สมดุลนี้ mode 0 ถูกครอบครองเมื่อ qubit 0, 1 และ 2 มีเลข 1 เป็นจำนวนคี่ ดังนั้นการเตรียมมันจึงหมายถึงการพลิก qubit 0 เพียงตัวเดียว ขณะที่การถอดรหัสบิตสตริงที่วัดได้ต้องหาพาริตีจากทั้งสามตัว ข้อผิดพลาดการพลิกบิตเดียวบน qubit 1 จึงทำให้ occupation ของทั้ง mode 0 และ mode 1 เสียหาย ซึ่งสำคัญกับเวิร์กโฟลว์ใดก็ตามที่อ่าน occupation จากตัวอย่างที่มีสัญญาณรบกวน
เมื่อ Evolution ถูกลดระดับผ่าน mapper แบบนี้ ให้ครอบ mapper ด้วย group_wise() (ดู การสังเคราะห์ Evolution gate ในปริภูมิเฟอร์มิออน) พจน์ hopping แมปไปเป็น Pauli string สองตัวที่ commute กัน ซึ่งแต่ละตัวไม่อนุรักษ์จำนวนอนุภาคด้วยตัวเอง มีเพียงผลคูณของทั้งสองใน evolution เท่านั้นที่อนุรักษ์ ภายใต้ Jordan-Wigner สตริงทั้งสองกระทำบน qubit ชุดเดียวกัน ลำดับพจน์ที่ทำให้ง่ายขึ้นจึงทำให้สองตัวนี้อยู่ติดกัน ภายใต้ต้นไม้แบบอื่น support ของพวกมันต่างกัน (เช่น Y_3 Z_1 Y_0 และ X_3 Z_2 X_0 ก่อนหน้านี้) ทั้งลำดับดิบและลำดับที่ทำให้ง่ายขึ้นต่างสอดแทรกพวกมันกับภาพของพจน์อื่น และสูตรผลคูณฝั่ง qubit จะทำให้จำนวนอนุภาครั่วไหล
สร้างต้นไม้ตามต้องการ
ตัวสร้างแบบโซ่และแบบ breadth-first ครอบคลุมตระกูลที่สม่ำเสมอ แต่อินเทอร์เฟซทั่วไปรับคู่ (parent, label) หนึ่งคู่ต่อ node โดยใช้ None สำหรับราก (C API รับดัชนีของรากแทน) ซึ่งสำคัญเพราะต้นไม้ที่น่าค้นหามักไม่ใช่ทั้งโซ่และไม่สม่ำเสมอ – เช่น อัลกอริทึม Bonsai สร้าง spanning tree ของกราฟการเชื่อมต่อของอุปกรณ์ การแตกกิ่งของมันจึงเป็นไปตามการเชื่อมต่อนั้น
- PYTHON
- C
>>> # Node 0 branches three ways, node 1 twice, and node 4 carries a single child.
>>> tree = TernaryTree([None, (0, "X"), (0, "Y"), (0, "Z"), (1, "X"), (1, "Z"), (4, "Y")])
>>> tree.num_nodes, tree.num_legs, tree.max_weight
(7, 15, 4)
>>> tree.children(0)
{'X': 1, 'Y': 2, 'Z': 3}
// Node 0, the root, branches three ways, node 1 twice, and node 4 carries a single child.
// The root has no parent and no incoming link, so its entries are never read: any value works.
#define IGNORED 0
uint32_t parents[7] = {IGNORED, 0, 0, 0, 1, 1, 4};
QfPauliLabel labels[7] = {
IGNORED, QfPauliLabel_X, QfPauliLabel_Y, QfPauliLabel_Z,
QfPauliLabel_X, QfPauliLabel_Z, QfPauliLabel_Y,
};
QfTernaryTree *tree;
QfExitCode exit = qf_ternary_tree_new(7, 0, parents, labels, &tree);
assert(exit == QfExitCode_Success);
assert(qf_ternary_tree_num_nodes(tree) == 7);
assert(qf_ternary_tree_num_legs(tree) == 15);
assert(qf_ternary_tree_max_weight(tree) == 4);
qf_ternary_tree_free(tree);
สังเกตจำนวน leg: เป็น เสมอไม่ว่ารูปร่างจะเป็นอย่างไร node ที่มีลูกน้อยกว่าไม่ได้สร้าง Pauli string น้อยลง – ลิงก์ที่ไม่ได้ใช้ของมันคือ leg และ leg สะกดสตริงได้เหมือนกับที่ลูกหลานของ edge ทำ จากสตริง ตัวนั้น ตัวจับคู่เป็นตัวดำเนินการ Majorana ของ mode และเหลืออยู่ตัวเดียวพอดี
พาริตีรวม
leg ที่เหลือนั้นคือพาริตีเฟอร์มิออนรวม ซึ่งเป็นสมบัติของการเข้ารหัสมากกว่าจะเป็นการแมปของตัวดำเนินการใด ๆ จึงเป็นเมธอดของ encoding เอง:
- PYTHON
- C
>>> TernaryTreeEncoding(TernaryTree.chain(4, "Z")).total_parity(4)
<SparseObservable with 1 term on 4 qubits: (1+0j)(Z_3 Z_2 Z_1 Z_0)>
>>> TernaryTreeEncoding(TernaryTree.breadth_first(4, 3)).total_parity(4)
<SparseObservable with 1 term on 4 qubits: (1+0j)(Z_3 Z_0)>
QfTernaryTree *tree;
qf_ternary_tree_chain(4, QfPauliLabel_Z, &tree);
QfTernaryTreeEncoding *encoding;
qf_ternary_tree_encoding_new(tree, NULL, &encoding);
// On the chain this is the full Z string; on the balanced tree it is not.
QkObs *parity;
qf_ternary_tree_encoding_total_parity(encoding, 4, &parity);
qf_ternary_tree_encoding_free(encoding);
qf_ternary_tree_free(tree);
qk_obs_free(parity);
ดังที่กรณีสมดุลแสดงให้เห็น โดยทั่วไปแล้วสิ่งนี้ ไม่ใช่ สตริง ทั้งหมด: มันคือ leg ที่ไปถึงได้ด้วยเส้นทาง ทั้งหมด ซึ่งกระทำเป็นเอกลักษณ์บนทุก node ที่ไม่อยู่บนเส้นทางนั้น
พาริตีรวมในที่นี้เป็น observable ที่ต้องวัด ไม่ใช่ข้อจำกัดที่ต้องบังคับใช้ ค่าลักษณะเฉพาะทั้งสองของมันเป็นค่าทางกายภาพ ดังนั้นอย่าโปรเจกต์สถานะลงบนเซกเตอร์ : การเข้ารหัสแบบ ternary tree ใช้ qubit หนึ่งตัวต่อ mode และครอบคลุมปริภูมิ มิติเต็ม จึงไม่มีปริภูมิย่อยที่ไม่เป็นกายภาพให้โปรเจกต์ทิ้ง
ตัวอย่างใช้งานจริง: ต้นไม้ Bravyi-Kitaev
สเปกมักถูกคำนวณมากกว่าพิมพ์เอง เพราะต้นไม้ของการเข้ารหัสจริงแทบไม่เคยเล็กพอจะเขียนด้วยมือ Bravyi-Kitaev เป็นตัวอย่างที่ดี: มันอยู่ในตระกูลนี้ แต่ต้นไม้ของมันไม่สม่ำเสมอ จึงไม่มีตัวสร้างอำนวยความสะดวกตัวใดครอบคลุม
ต้นไม้ของมันสร้างโดยการแบ่งแบบเวียนเกิดเหนือ node ซึ่งมีหนึ่ง node ต่อหนึ่ง mode ภายในช่วงของ node ให้ เป็นกำลังสองที่ใหญ่ที่สุดที่พอดี node ที่วางสำหรับช่วงนั้นคือ ซึ่งเป็นตัวสุดท้ายในบล็อกนั้น ลิงก์ X ของมันลงไปยังต้นไม้ที่สร้างจากบล็อกก่อนหน้า และลิงก์ Z ลงไปยังต้นไม้ที่สร้างจากส่วนที่เหลือหลังมัน โดยปล่อยลิงก์ Y เป็น leg:
>>> def bravyi_kitaev_tree(num_modes: int) -> TernaryTree:
... """Builds the ternary tree of the Bravyi-Kitaev encoding on ``num_modes`` modes."""
... spec: list[tuple[int, str] | None] = [None] * num_modes
... def build(left: int, right: int) -> int | None:
... if left > right:
... return None # an empty range: the parent's link stays a leg
... block = 1
... while 2 * block <= right - left + 1:
... block *= 2
... node = left + block - 1
... for child, label in (
... (build(left, node - 1), "X"), # the block before this node
... (build(node + 1, right), "Z"), # the remainder after it
... ):
... if child is not None:
... spec[child] = (node, label)
... return node
... build(0, num_modes - 1)
... return TernaryTree(spec)
ที่สี่ mode ช่วงทั้งหมดเป็นบล็อกเดียว ดังนั้น node ที่วางก่อนคือ 3 โดยมี อยู่ก่อนหน้าและไม่มีอะไรอยู่หลัง ช่วงย่อยนั้นให้ node 1 เหนือ node เดี่ยว 0 และ 2 การคอมไพล์ต้นไม้ยืนยันว่า encoding เป็น Bravyi-Kitaev จริง ไม่ใช่แค่ต้นไม้ที่มีรูปร่างในคลาสเดียวกัน – ภาพ Majorana ตัวแรกของมันคือ :
>>> tree = bravyi_kitaev_tree(4)
>>> tree.children(1)
{'X': 0, 'Z': 2}
>>> encoding = TernaryTreeEncoding(tree)
>>> encoding.majorana_image(0)
[(0, 'X'), (1, 'X'), (3, 'X')]
พาริตีรวมของมันทำให้ประเด็นของหัวข้อก่อนหน้าเป็นรูปธรรม: leg ที่เหลือคือตัวที่ไปถึงได้ด้วยเส้นทาง ทั้งหมด และในที่นี้เส้นทางนั้นหยุดที่ราก ดังนั้น observable จึงเป็น ของ qubit เดียว ไม่ใช่สตริงบนทั้งสี่ qubit:
>>> encoding.total_parity(4)
<SparseObservable with 1 term on 4 qubits: (1+0j)(Z_3)>
การขยายขนาดไม่ต้องการอะไรนอกจากอาร์กิวเมนต์ที่ใหญ่ขึ้น เนื่องจากไม่มี node ใดใช้ลิงก์ Y เป็น edge แต่ละ node จึงแตกกิ่งได้มากสุดสองทาง น้ำหนัก Pauli จึงโตแบบลอการิทึมแทนที่จะเป็นเชิงเส้น:
>>> [bravyi_kitaev_tree(n).max_weight for n in (4, 8, 16, 32, 64)]
[3, 4, 5, 6, 7]