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?

Jana litar evolusi masa Krylov (SKQD)

Penting

Konsep dalam panduan ini kini hanya tersedia dalam API Python. Fungsi setara akan disediakan dalam API C dalam keluaran akan datang.

Pepenyetuaan Kuantum Krylov Berasaskan Sampel (SKQD) ialah varian pepenyetuaan subruang yang berpusatkan kuantum. Daripada mensampel satu ansatz variasi tunggal (seperti dalam pepenyetuaan kuantum berasaskan sampel biasa (SQD)) atau satu ensembel litar evolusi masa rawak (seperti dalam SqDRIFT), ia mensampel asas Krylov: keluarga keadaan

∣ψk⟩=e−ik Δt H∣ψ0⟩,k=0,1,…,D−1,\lvert \psi_k \rangle = e^{-i k \, \Delta t \, H} \lvert \psi_0 \rangle, \qquad k = 0, 1, \dots, D - 1,

yang diperoleh dengan mengevolusikan keadaan rujukan ∣ψ0⟩\lvert \psi_0 \rangle untuk gandaan yang meningkat bagi langkah masa tetap Δt\Delta t. Rentetan bit yang disampel daripada DD litar ini mengisi subruang Krylov yang di dalamnya Hamiltonian kemudiannya dipepenyetuakan secara klasik.

Panduan ini menghasilkan semula langkah pembinaan litar bagi algoritma SKQD untuk model Anderson satu-bendasing (SIAM), iaitu bendasing magnetik yang ditambat kepada rendaman tanpa interaksi, mengikut penerbitan SKQD.

Nota

Langkah pensampelan SKQD hanya berfungsi jika keadaan asas adalah jarang dalam asas pengiraan: pepenyetuaan klasik mesti dapat membinanya semula daripada bilangan rentetan bit sampel yang boleh diurus. Seperti yang ditunjukkan di bawah, kejarangan itu tidak berlaku secara automatik; ia merupakan sifat asas satu-zarah yang digunakan untuk menyatakan masalah. Mendapatkan asas yang betul adalah inti pembinaan ini.

1. Bina Hamiltonian SIAM sebagai operator fermionik​

Model Anderson satu-bendasing meletakkan satu orbital bendasing berinteraksi (indeks 00) bersentuhan dengan rendaman tanpa interaksi berjumlah LL tapak yang tersusun sebagai rantai. Hamiltoniannya terbahagi kepada bahagian satu-jasad H1H_1 dan bahagian dua-jasad H2H_2,

H=H1+H2,H = H_1 + H_2,

dengan bahagian satu-jasad mengumpulkan tenaga atas-tapak bendasing (keupayaan kimia μ\mu), hibridisasi bendasing–rendaman VV, dan lompatan rendaman jiran terdekat tt,

H1=μ∑σa0σ†a0σ−V∑σ(a0σ†a1σ+h.c.)−t∑σ∑i=1L−1(aiσ†ai+1,σ+h.c.),H_1 = \mu \sum_\sigma a^\dagger_{0\sigma} a_{0\sigma} - V \sum_\sigma \left( a^\dagger_{0\sigma} a_{1\sigma} + \text{h.c.} \right) - t \sum_{\sigma} \sum_{i=1}^{L-1} \left( a^\dagger_{i\sigma} a_{i+1,\sigma} + \text{h.c.} \right),

dan bahagian dua-jasad membawa tolakan Coulomb atas-tapak UU pada bendasing sahaja,

H2=U a0↑†a0↑a0↓†a0↓.H_2 = U \, a^\dagger_{0\uparrow} a_{0\uparrow} a^\dagger_{0\downarrow} a_{0\downarrow}.

Panduan ini bekerja pada titik simetri zarah-lohong μ=−U/2\mu = -U/2. Kedua-dua bahagian ini dipetakan terus kepada kamiran satu-jasad h1e (lompatan rendaman, hibridisasi bendasing, tenaga atas-tapak bendasing) dan kamiran dua-jasad tunggal h2e (sebutan Coulomb bendasing). Mula-mula himpunkannya dalam asas kedudukan, iaitu tempat model ditakrifkan secara semula jadi.

