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?

Mensintesis gate Evolution di ruang fermionik

Penting

Konsep-konsep dalam panduan ini saat ini hanya tersedia di Python API. Fungsionalitas yang setara akan hadir di C API pada rilis mendatang.

Gate Evolution membawa evolusi waktu e−itHe^{-itH} dari seluruh operator. Sintesis fermion-ke-fermion menulis ulang gate itu menjadi FermionicCircuit berisi gate fermionik yang lebih kecil. Kedua sisi penulisan ulang tetap berada di ruang fermionik, dan inilah yang membedakannya dari sintesis fermion-ke-qubit yang menyusul: groups milik operator (lihat Group operator terms: use the operator structure) tetap bertahan, sedangkan pemetaan fermion-ke-qubit mengembalikan SparseObservable, yang tidak mengenal konsep pengelompokan.

Dua pengaturan menentukan metodenya. Evolution.synthesis menetapkannya pada satu gate, dan transpiler pass F2FSynthesis menetapkannya untuk seluruh circuit. Keduanya menerima F2FSynthesisPlugin, antarmuka yang diimplementasikan oleh setiap penulisan ulang semacam itu. Metode yang saat ini disediakan paket ini untuk evolusi adalah product formula, yang dipakai di sisa panduan ini: FermionicLieTrotter untuk formula orde pertama (default) dan FermionicSuzukiTrotter untuk orde genap yang lebih tinggi. Apa pun yang mengimplementasikan antarmuka itu dipilih dengan cara yang sama, dan bagian tentang kedua pengaturan tadi berlaku untuknya tanpa perubahan.

Catatan

Langkah fermion-ke-fermion ini opsional. Gate Evolution bisa langsung masuk ke tahap fermion-ke-qubit, yang memetakan dan mensintesisnya secara utuh berapa pun jumlah sukunya. Memecahnya lebih dulu adalah sebuah pilihan, dilakukan karena gate hasilnya masing-masing lebih murah atau karena pemecahan itu memunculkan struktur yang bisa dipakai tahap berikutnya.

Kedua langkah ini bisa digabungkan, jadi sebuah evolusi bisa berakhir diaproksimasi di salah satu tingkat, di keduanya, atau tidak sama sekali. Product formula mengaproksimasi kecuali faktor-faktornya saling komut; metode sintesis tidak harus mengaproksimasi sama sekali.

Catatan

Melewati langkah ini tidak harus berarti kehilangan pengelompokan sepenuhnya. Membungkus mapper dengan group_wise() memetakan operator satu grup sekali waktu dan menjumlahkan hasilnya, sehingga suku Pauli hasil pemetaan tiba dalam keadaan berkelompok, bukan berselang-seling. Product formula di sisi qubit mensintesisnya sesuai urutan yang diterima, sehingga suku-suku yang saling komut dan kebetulan bersebelahan bisa dijadwalkan bersamaan.

Pada Hamiltonian di bawah, cara itu memulihkan seluruh keuntungan depth dua-qubit dari pemecahan: 18 dibanding 30, dengan jumlah gate yang sama. Bedanya ada pada di mana struktur diekspresikan. Memecah di ruang fermionik menjadikan setiap grup sebagai gate terpisah, yang bisa dilihat dan dimanfaatkan setiap tahap berikutnya; group_wise menyisakan satu gate dan hanya mengurutkan ulang suku di dalamnya, yang cukup untuk penjadwalan tetapi tidak menyisakan apa pun untuk dikerjakan pass fermionik. Pakai cara ini kalau evolusinya harus tetap berupa satu gate, dan gunakan product formula fermionik untuk kasus lainnya.

Membangun Hamiltonian berkelompok​

Kedua product formula memecah operator menjadi faktor-faktor, dan untuk operator berkelompok, faktor-faktor itu adalah grup-grupnya. Jadi pengelompokan menentukan biaya sekaligus akurasi hasilnya. Ambil rantai Fermi-Hubbard enam situs, yang dikelompokkan menurut cara suku-sukunya saling komut: suku hopping pada ikatan genap, suku hopping pada ikatan ganjil, dan interaksi diagonal.

