Langkau ke kandungan utama
Projek ini dalam fasa beta. Jumpa pepijat? Buat isu GitHub. Ada soalan atau idea? Tanya dalam Discussions — kami suka dengar maklum balas anda!
Bagaimana kualiti terjemahan pada halaman ini?

Simulasikan dinamik Fermi-Hubbard 1D dengan set aliran

Penting

Fungsi yang diterangkan dalam panduan ini pada masa ini hanya tersedia dalam API Python. Fungsi setara akan disediakan dalam API C dalam keluaran akan datang.

Panduan ini membina litar untuk dinamik masa model Fermi-Hubbard satu dimensi, daripada menulis Hamiltonian hingga menyemak ketumpatan tapak yang telah berevolusi. Apa yang menjadikan laluan ini menarik ialah satu idea, yang diambil daripada rangka kerja set aliran oleh Gandon et al. [1], kerja yang turut mendorong TransferVertexOperator dalam pakej ini:

Daripada mengumpulkan sebutan Hamiltonian selepas pemetaan fermion-ke-qubit, kumpulkan sebelum pemetaan itu, ke dalam set aliran, subset satu dimensi bagi graf interaksi fermion berarah yang operator pindahannya saling kalis tukar tertib.

Pilihan itu mendorong segala-galanya di bawah. Ia memerlukan yang berikut:

  • Pengekodan fermion-ke-qubit tersuai yang disesuaikan dengan set aliran, yang di sini menggunakan satu qubit bantu supaya bilangan qubit yang dikaitkan dengan pelaksanaan sebutan Hamiltonian tertentu tidak lagi meningkat mengikut bilangan mod fermion (seperti yang berlaku bagi Jordan-Wigner (JW)).

  • Sintesis tersuai yang memanfaatkan kekalisan tukar tertib dalam setiap set. Faedahnya ialah litar yang kedalaman dua-qubitnya malar terhadap saiz sistem, berbanding pertumbuhan linear Trotterisasi sebutan demi sebutan berasaskan JW.

Jentera yang memasukkan pengekodan anda sendiri ke dalam transpiler (CustomF2QLayout dan MapperFnEvolutionSynthesis) boleh digunakan pada mana-mana model.

Lihat juga

Panduan transpilasi untuk peringkat transpiler yang dirujuk sepanjang panduan ini, dan panduan pemeta untuk resipi umum menulis pemeta tersuai.

Operator pindahan dan set aliran​

TransferVertexOperator dibina daripada operator bucu VjV_j dan operator pindahan TjkT_{jk}. Dari segi operator penciptaan dan pemusnahan fermion,

Vj=1−2aj†aj ,Tjk=−12(aj†−aj)(ak†+ak) ,V_j = 1 - 2 a^\dagger_j a_j \, , \qquad T_{jk} = -\tfrac{1}{2} \left( a^\dagger_j - a_j \right) \left( a^\dagger_k + a_k \right) \, ,

jadi VjV_j mengukur penghunian mod jj (ia +1+1 apabila kosong dan −1-1 apabila terisi), manakala TjkT_{jk} memindahkan fermion sepanjang tepi berarah j→kj \to k. Perhatikan bahawa dua indeks TjkT_{jk} tidak boleh ditukar ganti. Indeks pertama membawa gabungan tolak dan indeks kedua membawa tambah, itulah yang menjadikan TjkT_{jk} berbeza daripada TkjT_{kj} dan memberi tepi itu orientasinya.

Ini memenuhi hubungan kalis tukar tertib campuran. Dua operator pindahan yang bertindak pada tapak terpisah adalah kalis tukar tertib secara remeh, kerana setiap satu ialah hasil darab genap bagi operator fermion. Kes yang menarik ialah tapak yang dikongsi. Di situ, kedua-duanya kalis tukar tertib jika anak panah mengalir melalui tapak itu (satu tiba, satu pergi) dan antikalis tukar tertib jika ia berlanggar (kedua-duanya tiba, atau kedua-duanya pergi).

Peraturan ini paling mudah dilihat pada gambar. Melukis bucu sebagai nod dan setiap TjkT_{jk} sebagai anak panah daripada jj ke kk memberikan graf interaksi berarah, di sini untuk rantai empat tapak yang digunakan sepanjang panduan ini, yang membawa kedua-dua orientasi bagi setiap ikatan:

A four-site chain with both orientations of every bond drawn as arrows.

Jejaki peraturan pada rajah. Lihat T1,2T_{1,2} dan T2,3T_{2,3}, sebagai contoh. Satu anak panah tiba di tapak 2 dan satu lagi meninggalkannya, jadi fermion mengalir terus melaluinya dan kedua-dua operator kalis tukar tertib. Sebaliknya lihat T1,2T_{1,2} dan T3,2T_{3,2}. Kedua-dua anak panah menuju ke tapak 2. Ia berlanggar, dan kedua-duanya antikalis tukar tertib.

Inilah yang membolehkan set aliran. Pilih set anak panah yang membentuk laluan berarah dan setiap pasangan sama ada bertemu kepala-ke-ekor atau tidak bertemu langsung, jadi seluruh set kalis tukar tertib, walaupun operator individu bertindih pada tapak yang dikongsi. Berevolusi di bawah satu set aliran oleh itu tidak menanggung ralat Trotter, dan seperti ditunjukkan oleh langkah 7, ia juga boleh dilakukan pada kedalaman litar malar.

1. Hamiltonian Fermi-Hubbard​

Model Fermi-Hubbard 1D pada LL tapak (tanpa spin, sempadan terbuka) ialah

H=−t∑j(aj†aj+1+aj+1†aj)+U∑jnjnj+1 .H = -t \sum_{j} \left( a^\dagger_j a_{j+1} + a^\dagger_{j+1} a_j \right) + U \sum_{j} n_j n_{j+1} \, .

Mengikut Pers. (9) Rujukan [1], sebutan lompatan ialah t∑j(Tj,j+1+Tj+1,j)t \sum_j (T_{j,j+1} + T_{j+1,j}). Interaksi bersifat pepenjuru, jadi ia dinyatakan melalui operator bucu menggunakan nj=(1−Vj)/2n_j = (1 - V_j)/2:

njnj+1=14(1−Vj−Vj+1+VjVj+1) .n_j n_{j+1} = \tfrac{1}{4}\left( 1 - V_j - V_{j+1} + V_j V_{j+1} \right) \, .

Kedua-dua bahagian dibina terus sebagai TransferVertexOperator. Perhatikan bahawa VjV_j disimpan sebagai entri pepenjuru (j, j):

>>> from collections import defaultdict
>>> from qiskit_fermions.operators import TransferVertexOperator
>>>
>>> def fermi_hubbard_1d(num_sites, tunneling, interaction):
... data: defaultdict[tuple[tuple[int, int], ...], complex] = defaultdict(complex)
... for j in range(num_sites - 1):
... k = j + 1
... # hopping: -t (a^dag_j a_k + a^dag_k a_j) = t (T_jk + T_kj)
... data[((j, k),)] += tunneling
... data[((k, j),)] += tunneling
... # interaction: U n_j n_k with n_j = (1 - V_j) / 2
... data[()] += interaction / 4
... data[((j, j),)] -= interaction / 4
... data[((k, k),)] -= interaction / 4
... data[((j, j), (k, k))] += interaction / 4
... return TransferVertexOperator.from_dict(data)
>>>
>>> num_sites = 4
>>> hamiltonian = fermi_hubbard_1d(num_sites, tunneling=1.0, interaction=2.0)
>>> print(format(hamiltonian))
1.500000e0 +0.000000e0j * ()
-5.000000e-1 +0.000000e0j * (V(0))
5.000000e-1 +0.000000e0j * (V(0) V(1))
1.000000e0 +0.000000e0j * (T(0,1))
1.000000e0 +0.000000e0j * (T(1,0))
-1.000000e0 +0.000000e0j * (V(1))
5.000000e-1 +0.000000e0j * (V(1) V(2))
1.000000e0 +0.000000e0j * (T(1,2))
1.000000e0 +0.000000e0j * (T(2,1))
-1.000000e0 +0.000000e0j * (V(2))
5.000000e-1 +0.000000e0j * (V(2) V(3))
1.000000e0 +0.000000e0j * (T(2,3))
1.000000e0 +0.000000e0j * (T(3,2))
-5.000000e-1 +0.000000e0j * (V(3))

