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?

Sintesis gate Evolution dalam ruang boson

Evolusi masa e−itHe^{-itH} bagi Hamiltonian yang merupakan hasil tambah bahagian yang tidak berkomutasi tidak dapat dilaksanakan dengan tepat gate demi gate. Formula hasil darab menganggarkannya dengan jujukan evolusi pendek di bawah setiap bahagian, dan ralat penganggaran itu dipanggil ralat Trotter.

Anda boleh menggunakan formula hasil darab di dua tempat, dan keduanya tidak boleh ditukar ganti:

  1. Dalam ruang boson, sebelum sebarang pemotongan atau pengekodan dipilih. Formula itu melihat kumpulan operator (lihat Kumpulkan sebutan operator: gunakan struktur operator) dan boleh menyelitkannya.

  2. Dalam ruang qubit, di dalam PauliEvolutionGate Qiskit, selepas operator dipetakan. Formula itu melihat satu operator Pauli bagi setiap kumpulan dan hanya boleh membahagikan sebutan di dalamnya.

Hanya yang pertama dapat mengurangkan ralat antara kumpulan yang tidak berkomutasi, yang biasanya paling dominan. Panduan ini menunjukkan cara menggunakannya.

Penting

Kedua-dua langkah adalah pilihan. Gate Evolution yang tidak pernah diurai dalam ruang boson sampai ke peringkat boson-ke-qubit secara utuh dan dipetakan serta disintesis di situ. Itu tidak menimbulkan ralat pada peringkat boson, hanya apa sahaja yang diperkenalkan oleh formula peringkat qubit.

Sediakan​

Rantai Bose-Hubbard dua tapak, contoh terkecil di mana kumpulan tidak berkomutasi (lihat Simulasikan dinamik Bose-Hubbard dengan pilihan pengekodan untuk model penuh). Ia dikumpulkan menjadi satu kumpulan Hermitian untuk ikatan dan satu kumpulan pepenjuru untuk interaksi atas tapak:

>>> 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 ralat anda memerlukan rujukan. Evolusi tepat bagi Hamiltonian seperti yang dipetakan pada pemotongan yang sama ialah yang betul, kerana pemotongan ialah penganggaran yang berasingan:

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

error sengaja mensimulasikan litar yang diturunkan tanpa mengurai instance PauliEvolutionGatenya. Statevector menggunakan setiap satu sebagai eksponen tepat bagi kumpulannya, jadi satu-satunya yang tinggal ialah hasil darab eksponen kumpulan yang dipilih oleh sintesis boson.

Lalai​

Tanpa argumen synthesis, gate Evolution diurai dengan BosonicLieTrotter, formula peringkat pertama: satu faktor bagi setiap kumpulan, masing-masing untuk keseluruhan masa.

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

Tanpa penguraian, keseluruhan gate diserahkan kepada peringkat qubit dan tiada ralat Trotter boson sama sekali:

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

Apabila diurai, ralat sebenar muncul, kerana kedua-dua kumpulan tidak berkomutasi:

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

Kurangkan ralat​

Hantar kaedah sintesis yang berbeza kepada gate. Tertib yang lebih tinggi menyusun faktor secara simetri supaya sebutan ralat utama saling membatalkan; lebih banyak ulangan membahagikan masa kepada 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 peningkatan tertib sebanyak dua menyusun lima sapuan daripada tertib di bawahnya (pertumbuhan 5(k−2)/25^{(k-2)/2} bagi rekursi Suzuki, diterapkan pada 2n−12n - 1 faktor tertib 2), iaitu bilangan pada baris terakhir, ditandakan dengan bintang. Sapuan itu bertemu pada faktor yang sama, jadi gate jiran berevolusi di bawah satu operator dan digabungkan menjadi satu gate dengan masa terjumlah. Itu menjimatkan satu gate bagi setiap sambungan dan adalah tepat. Hantar merge_adjacent=False untuk mengekalkan jujukan buku teks. Walau bagaimanapun, tertib yang lebih tinggi memberikan ralat 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

Ulangan menukar kedalaman dengan ketepatan pada kadar yang berbeza. Ralat peringkat pertama menurun secara linear mengikut saiz langkah, manakala peringkat 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

Yang mana pertukaran yang lebih baik bergantung pada operator. Perhatikan bahawa operator dengan satu kumpulan adalah tepat pada sebarang tertib, kerana faktor tunggal berkomutasi dengan dirinya sendiri, dan formula itu kemudian runtuh menjadi satu gate dan bukannya dipecahkan semula.

Ukur kos​

Ralat yang lebih kecil memerlukan lebih banyak faktor, dan setiap faktor memerlukan gate berbelit sebaik sahaja peringkat qubit mensintesisnya. Berapa banyak bergantung pada pengekodan, jadi kira melalui saluran paip pratetap, dengan asas ditetapkan dan tiada pengoptimuman peringkat qubit (optimization_level=0), untuk melihat harga setiap kaedah:

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

Kiraan tepat bergantung pada cara Qiskit mensintesis gate evolusi, yang berubah antara keluaran, jadi plot menunjukkannya dan bukannya jadual:

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 kumpulan bar ialah satu kaedah: Lie-Trotter, Suzuki tertib 2, dan Suzuki tertib 4. Tertib membeli ketepatan pada harga yang meningkat mengikut faktor rekursi, dan pengekodan unari membayar lebih daripada kod Gray bagi setiap kaedah kerana setiap faktornya dipetakan kepada lebih banyak gate berbelit. Pilih kaedah berdasarkan ketepatan yang anda perlukan dan pengekodan berdasarkan perkakasan, dan pisahkan kedua-dua pilihan itu: litar boson di atas tidak pernah berubah.

