Petakan perwakilan operator
Pemeta ialah rutin yang mengubah operator daripada satu perwakilan kepada yang lain sambil mengekalkan kesetaraan matematik. Gunakannya untuk bekerja secara fleksibel merentasi algebra operator yang berbeza dan untuk menyediakan operator bagi pelaksanaan litar kuantum.
Laksanakan pemeta tersuai
Modul pemeta memudahkan pelaksanaan pemetaan tersuai. Pemeta berfungsi dengan mengubah tindakan asas (penciptaan, pemusnahan, Majorana, dan sebagainya) yang membentuk operator, kemudian menggabungkan tindakan yang telah diubah ini mengikut peraturan perwakilan sasaran.
Sebagai contoh konkrit, pertimbangkan transformasi Jordan-Wigner yang memetakan MajoranaOperator kepada operator qubit menggunakan 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.
Langkah utama untuk melaksanakan mana-mana pemeta tersuai ialah:
-
Takrifkan transformasi tindakan: Nyatakan bagaimana setiap tindakan (diindeks mengikut mod) dipetakan kepada perwakilan sasaran. Dalam contoh di atas, tindakan Majorana dipetakan kepada rentetan Pauli mengikut peraturan Jordan-Wigner.
-
Gunakan salah satu fungsi pemetaan yang disediakan: Modul
mappersmenyediakan fungsi utiliti untuk mengendalikan lelaran ke atas sebutan operator dan penerapan peta tindakan tersuai seterusnya bagi perwakilan operator yang disediakan oleh moduloperators. Sebagai contoh,map_majorana_action_generators()berfungsi dengan operator Majorana. -
Dapatkan hasilnya: Fungsi pemetaan mengembalikan operator dalam perwakilan sasaran dengan transformasi yang diterapkan secara konsisten pada semua sebutan.
Reka bentuk ini memudahkan pembuatan prototaip pemetaan tersuai tanpa pengetahuan mendalam tentang struktur dalaman operator. Transformasi diterapkan secara automatik pada setiap sebutan dalam operator sambil mengekalkan kesetaraan matematik.
Anda juga boleh melelar sebutan operator secara manual dan bukannya menggunakan salah satu fungsi pelelar yang disediakan. Lihat bahagian tentang lelaran sebutan dalam panduan operator untuk butiran.
Pelaksanaan pustaka
Modul qiskit_fermions.mappers.library menyediakan pelaksanaan pemetaan lazim yang cekap dan diuji secara menyeluruh. Pelaksanaan ini mengikut corak asas yang sama seperti pemeta tersuai tetapi dioptimumkan untuk kegunaan pengeluaran dan tersedia dalam API Python dan C.
Contoh di bawah menerapkan pemeta pustaka pada MajoranaOperator yang sama daripada bahagian pemeta tersuai, menunjukkan hasil yang setara:
- 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);
Dua langkah di atas ditulis secara terperinci untuk menunjukkan dua pemeta pustaka yang digubah bersama. Dalam amalan, anda akan memetakan MajoranaOperator dalam satu langkah sahaja, dengan majorana_jordan_wigner() (qf_maj_op_jordan_wigner() dalam API C), dan begitu juga untuk jenis operator lain – edge_vertex_jordan_wigner() dan transfer_vertex_jordan_wigner() (qf_edge_op_jordan_wigner(), qf_transfer_op_jordan_wigner()). Melalui FermionOperator juga mengembangkan hasil perantaraan: setiap penjana algebra ini dipetakan kepada satu rentetan Pauli, manakala setiap tindakan fermion dipetakan kepada hasil tambah dua sebutan, jadi sebutan yang dibina daripada beberapa penjana berkembang hanya untuk digabungkan semula kemudian. Penjimatan itu bertambah dengan panjang sebutan; bagi sebutan penjana tunggal dalam Hamiltonian lompatan, kedua-dua laluan berkos lebih kurang sama.
Dalam API Python, sebahagian pemeta pustaka ini juga boleh dicapai melalui pemeta kemudahan yang tidak bergantung pada jenis. fermion_operator() dan majorana_operator() berada di hadapan majorana_to_fermion() dan fermion_to_majorana() (antara lain), menyerahkan kepada pelaksanaan pustaka yang sesuai secara dalaman berdasarkan jenis objek yang diberikan, tanpa memerlukan anda memilih fungsi yang betul sendiri. Perkara yang sama terpakai pada jordan_wigner() dan ternary_tree() (diperkenalkan di bawah), yang masing-masing menghantar kepada pelaksanaan langsung yang sepadan dengan operator yang diberikan. Lihat qiskit_fermions.protocols untuk cara penghantaran ini dilaksanakan secara dalaman.
Pilih pengekodan dengan pokok ternari
Jordan-Wigner bukan satu-satunya pengekodan fermion-ke-qubit, dan ia bukan yang paling murah. Rentetan nya bertambah panjang dengan indeks mod, jadi operator Majorana pada mod terakhir daripada mod dipetakan kepada rentetan Pauli berpemberat . Pengekodan yang menyebarkan maklumat pariti merentasi pokok dan bukannya rantai mencapai prestasi asimptot yang lebih baik.
Pokok ternari pada nod merangkumi seluruh keluarga ini. Setiap nod ialah qubit yang membawa tiga pautan ke bawah berlabel X, Y dan Z; sesuatu pautan sama ada menurun ke nod anak (sebuah tepi) atau berakhir (sebuah daun). Laluan setiap daun kembali ke akar mengeja rentetan Pauli: melintasi pautan berlabel yang menurun daripada nod menyumbang pada qubit . Setiap pokok sedemikian menghasilkan pengekodan yang sah, dan yang biasa dikenali hanyalah bentuk tertentu – rantai di sepanjang Z ialah Jordan-Wigner, manakala pokok yang setiap nodnya mempunyai tiga anak, setakat yang dibenarkan oleh bilangan nod, ialah seimbang.
- 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);
Pemberat maksimum itu adalah optimum. Jiang et al. membuktikan bahawa operator Majorana bagi mana-mana pemetaan fermion-ke-qubit untuk mod mempunyai pemberat Pauli purata sekurang-kurangnya , jadi tiada pemetaan yang dapat membawa yang terbesar daripadanya di bawah . Kedua-dua pokok menggunakan tepat qubit, jadi penjimatan terletak pada pemberat Pauli, bukan bilangan qubit.
Untuk memetakan operator, susun pokok menjadi pengekodan dan serahkannya kepada pemeta bersama-sama bilangan qubit untuk hasilnya. Penyusunan memasangkan daun pokok menjadi operator Majorana dan membina rentetan Pauli mereka sekali sahaja, jadi pengekodan yang sama harus digunakan semula merentasi banyak operator. Bilangan qubit kekal bersama panggilan pemetaan dan bukannya pengekodan, jadi satu pengekodan boleh melayani daftar berlainan lebar; bilangan yang melebihi bilangan nod pokok diisi dengan identiti.
- 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);
Pemetaan melalui rantai di sepanjang Z menghasilkan semula fermion_jordan_wigner() (qf_ferm_op_jordan_wigner() dalam API C) dengan tepat, dan dalam pengertian inilah ia menggeneralisasikannya:
>>> 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
Setiap perwakilan operator mempunyai pemeta pokok ternari, mencerminkan set Jordan-Wigner; lihat qiskit_fermions.mappers.library untuk senarai penuh, dan Mapper Library dalam API C. Dalam API Python, ia juga boleh dicapai melalui ternary_tree(), pembalut tidak bergantung jenis yang memilih pemeta yang betul untuk operator yang diberikan, sama seperti 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)>
Sebahagian identiti yang benar di bawah Jordan-Wigner ialah sifat rantai dan bukannya pokok ternari. Operator nombor ialah hanya pada rantai; pada pokok lain, menjadi rentetan ke atas set qubit. Begitu juga operator bucu ialah Pauli berpemberat 1 hanya pada rantai, di mana rantai bagi dua rentetan Majorana saling membatalkan.
Oleh sebab setiap operator nombor masih dipetakan kepada satu rentetan , keadaan Fock masih merupakan keadaan asas pengiraan di bawah mana-mana pokok ternari, tetapi bit tidak lagi menunjukkan penghunian . Penghunian sebaliknya ialah pariti bit yang diukur pada sokongan imej . Baca sokongan tersebut daripada pemeta dan bukannya menerbitkannya daripada pokok, supaya penyahkodan kekal betul untuk sebarang bentuk atau pilih atur mod:
>>> 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)>
Pada pokok seimbang ini, mod 0 dihuni apabila qubit 0, 1 dan 2 memegang bilangan ganjil satu, jadi penyediaannya bermakna membalikkan qubit 0 sahaja, manakala penyahkodan rentetan bit yang diukur mengambil pariti ke atas ketiga-tiganya. Satu ralat balikan bit pada qubit 1 kemudian merosakkan penghunian kedua-dua mod 0 dan mod 1, yang penting bagi sebarang aliran kerja yang membaca penghunian daripada sampel bising.
Apabila Evolution diturunkan melalui pemeta sedemikian, bungkus pemeta dalam group_wise() (lihat Synthesize an Evolution gate in fermionic space). Sebutan lompatan dipetakan kepada dua rentetan Pauli yang berkomut, yang masing-masing tidak mengekalkan bilangan zarah dengan sendirinya; hanya hasil darabnya dalam evolusi yang berbuat demikian. Di bawah Jordan-Wigner kedua-dua rentetan bertindak pada qubit yang sama, jadi susunan sebutan yang dipermudahkan memastikannya bersebelahan. Di bawah pokok lain sokongan mereka berbeza (seperti dalam Y_3 Z_1 Y_0 dan X_3 Z_2 X_0 sebelum ini), susunan mentah dan susunan yang dipermudahkan kedua-duanya menyelang-nyelikannya dengan imej sebutan lain, dan formula hasil darab di sisi qubit kemudian membocorkan bilangan zarah.
Bina pokok sembarangan
Pembina rantai dan pembina bulatan-pertama-lebar merangkumi keluarga seragam, tetapi antara muka am mengambil satu pasangan (parent, label) bagi setiap nod, dengan None untuk akar (API C mengambil indeks akar sebagai gantinya). Ini penting kerana pokok yang berbaloi dicari biasanya bukan rantai mahupun seragam – algoritma Bonsai, sebagai contoh, menumbuhkan pokok rentang bagi graf gandingan peranti, jadi percabangannya mengikut ketersambungan itu.
- 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);
Perhatikan bilangan daun: ia sentiasa , walau apa jua bentuknya. Nod dengan lebih sedikit anak tidak menjana lebih sedikit rentetan Pauli – pautannya yang tidak digunakan ialah daun, dan daun mengeja rentetan sama seperti keturunan sesuatu tepi. Daripada rentetan itu, dipasangkan menjadi operator Majorana bagi mod, dan tepat satu tertinggal.
Jumlah pariti
Daun yang tertinggal itu ialah jumlah pariti fermion . Ia ialah sifat pengekodan dan bukannya pemetaan mana-mana operator, jadi ia ialah kaedah pada pengekodan itu sendiri:
- 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);
Seperti yang ditunjukkan oleh kes seimbang, ini bukan secara amnya rentetan semua-: ia ialah daun yang dicapai melalui laluan semua-, yang bertindak sebagai identiti pada setiap nod yang tidak berada pada laluan itu.
Jumlah pariti di sini ialah cerapan untuk diukur, bukan kekangan untuk dikenakan. Kedua-dua nilai eigennya adalah fizikal, jadi jangan unjurkan keadaan ke sektor : pengekodan pokok ternari menggunakan satu qubit bagi setiap mod dan adalah keseluruhan (onto) ruang berdimensi , tanpa meninggalkan subruang tidak fizikal untuk dibuang melalui unjuran.
Contoh terperinci: pokok Bravyi-Kitaev
Spesifikasi lebih kerap dikira daripada ditaip, kerana pokok untuk pengekodan sebenar jarang cukup kecil untuk ditulis dengan tangan. Bravyi-Kitaev ialah contoh yang baik: ia tergolong dalam keluarga ini, tetapi pokoknya tidak seragam, jadi tiada pembina kemudahan yang merangkuminya.
Pokoknya dibina melalui pembahagian rekursif ke atas nod, satu bagi setiap mod. Dalam julat nod , biarkan ialah kuasa dua terbesar yang muat; nod yang diletakkan untuk julat itu ialah , yang terakhir dalam blok tersebut. Pautan X nya menurun ke pokok yang dibina daripada blok sebelumnya dan pautan Z nya ke pokok yang dibina daripada baki selepasnya, meninggalkan pautan Y nya sebagai daun:
>>> 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)
Pada empat mod seluruh julat ialah satu blok, jadi nod yang diletakkan dahulu ialah 3, dengan sebelumnya dan tiada apa-apa selepasnya; sub-julat itu memberikan nod 1 di atas nod tunggal 0 dan 2. Menyusun pokok mengesahkan bahawa pengekodan itu ialah Bravyi-Kitaev dan bukan sekadar pokok dengan kelas bentuk yang sama – imej Majorana pertamanya ialah :
>>> 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')]
Jumlah paritinya menjadikan perkara dalam bahagian sebelumnya konkrit: daun yang tertinggal ialah yang dicapai melalui laluan semua-, dan di sini laluan itu berhenti pada akar, jadi cerapan itu ialah satu-qubit dan bukannya rentetan ke atas keempat-empat qubit:
>>> encoding.total_parity(4)
<SparseObservable with 1 term on 4 qubits: (1+0j)(Z_3)>
Penskalaan ke atas tidak memerlukan apa-apa selain hujah yang lebih besar. Oleh sebab tiada nod yang pernah menggunakan pautan Y nya untuk tepi, setiap satu bercabang paling banyak dua arah, jadi pemberat Pauli bertambah secara logaritma dan bukannya secara linear:
>>> [bravyi_kitaev_tree(n).max_weight for n in (4, 8, 16, 32, 64)]
[3, 4, 5, 6, 7]