Tapak dalaman mendapat pekali −1-1 pada VjV_j kerana ia muncul dalam dua ikatan, manakala tapak sempadan hanya muncul dalam satu. Setiap sebutan yang disenaraikan di sini muncul dalam graf yang dilukis di atas. Sebutan T(j,k) ialah anak panahnya, dan sebutan V(j) ialah nodnya.

2. Bahagikan kepada set aliran​

Seterusnya, labelkan setiap sebutan dengan set aliran yang dimilikinya. Atribut groups wujud untuk tujuan ini. Gate Evolution ke atas operator yang dikumpulkan terurai kepada satu Evolution bagi setiap kumpulan (langkah 6 menggunakannya), jadi pengumpulan yang diputuskan di sini pada aras fermion kekal dalam litar.

Bagi rantai 1D, dua set aliran lompatan ialah anak panah berorientasi timur (Tj,j+1T_{j,j+1}) dan anak panah berorientasi barat (Tj+1,jT_{j+1,j}). Sebutan interaksi pepenjuru kalis tukar tertib dengan segala yang pepenjuru dan membentuk kumpulan ketiga:

>>> def flow_set_groups(operator):
... groups = []
... for terms, _ in operator.iter_terms():
... match terms:
... case [(left, right)] if right == left + 1:
... groups.append(0) # east-oriented: T_{j,j+1}
... case [(left, right)] if right == left - 1:
... groups.append(1) # west-oriented: T_{j+1,j}
... case _:
... groups.append(2) # diagonal interaction terms
... return groups
>>>
>>> hamiltonian.groups = flow_set_groups(hamiltonian)
>>> hamiltonian.num_groups()
3
>>> for index, group in enumerate(hamiltonian.split_out_groups()):
... print(index, sorted(terms for terms, _ in group.iter_terms()))
0 [[(0, 1)], [(1, 2)], [(2, 3)]]
1 [[(1, 0)], [(2, 1)], [(3, 2)]]
2 [[], [(0, 0)], [(0, 0), (1, 1)], [(1, 1)], [(1, 1), (2, 2)], [(2, 2)], [(2, 2), (3, 3)], [(3, 3)]]

Kumpulan 0 dan 1 ialah dua set aliran. Setiap set ialah laluan berarah sepanjang rantai, jadi mengikut sifat aliran semua sebutan dalam satu kumpulan kalis tukar tertib.

Setiap kumpulan juga mesti Hermitian dengan sendirinya, kerana formula hasil darab mengeksponenkan kumpulan secara berasingan dan hanya faktor Hermitian mempunyai evolusi unitari. groups_are_hermitian() mengesahkannya:

>>> from qiskit_fermions.operators.terms.grouping import groups_are_hermitian
>>>
>>> groups_are_hermitian(hamiltonian)
[True, True, True]

Itu patut diberi perhatian, kerana dua kumpulan lompatan itu sehala: kumpulan 0 hanya mengandungi sebutan berorientasi timur dan kumpulan 1 hanya yang berorientasi barat. Memisahkan Hamiltonian dengan cara itu biasanya akan memecahkan sifat Hermitian, kerana aj†aka^\dagger_j a_k memerlukan pasangan konjugatnya ak†aja^\dagger_k a_j bersama-samanya. TransferVertexOperator mengelakkan masalah ini kerana TjkT_{jk} sudah Hermitian dengan sendirinya. Ia ialah hasil darab dua operator Majorana (lihat takrifnya dalam langkah 1), bukan aj†aka^\dagger_j a_k semata-mata, jadi imej Jordan-Wigner-nya membawa pekali nyata dan bukan khayal. Memilih perwakilan operator ini oleh itu menjadikan set aliran sehala sebagai faktor Trotter yang sah.

Nota

Label timur/barat yang digunakan sepanjang panduan ini bersifat geometri, dengan rantai dilukis dari kiri ke kanan dan indeks mod meningkat ke arah itu; Tj,j+1T_{j,j+1} menghala ke timur dan Tj+1,jT_{j+1,j} menghala ke barat. Rujukan [1] tidak konsisten secara dalaman tentang penamaan ini (prosa dan rajahnya tidak sepadan) jadi panduan ini sengaja tidak mengikut prosa, sebaliknya mengekalkan label daripada gambar. Apabila merujuk silang dengan kertas itu, padankan set aliran mengikut pemberat Pauli (1 dan 3 di bawah), bukan mengikut perkataan arah mata angin.

3. Tulis pengekodan tersuai​

Bahagian IV B Rujukan [1] mengelaskan bentuk yang boleh diambil oleh pengekodan fermion-ke-qubit setempat apabila terhad kepada satu set aliran. Jordan-Wigner biasa menggunakan Nq=NfN_q = N_f qubit dan memetakan setiap sebutan lompatan kepada Pauli berpemberat 2. Pengelasan itu menunjukkan bahawa membelanjakan qubit tambahan membeli struktur dalam operator yang dikodkan.

Pengekodan yang dilaksanakan di sini ialah binaan penyetempatan-nyah pariti dalam Lampiran B 2 (persamaan B4 dan B5), yang menggunakan satu qubit bantu, jadi Nq=Nf+1N_q = N_f + 1. Pariti fermion dinyahsetempatkan merentas sepasang qubit:

Vj=ZjZj+1 ,Tj,j+1=−12Xj+1 ,Tj+1,j=+12ZjXj+1Zj+2 .V_j = Z_j Z_{j+1} \, , \qquad T_{j,j+1} = -\tfrac{1}{2} X_{j+1} \, , \qquad T_{j+1,j} = +\tfrac{1}{2} Z_j X_{j+1} Z_{j+2} \, .

Indeks j+2j+2 dalam ungkapan terakhir memaksa qubit tambahan. Ikatan terakhir rantai mempunyai j=Nf−2j = N_f - 2, jadi Zj+2Z_{j+2}-nya jatuh pada qubit NfN_f, satu melepasi mod fermion terakhir Nf−1N_f - 1. Qubit hujung itu ialah qubit bantu, dan itulah sebabnya Nq=Nf+1N_q = N_f + 1 dan bukan NfN_f. Jangkauan yang sama muncul dalam VjV_j, yang Zj+1Z_{j+1}-nya berada pada qubit bantu untuk j=Nf−1j = N_f - 1.

Faedahnya kelihatan serta-merta; satu set aliran penuh dipetakan kepada hasil tambah Pauli berpemberat 1.

Nota

Pemfaktor 12\tfrac{1}{2} ditetapkan oleh normalisasi Tjk2=14T_{jk}^2 = \tfrac{1}{4}, dan tanda tolak relatif antara dua orientasi ditetapkan oleh identiti hasil darab Tj,j+1=−VjVj+1Tj+1,jT_{j,j+1} = -V_j V_{j+1} T_{j+1,j}, yang dipenuhi oleh Pauli di atas. Tersalah salah satunya menghasilkan operator yang mematuhi hubungan kalis tukar tertib tetapi tidak lagi mewakili Hamiltonian yang sama, jadi anda harus mengesahkan pengekodan secara berangka, seperti ditunjukkan dalam langkah 4.

Menulis pengekodan bermaksud menulis fungsi yang memetakan satu operator pindahan teritlak kepada rentetan Pauli; map_transfer_vertex_generators() mengendalikan lelaran ke atas sebutan dan gubahannya:

>>> from qiskit.quantum_info import SparseObservable
>>> from qiskit_fermions.mappers import map_transfer_vertex_generators
>>>
>>> def flow_set_action(action, num_qubits):
... match action:
... case (j, k) if j == k:
... # vertex operator: V_j = Z_j Z_{j+1}
... return SparseObservable.from_sparse_list(
... [("ZZ", [j, j + 1], 1.0)], num_qubits=num_qubits
... )
... case (j, k) if k == j + 1:
... # east-oriented transfer operator: weight 1
... return SparseObservable.from_sparse_list(
... [("X", [k], -0.5)], num_qubits=num_qubits
... )
... case (j, k) if k == j - 1:
... # west-oriented transfer operator: weight 3
... return SparseObservable.from_sparse_list(
... [("ZXZ", [k, k + 1, k + 2], 0.5)], num_qubits=num_qubits
... )
... case _:
... raise ValueError(f"not a nearest-neighbor transfer operator: {action}")
>>>
>>> def flow_set_encoding(operator, num_qubits):
... return map_transfer_vertex_generators(
... operator,
... lambda action: flow_set_action(action, num_qubits),
... identity=lambda: SparseObservable.identity(num_qubits),
... compose=SparseObservable.compose,
... ).simplify()
Penting

MapperFnEvolutionSynthesis.mapper_fn menjangkakan tandatangan (operator, num_qubits), dan jenis pulangan mestilah SparseObservable, yang menjadikan fungsi itu boleh digunakan terus sebagai pemalam transpiler. Lihat langkah 6.

Menggunakannya pada Hamiltonian daripada langkah 1 memberikan operator terkod pada Nf+1=5N_f + 1 = 5 qubit:

>>> num_qubits = num_sites + 1
>>> encoded = flow_set_encoding(hamiltonian, num_qubits)
>>> for label, indices, coeff in sorted(encoded.to_sparse_list()):
... print(f"{label:3s} {str(indices):12s} {coeff.real:+.2f}")
[] +1.50
X [1] -0.50
X [2] -0.50
X [3] -0.50
ZXZ [0, 1, 2] +0.50
ZXZ [1, 2, 3] +0.50
ZXZ [2, 3, 4] +0.50
ZZ [0, 1] -0.50
ZZ [0, 2] +0.50
ZZ [1, 2] -1.00
ZZ [1, 3] +0.50
ZZ [2, 3] -1.00
ZZ [2, 4] +0.50
ZZ [3, 4] -0.50

Terdapat 14 sebutan, dalam tiga bentuk: lompatan telah menjadi X dan ZXZ, interaksi ZZ, dan [] di awal ialah identiti, yang hanya menyumbang fasa global. Perhatikan bahawa sebutan ZZ mencapai jiran terdekat dan jiran kedua terdekat; VjVj+1V_jV_{j+1} merentangi qubit jj hingga j+2j+2, iaitu kos penyahsetempatan pariti.

Pengumpulan mengikut set aliran mendedahkan ketaksimetrian yang dibeli oleh binaan ini. Senaraikan pemberat Pauli yang terdapat dalam setiap kumpulan:

>>> for index, group in enumerate(hamiltonian.split_out_groups()):
... weights = {len(indices) for _, indices, _ in flow_set_encoding(group, num_qubits).to_sparse_list()}
... print(index, sorted(weights))
0 [1]
1 [3]
2 [0, 2]

Set aliran berorientasi timur (kumpulan 0) terdiri sepenuhnya daripada Pauli berpemberat 1. Evolusi masanya oleh itu ialah satu lapisan putaran qubit tunggal dan tidak memerlukan gate berbelit, manakala di bawah Jordan-Wigner setiap sebutan lompatan berpemberat 2 dan memerlukannya. Inilah pertukaran ruang-masa Rujukan [1] dalam bentuk paling ringkas: satu qubit tambahan menukarkan separuh Hamiltonian lompatan kepada putaran qubit tunggal.

Set aliran berorientasi barat (kumpulan 1) membayarnya pada pemberat tiga, dan interaksi (kumpulan 2) berada pada pemberat dua, dengan entri pemberat sifar ialah identiti. Tiada satu pun ini lagi dakwaan tentang litar; apa yang dihasilkan oleh sintesis daripada pemberat ini ialah subjek langkah 6 dan 7.

4. Sahkan pengekodan​

Pengekodan tersuai hanya berguna jika ia setia, dan yang ditulis dengan tangan harus disemak. Oleh sebab pengekodan ini menggunakan Nf+1N_f + 1 qubit, ruang Hilbert qubit adalah dua kali ganda lebih besar daripada ruang fermion, jadi Hamiltonian terkod tidak boleh sama dengan Hamiltonian Jordan-Wigner sebutan demi sebutan. Kedua-duanya dikaitkan oleh isometri sebaliknya.

Dari mana isometri datang: Di bawah Jordan-Wigner, keadaan asas ialah rentetan penghunian; qubit jj memegang njn_j. Pengekodan ini sebaliknya menyimpan pariti longgokan. Takrifkan

b0=0 ,bj+1=n0⊕n1⊕⋯⊕nj ,b_0 = 0 \, , \qquad b_{j+1} = n_0 \oplus n_1 \oplus \dots \oplus n_j \, ,

jadi bit bj+1b_{j+1} merekodkan pariti semua penghunian sehingga dan termasuk tapak jj. Terdapat Nf+1N_f + 1 bit sedemikian bagi NfN_f penghunian, dari situlah qubit bantu datang. Penghunian dipulihkan sebagai perbezaan antara bit berjiran, nj=bj⊕bj+1n_j = b_j \oplus b_{j+1}. Tapak yang dihuni oleh itu muncul sebagai dinding domain antara dua bit bersebelahan, sebab itu ia dipanggil pengekodan dinding domain (atau Kramers-Wannier). Itu juga sebabnya Vj=ZjZj+1V_j = Z_j Z_{j+1}: hasil darab dua ZZ berjiran membaca perbezaan itu.

Menulis peta ini untuk beberapa keadaan menjadikan strukturnya jelas (perhatikan bahawa b0=0b_0 = 0 ditetapkan, jadi hanya separuh daripada keadaan qubit 2Nf+12^{N_f+1} berada dalam imej, dan oleh itu ia isometri dan bukan uniter):

>>> def domain_wall_bits(occupations):
... parity = 0
... bits = [0]
... for occupation in occupations:
... parity ^= occupation
... bits.append(parity)
... return bits
>>>
>>> for occupations in ([0, 0, 0, 0], [1, 0, 0, 0], [1, 1, 0, 0], [1, 0, 1, 0]):
... bits = domain_wall_bits(occupations)
... # little-endian: reversed() prints qubit 0 on the right, as Qiskit labels do
... print("".join(map(str, occupations)), "->", "".join(str(b) for b in reversed(bits)))
0000 -> 00000
1000 -> 11110
1100 -> 00010
1010 -> 00110

Satu fermion pada tapak 0 membalikkan setiap pariti kumulatif di atasnya, dan inilah penyahsetempatan yang berlaku. Mengumpulkan lajur-lajur ini ke dalam matriks menghasilkan isometri WW, dan pengekodan itu setia apabila ia menjalin kedua-dua Hamiltonian:

H^flow set W=W H^JW .\hat{H}_{\text{flow set}} \, W = W \, \hat{H}_{\text{JW}} \, .

Iaitu, memetakan keadaan fermion ke dalam ruang qubit dan kemudian mengembangkannya di sana mesti memberikan keputusan yang sama seperti mengembangkannya dahulu dalam ruang fermion dan memetakannya selepas itu.

>>> import numpy as np
>>> from qiskit.quantum_info import SparsePauliOp
>>> from qiskit_fermions.mappers.library import transfer_vertex_jordan_wigner
>>>
>>> def to_matrix(observable):
... # SparseObservable carries no dense-matrix method, so route through
... # SparsePauliOp. This is only ever needed for the checks below --
... # the encoding itself never leaves SparseObservable.
... return SparsePauliOp.from_sparse_observable(observable).to_matrix()
>>>
>>> def domain_wall_isometry(num_sites):
... isometry = np.zeros((2 ** (num_sites + 1), 2**num_sites), dtype=complex)
... for index in range(2**num_sites):
... occupations = [(index >> j) & 1 for j in range(num_sites)]
... bits = domain_wall_bits(occupations)
... isometry[sum(b << j for j, b in enumerate(bits)), index] = 1.0
... return isometry
>>>
>>> isometry = domain_wall_isometry(num_sites)
>>> np.allclose(isometry.conj().T @ isometry, np.eye(2**num_sites))
True
>>>
>>> def intertwines(operator):
... """Whether both mappings of `operator` agree through the isometry."""
... flow = to_matrix(flow_set_encoding(operator, num_qubits))
... reference = to_matrix(transfer_vertex_jordan_wigner(operator, num_sites).simplify())
... return np.allclose(flow @ isometry, isometry @ reference)
>>>
>>> intertwines(hamiltonian)
True
>>> # and, more strongly, once per flow set: a product formula evolves the groups
>>> # separately, so each one has to be intertwined on its own
>>> all(intertwines(group) for group in hamiltonian.split_out_groups())
True