>>> from qiskit_fermions.operators import FermionOperator, cre, ann
>>>
>>> def fermi_hubbard_chain(num_sites, tunneling, interaction):
... """A 1D Fermi-Hubbard chain grouped into even bonds, odd bonds, and the interaction."""
... terms, groups = [], []
... for offset in (0, 1):
... for j in range(offset, num_sites - 1, 2):
... terms += [
... ([cre(j), ann(j + 1)], -tunneling),
... ([cre(j + 1), ann(j)], -tunneling),
... ]
... groups += [offset, offset]
... for j in range(num_sites - 1):
... terms.append(([cre(j), ann(j), cre(j + 1), ann(j + 1)], interaction))
... groups.append(2)
... operator = FermionOperator.from_terms(terms)
... operator.groups = groups
... return operator
>>>
>>> num_sites = 6
>>> hamiltonian = fermi_hubbard_chain(num_sites, tunneling=1.0, interaction=2.0)
>>> hamiltonian.num_groups()
3

Lima belas suku dalam tiga grup. Setiap grup hopping memasangkan tiap suku dengan pasangan konjugatnya, dan ini penting: sebuah faktor harus Hermitian supaya eksponensialnya uniter, dan itu tidak otomatis benar hanya karena jumlahnya Hermitian. groups_are_hermitian() memeriksa konvensi itu, bukan menyerahkannya pada pengamatan mata:

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

Memisahkan a0†a1a^\dagger_0 a_1 dari pasangannya a1†a0a^\dagger_1 a_0 akan merusaknya, dan membiarkan operator tanpa pengelompokan punya efek yang sama, karena kedua formula lalu memecahnya suku demi suku dan satu ai†aja^\dagger_i a_j saja tidaklah Hermitian:

>>> ungrouped = fermi_hubbard_chain(num_sites, tunneling=1.0, interaction=2.0)
>>> ungrouped.groups = range(len(list(ungrouped.iter_terms()))) # one group per term
>>> all(groups_are_hermitian(ungrouped))
False

Faktor seperti itu adalah operator fermionik yang valid, dan paket ini akan membuatkan gate untuknya, jadi tidak ada yang mengeluh sampai operator hasil pemetaan mencapai PauliEvolutionGate, yang membutuhkan koefisien real dan memunculkan ValueError: Operator contains complex coefficients, which are not supported. Memeriksa pengelompokan di awal mengubah kegagalan yang terlambat itu menjadi jawaban yang sudah tersedia sebelum circuit apa pun dibangun.

Menetapkan metode pada satu gate​

Berikan metode sintesis ke gate, lalu dekomposisikan. Setiap gate yang dihasilkannya kembali sebagai gate Evolution tersendiri:

>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.circuit.library.synthesis import (
... FermionicLieTrotter,
... FermionicSuzukiTrotter,
... )
>>>
>>> total_time = 1.0
>>>
>>> def factors(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, hamiltonian, time=total_time, synthesis=synthesis),
... circuit.modes,
... )
... return circuit.decompose().count_ops()["Evolution"]
>>>
>>> factors(FermionicLieTrotter())
3
>>> factors(FermionicSuzukiTrotter(order=2))
5
>>> factors(FermionicSuzukiTrotter(order=2, reps=4))
17
>>> factors(FermionicSuzukiTrotter(order=2, reps=4, merge_adjacent=False))
20

Orde pertama menghasilkan satu faktor per grup. Orde dua menghasilkan palindrom simetris, sehingga dua grup terluar muncul dua kali dengan setengah waktu masing-masing, jadi totalnya lima. Empat pengulangan akan memberi dua puluh, tetapi setiap pengulangan berakhir pada grup yang sama dengan awal pengulangan berikutnya. Gate-gate bertetangga itu berevolusi di bawah operator yang sama, jadi digabung menjadi satu gate dengan waktu yang dijumlahkan, yang menghemat satu gate per sambungan. Berikan merge_adjacent=False untuk mempertahankan urutan buku teks. Setiap orde memecah operator dengan cara yang sama; yang berubah adalah urutan faktor dan waktu evolusinya, bukan partisinya.

Catatan

Faktor-faktornya ditandai atomic, jadi dekomposisi ulang membiarkannya di tempat dan tidak memecahnya lebih jauh. Dekomposisi ini punya titik tetap, yang membuatnya aman untuk memanggil decompose() berulang kali.

