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

Add a solve_fermion function for compatibility with SQD #27

Merged
merged 14 commits into from
Sep 25, 2024
Merged
4 changes: 3 additions & 1 deletion qiskit_addon_dice_solver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
:toctree: ../stubs/
:nosignatures:

solve_fermion
solve_dice
"""

from .dice_solver import solve_dice
from .dice_solver import solve_fermion, solve_dice

__all__ = [
"solve_fermion",
"solve_dice",
]
119 changes: 119 additions & 0 deletions qiskit_addon_dice_solver/dice_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import numpy as np
from pyscf import tools
from qiskit_addon_sqd.fermion import bitstring_matrix_to_ci_strs

# Ensure the runtime linker can find the local boost binaries at runtime
DICE_BIN = os.path.join(os.path.abspath(os.path.dirname(__file__)), "bin")
Expand All @@ -46,6 +47,124 @@ def __init__(self, command, returncode, log_path):
super().__init__(message)


def solve_fermion(
bitstring_matrix: np.ndarray,
/,
hcore: np.ndarray,
eri: np.ndarray,
*,
mpirun_options: Sequence[str] | str | None = None,
working_dir: str | Path | None = None,
clean_working_dir: bool = True,
) -> tuple[float, np.ndarray, tuple[np.ndarray, np.ndarray]]:
"""
Approximate the ground state of a molecular Hamiltonian given a bitstring matrix defining the Hilbert subspace.

This solver is designed for compatibility with `qiskit-addon-sqd <https://qiskit.github.io/qiskit-addon-sqd/>`_ workflows.

In order to leverage the multi-processing nature of this tool, the user must specify
the CPU resources to use via the `mpirun_options` argument.

For example, to use 8 CPU slots in parallel in quiet mode:

.. code-block:: python

# Run 8 parallel slots in quiet mode
mpirun_opts = "-quiet -n 8"
# OR
mpirun_opts = ["-quiet", "-n", "8"]

energy, sci_coeffs, avg_occs = solve_fermion(..., mpirun_options=mpirun_opts)

For more information on the ``mpirun`` command line options, refer to the `man page <https://www.open-mpi.org/doc/current/man1/mpirun.1.php>`_.

.. note::

Only closed-shell systems are supported. The particle number for both
spin-up and spin-down determinants is expected to be equal.

.. note::

Determinant are interpreted by the ``Dice`` command line application as 5-byte unsigned integers; therefore, only systems
of ``40`` or fewer orbitals are supported.

Args:
bitstring_matrix: A set of configurations defining the subspace onto which the Hamiltonian
will be projected and diagonalized. This is a 2D array of ``bool`` representations of bit
values such that each row represents a single bitstring. The spin-up configurations
should be specified by column indices in range ``(N, N/2]``, and the spin-down
configurations should be specified by column indices in range ``(N/2, 0]``, where ``N``
is the number of qubits.
hcore: Core Hamiltonian matrix representing single-electron integrals
eri: Electronic repulsion integrals representing two-electron integrals
mpirun_options: Options controlling the CPU resource allocation for the ``Dice`` command line application.
These command-line options will be passed directly to the ``mpirun`` command line application during
invocation of ``Dice``. These may be formatted as a ``Sequence`` of strings or a single string. If a ``Sequence``,
the elements will be combined into a single, space-delimited string and passed to
``mpirun``. If the input is a single string, it will be passed to ``mpirun`` as-is. If no
``mpirun_options`` are provided by the user, ``Dice`` will run on a single MPI slot. For more
information on the ``mpirun`` command line options, refer to the `man page <https://www.open-mpi.org/doc/current/man1/mpirun.1.php>`_.
working_dir: An absolute path to a directory for storing temporary files. If
not provided, the system temporary files directory will be used.
clean_working_dir: Whether to delete intermediate files generated by the ``Dice`` command line application.
These files will be stored in a directory created inside ``working_dir``.
If ``False``, then this directory will be preserved.

Returns:
- Minimum energy from SCI calculation
- SCI coefficients
- Average orbital occupancy
"""
# Hard-code target S^2 until supported
spin_sq = 0.0

# Convert bitstring matrix to integer determinants for spin-up/down
ci_strs = bitstring_matrix_to_ci_strs(bitstring_matrix)
num_configurations = len(ci_strs[0])
num_up = bin(ci_strs[0][0])[2:].count("1")
num_dn = bin(ci_strs[1][0])[2:].count("1")
caleb-johnson marked this conversation as resolved.
Show resolved Hide resolved

# Set up the working directory
working_dir = working_dir or tempfile.gettempdir()
intermediate_dir = Path(tempfile.mkdtemp(prefix="dice_cli_files_", dir=working_dir))

# Write the integrals out as an FCI dump for Dice command line app
active_space_path = os.path.join(intermediate_dir, "fcidump.txt")
caleb-johnson marked this conversation as resolved.
Show resolved Hide resolved
num_orbitals = hcore.shape[0]
tools.fcidump.from_integrals(
active_space_path, hcore, eri, num_orbitals, (num_up + num_dn)
)

_write_input_files(
ci_strs,
active_space_path,
num_up,
num_dn,
num_configurations,
intermediate_dir,
spin_sq,
1,
caleb-johnson marked this conversation as resolved.
Show resolved Hide resolved
kevinsung marked this conversation as resolved.
Show resolved Hide resolved
)

# Navigate to working dir and call Dice
_call_dice(intermediate_dir, mpirun_options)

# Read outputs and convert outputs
e_dice, sci_coefficients, avg_occupancies = _read_dice_outputs(
intermediate_dir, num_orbitals
)

# Clean up the working directory of intermediate files, if desired
if clean_working_dir:
shutil.rmtree(intermediate_dir)

return (
e_dice,
sci_coefficients,
(avg_occupancies[:num_orbitals], avg_occupancies[num_orbitals:]),
)


def solve_dice(
addresses: tuple[Sequence[int], Sequence[int]],
active_space_path: str | Path,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
packages=find_packages(),
package_data={"dice_solver": ["bin/Dice", "bin/*.so*"]},
include_package_data=True,
install_requires=["numpy", "pyscf"],
install_requires=["numpy", "pyscf", "qiskit-addon-sqd>=0.6"],
extras_require={
"dev": ["tox>=4.0", "pytest>=8.0"],
"docs": [
Expand Down