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 Bose-Hubbard dengan pilihan pengekodan

Panduan ini membina litar untuk dinamik masa model Bose-Hubbard satu dimensi, daripada menulis Hamiltonian hingga menyemak ketumpatan tapak yang telah berkembang terhadap rujukan tepat. Apa yang menjadikan laluan ini menarik ialah satu idea:

Jangan pilih pengekodan boson-ke-qubit semasa anda membina litar. Bina litar pada mod bosonik, dan biarkan Transpiler menentukan bagaimana mod tersebut menjadi qubit.

Bagi boson, anda hampir sentiasa perlu menangguhkan pengekodan. Satu mod bosonik memegang zarah sebanyak mana pun, jadi ruang Focknya berdimensi tak terhingga dan tiada pengekodan wujud sehingga anda memilih pemangkasan dd (bilangan aras penghunian yang anda kekalkan). Dalam kod, pemangkasan diberikan sebagai penghunian terbesar yang boleh dipegang oleh satu mod, max_occupation =d−1= d - 1, jadi satu mod yang mengekalkan aras 0,1,20, 1, 2 mempunyai max_occupation = 2 dan d=3d = 3.

Itu menjadikan pemangkasan sebagai parameter pengekodan, bukan parameter fizik, dan ia ialah parameter yang anda mahu ubah: terlalu kecil dan jawapan salah, terlalu besar dan litar menjadi lebar tanpa keperluan. Jika anda menangguhkan pengekodan sehingga masa transpilasi, anda boleh menyapu pemangkasan tanpa mengubah litar, Hamiltonian, atau pengumpulan.

Lihat juga

Panduan operator untuk struktur data operator, panduan mapper untuk pengekodan dan resipi menulis mapper tersuai, dan panduan transpilasi untuk peringkat Transpiler yang dirujuk sepanjang panduan ini.

Pemangkasan, dan kosnya​

Apabila anda memangkas satu mod kepada dd aras, anda memecahkan komutator kanonik pada aras terakhir yang dikekalkan [1], jadi tiada pengekodan yang merupakan isomorfisme aljabar: satu pengekodan bosonik memetakan keadaan, dan operator yang bertindak dalam subruang terpangkas. Dua akibat penting untuk semua yang berikut:

  1. dd mesti membatasi penghunian yang boleh dicapai oleh dinamik, bukan sekadar penghunian keadaan awal.

  2. Pemetaan tidak berkomut dengan penertiban normal, jadi putuskan sekali sama ada Hamiltonian anda ditakrifkan dalam bentuk tertib normal, dan kekal konsisten.

Panduan mapper menerbitkan kecacatan itu dan menunjukkan kedua-dua akibat pada kod yang berjalan.

1. Hamiltonian​

Model Bose-Hubbard pada kekisi dengan ikatan ⟨ij⟩\langle i j \rangle ialah

H=−t∑⟨ij⟩(bi†bj+bj†bi)+U2∑ini(ni−1) ,H = -t \sum_{\langle i j \rangle} \left(b^\dagger_i b_j + b^\dagger_j b_i\right) + \frac{U}{2} \sum_i n_i (n_i - 1) \, ,

dengan ni=bi†bin_i = b^\dagger_i b_i. Interaksi mengira pasangan boson pada satu tapak, jadi ia memerlukan satu tapak memegang dua zarah serentak. Dalam tertib normal, ni(ni−1)=bi†bi†bibin_i(n_i-1) = b^\dagger_i b^\dagger_i b_i b_i, iaitu bentuk yang digunakan seterusnya.

Geometri hanya masuk melalui senarai tepi, jadi tiada apa yang khusus kepada rantai:

>>> from qiskit_bosons.operators import BosonOperator, ann, cre
>>>
>>> def bose_hubbard(edges, num_sites, tunneling, interaction):
... """Build the normal-ordered Bose-Hubbard Hamiltonian on an arbitrary lattice."""
... terms = {}
... for i, j in edges:
... terms[(cre(i), ann(j))] = -tunneling
... terms[(cre(j), ann(i))] = -tunneling
... for i in range(num_sites):
... # U/2 * n_i (n_i - 1), already normal-ordered
... terms[(cre(i), cre(i), ann(i), ann(i))] = 0.5 * interaction
... return BosonOperator.from_dict(terms)
>>>
>>> num_sites = 4
>>> edges = [(i, i + 1) for i in range(num_sites - 1)]
>>> hamiltonian = bose_hubbard(edges, num_sites, tunneling=1.0, interaction=2.0)
>>> print(f"Number of terms: {len(hamiltonian)}")
Number of terms: 10

