Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove discussion of addresses from API and docs and replace with "CI strings" #54

Merged
merged 5 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

110 changes: 81 additions & 29 deletions qiskit_addon_sqd/fermion.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
:toctree: ../stubs/
:nosignatures:

bitstring_matrix_to_sorted_addresses
bitstring_matrix_to_ci_strs
enlarge_batch_from_transitions
flip_orbital_occupancies
solve_fermion
optimize_orbitals
rotate_integrals
bitstring_matrix_to_sorted_addresses
"""

from __future__ import annotations
Expand All @@ -36,6 +37,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 +71,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 +85,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 +114,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 +173,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 +192,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 "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think simply "determinants" is more accurate than "base-10 determinants." They are simply integers, without reference to a "base."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the warning message

"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 +226,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 +302,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 +329,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 +355,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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're just integers, not "base-10" integers.

Copy link
Collaborator Author

@caleb-johnson caleb-johnson Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started thinking that integers can be specified as base-2, which is a pretty natural way to specify these configurations as well. So I thought being explicit wouldn't hurt. I guess saying "integer" in a software context implies base-10

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integers can be specified in base-2, or any other base. That's my point. The base 10 is not special here, nor in a general software context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all base-10 mentions in fermion module

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, on further thought, base 2 is special here, because the integer representation of a determinant is obtained by converting its string representation to an integer, in base 2.


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 +428,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
Loading