Hubungan penjalinan ini berlaku dengan tepat, dan secara berasingan bagi setiap set aliran. Kenyataan kedua yang lebih kuat itulah yang bergantung padanya langkah-langkah seterusnya: formula hasil darab mengembangkan kumpulan satu demi satu, dan hasil darab faktor yang terjalin itu sendiri terjalin, jadi sebarang penghampiran yang dibina daripada kumpulan ini adalah sepadan antara kedua-dua pengekodan walaupun pengekodan itu berbeza. Sebagai akibatnya, spektrum mesti sepadan, dengan setiap nilai eigen Jordan-Wigner muncul dua kali dalam ruang yang lebih besar, sekali bagi setiap nilai pariti global yang tidak dikekang:

>>> jw_matrix = to_matrix(transfer_vertex_jordan_wigner(hamiltonian, num_sites).simplify())
>>> flow_matrix = to_matrix(flow_set_encoding(hamiltonian, num_qubits))
>>>
>>> jw_spectrum = np.linalg.eigvalsh(jw_matrix)
>>> flow_spectrum = np.linalg.eigvalsh(flow_matrix)
>>> np.allclose(
... np.sort(flow_spectrum),
... np.sort(np.concatenate([jw_spectrum, jw_spectrum])),
... )
True
>>> print(f"{jw_spectrum[0]:.10f} {flow_spectrum[0]:.10f}")
-1.8557725066 -1.8557725066

5. Bina litar fermion​

Sebelum melibatkan sebarang qubit, dinamik dinyatakan pada peringkat fermion. FermionicCircuit bertindak ke atas mod fermion dan bukan qubit, dan gate Evolution ke atas Hamiltonian ialah keseluruhan litar:

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import FermionicLieTrotter
>>>
>>> total_time = 1.0
>>> circuit = FermionicCircuit(num_sites)
>>> circuit.append(
... Evolution(
... num_sites, hamiltonian, time=total_time, synthesis=FermionicLieTrotter()
... ),
... circuit.modes,
... )
>>> circuit.draw("mpl")
<Figure size ... with 1 Axes>

A single Evolution gate spanning all four fermionic modes.

Terdapat satu kotak legap merentasi keempat-empat mod, dan bahagian yang menarik ialah apa yang diuraikannya. Argumen synthesis menyatakan caranya: FermionicLieTrotter ialah formula hasil darab peringkat pertama, yang memecahkan operator mengikut groups dan mengembangkan setiap kumpulan secara bergilir. Jadi pembahagian set aliran daripada langkah 2 ialah penghampiran Trotter, dan satu decompose() melaksanakannya, menghasilkan satu Evolution bagi setiap set aliran mengikut tertib kumpulan: timur, barat, kemudian interaksi pepenjuru, masing-masing membawa 3, 3, dan 8 sebutan.

>>> flow_set_circuit = circuit.decompose()
>>> flow_set_circuit.draw("mpl")
<Figure size ... with 1 Axes>

The same circuit decomposed into three Evolution gates, one per flow set.

Perhatikan bahawa ini berlaku sepenuhnya dalam ruang fermion: tiada qubit yang disebut lagi, dan penghampiran sudah pun dibuat. Oleh itu bilangan langkah Trotter juga tergolong di sini, sebagai reps, yang membahagikan masa dan mengulang keseluruhan sapuan:

>>> def trotter_steps(reps):
... step = FermionicCircuit(num_sites)
... step.append(
... Evolution(
... num_sites,
... hamiltonian,
... time=total_time,
... synthesis=FermionicLieTrotter(reps=reps),
... ),
... step.modes,
... )
... return step.decompose()
>>>
>>> trotter_steps(3).count_ops()["Evolution"]
9

Sembilan gate: tiga set aliran, dikitar tiga kali pada sepertiga masa setiap satu. Kitaran itulah yang menjadikannya penghampiran Trotter dan bukan sekadar pembahagian semula satu kumpulan. Mengulang satu kumpulan tunggal tidak mencapai apa-apa, kerana eksponen berturut-turut bagi operator yang sama saling kalis tukar tertib, jadi A(t/3) A(t/3) A(t/3) hanyalah A(t). Formula yang membaca kumpulan boleh menyisipkannya; formula yang dikenakan selepas pemetaan tidak boleh, kerana pengelompokan sudah hilang pada ketika itu.

Inilah langkah yang menjadikan pengelompokan penting. Setiap gate yang dikeluarkan dipetakan dan disintesis secara bebas, jadi segala-galanya selepas ini bertindak berdasarkan pembahagian yang diputuskan pada peringkat fermion. Melangkau penguraian menyerahkan keseluruhan operator kepada peringkat seterusnya, dan pengelompokan diabaikan.

6. Transpile dengan pengekodan tersuai​

Pengekodan kini sedia digunakan oleh transpiler. Dua komponen lagi diperlukan:

  • Susun atur. TrivialF2QLayout lalai mengandaikan satu qubit bagi setiap mod, yang salah dalam keadaan ini. CustomF2QLayout mengaitkan daftar fermion dengan QuantumRegister yang bersaiz berbeza, dan beginilah qubit ancilla memasuki saluran paip.

  • Pemalam sintesis. MapperFnEvolutionSynthesis menerima fungsi pemeta secara langsung, memetakan Hamiltonian gate Evolution dengannya, dan mengeluarkan PauliEvolutionGate, sambil mengekalkan konvensyen e−itHe^{-itH}. product_formula menentukan bagaimana gate itu ditukar kepada putaran. Perhatikan bahawa ini ialah formula hasil darab kedua yang bebas, bertindak ke atas qubit selepas pemetaan: LieTrotter Qiskit dihantar di sini, dan langkah 7 menggantikannya dengan yang mengetahui tentang set aliran.

Memandangkan setiap perbandingan di bawah menjalankan semula saluran paip ini, wajar ia dibungkus sekali sahaja:

>>> from qiskit.circuit import QuantumRegister
>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit_fermions.transpiler import FermionicCircuitToDAG, QuantumDAGToCircuit
>>> from qiskit_fermions.transpiler.passes import (
... CustomF2QLayout,
... F2QSynthesis,
... MapperFnEvolutionSynthesis,
... )
>>>
>>> def flow_set_pass_manager(mode_register, product_formula=None):
... '''Build the pipeline mapping `mode_register` with the flow-set encoding.'''
... synthesis = F2QSynthesis()
... synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(
... flow_set_encoding, product_formula=product_formula
... )
... # this encoding spends one ancilla on top of the fermionic modes
... qubit_register = QuantumRegister(mode_register.size + 1, "q")
... return MultiStagePassManager(
... input=FermionicCircuitToDAG(),
... layout=CustomF2QLayout({mode_register: qubit_register}),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>>
>>> from qiskit.synthesis import LieTrotter
>>>
>>> pass_manager = flow_set_pass_manager(flow_set_circuit.register, LieTrotter())
>>> qubit_circuit = pass_manager.run(flow_set_circuit)
>>> qubit_circuit.num_qubits
5

Empat mod fermion telah menjadi litar lima qubit, dengan ancilla dibekalkan oleh susun atur tersuai.

Sekarang lihat apa yang sebenarnya terkandung dalam litar yang terhasil. Penguraian berulang mengurangkan gate evolusi sehingga ke U dan CX, supaya kos dua-qubit bagi dua strategi sintesis yang dibandingkan di bawah diukur dalam unit yang sama: count_ops() kemudian melaporkan bilangan CX, dan depth() menerima fungsi penapis untuk mengukur kedalaman dua-qubit. Perhatikan bahawa reps ini ialah kedalaman penguraian, tidak berkaitan dengan bilangan langkah Trotter yang diambil oleh formula hasil darab, yang juga dipanggil reps kemudian dalam panduan ini:

>>> decomposed = qubit_circuit.decompose(reps=6)
>>> decomposed.count_ops()["cx"]
26
>>> decomposed.depth(lambda instruction: len(instruction.qubits) == 2)
23

Dua puluh enam gate CX dalam dua puluh tiga lapisan, jadi hampir tiada apa yang berjalan selari. Melukis litar menunjukkan sebabnya:

>>> decomposed.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

The transpiled circuit under LieTrotter&#39;s term-by-term synthesis, a long serial chain of CX ladders.

Setiap sebutan telah dikompil secara terasing: penukaran asas, tangga CX sehingga satu putaran, kemudian tangga itu dibatalkan. Set aliran berat-1 percuma; sebutannya ialah putaran tunggal tanpa tangga di sekelilingnya, tetapi setiap sebutan berat-3 menanggung empat gate CX, dan kerana tangga dikeluarkan secara berjujukan, tiada apa yang bertindih.

Jadi pengelompokan bertahan sepanjang saluran paip (tiga set aliran, tiga PauliEvolutionGate yang dipetakan) namun masih hampir tidak memberi apa-apa faedah. Penghampiran Trotter fermion pada langkah 5 telah menjalankan tugasnya; yang membazirkannya ialah formula di sebelah qubit. LieTrotter menerima satu set sebutan yang kebetulan tidak diberitahu apa-apa tentangnya, dan mengkompil setiap satu secara terasing. Ia tidak dapat mengetahui bahawa semuanya saling kalis tukar tertib, jadi ia menyirikan apa yang boleh berkongsi satu penukaran asas.

Itulah jurang yang wujud pengekodan ini untuk menutupnya: dua formula hasil darab sedang berfungsi, dan setakat ini hanya yang fermion telah diberi struktur itu. Langkah seterusnya membekalkan sintesis di sebelah qubit yang turut memanfaatkannya.

7. Sintesis yang peka set aliran​

Memulihkan kelebihan bermakna memberitahu sintesis di sebelah qubit tentang struktur itu. Ini ialah Bahagian IV C Rujukan [1]: kerana semua sebutan bagi satu set aliran saling kalis tukar tertib, ia boleh dipepenjurukan serentak oleh satu litar Clifford, diputar dalam satu asas bersama dan diputar kembali, dan bukannya sekali bagi setiap sebutan.

Bagi pengekodan ini, Clifford itu mudah. Mengkonjugat dengan rantai gate CZ pada jiran terdekat memetakan

Xj+1  ⟼  ZjXj+1Zj+2X_{j+1} \; \longmapsto \; Z_j X_{j+1} Z_{j+2}

serentak untuk setiap jj. Satu rantai CZ menukarkan keseluruhan set aliran berat-3 (barat) kepada satu lapisan putaran XX qubit tunggal yang bebas. Antara muka EvolutionSynthesis hanya meminta kaedah synthesize(), dan itu sahaja yang dilaksanakan oleh kelas ini. Ia tidak membawa bilangan langkah Trotter, kerana ia menerima satu set aliran pada satu masa dan oleh itu tidak dapat membahagikan evolusi merentasi set-set itu. Pembahagian itu terletak satu peringkat di atas, dalam ruang fermion, tempat F2FSynthesisPlugin melihat kesemuanya sekaligus (lihat langkah 9):

>>> from qiskit.circuit import QuantumCircuit
>>> from qiskit.synthesis.evolution import EvolutionSynthesis
>>>
>>> def cz_brickwork(circuit):
... # emit even and odd bonds as two layers, so the CZs are scheduled in
... # parallel: a chain of overlapping CZs has depth 2, not num_qubits - 1
... #
... # NOTE: CZ gates are diagonal, so they all commute and each is its own inverse.
... # This whole layer is therefore self-inverse, which is why the conjugation below
... # calls it twice rather than calling it and then its inverse.
... for offset in (0, 1):
... for j in range(offset, circuit.num_qubits - 1, 2):
... circuit.cz(j, j + 1)
>>>
>>> # The interaction reaches nearest (distance 1) and next-nearest (distance 2)
>>> # neighbors, so every interior qubit carries four Rzz gates and can only do one at
>>> # a time: four layers is the best any schedule can do. These offsets achieve it.
>>> def rzz_layers(circuit, angles):
... """Emit the diagonal Rzz gates in four layers of disjoint qubit pairs."""
... near = [(j, k) for j, k in angles if k - j == 1]
... far = [(j, k) for j, k in angles if k - j == 2]
... schedule = [
... [(j, k) for j, k in near if j % 2 == 0],
... [(j, k) for j, k in far if j % 4 in (0, 1)],
... [(j, k) for j, k in near if j % 2 == 1],
... [(j, k) for j, k in far if j % 4 in (2, 3)],
... ]
... # emit layer by layer, since Qiskit's depth follows the order gates were added
... for layer in schedule:
... for j, k in layer:
... circuit.rzz(angles[j, k], j, k)
>>>
>>> class FlowSetSynthesis(EvolutionSynthesis):
... '''Synthesize one flow set of the encoded Hamiltonian at a time.'''
...
... preserve_order = True
...
... def synthesize(self, evolution):
... circuit = QuantumCircuit(evolution.operator.num_qubits)
... east, west, diagonal = [], [], {}
...
... for label, indices, coeff in evolution.operator.to_sparse_list():
... angle = 2 * evolution.time * coeff.real
... match label, indices:
... case "", _: # the identity contributes only a global phase
... circuit.global_phase -= angle / 2
... case "X", [qubit]: # east flow set: already weight-1
... east.append((qubit, angle))
... case "ZXZ", [j, k, l] if (k, l) == (j + 1, j + 2):
... west.append((k, angle)) # needs the CZ conjugation below
... case "ZZ", [j, k]: # interaction: diagonal, see rzz_layers above
... diagonal[min(j, k), max(j, k)] = angle
... case _:
... raise ValueError(f"unexpected Pauli term: {label} on {indices}")
...
... # east flow set: bare rotations, no entangling gates at all
... for qubit, angle in east:
... circuit.rx(angle, qubit)
...
... # west flow set: one CZ chain diagonalizes the WHOLE set at once
... if west:
... cz_brickwork(circuit)
... for qubit, angle in west:
... circuit.rx(angle, qubit)
... cz_brickwork(circuit)
...
... if diagonal:
... rzz_layers(circuit, diagonal)
...
... return circuit
Nota

Dua perincian mudah terlepas pandang. EvolutionSynthesis tersuai mesti mendedahkan atribut preserve_order, yang dibaca oleh pas sintesis aras tinggi.

Kedua-dua pembantu mengeluarkan gate mereka dalam lapisan eksplisit dan bukan mengikut tertib sebutan, kerana depth() menjadualkan gate secepat mungkin, mengikut tertib ia ditambah. Memandangkan semua gate dalam satu set ini saling kalis tukar tertib, tertibnya bebas dipilih. Sintesis sebutan demi sebutan memilih tertib dengan buruk. Mengeluarkan rantai CZ yang bertindih secara berjujukan melaporkan kedalaman yang bertambah dengan bilangan qubit dan bukannya 2 malar yang dapat dicapai oleh perkakasan.

Nota

Inilah sebabnya sintesis di atas tidak membawa bilangan langkah Trotter. Ia menerima satu set aliran bagi setiap gate, jadi pengulangan di dalamnya akan mengulang dalam satu set dan bukan berkitar antara set-set itu:

repeating within one set: A(t/2) A(t/2) B(t/2) B(t/2)
what a Trotterization needs: A(t/2) B(t/2) A(t/2) B(t/2)

Bentuk pertama tidak memberi apa-apa faedah: eksponen berturut-turut bagi operator yang sama saling kalis tukar tertib, jadi A(t/2) A(t/2) hanyalah A(t) dan ralat kekal pada nilai satu langkahnya walau berapa banyak langkah diminta.

Oleh itu bilangan langkah tergolong pada FermionicLieTrotter fermion dalam langkah 5, yang membaca groups dan boleh menyisipkannya. Pembahagian tugas itulah intinya: formula fermion memutuskan berapa banyak langkah dan dalam tertib apa, dan kelas ini memutuskan betapa murahnya setiap set yang terhasil direalisasikan.

Untuk memilih tertib, hantarkannya kepada MapperFnEvolutionSynthesis.product_formula, iaitu argumen yang diterima oleh flow_set_pass_manager. Tiada apa-apa lagi tentang saluran paip (atau litar fermion yang dijalankannya) berubah:

>>> pass_manager = flow_set_pass_manager(flow_set_circuit.register, FlowSetSynthesis())
>>>
>>> flow_circuit = pass_manager.run(flow_set_circuit).decompose(reps=6)
>>> flow_circuit.count_ops()["cx"]
22
>>> flow_circuit.depth(lambda instruction: len(instruction.qubits) == 2)
12
>>> flow_circuit.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

The flow-set-aware circuit, markedly shorter than the previous one, with entangling gates stacked into parallel layers.

Daripada kedalaman 23 dengan 26 gate CX kepada kedalaman 12 dengan 22 gate CX. Perhatikan bahawa ini ialah gate berlilit yang lebih sedikit dan hampir separuh kedalaman. Membandingkan kedua-dua gambar, perbezaannya bukan kerana gate lebih murah tetapi kerana ia bertindan. Tangga bersiri telah hilang; gate berlilit kini berada di atas satu sama lain dalam lapisan bersama kerana keseluruhan set aliran barat diputar dalam satu asas bersama dan interaksi dikeluarkan dalam lapisan pasangan yang tidak bersentuhan.

Nota

FlowSetSynthesis memadankan corak indeks qubit mutlak, yang selamat di sini hanya kerana setiap set aliran Hamiltonian ini merentasi keseluruhan daftar. Formula hasil darab fermion mengecilkan setiap faktor kepada mod yang benar-benar disentuhnya, jadi pengelompokan yang set-setnya hanya meliputi sebahagian rantai akan tiba dengan label baharu dan corak di atas tidak akan sepadan.

Nota

Kedua-dua lukisan menunjukkan litar selepas decompose(reps=6), yang menulis semula segala-galanya ke dalam asas U dan CX, jadi gate CZ, Rx, dan Rzz yang dikeluarkan oleh FlowSetSynthesis tidak kelihatan sebagai demikian. Ini disengajakan: ia meletakkan kedua-dua litar dalam asas yang sama, satu-satunya cara bilangan dan kedalaman dua-qubit di atas boleh dibandingkan. Yang ketara dalam rajah ini ialah struktur lapisan, bukan label gate.

8. Kedalaman malar pada skala besar​

Soalan yang menarik bukanlah bilangan pada empat mod tetapi bagaimana ia berskala. Dua lapisan batu-bata CZ tidak bertambah dengan panjang rantai, jadi kedalaman dua-qubit bagi satu langkah Trotter sepatutnya malar. Mengukurnya secara langsung pada Hamiltonian Fermi-Hubbard penuh, jauh melepasi apa yang boleh disimulasikan dengan daya kasar:

>>> def evolution_stats(num_sites, product_formula):
... operator = fermi_hubbard_1d(num_sites, tunneling=1.0, interaction=2.0)
... operator.groups = flow_set_groups(operator)
...
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(
... num_sites, operator, time=total_time, synthesis=FermionicLieTrotter()
... ),
... circuit.modes,
... )
... circuit = circuit.decompose() # one Evolution per flow set, as in step 5
...
... pass_manager = flow_set_pass_manager(circuit.register, product_formula)
... decomposed = pass_manager.run(circuit).decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... return decomposed.count_ops()["cx"], decomposed.depth(two_qubit)
>>>
>>> sites = [4, 10, 20, 50, 100]
>>> flow = [evolution_stats(n, FlowSetSynthesis()) for n in sites]
>>> lie_trotter = [evolution_stats(n, LieTrotter()) for n in sites]
>>>
>>> [depth for _, depth in flow]
[12, 12, 12, 12, 12]
>>> [depth for _, depth in lie_trotter]
[23, 47, 87, 207, 407]

