How to build Qiskit circuits for Trotterized Hamiltonian simulation¶
This guide provides some examples of building and transpiling Qiskit circuits for Trotterized time evolution of various Hamiltonians.
[1]:
import numpy as np
import pyscf
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import ffsim
Molecular Hamiltonian in the double-factorized representation¶
[2]:
# Build N2 molecule
mol = pyscf.gto.Mole()
mol.build(
atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]],
basis="sto-6g",
symmetry="Dooh",
)
# Define active space
n_frozen = pyscf.data.elements.chemcore(mol)
active_space = range(n_frozen, mol.nao_nr())
# Get molecular data and Hamiltonian
scf = pyscf.scf.RHF(mol).run()
mol_data = ffsim.MolecularData.from_scf(scf, active_space=active_space)
norb, nelec = mol_data.norb, mol_data.nelec
mol_hamiltonian = mol_data.hamiltonian
print(f"norb = {norb}")
print(f"nelec = {nelec}")
# Get Hamiltonian in double-factorized representation
df_hamiltonian = ffsim.DoubleFactorizedHamiltonian.from_molecular_hamiltonian(
mol_hamiltonian
)
# Construct circuit
# For the initial state, take the Hartree-Fock state
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
circuit.append(ffsim.qiskit.PrepareHartreeFockJW(norb, nelec), qubits)
circuit.append(
ffsim.qiskit.SimulateTrotterDoubleFactorizedJW(
df_hamiltonian, time=1.0, n_steps=1, order=0
),
qubits,
)
circuit.measure_all()
# Create a generic backend and transpile the circuit to it
backend = GenericBackendV2(2 * norb, basis_gates=["cp", "xx_plus_yy", "p", "x"])
pass_manager = generate_preset_pass_manager(optimization_level=3, backend=backend)
# Set the pre-initialization stage of the pass manager with passes suggested by ffsim
pass_manager.pre_init = ffsim.qiskit.PRE_INIT
transpiled = pass_manager.run(circuit)
transpiled.count_ops()
converged SCF energy = -108.464957764796
norb = 8
nelec = (5, 5)
[2]:
OrderedDict([('xx_plus_yy', 1946),
('cp', 1506),
('p', 462),
('measure', 16),
('x', 10),
('barrier', 1)])
Fermi-Hubbard model with split-operator method¶
[3]:
# Build Hubbard model and set number of electrons
norb_x = 4
norb_y = 4
norb = norb_x * norb_y
nelec = (2, 2)
n_alpha, n_beta = nelec
op = ffsim.fermi_hubbard_2d(
norb_x=norb_x,
norb_y=norb_y,
tunneling=1.0,
interaction=4.0,
chemical_potential=0.0,
periodic_x=True,
periodic_y=False,
)
# Convert the FermionOperator to a DiagonalCoulombHamiltonian
dc_hamiltonian = ffsim.DiagonalCoulombHamiltonian.from_fermion_operator(op)
# Construct circuit
# For the initial state, take the ground state of the one-body part of the Hamiltonian
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
eigs, orbital_rotation = np.linalg.eigh(dc_hamiltonian.one_body_tensor)
circuit.append(
ffsim.qiskit.PrepareSlaterDeterminantJW(
norb,
occupied_orbitals=(range(n_alpha), range(n_beta)),
orbital_rotation=orbital_rotation,
),
qubits,
)
circuit.append(
ffsim.qiskit.SimulateTrotterDiagCoulombSplitOpJW(
dc_hamiltonian, time=1.0, n_steps=1, order=0
),
qubits,
)
circuit.measure_all()
# Create a generic backend and transpile the circuit to it
backend = GenericBackendV2(2 * norb, basis_gates=["cp", "xx_plus_yy", "p", "x"])
pass_manager = generate_preset_pass_manager(optimization_level=3, backend=backend)
# Set the pre-initialization stage of the pass manager with passes suggested by ffsim
pass_manager.pre_init = ffsim.qiskit.PRE_INIT
transpiled = pass_manager.run(circuit)
transpiled.count_ops()
[3]:
OrderedDict([('xx_plus_yy', 514),
('p', 54),
('measure', 32),
('cp', 16),
('x', 4),
('barrier', 1)])