Dua sifat patut disemak dahulu. Hamiltonian mesti Hermitian supaya e−itHe^{-itH} unitari, dan ia mengekalkan jumlah bilangan boson:

>>> hamiltonian.is_hermitian()
True
>>> hamiltonian.conserves_particle_number()
True

Sekarang soalan bosonik: berapa banyak aras yang dituntut oleh operator ini?

>>> hamiltonian.max_occupation()
2

Sebutan interaksi menggunakan dua operator penciptaan pada satu mod, jadi satu mod mesti memegang dua boson supaya sebutan itu bertindak sama sekali. Itu ialah penghunian maksimum sebanyak 2, iaitu nombor yang diambil oleh LevelSpec sebagai max_occupationnya. Satu mod yang mengekalkan penghunian 0,1,20, 1, 2 mempunyai d=max_occupation+1=3d = \texttt{max\_occupation} + 1 = 3 aras, jadi max_occupation >= 2 ialah keperluan yang sama dengan d≥3d \geq 3.

Ini hanyalah had bawah: ia datang daripada satu penggunaan operator, dan penggunaan berulang sepanjang evolusi masa boleh sampai lebih jauh. Ia menolak potongan yang pasti terlalu kecil, dan langkah 6 menentukan, bagi model dan keadaan ini, seberapa besar potongan itu mesti. mode_occupations() melaporkan had bagi setiap mod secara berasingan, iaitu perkara yang dibandingkan dengan potongan per-mod.

2. Kumpulkan sebelum memetakan​

Evolusi ini ditrotterkan, dan cara Hamiltonian dipartisi menentukan apa yang diberikan kepada sintesis. Dua pertimbangan menuju ke arah yang sama.

Pertama, satu yang fizikal: sebutan lompatan individu bi†bjb^\dagger_i b_j bukan Hermitian. Hanya pasangan bi†bj+bj†bib^\dagger_i b_j + b^\dagger_j b_i yang Hermitian. Jika anda mengevolusikan di bawah satu sebutan bukan Hermitian, operasi itu tidak unitari, dan operator terpeta akan membawa pekali kompleks yang ditolak oleh PauliEvolutionGate. Oleh itu, dua sebutan lompatan bagi setiap ikatan mesti kekal bersama.

Kedua, satu yang berkaitan kecekapan: semua sebutan interaksi atas-tapak adalah pepenjuru dalam asas penghunian dan saling berkomut, jadi ia boleh berkongsi satu kumpulan dan disintesis bersama. Perkara yang sama berlaku bagi ikatan yang tidak menyentuh tapak sepunya. Pada rantai, setiap ikatan kedua ialah set sedemikian: ikatan 0 dan 2 bertindak pada pasangan mod yang tidak bertindih, jadi sebutan lompatan mereka berkomut dan boleh berkongsi satu kumpulan, dan ikatan 1 membentuk kumpulannya sendiri. Jika anda mengumpulkan ikatan genap dan ganjil, anda hanya memerlukan tiga kumpulan, sedangkan satu kumpulan bagi setiap ikatan memerlukan empat. Kumpulan yang lebih sedikit bermakna faktor yang lebih sedikit, dan juga litar yang lebih cetek: dengan kod Gray pada max_occupation = 2 kedalaman dua-qubit ialah 95 berbanding 139. Kekisi menentukan ikatan mana yang boleh berkongsi satu kumpulan, itulah sebabnya partisi dipilih di sini.

Atribut groups membawa keputusan itu ke dalam litar: Gate Evolution ke atas operator berkumpulan terurai kepada satu subevolusi bagi setiap kumpulan. Partisi dipilih pada peringkat bosonik, ketika struktur kekisi masih kelihatan, dan ia kekal tidak terjejas sepanjang pemetaan. Panduan pengumpulan merangkumi semakan yang patut dilepasi oleh sesuatu pengumpulan.

