Skip to content

Commit

Permalink
Remove discussion of addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
caleb-johnson committed Sep 20, 2024
1 parent 2a7a1ac commit 8d8b522
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 78 deletions.
6 changes: 3 additions & 3 deletions docs/how_tos/choose_subspace_dimension.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
"source": [
"from qiskit_addon_sqd.configuration_recovery import recover_configurations\n",
"from qiskit_addon_sqd.fermion import (\n",
" bitstring_matrix_to_sorted_addresses,\n",
" bitstring_matrix_to_ci_strs,\n",
" flip_orbital_occupancies,\n",
" solve_fermion,\n",
")\n",
Expand Down Expand Up @@ -176,8 +176,8 @@
" int_occs = np.zeros((n_batches, 2 * num_orbitals))\n",
" cs = []\n",
" for j in range(n_batches):\n",
" addresses = bitstring_matrix_to_sorted_addresses(batches[j], open_shell=open_shell)\n",
" int_d[j] = len(addresses[0]) * len(addresses[1])\n",
" ci_strs = bitstring_matrix_to_ci_strs(batches[j], open_shell=open_shell)\n",
" int_d[j] = len(ci_strs[0]) * len(ci_strs[1])\n",
" energy_sci, coeffs_sci, avg_occs, spin = solve_fermion(\n",
" batches[j],\n",
" hcore,\n",
Expand Down
6 changes: 3 additions & 3 deletions docs/how_tos/integrate_dice_solver.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"from qiskit_addon_sqd.configuration_recovery import recover_configurations\n",
"from qiskit_addon_sqd.counts import counts_to_arrays, generate_counts_uniform\n",
"from qiskit_addon_sqd.fermion import (\n",
" bitstring_matrix_to_sorted_addresses,\n",
" bitstring_matrix_to_ci_strs,\n",
" flip_orbital_occupancies,\n",
")\n",
"from qiskit_addon_sqd.subsampling import postselect_and_subsample\n",
Expand Down Expand Up @@ -100,9 +100,9 @@
" int_e = np.zeros(n_batches)\n",
" int_occs = np.zeros((n_batches, 2 * num_orbitals))\n",
" for j in range(n_batches):\n",
" addresses = bitstring_matrix_to_sorted_addresses(batches[j], open_shell=open_shell)\n",
" ci_strs = bitstring_matrix_to_ci_strs(batches[j], open_shell=open_shell)\n",
" energy_sci, wf_mags, avg_occs = solve_dice(\n",
" addresses,\n",
" ci_strs,\n",
" active_space_path,\n",
" os.path.abspath(\".\"),\n",
" spin_sq=spin_sq,\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"This package provides two tools to perform this projection:\n",
"\n",
"- ``qiskit_addon_sqd.qubit.matrix_elements_from_pauli()``: is a lower-level function\n",
"that returns the non-zero matrix elements of a Pauli string and the corresponding addresses\n",
"that returns the non-zero matrix elements of a Pauli string and the corresponding indices\n",
"of the non-zero elements.\n",
"\n",
"- ``qiskit_addon_sqd.qubit.project_operator_to_subspace()``: is a higher-level function that \n",
Expand Down Expand Up @@ -92,7 +92,7 @@
"id": "5707b93c",
"metadata": {},
"source": [
"### First method: nonzero matrix elements and addresses."
"### First method: nonzero matrix elements and indices."
]
},
{
Expand Down Expand Up @@ -142,11 +142,9 @@
"operator_from_matrix_elements = coo_matrix((d, d), dtype=\"complex128\")\n",
"\n",
"for pauli in hamiltonian.paulis:\n",
" matrix_elements, row_addresses, col_addresses = matrix_elements_from_pauli(\n",
" bitstring_matrix, pauli\n",
" )\n",
" matrix_elements, row_indices, col_indices = matrix_elements_from_pauli(bitstring_matrix, pauli)\n",
" operator_from_matrix_elements += coo_matrix(\n",
" (matrix_elements, (row_addresses, col_addresses)), (d, d)\n",
" (matrix_elements, (row_indices, col_indices)), (d, d)\n",
" )"
]
},
Expand Down
38 changes: 18 additions & 20 deletions docs/how_tos/select_open_closed_shell.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@
}
],
"source": [
"from qiskit_addon_sqd.fermion import bitstring_matrix_to_sorted_addresses\n",
"from qiskit_addon_sqd.fermion import bitstring_matrix_to_ci_strs\n",
"\n",
"addresses = bitstring_matrix_to_sorted_addresses(batches[0], open_shell=open_shell)\n",
"print(addresses)"
"ci_strs = bitstring_matrix_to_ci_strs(batches[0], open_shell=open_shell)\n",
"print(ci_strs)"
]
},
{
Expand Down Expand Up @@ -277,15 +277,14 @@
}
],
"source": [
"addresses_up = addresses[0]\n",
"addresses_dn = addresses[1]\n",
"ci_strs_up, ci_strs_dn = ci_strs\n",
"\n",
"print(\"Basis elements of the subspace:\")\n",
"\n",
"for address_up in addresses_up:\n",
" for address_dn in addresses_dn:\n",
"for ci_str_up in ci_strs_up:\n",
" for ci_str_dn in ci_strs_dn:\n",
" format_name = \"{0:0\" + str(num_orbitals) + \"b}\"\n",
" print(\"|\" + format_name.format(address_up) + format_name.format(address_dn) + \">\")"
" print(\"|\" + format_name.format(ci_str_up) + format_name.format(ci_str_dn) + \">\")"
]
},
{
Expand Down Expand Up @@ -436,13 +435,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"(array([1, 4], dtype=int64), array([2, 8], dtype=int64))\n"
"(array([2, 8], dtype=int64), array([1, 4], dtype=int64))\n"
]
}
],
"source": [
"addresses = bitstring_matrix_to_sorted_addresses(batches[0], open_shell=open_shell)\n",
"print(addresses)"
"ci_strs = bitstring_matrix_to_ci_strs(batches[0], open_shell=open_shell)\n",
"print(ci_strs)"
]
},
{
Expand Down Expand Up @@ -484,23 +483,22 @@
"output_type": "stream",
"text": [
"Basis elements of the subspace:\n",
"|00010010>\n",
"|00011000>\n",
"|01000010>\n",
"|01001000>\n"
"|00100001>\n",
"|00100100>\n",
"|10000001>\n",
"|10000100>\n"
]
}
],
"source": [
"addresses_up = addresses[0]\n",
"addresses_dn = addresses[1]\n",
"ci_strs_up, ci_strs_dn = ci_strs\n",
"\n",
"print(\"Basis elements of the subspace:\")\n",
"\n",
"for address_up in addresses_up:\n",
" for address_dn in addresses_dn:\n",
"for ci_str_up in ci_strs_up:\n",
" for ci_str_dn in ci_strs_dn:\n",
" format_name = \"{0:0\" + str(num_orbitals) + \"b}\"\n",
" print(\"|\" + format_name.format(address_up) + format_name.format(address_dn) + \">\")"
" print(\"|\" + format_name.format(ci_str_up) + format_name.format(ci_str_dn) + \">\")"
]
},
{
Expand Down
28 changes: 13 additions & 15 deletions docs/how_tos/use_oo_to_optimize_hamiltonian_basis.ipynb

Large diffs are not rendered by default.

109 changes: 80 additions & 29 deletions qiskit_addon_sqd/fermion.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
:toctree: ../stubs/
:nosignatures:
bitstring_matrix_to_sorted_addresses
bitstring_matrix_to_ci_strs
enlarge_batch_from_transitions
flip_orbital_occupancies
solve_fermion
Expand All @@ -36,6 +36,7 @@
from jax import numpy as jnp
from jax.scipy.linalg import expm
from pyscf import fci
from qiskit.utils import deprecate_func
from scipy import linalg as LA

config.update("jax_enable_x64", True) # To deal with large integers
Expand Down Expand Up @@ -69,7 +70,7 @@ def solve_fermion(
hcore: Core Hamiltonian matrix representing single-electron integrals
eri: Electronic repulsion integrals representing two-electron integrals
open_shell: A flag specifying whether configurations from the left and right
halves of the bitstrings should be kept separate. If ``False``, addresses
halves of the bitstrings should be kept separate. If ``False``, CI strings
from the left and right halves of the bitstrings are combined into a single
set of unique configurations and used for both the alpha and beta subspaces.
spin_sq: Target value for the total spin squared for the ground state.
Expand All @@ -83,25 +84,22 @@ def solve_fermion(
- SCI coefficients
- Average orbital occupancy
- Expectation value of spin-squared
Raises:
ValueError: The input determinant ``addresses`` must be non-empty, sorted arrays of integers.
"""
if isinstance(bitstring_matrix, tuple):
warnings.warn(
"Passing the input determinants as integers is deprecated. Users should instead pass a bitstring matrix defining the subspace.",
DeprecationWarning,
stacklevel=2,
)
addresses = bitstring_matrix
ci_strs = bitstring_matrix
else:
# This will become the default code path after the deprecation period.
addresses = bitstring_matrix_to_sorted_addresses(bitstring_matrix, open_shell=open_shell)
addresses = addresses[::-1]
addresses = _check_addresses(addresses)
ci_strs = bitstring_matrix_to_ci_strs(bitstring_matrix, open_shell=open_shell)
ci_strs = ci_strs[::-1]
ci_strs = _check_ci_strs(ci_strs)

num_up = format(addresses[0][0], "b").count("1")
num_dn = format(addresses[1][0], "b").count("1")
num_up = format(ci_strs[0][0], "b").count("1")
num_dn = format(ci_strs[1][0], "b").count("1")

# Number of molecular orbitals
norb = hcore.shape[0]
Expand All @@ -115,7 +113,7 @@ def solve_fermion(
eri,
norb,
(num_up, num_dn),
ci_strs=addresses,
ci_strs=ci_strs,
verbose=verbose,
max_cycle=max_davidson,
)
Expand Down Expand Up @@ -174,7 +172,7 @@ def optimize_orbitals(
to be of shape (# orbitals, # orbitals) before being used as a
similarity transform operator on the orbitals. Thus ``len(k_flat)=# orbitals**2``.
open_shell: A flag specifying whether configurations from the left and right
halves of the bitstrings should be kept separate. If ``False``, addresses
halves of the bitstrings should be kept separate. If ``False``, CI strings
from the left and right halves of the bitstrings are combined into a single
set of unique configurations and used for both the alpha and beta subspaces.
spin_sq: Target value for the total spin squared for the ground state
Expand All @@ -193,20 +191,20 @@ def optimize_orbitals(
"""
if isinstance(bitstring_matrix, tuple):
warnings.warn(
"Passing a length-2 tuple of sorted addresses to define the subspace is deprecated. Users "
"Passing a length-2 tuple of base-10 determinants to define the subspace is deprecated. Users "
"should instead pass in the bitstring matrix defining the subspace.",
DeprecationWarning,
stacklevel=2,
)
addresses = bitstring_matrix
ci_strs = bitstring_matrix
else:
# Flip the output so the alpha addresses are on the left with [::-1]
addresses = bitstring_matrix_to_sorted_addresses(bitstring_matrix, open_shell=open_shell)
addresses = addresses[::-1]
addresses = _check_addresses(addresses)
# Flip the output so the alpha CI strs are on the left with [::-1]
ci_strs = bitstring_matrix_to_ci_strs(bitstring_matrix, open_shell=open_shell)
ci_strs = ci_strs[::-1]
ci_strs = _check_ci_strs(ci_strs)

num_up = format(addresses[0][0], "b").count("1")
num_dn = format(addresses[1][0], "b").count("1")
num_up = format(ci_strs[0][0], "b").count("1")
num_dn = format(ci_strs[1][0], "b").count("1")

# TODO: Need metadata showing the optimization history
## hcore and eri in physicist ordering
Expand All @@ -227,7 +225,7 @@ def optimize_orbitals(
eri_rot_chem,
num_orbitals,
(num_up, num_dn),
ci_strs=addresses,
ci_strs=ci_strs,
max_cycle=max_davidson,
)

Expand Down Expand Up @@ -303,6 +301,12 @@ def flip_orbital_occupancies(occupancies: np.ndarray) -> np.ndarray:
return occ_out


@deprecate_func(
removal_timeline="no sooner than qiskit-addon-sqd 0.8.0",
since="0.6.0",
package_name="qiskit-addon-sqd",
additional_msg="Use the bitstring_matrix_to_ci_strs function.",
)
def bitstring_matrix_to_sorted_addresses(
bitstring_matrix: np.ndarray, open_shell: bool = False
) -> tuple[np.ndarray, np.ndarray]:
Expand All @@ -324,8 +328,8 @@ def bitstring_matrix_to_sorted_addresses(
and right bitstrings.
Returns:
A length-2 tuple of sorted, unique base-10 determinant addresses representing the left
and right halves of the bitstrings, respectively.
A length-2 tuple of sorted, unique base-10 determinant addresses representing the
left (spin-down) and right (spin-up) halves of the bitstrings, respectively.
"""
num_orbitals = bitstring_matrix.shape[1] // 2
num_configs = bitstring_matrix.shape[0]
Expand All @@ -350,6 +354,53 @@ def bitstring_matrix_to_sorted_addresses(
return addresses_left, addresses_right


def bitstring_matrix_to_ci_strs(
bitstring_matrix: np.ndarray, open_shell: bool = False
) -> tuple[np.ndarray, np.ndarray]:
"""
Convert bitstrings (rows) in a ``bitstring_matrix`` into base-10 integer representations of determinants.
This function separates each bitstring in ``bitstring_matrix`` in half, flips the
bits and translates them into integer representations, and finally appends them to
their respective (spin-up or spin-down) lists. Those lists are sorted and output
from this function.
Args:
bitstring_matrix: A 2D array of ``bool`` representations of bit
values such that each row represents a single bitstring
open_shell: A flag specifying whether unique configurations from the left and right
halves of the bitstrings should be kept separate. If ``False``, configurations
from the left and right halves of the bitstrings are combined into a single
set of unique configurations. That combined set will be returned for both the left
and right bitstrings.
Returns:
A length-2 tuple of sorted, unique base-10 determinants representing the
right (spin-up) and left (spin-down) halves of the bitstrings, respectively.
"""
num_orbitals = bitstring_matrix.shape[1] // 2
num_configs = bitstring_matrix.shape[0]

ci_str_left = np.zeros(num_configs)
ci_str_right = np.zeros(num_configs)
bts_matrix_left = bitstring_matrix[:, :num_orbitals]
bts_matrix_right = bitstring_matrix[:, num_orbitals:]

# For performance, we accumulate the left and right CI strings together, column-wise,
# across the two halves of the input bitstring matrix.
for i in range(num_orbitals):
ci_str_left[:] += bts_matrix_left[:, i] * 2 ** (num_orbitals - 1 - i)
ci_str_right[:] += bts_matrix_right[:, i] * 2 ** (num_orbitals - 1 - i)

ci_strs_right = np.unique(ci_str_right.astype("longlong"))
ci_strs_left = np.unique(ci_str_left.astype("longlong"))

if not open_shell:
ci_strs_left = ci_strs_right = np.union1d(ci_strs_left, ci_strs_right)

return ci_strs_right, ci_strs_left


def enlarge_batch_from_transitions(
bitstring_matrix: np.ndarray, transition_operators: np.ndarray
) -> np.ndarray:
Expand All @@ -376,25 +427,25 @@ def enlarge_batch_from_transitions(
return np.array(bitstring_matrix_augmented)


def _check_addresses(
addresses: tuple[np.ndarray, np.ndarray],
def _check_ci_strs(
ci_strs: tuple[np.ndarray, np.ndarray],
) -> tuple[np.ndarray, np.ndarray]:
"""Make sure the hamming weight is consistent in all determinants."""
addr_up, addr_dn = addresses
addr_up, addr_dn = ci_strs
addr_up_ham = format(addr_up[0], "b").count("1")
for i, addr in enumerate(addr_up):
ham = format(addr, "b").count("1")
if ham != addr_up_ham:
raise ValueError(
f"Spin-up address in index 0 has hamming weight {addr_up_ham}, but address in "
f"Spin-up CI string in index 0 has hamming weight {addr_up_ham}, but CI string in "
f"index {i} has hamming weight {ham}."
)
addr_dn_ham = format(addr_dn[0], "b").count("1")
for i, addr in enumerate(addr_dn):
ham = format(addr, "b").count("1")
if ham != addr_dn_ham:
raise ValueError(
f"Spin-down address in index 0 has hamming weight {addr_dn_ham}, but address in "
f"Spin-down CI string in index 0 has hamming weight {addr_dn_ham}, but CI string in "
f"index {i} has hamming weight {ham}."
)

Expand Down
4 changes: 2 additions & 2 deletions qiskit_addon_sqd/qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def solve_qubit(
if bitstring_matrix.shape[1] > 63:
raise ValueError("Bitstrings (rows) in bitstring_matrix must have length < 64.")

# Projection requires the bitstring matrix be sorted in accordance with its address representation
# Projection requires the bitstring matrix be sorted in accordance with its base-10 representation
bitstring_matrix = sort_and_remove_duplicates(bitstring_matrix)

# Get a sparse representation of the projected operator
Expand Down Expand Up @@ -171,7 +171,7 @@ def matrix_elements_from_pauli(
.. note::
The bitstrings in the ``bitstring_matrix`` must be sorted and unique according
to their base-10 address representation. Otherwise the projection will return wrong
to their base-10 CI string representation. Otherwise the projection will return wrong
results. This function does not explicitly check for uniqueness and order because
this can be rather time consuming. See :func:`qiskit_addon_sqd.qubit.sort_and_remove_duplicates`
for a simple way to ensure your bitstring matrix is well-formatted.
Expand Down

0 comments on commit 8d8b522

Please sign in to comment.