>>> import numpy as np
>>>
>>> norb = 8
>>> nelec = (norb // 2, norb // 2) # half filling
>>> hopping, onsite, hybridization = 1.0, 10.0, 1.0
>>> chemical_potential = -0.5 * onsite
>>>
>>> # one-body integrals: bath hopping, impurity hybridization, and impurity on-site energy
>>> h1e = np.zeros((norb, norb))
>>> np.fill_diagonal(h1e[:, 1:], -hopping)
>>> np.fill_diagonal(h1e[1:, :], -hopping)
>>> h1e[0, 1] = h1e[1, 0] = -hybridization
>>> h1e[0, 0] = chemical_potential
>>>
>>> # two-body integrals: on-site Coulomb repulsion on the impurity orbital
>>> h2e = np.zeros((norb,) * 4)
>>> h2e[0, 0, 0, 0] = onsite

2. Tukar kepada asas momentum​

Bahagian rendaman SIAM ialah rantai fermion bebas, jadi ia dipepenyetuakan oleh pertukaran kepada asas momentum. Ini ialah langkah penentu bagi SKQD. Oleh kerana rendaman tidak berubah terhadap translasi, keadaan asas tidak jarang dalam asas kedudukan, tetapi jarang dalam asas momentum, yang di dalamnya Hamiltonian rendaman adalah pepenjuru. Putaran orbital satu-zarah yang melakukan pertukaran ini ialah algebra linear khusus masalah. Ia memepenjurukan matriks lompatan rendaman sambil membiarkan orbital bendasing tidak disentuh, kemudian memindahkan bendasing ke tapak tengah:

>>> def momentum_basis(norb):
... """Orbital rotation from the position to the momentum basis."""
... n_bath = norb - 1
... hopping_matrix = np.zeros((n_bath, n_bath))
... np.fill_diagonal(hopping_matrix[:, 1:], -1)
... np.fill_diagonal(hopping_matrix[1:, :], -1)
... _, vecs = np.linalg.eigh(hopping_matrix)
... orbital_rotation = np.zeros((norb, norb))
... orbital_rotation[0, 0] = 1.0
... orbital_rotation[1:, 1:] = vecs
... new_index = n_bath // 2
... perm = np.r_[1:(new_index + 1), 0, (new_index + 1):norb]
... return orbital_rotation[:, perm]
...
>>> orbital_rotation = momentum_basis(norb)

Tidak seperti aliran kerja variasi, ini memutarkan kamiran Hamiltonian ke dalam asas momentum, supaya keseluruhan litar (Hamiltonian dan keadaan sekali gus) berada dalam asas yang keadaan asasnya jarang dan rentetan bit yang disampel oleh itu bermaklumat. Memutarkan kamiran satu- dan dua-jasad ialah perubahan asas piawai bagi tensor struktur elektronik:

>>> rotation = orbital_rotation.T.conj()
>>> h1e_momentum = np.einsum("ab,Aa,Bb->AB", h1e, rotation, rotation.conj(), optimize="greedy")
>>> h2e_momentum = np.einsum(
... "abcd,Aa,Bb,Cc,Dd->ABCD",
... h2e,
... rotation,
... rotation.conj(),
... rotation,
... rotation.conj(),
... optimize="greedy",
... )

Oleh kerana putaran membiarkan orbital bendasing tetap (ia hanya bertindak pada rendaman), interaksi atas-tapak tidak berubah. h2e_momentum kekal sebagai satu sebutan nombor-nombor tunggal, sama seperti h2e. Satu-satunya perubahan ialah bendasing telah dipindahkan ke indeks tengah. Ini kelihatan terus dalam tensor kamiran, yang masing-masing membawa satu entri bukan sifar:

>>> print(f"h2e nonzero at: {np.argwhere(np.abs(h2e) > 1e-12).tolist()}")
h2e nonzero at: [[0, 0, 0, 0]]
>>> print(f"h2e_momentum nonzero at: {np.argwhere(np.abs(h2e_momentum) > 1e-12).tolist()}")
h2e_momentum nonzero at: [[3, 3, 3, 3]]

Kelokalan inilah yang menjadikan interaksi murah untuk disintesis kemudian.

Himpunkan kamiran satu-jasad ke dalam FermionOperator dengan pembina kamiran elektronik from_1body_tril_spin_sym(), yang menjangkakan ia dipek dalam susunan segi tiga bawah. Bahagian dua-jasad ialah satu sebutan nombor-nombor tunggal, jadi daripada menyalurkannya melalui jentera kamiran dua-jasad, bina terus dengan cre()/ann(): operator Coulomb atas-tapak U np↑np↓U \, n_{p\uparrow} n_{p\downarrow} untuk mod bendasing pp (mod ↑\uparrow pada indeks p, mod ↓\downarrow pada indeks p + norb dalam susun atur spin berblok). Kedua-dua pembina menggandakan spin bagi norb orbital ruang kepada 2 * norb mod fermionik.

Kekalkan bahagian satu-jasad H1H_1 dan bahagian dua-jasad H2H_2 sebagai operator berasingan dan bukannya menjumlahkannya serta-merta, sepadan dengan sebutan H1H_1 dan H2H_2 dalam persamaan bahagian 1. Membina setiap bahagian sekali (dalam kedua-dua asas) menjadikannya boleh digunakan semula di bawah. Apabila dijumlahkan, ia memberikan Hamiltonian penuh untuk pepenyetuaan tepat dalam mana-mana asas. Secara individu, H2H_2 (asas momentum) ialah operator yang dievolusikan oleh langkah Trotter dalam langkah 4.

>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> # U n_imp_up n_imp_down: the impurity's up/down modes sit at `imp` and `imp + norb` in the
>>> # block-spin layout (the impurity is orbital 0 in the position basis, orbital 3 in the
>>> # momentum basis -- the single relocated index found above)
>>> imp_pos, imp_mom = 0, 3
>>>
>>> one_body_position = FermionOperator.from_1body_tril_spin_sym(
... h1e[np.tril_indices(norb)], norb
... )
>>> two_body_position = FermionOperator.from_dict(
... {(cre(imp_pos), ann(imp_pos), cre(imp_pos + norb), ann(imp_pos + norb)): onsite}
... )
>>> one_body_momentum = FermionOperator.from_1body_tril_spin_sym(
... h1e_momentum[np.tril_indices(norb)], norb
... )
>>> two_body_momentum = FermionOperator.from_dict(
... {(cre(imp_mom), ann(imp_mom), cre(imp_mom + norb), ann(imp_mom + norb)): onsite}
... )
>>>
>>> hamiltonian_position = (
... (one_body_position + two_body_position).normal_ordered().simplify(atol=1e-14)
... )
>>> hamiltonian_momentum = (
... (one_body_momentum + two_body_momentum).normal_ordered().simplify(atol=1e-14)
... )

Perubahan asas ialah transformasi keserupaan uniter, jadi ia tidak mengubah spektrum. Sahkan operator itu betul dengan membandingkan nilai eigen terendahnya dalam sektor (norb,nelec)(\text{norb}, \text{nelec}) dengan pepenyetuaan tepat. FermionOperator mendedahkan LinearOperator SciPy (disokong oleh kernel hasil darab matriks-vektor interaksi konfigurasi penuh (FCI) asli), jadi ia boleh diserahkan terus kepada scipy.sparse.linalg.eigsh().

>>> import ffsim
>>> import scipy.sparse.linalg
>>>
>>> linop = ffsim.linear_operator(hamiltonian_momentum, norb, nelec)
>>> reference_energy, ground_state = scipy.sparse.linalg.eigsh(linop, k=1, which="SA")
>>> reference_energy = reference_energy[0]
>>> ground_state = ground_state[:, 0]
>>> print(f"exact ground-state energy: {reference_energy:.6f}")
exact ground-state energy: -13.422492

Kelebihan perubahan asas ialah kejarangan. Untuk menjadikannya konkrit, kira berapa banyak penentu asas pengiraan diperlukan untuk menangkap 99% berat keadaan asas, dan bandingkan asas momentum dengan asas kedudukan. Tenaga adalah bebas asas, tetapi bilangan penentu tidak:

>>> def determinants_for(vec, fraction=0.99):
... weights = np.sort(np.abs(vec) ** 2)[::-1]
... return int(np.searchsorted(np.cumsum(weights), fraction) + 1)
...
>>> # the same Hamiltonian in the position basis has an identical spectrum, but a dense ground state
>>> position_linop = ffsim.linear_operator(hamiltonian_position, norb, nelec)
>>> position_energy, position_ground = scipy.sparse.linalg.eigsh(position_linop, k=1, which="SA")
>>>
>>> print(f"position-basis energy: {position_energy[0]:.6f}")
position-basis energy: -13.422492
>>> print(f"determinants for 99% weight: {determinants_for(position_ground[:, 0])} (position)")
determinants for 99% weight: 2563 (position)
>>> print(f"determinants for 99% weight: {determinants_for(ground_state)} (momentum)")
determinants for 99% weight: 21 (momentum)

Kedua-dua operator berkongsi tenaga yang sama, tetapi keadaan asas dalam asas kedudukan tersebar merentasi beribu-ribu penentu, manakala asas momentum menumpukannya kepada sekitar 20, itulah sebabnya pensampelan SKQD berfungsi dalam asas momentum dan bukan asas kedudukan.

3. Sediakan keadaan rujukan​

Rujukan SKQD ∣ψ0⟩\lvert \psi_0 \rangle ialah superposisi semua pengujaan elektron yang paling hampir dengan aras Fermi ke dalam mod momentum kosong terdekat. Penerbitan SKQD membinanya dengan rangkaian XXPlusYYGate yang ditulis tangan. Ia kelihatan berkorelasi kuat (ia mempunyai ratusan amplitud asas pengiraan bukan sifar), tetapi rangkaian putaran dua-mod yang memelihara nombor ialah operasi satu-zarah (Gauss fermionik), jadi keadaan yang dihasilkannya ialah satu penentu Slater tunggal. Itu bermakna anda boleh menyediakannya dengan satu Gate PrepareSlaterDeterminant, dengan putaran satu-zarah yang dilaksanakan oleh rangkaian itu.

Oleh kerana setiap XXPlusYYGate bertindak sebagai putaran Givens pada sepasang mod, putaran itu boleh dibina terus dalam gambaran satu-zarah (satu matriks Givens bagi setiap Gate) dan bukannya mengeluarkan Gate dua-qubit secara manual. Gate ini membentuk kerja bata putaran jiran terdekat yang mengembang dari aras Fermi dalam lapisan yang meluas, menyebarkan setiap elektron berhampiran Fermi merentasi mod kosong tepat di atasnya:

>>> def fermi_level_rotation(norb, nocc, n_excited=3):
... """Orbital rotation exciting the near-Fermi electrons into the nearest empty modes."""
... cos, sin = np.cos(np.pi / 4), np.sin(np.pi / 4)
... fermi = nocc - 1 # highest occupied mode; the Fermi level sits between it and mode `nocc`
... # Nearest-neighbor Givens rotations fanning out from the Fermi level in expanding layers:
... # layer `depth` couples the mode `depth` steps below the level to the one above it, mixing
... # the `n_excited` highest-occupied modes with the empty modes just above them. A final
... # rotation carries amplitude up into the topmost empty mode of the window.
... network = [
... (lower, lower + 1)
... for depth in range(n_excited)
... for lower in range(fermi - depth, fermi + depth + 1, 2)
... ]
... network.append((fermi + n_excited, fermi + n_excited + 1))
... unitary = np.eye(norb, dtype=complex)
... for i, j in network:
... givens = np.eye(norb, dtype=complex)
... givens[i, i] = givens[j, j] = cos
... givens[i, j], givens[j, i] = sin, -sin
... unitary = givens @ unitary
... return unitary
...
>>> reference_rotation = fermi_level_rotation(norb, nelec[0])
Nota

Putaran di atas dinyatakan dalam asas momentum, sepadan dengan Hamiltonian. Elektron berhampiran aras Fermi ialah mod momentum diduduki bertenaga tertinggi, dan rangkaian itu mengujakannya ke dalam mod kosong bertenaga terendah. Inilah sebabnya rujukan mesti dibina selepas perubahan asas pada langkah sebelumnya, bukan sebelumnya.

Penyediaan penentu Slater dilakukan bagi setiap sektor spin, kerana setiap sektor mempunyai bentuk (elektron, orbital) tersendiri. Satu Gate PrepareSlaterDeterminant (satu penghunian, mod nocc terendah diisi, bersama putaran satu-zarah) menyediakan satu sektor; Gate yang sama dikenakan sekali pada mod alfa 0..norb dan sekali pada mod beta norb..2*norb, kerana kedua-dua sektor spin berkongsi penghunian dan putaran rujukan yang sama di sini:

>>> from qiskit_fermions.circuit.library import PrepareSlaterDeterminant
>>>
>>> occupation = [i < nelec[0] for i in range(norb)]
>>> reference_preparation = PrepareSlaterDeterminant(occupation, reference_rotation)

Gate deklaratif ini menggantikan rangkaian XXPlusYYGate yang dikod tangan; Transpiler menjana sintesis putaran Givens.

4. Himpunkan litar Krylov​

Setiap litar Krylov ialah penyediaan keadaan rujukan diikuti evolusi di bawah e−ik Δt He^{-i k \, \Delta t \, H}. Pisahkan Hamiltonian kepada bahagian satu-jasad dan dua-jasadnya, H=H1+H2H = H_1 + H_2, dan lakukan Trotterisasi evolusi ke atas pembahagian itu. Ini ialah pilihan penentu bagi kedalaman litar. Evolusi satu-jasad e−itH1e^{-i t H_1} ialah operasi fermion bebas (Gauss fermionik), jadi ia ialah putaran asas satu-zarah (satu OrbitalRotation dengan uniter e−it h1e^{-i t \, \mathbf{h}_1}) dan bukannya sesuatu yang perlu ditrotterkan secara Pauli sebutan demi sebutan. Evolusi dua-jasad e−itH2e^{-i t H_2} hanya bertindak pada sebutan interaksi atas-tapak tunggal. Membina langkah dengan cara ini membolehkan Transpiler menghasilkan kerja bata putaran Givens yang cetek ditambah satu Gate interaksi dua-qubit, struktur cetek yang dibina secara manual oleh penerbitan SKQD.

Kita sudah mempunyai bahagian dua-jasad. Ia ialah operator two_body_momentum yang dibina pada langkah 2 (sebutan atas-tapak tunggal). Evolusi satu-jasad e−itH1e^{-i t H_1} adalah bebas spin, jadi ia ialah satu OrbitalRotation bagi setiap sektor dengan uniter e−it h1e^{-i t \, \mathbf{h}_1} yang dikenakan sekali pada setiap sektor spin, struktur selari yang sama seperti penyediaan keadaan rujukan.

Setiap litar Krylov berevolusi untuk jumlah masa k Δtk \, \Delta t. Ini direalisasikan dengan hasil darab Trotter tertib kedua bagi kk langkah bersaiz Δt\Delta t, supaya ralat bagi setiap langkah kekal tetap apabila dimensi Krylov membesar. Setiap langkah mengapit satu putaran satu-jasad penuh (satu bagi setiap sektor spin) di antara dua separuh langkah evolusi dua-jasad (murah, sebutan tunggal). Dimensi Krylov DD ialah bilangan litar sedemikian, dengan dim berjulat dari 0 (keadaan rujukan sahaja) hingga D−1D - 1.

>>> import scipy.linalg
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import Evolution, OrbitalRotation
>>>
>>> num_modes = 2 * norb
>>> time_step = 0.2
>>>
>>> half_exp_h2 = Evolution(num_modes, two_body_momentum, time_step / 2)
>>> full_exp_h1 = OrbitalRotation(scipy.linalg.expm(-1j * time_step * h1e_momentum))
>>>
>>> def krylov_circuit(dim):
... # closes over the fermionic gates defined in the outer scope:
... # `reference_preparation`, `half_exp_h2` and `full_exp_h1`
... circuit = FermionicCircuit(num_modes)
... circuit.append(reference_preparation, range(norb))
... circuit.append(reference_preparation, range(norb, num_modes))
... for _ in range(dim): # second-order Trotter product of `dim` steps
... circuit.append(half_exp_h2, circuit.modes)
... circuit.append(full_exp_h1, range(norb))
... circuit.append(full_exp_h1, range(norb, num_modes))
... circuit.append(half_exp_h2, circuit.modes)
... return circuit
Nota

Litar yang dibina di sini belum membawa pengukuran, kerana litar fermionik yang sama juga memacu simulasi tepat pada langkah 6, dan pengukuran bukan operasi uniter. Langkah 5 menambahkannya dengan FermionicCircuit.measure_all(), yang membaca mod i ke dalam bit klasik i.

Utamakan mengukur litar fermionik berbanding litar yang telah ditranspilasi. Pengukuran fermionik menamakan mod yang dibacanya, jadi susun atur dan penghalaan membawanya bersama qubit yang menjadi tempat mod itu berakhir, dan rentetan bit yang disampel diindeks mengikut mod. Mengukur litar yang telah ditranspilasi sebaliknya meninggalkan anda untuk membatalkan final_index_layout() sendiri, dan secara senyap mengembalikan keputusan yang ditukar tertib jika anda terlupa. Ini bergantung pada pengekodan Jordan-Wigner asas penghunian yang digunakan oleh pratetap, yang setiap mod dibawa oleh satu qubit.

5. Transpilasi kepada litar qubit​

Transpilasi FermionicCircuit memetakan Gate fermioniknya kepada Gate qubit. Ini menggunakan pemetaan Jordan-Wigner jordan_wigner(). Gate OrbitalRotation disintesis kepada kerja bata putaran Givens, dan Evolution sebutan tunggal diturunkan kepada satu Gate interaksi dua-qubit.

Oleh kerana setiap Gate fermionik di sini mempunyai sintesis lalai, anda boleh menggunakan generate_preset_jw_pass_manager() yang siap sedia terus dan bukannya menghimpun peringkat secara manual. Sebarang argumen kata kunci dimajukan kepada peringkat qubit; hantar optimization_level=0 untuk gambaran setia dan tidak dioptimumkan bagi kedalaman yang disintesis. Menjana keseluruhan keluarga litar Krylov kemudiannya ialah gelung ke atas dimensi Krylov. Simpan FermionicCircuit yang belum ditranspilasi bersama litar qubit yang telah ditranspilasi. Yang fermionik memacu simulasi tepat (vektor keadaan) pada langkah 6, jadi ia mesti kekal bebas pengukuran; measure_all(inplace=False) mengembalikan salinan yang diukur untuk ditranspilasi dan membiarkan yang asal tidak disentuh, dan salinan itulah yang akan dilaksanakan oleh Backend.

>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> pass_manager = generate_preset_jw_pass_manager(optimization_level=0)
>>>
>>> def krylov_circuits(krylov_dim):
... fermionic_circuits, circuits = [], []
... for dim in range(krylov_dim):
... fermionic_circuit = krylov_circuit(dim)
... fermionic_circuits.append(fermionic_circuit)
... measured = fermionic_circuit.measure_all(inplace=False)
... circuits.append(pass_manager.run(measured))
... return fermionic_circuits, circuits
...
>>> krylov_dim = 5
>>> fermionic_circuits, circuits = krylov_circuits(krylov_dim)

Dimensi Krylov kelihatan terus dalam kedalaman dua-qubit litar. Penyediaan keadaan rujukan ialah kos tetap, dan setiap kuasa Krylov tambahan menambah satu lagi langkah Trotter tertib kedua bagi operator evolusi masa.

>>> two_qubit = lambda instr: instr.operation.num_qubits == 2
>>> [circuit.depth(two_qubit) for circuit in circuits]
[4, 12, 21, 30, 39]
Nota

Memisahkan Hamiltonian memastikan kedalaman ini sederhana. Evolusi satu-jasad ialah putaran orbital sebenar, jadi ia disintesis kepada kerja bata XXPlusYYGate pada qubit bersebelahan. Setiap separuh langkah interaksi atas-tapak ialah satu RZZGate yang menggandingkan dua mod spin bendasing; sebutan murah yang diapit oleh hasil darab tertib kedua di sekeliling putaran. Menyerahkan keseluruhan Hamiltonian kepada satu Gate Evolution sebaliknya akan mentrotterkan secara Pauli bahagian satu-jasad sebutan demi sebutan, merambatkan rentetan Z Jordan-Wigner yang panjang merentasi gandingan bendasing-rendaman (jarak jauh) dan membengkakkan kedalaman sebanyak satu tertib magnitud.

Litar pertama hanya menyediakan penentu rujukan, manakala yang terakhir membawa langkah Trotter paling banyak (empat dalam kes ini):

>>> circuits[0].draw("mpl", fold=-1, measure_arrows=False)
<Figure size ... with 1 Axes>

The reference-state preparation circuit (Krylov dimension 0).

>>> circuits[-1].draw("mpl", fold=-1, measure_arrows=False)
<Figure size ... with 1 Axes>

A second-order Trotter Krylov circuit with four evolution steps.

6. Sampelkan rentetan bit daripada litar Krylov​

Pada perkakasan, anda akan menjalankan litar yang telah ditranspilasi dan mengukur. Ini ialah versi tanpa hingar (vektor keadaan) bagi langkah itu, yang menunjukkan rentetan bit yang akan dikumpulkan oleh SKQD. Simulasikan setiap FermionicCircuit yang belum ditranspilasi dengan ffsim.apply_unitary() dan sampelkan vektor keadaan yang terhasil dengan ffsim.sample_state_vector().

Gate PrepareSlaterDeterminant ialah sahkan-kemudian-putar di bawah simulasi (ia menyemak bahawa keadaan masukan menduduki penentu rujukannya, kemudian mengenakan putaran). Oleh itu, masukan ialah penentu penghunian biasa: mod nocc terendah diisi bagi setiap spin, tanpa putaran. Gate itu mengenakan reference_rotation sendiri.

>>> from collections import Counter
>>>
>>> reference = ffsim.slater_determinant(
... norb, (list(range(nelec[0])), list(range(nelec[1])))
... )
>>>
>>> shots = 1000
>>> counts = Counter()
>>> for dim, fermionic_circuit in enumerate(fermionic_circuits):
... statevector = ffsim.apply_unitary(reference, fermionic_circuit, norb=norb, nelec=nelec)
... samples = ffsim.sample_state_vector(
... statevector, norb=norb, nelec=nelec, shots=shots, seed=dim
... )
... counts += Counter(samples)

Dengan menggabungkan sampel daripada kesemua lima litar Krylov, sokongannya sangat kecil: beberapa ratus rentetan bit berbeza daripada (84)2=4900\binom{8}{4}^2 = 4900 penentu dalam sektor (4, 4). Penumpuan ini, akibat langsung kejarangan asas momentum yang ditetapkan pada langkah 2, menjadikan pepenyetuaan klasik seterusnya boleh dilaksanakan.

Memplot kiraan dengan qiskit.visualization.plot_histogram() menjadikan sifat jarang (sparsity) kelihatan. Beberapa konfigurasi mendominasi, diketuai oleh penentu rujukan.

>>> from qiskit.visualization import plot_histogram
>>>
>>> plot_histogram(dict(counts), number_to_keep=25)
<Figure size ... with 1 Axes>

Histogram of the bitstrings sampled from the Krylov circuits, showing a small support.

7. Pepenyetelan pepenjuru dalam subruang yang disampel​

Rentetan bit yang disampel ialah input kepada separuh klasik SKQD. Hamiltonian diunjurkan ke atas subruang yang dijangkau oleh konfigurasi yang disampel dan dipepenjurukan di sana. Ini diserahkan kepada pakej qiskit-addon-sqd, yang diagonalize_fermionic_hamiltonian() menjalankan keseluruhan gelung pepenjuruan kuantum berasaskan sampel. Ia membina subruang daripada kelompok konfigurasi yang disampel, memepenjurukan Hamiltonian di dalamnya, dan memperhalusi subruang secara berulang dengan menggunakan pemulihan konfigurasi: membalikkan penghunian untuk membaiki konfigurasi yang melanggar simetri bilangan zarah yang diketahui. Pemulihan konfigurasi direka untuk membatalkan kerosakan akibat hingar perkakasan, yang merosakkan rentetan bit yang disampel ke dalam sektor bilangan zarah yang salah; persampelan di sini bebas hingar, jadi tiada apa yang perlu dibaiki dalam hal itu.

Penyelesai menggunakan kiraan yang disampel sebagai BitArray, yang dibina terus daripada counts terkumpul langkah 6. Berikan num_bits secara eksplisit: tanpanya, lebar disimpulkan daripada rentetan bit disampel yang paling lebar, yang lebih sempit daripada 2 * norb apabila tiada sampel menghuni mod terakhir, dan penyelesai kemudian menolak tatasusunan itu. Penyelesai memepenjurukan dalam asas momentum yang sama yang digunakan oleh seluruh pembinaan lain, jadi ia mengambil kamiran h1e_momentum dan h2e_momentum daripada langkah 2 secara terus, tanpa pembungkusan semula. Tenaga yang dipulihkan sepadan dengan rujukan tepat sehingga jauh di bawah satu mili-Hartree, daripada hanya beberapa ratus konfigurasi yang disampel. Oleh sebab tenaga SQD ialah batas atas variasi bagi keadaan dasar sebenar, ia sentiasa berada pada atau di atas reference_energy (masih dalam skop daripada langkah 2):

>>> from qiskit.primitives import BitArray
>>> from qiskit_addon_sqd.fermion import diagonalize_fermionic_hamiltonian
>>>
>>> bit_array = BitArray.from_counts(dict(counts), num_bits=2 * norb)
>>>
>>> # record each iteration's batch results so we can watch the energy converge
>>> result_history = []
>>> result = diagonalize_fermionic_hamiltonian(
... h1e_momentum,
... h2e_momentum,
... bit_array,
... samples_per_batch=100,
... norb=norb,
... nelec=nelec,
... num_batches=3,
... max_iterations=3,
... symmetrize_spin=True,
... callback=result_history.append,
... seed=np.random.default_rng(24),
... )
>>> print(f"SQD estimate: {result.energy:.4f} (exact: {reference_energy:.4f})")
SQD estimate: -13.4224 (exact: -13.4225)

Visualisasikan larian: penumpuan tenaga merentasi lelaran, dan purata penghunian setiap orbital ruang dalam keadaan dasar yang dipulihkan. Tenaga setiap lelaran ialah yang terendah merentasi kelompok lelaran itu, dan penghunian menjumlahkan orbital_occupancies merentasi dua sektor spin. Walaupun tanpa hingar untuk dibetulkan, tenaga berkurang dari lelaran ke lelaran. Setiap lelaran memepenjurukan dalam num_batches kelompok yang hanya terdiri daripada samples_per_batch konfigurasi yang disubsampel daripada kolam, dan penghalusan berulang akibat pembawaan rentetan bit meningkatkan secara berterusan konfigurasi yang masuk ke dalam kelompok tersebut, sehingga tenaga yang dipulihkan bertambah baik:

>>> import matplotlib.pyplot as plt
>>> from matplotlib.ticker import MaxNLocator
>>>
>>> min_energies = [min(batch, key=lambda r: r.energy).energy for batch in result_history]
>>> occupancies = np.sum(result.orbital_occupancies, axis=0)
>>>
>>> fig, axs = plt.subplots(1, 2, figsize=(12, 5))
>>> _ = axs[0].plot(range(len(min_energies)), min_energies, marker="o", label="SQD energy")
>>> _ = axs[0].axhline(reference_energy, color="gray", linestyle="--", label="exact energy")
>>> _ = axs[0].set_xlabel("iteration")
>>> _ = axs[0].set_ylabel("energy")
>>> _ = axs[0].set_title("Energy vs SQD iteration")
>>> _ = axs[0].xaxis.set_major_locator(MaxNLocator(integer=True))
>>> _ = axs[0].legend()
>>> _ = axs[1].bar(range(norb), occupancies)
>>> _ = axs[1].set_xlabel("spatial orbital")
>>> _ = axs[1].set_ylabel("average occupancy")
>>> _ = axs[1].set_title("Occupancy per spatial orbital")
>>> fig
<Figure size ... with 2 Axes>

SQD energy convergence and the average occupancy per spatial orbital.

Ini melengkapkan gelung SKQD. Litar asas momentum bagi langkah 1–5 menghasilkan sampel jarang (langkah 6), dan pepenjuruan berasaskan sampel di atas menukarkan sampel itu kembali menjadi tenaga keadaan dasar.

Langkah seterusnya​

Panduan ini menjalankan keseluruhan saluran paip SKQD pada simulator vektor keadaan tanpa hingar. Pada perkakasan, circuits yang telah ditranspile akan dilaksanakan dan diukur sebagai ganti persampelan vektor keadaan dalam langkah 6, dengan kiraan yang terhasil dimasukkan ke dalam pepenjuruan langkah 7 tanpa perubahan. Rujuk dokumentasi Qiskit untuk bantuan menjalankan litar, dan tutorial addon SQD untuk maklumat tentang pasca-pemprosesan pepenjuruan subruang, termasuk tingkah lakunya pada sampel berhingar, di mana pemulihan konfigurasi memberikan sumbangan paling besar.

Baca panduan perhubungan ffsim untuk memahami mengapa ffsim.apply_unitary() dan ffsim.sample_state_vector() berfungsi secara natif pada litar fermionik pakej ini, dan mengapa sektor bilangan zarah tetap yang digunakan dalam langkah 6 menjadikan sifat jarang asas momentum yang ditetapkan dalam langkah 2 cekap dari segi sampel.