>>> def group_of(actions, edges):
... """Assign each term to the even bonds, the odd bonds, or the on-site group."""
... creations = sorted(mode for action, mode in actions if action)
... annihilations = sorted(mode for action, mode in actions if not action)
... if creations == annihilations:
... return 2 # diagonal: the on-site interaction group
... bond = tuple(sorted(set(creations) | set(annihilations)))
... return edges.index(bond) % 2 # bonds 0 and 2 share a group, bond 1 has its own
>>>
>>> hamiltonian.groups = [group_of(actions, edges) for actions, _ in hamiltonian.iter_terms()]
>>> hamiltonian.num_groups()
3

Setiap kumpulan mesti Hermitian sendiri, iaitu apa yang diperoleh daripada pemasangan sebelumnya. groups_are_hermitian() menyemak semua kumpulan dalam satu panggilan:

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

Untuk melihat apa yang dipegang oleh setiap kumpulan, asingkan kumpulan:

>>> for index, group in enumerate(hamiltonian.split_out_groups()):
... print(f"group {index}: {len(group)} terms, hermitian={group.is_hermitian()}")
group 0: 4 terms, hermitian=True
group 1: 2 terms, hermitian=True
group 2: 4 terms, hermitian=True

Pengumpulan genap-dan-ganjil ialah satu pilihan, dan alternatifnya mudah diukur. Jika anda memberikan setiap ikatan kumpulannya sendiri, anda memerlukan empat kumpulan, dan litar yang sama kemudian menelan kedalaman dua-qubit yang lebih tinggi. Kedalaman dikira selepas litar diturunkan dengan kod Gray pada max_occupation = 2, yang diterangkan oleh langkah 4:

>>> from qiskit.transpiler import PassManager
>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution
>>> from qiskit_bosons.mappers import LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> def two_qubit_depth(operator):
... """The two-qubit depth of one evolution under ``operator``, lowered with the Gray code."""
... probe = BosonicCircuit(num_sites)
... probe.append(Evolution(num_sites, operator, time=0.4), probe.modes)
... pass_manager = generate_preset_occupation_pass_manager(LevelSpec(2, "gray"))
... pass_manager.qubit = PassManager()
... lowered = pass_manager.run(probe.decompose())
... return lowered.decompose(reps=6).depth(lambda instruction: len(instruction.qubits) == 2)
>>>
>>> per_bond = bose_hubbard(edges, num_sites, tunneling=1.0, interaction=2.0)
>>> per_bond.groups = [
... len(edges) if sorted(m for a, m in actions if a) == sorted(m for a, m in actions if not a)
... else edges.index(tuple(sorted({m for _, m in actions})))
... for actions, _ in per_bond.iter_terms()
... ]
>>> per_bond.num_groups(), two_qubit_depth(per_bond)
(4, 139)
>>> hamiltonian.num_groups(), two_qubit_depth(hamiltonian)
(3, 95)

3. Litar bosonik​

Perhatikan apa yang tiada dalam litar. Tiada pemangkasan, tiada pengekodan, dan tiada bilangan qubit. Ia bertindak pada empat mod bosonik:

>>> from qiskit_bosons.circuit import BosonicCircuit
>>> from qiskit_bosons.circuit.library import Evolution, InitializeModes
>>>
>>> total_time = 0.4
>>> occupation = [1, 1, 0, 0] # one boson on each of the first two sites
>>>
>>> circuit = BosonicCircuit(num_sites)
>>> circuit.append(InitializeModes(occupation), circuit.modes)
>>> circuit.append(Evolution(num_sites, hamiltonian, time=total_time), circuit.modes)
>>> dict(circuit.count_ops())
{'InitializeModes': 1, 'Evolution': 1}

A BosonicCircuit on four modes with an InitializeModes gate followed by one Evolution gate.

Wayar ialah mod bosonik, bukan qubit. Perhatikan bahawa InitializeModes mengambil integer, bukan bendera: satu mod boleh memegang sebarang bilangan boson, jadi keadaan Fock ialah senarai nombor penghunian.

Satu decompose() menukar pengumpulan daripada langkah 2 kepada satu Gate bagi setiap kumpulan:

>>> decomposed = circuit.decompose()
>>> dict(decomposed.count_ops())
{'Evolution': 3, 'InitializeModes': 1}

The decomposed BosonicCircuit with one Evolution gate per group.

Kumpulan pertama memegang ikatan pada mod 0 dan 1 serta pada mod 2 dan 3, jadi Gatenya merentangi keempat-empat mod, begitu juga kumpulan atas-tapak. Ikatan tengah mempunyai Gate tersendiri pada mod 1 dan 2.

Langkau penguraian itu dan pengumpulan hanya diabaikan. Kaedah yang melakukan pemisahan ialah synthesis bagi Gate, dengan BosonicLieTrotter sebagai lalai; Mensintesis Gate Evolution dalam ruang bosonik menunjukkan alternatifnya.

4. Pilih pengekodan pada masa transpilasi​

Inilah manfaatnya. Litar yang sama diturunkan empat kali, di bawah empat nilai LevelEncoding yang berbeza, dengan menukar satu argumen. Satu LevelSpec memasangkan pengekodan dengan pemangkasan bagi setiap mod, dan pratetap berikut menerbitkan susun atur, penyediaan keadaan, dan pemetaan operator daripada satu spesifikasi itu (lihat Huraikan pemangkasan dengan LevelSpec). Rentetan seperti "gray" diterima di mana-mana sahaja LevelEncoding diterima.

>>> from qiskit.transpiler import PassManager
>>> from qiskit_bosons.mappers import LevelEncoding, LevelSpec
>>> from qiskit_bosons.transpiler.presets import generate_preset_occupation_pass_manager
>>>
>>> max_occupation = 2 # the largest occupation per mode, so d = max_occupation + 1 = 3 levels
>>>
>>> encodings = (
... LevelEncoding.UNARY_PROJECTED,
... LevelEncoding.UNARY_UNPROJECTED,
... LevelEncoding.STANDARD_BINARY,
... LevelEncoding.GRAY,
... )
>>> for encoding in encodings:
... pass_manager = generate_preset_occupation_pass_manager(
... LevelSpec(max_occupation, encoding)
... )
... pass_manager.qubit = PassManager() # keep the circuit as the pipeline synthesized it
... qubit_circuit = pass_manager.run(decomposed)
... basis = qubit_circuit.decompose(reps=6)
... two_qubit = basis.depth(lambda instruction: len(instruction.qubits) == 2)
... print(f"{encoding!s:18s} qubits={qubit_circuit.num_qubits:2d} 2q-depth={two_qubit:4d}")
unary_projected qubits=12 2q-depth= 645
unary_unprojected qubits=12 2q-depth= 383
standard_binary qubits= 8 2q-depth= 218
gray qubits= 8 2q-depth= 95

Nombor-nombor ini bercanggah dengan andaian yang semula jadi. Pengekodan unari menggunakan dd qubit bagi setiap mod berbanding ⌈log⁡2d⌉\lceil \log_2 d \rceil bagi kod padat, dan pada d=3d = 3 kod padat menang pada kedua-dua paksi serentak: qubit lebih sedikit dan jauh lebih sedikit Gate berbelit. Kedua-dua varian unari berbeza pada operator yang dihasilkan, bukan pada susun atur mereka: LevelEncoding.UNARY_PROJECTED membawa projektor pada setiap qubit selang-seli bagi satu mod, yang menjadikannya paling mahal untuk dievolusikan antara keempat-empatnya, dan LevelEncoding.UNARY_UNPROJECTED meninggalkan projektor dan bersetuju dengannya pada subruang satu-panas. Panduan mapper menunjukkan kedua-dua operator bersebelahan, dan menerangkan mengapa kod Gray mengatasi binari standard (SB) pada sebutan lompatan.

Sawaya et al. melaporkan susunan yang sama [2]:

walaupun kod unari cenderung memerlukan lebih banyak qubit tetapi lebih sedikit operasi, lazimnya kod SB atau Gray lebih cekap dari segi bilangan qubit dan bilangan operasi

Kesimpulan mereka ialah kod terbaik bergantung pada Hamiltonian dan dd, yang merupakan hujah terkuat untuk tidak menetapkan pilihan itu dalam litar.

Nota

