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 Evolution gate di ruang bosonik

Evolusi waktu e−itHe^{-itH} dari Hamiltonian yang merupakan jumlah dari bagian-bagian yang tidak komut tidak bisa diimplementasikan secara eksak gate demi gate. Product formula memperkirakannya dengan serangkaian evolusi singkat di bawah masing-masing bagian, dan error aproksimasinya disebut error Trotter.

Kamu bisa menerapkan product formula di dua tempat, dan keduanya tidak bisa saling menggantikan:

  1. Di ruang bosonik, sebelum truncation atau encoding dipilih. Formula ini melihat grup dari operator (lihat Mengelompokkan suku operator: manfaatkan struktur operator) dan bisa menyisipkannya secara berselang-seling.

  2. Di ruang qubit, di dalam PauliEvolutionGate milik Qiskit, setelah operator dipetakan. Formula ini melihat satu operator Pauli per grup dan hanya bisa membagi suku-suku di dalamnya.

Hanya yang pertama yang bisa mengurangi error antar grup yang tidak komut, yang biasanya paling dominan. Panduan ini menunjukkan cara memakainya.

Penting

Kedua langkah ini opsional. Sebuah gate Evolution yang tidak pernah didekomposisi di ruang bosonik sampai ke tahap boson-ke-qubit secara utuh, lalu dipetakan dan disintesis di sana. Itu tidak menimbulkan error di level bosonik, hanya apa pun yang diperkenalkan formula level qubit.

Persiapan​

Rantai Bose-Hubbard dua situs, contoh terkecil di mana grup-grupnya tidak komut (lihat Simulasi dinamika Bose-Hubbard dengan pilihan encoding untuk model lengkapnya). Rantai ini dikelompokkan menjadi satu grup Hermitian untuk bond dan satu grup diagonal untuk interaksi on-site:

>>> import numpy as np
>>> from qiskit.quantum_info import SparsePauliOp, Statevector
>>> from qiskit.transpiler import PassManager
>>> from scipy.sparse import csc_matrix
>>> from scipy.sparse.linalg import expm_multiply
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> num_sites, max_occupation, total_time = 2, 2, 0.8
>>> bonds = [(0, 1)]
>>> terms = {}
>>> for left, right in bonds:
... terms[(cre(left), ann(right))] = -1.0
... terms[(cre(right), ann(left))] = -1.0
>>> for site in range(num_sites):
... terms[(cre(site), cre(site), ann(site), ann(site))] = 1.0
>>> hamiltonian = BosonOperator.from_dict(terms)
>>> hamiltonian.groups = [
... len(bonds)
... if len({mode for _, mode in term}) == 1
... else bonds.index(tuple(sorted({mode for _, mode in term})))
... for term, _ in hamiltonian.iter_terms()
... ]
>>> hamiltonian.num_groups()
2

Untuk mengukur error, kamu butuh referensi. Evolusi eksak dari Hamiltonian yang dipetakan pada truncation yang sama adalah pilihan yang tepat, karena truncation adalah aproksimasi yang terpisah:

>>> spec = LevelSpec(max_occupation, "unary_projected")
>>> mapped = occupation_encoding(hamiltonian, spec).simplify()
>>> matrix = csc_matrix(SparsePauliOp.from_sparse_observable(mapped).to_matrix(sparse=True))
>>> pm = generate_preset_occupation_pass_manager(spec)
>>> pm.qubit = PassManager() # keep each group as one exact PauliEvolutionGate
>>>
>>> def build(**evolution_kwargs):
... circuit = BosonicCircuit(num_sites)
... circuit.append(InitializeModes([1, 1]), circuit.modes)
... circuit.append(
... Evolution(num_sites, hamiltonian, time=total_time, **evolution_kwargs), circuit.modes
... )
... return circuit
>>>
>>> # Under the unary encodings, occupation ``n`` of mode ``m`` sets qubit ``m * d + n``, with
>>> # ``d = max_occupation + 1`` levels per mode. That places |1, 1> on the basis state below.
>>> levels = max_occupation + 1
>>> index = sum(1 << (mode * levels + n) for mode, n in enumerate([1, 1]))
>>> initial = Statevector.from_int(index, 2**(num_sites * levels)).data
>>> exact = expm_multiply(-1j * total_time * matrix, initial)
>>> def error(circuit):
... """Distance to the exact evolution, of the circuit's product of group exponentials."""
... lowered = pm.run(circuit)
... state = Statevector.from_int(0, 2**lowered.num_qubits).evolve(lowered).data
... return float(np.max(np.abs(state - exact)))
Catatan

