Lewati ke konten utama
Proyek ini masih dalam tahap beta. Menemukan bug? Buat isu GitHub. Punya pertanyaan atau ide? Tanya di Discussions — kami sangat menghargai masukan kamu!
Bagaimana kualitas terjemahan di halaman ini?

Mapping representasi operator

Mapper adalah rutin yang mengubah operator dari satu representasi ke representasi lain sambil menjaga kesetaraan matematis. Pakai untuk bekerja secara fleksibel di berbagai aljabar operator dan untuk menyiapkan operator bagi eksekusi circuit kuantum.

Mengimplementasikan mapper kustom​

Modul mappers memudahkan implementasi mapping kustom. Mapper bekerja dengan mentransformasi aksi-aksi fundamental (creation, annihilation, Majorana, dan seterusnya) yang menyusun sebuah operator, lalu menggabungkan aksi-aksi hasil transformasi itu sesuai aturan representasi target.

Sebagai contoh konkret, coba lihat transformasi Jordan-Wigner yang memetakan MajoranaOperator ke operator qubit dengan map_majorana_action_generators():

>>> 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)>

Langkah-langkah utama untuk membuat mapper kustom apa pun:

  1. Tentukan transformasi aksi: Tentukan bagaimana setiap aksi (diindeks berdasarkan mode) dipetakan ke representasi target. Di contoh di atas, aksi Majorana dipetakan ke string Pauli sesuai aturan Jordan-Wigner.

  2. Pakai salah satu fungsi pemetaan bawaan: Modul mappers menyediakan fungsi utilitas untuk mengiterasi suku-suku operator dan menerapkan peta aksi kustom secara berurutan untuk representasi operator yang disediakan modul operators. Misalnya, map_majorana_action_generators() bekerja dengan operator Majorana.

  3. Ambil hasilnya: Fungsi pemetaan mengembalikan operator dalam representasi target, dengan transformasi yang diterapkan secara konsisten ke semua suku.

Desain ini bikin kamu gampang membuat prototipe pemetaan kustom tanpa perlu paham struktur internal operator. Transformasinya diterapkan otomatis ke setiap suku di operator, sambil menjaga ekuivalensi matematisnya.

Petunjuk

Kamu juga bisa mengiterasi suku-suku operator secara manual, tanpa memakai fungsi iterator bawaan. Lihat bagian iterasi suku di panduan operator untuk detailnya.

Implementasi pustaka​

Modul qiskit_fermions.mappers.library menyediakan implementasi pemetaan umum yang efisien dan sudah diuji menyeluruh. Pola dasarnya sama dengan mapper kustom, tapi sudah dioptimalkan untuk penggunaan produksi dan tersedia di API Python maupun C.

Contoh di bawah menerapkan mapper pustaka ke MajoranaOperator yang sama dari bagian mapper kustom, dan menunjukkan hasil yang setara:

>>> 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
Catatan

Dua langkah di atas sengaja ditulis lengkap untuk menunjukkan dua mapper pustaka yang dikomposisikan. Dalam praktiknya, kamu akan memetakan MajoranaOperator dalam satu langkah saja, dengan majorana_jordan_wigner() (qf_maj_op_jordan_wigner() di API C), dan begitu juga untuk tipe operator lainnya – edge_vertex_jordan_wigner() dan transfer_vertex_jordan_wigner() (qf_edge_op_jordan_wigner(), qf_transfer_op_jordan_wigner()). Lewat FermionOperator juga membengkakkan hasil antara: setiap generator aljabar ini dipetakan ke satu string Pauli, sedangkan setiap aksi fermionik dipetakan ke jumlah dua suku, jadi suku yang dibangun dari beberapa generator mengembang dulu hanya untuk digabung lagi. Penghematan itu makin besar seiring panjang sukunya; untuk suku generator tunggal dari Hamiltonian hopping, kedua rute biayanya hampir sama.

Di API Python, sebagian mapper pustaka ini juga bisa diakses lewat mapper praktis yang tidak bergantung pada tipe. fermion_operator() dan majorana_operator() berada di depan majorana_to_fermion() dan fermion_to_majorana() (dan lainnya), dan secara internal mendelegasikan ke implementasi pustaka yang sesuai berdasarkan tipe objek yang diberikan, jadi kamu tidak perlu memilih fungsi yang tepat sendiri. Hal yang sama berlaku untuk jordan_wigner() dan ternary_tree() (diperkenalkan di bawah), yang masing-masing meneruskan ke implementasi langsung yang cocok dengan operator yang diberikan. Lihat qiskit_fermions.protocols untuk cara dispatch ini diimplementasikan secara internal.

Pilih encoding dengan pohon terner​