Gunakan satu kaedah pada keseluruhan litar​

Untuk menetapkan synthesis pada setiap gate, anda mesti menyalurkan pilihan itu melalui segala yang membina satu. Pas B2BSynthesis membuat pilihan itu sekali untuk satu saluran paip. confignya menamakan kaedah bagi setiap jenis gate, dan ia memilih kaedah serta mengembangkan 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}

Kaedah juga boleh dinamakan, dengan argumen untuk __init__nya, dan bukannya dibina: B2BSynthesis({"Evolution": ("SuzukiTrotter", (), {"order": 2})}). Nama yang tersedia ialah nama dalam B2BSynthesisPluginManager, dan kaedah yang tidak didaftarkan di mana-mana boleh diserahkan sebagai instance, seperti di atas.

Dengan apply=False pas itu hanya memilih kaedah dan membiarkan pengembangan sehingga ke titik yang dikehendaki, contohnya untuk memeriksa litar dahulu. Gate atomik tidak pernah disentuh, malah tidak ditanda:

>>> 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 tidak dikembangkan sampai ke peringkat boson-ke-qubit secara utuh, di mana ia dipetakan tanpa synthesisnya dirujuk, jadi setiap kaedah kemudian menghasilkan output yang sama.

filter pilihan menghadkannya kepada gate yang mendapat manfaat. Evolusi yang semua kumpulannya berkomutasi, seperti Hamiltonian yang hanya terdiri daripada operator nombor, disintesis dengan tepat pada sebarang tertib, jadi tertib yang lebih tinggi hanya akan menambah kedalaman di situ.

Hadkan pas kepada gate yang mendapat manfaat​

Operator pepenjuru tulen menjelaskan perkara ini, kerana operator nombor berkomutasi sesama sendiri. Kumpulannya tepat pada peringkat pertama, dan peringkat kedua membelanjakan gate berbelit dengan sia-sia:

>>> # 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-dua litar adalah tepat, jadi yang kedua membelanjakan lebih banyak gate berbelit tanpa sebarang faedah. (Formula simetri tertib dua menggunakan kumpulan pertama dua kali pada separuh masa setiap satu, dan oleh itu kos bergantung pada kumpulan mana yang datang dahulu.) filter menerima predikat ke atas DAGOpNode dan membiarkan gate yang ditolak tidak disentuh, jadi satu pas boleh menaikkan tertib pada gate yang mendapat manfaat dan mengekalkan yang lain pada peringkat pertama. Predikat di bawah menerima gate hanya apabila operatornya memindahkan boson antara mod, iaitu apabila kumpulan gagal berkomutasi:

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

Jika anda menjalankan pas itu sekali lagi, tiada apa-apa yang berubah: faktor yang dikeluarkannya ialah atomik.

Faktor atomik​

Evolution yang ditandakan atomic tidak mempunyai takrifan, jadi decompose() membiarkannya di tempatnya walau berapa kali ia digunakan. Sintesis menandakan faktor yang dikeluarkannya dengan cara ini, begitu juga BosonicQDrift untuk sebutan yang disampelnya.

Ini lebih daripada sekadar kekemasan. Membahagikan kumpulan Hermitian dengan lebih lanjut, sebutan demi sebutan, menghasilkan faktor yang secara individu tidak Hermitian, dan eksponen operator bukan Hermitian tidak uniter:

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

Setiap faktor mesti Hermitian dengan sendirinya. Operator Hermitian yang dibahagikan kepada kumpulan boleh menghasilkan kumpulan bukan Hermitian, seperti sebutan lompatan yang dipisahkan daripada adjointnya. Semak dengan groups_are_hermitian(). Operator tanpa kumpulan dibahagikan sebutan demi sebutan, dan peringkat qubit kemudian menolak faktor dengan Operator contains complex coefficients. Simetrisasi tertib genap membatalkan sebahagian ralat faktor bukan Hermitian, jadi operator yang dikumpulkan dengan salah boleh kelihatan lebih baik pada tertib 2 berbanding tertib 1 sambil masih salah.

Ulang langkah pendek​

Sebagai alternatif peringkat litar, bina satu langkah Trotter pendek dan ulanginya. repeat() meratakan ulangan menjadi satu litar dan membiarkan sempadan bersebelahan, supaya pas boleh bertindak merentasinya:

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

Hantar insert_barriers=True untuk mengekalkan ulangan terpisah. Penghalang dipetakan kepada setiap qubit bagi daftar yang disentuhnya, kerana pengekodan memerlukan beberapa qubit bagi setiap mod dan mod tidak mempunyai imej setiap qubit tersendiri:

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

repeat tidak berkaitan dengan reps bagi kaedah sintesis. Sintesis membahagikan satu evolusi kepada lebih banyak faktor yang lebih pendek. Mengulang litar yang mengandungi satu evolusi hanya menggandakan jumlah masanya.

Gabungkan dengan formula peringkat qubit​

Kedua-dua peringkat boleh digabungkan, dan di mana kedua-duanya menganggar, ketepatan hasil ditentukan oleh yang lebih lemah antara keduanya. Menaikkan tertib boson sementara formula sebelah qubit kekal peringkat pertama memberikan sedikit manfaat. Lihat MapperFnEvolutionSynthesis untuk formula sebelah qubit. Ia mengekalkan tertib sebutan Pauli yang dipilih oleh pemeta, dan oleh itu group_wise() boleh mengeluarkan sebutan setiap kumpulan bersebelahan antara satu sama lain, dan simplify() boleh menetapkan tertib kanonik sebaliknya.

Langkah seterusnya​