Simulate Trotterized time evolution for the molecular Hamiltonian

In this guide, we show how to use ffsim to simulate Trotterized time evolution for the molecular Hamiltonian in the double-factorized representation.

First, let’s build a molecule to use as an example.

[1]:
import pyscf

import ffsim

# 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}")
converged SCF energy = -108.464957764796
norb = 8
nelec = (5, 5)

Next, let’s get the Hamiltonian in double-factorized form.

[2]:
df_hamiltonian = ffsim.DoubleFactorizedHamiltonian.from_molecular_hamiltonian(
    mol_hamiltonian
)

print(
    f"Number of terms in double factorization: {len(df_hamiltonian.diag_coulomb_mats)}"
)
Number of terms in double factorization: 35

Now, let’s apply time evolution to an initial state, which we’ll take as the Hartree-Fock state here.

[3]:
# Construct the initial state.
initial_state = ffsim.hartree_fock_state(norb, nelec)

# Set the evolution time.
time = 1.0

# Perform the Trotterized time evolution
final_state = ffsim.simulate_trotter_double_factorized(
    initial_state,
    df_hamiltonian,
    time,
    norb=norb,
    nelec=nelec,
    n_steps=5,
    order=0,
)

To check the error from Trotterization, we can compute the result of exact time evolution and compare it with the final state we got.

[4]:
import numpy as np
import scipy.sparse.linalg

# Convert the Hamiltonian to a LinearOperator
linop = ffsim.linear_operator(mol_hamiltonian, norb=norb, nelec=nelec)

# Compute the exact result of time evolution
exact_state = scipy.sparse.linalg.expm_multiply(
    -1j * time * linop,
    initial_state,
    traceA=-1j * time * ffsim.trace(mol_hamiltonian, norb=norb, nelec=nelec),
)

# Compute fidelity between results from Trotterized evolution and exact evolution
fidelity = abs(np.vdot(final_state, exact_state))

print(f"Fidelity of Trotter-evolved state with exact state: {fidelity}")
Fidelity of Trotter-evolved state with exact state: 0.9985212764983

To reduce the Trotter error, we can increase the number of steps or use a higher-order formula.

[5]:
final_state = ffsim.simulate_trotter_double_factorized(
    initial_state,
    df_hamiltonian,
    time,
    norb=norb,
    nelec=nelec,
    n_steps=5,
    order=1,
)

fidelity = abs(np.vdot(final_state, exact_state))

print(f"Fidelity of Trotter-evolved state with exact state: {fidelity}")
Fidelity of Trotter-evolved state with exact state: 0.9999923953867433