Di bawah pengekodan aras, satu mod merentangi beberapa qubit, jadi daftar qubit lebih lebar daripada daftar mod. Pratetap menggunakan OccupationB2QLayout, yang menerbitkan lebar daftar daripada LevelSpec. Panduan transpilasi menerangkan peringkat yang dijalankan oleh pratetap.

5. Sahkan terhadap dinamik tepat​

Tiada satu pun langkah sebelumnya mengesahkan bahawa litar itu betul. Untuk itu, bandingkan ketumpatan tapak ⟨nm⟩\langle n_m \rangle selepas evolusi terhadap rujukan tepat yang dikira pada Hamiltonian terkod.

Rujukan mesti dikira dalam ruang terpangkas yang sama dengan yang digunakan litar, kerana membandingkan terhadap pengiraan tanpa pemangkasan akan mengelirukan ralat Trotter dengan ralat pemangkasan, yang merupakan perkara berbeza dan wajar diasingkan (lihat Pangkas ruang Fock).

>>> import numpy as np
>>> from qiskit.quantum_info import SparsePauliOp, Statevector
>>> from scipy.sparse.linalg import expm_multiply
>>> from qiskit_bosons.mappers.library import occupation_encoding
>>>
>>> def sparse_matrix(operator, encoding):
... """Map a bosonic operator and hand back a sparse qubit matrix."""
... spec = LevelSpec(max_occupation, encoding).resolve(num_sites)
... observable = occupation_encoding(operator, spec)
... return SparsePauliOp.from_sparse_observable(observable).to_matrix(sparse=True)
>>>
>>> def fock_index(occupation, encoding):
... """The computational basis index encoding a product Fock state."""
... spec = LevelSpec(max_occupation, encoding).resolve(num_sites)
... return sum(
... encoding.level_to_bits(n) << offset for n, offset in zip(occupation, spec.offsets)
... )
>>>
>>> def densities(state, encoding):
... """The per-site boson number <n_m> of an encoded state vector."""
... out = []
... for mode in range(num_sites):
... number = BosonOperator.from_dict({(cre(mode), ann(mode)): 1.0})
... matrix = sparse_matrix(number, encoding)
... out.append(float((state.conj() @ (matrix @ state)).real))
... return out

Dengan itu di tangan, evolusi tepat dalam ruang terpangkas:

>>> encoding = LevelEncoding.GRAY
>>> num_qubits = LevelSpec(max_occupation, encoding).resolve(num_sites).total_width
>>>
>>> initial = np.zeros(2**num_qubits, dtype=complex)
>>> initial[fock_index(occupation, encoding)] = 1.0
>>>
>>> evolved_exact = expm_multiply(
... -1j * total_time * sparse_matrix(hamiltonian, encoding), initial
... )
>>> reference = densities(evolved_exact, encoding)
>>> for site, value in enumerate(reference):
... print(f"site {site}: {value:.6f}")
site 0: 0.993834
site 1: 0.858853
site 2: 0.141309
site 3: 0.006004

Boson merebak dari hujung rantai yang dihuni ke arah yang kosong, dan kedua-dua boson terabadi:

>>> bool(np.isclose(sum(reference), 2.0))
True

Sekarang litar. Perhatikan bahawa rujukan sebelumnya dibina daripada initial secara langsung, jadi litar yang dibandingkan dengannya mesti hanya mengandungi evolusi, kerana Gate InitializeModes akan menyediakan semula keadaan yang sudah disediakan.

>>> from qiskit_bosons.circuit.library.synthesis import BosonicLieTrotter
>>>
>>> def trotter_densities(steps, encoding):
... """Site densities after `steps` first-order Trotter steps of the grouped evolution."""
... circuit = BosonicCircuit(num_sites)
... circuit.append(
... Evolution(
... num_sites, hamiltonian, time=total_time, synthesis=BosonicLieTrotter(reps=steps)
... ),
... circuit.modes,
... )
... pass_manager = generate_preset_occupation_pass_manager(
... LevelSpec(max_occupation, encoding)
... )
... qubit_circuit = pass_manager.run(circuit.decompose())
... # decompose BEFORE evolving: see the warning that follows
... basis = qubit_circuit.decompose(reps=6)
... state = np.asarray(Statevector(initial).evolve(basis).data)
... return densities(state, encoding)
>>>
>>> for steps in (1, 2, 4, 8, 16):
... error = max(
... abs(a - b) for a, b in zip(trotter_densities(steps, encoding), reference)
... )
... print(f"steps={steps:2d} max density error = {error:.4f}")
steps= 1 max density error = 0.0843
steps= 2 max density error = 0.0389
steps= 4 max density error = 0.0183
steps= 8 max density error = 0.0088
steps=16 max density error = 0.0043

