Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Comprendre les options à couches ouvertes et à couches fermées et leur effet sur la construction du sous-espace

Dans ce guide, nous montrons comment choisir les dimensions des sous-espaces dans le package qiskit_addon_sqd pour post-traiter des échantillons quantiques à l'aide de la technique de récupération de configurations auto-cohérente.

Plus important encore, ce guide met aussi en évidence certaines différences de comportement dans la construction du sous-espace selon que l'on utilise le mode open_shell = False ou open_shell = True :

  • open_shell = False ne fonctionne que lorsque le nombre d'électrons de spin up et de spin down est identique.

  • open_shell = True doit être utilisé lorsque le nombre d'électrons de spin up et de spin down est différent. Il peut aussi être utilisé lorsque ce nombre est identique. Cependant, dans ce dernier cas, les tailles des sous-espaces générés diffèrent entre open_shell = False et open_shell = True, comme expliqué dans ce guide.

NOTE : Certaines manipulations des configurations électroniques (chaînes de bits) de ce package ont pour objectif de préserver la symétrie de spin totale S2S^2. Les solveurs standards d'interaction de configurations sélectionnées (SCI) ne peuvent pas imposer exactement la conservation de S2S^2. Ils le font donc de manière approchée via un multiplicateur de Lagrange.

Le choix des configurations électroniques fournies au solveur d'états propres peut aussi avoir un fort effet sur la conservation du spin. Par exemple, dans un système (2 électrons, 2 orbitales), on peut échantillonner la configuration ∣1001⟩|1001\rangle (qui présente une seule excitation de spin up par rapport à l'état RHF ∣0101⟩|0101\rangle), qui est une combinaison linéaire des états singulet et triplet à couches ouvertes, respectivement (∣1001⟩±∣0110⟩)/2(|1001\rangle ± |0110\rangle) /\sqrt{2}. Si la configuration |0110⟩ n'est pas échantillonnée, on ne peut construire aucune des fonctions propres de spin total, ce qui conduit à une contamination de spin ou à une redondance (la configuration |1001⟩ intervient dans un calcul CI, mais a un coefficient 0 dans le vecteur CI). Étant donné qu'un seul échantillon ∣1001⟩|1001\rangle est généré dans l'ordinateur quantique, voici comment le package sqd gère cette situation :

  • open_shell = False:

    1. La chaîne de bits 10011001 est coupée en deux, représentant les configurations de spin up et de spin down : 1010 (up) et 0101 (down).

    2. La liste des configurations uniques polarisées en spin est construite : U=[01,10]\mathcal{U} = [01, 10].

    3. Nous considérons ensuite toutes les combinaisons possibles des éléments de U\mathcal{U} pour former la base : {∣0101⟩,∣0110⟩,∣1001⟩,∣1010⟩}\left \{ |0101\rangle, |0110\rangle , |1001\rangle , |1010\rangle \right \}, qui contient les états singulet et triplet.

  • open_shell = True:

    1. Contrairement au cas open_shell = False, nous ne combinons pas les moitiés de la chaîne de bits pour former la base.

Couches fermées​

Cet exemple montre comment les chaînes de bits sont manipulées dans un système (2 électrons, 4 orbitales).

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit_addon_sqd": "qiskit-addon-sqd"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Specify molecule properties
num_orbitals = 4
num_elec_a = num_elec_b = 1
open_shell = False

Spécifie à la main un dictionnaire de résultats de mesure :

counts_dict = {
"00010010": 1 / 2.0 - 0.01,
"01001000": 1 / 2.0 - 0.01,
"00010001": 0.02,
}

Transforme le dict de comptages en une matrice de chaînes de bits et un tableau de probabilités pour le post-traitement :

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)
[[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]

Sous-échantillonne un seul lot de taille deux :

  • n_batches = 1 : nombre de lots de configurations utilisés par les différents appels au solveur d'états propres

  • samples_per_batch = 2 : nombre de configurations uniques à inclure dans chaque lot

from qiskit_addon_sqd.subsampling import (
postselect_by_hamming_right_and_left,
subsample,
)

n_batches = 1
samples_per_batch = 2

# seed for random number generator
rand_seed = 48

# Post-select the configurations with the correct Hamming weight on each half
bitstring_matrix_postsel, probs_arr_postsel = (
postselect_by_hamming_right_and_left(
bitstring_matrix_full,
probs_arr_full,
hamming_right=num_elec_a,
hamming_left=num_elec_b,
)
)

# Generate the batches
batches = subsample(
bitstring_matrix_postsel,
probs_arr_postsel,
samples_per_batch,
n_batches,
rand_seed=rand_seed,
)

print(batches[0])
[[False False False True False False True False]
[False True False False True False False False]]

Obtiens la représentation décimale des chaînes de bits de spin up et de spin down utilisées par le solveur d'états propres.

Le premier élément du tuple correspond à la représentation décimale des configurations de spin up, tandis que le second élément du tuple correspond à la représentation décimale des configurations de spin down :

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)
(array([1, 2, 4, 8], dtype=int64), array([1, 2, 4, 8], dtype=int64))

