ข้ามไปยังเนื้อหาหลัก

อัลกอริทึม SqDRIFT สำหรับการประมาณสถานะพื้น

ประมาณการการใช้งาน: 180 วินาทีบนโปรเซสเซอร์ Heron r3 (หมายเหตุ: นี่เป็นเพียงค่าประมาณ เวลารันจริงของคุณอาจแตกต่างออกไป)

ผลลัพธ์การเรียนรู้​

  • เรียนรู้วิธีสร้าง circuit ที่ตื้นกว่าเมื่อเทียบกับ Trotterization

  • ดูเวิร์กโฟลว์แบบครบวงจรสำหรับการประมาณสถานะพื้นโดยใช้ qDRIFT และ SQD

  • เรียนรู้วิธีใช้ qiskit-fermions ร่วมกับ Qiskit addon อื่นๆ เพื่อสร้างเวิร์กโฟลว์ดังกล่าว

บทเรียนนี้นำเสนอในรูปแบบ Python notebook เพื่อวัตถุประสงค์ด้านการสอน

ความรู้พื้นฐานที่ต้องมี​

ความเป็นมา​

SqDRIFT เป็นรูปแบบหนึ่งของ SKQD ที่แทนที่ความจำเป็นในการเลือก ansatz เพื่อสุ่มบิตสตริง ด้วยกลุ่มของ circuit การวิวัฒน์ตามเวลาที่สร้างโดยตรงจาก Hamiltonian เป้าหมาย ทำได้โดยการ subsample ตัวดำเนินการวิวัฒน์ตามเวลาที่เล็กกว่าจาก Hamiltonian ตามสัมประสิทธิ์ของมัน ซึ่งเรียกว่าวิธี qDRIFT Trotterization

บทเรียนนี้ใช้ Qiskit Fermions เพื่อสร้าง circuit แบบ fermionic ที่เป็นธรรมชาติยิ่งขึ้นสำหรับอัลกอริทึม qDRIFT จากนั้นใช้ pass ด้าน layout และการสังเคราะห์แบบ fermionic ก่อนนำ circuit เข้าสู่ไปป์ไลน์ Qiskit แบบดั้งเดิมเพื่อรันบนฮาร์ดแวร์

ให้ Hamiltonian อยู่ในรูป:

H=∑i=1NcihiH = \sum_{i=1}^{N} c_i h_i

โดยที่ ไม่เสียนัยทั่วไป เรากำหนดให้ ci>0c_i > 0 และค่าเฉพาะที่ใหญ่ที่สุดของ hih_i เท่ากับ 11 ในค่าสัมบูรณ์ ตัวประกอบหน้าที่มีเครื่องหมายหรือเชิงซ้อนใดๆ ถูกรวมเข้าใน hih_i ดังนั้นสัมประสิทธิ์ cic_i จึงเป็นน้ำหนักบวกอย่างเคร่งครัด ขณะที่ hih_i แบกทิศทางของแต่ละเทอม ที่นี่ NN คือจำนวนเทอม (หรือหลังจัดกลุ่มคือจำนวนกลุ่ม) ใน Hamiltonian ซึ่งเป็นสมบัติของ Hamiltonian และต่างจากจำนวนตัวดำเนินการที่สุ่มเข้าสู่ circuit เดียว ซึ่งเขียนเป็น nn ด้านล่าง

อัลกอริทึม qDRIFT จึงสร้างตัวดำเนินการ VkV_k สำหรับเวลาเป้าหมาย tt โดย kk ไล่จาก 1⋯K1 \cdots K และหมายถึง circuit SqDRIFT ลำดับที่ kthk_{th} นิยามเป็น:

Vk=∏j=1ne−ihkjλt/nV_k = \prod_{j=1}^{n} e^{-i h_{k_j} \lambda t / n }

ที่นี่ nn คือจำนวนตัวดำเนินการที่สุ่มต่อ circuit และ KK คือจำนวน circuit ในกลุ่ม ผลคูณวิ่งตามการสุ่ม nn ครั้ง ไม่ใช่ทั้ง NN เทอมของ Hamiltonian และเนื่องจากเทอมถูกสุ่มแบบคืนที่ hih_i ตัวเดียวกันอาจปรากฏมากกว่าหนึ่งครั้งใน VkV_k ตัวเดียว

ปริมาณ:

λ=∑i=1Nci\lambda = \sum_{i=1}^{N} c_i

คือนอร์ม L1L_1 ของสัมประสิทธิ์ ดังนั้นแต่ละขั้นทั้ง nn ขั้นจึงวิวัฒน์เป็นเวลาเท่ากัน λt/n\lambda t / n ไม่ว่าจะสุ่มได้เทอมใด ความสม่ำเสมอของมุมแต่ละขั้นคือลักษณะเฉพาะของ qDRIFT: สัมประสิทธิ์มีอิทธิพลต่อผลลัพธ์ผ่าน ความถี่ที่ เทอมของมันถูกสุ่ม ไม่ใช่ผ่านขนาดที่เทอมนั้นถูกหมุน ดัชนีถูกสุ่มจากการแจกแจง:

P[ki]=ciλP[k_i] = \frac{c_i}{\lambda}

ดังนั้นอนุกรม (k1,…,kn)(k_1, \ldots, k_n) คือลำดับสุ่มของดัชนีเทอมที่สุ่มจากการแจกแจงนี้ เนื่องจาก cic_i เป็นบวกและรวมกันได้ λ\lambda นี่จึงเป็นการแจกแจงความน่าจะเป็นที่ทำให้เป็นบรรทัดฐานแล้ว และค่าคาดหมายของช่องสัญญาณที่ได้เหนือการสุ่มประมาณการวิวัฒน์ภายใต้ HH โดยมีความคลาดเคลื่อนที่ลดลงเมื่อ nn เพิ่มขึ้น โปรดทราบว่าความคลาดเคลื่อนของการประมาณขึ้นกับ λ\lambda ไม่ใช่จำนวนเทอม NN

(เอกสาร SqDRIFT เขียนจำนวนเทอมเป็น N\mathcal{N} และความยาวลำดับเป็น NN ส่วนเราใช้ NN และ nn ที่นี่เพื่อให้สองอย่างนี้แยกจากกันอย่างชัดเจน)