error sengaja mensimulasikan circuit hasil lowering tanpa mendekomposisi instance PauliEvolutionGate-nya. Statevector menerapkan masing-masing sebagai eksponensial eksak dari grupnya, jadi satu-satunya yang tersisa adalah hasil kali eksponensial grup yang dipilih oleh sintesis bosonik.

Default​

Tanpa argumen synthesis, gate Evolution didekomposisi dengan BosonicLieTrotter, formula orde pertama: satu faktor per grup, masing-masing untuk seluruh waktu.

>>> dict(build().decompose().count_ops())
{'Evolution': 2, 'InitializeModes': 1}

Tanpa dekomposisi, seluruh gate diserahkan ke tahap qubit dan sama sekali tidak ada error Trotter bosonik:

>>> error(build()) < 1e-6
True

Setelah didekomposisi, muncul error yang nyata, karena kedua grup tidak komut:

>>> error(build().decompose()) > 0.1
True

Kurangi error​

Berikan metode sintesis yang berbeda ke gate. Orde yang lebih tinggi menyusun faktor-faktor secara simetris sehingga suku error utamanya saling meniadakan; lebih banyak repetisi membagi waktu menjadi langkah-langkah yang lebih pendek.

>>> from qiskit_bosons.circuit.library.synthesis import BosonicLieTrotter, BosonicSuzukiTrotter
>>>
>>> for label, synthesis in [
... ("Lie-Trotter", BosonicLieTrotter()),
... ("Suzuki, order 2", BosonicSuzukiTrotter(order=2)),
... ("Suzuki, order 4", BosonicSuzukiTrotter(order=4)),
... ("Suzuki, order 4*", BosonicSuzukiTrotter(order=4, merge_adjacent=False)),
... ]:
... circuit = build(synthesis=synthesis).decompose()
... print(f"{label:16s} {circuit.count_ops()['Evolution']:3d} factors")
Lie-Trotter 2 factors
Suzuki, order 2 3 factors
Suzuki, order 4 11 factors
Suzuki, order 4* 15 factors

Setiap kenaikan orde sebesar dua menyusun lima sapuan dari orde di bawahnya (pertumbuhan 5(k−2)/25^{(k-2)/2} dari rekursi Suzuki, diterapkan pada 2n−12n - 1 faktor dari orde 2), yaitu jumlah pada baris terakhir, yang ditandai bintang. Sapuan-sapuan itu bertemu di faktor yang sama, sehingga gate bertetangga berevolusi di bawah satu operator dan digabung menjadi satu gate dengan waktu yang dijumlahkan. Itu menghemat satu gate per sambungan dan bersifat eksak. Berikan merge_adjacent=False untuk mempertahankan urutan versi buku teks. Bagaimanapun, orde yang lebih tinggi memberi error yang lebih kecil:

>>> lie = error(build(synthesis=BosonicLieTrotter()).decompose())
>>> second = error(build(synthesis=BosonicSuzukiTrotter(order=2)).decompose())
>>> fourth = error(build(synthesis=BosonicSuzukiTrotter(order=4)).decompose())
>>> lie > second > fourth
True

Repetisi menukar depth dengan akurasi pada laju yang berbeda. Error orde pertama turun secara linear terhadap ukuran langkah, sedangkan orde kedua secara kuadratik:

>>> lie_errors = [error(build(synthesis=BosonicLieTrotter(reps=r)).decompose()) for r in (1, 2, 4, 8)]
>>> bool(all(a > b for a, b in zip(lie_errors, lie_errors[1:])))
True
>>> bool(lie_errors[0] / lie_errors[-1] > 6) # about 8, i.e. linear in 1 / reps
True
>>> suzuki_errors = [
... error(build(synthesis=BosonicSuzukiTrotter(order=2, reps=r)).decompose()) for r in (1, 2, 4)
... ]
>>> bool(suzuki_errors[0] / suzuki_errors[-1] > 10) # about 16, i.e. quadratic in 1 / reps
True

Mana yang lebih baik tergantung pada operatornya. Perhatikan bahwa operator dengan satu grup saja eksak pada orde berapa pun, karena satu faktor tunggal komut dengan dirinya sendiri, dan formula itu lalu menyusut menjadi satu gate alih-alih membaginya ulang.

Ukur biayanya​

Error yang lebih kecil butuh lebih banyak faktor, dan setiap faktor memakan gate entangling setelah tahap qubit mensintesisnya. Berapa banyaknya tergantung pada encoding, jadi hitung lewat pipeline preset, dengan basis tetap dan tanpa optimisasi level qubit (optimization_level=0), untuk melihat harga tiap metode:

