Compute expectation values¶
Computing the expectation value of an operator on a state vector involves the following steps:
Convert your operator to a SciPy LinearOperator using the
ffsim.linear_operatorprotocol.Use matrix multiplication and
np.vdotto compute the expectation value.
If you already have a SciPy LinearOperator, you can skip Step 1. Otherwise, you can get a LinearOperator from any object that implements ffsim’s SupportsLinearOperator protocol. Such objects include the Hamiltonian classes as well as the FermionOperator class.
The following code cell demonstrates these steps for a molecular Hamiltonian with randomly generated one- and two-body tensors.
[1]:
import numpy as np
import ffsim
# Initialize pseudorandom number generator
rng = np.random.default_rng(12345)
# Set numbers of orbitals and electrons to use for example
norb = 8
nelec = (5, 5)
# Generate a random state vector
vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng)
# Generate random one- and two-body tensors
one_body_tensor = ffsim.random.random_hermitian(norb, seed=rng)
two_body_tensor = ffsim.random.random_two_body_tensor(norb, seed=rng)
# Form MolecularHamiltonian object
mol_ham = ffsim.MolecularHamiltonian(
one_body_tensor=one_body_tensor, two_body_tensor=two_body_tensor
)
# Convert the Hamiltonian to a SciPy LinearOperator
linop = ffsim.linear_operator(mol_ham, norb=norb, nelec=nelec)
# The expectation value of a Hamiltonian is real, so we can discard the imaginary part
expectation_value = np.vdot(vec, linop @ vec).real
print(expectation_value)
-998.3155443224805
For a molecular system, the expectation value of the Hamiltonian on the Hartree-Fock state equals the Hartree-Fock energy.
[2]:
import pyscf
import pyscf.data.elements
# Build N2 molecule
mol = pyscf.gto.Mole()
mol.build(atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]], basis="sto-6g", symmetry="Dooh")
scf = pyscf.scf.RHF(mol).run()
# Use the MolecularData class to obtain the MolecularHamiltonian for the molecule
mol_data = ffsim.MolecularData.from_scf(scf)
norb, nelec = mol_data.norb, mol_data.nelec
mol_hamiltonian = mol_data.hamiltonian
# Compute the expectation value on the Hartree-Fock state
linop = ffsim.linear_operator(mol_hamiltonian, norb=norb, nelec=nelec)
vec = ffsim.hartree_fock_state(norb, nelec)
expectation_value = np.vdot(vec, linop @ vec).real
# It should match the Hartree-Fock energy
print(expectation_value)
converged SCF energy = -108.464957764796
-108.46495776479557
As mentioned above, you can obtain LinearOperators from FermionOperators as well as other Hamiltonian classes. The following code example constructs a Fermi-Hubbard model Hamiltonian as a FermionOperator, then converts it to an instance of the DiagonalCoulombHamiltonian class. Both classes can be converted to LinearOperators, yielding the same expectation value.
[3]:
# Set Fermi-Hubbard model parameters
norb_x = 3
norb_y = 2
tunneling = 1.0
interaction = 4.0
norb = norb_x * norb_y
nelec = (norb // 2, norb // 2)
# Create the Hamiltonian as a FermionOperator
op = ffsim.fermi_hubbard_2d(
norb_x=norb_x, norb_y=norb_y, tunneling=tunneling, interaction=interaction
)
# Generate a random state vector
vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng)
# Compute the expectation value
linop = ffsim.linear_operator(op, norb=norb, nelec=nelec)
print(np.vdot(vec, linop @ vec).real)
# Convert to a DiagonalCoulombHamiltonian and compute the expectation value
dc_ham = ffsim.DiagonalCoulombHamiltonian.from_fermion_operator(op)
linop = ffsim.linear_operator(dc_ham, norb=norb, nelec=nelec)
print(np.vdot(vec, linop @ vec).real)
5.926393653639861
5.926393653639866