บทเรียนนี้แสดงวิธีสร้างกลุ่มของ circuit สุ่มดังกล่าว หลังจากสร้าง circuit เหล่านี้แล้ว คล้ายกับที่เราสร้างปริภูมิย่อย Krylov สำหรับตัวดำเนินการต่างๆ เราสุ่มบิตสตริงจากตัวดำเนินการหลายตัวที่มีพารามิเตอร์เวลาต่างกัน วิธีนี้ทำให้เกิดการซ้อนทับสูงขึ้นระหว่างเวกเตอร์สถานะพื้นกับบิตสตริงที่สุ่มได้

ข้อกำหนด​

ก่อนเริ่มบทเรียนนี้ ตรวจสอบให้แน่ใจว่าคุณติดตั้งสิ่งต่อไปนี้แล้ว

  • สภาพแวดล้อมเสมือน Python (>=3.10)
  • pip>=25.1
  • qiskit ~= 2.5
  • qiskit-fermions==0.1.0 (โปรดทราบว่าชื่อเป็นพหูพจน์)
  • numpy
  • pyscf
  • qiskit-aer
  • qiskit-ibm-runtime
  • qiskit-addon-sqd

คุณสามารถติดตั้งแพ็กเกจที่จำเป็นทั้งหมดได้ด้วย:

pip install "qiskit~=2.5" "qiskit-fermions==0.1.0" qiskit-aer qiskit-ibm-runtime qiskit-addon-sqd pyscf numpy

การตั้งค่า​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "pyscf": "pyscf", "qiskit": "qiskit", "qiskit_addon_sqd": "qiskit-addon-sqd", "qiskit_aer": "qiskit-aer", "qiskit_fermions": "qiskit-fermions", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Third-party scientific computing
import numpy as np

# PySCF
from pyscf import tools, ao2mo, fci

# Qiskit core
from qiskit import transpile
from qiskit.primitives import BitArray

# Qiskit Aer
from qiskit_aer import AerSimulator

# IBM Quantum Compute Service
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

# Qiskit Fermions
from qiskit_fermions.operators.library import FCIDump
from qiskit_fermions.operators import FermionOperator
from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
from qiskit_fermions.operators.terms.grouping import (
group_terms_by_electronic_structure,
)
from qiskit_fermions.operators.terms.ordering import canonical_order
from qiskit_fermions.circuit import FermionicCircuit
from qiskit_fermions.circuit.library import Evolution
from qiskit_fermions.transpiler import FermionicPassManager
from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
from qiskit_fermions.transpiler.passes import QDriftTrotterization
from qiskit_fermions.circuit.library import InitializeModes

# Qiskit addon SQD
from qiskit_addon_sqd.fermion import (
diagonalize_fermionic_hamiltonian,
SCIResult,
)

ตัวอย่างบนซิมูเลเตอร์​

ขั้นที่ 1: แมปอินพุตคลาสสิกไปเป็นปัญหาควอนตัม​

การอ่านและเตรียม FCIDump

สำหรับบทเรียนนี้ เราจะโหลด Hamiltonian โครงสร้างอิเล็กทรอนิกส์ของไนโตรเจน (N2) นอกจากนี้ยังมีวิธีอื่นในการสร้างตัวดำเนินการ fermionic ดูเอกสารที่ qiskit_fermions.operators.library

เกี่ยวกับ FCIDump นี้ ไฟล์ N2_sto_3g อธิบายโมเลกุลไนโตรเจน (N2N_2) ในเบซิส STO-3G ขั้นต่ำที่ระยะห่างระหว่างอะตอม 1.09 A˚\AA ซึ่งเป็นความยาวพันธะสมดุลจากการทดลอง ส่วนหัวของไฟล์ประกาศ NORB=10, NELEC=14 และ MS2=0: ออร์บิทัลเชิงพื้นที่ 10 ตัว (จึงเป็นสปินออร์บิทัล 20 ตัว และ 20 qubit ภายใต้ Jordan-Wigner) อิเล็กตรอน 14 ตัวในสปินซิงเกลต์ คืออิเล็กตรอน α\alpha เจ็ดตัวและ β\beta เจ็ดตัว ออร์บิทัลทั้งหมดมีป้ายสมมาตร 1 นั่นคือไม่ได้ใช้ประโยชน์จากสมมาตรกลุ่มจุด เนื่องจากเป็นดัมพ์ STO-3G เต็มปริภูมิ ไม่มีออร์บิทัลใดถูกแช่แข็ง และปริภูมิสหสัมพันธ์เล็กพอที่จะคำนวณพลังงานอ้างอิง FCI ที่แน่นอนแบบคลาสสิกเพื่อเปรียบเทียบได้ ดังที่แสดงในเซลล์ถัดไป

สามารถสร้างไฟล์ที่เทียบเท่ากันขึ้นใหม่ด้วย PySCF ได้:

from pyscf import gto, scf, tools

mol = gto.M(atom="N 0 0 0; N 0 0 1.09", basis="sto-3g", symmetry=False)
mf = scf.RHF(mol).run()
tools.fcidump.from_scf(mf, "N2_sto_3g")

เนื่องจากปริพันธ์ขึ้นกับออร์บิทัล SCF ที่ลู่เข้าแล้ว ไฟล์ที่สร้างใหม่อาจต่างจากไฟล์ที่ให้มาในเฟสหรือลำดับของออร์บิทัล พลังงานรวมไม่ได้รับผลกระทบ

การรับไฟล์ ค้นหา FCIDump ได้ใน GitHub repository นี้ คุณสามารถรันเซลล์ด้านล่างเพื่อดึงไฟล์ไปยังตำแหน่งที่ส่วนที่เหลือของบทเรียนคาดหวังได้

ก่อนอื่นเราใช้ cisolver ที่ pyscf ให้มาเพื่อหาพลังงานอ้างอิง นี่คือพลังงานสถานะพื้นที่แท้จริงของโมเลกุลที่เราใช้งาน สำหรับสิ่งนี้ เราจะประกาศ norb และ nelec ก่อน ซึ่งคือจำนวนออร์บิทัลและจำนวนอิเล็กตรอนตามลำดับ จากนั้นประกาศ h1e และ h2e ซึ่งคือปริพันธ์หนึ่งและสองอิเล็กตรอนตามลำดับ ทั้งหมดนี้จะถูกใช้กับ SQD ในภายหลังด้วย

import os
from urllib.request import urlopen