Kedalaman dua-qubit kekal pada 12 dari empat mod hingga 100, manakala LieTrotter bertambah secara linear kepada 407. Set aliran membolehkannya menyelari sedikit dalam saluran paip, tetapi setiap set masih dihampiri Trotter sebutan demi sebutan, jadi pertumbuhan itu hanya diperlahankan, bukan dihapuskan:

>>> import matplotlib.pyplot as plt
>>>
>>> figure, axes = plt.subplots(1, 2, figsize=(9, 3.5), layout="constrained")
>>> for panel, index, title in zip(axes, (1, 0), ("two-qubit depth", "CX count")):
... _ = panel.plot(sites, [stat[index] for stat in lie_trotter], "o-", label="qubit-level")
... _ = panel.plot(sites, [stat[index] for stat in flow], "s-", label="flow set")
... _ = panel.set(xlabel="modes", title=title)
... _ = panel.legend()
>>> figure
<Figure size ... with 2 Axes>

Two-qubit depth against number of modes; the qubit-level LieTrotter grows linearly while the flow-set synthesis stays flat at 12.

Bilangan gate bertambah secara linear di bawah kedua-duanya (terdapat O(N)O(N) sebutan untuk dikenakan, dan itu tidak dapat dielakkan), tetapi sintesis set aliran mengenakannya dalam bilangan lapisan selari yang malar. Ini ialah keputusan kedalaman malar Rujukan [1].

Nota

Kedalaman 12 terbahagi merentasi tiga set aliran sebagai 0 + 4 + 8, pada setiap panjang rantai. Set aliran timur menyumbang sifar; ia hanyalah putaran Rx semata-mata. Set aliran barat berharga 4, daripada dua lapisan batu-bata CZ yang masuk dan dua yang keluar; menggugurkan interaksi meninggalkan kedalaman dua-qubit empat pada sebarang panjang rantai. Baki lapan ialah interaksi yang sebutan Rzznya bertindak ke atas jiran terdekat dan jiran terdekat kedua, justeru memerlukan empat lapisan tidak bersentuhan, setiap satu berharga dua gate CX. Empat adalah optimum di sini: setiap qubit dalaman membawa empat gate Rzz (dua kepada setiap jiran dan dua kepada setiap jiran terdekat kedua) dan hanya boleh mengambil bahagian dalam satu pada satu masa, jadi tiada jadual yang boleh lebih baik.

Dua pertiga daripada kedalaman ialah interaksi, yang menunjukkan bahawa pengekodan ini cemerlang pada lompatan, dan seterusnya pada dinamik rantai fermion bebas. Namun, perhatikan bahawa interaksi tidak kalis tukar tertib dengan lompatan. Mengabaikannya bukan penghampiran kepada Fermi-Hubbard tetapi model yang berbeza. Di mana keseimbangan terletak bagi sesuatu masalah wajar diterokai.

9. Semak dinamik​

Di bawah peta domain-wall, corak penghunian menjadi rentetan pariti kumulatif, jadi keadaan awal disediakan secara berbeza daripada di bawah Jordan-Wigner. Ini ialah akibat praktikal yang mudah terlepas pandang daripada penyahsetempatan pariti. Pembantu daripada langkah 4 memberikan label yang betul:

>>> occupations = [1, 0, 1, 0]
>>> initial_label = "".join(str(b) for b in reversed(domain_wall_bits(occupations)))
>>> initial_label
'00110'

Dengan keadaan telah disediakan, dua soalan kekal, dan yang terbaik ialah ditanya secara berasingan: adakah sintesis set aliran mengira perkara yang betul, dan adakah ia mengiranya dengan lebih murah?