Jordan-Wigner bukan satu-satunya encoding fermion-ke-qubit, dan bukan yang paling murah. String ZZ-nya bertambah panjang seiring indeks mode, jadi operator Majorana pada mode terakhir dari nn mode dipetakan ke string Pauli berbobot nn. Encoding yang menyebarkan informasi paritas lewat pohon, bukan rantai, secara asimtotik lebih baik.

Sebuah pohon terner dengan nn simpul mencakup seluruh keluarga ini. Setiap simpul adalah sebuah qubit dengan tiga tautan ke bawah berlabel X, Y, dan Z; sebuah tautan bisa turun ke simpul anak (sebuah edge) atau berhenti (sebuah leg). Jalur setiap leg kembali ke akar mengeja sebuah string Pauli: melewati tautan berlabel PP yang turun dari simpul uu menyumbang PP pada qubit uu. Setiap pohon seperti itu menghasilkan encoding yang valid, dan yang sudah familiar hanyalah bentuk tertentu – rantai sepanjang Z adalah Jordan-Wigner, sedangkan pohon yang setiap simpulnya punya tiga anak, sejauh jumlah simpul memungkinkan, adalah pohon seimbang.

>>> 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

Bobot maksimum itu optimal. Jiang et al. membuktikan bahwa operator Majorana dari sembarang pemetaan fermion-ke-qubit untuk nn mode punya bobot Pauli rata-rata paling sedikit log⁡3(2n)\log_3(2n), jadi tidak ada pemetaan yang bisa menurunkan yang terbesar di bawah ⌈log⁡3(2n+1)⌉\lceil \log_3(2n+1) \rceil. Kedua pohon memakai tepat nn qubit, jadi penghematannya ada di bobot Pauli, bukan jumlah qubit.

Untuk memetakan operator, kompilasi pohon menjadi encoding lalu berikan ke mapper bersama jumlah qubit untuk hasilnya. Kompilasi memasangkan leg pohon menjadi operator Majorana dan membangun string Pauli-nya sekali saja, jadi encoding yang sama sebaiknya dipakai ulang untuk banyak operator. Jumlah qubit tetap ada di pemanggilan pemetaan, bukan di encoding, jadi satu encoding bisa melayani register dengan lebar berbeda; jumlah di atas jumlah simpul pohon diisi dengan identitas.

>>> 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)>

Memetakan lewat rantai sepanjang Z menghasilkan persis sama dengan fermion_jordan_wigner() (qf_ferm_op_jordan_wigner() di API C), dan inilah maksud dari "menggeneralisasi" Jordan-Wigner:

>>> 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 representasi operator punya mapper pohon terner, mengikuti set Jordan-Wigner; lihat qiskit_fermions.mappers.library untuk daftar lengkapnya, dan Mapper Library di API C. Di API Python, semuanya juga bisa diakses lewat ternary_tree(), pembungkus yang tidak bergantung pada tipe dan memilih mapper yang tepat 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)>
Catatan

Beberapa identitas yang berlaku di Jordan-Wigner adalah sifat dari rantai, bukan dari pohon terner. Operator bilangan nj=(1−Zj)/2n_j = (1 - Z_j)/2 hanya berlaku di rantai; di pohon lain, ZjZ_j menjadi string ZZ atas sekumpulan qubit. Demikian juga operator vertex VjV_j adalah Pauli berbobot 1 ZjZ_j hanya di rantai, tempat rantai ZZ dari dua string Majorana saling meniadakan.

Karena setiap operator bilangan tetap dipetakan ke satu string ZZ, state Fock tetap berupa state basis komputasi di pohon terner mana pun, tapi bit qq tidak lagi menunjukkan okupasi qq. Okupasi jj justru adalah paritas dari bit terukur pada support citra njn_j. Baca support itu dari mapper, jangan menurunkannya dari pohon, supaya dekoding tetap benar untuk bentuk atau permutasi mode apa pun:

>>> 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)>

Di pohon seimbang ini, mode 0 terisi ketika qubit 0, 1, dan 2 memuat jumlah ganjil angka satu, jadi menyiapkannya berarti membalik qubit 0 saja, sedangkan mendekode bitstring hasil ukur butuh paritas dari ketiganya. Satu error bit-flip pada qubit 1 lalu merusak okupasi mode 0 dan mode 1 sekaligus, yang penting untuk workflow apa pun yang membaca okupasi dari sampel yang berderau.