# The FCIDump is stored with this tutorial in the Qiskit documentation repository.
FCIDUMP_URL = "https://raw.githubusercontent.com/Qiskit/documentation/main/docs/tutorials/assets/sqdrift/fcidump_files/N2_sto_3g"
FCIDUMP_PATH = "fcidump_files/N2_sto_3g"

if not os.path.exists(FCIDUMP_PATH):
os.makedirs(os.path.dirname(FCIDUMP_PATH), exist_ok=True)
with urlopen(FCIDUMP_URL) as response:
contents = response.read()
with open(FCIDUMP_PATH, "wb") as f:
f.write(contents)
print(f"Downloaded FCIDump to {FCIDUMP_PATH}")
else:
print(f"Using existing FCIDump at {FCIDUMP_PATH}")
Using existing FCIDump at assets/sqdrift/fcidump_files/N2_sto_3g
name = "fcidump_files/N2_sto_3g"

fcidump = tools.fcidump.read(name)

# Extract metadata from the FCIDump header
norb = fcidump["NORB"] # number of spatial orbitals
nelec = fcidump["NELEC"] # total number of electrons
e_nuc = fcidump["ECORE"] # nuclear repulsion / core energy
ms2 = fcidump["MS2"] # 2S (spin)

num_elec_a = (nelec + ms2) // 2 # alpha electrons
num_elec_b = (nelec - ms2) // 2 # beta electrons

# Reconstruct full 4-index ERIs from the FCIDump (stored in 8-fold symmetry)
h1e = fcidump["H1"] # shape (norb, norb)
h2e = ao2mo.restore( # shape (norb, norb, norb, norb)
1, fcidump["H2"], norb
)

cisolver = fci.direct_spin1.FCI()
cisolver.max_cycle = 200
cisolver.conv_tol = 1e-12

e_fci, _ = cisolver.kernel(
h1e,
h2e,
norb,
(num_elec_a, num_elec_b),
ecore=e_nuc, # adds nuclear repulsion to the final energy
)

reference_energy = e_fci

print(f"Reference FCI Energy = {reference_energy:.10f} Ha")

nuclear_repulsion_energy = fcidump["ECORE"]
print(f"Nuclear Repulsion Energy = {nuclear_repulsion_energy:.10f} Ha")
Parsing assets/sqdrift/fcidump_files/N2_sto_3g
Reference FCI Energy = -107.6481842917 Ha
Nuclear Repulsion Energy = 23.7887003074 Ha

การโหลด Hamiltonian

เมื่อข้อมูลที่จำเป็นพร้อมแล้ว เราอ่าน Hamiltonian จากไฟล์ FCI ในรูปแบบที่เข้ากันได้กับ qiskit-fermions

fcidump = FCIDump.from_file(name)
hamiltonian = FermionOperator.from_fcidump(fcidump)
num_modes = 2 * fcidump.norb

เวิร์กโฟลว์แบบ fermionic ด้วย qiskit-fermions

เราจะแมป Hamiltonian ไปเป็นโมเดล circuit แบบ fermionic ด้วย qiskit-fermions ก่อน ซึ่งมี transpiler pass และ gate เฉพาะสำหรับ circuit แบบ fermionic สิ่งเหล่านี้จะถูกใช้ก่อน transpiler pass แบบดั้งเดิมของ Qiskit สำหรับเวิร์กโฟลว์นี้ในภายหลัง

การจัดกลุ่มเทอม

เพื่อให้ผลลัพธ์ทำซ้ำได้ ก่อนอื่นเราใช้ canonical_order เพื่อเรียงเทอมตามโครงสร้างของมันเท่านั้น ลำดับของตัวดำเนินการในรายการ canon จึงถูกตรึงไว้ วิธีนี้รับประกันว่าตัวดำเนินการที่สร้างขึ้นทำซ้ำได้ เพราะ pass QDriftTrotterization ที่เราจะใช้ต่อไปสุ่มดัชนีเพื่อสร้างตัวดำเนินการ qDRIFT

ในขั้นนี้ เราใช้ประโยชน์จากสมมาตรจำนวนมากที่มีอยู่ใน Hamiltonian โครงสร้างอิเล็กทรอนิกส์ โดยจัดกลุ่มเทอมที่เกี่ยวข้องกันซึ่งมีสัมประสิทธิ์เท่ากัน แม้การทำเช่นนี้จะเปลี่ยนการแจกแจงสัมประสิทธิ์ตัวดำเนินการที่โปรโตคอล qDRIFT สุ่มจาก แต่ไม่กระทบการรับประกันการลู่เข้าของมัน ที่สำคัญคือ การจัดกลุ่มเทอมที่สัมพันธ์กันด้วยสมมาตรทำให้เทอม Pauli หักล้างกันอย่างเป็นคุณ และทำให้ circuit ตื้นลงโดยรวมเมื่อวิวัฒน์สถานะตามเวลาภายใต้การกระทำของมัน

qiskit-fermions มีฟังก์ชัน group_terms_by_electronic_structure ที่ทำการจัดกลุ่มนี้ให้เรา

โปรดทราบว่า group_terms_by_electronic_structure สมมติให้เทอมเป็น normal ordering

การกรองเทอมแนวทแยง

เราลบเทอมแนวทแยงออกจาก Hamiltonian ที่ใช้สร้าง circuit เพื่อให้ช่องสุ่ม qDRIFT จำนวน nn ช่องถูกใช้กับเทอมที่ย้ายประชากรระหว่างการจัดเรียง เทอมเหล่านี้ควรกรองออกจาก Hamiltonian ที่จุดนี้ ก่อนสร้าง gate Evolution ในขั้นถัดไป

เทอมที่กล่าวถึงคือเทอมที่เป็นแนวทแยงในเบซิสเลขการครอบครอง นั่นคือผลคูณของตัวดำเนินการจำนวน ai†aia^\dagger_i a_i เทอมสามชนิดเข้าข่ายคำอธิบายนี้:

  • ออฟเซ็ตพลังงานคงที่ ซึ่งเป็นผลคูณของตัวดำเนินการจำนวนศูนย์ตัว การวิวัฒน์ตามเวลาของมันให้เพียงเฟสรวม

  • ตัวดำเนินการจำนวนเดี่ยว nin_i ซึ่งการวิวัฒน์ตามเวลาลดรูปเป็นการหมุน ZZ ของ qubit เดี่ยว

  • ผลคูณอันดับสูงกว่า เช่น ninjn_i n_j