Menetapkan metode untuk seluruh pipeline​

Menetapkan synthesis per gate berarti harus meneruskan pilihan itu ke semua yang membangun gate, termasuk UCC dan UCJ, yang membangun gate-nya secara internal. Pass F2FSynthesis justru menerapkan satu metode ke setiap Evolution dalam circuit, sehingga pilihannya dibuat sekali untuk seluruh pipeline. Pass ini dikonfigurasi berdasarkan nama gate, sama seperti F2QSynthesis, dan gate yang tidak disebutkan dibiarkan apa adanya:

>>> from qiskit_fermions.transpiler import FermionicPassManager
>>> from qiskit_fermions.transpiler.passes import F2FSynthesis
>>>
>>> circuit = FermionicCircuit(num_sites)
>>> circuit.append(Evolution(num_sites, hamiltonian, time=total_time), circuit.modes)
>>>
>>> expanded = FermionicPassManager(
... F2FSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2, "reps": 4})})
... ).run(circuit)
>>> expanded.count_ops()["Evolution"]
17

Ini jumlah yang sama dengan hasil decompose() untuk metode itu di atas. Pass MergeEvolutions menerapkan penggabungan yang sama ke gate bertetangga yang tidak berasal dari satu sintesis, misalnya sambungan antara circuit-circuit berurutan. Pass ini menjalankan formulanya sendiri, jadi tidak perlu langkah ekspansi terpisah, dan menjalankannya dua kali tidak mengubah apa-apa, karena faktor yang dihasilkannya bersifat atomic sehingga tidak membawa definisi sendiri.

Catatan

Berikan apply=False untuk hanya memilih metode dan menyerahkan ekspansinya ke hal lain, seperti Decompose milik Qiskit (menyebut gate sebagai Decompose("Evolution") membatasinya pada evolusi saja, dan membiarkan gate fermionik lain ke plugin sintesisnya masing-masing).

Ini berguna untuk memilih metode bagi seluruh circuit di awal sambil menunda ekspansi sampai saat diperlukan, misalnya untuk memeriksa circuit-nya terlebih dulu:

>>> selected = FermionicPassManager(
... F2FSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2})}, apply=False)
... ).run(circuit)
>>> selected.count_ops()["Evolution"] # selected, not yet expanded
1
>>> selected.decompose().count_ops()["Evolution"] # expanded on demand
5

Tapi waspadai apa yang dilakukan gate yang belum diekspansi: gate itu sampai ke tahap fermion-ke-qubit secara utuh, dan di sana dipetakan tanpa synthesis dipertimbangkan. Ini mudah jadi kesalahan saat membandingkan metode, karena semuanya lalu menghasilkan output identik dan perbandingannya diam-diam tidak mengukur apa-apa. Pada Hamiltonian di atas, membiarkan gate orde dua tanpa ekspansi meruntuhkan circuit yang jauh lebih besar di bawah menjadi biaya satu evolusi hasil pemetaan, apa pun metode yang dipilih.

Mengukur hasilnya​

Orde yang lebih tinggi memakan depth, jadi pertanyaannya bukan apakah lebih akurat, melainkan apakah lebih akurat per gate. Dibutuhkan dua angka per metode: galat state yang dihasilkannya, dan biaya dua-qubit dari circuit yang menghasilkannya.

Galat diukur dengan ffsim, yang mensimulasikan FermionicCircuit secara langsung (lihat How this package relates to ffsim). Tidak ada pemetaan fermion-ke-qubit yang terlibat, sehingga perbandingan ini memisahkan product formula fermionik dari apa pun yang mungkin disumbangkan pemetaan atau sintesis di sisi qubit. Simulasi ini juga bekerja di sektor jumlah partikel tetap, yang lebih kecil daripada ruang penuh 2N2^N dan merupakan ruang tempat evolusi itu benar-benar berada.

