Sample from state vectors¶
This guide shows how to use the ffsim.sample_state_vector function to sample configurations (bitstrings) from fermionic state vectors. We will cover:
Spinful vs spinless systems
Sampling all orbitals vs a subset of orbitals
Different bitstring output types
Using
StateVectorobjects vs raw NumPy arrays
Spinful example: alpha–beta sectors¶
We consider a spinful system with separate alpha and beta electrons. Here:
norbis the number of spatial orbitals.nelecis a tuple(n_alpha, n_beta).
The state vector lives in the tensor product of alpha and beta subspaces, each with fixed particle number. ffsim.sample_state_vector can return bitstrings either:
concatenated as \(s_b s_a\) (beta on the left, alpha on the right), or
split as a pair
(strings_a, strings_b).
The following example samples from a Hartree-Fock state, which always yields the Hartree-Fock configuration.
[1]:
import numpy as np
import ffsim
# Initialize pseudorandom number generator
rng = np.random.default_rng(12345)
# Spinful example: 3 spatial orbitals, 2 alpha and 1 beta electron
norb = 3
nelec = (2, 1)
# Hartree–Fock state in this subspace
vec_spinful = ffsim.hartree_fock_state(norb, nelec)
print("Hartree–Fock state:")
print(vec_spinful)
# Sample 5 bitstrings with default concatenation (s_b s_a)
samples_concat = ffsim.sample_state_vector(
vec_spinful, norb=norb, nelec=nelec, shots=5, seed=rng
)
print("Concatenated samples (s_b s_a):", samples_concat)
# Sample again, but keep alpha and beta strings separate
samples_a, samples_b = ffsim.sample_state_vector(
vec_spinful, norb=norb, nelec=nelec, shots=5, concatenate=False, seed=rng
)
print("Alpha strings:", samples_a)
print("Beta strings:", samples_b)
Hartree–Fock state:
[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]
Concatenated samples (s_b s_a): ['001011', '001011', '001011', '001011', '001011']
Alpha strings: ['011', '011', '011', '011', '011']
Beta strings: ['001', '001', '001', '001', '001']
For spinful systems:
Each alpha configuration is a bitstring of length
norb.Each beta configuration is also a bitstring of length
norb.
By default (concatenate=True), the output for each sample is a single bitstring "s_b s_a":
The beta string
s_bappears on the left.The alpha string
s_aappears on the right.
If you set concatenate=False, sample_state_vector returns two lists:
strings_a: one alpha bitstring for each shot,strings_b: one beta bitstring for each shot.
This is often convenient when post-processing alpha and beta sectors separately.
Spinless example: sampling all orbitals¶
We now look at a spinless system where we only track one kind of fermion. Again norb is the number of spatial orbitals. But, nelec is not a tuple, it is a single integer and the Hilbert space lives in the fixed-particle-number subspace. After having norb and nelec, we will generate a random normalized state vector in that subspace. Then, we call ffsim.sample_state_vector to draw bitstrings.
[2]:
# Spinless example: 4 orbitals, 2 fermions
norb = 4
nelec = 2 # spinless: just one integer
# Dimension of the fixed-particle-number space
dim = ffsim.dim(norb, nelec)
print(f"Dimension (spinless, norb={norb}, nelec={nelec}): {dim}")
# Random normalized state vector
vec = ffsim.random.random_state_vector(dim, seed=rng)
# Sample 5 bitstrings from this state
samples = ffsim.sample_state_vector(vec, norb=norb, nelec=nelec, shots=5, seed=rng)
print("Samples (strings):", samples)
Dimension (spinless, norb=4, nelec=2): 6
Samples (strings): ['0011', '0101', '0011', '1001', '0011']
Each sampled bitstring has length norb in the spinless case. For example, for norb = 4:
"0101"means orbitals 0 and 2 are occupied (reading from right to left),"1100"means orbitals 2 and 3 are occupied, etc.
Sampling a subset of orbitals¶
Sometimes, we only want measurement outcomes on a subset of spin-orbitals: for example, when we are only interested in a local region or a fragment.
This is controlled by the orbs argument.
Spinless:
orbsis a list of spatial orbital indices (0 tonorb - 1).
Spinful:
orbsis a pair(orbs_a, orbs_b)of lists for alpha and beta.
[3]:
# Spinless again: 4 orbitals, 2 fermions
norb = 4
nelec = 2
dim = ffsim.dim(norb, nelec)
vec = ffsim.random.random_state_vector(dim, seed=rng)
print("All-orbit samples:")
samples_all = ffsim.sample_state_vector(vec, norb=norb, nelec=nelec, shots=5, seed=rng)
print(samples_all)
print("\nSamples restricted to orbitals [0, 2]:")
samples_02 = ffsim.sample_state_vector(
vec, norb=norb, nelec=nelec, orbs=[0, 2], shots=5, seed=rng
)
print(samples_02)
All-orbit samples:
['0101', '0110', '0110', '0101', '1100']
Samples restricted to orbitals [0, 2]:
['11', '11', '11', '10', '01']
[4]:
# Spinful subset example
norb = 3
nelec = (2, 1)
vec_spinful = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng)
# Define which spatial orbitals to keep for alpha and beta
orbs_a = [0, 2] # alpha sector
orbs_b = [1, 2] # beta sector
samples_a, samples_b = ffsim.sample_state_vector(
vec_spinful,
norb=norb,
nelec=nelec,
orbs=(orbs_a, orbs_b),
shots=5,
concatenate=False,
seed=rng,
)
print("Restricted alpha strings:", samples_a)
print("Restricted beta strings:", samples_b)
Restricted alpha strings: ['11', '10', '01', '10', '01']
Restricted beta strings: ['01', '10', '10', '10', '10']
Choosing the bitstring type¶
The bitstring_type argument controls how bitstrings are represented: ffsim.BitstringType has three options:
STRING: lists of strings like"0101"(default),INT: lists of integers, where each integer is the bitstring interpreted in binary (e.g."0101" → 5),BIT_ARRAY: 2D NumPy arrays of booleans, where each row is one sample.
This is useful when interfacing with different libraries or when you prefer a specific format for analysis.
[5]:
norb = 4
nelec = 2
dim = ffsim.dim(norb, nelec)
vec = ffsim.random.random_state_vector(dim, seed=rng)
# Default: STRING
samples_str = ffsim.sample_state_vector(
vec,
norb=norb,
nelec=nelec,
shots=4,
bitstring_type=ffsim.BitstringType.STRING,
seed=rng,
)
print("STRING:", samples_str)
# Integers
samples_int = ffsim.sample_state_vector(
vec,
norb=norb,
nelec=nelec,
shots=4,
bitstring_type=ffsim.BitstringType.INT,
seed=rng,
)
print("INT :", samples_int)
# Bit arrays
samples_array = ffsim.sample_state_vector(
vec,
norb=norb,
nelec=nelec,
shots=4,
bitstring_type=ffsim.BitstringType.BIT_ARRAY,
seed=rng,
)
print("BIT_ARRAY shape:", samples_array.shape)
print(samples_array.astype(int))
STRING: ['0101', '1010', '1010', '1010']
INT : [10, 6, 3, 6]
BIT_ARRAY shape: (4, 4)
[[1 0 1 0]
[1 1 0 0]
[1 0 1 0]
[1 0 1 0]]
Seeding and reproducibility¶
The seed argument lets you seed the pseudorandom number generator, allowing your results to be reproducible. The seed can be anything accepted by np.random.default_rng, such as an integer or an existing Generator instance.
[6]:
norb = 3
nelec = 2
dim = ffsim.dim(norb, nelec)
vec = ffsim.random.random_state_vector(dim, seed=rng)
# Same seed → same samples
samples1 = ffsim.sample_state_vector(vec, norb=norb, nelec=nelec, shots=6, seed=2025)
samples2 = ffsim.sample_state_vector(vec, norb=norb, nelec=nelec, shots=6, seed=2025)
print("Samples 1:", samples1)
print("Samples 2:", samples2)
Samples 1: ['110', '110', '110', '110', '110', '101']
Samples 2: ['110', '110', '110', '110', '110', '101']
Using StateVector objects¶
Many parts of ffsim work with a StateVector dataclass that stores:
the raw vector coefficients,
the number of spatial orbitals
norb,the electron numbers
nelec.
sample_state_vector accepts either:
a raw
np.ndarrayplus explicitnorbandnelec, ora
StateVectorobject, in which casenorbandnelecmust be omitted.
[7]:
from ffsim.states import StateVector
norb = 4
nelec = (2, 2)
dim = ffsim.dim(norb, nelec)
# Raw state vector
vec_array = ffsim.random.random_state_vector(dim, seed=rng)
# Wrap into a StateVector
state = StateVector(vec=vec_array, norb=norb, nelec=nelec)
# Case 1: raw NumPy array → need norb and nelec
samples_array = ffsim.sample_state_vector(
vec_array, norb=norb, nelec=nelec, shots=4, seed=rng
)
print("Samples from raw array:", samples_array)
# Case 2: StateVector → norb and nelec taken from the object
samples_state = ffsim.sample_state_vector(state, shots=4, seed=rng)
print("Samples from StateVector:", samples_state)
Samples from raw array: ['10010011', '00111100', '01010011', '01100110']
Samples from StateVector: ['10100011', '10100011', '10100011', '00111100']
Summary¶
In this guide we showed how to use ffsim.sample_state_vector to:
sample from spinless and spinful state vectors,
restrict sampling to subsets of orbitals via
orbs,choose different bitstring output formats via
bitstring_type,control randomness with
seed,work with both raw NumPy arrays and
StateVectorobjects.