เทอมเหล่านี้โดยลำพังไม่ได้ย้ายประชากรระหว่างการจัดเรียงเลขการครอบครอง แต่กระทำต่อเฟสของการจัดเรียงที่มีอยู่แล้วเท่านั้น อย่างไรก็ตาม มันไม่ได้เฉื่อย: เฟสสัมพัทธ์เหล่านั้นป้อนเข้าสู่การแทรกสอดที่เกิดจากเทอม excitation ที่อยู่ภายหลังใน circuit ดังนั้นการกรองมันจึงเปลี่ยนการวิวัฒน์ที่เกิดขึ้นจริง และอาจเปลี่ยนการแจกแจงการสุ่มตัวอย่าง นี่เป็นการประมาณโดยเจตนาในขั้นตอนการสร้าง circuit เพื่อโฟกัสการสุ่มตัวอย่างไปที่เทอม excitation ไม่ใช่ขั้นตอนที่ปล่อยการแจกแจงที่สุ่มได้ไว้ไม่เปลี่ยน ต่างจากการจัดกลุ่มสมมาตรข้างต้นที่ไม่กระทบการรับประกันการลู่เข้าของ qDRIFT ตัวกรองนี้เปลี่ยนตัวดำเนินการที่ถูกวิวัฒน์ circuit จึงไม่ประมาณการวิวัฒน์ภายใต้ Hamiltonian เต็มอีกต่อไป และขอบเขตความคลาดเคลื่อนของ qDRIFT ใช้กับตัวดำเนินการที่กรองแล้ว ไม่ใช่ตัวดั้งเดิม ซึ่งยอมรับได้ที่นี่ เพราะ circuit เป็นเพียงฮิวริสติกการสุ่มตัวอย่างที่ใช้เสนอการจัดเรียง: ไม่มีเทอมใดหายไปจากการประมาณพลังงานเอง เพราะตัวกรองใช้กับ Hamiltonian ที่ใช้สร้าง circuit เท่านั้น ขณะที่ classical diagonalization ภายหลังใช้ Hamiltonian เต็มรวมเทอมแนวทแยงด้วย ความแม่นยำของ SQD ขึ้นกับขั้นตอนคลาสสิกนั้น ซึ่งยังคงเป็น variational ในปริภูมิย่อยที่สุ่มได้ไม่ว่าการจัดเรียงจะถูกเสนอมาอย่างไร

ฟังก์ชัน filter_diagonal_terms() ลบเทอมดังกล่าวออกจากตัวดำเนินการในที่เดิม (in place) มันระบุเทอมเหล่านั้นจากโครงสร้าง normal-ordered ของมัน — มัลติเซตของโหมดสร้างตรงกับมัลติเซตของโหมดทำลาย — จึงใช้ได้เฉพาะกับตัวดำเนินการที่เป็น normal-ordered อยู่แล้ว สมมติฐานนี้ไม่ได้ถูกตรวจสอบขณะรัน

# Apply automatic grouping
canon = canonical_order(hamiltonian.normal_ordered().simplify(atol=1e-16))
exit_code = group_terms_by_electronic_structure(
canon, num_modes, two_body_physicist_order=False
)
filter_diagonal_terms(canon)

print(len(canon.groups))
5060

เมื่อจัดกลุ่มเทอมใน Hamiltonian แล้ว เราจะตัดสินใจพารามิเตอร์ต่อไปนี้เพื่อสร้างกลุ่มของ circuit:

  • จำนวน circuit ที่จะสร้าง: num_circuits
  • ความยาวของแต่ละ circuit ในหน่วยกลุ่ม excitation: num_exc
  • ตัวประกอบสำหรับเวลาวิวัฒน์ที่ต่างกัน: times

การสร้าง circuit แบบ fermionic

ตอนนี้เราจะสร้าง circuit แบบ fermionic สำหรับแต่ละขั้นเวลา แต่ละ circuit ประกอบด้วย gate วิวัฒน์เดียว ด้วยเวลาวิวัฒน์ที่เราประกาศไว้ก่อนหน้า ตัวดำเนินการวิวัฒน์คือ Hamiltonian ต่อมาเรารัน transpiler pass บน circuit เหล่านี้เพื่อสร้าง circuit qDRIFT

การเตรียม Ansatz

เราเตรียมสถานะ Hartree-Fock ด้วยคลาส InitializeModes สำหรับไนโตรเจน กระบวนการคือการใส่ gate X ที่ qubit num_elec_a ตัวแรก แล้วที่ qubit num_elec_b ตัว ซึ่งทั้งสองเท่ากับเจ็ดสำหรับไนโตรเจน สถานะนี้แทนอิเล็กตรอน α\alpha เจ็ดตัวและ β\beta เจ็ดตัวของไนโตรเจน

# SqDRIFT parameters
times = [1.0, 10.0] # Total evolution times used for the subspace creation
num_exc = 10 # Number of excitation groups per circuit
num_circuits = 200 # Number of circuits to generate

init_circuits = []

hf_gate = InitializeModes.from_hartree_fock(norb, (num_elec_a, num_elec_b))

for time in times:
evo_gate = Evolution(num_modes, canon, time)
circ = FermionicCircuit(num_modes)
circ.append(hf_gate, circ.modes)
circ.append(evo_gate, circ.modes)
init_circuits.append(circ)

ขั้นที่ 2: ปรับปัญหาให้เหมาะสมสำหรับการรันบนฮาร์ดแวร์ควอนตัม​

เมื่อเรามี circuit แล้ว เราจะใช้ pass ที่มีใน qiskit-fermions ก่อนเพื่อปรับให้เหมาะสมในระดับ fermionic จากนั้น transpile circuit ของเราสำหรับ backend ที่เลือก เนื่องจากนี่เป็นการทดลองบนซิมูเลเตอร์ เราจะทำสิ่งนี้สำหรับ AerSimulator ก่อน การคำนวณน้ำหนักของแต่ละกลุ่ม