Ambil ketepatan dahulu. FlowSetSynthesis dan LieTrotter Qiskit menerima set aliran terkod yang sama daripada penghampiran Trotter fermion yang sama, jadi mereka mesti merealisasikan uniter yang sama sehingga fasa global. Clifford tersuai menyusun semula kerja; ia tidak mengubah keputusan. Vektor keadaan rawak mengesan sebarang percanggahan dengan jauh lebih murah daripada membina uniter penuh:

>>> from qiskit.quantum_info import Statevector
>>>
>>> rng = np.random.default_rng(1234)
>>>
>>> def agrees_up_to_one_phase(reps, samples=3):
... """Compare both qubit-side syntheses of the same fermionic Trotterization."""
... step = trotter_steps(reps)
... trotter = flow_set_pass_manager(step.register, LieTrotter()).run(step)
... flow = flow_set_pass_manager(step.register, FlowSetSynthesis()).run(step)
... trotter, flow = trotter.decompose(reps=6), flow.decompose(reps=6)
... dimension = 2**trotter.num_qubits
... residuals, phases = [], []
... for _ in range(samples):
... vector = rng.normal(size=dimension) + 1j * rng.normal(size=dimension)
... state = Statevector(vector / np.linalg.norm(vector))
... a = state.evolve(trotter).data
... b = state.evolve(flow).data
... overlap = np.vdot(a, b)
... phases.append(overlap / abs(overlap))
... residuals.append(np.linalg.norm(b - a * overlap / abs(overlap)))
... # the SAME global phase must work for every input state
... spread = max(abs(phase - phases[0]) for phase in phases)
... return bool(max(residuals) < 1e-9), bool(spread < 1e-9)
>>>
>>> [agrees_up_to_one_phase(reps) for reps in (1, 3)]
[(True, True), (True, True)]
Nota

Kedua-dua bahagian semakan itu penting. Pertindihan modulus-unit pada satu keadaan adalah perlu tetapi tidak mencukupi: sintesis yang tersilap fasa bergantung keadaan masih akan menunjukkannya. Memerlukan fasa yang sama merentasi beberapa keadaan rawak ialah yang menutup jurang itu. Menyemak lebih daripada satu reps juga penting, kerana ia mengesahkan persetujuan itu bertahan melalui penyisipan set aliran dan bukan hanya untuk satu sapuan.

Jadi pilihan sintesis di sebelah qubit tidak memberi kesan kepada ketepatan, dan itulah yang menjadikan perbandingan kos bermakna. Soalan yang tinggal ialah apa yang diperoleh pengekodan secara keseluruhan berbanding alternatif yang jelas: Jordan-Wigner biasa pada num_sites qubit, tanpa ancilla dan tanpa Clifford tersuai. Pengekodan dan sintesis di sebelah qubit kedua-duanya berbeza antara dua lajur, tetapi semakan di atas menunjukkan yang kemudian tidak boleh menjejaskan keputusan, jadi apa yang diuji oleh kesetiaan ialah pengekodan. Kedua-dua saluran paip juga bermula daripada penghampiran Trotter fermion yang sama, jadi sebarang perbezaan dalam kesetiaan akan menunjukkan pepijat dan bukan pertukaran. Kedua-dua lajur ialah pengekodan, set aliran berbanding Jordan-Wigner:

>>> def jordan_wigner_pass_manager(mode_register, product_formula):
... """The same pipeline under plain Jordan-Wigner: one qubit per mode."""
... synthesis = F2QSynthesis()
... synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(
... transfer_vertex_jordan_wigner, product_formula=product_formula
... )
... qubit_register = QuantumRegister(mode_register.size, "q")
... return MultiStagePassManager(
... input=FermionicCircuitToDAG(),
... layout=CustomF2QLayout({mode_register: qubit_register}),
... synthesis=synthesis,
... output=QuantumDAGToCircuit(),
... )
>>>
>>> from scipy.linalg import expm
>>>
>>> jw_state = np.zeros(2**num_sites, dtype=complex)
>>> jw_state[sum(o << j for j, o in enumerate(occupations))] = 1.0
>>> reference_jw = expm(-1j * total_time * jw_matrix) @ jw_state
>>> reference_flow = expm(-1j * total_time * flow_matrix) @ (isometry @ jw_state)
>>>
>>> def cost_and_fidelity(reps):
... step = trotter_steps(reps)
... flow = flow_set_pass_manager(step.register, FlowSetSynthesis()).run(step)
... jw = jordan_wigner_pass_manager(step.register, LieTrotter()).run(step)
... flow, jw = flow.decompose(reps=6), jw.decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... evolved_flow = Statevector.from_label(initial_label).evolve(flow).data
... evolved_jw = Statevector(jw_state).evolve(jw).data
... return (
... abs(np.vdot(evolved_flow, reference_flow)) ** 2,
... flow.count_ops()["cx"],
... flow.depth(two_qubit),
... abs(np.vdot(evolved_jw, reference_jw)) ** 2,
... jw.count_ops()["cx"],
... jw.depth(two_qubit),
... )
>>>
>>> for reps in (1, 2, 4, 8):
... flow_f, flow_cx, flow_d, jw_f, jw_cx, jw_d = cost_and_fidelity(reps)
... print(
... f"{reps} step(s): flow set {flow_f:.6f}"
... f" ({flow_cx:3d} CX, depth {flow_d:3d})"
... f" JW {jw_f:.6f} ({jw_cx:3d} CX, depth {jw_d:3d})"
... )
1 step(s): flow set 0.590661 ( 22 CX, depth 12) JW 0.590661 ( 18 CX, depth 14)
2 step(s): flow set 0.926656 ( 44 CX, depth 24) JW 0.926656 ( 36 CX, depth 26)
4 step(s): flow set 0.982053 ( 88 CX, depth 48) JW 0.982053 ( 72 CX, depth 50)
8 step(s): flow set 0.995395 (176 CX, depth 96) JW 0.995395 (144 CX, depth 98)

Baca lajur kesetiaan dahulu: serupa, sehingga setiap digit yang dicetak, dalam dua ruang Hilbert yang berbeza di bawah dua pengekodan yang berbeza. Itulah penghampiran Trotter fermion yang kelihatan. Penghampiran telah ditetapkan pada langkah 5, sebelum mana-mana pengekodan dipilih, jadi kedua-dua litar membawa ralat formula peringkat pertama yang sama tepat merentasi tiga set aliran, dan ia jatuh kira-kira empat kali ganda bagi setiap penggandaan bilangan langkah.

Apa yang berbeza antara pengekodan ialah kos, dan pertukaran itu wajar dinyatakan dengan jelas. Set aliran membelanjakan gate berlilit lebih banyak: 22 berbanding 18 pada satu langkah. Ancilla bukan puncanya, kerana ia tidak membawa gate dua-qubit sendiri. Puncanya ialah interaksi, tepat seperti yang diperingatkan oleh langkah 3: penyahsetempatan pariti menukarkan tiga sebutan ZZ jiran terdekat kepada tujuh yang merentasi jiran terdekat dan jiran terdekat kedua, dan setiap satu berharga dua gate CX. Itulah harga yang dibayar untuk menjadikan lompatan murah.

Ia juga menang hanya tipis pada kedalaman pada saiz ini (12 berbanding 14), kerana empat mod terlalu kecil untuk asimptot kelihatan. Intinya ialah penskalaan yang diukur dalam langkah 8: kedalaman 12 itu malar, manakala kedalaman Jordan-Wigner bertambah dengan rantai. Pada 14 mod ia sudah 12 berbanding 34.

Amaran

Panggilan decompose() di atas adalah penting, atas dua sebab yang bebas.

evolve() dan Operator menggunakan takrifan tepat PauliEvolutionGate, memintas sintesis tersuai di sebelah qubit. Mengesahkan terhadap litar yang tidak diuraikan melaporkan kesetiaan sempurna walaupun bagi sintesis yang sengaja disalahkan.

