State vectors and gates¶
This page explains how state vectors are represented in ffsim and how you apply gates to them.
State vectors¶
In ffsim, state vectors are represented as plain one-dimensional NumPy arrays. The length of a state vector is determined by the number of orbitals in the system and their occupancies. The number of
You can contrast this expression with a generic quantum circuit simulator, for which a state vector would have length
ffsim includes convenient functions to calculate the full dimension of the vector space as well as the dimensions of the individual spin subsystems.
[1]:
import ffsim
# Let's use 3 spatial orbitals with 2 alpha electrons and 1 beta electron.
norb = 3
nelec = (2, 1)
# Get the dimension of the vector space.
dim = ffsim.dim(norb, nelec)
# We can also get the dimensions of the alpha- and beta- spaces separately.
dim_a, dim_b = ffsim.dims(norb, nelec)
# The full dimension is the product of alpha- and beta- dimensions.
assert dim == dim_a * dim_b
print(f"The dimension of the vector space is {dim}.")
print(f"On the other hand, 2 ** (2 * norb) = {2 ** (2 * norb)}.")
The dimension of the vector space is 9.
On the other hand, 2 ** (2 * norb) = 64.
Each entry of the state vector is associated with an electronic configuration, which can be labeled by the concatenation of two bitstrings, pyscf.fci
. You can use the addresses_to_strings
function in ffsim to convert a list of state vector indices to the corresponding bitstrings.
[2]:
strings = ffsim.addresses_to_strings(
range(dim), norb=norb, nelec=nelec, bitstring_type=ffsim.BitstringType.STRING
)
strings
[2]:
['001011',
'010011',
'100011',
'001101',
'010101',
'100101',
'001110',
'010110',
'100110']
The first electronic configuration always has the electrons occupying the lowest-numbered orbitals (note that the bit positions increase from right to left). When using molecular orbitals, this configuration corresponds to the Hartree-Fock state. ffsim includes a convenient function to construct the Hartree-Fock state, which is just a vector with a 1 in its first position and 0 everywhere else:
[3]:
vec = ffsim.hartree_fock_state(norb, nelec)
vec
[3]:
array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
0.+0.j])
It is sometimes convenient to represent the state vector as a matrix whose rows are indexed by the spin
[4]:
mat = vec.reshape((dim_a, dim_b))
mat
[4]:
array([[1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j]])
Gates¶
In ffsim, you apply a unitary gate to a state vector by calling a function whose name begins with apply_
. For example, the function for applying an orbital rotation is called apply_orbital_rotation
. The first argument to the function is always the state vector itself. The number of orbitals, as well as the number of alpha and beta electrons, are passed as the arguments norb
and nelec
. See the API reference for the full list of supported gates and their
definitions (search for ffsim.apply_
).
As an example, the following code cell generates a random orbital rotation (represented by an
[5]:
# Generate a random orbital rotation.
orbital_rotation = ffsim.random.random_unitary(norb, seed=1234)
# Apply the orbital rotation to the state vector.
rotated_vec = ffsim.apply_orbital_rotation(
vec, orbital_rotation, norb=norb, nelec=nelec
)
rotated_vec
[5]:
array([ 0.23611476+0.03101213j, -0.06273307+0.1102529j ,
0.09723851+0.36730125j, 0.13113848+0.17276745j,
-0.11157654+0.02998708j, -0.17558331+0.29821173j,
-0.20881506-0.33731417j, 0.20835741-0.03525116j,
0.3714141 -0.51253171j])
As a further demonstration, let’s apply a few more gates to the rotated state vector.
[6]:
# Apply some more gates
rotated_vec = ffsim.apply_on_site_interaction(
rotated_vec, 0.1, 2, norb=norb, nelec=nelec
)
rotated_vec = ffsim.apply_tunneling_interaction(
rotated_vec, 0.1, (0, 1), norb=norb, nelec=nelec
)
rotated_vec
[6]:
array([ 0.22392824+0.02459434j, -0.06551571+0.13327423j,
0.09723851+0.36730125j, 0.15828306+0.13957088j,
-0.12204343+0.06677383j, -0.15624569+0.31980058j,
-0.21928194-0.30052742j, 0.23550198-0.06844774j,
0.39075171-0.49094286j])
Treating spinless fermions¶
Many functions in ffsim support spinless fermions, which are not distinguished into spin nelec
variable is simply an integer, rather than a pair of integers. The following code cell gives an example of creating a spinless state vector and applying a gate to it.
[7]:
norb = 3
nelec = 2
vec = ffsim.hartree_fock_state(norb, nelec)
orbital_rotation = ffsim.random.random_unitary(norb, seed=1234)
rotated_vec = ffsim.apply_orbital_rotation(
vec, orbital_rotation, norb=norb, nelec=nelec
)
rotated_vec
[7]:
array([-0.4390672 -0.1561685j , -0.18007105-0.38435478j,
0.26121865+0.73105542j])
How much memory does a state vector occupy?¶
The following code cell shows how to compute the number of gibibytes of memory occupied by the state vector for a system with a specified number of spatial orbitals,
[8]:
# 128 bits per complex number, 8 bits per byte, 1024**3 bytes per GiB
GIB_PER_AMPLITUDE = 128 / 8 / 1024**3
norb = 26
nelec = (5, 5)
dim = ffsim.dim(norb, nelec)
gib = GIB_PER_AMPLITUDE * dim
print(f"Storage required for state vector: {gib:.2f} GiB")
Storage required for state vector: 64.48 GiB