ในขั้นตอนนี้ เราทำการสุ่มตัวอย่างพจน์แบบ qDRIFT โดยสุ่มด้วยความน่าจะเป็นที่แปรผันตามสัมประสิทธิ์ของพจน์เหล่านั้นใน Hamiltonian ซึ่ง transpiler pass ของ qDRIFT ทำสิ่งนี้ให้เรา ตอนนี้เราสามารถสร้าง circuit ที่ตื้นกว่าเดิม ซึ่งรันบนฮาร์ดแวร์ได้อย่างมีประสิทธิภาพมากขึ้นแม้การเชื่อมต่อของ qubit จะจำกัด แม้ Hamiltonian จะมีการจับคู่ระยะไกลและพจน์ที่มีอันดับสูงกว่าสองก็ตาม หลังจากจัดกลุ่มพจน์แล้ว จะสุ่มตัวอย่างตัวดำเนินการตามน้ำหนักของมัน สำหรับตัวดำเนินการแต่ละตัว hih_i น้ำหนัก WhiW_{h_i} นิยามได้ดังนี้

Whi=∣ci∣/λW_{h_i} = |c_i| / \lambda

เนื่องจากพจน์ถูกจัดกลุ่มไว้แล้วในขั้นตอนที่ 1 แต่ละ hih_i ในที่นี้จึงเป็นกลุ่มทั้งกลุ่ม โดย cic_i คือค่าเฉลี่ยของสัมประสิทธิ์สัมบูรณ์ของพจน์ในกลุ่ม ii และแต่ละพจน์ในกลุ่มจะถูกวิวัฒน์โดยลดสัมประสิทธิ์ของมันให้เหลือเพียงเครื่องหมาย

การปรับแต่งระดับเฟอร์มิออนและแบบเนทีฟของฮาร์ดแวร์

ฟังก์ชัน generate_preset_jw_pass_manager() คืนค่า MultiStagePassManager ที่รับ FermionicCircuit และสร้าง circuit สุดท้ายที่ปรับแต่งแล้ว ซึ่งเราสามารถ transpile เพื่อรันบนฮาร์ดแวร์ของเราได้ เราแทนที่ขั้นตอนการปรับแต่งเริ่มต้นด้วย FermionicPassManager ที่มี pass QDriftTrotterization ของเรา:

  • pass QDriftTrotterization ใช้การคำนวณน้ำหนักและการสุ่มตัวอย่างภายในเพื่อสร้าง circuit ที่เราจะใช้ในการสุ่มตัวอย่าง

  • pass RelabelModes เป็น pass สำหรับการปรับแต่งอีกตัวหนึ่ง ซึ่งใช้สลับโหมดเฟอร์มิออนเพื่อปรับการเชื่อมต่อระหว่าง qubit ให้เหมาะสมและลดความลึกของ gate อ่านเพิ่มเติมได้ใน API reference

ขั้นตอนที่เหลือของ MultiStagePassManager จะทำงานโดยอัตโนมัติและจัดการการแมปจากเฟอร์มิออนไปเป็น qubit ทั้งหมด:

  • F2QLayout: pass manager ที่กำหนดไว้ล่วงหน้าใช้ pass TrivialF2QLayout ซึ่งแมปบิตเฟอร์มิออน nn บิตไปยัง qubit nn ตัวแบบตรงไปตรงมา

  • F2QSynth: pass สำหรับ transpile ที่แมปคำสั่งของ circuit ที่อิงเฟอร์มิออนไปเป็นคำสั่งที่อิง qubit

qdrift = QDriftTrotterization(num_exc, rng=19)

pm = generate_preset_jw_pass_manager()
pm.optimization = FermionicPassManager([qdrift])

sqdrift_circuits = []
for circ in init_circuits:
sqdrift_circuits += (pm.run(circ) for _ in range(num_circuits))

for circ in sqdrift_circuits:
circ.measure_all()

print(len(sqdrift_circuits))
400

เมื่อทำการปรับแต่งระดับเฟอร์มิออนเสร็จแล้ว เราสามารถ transpile circuit เพื่อรันบนซิมูเลเตอร์ได้

simulator = AerSimulator()
shots = 100

transpiled_circuits = transpile(sqdrift_circuits, simulator)

ขั้นตอนที่ 3: รันโดยใช้ Qiskit primitives​

เมื่อเรามี circuit แล้ว เราสามารถรันด้วย Qiskit primitives บน AerSimulator ได้ เราจะรวม counts ทั้งหมดจาก circuit ต่างๆ เข้าด้วยกัน แล้วแปลงเป็นเวกเตอร์บูลีนก่อนจะประมวลผลภายหลังด้วย SQD เป็นขั้นสุดท้าย

print(
f"Executing {len(transpiled_circuits)} circuits with {shots} shots each..."
)

job = simulator.run(transpiled_circuits, shots=shots)
result = job.result()

all_counts = [result.get_counts(i) for i in range(len(transpiled_circuits))]

print(len(all_counts), "length before post processing")
Executing 400 circuits with 100 shots each...
400 length before post processing

ขั้นตอนที่ 4: ประมวลผลภายหลังและคืนผลลัพธ์ในรูปแบบคลาสสิกที่ต้องการ​

การใช้ bitstring สำหรับ SQD

ตอนนี้เราสามารถรันวิธี diagonalization บน bitstring ที่เลือกไว้ เพื่อหาค่าลักษณะเฉพาะที่ต่ำที่สุด ซึ่งจะตรงกับพลังงานสถานะพื้นของโมเลกุล เราสร้างฟังก์ชัน callback ประกาศ occupancy เริ่มต้น และตั้งค่าพารามิเตอร์ ก่อนจะรันวิธี diagonalization เป็นขั้นสุดท้าย ฟังก์ชัน callback ใช้พิมพ์รอบการวนซ้ำปัจจุบันและค่าประมาณค่าลักษณะเฉพาะปัจจุบันในแต่ละรอบ

สุดท้าย เพื่อให้ได้ค่าประมาณสถานะพื้น เราบวก nuclear_repulsion_energy เข้ากับพลังงานที่ได้

หมายเหตุ: มิติของ subspace ไม่คงที่ตลอดการวนซ้ำ แม้บนซิมูเลเตอร์ที่ไม่มีสัญญาณรบกวนก็ตาม เพราะแต่ละ subsample สุ่มได้ชุด configuration ที่ต่างกัน และขั้นตอน recovery ปรับรูปแบบ pool ระหว่างรอบการวนซ้ำ มิติที่รายงานจึงแตกต่างกันไปในแต่ละ subsample การสุ่มตัวอย่างแบบไม่มีสัญญาณรบกวนเพียงอย่างเดียวไม่ได้ตรึงมิติของ subspace ที่เลือก อย่างไรก็ตาม การรันบนฮาร์ดแวร์มักให้ subspace ที่ใหญ่กว่าอย่างเป็นระบบ เพราะ shot ที่มีสัญญาณรบกวนทำลายสมมาตรของจำนวนอนุภาค และ configuration recovery เปลี่ยน shot เหล่านั้นให้เป็นเวกเตอร์ฐานเพิ่มเติม ด้วยเหตุนี้ เราจึงจะเพิ่มอีกขั้นตอนหนึ่งสำหรับการตัด bitstring ในส่วนของฮาร์ดแวร์ด้วย