Ralat menjadi separuh apabila bilangan langkah berganda, iaitu tandatangan O(t2/steps)O(t^2/\text{steps}) bagi formula hasil darab peringkat pertama. Itulah bukti bahawa percanggahan ialah ralat Trotter dan pengekodan adalah setia.

Penting

Pengulangan datang daripada synthesis bagi Gate, yang mentrotterkan dalam ruang bosonik tempat pengumpulan masih wujud: setiap langkah menggunakan setiap kumpulan sekali, dan langkah-langkah itu berturutan. Itu berbeza daripada menyerahkan LieTrotter dengan reps > 1 kepada pemalam sintesis peringkat qubit. Di situ, setiap kumpulan sudah menjadi PauliEvolutionGate tersendiri, jadi formula hanya membahagikan sebutan dalam satu kumpulan dan tidak pernah menyelang-nyelikan kumpulan. Kumpulan tidak saling berkomut, jadi itu meninggalkan satu langkah Trotter luar yang ralatnya tidak mengecil sebesar mana pun reps. Mensintesis Gate Evolution dalam ruang bosonik membandingkan kedua-duanya dan merangkumi peringkat lebih tinggi.

Amaran

Panggilan decompose() sebelumnya adalah penting. evolve() menggunakan takrifan tepat bagi PauliEvolutionGate dan memintas formula hasil darab sepenuhnya. Jika anda mengesahkan terhadap litar yang tidak diuraikan, anda melihat persetujuan hampir sempurna walaupun bagi sintesis yang sengaja salah. Litar yang tidak diuraikan juga memaksa operator padat 2n×2n2^{n} \times 2^{n} dibina, manakala litar yang diuraikan menggunakan Gate satu demi satu. Simulator lain boleh memperlakukan Gate secara berbeza, jadi semakan ini bergantung pada simulator, manakala perkakasan sentiasa memerlukan bentuk yang diuraikan.

Akhir sekali, semakan utama: fizik tidak boleh bergantung pada pengekodan yang dipilih.

>>> def exact_densities(encoding):
... """Exact truncated-space densities, computed under a given encoding."""
... width = LevelSpec(max_occupation, encoding).resolve(num_sites).total_width
... start = np.zeros(2**width, dtype=complex)
... start[fock_index(occupation, encoding)] = 1.0
... evolved = expm_multiply(
... -1j * total_time * sparse_matrix(hamiltonian, encoding), start
... )
... return densities(evolved, encoding)
>>>
>>> all(
... bool(np.allclose(exact_densities(name), reference, atol=1e-10))
... for name in encodings
... )
True

Bilangan qubit, struktur Pauli, dan kedalaman litar semuanya berbeza, dan fizik bersetuju hingga sepuluh tempat perpuluhan. Pengekodan ialah pilihan pelaksanaan, itulah sebabnya ia milik Transpiler. Kedua-dua varian unari bersetuju di sini kerana keadaan yang disimulasikan kekal pada subruang satu-panas; di luarnya ia akan berbeza.

6. Pemangkasan ialah parameter penumpuan​

Langkah akhir khusus untuk boson. Semua setakat ini menetapkan max_occupation = 2, iaitu d=3d = 3. Namun, pemangkasan bukan pilihan bebas: ia ialah parameter penumpuan, dan jawapan hanya boleh dipercayai selepas ia berhenti berubah (lihat Pangkas ruang Fock).

Litar tidak membawa pemangkasan, jadi litar itu sendiri tidak berubah apabila potongan berubah: hanya transpilasi diulang. Apa yang berubah ialah saiz hasilnya, dan sapuan hanya semurah ini kerana sistemnya kecil. Rujukan berikut ialah simulasi klasik tepat bagi dinamik terpangkas, yang mungkin untuk empat tapak dan bertambah secara eksponen dengan bilangan qubit. Bagi masalah di luar jangkauan itu tiada rujukan tepat untuk dibandingkan, dan penumpuan mesti dinilai dengan cara lain.