>>> basis = ["rz", "sx", "x", "cx"]
>>>
>>> def cx_count(circuit, encoding):
... spec = LevelSpec(max_occupation, encoding)
... lowered = generate_preset_occupation_pass_manager(
... spec, optimization_level=0, basis_gates=basis
... ).run(circuit)
... return lowered.count_ops().get("cx", 0)
>>>
>>> methods = [
... ("Lie-Trotter", BosonicLieTrotter()),
... ("Suzuki, order 2", BosonicSuzukiTrotter(order=2)),
... ("Suzuki, order 4", BosonicSuzukiTrotter(order=4)),
... ]
>>> encodings = ("gray", "unary_unprojected")
>>> counts = {
... encoding: [
... cx_count(build(synthesis=method).decompose(), encoding) for _, method in methods
... ]
... for encoding in encodings
... }
>>> # the order buys accuracy at a price that grows with it
>>> bool(counts["gray"][0] < counts["gray"][1] < counts["gray"][2])
True

Jumlah persisnya bergantung pada cara Qiskit mensintesis evolution gate, yang berubah antar rilis, jadi plot menampilkannya sebagai ganti tabel:

Grouped bar chart of the CX count for Lie-Trotter and Suzuki of order 2 and 4, for the Gray and the unary encoding. The counts grow with the order, and the unary bars are higher.

Setiap kelompok batang adalah satu metode: Lie-Trotter, Suzuki orde 2, dan Suzuki orde 4. Orde membeli akurasi dengan harga yang tumbuh sesuai faktor rekursi, dan encoding unary membayar lebih mahal daripada kode Gray untuk setiap metode karena tiap faktornya dipetakan ke lebih banyak gate entangling. Pilih metode sesuai akurasi yang kamu butuhkan dan encoding sesuai hardware, dan pisahkan kedua pilihan itu: circuit bosonik di atas tidak pernah berubah.

Terapkan satu metode ke seluruh circuit​

Untuk mengatur synthesis pada setiap gate, kamu harus meneruskan pilihan itu ke semua yang membangunnya. Pass B2BSynthesis membuat pilihan itu sekali untuk seluruh pipeline. config-nya menamai metode untuk setiap jenis gate, dan pass ini sekaligus memilih metode dan mengekspansi gate:

>>> from qiskit_bosons.transpiler import BosonicPassManager
>>> from qiskit_bosons.transpiler.passes import B2BSynthesis
>>>
>>> trotterize = BosonicPassManager([B2BSynthesis({"Evolution": BosonicSuzukiTrotter(order=2)})])
>>> dict(trotterize.run(build()).count_ops())
{'Evolution': 3, 'InitializeModes': 1}

Sebuah metode juga bisa disebut namanya, dengan argumen untuk __init__-nya, alih-alih dibangun: B2BSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2})}). Nama yang tersedia adalah milik B2BSynthesisPluginManager, dan metode yang tidak terdaftar di mana pun bisa diserahkan sebagai instance, seperti di atas.

Dengan apply=False, pass hanya memilih metode dan menunda ekspansi sampai saat dibutuhkan, misalnya untuk memeriksa circuit terlebih dahulu. Gate atomik tidak pernah disentuh, bahkan tidak diberi tag:

>>> selected = BosonicPassManager(
... [B2BSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2})}, apply=False)]
... ).run(build())
>>> selected.count_ops()["Evolution"] # selected, not yet expanded
1
>>> selected.decompose().count_ops()["Evolution"] # expanded on demand
3

Gate yang belum diekspansi sampai ke tahap boson-ke-qubit secara utuh, di mana ia dipetakan tanpa synthesis-nya dipertimbangkan, sehingga setiap metode menghasilkan output yang identik.

filter opsional membatasinya pada gate yang diuntungkan. Evolusi yang semua grupnya komut, seperti Hamiltonian yang hanya terdiri dari operator bilangan, disintesis secara eksak pada orde berapa pun, jadi orde yang lebih tinggi hanya menambah depth di sana.

Batasi pass pada gate yang diuntungkan​

Operator yang murni diagonal membuat poin ini konkret, karena operator bilangan saling komut. Grup-grupnya eksak pada orde pertama, dan orde kedua menghabiskan gate entangling tanpa guna:

>>> # one group per term, each Hermitian; the order is fixed because order two evolves the first
>>> # group twice, so which group comes first changes the cost
>>> diagonal = BosonOperator.from_terms_with_groups(
... [([cre(0), ann(0), cre(1), ann(1)], 1.0, 0), ([cre(0), ann(0)], 0.5, 1)]
... )
>>>
>>> def diagonal_cost(synthesis):
... circuit = BosonicCircuit(num_sites)
... circuit.append(Evolution(num_sites, diagonal, time=total_time, synthesis=synthesis), circuit.modes)
... return cx_count(circuit.decompose(), "gray")
>>>
>>> bool(diagonal_cost(BosonicSuzukiTrotter(order=2)) > diagonal_cost(BosonicLieTrotter()))
True