combined_counts = {}
for counts in all_counts:
for bitstring, count in counts.items():
combined_counts[bitstring] = combined_counts.get(bitstring, 0) + count

bit_array = BitArray.from_counts(combined_counts)
print(bit_array.num_shots)

print(f" Alpha electrons: {num_elec_a}")
print(f" Beta electrons: {num_elec_b}")
print(f" Number of orbitals: {norb}")
print(f" Number of spin orbitals (qubits): {2*norb}")
print(f"Integral shapes: h1e={h1e.shape}, h2e={h2e.shape}")

# SQD parameters
samples_per_batch = 300
num_batches = 3
max_iterations = 5

initial_occupancies = (
np.array([1] * num_elec_a + [0] * (norb - num_elec_a)), # alpha
np.array([1] * num_elec_b + [0] * (norb - num_elec_b)), # beta
)

result_history = []

def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy + nuclear_repulsion_energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)

# Run SQD with configuration recovery
print("\nRunning SQD with configuration recovery...")
result = diagonalize_fermionic_hamiltonian(
h1e,
h2e,
bit_array,
samples_per_batch=samples_per_batch,
norb=norb,
nelec=(num_elec_a, num_elec_b),
num_batches=num_batches,
energy_tol=1e-3,
occupancies_tol=1e-3,
max_iterations=max_iterations,
initial_occupancies=initial_occupancies,
seed=42,
callback=callback,
)

computed_energy = result.energy + nuclear_repulsion_energy

print("FINAL SQD RESULTS")
print(f"Orbital occupancies (alpha): {result.orbital_occupancies[0]}")
print(f"Orbital occupancies (beta): {result.orbital_occupancies[1]}")

energy_error = abs(computed_energy - reference_energy)
print(f"Reference Energy: {reference_energy:.10f} Ha")
print(f"Computed Energy: {computed_energy:.10f} Ha")
print(f"Error: {energy_error:.10e} Ha")
40000
Alpha electrons: 7
Beta electrons: 7
Number of orbitals: 10
Number of spin orbitals (qubits): 20
Integral shapes: h1e=(10, 10), h2e=(10, 10, 10, 10)

Running SQD with configuration recovery...
Iteration 1
Subsample 0
Energy: -107.64767025226178
Subspace dimension: 5538
Subsample 1
Energy: -107.64772799119115
Subspace dimension: 5670
Subsample 2
Energy: -107.64765512281548
Subspace dimension: 5767
Iteration 2
Subsample 0
Energy: -107.64795948524682
Subspace dimension: 6080
Subsample 1
Energy: -107.64806617355072
Subspace dimension: 6300
Subsample 2
Energy: -107.64802260640258
Subspace dimension: 6308
FINAL SQD RESULTS
Orbital occupancies (alpha): [0.99999464 0.99999643 0.99584631 0.99332984 0.96684652 0.96686712
0.99301927 0.0373282 0.0373266 0.00944508]
Orbital occupancies (beta): [0.99999462 0.99999643 0.9958261 0.99332349 0.96684268 0.96686737
0.99302145 0.03733536 0.03733399 0.0094585 ]
Reference Energy: -107.6481842917 Ha
Computed Energy: -107.6480661736 Ha
Error: 1.1811817564e-04 Ha

ตัวอย่างบนฮาร์ดแวร์​

ตัวอย่างนี้ใช้ 20 qubit (10 วงโคจรเชิงพื้นที่) ตัวเลือกนี้เป็นเพียงความสะดวกสำหรับบทเรียนที่ควรรันได้เร็ว ไม่ใช่เพดานตายตัวของวิธีการนี้

ต้นทุนของขั้นตอนคลาสสิกไม่ได้ถูกกำหนดโดยจำนวน qubit โดยตรง SQD ทำ diagonalization ของ Hamiltonian ที่ฉายลงบน subspace ที่ขึงด้วย configuration ที่ สุ่มตัวอย่างได้ สิ่งที่กำหนดต้นทุนคลาสสิกจึงเป็นมิติของ subspace ที่เลือก ซึ่งในที่นี้ถูกกำหนดโดย samples_per_batch, num_batches และจำนวน configuration ที่แตกต่างกันซึ่ง circuit สร้างขึ้นจริง ร่วมกับพีชคณิตเชิงเส้นแบบเบาบางที่ต้องใช้เพื่อประยุกต์ Hamiltonian ที่ฉายแล้ว ปริภูมิ CI เต็มรูปแบบเติบโตแบบคอมบินาทอริกตามจำนวนวงโคจรและอิเล็กตรอน แต่ subspace ที่เลือกเป็นส่วนเล็กๆ ที่ปรับได้ของมัน และเราควบคุมขนาดของมันได้โดยตรง ดังนั้น จำนวน qubit และความยากเชิงคลาสสิกจึงปรับได้ค่อนข้างเป็นอิสระต่อกัน ปริภูมิวงโคจรที่กว้างกว่าซึ่งสุ่มตัวอย่างลงใน subspace ขนาดพอเหมาะ อาจถูกกว่าระบบที่เล็กกว่าที่ทำ diagonalization บน subspace ขนาดใหญ่มาก

ในทางปฏิบัติ ขนาดระบบที่ทำได้จริงจึงขึ้นอยู่กับมิติของ subspace ที่คุณต้องการสำหรับความแม่นยำที่ต้องการ และขึ้นอยู่กับหน่วยความจำและคอร์ที่ eigensolver ใช้ได้ ปริภูมิวงโคจรที่ใหญ่ขึ้นมักต้องการ subspace ที่ใหญ่ขึ้นเพื่อให้ได้ความแม่นยำระดับเคมี และนั่นคือสิ่งที่ท้ายที่สุดนำไปสู่การใช้ทรัพยากรแบบกระจาย ดู qiskit-addon-sqd-hpc สำหรับการขยายขั้นตอนนี้ออกไป แทนที่จะสมมติค่าตัดขาดตายตัว แนวทางปฏิบัติคือสังเกตมิติของ subspace ที่รายงานและการลู่เข้าของพลังงานตลอดการวนซ้ำ แล้วเพิ่มขนาด subspace จนกว่าพลังงานจะไม่ดีขึ้นอีกหรือหน่วยความจำที่มีอยู่หมด