Perkara yang sama terpakai satu peringkat di atas, kepada sintesis fermion. Gate Evolution yang tidak pernah diuraikan pergi ke peringkat fermion-ke-qubit secara keseluruhan, yang memetakannya tanpa merujuk Evolution.synthesis sama sekali, jadi reps secara senyap tidak akan mempunyai kesan. trotter_steps daripada langkah 5 melaksanakan penguraian itu; saluran paip yang menetapkan kaedah dengan F2FSynthesis mendapat pengembangan yang sama daripada pas itu.

Sebagai semakan silang akhir, ketumpatan tapak ⟨nj(t)⟩=(1−⟨Vj⟩)/2\langle n_j(t) \rangle = (1 - \langle V_j \rangle)/2 yang dikira dalam ruang terkod mesti menghasilkan semula keputusan Jordan-Wigner. Semakan ini mengenai pengekodan, bukan litar, jadi tiada penghampiran Trotter terlibat. Kedua-dua evolusi masa dikira dengan tepat, melalui eksponen matriks bagi Hamiltonian masing-masing. Oleh itu sebarang percanggahan akan menunjuk kepada pengekodan dan bukan formula hasil darab:

>>> time = 0.8
>>> state_jw = np.zeros(2**num_sites, dtype=complex)
>>> state_jw[sum(o << j for j, o in enumerate(occupations))] = 1.0
>>> evolved_jw = expm(-1j * time * jw_matrix) @ state_jw
>>> evolved_flow = expm(-1j * time * flow_matrix) @ (isometry @ state_jw)
>>>
>>> for j in range(num_sites):
... v_flow = to_matrix(
... SparseObservable.from_sparse_list([("ZZ", [j, j + 1], 1.0)], num_qubits)
... )
... v_jw = to_matrix(SparseObservable.from_sparse_list([("Z", [j], 1.0)], num_sites))
... density_flow = (1 - np.vdot(evolved_flow, v_flow @ evolved_flow).real) / 2
... density_jw = (1 - np.vdot(evolved_jw, v_jw @ evolved_jw).real) / 2
... print(f"site {j}: {density_flow:.10f} {density_jw:.10f}")
site 0: 0.6507935456 0.6507935456
site 1: 0.5963201678 0.5963201678
site 2: 0.3365016619 0.3365016619
site 3: 0.4163846247 0.4163846247

Ketumpatan bersetuju sehingga sepuluh tempat perpuluhan.

Apa yang perlu diingat​

  • Kelompokkan sebelum memetakan. Set aliran ditakrifkan pada graf interaksi fermion berarah, jadi pengelompokan dipilih semasa struktur masalah masih tersedia. Atribut groups membawanya ke dalam litar, di mana satu decompose() menukarkannya kepada satu gate Evolution bagi setiap set aliran.

  • Ancilla membeli struktur. Membelanjakan satu qubit tambahan menukarkan keseluruhan set aliran kepada Pauli berat-1, yang evolusinya tidak memerlukan gate berlilit. Rujukan [1] memerhatikan pertukaran ini secara umum. Nisbah qubit-kepada-fermion yang lebih besar membolehkan litar evolusi yang lebih cetek.

  • Pengekodan tersuai ialah satu fungsi. Petakan satu operator pindahan teritlak kepada rentetan Pauli; map_transfer_vertex_generators() dan MapperFnEvolutionSynthesis melakukan selebihnya.

  • Pemetaan dan pengelompokan sahaja tidak mencukupi. Memecahkan evolusi bagi setiap set aliran hanya memperlahankan pertumbuhan linear dalam kedalaman; sintesis sebutan demi sebutan bagi setiap set masih mengabaikan hakikat bahawa sebutannya saling kalis tukar tertib. Hanya membekalkan EvolutionSynthesis yang peka set aliran menjadikan kedalaman malar. Pengekodan tersuai, pengelompokan, dan sintesis tersuai saling melengkapi: ketiga-tiganya diperlukan.

  • Dua formula hasil darab, dua tugas. FermionicLieTrotter menetapkan penghampiran dalam ruang fermion, tempat pengelompokan masih kelihatan, jadi ia sahaja menentukan ralat Trotter dan bilangan langkah. EvolutionSynthesis di sebelah qubit kemudian hanya memutuskan betapa murahnya setiap set direalisasikan: langkah 9 mengesahkan kedua-dua pilihan memberikan uniter yang sama. Memisahkan kedua-duanya membolehkan ketepatan dan kos ditala secara bebas. Lihat Sintesiskan gate Evolution dalam ruang fermion.

  • Sahkannya. Pengekodan yang memenuhi hubungan komutasi mungkin masih mewakili Hamiltonian yang berbeza jika pemfaktor atau tanda tersilap. Menyemak hubungan penjalinan (atau, lebih murah, spektrum) mengesan perkara ini.

Nota

FlowSetSynthesis di atas sengaja ditulis untuk pengekodan ini pada rantai 1D. Ia memadankan corak label Pauli khusus yang dihasilkan oleh pengekodan dan membangkitkan ralat pada apa-apa yang lain, dan bukan secara senyap kembali kepada formula hasil darab generik. Pelaksanaan umum mempunyai dua bahagian. Membaca struktur set aliran daripada groups operator ialah apa yang sudah dilakukan oleh F2FSynthesisPlugin bagi sesuatu evolusi. Menerbitkan Clifford pepenjuru daripada kumpulan penstabil, seperti yang diterangkan dalam Bahagian IV C Rujukan [1], ialah bahagian yang kekal khusus pengekodan.

Selain itu, penyahsetempatan pariti menjadikan sebutan interaksi njnj+1n_j n_{j+1} berat-2 dan bukan pepenjuru dalam qubit tunggal, yang merupakan kos sepadan kepada sebutan lompatan yang lebih murah.

Seterusnya: dua dimensi​

Semua di atas adalah satu dimensi, dan sengaja begitu. Pada rantai, set aliran hanyalah dua orientasi anak panah, dan pengekodan memerlukan satu ancilla. Kedua-dua penyederhanaan ini tidak bertahan dalam dua dimensi, di mana pengekodan setempat diperlukan.

Pada kekisi satah, set aliran ialah laluan berarah (atau kesatuan laluan berarah yang tidak bersentuhan bucu) melalui graf interaksi, dan mencari set kecil laluan sedemikian yang meliputi setiap tepi menjadi masalah kombinatorik tersendiri, dan bukan pengelasan dua baris. Pengekodan juga berubah sifat; dan bukannya satu ancilla bagi seluruh rantai, ia menambah satu bagi setiap tapak atau sel kekisi. Verstraete-Cirac memasangkan setiap tapak fermion dengan qubit tambahannya sendiri, nisbah qubit-kepada-mod 22, manakala Derby-Klassen meletakkan satu di tengah setiap plakuet ganjil, bagi nisbah 3/23/2. Susun atur daftar kemudian mempunyai struktur dua dimensi sebenar, dan kumpulan penstabil yang mesti diunjurkan padanya tidak lagi remeh. Hamiltonian terkod hanya setia pada subruang, yang mengubah kedua-dua penyediaan keadaan dan resipi pengesahan daripada langkah 4.

Komponen yang dibina oleh panduan ini ialah yang terbawa ke hadapan: CustomF2QLayout untuk daftar yang saiznya berbeza daripada bilangan mod, map_transfer_vertex_generators() untuk pengekodan itu sendiri, MapperFnEvolutionSynthesis untuk menyambungkannya ke dalam transpiler, dan EvolutionSynthesis tersuai untuk menjadikan struktur set aliran berbaloi. Hanya pengekodan dan penguraian set aliran yang berbeza.

Panduan set aliran 2D bersambung di sini, menyelesaikan pengekodan Verstraete-Cirac pada kekisi petak: empat set aliran, Clifford kedalaman-2 bagi setiap satu, dan unjuran penstabil yang kemudiannya diperlukan oleh pengesahan.

[1] (1,2,3,4,5,6,7,8,9)

A. Gandon, S. Piccinelli, M. Rossmannek, F. Tacchino, A. Baiardi, J. Nys, and I. Tavernelli, Stabilizer-based quantum simulation of fermion dynamics with local qubit encodings (2026), arXiv:2512.11418v2.