Understand open-shell versus closed-shell options and the effect on subspace construction
In this guide, we will show how to choose subspace dimensions in the qiskit_addon_sqd package to post-process quantum samples using the self-consistent configuration recovery technique.
More importantly, this guide also highlights some differences in the behavior in the subspace construction when run in open_shell = False or open_shell = True modes:
-
open_shell = Falseonly works when the number of spin-up and spin-down electrons is the same. -
open_shell = Truemust be used when the number of spin-up and spin-down electrons is different. It can also be used when the number of spin-up and spin-down electrons is the same. However, in this last case, there is a difference in the sizes of the subspaces generated betweenopen_shell = Falseandopen_shell = True, as discussed in this guide.
NOTE: Some of the electronic-configuration (bitstring) manipulations in this package have as a goal to preserve the total spin symmetry . Standard Selected Configuration Interaction (SCI) solvers cannot impose conservation exactly. Consequently, they do so approximately via a Lagrange multiplier.
The choice of electronic configurations entering the eigenstate solver can also have a strong effect in the conservation of spin. For example, in a (2-electron, 2-orbital) system, one can sample the configuration (having a single spin-up excitation over the RHF state ), which is a linear combination of the open-shell singlet and triplet states, respectively . If the configuration |0110⟩ is not sampled, one can construct neither eigenfunction of total spin, leading to spin contamination or redundancy (the configuration |1001⟩ is involved in a CI calculation, but has coefficient 0 in the CI vector). Given that a single sample is generated in the quantum computer, this is how the sqd package handles this situation:
-
open_shell = False:-
The bitstring is split in half, representing spin-up and spin-down configurations: (up) and (down).
-
The list of unique spin-polarized configurations is constructed: .
-
We then consider all possible combinations of elements to form the basis: , which contains the singlet and triplet states.
-
-
open_shell = True:- Contrary to the
open_shell = Falsecase, we do not combine the halves of the bitstring to form the basis.
- Contrary to the
Closed-shell
This example shows how the bitstrings are manipulated in a (2-electron, 4-orbital) system.
# Specify molecule properties
num_orbitals = 4
num_elec_a = num_elec_b = 1
open_shell = FalseSpecify by hand a dictionary of measurement outcomes:
counts_dict = {
"00010010": 1 / 2.0 - 0.01,
"01001000": 1 / 2.0 - 0.01,
"00010001": 0.02,
}Transform the counts dict into a bitstring matrix and probability array for post-processing:
from qiskit_addon_sqd.counts import counts_to_arrays
# Convert counts into bitstring and probability arrays
bitstring_matrix_full, probs_arr_full = counts_to_arrays(counts_dict)
print(bitstring_matrix_full)
print(probs_arr_full)Output:
[[False False False True False False True False]
[False True False False True False False False]
[False False False True False False False True]]
[0.49 0.49 0.02]
Subsample a single batch of size two:
n_batches = 1: Number of batches of configurations used by the different calls to the eigenstate solversamples_per_batch = 2: Number of unique configurations to include in each batch
from qiskit_addon_sqd.subsampling import postselect_and_subsample
n_batches = 1
samples_per_batch = 2
# seed for random number generator
rand_seed = 48
# Generate the batches
batches = postselect_and_subsample(
bitstring_matrix_full,
probs_arr_full,
hamming_right=num_elec_a,
hamming_left=num_elec_b,
samples_per_batch=samples_per_batch,
num_batches=n_batches,
rand_seed=rand_seed,
)
print(batches[0])Output:
[[False False False True False False True False]
[False True False False True False False False]]
Obtain decimal representation of the spin-up and spin-down bitstrings used by the eigenstate solver.
The first element in the tuple corresponds to the decimal representation of the spin-up configurations, while the second element in the tuple corresponds to the decimal representation of the spin-down configurations:
from qiskit_addon_sqd.fermion import bitstring_matrix_to_ci_strs
ci_strs = bitstring_matrix_to_ci_strs(batches[0], open_shell=open_shell)
print(ci_strs)Output:
(array([1, 2, 4, 8], dtype=int64), array([1, 2, 4, 8], dtype=int64))
Note that while the number of samples per batch is two, and the sampled bitstrings are: and , four electronic configurations are generated per spin-species. In this case, the set of unique spin-polarized configurations is given by:
whose base-10 decimal representation is
Basis of the subspace:
The eigenstate solver takes all possible pairs of spin-up and spin-down bitstrings to construct the basis of the subspace:
-
Element 1:
-
Element 2:
-
Element 3:
-
Element 4:
-
Element 5:
-
Element 6:
-
Element 7:
-
Element 8:
-
Element 9:
-
Element 10:
-
Element 11:
-
Element 12:
-
Element 13:
-
Element 14:
-
Element 15:
-
Element 16:
ci_strs_up, ci_strs_dn = ci_strs
print("Basis elements of the subspace:")
for ci_str_up in ci_strs_up:
for ci_str_dn in ci_strs_dn:
format_name = "{0:0" + str(num_orbitals) + "b}"
print(
"|"
+ format_name.format(ci_str_up)
+ format_name.format(ci_str_dn)
+ ">"
)Output:
Basis elements of the subspace:
|00010001>
|00010010>
|00010100>
|00011000>
|00100001>
|00100010>
|00100100>
|00101000>
|01000001>
|01000010>
|01000100>
|01001000>
|10000001>
|10000010>
|10000100>
|10001000>
The subspace dimension is upper-bounded by: (samples_per_batch).
Open-shell
This example shows how the bitstrings are manipulated in a (2-electron, 4-orbital) system.
# Specify molecule properties
num_orbitals = 4
num_elec_a = num_elec_b = 1
open_shell = TrueSpecify by hand a dictionary of measurement outcomes:
counts_dict = {
"00010010": 1 / 2.0 - 0.01,
"01001000": 1 / 2.0 - 0.01,
"00010001": 0.02,
}Transform the counts dict into a bitstring matrix and probability array for post-processing:
# Convert counts into bitstring and probability arrays
bitstring_matrix_full, probs_arr_full = counts_to_arrays(counts_dict)
print(bitstring_matrix_full)
print(probs_arr_full)Output:
[[False False False True False False True False]
[False True False False True False False False]
[False False False True False False False True]]
[0.49 0.49 0.02]
Subsample a single batch of size two:
n_batches = 1: Number of batches of configurations used by the different calls to the eigenstate solversamples_per_batch = 2: Number of unique configurations to include in each batch
n_batches = 1
samples_per_batch = 2
# seed for random number generator
rand_seed = 48
# Generate the batches
batches = postselect_and_subsample(
bitstring_matrix_full,
probs_arr_full,
hamming_right=num_elec_a,
hamming_left=num_elec_b,
samples_per_batch=samples_per_batch,
num_batches=n_batches,
rand_seed=rand_seed,
)
print(batches[0])Output:
[[False False False True False False True False]
[False True False False True False False False]]
Obtain decimal representation of the spin-up and spin-down bitstrings used by the eigenstate solver.
The first element in the tuple corresponds to the decimal representation of the spin-up configurations, while the second element in the tuple corresponds to the decimal representation of the spin-down configurations:
ci_strs = bitstring_matrix_to_ci_strs(batches[0], open_shell=open_shell)
print(ci_strs)Output:
(array([2, 8], dtype=int64), array([1, 4], dtype=int64))
If we specify that open_shell = True, we do not include all unique half-bitstrings as spin-up and spin-down configurations, thus yielding a smaller basis than when specifying open_shell = False.
Basis of the subspace:
The eigenstate solver takes all possible pairs of spin-up and spin-down bitstrings to construct the basis of the subspace:
-
Element 1:
-
Element 2:
-
Element 3:
-
Element 4:
ci_strs_up, ci_strs_dn = ci_strs
print("Basis elements of the subspace:")
for ci_str_up in ci_strs_up:
for ci_str_dn in ci_strs_dn:
format_name = "{0:0" + str(num_orbitals) + "b}"
print(
"|"
+ format_name.format(ci_str_up)
+ format_name.format(ci_str_dn)
+ ">"
)Output:
Basis elements of the subspace:
|00100001>
|00100100>
|10000001>
|10000100>
The subspace dimension is upper-bounded by: (samples_per_batch).