Kedua circuit eksak, jadi yang kedua memakai lebih banyak gate entangling tanpa keuntungan. (Formula simetris orde dua menerapkan grup pertama dua kali dengan setengah waktu masing-masing, sehingga biayanya bergantung pada grup mana yang datang lebih dulu.) filter menerima predikat atas DAGOpNode dan membiarkan gate yang ditolak tidak tersentuh, sehingga satu pass bisa menaikkan orde pada gate yang diuntungkan dan mempertahankan sisanya di orde pertama. Predikat di bawah menerima gate hanya jika operatornya memindahkan boson antar mode, yaitu saat grup-grupnya tidak komut:

>>> def is_diagonal(operator):
... """Whether every term only counts bosons, 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 = BosonicCircuit(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
>>>
>>> order_2 = BosonicSuzukiTrotter(order=2)
>>> everywhere = cx_count(
... BosonicPassManager([B2BSynthesis({"Evolution": order_2})]).run(mixed_circuit()), "gray"
... )
>>> restricted = cx_count(
... BosonicPassManager(
... [
... B2BSynthesis(
... {"Evolution": order_2},
... filter=lambda node: not is_diagonal(node.op.operator),
... )
... ]
... ).run(mixed_circuit()),
... "gray",
... )
>>> bool(everywhere > restricted) # the filter saves entangling gates
True

Kalau kamu menjalankan pass itu lagi, tidak ada yang berubah: faktor yang dihasilkannya bersifat atomik.

Faktor atomik​

Sebuah Evolution yang ditandai atomic tidak punya definisi, sehingga decompose() membiarkannya di tempat sebanyak apa pun ia diterapkan. Sebuah sintesis menandai faktor yang dihasilkannya dengan cara ini, begitu pula BosonicQDrift untuk suku yang disampelnya.

Ini lebih dari sekadar kerapian. Membagi grup Hermitian lebih lanjut, suku demi suku, menghasilkan faktor yang masing-masing tidak Hermitian, dan eksponensial dari operator non-Hermitian tidak unitari:

>>> circuit = build()
>>> once = circuit.decompose()
>>> dict(circuit.decompose(reps=5).count_ops()) == dict(once.count_ops())
True
Perhatian

Setiap faktor harus Hermitian dengan sendirinya. Operator Hermitian yang dibagi menjadi grup bisa menghasilkan grup non-Hermitian, seperti suku hopping yang dipisahkan dari adjoint-nya. Periksa dengan groups_are_hermitian(). Operator tanpa grup dibagi suku demi suku, dan tahap qubit lalu menolak faktornya dengan Operator contains complex coefficients. Simetrisasi pada orde genap sebagian meniadakan error dari faktor non-Hermitian, sehingga operator yang pengelompokannya salah bisa tampak lebih baik pada orde 2 daripada orde 1 padahal tetap salah.

Ulangi langkah pendek​

Sebagai alternatif di level circuit, bangun satu langkah Trotter pendek lalu ulangi. repeat() meratakan repetisi menjadi satu circuit dan membiarkan batasnya bersebelahan, sehingga pass bisa bekerja melintasinya:

>>> step = BosonicCircuit(num_sites)
>>> step.append(Evolution(num_sites, hamiltonian, time=total_time / 4), step.modes)
>>> dict(step.repeat(4).count_ops())
{'Evolution': 4}

Berikan insert_barriers=True untuk memisahkan repetisi. Barrier dipetakan ke setiap qubit dari register yang disentuhnya, karena sebuah encoding butuh beberapa qubit per mode dan mode tidak punya citra per qubit sendiri:

>>> [inst.operation.name for inst in step.repeat(3, insert_barriers=True)._inner.data]
['Evolution', 'barrier', 'Evolution', 'barrier', 'Evolution']
Catatan

repeat tidak berkaitan dengan reps pada metode sintesis. Sebuah sintesis membagi satu evolusi menjadi lebih banyak faktor yang lebih pendek. Mengulang circuit yang berisi satu evolusi hanya melipatgandakan total waktunya.

Gabungkan dengan formula level qubit​

Kedua level ini bisa dikomposisikan, dan jika keduanya mengaproksimasi, akurasi hasil ditentukan oleh yang lebih lemah. Menaikkan orde bosonik sementara formula sisi qubit tetap orde pertama tidak banyak membantu. Lihat MapperFnEvolutionSynthesis untuk formula sisi qubit. Ia mempertahankan urutan suku Pauli yang dipilih mapper, sehingga group_wise() bisa mengeluarkan suku-suku tiap grup secara bersebelahan, dan simplify() bisa mengunci urutan kanonik sebagai gantinya.

Langkah berikutnya​