Ketika sebuah Evolution diturunkan lewat mapper seperti itu, bungkus mapper tersebut dengan group_wise() (lihat Sintesis Evolution gate di ruang fermionik). Suku hopping dipetakan ke dua string Pauli yang saling komut, dan tak satu pun menjaga jumlah partikel sendirian; hanya hasil kali keduanya dalam evolusi yang menjaganya. Di Jordan-Wigner kedua string bekerja pada qubit yang sama, jadi urutan suku yang disederhanakan menjaga keduanya tetap berdekatan. Di pohon lain support keduanya berbeda (seperti Y_3 Z_1 Y_0 dan X_3 Z_2 X_0 tadi), urutan mentah maupun yang disederhanakan sama-sama menyelipkan citra suku lain di antara keduanya, dan rumus produk di sisi qubit lalu membocorkan jumlah partikel.

Bangun pohon sembarang​

Konstruktor rantai dan breadth-first mencakup keluarga yang seragam, tapi antarmuka umumnya menerima satu pasangan (parent, label) per simpul, dengan None untuk akar (API C menerima indeks akar sebagai gantinya). Ini penting karena pohon yang layak dicari biasanya bukan rantai dan tidak seragam – algoritma Bonsai, misalnya, menumbuhkan pohon rentang dari graf kopling perangkat, jadi percabangannya mengikuti konektivitas itu.

>>> # 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}

Perhatikan jumlah leg: selalu 2n+12n+1, apa pun bentuknya. Simpul dengan lebih sedikit anak tidak menghasilkan lebih sedikit string Pauli – tautan yang tidak terpakai adalah leg, dan sebuah leg mengeja string sama seperti keturunan sebuah edge. Dari 2n+12n+1 string itu, 2n2n dipasangkan menjadi operator Majorana untuk nn mode, dan tepat satu tersisa.

Paritas total​

Leg yang tersisa itu adalah paritas fermionik total ∏j(1−2nj)\prod_j (1 - 2 n_j). Ini sifat dari encoding, bukan pemetaan operator mana pun, jadi ini sebuah metode pada encoding itu sendiri:

>>> 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)>

Seperti ditunjukkan kasus seimbang, ini bukan string serba-ZZ secara umum: ini adalah leg yang dicapai oleh jalur serba-ZZ, yang bekerja sebagai identitas pada setiap simpul yang tidak ada di jalur itu.

Penting

Paritas total di sini adalah observabel untuk diukur, bukan batasan untuk dipaksakan. Kedua nilai eigennya fisis, jadi jangan memproyeksikan state ke sektor +1+1: encoding pohon terner memakai satu qubit per mode dan mencakup seluruh ruang berdimensi 2n2^n, sehingga tidak ada subruang tak fisis yang perlu diproyeksikan keluar.

Contoh kerja: pohon Bravyi-Kitaev​

Spesifikasi lebih sering dihitung daripada diketik, karena pohon untuk encoding nyata jarang cukup kecil untuk ditulis tangan. Bravyi-Kitaev contoh yang bagus: ia termasuk keluarga ini, tapi pohonnya tidak seragam, jadi tidak ada konstruktor praktis yang mencakupnya.

Pohonnya dibangun dengan pemisahan rekursif atas simpul-simpul, satu per mode. Dalam rentang simpul [l,r][l, r], misalkan hh adalah pangkat dua terbesar yang muat; simpul yang ditempatkan untuk rentang itu adalah l+h−1l + h - 1, yang terakhir di blok tersebut. Tautan X-nya turun ke pohon yang dibangun dari blok sebelumnya dan tautan Z-nya ke pohon yang dibangun dari sisa setelahnya, sedangkan tautan Y-nya dibiarkan menjadi 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)

Pada empat mode seluruh rentang adalah satu blok, jadi simpul yang pertama ditempatkan adalah 3, dengan [0,2][0, 2] sebelumnya dan tidak ada setelahnya; sub-rentang itu menghasilkan simpul 1 di atas simpul tunggal 0 dan 2. Mengompilasi pohon mengonfirmasi bahwa encoding-nya memang Bravyi-Kitaev dan bukan sekadar pohon dengan kelas bentuk yang sama – citra Majorana pertamanya adalah X0X1X3X_0 X_1 X_3:

>>> 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')]

Paritas totalnya membuat poin bagian sebelumnya jadi konkret: leg yang tersisa adalah yang dicapai oleh jalur serba-ZZ, dan di sini jalur itu berhenti di akar, jadi observabelnya adalah Z3Z_3 satu qubit, bukan string atas keempat qubit:

>>> encoding.total_parity(4)
<SparseObservable with 1 term on 4 qubits: (1+0j)(Z_3)>

Menskalakan tidak butuh apa-apa selain argumen yang lebih besar. Karena tidak ada simpul yang memakai tautan Y-nya untuk edge, masing-masing bercabang paling banyak dua arah, jadi bobot Pauli tumbuh secara logaritmik, bukan linear:

>>> [bravyi_kitaev_tree(n).max_weight for n in (4, 8, 16, 32, 64)]
[3, 4, 5, 6, 7]