>>> import ffsim
>>> import numpy as np
>>> from scipy.sparse.linalg import expm_multiply
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> norb, nelec = num_sites, (3, 0) # three spinless fermions on six modes
>>> initial = ffsim.hartree_fock_state(norb, nelec)
>>>
>>> hamiltonian_operator = ffsim.linear_operator(hamiltonian, norb=norb, nelec=nelec)
>>> exact = expm_multiply(
... -1j * total_time * hamiltonian_operator,
... initial,
... traceA=-1j * total_time * ffsim.trace(hamiltonian, norb=norb, nelec=nelec),
... )
>>>
>>> def cost_and_error(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, hamiltonian, time=total_time, synthesis=synthesis),
... circuit.modes,
... )
... # the method is set on the gate, so decompose() is what expands it here; a pipeline
... # would reach for F2FSynthesis instead (see the note above)
... circuit = circuit.decompose()
... evolved = ffsim.apply_unitary(initial, circuit, norb=norb, nelec=nelec)
... error = 1 - abs(np.vdot(evolved, exact)) ** 2
... qubit_circuit = generate_preset_jw_pass_manager().run(circuit).decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... return error, qubit_circuit.count_ops()["cx"], qubit_circuit.depth(two_qubit)
>>>
>>> methods = [
... ("order 1, reps 1 ", FermionicLieTrotter()),
... ("order 2, reps 1 ", FermionicSuzukiTrotter(order=2)),
... ("order 1, reps 10", FermionicLieTrotter(reps=10)),
... ("order 2, reps 4 ", FermionicSuzukiTrotter(order=2, reps=4)),
... ]
>>> costs = {label: cost_and_error(synthesis) for label, synthesis in methods}
>>> labels, counts, depths = [], [], []
>>> for label, (error, gates, depth) in costs.items():
... labels.append(label.strip().replace(", ", "\n"))
... counts.append(gates)
... depths.append(depth)
... print(f"{label} error {error:.6f}")
order 1, reps 1 error 0.515997
order 2, reps 1 error 0.180958
order 1, reps 10 error 0.004026
order 2, reps 4 error 0.000421

Biaya dua-qubit sengaja tidak dimasukkan ke tabel itu. Biaya ini bergantung pada sintesis sisi qubit yang dipilih Qiskit, yang membaik antar rilis, sehingga angka yang dicetak di sini hanya akan jadi cuplikan sesaat, bukan properti product formula. Sebagai gantinya, gambar di bawah memplotnya, dan dasar perbandingannya dinyatakan sebagai sebuah relasi:

Grouped bars of CX count and depth per method, with order two at four repetitions clearly shorter than order one at ten.

Baca dua metode terakhir bersamaan, karena itulah perbandingan yang penting. Orde dua dengan empat pengulangan kira-kira sepuluh kali lebih akurat daripada orde satu dengan sepuluh pengulangan, dan hasil itu dicapai dengan gate entangling yang lebih sedikit, bukan lebih banyak, seperti yang ditunjukkan gambar. Di sini, menghabiskan anggaran gate untuk orde yang lebih tinggi lebih baik daripada untuk lebih banyak langkah orde pertama.

Hasil itu tidak berlaku universal. Menaikkan reps dan menaikkan order mengurangi galat dengan laju berbeda untuk jenis biaya yang sama, dan mana yang menang bergantung pada operator dan waktu yang dievolusikan. Dua baris di atas adalah cara untuk mencari tahu pada masalah tertentu.

Catatan

Formula fermionik dikomposisikan dengan formula yang dipilih untuk tahap fermion-ke-qubit (lihat MapperFnEvolutionSynthesis.product_formula). Kalau keduanya mengaproksimasi, akurasi hasil ditentukan oleh yang lebih lemah di antara keduanya, jadi menaikkan orde di sini sementara formula sisi qubit tetap orde pertama hanya memberi sedikit manfaat.

Batasi pass ke gate yang diuntungkan​

Orde yang lebih tinggi hanya membantu kalau faktor-faktornya tidak saling komut. Kalau semuanya saling komut, formula orde pertama sudah eksak dan faktor tambahan murni overhead. Operator yang sepenuhnya diagonal membuat hal ini konkret, karena operator jumlah saling komut satu sama lain:

>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> diagonal = FermionOperator.from_terms(
... [([cre(j), ann(j), cre(j + 1), ann(j + 1)], 1.0 + 0.1 * j) for j in range(num_sites - 1)]
... )
>>> diagonal.groups = list(range(num_sites - 1))
>>>
>>> def diagonal_cost(synthesis):
... circuit = FermionicCircuit(num_sites)
... circuit.append(
... Evolution(num_sites, diagonal, time=total_time, synthesis=synthesis), circuit.modes
... )
... transpiled = generate_preset_jw_pass_manager().run(circuit.decompose())
... return transpiled.decompose(reps=6).count_ops()["cx"]
>>>
>>> diagonal_cost(FermionicLieTrotter())
10
>>> diagonal_cost(FermionicSuzukiTrotter(order=2))
18

Kedua circuit eksak, jadi yang kedua memakai 80% lebih banyak gate entangling tanpa manfaat. Operator Coulomb diagonal dari UCJ berbentuk seperti ini, itulah sebabnya F2FSynthesis.filter ada: filter ini menerima predikat atas DAGOpNode dan membiarkan gate yang ditolak tidak tersentuh, sehingga satu pass bisa menaikkan orde pada gate yang diuntungkan dan membiarkan sisanya di orde pertama.

Circuit yang memuat kedua jenis evolusi menunjukkan berapa banyak yang dihemat. Predikat di bawah hanya menerima gate kalau operatornya memindahkan partikel antar mode, yang persis saat grup-grupnya tidak saling komut dan orde yang lebih tinggi punya sesuatu untuk dibeli:

>>> def is_diagonal(operator):
... """Whether every term only counts particles, rather than moving them between modes."""
... return all(
... sorted(mode for created, mode in actions if created)
... == sorted(mode for created, mode in actions if not created)
... for actions, _ in operator.iter_terms()
... )
>>>
>>> def mixed_circuit():
... circuit = FermionicCircuit(num_sites)
... circuit.append(Evolution(num_sites, hamiltonian, time=total_time), circuit.modes)
... circuit.append(Evolution(num_sites, diagonal, time=total_time), circuit.modes)
... return circuit
>>>
>>> def two_qubit_cost(circuit):
... transpiled = generate_preset_jw_pass_manager().run(circuit).decompose(reps=6)
... two_qubit = lambda instruction: len(instruction.qubits) == 2
... return transpiled.count_ops()["cx"], transpiled.depth(two_qubit)
>>>
>>> order_2 = FermionicSuzukiTrotter(order=2)
>>>
>>> everywhere = two_qubit_cost(
... FermionicPassManager(F2FSynthesis({"Evolution": order_2})).run(mixed_circuit())
... )
>>> restricted = two_qubit_cost(
... FermionicPassManager(
... F2FSynthesis(
... {"Evolution": order_2}, filter=lambda node: not is_diagonal(node.op.operator)
... )
... ).run(mixed_circuit())
... )
>>> everywhere[0] - restricted[0] # the gates the filter saved
8
>>> restricted[0] < everywhere[0] and restricted[1] < everywhere[1]
True

Gate yang dihemat filter persis sama dengan yang dihabiskan orde dua pada evolusi diagonal, yang oleh perbandingan diagonal di atas dihargai delapan (18 berbanding 10). Gate itu tidak membeli apa-apa di sana, karena evolusi tersebut sudah eksak pada orde pertama, jadi membuangnya tidak mengorbankan akurasi.

Catatan

Bandingkan kedua circuit orde dua satu sama lain, bukan dengan yang tidak diekspansi. Membiarkan kedua gate tanpa ekspansi mengirim masing-masing ke tahap fermion-ke-qubit secara utuh, yang merupakan kesalahan yang dijelaskan catatan apply=False di atas: jumlah gate entangling-nya bisa sama dengan circuit yang difilter padahal circuit-nya berbeda dengan depth berbeda, sehingga membaca manfaat filter dari angka itu tidak mengukur apa-apa.

Peringatan

Tidak satu pun dari kedua pengaturan memverifikasi bahwa faktor-faktornya Hermitian, dan operator yang pengelompokannya salah bisa tampak lebih baik pada orde genap daripada orde pertama padahal tetap salah, karena simetrisasi membatalkan sebagian galat. Kelompokkan pasangan konjugat bersama, seperti yang dilakukan fermi_hubbard_chain di atas.

Langkah berikutnya​