>>> def densities_at_cutoff(max_occ, encoding=LevelEncoding.GRAY):
... """Exact truncated-space densities at a given cutoff."""
... spec = LevelSpec(max_occ, encoding).resolve(num_sites)
...
... start = np.zeros(2**spec.total_width, dtype=complex)
... start[
... sum(encoding.level_to_bits(n) << offset for n, offset in zip(occupation, spec.offsets))
... ] = 1.0
...
... def matrix(operator):
... observable = occupation_encoding(operator, spec)
... return SparsePauliOp.from_sparse_observable(observable).to_matrix(sparse=True)
...
... evolved = expm_multiply(-1j * total_time * matrix(hamiltonian), start)
... out = []
... for mode in range(num_sites):
... number = BosonOperator.from_dict({(cre(mode), ann(mode)): 1.0})
... out.append(float((evolved.conj() @ (matrix(number) @ evolved)).real))
... return out
>>>
>>> previous = None
>>> for max_occ in (1, 2, 3, 4):
... current = densities_at_cutoff(max_occ)
... # Report the drift bucketed rather than to full precision: after it reaches machine
... # epsilon, the exact digits are arithmetic noise and vary between BLAS builds.
... if previous is None:
... drift = "--"
... else:
... gap = max(abs(a - b) for a, b in zip(current, previous))
... drift = f"{gap:.0e}" if gap > 1e-12 else "< 1e-12"
... print(f"d={max_occ + 1} "
... + " ".join(f"{x:.6f}" for x in current)
... + f" drift={drift}")
... previous = current
d=2 0.993986 0.858173 0.141827 0.006014 drift=--
d=3 0.993834 0.858853 0.141309 0.006004 drift=7e-04
d=4 0.993834 0.858853 0.141309 0.006004 drift=< 1e-12
d=5 0.993834 0.858853 0.141309 0.006004 drift=< 1e-12

Pada max_occupation = 1 (d=2d = 2) setiap tapak memegang paling banyak satu boson, jadi sebutan interaksi, yang memerlukan dua pada satu tapak, tidak boleh bertindak langsung, dan ketumpatan tersasar kira-kira 7×10−47 \times 10^{-4}. Apabila anda menambah aras ketiga, hasilnya menangkap interaksi, dan dari max_occupation = 3 seterusnya hasilnya pegun hingga ketepatan mesin: bertumpu.

Sebab ia berhenti pada max_occupation = 2 ialah bilangan zarah. Hamiltonian mengabadikan bilangan boson, dan keadaan ini memegang dua, jadi tiada tapak boleh memegang lebih daripada dua, walau apa pun masa evolusi dan sama ada interaksi bersifat tolakan atau tarikan. Jika anda mengevolusikan hingga t=6t = 6 dan bukannya t=0.4t = 0.4, atau menterbalikkan tanda UU, ketumpatan pada max_occupation = 2 dan lebih tinggi kekal tidak berubah hingga 10−1310^{-13}.

Apa yang benar-benar menaikkan keperluan ialah bilangan boson. Tiga boson pada rantai yang sama berubah kira-kira 2×10−32 \times 10^{-3} antara max_occupation = 2 dan 3 selepas evolusi pendek yang sama, dan kira-kira 0.30.3 selepas t=6t = 6. Itu ialah pernyataan tentang model ini dan kuantiti terabadi, dan ia hanya had bagi operator yang mengabadikan bilangan zarah; ia bukan peraturan am, jadi potongan mesti disapu, bukan diteka.

Sapuan juga ada harganya di sisi litar, yang tidak ditunjukkan oleh rujukan tepat. Litar bosonik yang sama, diturunkan dengan kod Gray pada setiap potongan:

>>> for max_occ in (1, 2, 3, 4):
... pass_manager = generate_preset_occupation_pass_manager(
... LevelSpec(max_occ, LevelEncoding.GRAY)
... )
... pass_manager.qubit = PassManager()
... lowered = pass_manager.run(decomposed)
... depth = lowered.decompose(reps=6).depth(lambda instruction: len(instruction.qubits) == 2)
... print(f"d={max_occ + 1} qubits={lowered.num_qubits:2d} 2q-depth={depth:4d}")
d=2 qubits= 4 2q-depth= 8
d=3 qubits= 8 2q-depth= 95
d=4 qubits= 8 2q-depth= 217
d=5 qubits=12 2q-depth=1176

Setiap aras tambahan menelan kedalaman, dan bilangan qubit melonjak setiap kali dd melepasi kuasa dua. Apabila anda menumpu dalam dd, anda justeru menukar ketepatan dengan saiz litar, jadi sapuan lebih daripada sekadar semakan berangka.

Perhatikan juga bahawa d=2d = 2 hampir betul ialah satu kebetulan bagi cerapan: ketumpatan tapak hampir tidak bergerak, manakala keadaan asasnya salah secara kualitatif (ia tiada komponen berpenghunian dua). Jika satu cerapan menumpu, itu tidak menunjukkan bahawa cerapan lain telah menumpu.

Apa yang perlu diambil​

  • Pengekodan ialah isu Transpiler. Litar boson dalam langkah 3 tidak menyebut pemotongan, pengekodan, mahupun bilangan qubit. Empat pengekodan kemudian menurunkannya kepada empat litar yang berbeza, semuanya menghasilkan semula fizik yang sama. Tiada apa-apa di hulu Transpiler yang perlu diubah.

  • Bagi boson, ini bukan sekadar memudahkan. Pemotongan ialah parameter penumpuan yang perlu disapu, dan ia terletak di dalam pengekodan. Litar yang dibina berdasarkan pengekodan tetap perlu dibina semula untuk setiap nilai dd.

  • Kumpulkan sebelum pemetaan, dan kumpulkan demi kehermitan. Satu sebutan lompatan tunggal tidak Hermitian dan tidak boleh dieksponenkan; pasangan ikatan pula Hermitian dan oleh itu boleh dieksponenkan. Tentukan pembahagian pada peringkat boson, di mana kekisi masih kelihatan. Itu perlu secara fizikal, dan atribut groups wujud untuk membawa keputusan itu ke hadapan.

  • Jangan anggap pengekodan berpemberat rendah ialah yang murah. Pada d=3d = 3 kod padat mengalahkan kedua-dua varian unari dari segi qubit dan gate serentak, dan varian terunjur ialah yang paling mahal. Ukur; titik silangnya bergantung pada dd.

  • Pemotongan merosakkan aljabar, secara yang boleh dikuantifikasikan. Hanya pada aras teratas yang dikekalkan, sebanyak dd. Itulah juga sebabnya pemetaan tidak berkomutasi dengan tertib normal.

Langkah seterusnya​

Dua arah memanjangkan contoh ini. Jika anda menggantikan senarai tepi rantai dengan ikatan kekisi segi empat sama, anda tidak perlu mengubah apa-apa lagi, kerana fungsi bose_hubbard yang ditakrifkan sebelum ini sudah tidak bergantung pada geometri. Pengekodan tersuai anda sendiri (contohnya kod unari-blok, yang menginterpolasi antara unari dan padat) juga boleh disambungkan melalui sambungan MapperFnEvolutionSynthesis yang sama yang digunakan oleh pratetap.

Penting

Setiap kedalaman yang dilaporkan adalah pada daftar abstrak yang bersambung sepenuhnya. Pemetaan kepada kesambungan perkakasan sebenar ialah peringkat berasingan, dan blok qubit setiap mod bagi pengekodan penghunian memberikan penghala struktur yang agak berbeza untuk digunakan berbanding satu qubit setiap mod.

[1]

R. Somma, G. Ortiz, E. Knill, and J. Gubernatis, Quantum Simulations of Physics Problems (2003), arXiv:quant-ph/0304063, Sec. 4, Eq. 22.

[2]

N. P. D. Sawaya, T. Menke, T. H. Kyaw, S. Johri, A. Aspuru-Guzik, and G. G. Guerreschi, Resource-efficient digital quantum simulation of d-level systems for photonic, vibrational, and spin-s Hamiltonians, npj Quantum Information 6, 49 (2020), arXiv:1909.12847.