หมายเหตุ: เนื่องจากข้อผิดพลาดจากการสุ่มตัวอย่างอันเกิดจากสัญญาณรบกวนในฮาร์ดแวร์ subspace ที่สร้างขึ้นสำหรับ diagonalization ในการรันบนฮาร์ดแวร์จะใหญ่กว่าที่ได้จากการใช้ซิมูเลเตอร์ แม้จะเพิ่มมิติของ subspace ที่เราต้องการทำ diagonalization แต่เวิร์กโฟลว์ก็ยังให้คำตอบที่แม่นยำ เพราะ SQD ทนทานต่อสัญญาณรบกวน

การตัด string ที่ไม่แท้

ตรงนี้เราเลือกทำขั้นตอนเพิ่มเติมได้ เมื่อเรามี bitstring ทั้งหมดจากการรัน circuit แล้ว เราจะกรอง bitstring ที่ไม่ถูกต้องออกก่อนรัน SQD หรือจะไปต่อโดยไม่ตัดก็ได้ โดยทั่วไปการข้ามการตัดเหมาะกว่าสำหรับการรันบนฮาร์ดแวร์ เพราะจะเหลือ shot ที่สมมาตรถูกทำลายไว้ให้ configuration recovery ซึ่งซ่อมแซมให้เป็น configuration ที่ถูกต้องได้ และทำให้ subspace กว้างขึ้นแทนที่จะทิ้ง shot เหล่านั้นไปเลย

เนื่องจากไนโตรเจนมีอิเล็กตรอน α\alpha ได้เพียงเจ็ดตัวและ β\beta เจ็ดตัว bitstring ใดที่มีเลข 1 มากกว่าหรือน้อยกว่าเจ็ดตัวในครึ่งแรกและครึ่งหลังของผลลัพธ์จึงทิ้งได้ เรากำหนดฟังก์ชันที่ตรวจสอบว่า bitstring ถูกต้องหรือไม่ และทิ้งถ้าไม่ถูกต้อง เมื่อกรอง bitstring ที่ไม่แท้ออกแล้ว ส่วนที่เหลือจะถูกส่งเข้าสู่วิธี diagonalization ใช้แฟลก PRUNE ด้านล่างเพื่อสลับระหว่างสองพฤติกรรม

โปรดจำไว้ว่าการตัดเป็นเพียงหนึ่งในหลายตัวเลือกที่กำหนดรูปร่างของ subspace สุดท้าย ควบคู่ไปกับจำนวน circuit ชุดของเวลาวิวัฒน์ และการกรองพจน์แนวทแยง การเปรียบเทียบการรันที่ตัดกับการรันที่ไม่ตัดจะให้ข้อมูลก็ต่อเมื่อทุกอย่างอื่นคงที่ บทเรียนนี้ เวอร์ชัน C++ อภิปรายเรื่องนี้โดยละเอียดกว่า เพราะใช้ postselect แทนการ recover และยังต่างกันในพารามิเตอร์อื่นเหล่านั้นด้วย

name = "fcidump_files/N2_sto_3g"

fcidump = tools.fcidump.read(name)

# Extract metadata from the FCIDump header
norb = fcidump["NORB"] # number of spatial orbitals
nelec = fcidump["NELEC"] # total number of electrons
e_nuc = fcidump["ECORE"] # nuclear repulsion / core energy
ms2 = fcidump["MS2"] # 2S (spin)

num_elec_a = (nelec + ms2) // 2 # alpha electrons
num_elec_b = (nelec - ms2) // 2 # beta electrons

# Reconstruct full 4-index ERIs from the FCIDump (stored in 8-fold symmetry)
h1e = fcidump["H1"] # shape (norb, norb)
h2e = ao2mo.restore( # shape (norb, norb, norb, norb)
1, fcidump["H2"], norb
)

cisolver = fci.direct_spin1.FCI()
cisolver.max_cycle = 200
cisolver.conv_tol = 1e-12

e_fci, _ = cisolver.kernel(
h1e,
h2e,
norb,
(num_elec_a, num_elec_b),
ecore=e_nuc, # adds nuclear repulsion to the final energy
)

reference_energy = e_fci

print(f"Reference FCI Energy = {reference_energy:.10f} Ha")

nuclear_repulsion_energy = fcidump["ECORE"]
print(f"Nuclear Repulsion Energy = {nuclear_repulsion_energy:.10f} Ha")

fcidump = FCIDump.from_file(name)
hamiltonian = FermionOperator.from_fcidump(fcidump)
num_modes = 2 * fcidump.norb

# Apply automatic grouping
canon = canonical_order(hamiltonian.normal_ordered().simplify(atol=1e-16))
exit_code = group_terms_by_electronic_structure(
canon, num_modes, two_body_physicist_order=False
)
filter_diagonal_terms(canon)

print(len(canon.groups))

# SqDRIFT parameters
times = [1.0, 10.0] # Total evolution times used for the subspace creation
num_exc = 10 # Number of excitation groups per circuit
num_circuits = 200 # Number of circuits to generate

init_circuits = []
hf_gate = InitializeModes.from_hartree_fock(norb, (num_elec_a, num_elec_b))

for time in times:
evo_gate = Evolution(num_modes, canon, time)
circ = FermionicCircuit(num_modes)
circ.append(hf_gate, circ.modes)
circ.append(evo_gate, circ.modes)
init_circuits.append(circ)

# Calculate weights for sampling (one per group)
qdrift = QDriftTrotterization(num_exc, rng=19)

pm = generate_preset_jw_pass_manager()
pm.optimization = FermionicPassManager([qdrift])

sqdrift_circuits = []
for circ in init_circuits:
sqdrift_circuits += (pm.run(circ) for _ in range(num_circuits))

for circ in sqdrift_circuits:
circ.measure_all()

print(len(sqdrift_circuits))

# This example assumes you have saved your IBM Quantum Platform account locally.
service = QiskitRuntimeService(channel="ibm_quantum_platform")