Note que, bien que le nombre d'échantillons par lot soit de deux et que les chaînes de bits échantillonnées soient : 0001001000010010 et 0100100001001000, quatre configurations électroniques sont générées par espèce de spin. Dans ce cas, l'ensemble des configurations uniques polarisées en spin est donné par :

U={0001,0010,0100,1000}\mathcal{U} = \{ 0001, 0010, 0100, 1000 \}

dont la représentation décimale en base 10 est

U10={1,2,4,8}\mathcal{U}_{10} = \{ 1, 2, 4, 8 \}

Base du sous-espace :

Le solveur d'états propres prend toutes les paires possibles de chaînes de bits de spin up et de spin down pour construire la base B\mathcal{B} du sous-espace :

  • Élément 1 : ∣00010001⟩|00010001\rangle

  • Élément 2 : ∣00010010⟩|00010010\rangle

  • Élément 3 : ∣00010100⟩|00010100\rangle

  • Élément 4 : ∣00011000⟩|00011000\rangle

  • Élément 5 : ∣00100001⟩|00100001\rangle

  • Élément 6 : ∣00100010⟩|00100010\rangle

  • Élément 7 : ∣00100100⟩|00100100\rangle

  • Élément 8 : ∣00101000⟩|00101000\rangle

  • Élément 9 : ∣01000001⟩|01000001\rangle

  • Élément 10 : ∣01000010⟩|01000010\rangle

  • Élément 11 : ∣01000100⟩|01000100\rangle

  • Élément 12 : ∣01001000⟩|01001000\rangle

  • Élément 13 : ∣10000001⟩|10000001\rangle

  • Élément 14 : ∣10000010⟩|10000010\rangle

  • Élément 15 : ∣10000100⟩|10000100\rangle

  • Élément 16 : ∣10001000⟩|10001000\rangle

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)
+ ">"
)
Basis elements of the subspace:
|00010001>
|00010010>
|00010100>
|00011000>
|00100001>
|00100010>
|00100100>
|00101000>
|01000001>
|01000010>
|01000100>
|01001000>
|10000001>
|10000010>
|10000100>
|10001000>

La dimension du sous-espace est majorée par : 2⋅2 \cdot (samples_per_batch)2^2.

Couches ouvertes​

Cet exemple montre comment les chaînes de bits sont manipulées dans un système (2 électrons, 4 orbitales).

# Specify molecule properties
num_orbitals = 4
num_elec_a = num_elec_b = 1
open_shell = True

Spécifie à la main un dictionnaire de résultats de mesure :

counts_dict = {
"00010010": 1 / 2.0 - 0.01,
"01001000": 1 / 2.0 - 0.01,
"00010001": 0.02,
}

Transforme le dict de comptages en une matrice de chaînes de bits et un tableau de probabilités pour le post-traitement :

# 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)
[[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]

Sous-échantillonne un seul lot de taille deux :

  • n_batches = 1 : nombre de lots de configurations utilisés par les différents appels au solveur d'états propres

  • samples_per_batch = 2 : nombre de configurations uniques à inclure dans chaque lot

n_batches = 1
samples_per_batch = 2

# seed for random number generator
rand_seed = 48

# Post-select the configurations with the correct Hamming weight on each half
bitstring_matrix_postsel, probs_arr_postsel = (
postselect_by_hamming_right_and_left(
bitstring_matrix_full,
probs_arr_full,
hamming_right=num_elec_a,
hamming_left=num_elec_b,
)
)

# Generate the batches
batches = subsample(
bitstring_matrix_postsel,
probs_arr_postsel,
samples_per_batch,
n_batches,
rand_seed=rand_seed,
)

print(batches[0])
[[False False False True False False True False]
[False True False False True False False False]]

Obtiens la représentation décimale des chaînes de bits de spin up et de spin down utilisées par le solveur d'états propres.

Le premier élément du tuple correspond à la représentation décimale des configurations de spin up, tandis que le second élément du tuple correspond à la représentation décimale des configurations de spin down :

ci_strs = bitstring_matrix_to_ci_strs(batches[0], open_shell=open_shell)
print(ci_strs)
(array([2, 8], dtype=int64), array([1, 4], dtype=int64))

Si nous spécifions open_shell = True, nous n'incluons pas toutes les demi-chaînes de bits uniques comme configurations de spin up et de spin down, ce qui donne une base plus petite qu'avec open_shell = False. Base du sous-espace :

Le solveur d'états propres prend toutes les paires possibles de chaînes de bits de spin up et de spin down pour construire la base B\mathcal{B} du sous-espace :

  • Élément 1 : ∣00010010⟩|00010010\rangle

  • Élément 2 : ∣00011000⟩|00011000\rangle

  • Élément 3 : ∣01000010⟩|01000010\rangle

  • Élément 4 : ∣01001000⟩|01001000\rangle

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)
+ ">"
)
Basis elements of the subspace:
|00100001>
|00100100>
|10000001>
|10000100>

La dimension du sous-espace est majorée par : (samples_per_batch)2^2.