# Select backend (choose based on qubit requirements)
backend = service.least_busy(
operational=True,
simulator=False,
min_num_qubits=2 * norb,
)

print(f"Selected backend: {backend.name} ({backend.num_qubits} qubits)")

# Transpile for hardware
transpiled_circuits = transpile(
sqdrift_circuits,
backend=backend,
optimization_level=3,
seed_transpiler=42,
)

shots = 100

sampler = Sampler(mode=backend)

sampler.options.environment.job_tags = ["TUT-SqDRIFT"]

job = sampler.run(transpiled_circuits, shots=shots)
result = job.result()

# Extract counts from SamplerV2 results
all_counts = [pub_result.data.meas.get_counts() for pub_result in result]

# Set to True to filter out bitstrings that violate electron-number conservation
PRUNE = False

def is_valid_bitstring(
bitstring: str, norb: int, nelec: tuple[int, int]
) -> bool:
n_alpha, n_beta = nelec
return (
len(bitstring) == 2 * norb
and bitstring[norb:].count("1") == n_alpha
and bitstring[:norb].count("1") == n_beta
)

if PRUNE:
all_counts_filtered = []
for counts in all_counts:
filtered_count = {}
for key in counts:
if not is_valid_bitstring(key, norb, (num_elec_a, num_elec_b)):
continue
elif key not in filtered_count.keys():
filtered_count[key] = counts[key]
else:
filtered_count[key] += counts[key]
all_counts_filtered.append(filtered_count)
all_counts = all_counts_filtered

combined_counts = {}
for counts in all_counts:
for bitstring, count in counts.items():
combined_counts[bitstring] = combined_counts.get(bitstring, 0) + count

bit_array = BitArray.from_counts(combined_counts)
print(bit_array.num_shots)

print("Electron configuration:")
print(f" Total electrons: {nelec}")
print(f" Alpha electrons: {num_elec_a}")
print(f" Beta electrons: {num_elec_b}")
print(f" Number of orbitals: {norb}")
print(f" Number of spin orbitals (qubits): {2*norb}")
print(f"Integral shapes: h1e={h1e.shape}, h2e={h2e.shape}")

# SQD parameters
samples_per_batch = 300
num_batches = 3
max_iterations = 5

initial_occupancies = (
np.array([1] * num_elec_a + [0] * (norb - num_elec_a)), # alpha
np.array([1] * num_elec_b + [0] * (norb - num_elec_b)), # beta
)

result_history = []

def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy + nuclear_repulsion_energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)

# Run SQD with configuration recovery
print("\nRunning SQD with configuration recovery...")
result = diagonalize_fermionic_hamiltonian(
h1e,
h2e,
bit_array,
samples_per_batch=samples_per_batch,
norb=norb,
nelec=(num_elec_a, num_elec_b),
num_batches=num_batches,
energy_tol=1e-3,
occupancies_tol=1e-3,
max_iterations=max_iterations,
initial_occupancies=initial_occupancies,
seed=42,
callback=callback,
)

computed_energy = result.energy + nuclear_repulsion_energy

print("FINAL SQD RESULTS")
print(f"Orbital occupancies (alpha): {result.orbital_occupancies[0]}")
print(f"Orbital occupancies (beta): {result.orbital_occupancies[1]}")

energy_error = abs(computed_energy - reference_energy)
print(f"Reference Energy: {reference_energy:.10f} Ha")
print(f"Computed Energy: {computed_energy:.10f} Ha")
print(f"Error: {energy_error:.10e} Ha")
Parsing assets/sqdrift/fcidump_files/N2_sto_3g
Reference FCI Energy = -107.6481842917 Ha
Nuclear Repulsion Energy = 23.7887003074 Ha
5060
400
Selected backend: ibm_aachen (156 qubits)
40000
Electron configuration:
Total electrons: 14
Alpha electrons: 7
Beta electrons: 7
Number of orbitals: 10
Number of spin orbitals (qubits): 20
Integral shapes: h1e=(10, 10), h2e=(10, 10, 10, 10)

Running SQD with configuration recovery...
Iteration 1
Subsample 0
Energy: -107.64593072647523
Subspace dimension: 7221
Subsample 1
Energy: -107.6458270048177
Subspace dimension: 7209
Subsample 2
Energy: -107.64007673117075
Subspace dimension: 7138
Iteration 2
Subsample 0
Energy: -107.64757372124944
Subspace dimension: 9009
Subsample 1
Energy: -107.64674060104392
Subspace dimension: 8245
Subsample 2
Energy: -107.64731360491942
Subspace dimension: 8178
Iteration 3
Subsample 0
Energy: -107.64765518770588
Subspace dimension: 8835
Subsample 1
Energy: -107.64767975712016
Subspace dimension: 8649
Subsample 2
Energy: -107.64761634415606
Subspace dimension: 8648
FINAL SQD RESULTS
Orbital occupancies (alpha): [0.99999504 0.9999964 0.99590318 0.9932359 0.96697158 0.96696295
0.99298797 0.03728154 0.03728186 0.00938359]
Orbital occupancies (beta): [0.9999946 0.99999641 0.99590413 0.99323077 0.96697361 0.96696174
0.99298424 0.03728121 0.03728169 0.00939159]
Reference Energy: -107.6481842917 Ha
Computed Energy: -107.6476797571 Ha
Error: 5.0453460619e-04 Ha

ขั้นตอนถัดไป​

คำแนะนำ

หากคุณสนใจงานนี้ คุณอาจสนใจเนื้อหาต่อไปนี้:

  • Sample-based Krylov quantum diagonalization of a fermionic lattice model - บทเรียนที่เกี่ยวข้องซึ่งใช้ circuit วิวัฒน์ตามเวลาแทน ansatz แบบแปรผัน
  • Sample-based quantum diagonalization of a chemistry Hamiltonian - บทเรียนเกี่ยวกับวิธีสร้าง circuit แบบ local unitary cluster Jastrow (LUCJ) สำหรับการจำลองเคมีควอนตัม
  • บทความ SqDRIFT - งานวิชาการที่บทเรียนนี้อ้างอิง (โปรดทราบว่าการปรับแต่งบางอย่างที่กล่าวถึงในบทความนี้ยังอยู่ระหว่างการพัฒนา และบทเรียนนี้อาจเปลี่ยนแปลงในอนาคตตามความก้าวหน้าของไลบรารีที่ใช้)